Skip to content

Commit

Permalink
#479 A node marking action is implemented.
Browse files Browse the repository at this point in the history
The user can toggle the marking on any node. A marked node will have a white sphere hovering above it.
  • Loading branch information
Li-ID committed Oct 20, 2022
1 parent e51ae75 commit e12c2b2
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 3 deletions.
4 changes: 4 additions & 0 deletions Assets/SEE/Controls/Actions/ActionStateType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ public class ActionStateType
new ActionStateType(9, "Draw", "Draw a line",
Color.magenta.Darker(), "Materials/ModernUIPack/Pencil",
DrawAction.CreateReversibleAction);
public static ActionStateType Mark { get; } =
new ActionStateType(10, "Mark", "Mark a Node with a hovering sphere",
Color.magenta.Darker(), "Materials/40+ Simple Icons - Free/Purpose_Simple_Icons_UI",
MarkAction.CreateReversibleAction);
#endregion

/// <summary>
Expand Down
165 changes: 165 additions & 0 deletions Assets/SEE/Controls/Actions/MarkAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using System.Collections.Generic;
using SEE.Game;
using SEE.GO;
using SEE.Net;
using SEE.Utils;
using UnityEngine;

namespace SEE.Controls.Actions
{
/// <summary>
/// Action to toggle a marking for a node.
/// The mark is a white sphere, hovering over the node.
/// </summary>
internal class MarkAction : AbstractPlayerAction
{
/// <summary>
/// If the user clicks with the mouse hitting a game object representing a node,
/// this node gets a mark.
/// <see cref="ReversibleAction.Update"/>.
/// </summary>
/// <returns>true if completed</returns>
public override bool Update()
{
bool result = false;

// FIXME: Needs adaptation for VR where no mouse is available.
if (Input.GetMouseButtonDown(0)
&& Raycasting.RaycastGraphElement(out RaycastHit raycastHit, out GraphElementRef _) == HitGraphElement.Node)
{
/// the hit object is the Node which gets a mark as a child.
GameObject parent = raycastHit.collider.gameObject;
/// the position of the Node is used for the mark.
Vector3 position = parent.transform.position;
/// the scale of the Node is used to make the Sphere fit into the ground space of the node.
Vector3 scale = FindSize(parent);
GameNodeMarker.Mark(parent, position: position, worldSpaceScale: scale);
memento = new Memento(parent, position: position, scale: scale);
new MarkNetAction(parentID: memento.Parent.name, memento.Position, memento.Scale).Execute();
result = true;
currentState = ReversibleAction.Progress.Completed;
}
return result;
}

/// <summary>
/// Memento capturing the data necessary to re-do this action.
/// </summary>
private Memento memento;

/// <summary>
/// The information we need to re-add a mark whose addition was undone.
/// </summary>
private struct Memento
{
/// <summary>
/// The node which is marked.
/// </summary>
public readonly GameObject Parent;
/// <summary>
/// The position of the mark in world space.
/// </summary>
public readonly Vector3 Position;
/// <summary>
/// The scale of the new mark in world space.
/// </summary>
public readonly Vector3 Scale;

/// <summary>
/// Constructor setting the information necessary to re-do this action.
/// </summary>
/// <param name="parent">The node which is marked.</param>
/// <param name="position">position of the mark in world space.</param>
/// <param name="scale">scale of the mark in world space.</param>
public Memento(GameObject parent, Vector3 position, Vector3 scale)
{
Parent = parent;
Position = position;
Scale = scale;
}
}

/// <summary>
/// Returns a scale of a cube that fits into the ground area of <paramref name="parent"/>.
/// </summary>
/// <param name="parent">parent in which ground area to fit the cube.</param>
/// <returns>the scale of a cube that fits into the ground area of <paramref name="parent"/>.</returns>
private static Vector3 FindSize(GameObject parent)
{
Vector3 result = parent.transform.lossyScale;
/// The ground area of the result must be a square.
if (result.x > result.z)
{
result.x = result.z;
}
else
{
result.z = result.x;
}
/// make the square a cube.
result.y = result.z;
return result;
}

/// <summary>
/// Undoes this MarkAction.
/// </summary>
public override void Undo()
{
base.Undo();

GameNodeMarker.Mark(memento.Parent, position: memento.Position, worldSpaceScale: memento.Scale);
new MarkNetAction(parentID: memento.Parent.name, memento.Position, memento.Scale).Execute();

}

/// <summary>
/// Redoes this MarkAction.
/// </summary>
public override void Redo()
{
base.Redo();
GameNodeMarker.Mark(memento.Parent, position: memento.Position, worldSpaceScale: memento.Scale);
new MarkNetAction(parentID: memento.Parent.name, memento.Position, memento.Scale).Execute();
}

/// <summary>
/// Returns a new instance of <see cref="MarkAction"/>.
/// </summary
/// <returns>new instance</returns>
public static ReversibleAction CreateReversibleAction()
{
return new MarkAction();
}

/// <summary>
/// Returns a new instance of <see cref="MarkAction"/>.
/// </summary>
/// <returns>new instance</returns>
public override ReversibleAction NewInstance()
{
return CreateReversibleAction();
}

/// <summary>
/// Returns the <see cref="ActionStateType"/> of this action.
/// </summary>
/// <returns><see cref="ActionStateType.NewNode"/></returns>
public override ActionStateType GetActionStateType()
{
return ActionStateType.Mark;
}

/// <summary>
/// Returns all IDs of gameObjects manipulated by this action.
/// </summary>
/// <returns>all IDs of gameObjects manipulated by this action</returns>
public override HashSet<string> GetChangedObjects()
{
return new HashSet<string>
{
memento.Parent.name
};
}
}
}
11 changes: 11 additions & 0 deletions Assets/SEE/Controls/Actions/MarkAction.cs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a8cbb4ba5a85514e95f72f80e62bf06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
35 changes: 32 additions & 3 deletions Assets/SEE/DataModel/DG/Node.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace SEE.DataModel.DG
{
Expand Down Expand Up @@ -163,10 +164,38 @@ private set
}

/// <summary>
/// True iff node has no parent.
/// Is true, if the node is marked
/// </summary>
/// <returns>true iff node is a root node</returns>
public bool IsRoot()
private bool isMarked = false;

/// <summary>
/// Is true, if the node is marked
/// </summary>
public bool IsMarked
{
get => isMarked;
set => isMarked = value;
}

/// <summary>
/// The GameObject with which the node was marked
/// </summary>
private GameObject marking;

/// <summary>
/// The GameObject with which the node was marked
/// </summary>
public GameObject Marking
{
get => marking;
set => marking = value;
}

/// <summary>
/// True iff node has no parent.
/// </summary>
/// <returns>true iff node is a root node</returns>
public bool IsRoot()
{
return Parent == null;
}
Expand Down
70 changes: 70 additions & 0 deletions Assets/SEE/Game/GameNodeMarker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using SEE.DataModel.DG;
using SEE.Game.City;
using SEE.GO;
using UnityEngine;

namespace SEE.Game
{
/// <summary>
/// Toggle a marking for a node.
/// The mark is a white sphere, hovering over the node.
/// </summary>
public static class GameNodeMarker
{

/// <summary>
/// Toggles the marking of a node <paramref name="parent"/> at the
/// given <paramref name="position"/> with the given <paramref name="worldSpaceScale"/>.
/// The marking is a new GameObject, added as a child of the Node.
/// The GameObject is represented by a white hovering sphere above the node which is marked.
///
/// Precondition: <paramref name="parent"/> must have a valid node reference.
/// </summary>
/// <param name="parent">node which marking will be toggled.</param>
/// <param name="position">the position in world space for the center point of the mark.</param>
/// <param name="worldSpaceScale">the scale in world space of the mark.</param>
/// <exception cref="Exception">thrown if <paramref name="parent"/> is not contained in a code city.</exception>
public static void Mark(GameObject parent, Vector3 position, Vector3 worldSpaceScale)
{
SEECity city = parent.ContainingCity() as SEECity;
if (city != null)
{
/// Gets the actual GameObject which represents the node.
Node parentNode = parent.GetNode();
/// Mark if the node is not marked.
if (!parentNode.IsMarked) {
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.localScale = worldSpaceScale;
/// The sphere is located at the golden ratio with the smaller part being the distance to the sphere, and the bigger part being the sphere.
/// Mathematical explanation of the position:
/// parent.transform.position.y = middle of node.
/// parent.transform.lossyScale.y/2 = half of the height of the node.
/// worldSpaceScale.y*0.5 = half of the height of the sphere.
/// because the position of the sphere describes the middle of itself, we add all 3 values together, to put the mark ontop of the node.
/// We finally add worldSpaceScale.y*0.38196601125F to get the smaller part of the golden ratio in relation to the size of the sphere as distance to the node.
/// Because "worldSpaceScale.y*0.5 + worldSpaceScale.y*0.38196601125F" is the same result as "worldSpaceScale.y*0.88196601125F", we simplify it.
sphere.transform.position = new Vector3(position.x, parent.transform.position.y + parent.transform.lossyScale.y/2 + worldSpaceScale.y* 0.88196601125F, position.z);
sphere.transform.SetParent(parent.transform);
Portal.SetPortal(city.gameObject, gameObject: sphere);
parentNode.IsMarked = true;
parentNode.Marking = sphere;
return;
}
/// unmark if the node is marked.
else
{
parentNode.IsMarked = false;
GameObject marking = parentNode.Marking;
GameObject.Destroy(marking);
return;
}

}
else
{
throw new Exception($"The node {parent.name} is not contained in a code city.");
}
}
}
}
11 changes: 11 additions & 0 deletions Assets/SEE/Game/GameNodeMarker.cs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9159bf7682c3e9d47ae67bd910089581
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
72 changes: 72 additions & 0 deletions Assets/SEE/Net/Actions/MarkNetAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using SEE.Game;
using UnityEngine;

namespace SEE.Net
{
/// <summary>
/// This class is responsible for marking a node via network from one client to all others and
/// to the server.
/// </summary>
public class MarkNetAction : AbstractNetAction
{
// Note: All attributes are made public so that they will be serialized
// for the network transfer.

/// <summary>
/// The node which is marked.
/// </summary>
public string ParentID;

/// <summary>
/// The position of the mark in world space.
/// </summary>
public Vector3 Position;

/// <summary>
/// The scale of the new mark in world space.
/// </summary>
public Vector3 Scale;

/// <summary>
/// Constructor.
/// </summary>
/// <param name="parentID">unique ID of the Node which will get marked.</param>
/// <param name="position">the position for the mark.</param>
/// <param name="scale">the scale of the mark in world space.</param>
public MarkNetAction
(string parentID,
Vector3 position,
Vector3 scale)
: base()
{
this.ParentID = parentID;
this.Position = position;
this.Scale = scale;
}

/// <summary>
/// Things to execute on the server (none for this class). Necessary because it is abstract
/// in the superclass.
/// </summary>
protected override void ExecuteOnServer()
{
// Intentionally left blank.
}

/// <summary>
/// Toggle the marking of the node on each client.
/// </summary>
protected override void ExecuteOnClient()
{
if (!IsRequester())
{
GameObject parent = GraphElementIDMap.Find(ParentID);
if (parent == null)
{
throw new System.Exception($"There is no node with the ID {ParentID}.");
}
GameNodeMarker.Mark(parent, Position, Scale);
}
}
}
}
Loading

0 comments on commit e12c2b2

Please sign in to comment.