Skip to content

Commit

Permalink
#479 mark node feature
Browse files Browse the repository at this point in the history
all in one: create MarkAction.cs, MarkNetAction.cs and GameNodeMarker.cs, also add the mark action in ActionStateType.cs
  • Loading branch information
SchrammJonas committed Oct 27, 2022
1 parent e51ae75 commit 66269dc
Show file tree
Hide file tree
Showing 36 changed files with 11,738 additions and 1,786 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",
Color.magenta, "Materials/ModernUIPack/Pencil",
MarkAction.CreateReversibleAction);
#endregion

/// <summary>
Expand Down
140 changes: 140 additions & 0 deletions Assets/SEE/Controls/Actions/MarkAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
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 visually mark/unmark a node as selected via a sphere floating above it.
/// </summary>
internal class MarkAction : AbstractPlayerAction
{
/// <summary>
/// If the user clicks with the mouse hitting a game object representing a graph node,
/// the node's marking status is toggled. Being marked means a sphere is floating above the node.
/// <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 that is either selected or unselected
GameObject targetNode = raycastHit.collider.gameObject;

GameObject markerSphere = GameNodeMarker.TryMarking(targetNode);

memento = new Memento(targetNode);

// propagate the MarkAction to other clients
new MarkNetAction(markerSphere).Execute();

// MarkAction completed successfully
result = true;

currentState = ReversibleAction.Progress.Completed;

}
return result;
}

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

/// <summary>
/// The information we need to re-add a marker whose addition was undone.
/// </summary>
private struct Memento
{
/// <summary>
/// The node marked by the marker.
/// </summary>
public readonly GameObject MarkedNode;

/// <summary>
/// The node ID for the added node. It must be kept to re-use the
/// original name of the node in Redo().
/// </summary>
public string MarkerID;

/// <summary>
/// Constructor setting the information necessary to re-do this action.
/// </summary>
/// <param name="parent">the node targeted by our MarkAction</param>
public Memento(GameObject markedNode)
{
MarkedNode = markedNode;
MarkerID = markedNode.ID();
}
}

/// <summary>
/// Undoes this MarkAction.
/// </summary>
public override void Undo()
{
base.Undo();
GameNodeMarker.TryMarking(memento.MarkedNode);
new MarkNetAction(memento.MarkedNode).Execute();
}

/// <summary>
/// Redoes this MarkAction.
/// </summary>
public override void Redo()
{
base.Redo();
GameNodeMarker.TryMarking(memento.MarkedNode);
new MarkNetAction(memento.MarkedNode).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.MarkedNode.name,
memento.MarkerID
};
}
}
}
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: c6e496ba734f3a9449f72dc8f07fe2da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
100 changes: 100 additions & 0 deletions Assets/SEE/Game/GameNodeMarker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using SEE.GO;
using SEE.Utils;
using UnityEngine;

namespace SEE.Game
{
/// <summary>
/// Creates new game objects representing graph nodes or deleting these again,
/// respectively.
/// </summary>
public static class GameNodeMarker
{

/// <summary>
/// Check the transfered target node whether it was been marked already, and toggle it to the other state.
/// <param name="targetNode">the node that was targeted for marking.</param>
/// </summary>
/// <returns>new instance</returns>
public static GameObject TryMarking(GameObject targetNode)
{

// save the sphere that was used as a marker
GameObject markerSphere = null;

// iterate over all children of the targeted node
foreach (Transform child in targetNode.transform)
{
// exit the loop if a (/the) marker was found
if (child.name == "MarkerSphere" + targetNode.name)
{
markerSphere = child.gameObject;
break;
}
}

if (markerSphere != null)
{
// delete existing marker sphere
Destroyer.DestroyGameObject(markerSphere);
return null;
}
else
{
// create a new marker sphere because there is none currently
GameObject newMarkerSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);

// the scale of the marker sphere depends on the scale of the marked node
newMarkerSphere.transform.localScale = CalculateMarkerSize(targetNode);

// the position of the marker sphere is above the marked node
newMarkerSphere.transform.position = CalculateMarkerPosition(targetNode);

// FIXME: check back with lead devs whether this consitutes desired behaviour
// ensure markers are not blocking us from unmarking or marking other nodes
newMarkerSphere.GetComponent<SphereCollider>().radius = 0;

// FIXME: marker color could be a configuration option
newMarkerSphere.SetColor(Color.green);

// marker spheres can be recognized by their name-prefix "MarkerSphere"
newMarkerSphere.name = "MarkerSphere" + targetNode.name;

// assign the marker sphere to the node it is marking
newMarkerSphere.transform.SetParent(targetNode.transform);

return newMarkerSphere;
}
}

/// <summary>
/// Returns the scale of the marker sphere based on the area of the <paramref name="targetNode"/>.
/// </summary>
/// <param name="targetNode">The marked node relative to which the marker sphere is positioned and scaled.</param>
/// <returns>The scale of the marker sphere.</returns>
private static Vector3 CalculateMarkerSize(GameObject targetNode)
{
// calculate the maximum ground area
float verticalLength = Math.Min(targetNode.transform.lossyScale.x,
targetNode.transform.lossyScale.z);

// return the marker size as a cube
return Vector3.one * verticalLength;
}

/// <summary>
/// Returns the position of the marker sphere based on the position of the <paramref name="targetNode"/>.
/// </summary>
/// <param name="targetNode">The marked node relative to which the marker sphere is positioned and scaled.</param>
/// <returns>The position of the marker sphere.</returns>
private static Vector3 CalculateMarkerPosition(GameObject targetNode)
{
return new Vector3(targetNode.transform.position.x,
targetNode.transform.position.y
+ targetNode.transform.lossyScale.y / 2
+ CalculateMarkerSize(targetNode).y,
targetNode.transform.position.z);
}
}
}
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: b91a45137cc401b4a94beddcdd11df09
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
51 changes: 51 additions & 0 deletions Assets/SEE/Net/Actions/MarkNetAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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 GameObject that is targeted for marking.
/// </summary>
public GameObject TargetNode;

/// <summary>
/// Constructor.
/// </summary>
/// <param name="targetNode">the node that was targeted for marking.</param>
public MarkNetAction
(GameObject targetNode)
: base()
{
this.TargetNode = targetNode;
}

/// <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>
/// Tries marking on each client.
/// </summary>
protected override void ExecuteOnClient()
{
if (!IsRequester())
{
GameObject markerSphere = GameNodeMarker.TryMarking(TargetNode);
}
}
}
}
11 changes: 11 additions & 0 deletions Assets/SEE/Net/Actions/MarkNetAction.cs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24533459853e8e849a8d32b16b21ede0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
Loading

0 comments on commit 66269dc

Please sign in to comment.