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

5e Cone Area of Effect on Square Grid: "Template/Stencil" Method #4588

Draft
wants to merge 8 commits into
base: develop
Choose a base branch
from

Conversation

mal-w
Copy link

@mal-w mal-w commented Dec 27, 2023

Identify the Bug or Feature request

Relates to but does not fix #1276.

Current cone template does not support D&D5e.

This PR implements a D&D 5e cone AoE on a Square Grid following the "Template" methodology (as opposed to the "Token" methodology).

Description of the Change

Create a new Template type called TriangleTemplate. The goal of this template is to match the cone's area of effect (see bellow quoted from SRD).

Cone

A cone extends in a direction you choose from its point of origin. A cone’s width at a given point along its length is equal to that point’s distance from the point of origin. A cone’s area of effect specifies its maximum length.

A cone’s point of origin is not included in the cone’s area of effect, unless you decide otherwise.

From the perspective of the user, the way the new cone template works is:

  1. Select the tool
  2. Click a starting point
  3. Move the mouse toward the "ending" point (i.e. the center of the cone). See additional notes below.
  4. Click again to confirm

During step 3, the user can hold control to move the origin like with all other templates. They can also hit control to cancel.

During step 3, the shape of the cone as per the SRD has its boundary drawn (this is to make the resulting area of effect clearer). The cone itself is compared against the grid to determine which grid cells are contained in the area of effect. The grid cells contained in the area of effect have a boundary drawn around them, and are also filled with the color of the drawing tools.

During step 4 the shape of the cone is discarded, and only the resulting area of effect is drawn into the grid.

Here is a screen recording of this feature in action. The jank (slowness and choppyness) is from my screencasting and not how it behaves in the app.

Technical Components

  1. Add TriangleTemplateTool.java
  2. Add this tool to the ToolbarPanel.java
  3. Add TriangleTemplate.java
  4. Add protobuf definition for TriangleTemplateDto in drawing_dto.proto
  5. Add case to fromDto in Drawable.java.

TriangleTemplate is the actual area of effect definition (it's parameters, how to draw it, etc.). The parameters are a starting vertex, the direction or theta of the cone and the radius of the cone. The vertex is in pixel space, since this template is not snapped to the grid for the drawing components, only the generated AOE is snapped to the grid. The theta is a real number from [0,360) giving the angle of the cone. The radius is snapped to the size of the grid. It handles painting the AOE onto the grid. The most important method to understand is the paint method. This calculates the co-ordinates of the cone and the gridded bounding rectangle gridSnappedBoundingBox (any grid square that could interesect with the cone is considered). The gridSnappedBoundingBox is added to a PriorityQueue which is used to iterate over candidate regions to establish if they should be in the AoE or not. The process is as follows as long as there is an entry in the Priority Queue:

  1. If there is no intersection between the candidate AoE and the Cone, we don't include this candidate in the final AoE and do nothing more with this candidate.
  2. If the candidate AoE is a subset of the Cone (i.e. subtracting the Cone from the candidate leaves us with an empty Area), then the whole candidate is included in the final AoE and we do nothing more with the candidate.
  3. If neither 1 nor 2 are met, there is some ammount of intersection between the candidate AoE and the Cone. This leaves us with two cases:
    a. The Candidate AoE is a single Grid Square: In this case, use the Shoelace formula to determine if more than half of the Grid Square is covered by the Cone. If more than half is covered, include the candidate in the final AoE. The shoelace formula was selected since it is efficient compared with continuing to sub-divide the Grid down to the pixel level and testing the AoE. It also doesn't have any issues with granularity and doesn't require programming any further stopping conditions.
    b. If the candidate AoE is not a single grid square, split the candidate AoE in 4 (2x2) and add each of the four resulting squares to the PriorityQueue. Splitting the candidates was chosen since it is more efficient than iterating over each grid square in gridSnappedBoundingBox, since we can short cicuit some of the AoE processing if we end up in 1 or 2.

TriangleTemplateTool is fairly simple (although long). It is mostly method definitions copied from RadiusTemplateTool. This class handles the drawing state machine (are we drawing, is the CTRL key pressed or not, have we set the starting point, handle final mouse-click, etc.).

Possible Drawbacks

Some stuff I might have overlooked:

  • Grid Resizing?

Documentation Notes

Release Notes


This change is Reviewable


WIP Components

  • Decide on toolbar icon.

  • Improve naming in code

  • Make "sensitivity" a parameter of the template so that if this becomes something we want to adjust through the UI we can implement that without changing the core functionality in the future.

  • Set the default sensitivity to 0.

  • Fix bugs with squares being left out.

  • Test performance of a static triangle scaled and rotated using AffineTransform.

  • Grid adjustment bug. The shape does not change when the grid is changed.

  • Fix how the name and icon look in the Draw Explorer tab.

  • Cleanup.

  • Open follow up issues (sensitivity adjustment, "token" method, "gridless" method [1])

[1]

You can also use this method without a grid. If you do so, a creature is included in an area of effect if any part of the miniature's base is overlapped by the template.

This requires interacting with the tokens themselves which is not something templates currently do.

Correct issue with scaling when drawing shapes.

Was drawing in the ZonePoint space (0,0), (0, 1), etc.
When I needed to draw in GridPoint space (100,100), (100, 200).

Fix cone size and calculate theta instead of endpoint.

There is an issue still with calculating the theta
on the fly... The issue comes up when we zoom in
and out while we are adjusting the angle.

Add bounding boxes to the cone.

Add logic to reduce cone into AOE squares in grid.

Add logic to reduce cone into AOE squares in grid.

Add shoelace formula for partially intersecting segments.

Improve performance of painting.

Instead of iterating over gridsquare, we split the candidate aoe vertically
and horizontally and apply tests on the aoe before getting down to
the grid size. The tests are more limited when looking at larger than
grid size: It's either no intersection with the cone or full
 intersection... We can only evaluate the overlapping area against the
  threshold meaningfully at the grid square level.

Add persisting of AOE when confirmed.

Added dto and related usage in Drawable.java and implement
builder in RightAngleConeTemplate.java.

Also added some logic to not show the cone overlay when
the template is "confirmed" and added to the layer...
This is driven by the showOAEOverlay property in
RightAngleConeTemplate.java, which is set to true
when constructed without an id (i.e. in drawing mode)
and false when given an id (i.e. confirmed and persisted
into the campaign).

I also fixed a minor bug with the behavior of the threshold
which was doing the opposite of what is expected (
90% threshold setting was actually 10%).

Remove snapping to grid

Use ZonePoint instead of CellPoint to determine radius more fluidly

Remove useless comments

Re-organize code

Run spotlessApply
@mal-w mal-w force-pushed the feature/right-angle-cone-templates branch from dbb32f0 to f4f10fc Compare December 27, 2023 06:27
@bubblobill
Copy link
Collaborator

Buuuut... 5e cones are not a right triangle. They are an isoceles triangle with base = height.
image

@mal-w
Copy link
Author

mal-w commented Dec 28, 2023 via email

@mal-w
Copy link
Author

mal-w commented Dec 28, 2023

https://github.com/RPTools/maptool/pull/4588/files#diff-d7a136802dcdea693263c74f247c8b1cf45a1527dbb69b3a4b3266cb1822189eR36

  // The definition of the cone is it is as wide as it is
  // long, so the cone angle is tan inverse of 1/2, since
  // if the length of the cone is 1, there is half it's
  // length from the midpoint to the left and right edge
  // of the base of the cone respectively...
  public static double CONE_ANGLE = Math.atan2(0.5, 1.0);

  // This is the ratio of the cone's side length to the
  // length from the point of the cone to the midpoint of the
  // base of the cone...
  public static double CONE_SIDE_LENGTH_RATIO = 1 / Math.cos(CONE_ANGLE);

@mal-w
Copy link
Author

mal-w commented Dec 28, 2023

Screencast.from.2023-12-28.13-38-16.webm

@mal-w mal-w changed the title Feature/right angle cone templates 5e Cone Area of Effect on Square Grid: "Template/Stencil" Method Dec 28, 2023
@mal-w mal-w marked this pull request as draft December 28, 2023 19:00
@bubblobill
Copy link
Collaborator

bubblobill commented Dec 29, 2023

It seems you use Affine Transforms purely to satisfy overrides and copied code.
I wonder if it wouldn't be easier/faster to create a static Path2D once, then use affine rotate and scale to create the drawn triangle.

Path2D aTriangleOfSizeOne = pt(0,0) -> pt1 ->pt2
AffineTransform at = new AffineTransform();
at.scale(scale, scale);
at.rotate(Math.toRadians(rotationAngle));
at.translate(screenPtX, screenPtY);
Shape drawThis = at.createTransformedShape(aTriangleOfSizeOne);

@mal-w
Copy link
Author

mal-w commented Dec 29, 2023 via email

@cwisniew cwisniew added the feature Adding functionality that adds value label Dec 30, 2023
@mal-w
Copy link
Author

mal-w commented Dec 30, 2023

image

In my most recent commit I updated the icons.. Not sure if these actually make sense. I changed the cone template's icon to have a rounded end and basically copied the cone template's old icon to be the icon for the triangle icon.

@bubblobill
Copy link
Collaborator

Would the resulting code change then be: create static stencil at instantiation of the class then use the radius and theta to scale and rotate as those parameters change from mouse movement?
That was my thinking. Build once, manipulate forever. You aren't creating the world's most complex geometry so I'm not sure if there are efficiency gains or not. Affine transforms essentially do the same manipulations you are already doing to recalculate the point locations. Worth learning about when doing stuff like this.

@mal-w
Copy link
Author

mal-w commented Jan 2, 2024

In 9666847 I tested this and the method of re-calculated the cone from scratch each time was faster, but really insignificantly so...

Test results:

320000 Triangles Calculated in Duration Using Transformed Stencil (getConePath): 141us
320000 Triangles Calculated in Duration Using Re-Render from Scratch (getConePathInneficientMethod): 79us

If there is any selection to be made, we should pick whichever has easier to understand logic between getConePath and getConePathInneficientMethod.

@bubblobill
Copy link
Collaborator

Thanks for doing the comparison. Don't think of it as insignificant, think of it as nearly half.
Looks like your original approach worked best, probably due to the simplicity of the shape. I shall keep this in mind with my own work using simple shapes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Adding functionality that adds value
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

Cones Templates: Increased Support
3 participants