Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved AddNode placement (+ additional fixes) #820

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

tinxx
Copy link
Collaborator

@tinxx tinxx commented Jan 15, 2025

This MR improves the placement of new nodes using AddNodeAction and provides some additional fixes.

AddNode placement

Previously, new nodes were placed anywhere with a fixed size of 0.2.
This led to new nodes regularly overlapping with existing nodes and several other shortcomings.

The updated logic improves various key points:

  • New nodes cannot overlap with existing nodes.
    • Nodes are placed within the boundaries of their parents (similar to MoveAction).
    • A notification is shown if placement is not possible.
  • The base size keeps the earlier relative size of 0.2 but is allowed to shrink based on the available space down to a defined minimal size.
  • New nodes have a (configurable) uniform world-space height.
  • The new nodes' base aspect ratio is squarified.

fixes #786

Miscellaneous changes

  • Resize handle position:
    • The resize handles are now placed on top of the resized node. This allows for a better accessibility in tightly packed constellations.
  • Updated shaders:
    • The custom shaders led to a problem where resize handles were displayed over the edges or vice versa independently of the actual position of the fragments. In other words: (parts of) objects that were behind one another were printed in wrong order.
    • A two-pass shader was implemented that writes opaque edge fragments to the depth buffer while keeping ZWrite for semitransparent parts off. This minimizes the display artifacts with the current scenes and does not interfere with TextMeshPro's shaders while fading edges out.
  • OverlapsWithSiblings() now ignores inactive nodes (e.g., nodes that have been deleted but are actually still hanging around invisibly).
  • BitIncrement() and BitDecrement() is used to get the next greater or next smaller float value.
    • This makes it easier and more robust to set values close to a threshold while preventing instant redetection (AddNodeAction, ResizeNodeAction).
  • Bump server dependency versions:
    • Frontend dependencies have been updated to (hopefully) satisfy dependabot.
    • Backend dependencies patch/incremental versions have been bumped. Some additional documentation concerning version checks and updates have been added to the README.
  • Pass server configuration via environment variables (fixes Allow Passing Command-Line Arguments via Environment #758).

Here there are some screenshots visualizing the shader problem:

  1. Initial problem – wrong order on screen:

Screenshot From 2025-01-14 15-38-52
Screenshot From 2025-01-14 15-39-42

  1. Problem using TextMeshPro's shaders in combination with ZWrite on:

Screenshot From 2025-01-14 15-35-43
Opaque edges for reference.

Screenshot From 2025-01-14 15-35-51
Semitransparent edges during fade-out occluding parts of the labels.

tinxx added 8 commits December 2, 2024 10:24
- Position new nodes without sibling overlap.
- X/Z size is determined relative to parent extents and
  actual available space in target region.
- Maximal X/Z size is kept to 20% of parent according to
  previous implementation.
- X/Z size of new nodes is squarified.
- Z height is a fixed world-space configuration value.
- If space does not suffice, placement is prevented and
  a notification is displayed.
Ignore inactive nodes when checking overlap.
Place ResizeNodeAction handles on top of the resized
node. This is especially helpful for nodes with a hight
greater than the original intended architecture nodes
(which virtuially had no height at all).

Previously, it was possible to take the cursor movement
directly via a raycast on a parent node because the
handles were assumed to be flat on the surface.
This assumption is not true when the nodes have a
miscellaneous height and/or when the handles are
positioned on top. A new mechanism translates the 2D
movement of the cursor into the 3D world space.
However, this is not yet compatible with VR controls.
Use BitIncrement() and BitDecrement() in ResizeNodeAction
for setting float values near thresholds while preventing
redetection instead of using a static offset that might not
work in specific edge cases.
The edge shader is modified to handle opaque vs. transparent
fragments in separate passes. Opaque elements write to the
depth buffer (ZWrite) so that the order is preserved. This
fixes the problem of resize handles occluding edges that
should in fact be drawn over the handles.

Please note that other approaches were problematic:

1. Keep the shader as-is but enable ZWrite:
  This solves the issue described above but transparent
  parts would occlude labels using TextMeshPro shaders.

2. Using ZWrite and pushing depth buffer values for semi-
transparent fragments back just before the clipping plane,
e.g., 0.99:
  This does not work the same way as not writing to the
  depth buffer, thus has several side effects  and still
  interferes with TMP shaders.
@tinxx tinxx added the Improvement Improvement of existing features label Jan 15, 2025
@tinxx tinxx requested a review from koschke January 15, 2025 12:26
@tinxx tinxx self-assigned this Jan 15, 2025
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few bad patterns I found which you should check.

Allow passing server configuration via environment.
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few bad patterns I found which you should check.

Copy link
Collaborator

@falko17 falko17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR! I only have minor comments, which are listed below.

/// This is necessary to compensate for precision fluctuations in float values.
/// </para>
/// </summary>
private const float tolerance = 0.0001f;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that there exists a Mathf.Approximately function that we have been using so far for this purpose. Unless we need to specify the tolerance here as something different from Mathf.Epsilon, we should probably prefer that one.

Bounds2D potentialGrow = new(0f, 0f, 0f, 0f);
foreach (Transform sibling in parent.transform)
{
if (!sibling.gameObject.IsNodeAndActiveSelf())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is very long. Is it possible to factor some parts out of it? For example, could we extract the contents of each of these foreach loops into their own (possibly local) method?

{
continue;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change


// Determine shrink direction: weight by area size
float area = 0f;
float potentialArea;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this variable declared here? It seems that it's re-defined in every if block and not used outside of them.

}

// Grow to fill available space
foreach (Direction2D direction in new Direction2D[] {Direction2D.Left, Direction2D.Right, Direction2D.Back, Direction2D.Front})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can leave out the type declaration here:

Suggested change
foreach (Direction2D direction in new Direction2D[] {Direction2D.Left, Direction2D.Right, Direction2D.Back, Direction2D.Front})
foreach (Direction2D direction in new[] {Direction2D.Left, Direction2D.Right, Direction2D.Back, Direction2D.Front})

Comment on lines +215 to +220
if (Front < other.Back || Back > other.Front || Left > other.Right || Right < other.Left)
{
// No overlap detected
return false;
}
return true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be rewritten as:

Suggested change
if (Front < other.Back || Back > other.Front || Left > other.Right || Right < other.Left)
{
// No overlap detected
return false;
}
return true;
return Front >= other.Back && Back <= other.Front && Left <= other.Right && Right >= other.Left;

@@ -1033,18 +1060,18 @@ public static GraphElementOperator Operator(this GameObject gameObject)
}

/// <summary>
/// Checks if <paramref name="gameObject"/> overlaps with any other direct child node of its parent.
/// Checks if <paramref name="gameObject"/> overlaps with any other acrtive direct child node of its parent.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Checks if <paramref name="gameObject"/> overlaps with any other acrtive direct child node of its parent.
/// Checks if <paramref name="gameObject"/> overlaps with any other active direct child node of its parent.

/// </summary>
public static partial class SEEMath
{

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

/// Please consult method descriptions for credits and license.
/// </para>
/// </summary>
public static partial class SEEMath
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this class partial?

Please note that updating dependencies might require code changes.
It is usually safe -- and strongly suggested -- to bump patch level versions.
This is typically the third part of the version number, e.g., `1.2.3` -> `1.2.5`.
Changes to the first two segments are often associated to breaking changes and need additional manual review and testing.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only be true for the first segment, right? Or are the dependencies using something other than semantic versioning?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Improvement Improvement of existing features
Projects
None yet
2 participants