From 55e440e4798d338fcec7611bf307ad2a3a48f3c8 Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Wed, 6 Aug 2025 20:50:28 -0700 Subject: [PATCH 01/15] Add support for rotated rectangle constraints --- .../generation/transformers/constraints.rs | 74 +++++++- src-core/src/spec/trajectory.rs | 5 +- .../KeepInRectangleOverlay.tsx | 166 +++++++++++++----- src/document/ConstraintDefinitions.tsx | 7 + 4 files changed, 197 insertions(+), 55 deletions(-) diff --git a/src-core/src/generation/transformers/constraints.rs b/src-core/src/generation/transformers/constraints.rs index 12524504aa..6a230c571c 100644 --- a/src-core/src/generation/transformers/constraints.rs +++ b/src-core/src/generation/transformers/constraints.rs @@ -131,9 +131,40 @@ impl SwerveGenerationTransformer for ConstraintSetter { None => generator.wpt_keep_in_circle(from, x, y, r), Some(to) => generator.sgmt_keep_in_circle(from, to, x, y, r), }, - ConstraintData::KeepInRectangle { x, y, w, h } => { - let xs = vec![x, x + w, x + w, x]; - let ys = vec![y, y, y + h, y + h]; + ConstraintData::KeepInRectangle { x, y, w, h, rotation } => { + // Calculate the center of the rectangle + let center_x = x + w / 2.0; + let center_y = y + h / 2.0; + + // Original corner points relative to bottom-left origin + let corners = vec![ + (x, y), // bottom-left + (x + w, y), // bottom-right + (x + w, y + h), // top-right + (x, y + h), // top-left + ]; + + // Apply rotation around center + let cos_r = rotation.cos(); + let sin_r = rotation.sin(); + + let mut xs = Vec::new(); + let mut ys = Vec::new(); + + for (corner_x, corner_y) in corners { + // Translate to origin (center of rectangle) + let rel_x = corner_x - center_x; + let rel_y = corner_y - center_y; + + // Apply rotation + let rotated_x = rel_x * cos_r - rel_y * sin_r; + let rotated_y = rel_x * sin_r + rel_y * cos_r; + + // Translate back + xs.push(center_x + rotated_x); + ys.push(center_y + rotated_y); + } + match to_opt { None => generator.wpt_keep_in_polygon(from, xs, ys), Some(to) => generator.sgmt_keep_in_polygon(from, to, xs, ys), @@ -209,9 +240,40 @@ impl DifferentialGenerationTransformer for ConstraintSetter { None => generator.wpt_keep_in_circle(from, x, y, r), Some(to) => generator.sgmt_keep_in_circle(from, to, x, y, r), }, - ConstraintData::KeepInRectangle { x, y, w, h } => { - let xs = vec![x, x + w, x + w, x]; - let ys = vec![y, y, y + h, y + h]; + ConstraintData::KeepInRectangle { x, y, w, h, rotation } => { + // Calculate the center of the rectangle + let center_x = x + w / 2.0; + let center_y = y + h / 2.0; + + // Original corner points relative to bottom-left origin + let corners = vec![ + (x, y), // bottom-left + (x + w, y), // bottom-right + (x + w, y + h), // top-right + (x, y + h), // top-left + ]; + + // Apply rotation around center + let cos_r = rotation.cos(); + let sin_r = rotation.sin(); + + let mut xs = Vec::new(); + let mut ys = Vec::new(); + + for (corner_x, corner_y) in corners { + // Translate to origin (center of rectangle) + let rel_x = corner_x - center_x; + let rel_y = corner_y - center_y; + + // Apply rotation + let rotated_x = rel_x * cos_r - rel_y * sin_r; + let rotated_y = rel_x * sin_r + rel_y * cos_r; + + // Translate back + xs.push(center_x + rotated_x); + ys.push(center_y + rotated_y); + } + match to_opt { None => generator.wpt_keep_in_polygon(from, xs, ys), Some(to) => generator.sgmt_keep_in_polygon(from, to, xs, ys), diff --git a/src-core/src/spec/trajectory.rs b/src-core/src/spec/trajectory.rs index 8e1797988c..7512d8f4ca 100644 --- a/src-core/src/spec/trajectory.rs +++ b/src-core/src/spec/trajectory.rs @@ -146,7 +146,7 @@ pub enum ConstraintData { /// A constraint to contain the bumpers within a circlular region of the field KeepInCircle { x: T, y: T, r: T }, /// A constraint to contain the bumpers within a rectangular region of the field - KeepInRectangle { x: T, y: T, w: T, h: T }, + KeepInRectangle { x: T, y: T, w: T, h: T, rotation: T }, /// A constraint to contain the bumpers within two line KeepInLane { tolerance: T }, /// A constraint to contain the bumpers outside a circlular region of the field @@ -192,11 +192,12 @@ impl ConstraintData { y: y.snapshot(), r: r.snapshot(), }, - ConstraintData::KeepInRectangle { x, y, w, h } => ConstraintData::KeepInRectangle { + ConstraintData::KeepInRectangle { x, y, w, h, rotation } => ConstraintData::KeepInRectangle { x: x.snapshot(), y: y.snapshot(), w: w.snapshot(), h: h.snapshot(), + rotation: rotation.snapshot(), }, ConstraintData::KeepInLane { tolerance } => ConstraintData::KeepInLane { tolerance: tolerance.snapshot(), diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index a3bb7fa58a..5eb9a10e28 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -23,6 +23,8 @@ class KeepInRectangleOverlay extends Component< object > { rootRef: React.RefObject = React.createRef(); + private initialRotation: number = 0; + private initialMouseAngle: number = 0; componentDidMount() { if (this.rootRef.current) { // Theres probably a better way to do this @@ -100,6 +102,21 @@ class KeepInRectangleOverlay extends Component< d3.select( `#dragTarget-keepInRectangleRegion` ).call(dragHandleRegion); + + const dragHandleRotation = d3 + .drag() + .on("drag", (event) => this.dragRotation(event)) + .on("start", (event) => { + doc.history.startGroup(() => {}); + this.startRotation(event); + }) + .on("end", (_event) => { + doc.history.stopGroup(); + }) + .container(this.rootRef.current); + d3.select( + `#dragTarget-keepInRectangleRotation` + ).call(dragHandleRotation); } } @@ -120,6 +137,38 @@ class KeepInRectangleOverlay extends Component< data.y.set(data.serialize.props.y.val + event.dy); } + startRotation(event: any) { + const data = this.props.data; + this.initialRotation = data.serialize.props.rotation.val; + + const centerX = data.serialize.props.x.val + data.serialize.props.w.val / 2; + const centerY = data.serialize.props.y.val + data.serialize.props.h.val / 2; + + // Store initial mouse angle relative to center + const mouseX = event.x - centerX; + const mouseY = event.y - centerY; + this.initialMouseAngle = Math.atan2(mouseY, mouseX); + } + + dragRotation(event: any) { + const data = this.props.data; + const centerX = data.serialize.props.x.val + data.serialize.props.w.val / 2; + const centerY = data.serialize.props.y.val + data.serialize.props.h.val / 2; + + // Get current mouse position relative to center + const mouseX = event.x - centerX; + const mouseY = event.y - centerY; + + // Calculate current mouse angle + const currentMouseAngle = Math.atan2(mouseY, mouseX); + + // Calculate the change in angle from initial mouse position + const angleDelta = currentMouseAngle - this.initialMouseAngle; + + // Set the rotation as initial rotation plus the change + data.rotation.set(this.initialRotation + angleDelta); + } + fixWidthHeight() { if (this.props.data.serialize.props.w.val < 0.0) { this.props.data.x.set( @@ -144,64 +193,87 @@ class KeepInRectangleOverlay extends Component< const y = data.props.y.val; const w = data.props.w.val; const h = data.props.h.val; + const rotation = data.props.rotation.val; + + // Calculate center and rotated corners + const centerX = x + w / 2; + const centerY = y + h / 2; + const cos_r = Math.cos(rotation); + const sin_r = Math.sin(rotation); + + // Original corner points relative to bottom-left origin + const corners = [ + [x, y], // bottom-left + [x + w, y], // bottom-right + [x + w, y + h], // top-right + [x, y + h], // top-left + ]; + + // Apply rotation around center + const rotatedCorners = corners.map(([corner_x, corner_y]) => { + const rel_x = corner_x - centerX; + const rel_y = corner_y - centerY; + + const rotated_x = rel_x * cos_r - rel_y * sin_r; + const rotated_y = rel_x * sin_r + rel_y * cos_r; + + return [centerX + rotated_x, centerY + rotated_y]; + }); + + // Create SVG polygon path + const polygonPoints = rotatedCorners.map(corner => corner.join(",")).join(" "); + return ( - {/* Fill Rect*/} - = 0 ? x : x + w} - y={h >= 0 ? y : y + h} - width={Math.abs(w)} - height={Math.abs(h)} + {/* Fill Polygon*/} + - {/*Border Rect*/} - = 0 ? x : x + w} - y={h >= 0 ? y : y + h} - width={Math.abs(w)} - height={Math.abs(h)} + /> + {/*Border Polygon*/} + - {/* Corners */} - + /> + {/* Rotated Corners */} + {rotatedCorners.map((corner, index) => ( + + ))} + {/* Rotation Handle - show as a line from center to top-right */} + - - + cx={rotatedCorners[2][0]} + cy={rotatedCorners[2][1]} + r={DOT * 1.5} + fill={"blue"} + fillOpacity={0.8} + id="dragTarget-keepInRectangleRotation" + /> ); } diff --git a/src/document/ConstraintDefinitions.tsx b/src/document/ConstraintDefinitions.tsx index 134b3ea97f..c09132a674 100644 --- a/src/document/ConstraintDefinitions.tsx +++ b/src/document/ConstraintDefinitions.tsx @@ -66,6 +66,7 @@ export type ConstraintDataTypeMap = { y: Expr; w: Expr; h: Expr; + rotation: Expr; }; KeepInLane: { tolerance: Expr; @@ -244,6 +245,12 @@ export const ConstraintDefinitions: defs = { description: "The height of the keep-in region", dimension: Dimensions.Length, defaultVal: { exp: "1 m", val: 1 } + }, + rotation: { + name: "Rotation", + description: "The rotation angle of the rectangle around its center", + dimension: Dimensions.Angle, + defaultVal: { exp: "0 deg", val: 0 } } }, wptScope: true, From c2a099d5bdbc3feb2f0c0cbc1efc7e3c34ccfc7e Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Wed, 6 Aug 2025 21:50:45 -0700 Subject: [PATCH 02/15] format --- .../generation/transformers/constraints.rs | 32 ++++++------ src-core/src/spec/trajectory.rs | 8 ++- .../KeepInRectangleOverlay.tsx | 49 +++++++++++-------- 3 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src-core/src/generation/transformers/constraints.rs b/src-core/src/generation/transformers/constraints.rs index 6a230c571c..ee54aaf8c0 100644 --- a/src-core/src/generation/transformers/constraints.rs +++ b/src-core/src/generation/transformers/constraints.rs @@ -135,36 +135,36 @@ impl SwerveGenerationTransformer for ConstraintSetter { // Calculate the center of the rectangle let center_x = x + w / 2.0; let center_y = y + h / 2.0; - + // Original corner points relative to bottom-left origin let corners = vec![ (x, y), // bottom-left - (x + w, y), // bottom-right + (x + w, y), // bottom-right (x + w, y + h), // top-right (x, y + h), // top-left ]; - + // Apply rotation around center let cos_r = rotation.cos(); let sin_r = rotation.sin(); - + let mut xs = Vec::new(); let mut ys = Vec::new(); - + for (corner_x, corner_y) in corners { // Translate to origin (center of rectangle) let rel_x = corner_x - center_x; let rel_y = corner_y - center_y; - + // Apply rotation let rotated_x = rel_x * cos_r - rel_y * sin_r; let rotated_y = rel_x * sin_r + rel_y * cos_r; - + // Translate back xs.push(center_x + rotated_x); ys.push(center_y + rotated_y); } - + match to_opt { None => generator.wpt_keep_in_polygon(from, xs, ys), Some(to) => generator.sgmt_keep_in_polygon(from, to, xs, ys), @@ -244,36 +244,36 @@ impl DifferentialGenerationTransformer for ConstraintSetter { // Calculate the center of the rectangle let center_x = x + w / 2.0; let center_y = y + h / 2.0; - + // Original corner points relative to bottom-left origin let corners = vec![ (x, y), // bottom-left - (x + w, y), // bottom-right + (x + w, y), // bottom-right (x + w, y + h), // top-right (x, y + h), // top-left ]; - + // Apply rotation around center let cos_r = rotation.cos(); let sin_r = rotation.sin(); - + let mut xs = Vec::new(); let mut ys = Vec::new(); - + for (corner_x, corner_y) in corners { // Translate to origin (center of rectangle) let rel_x = corner_x - center_x; let rel_y = corner_y - center_y; - + // Apply rotation let rotated_x = rel_x * cos_r - rel_y * sin_r; let rotated_y = rel_x * sin_r + rel_y * cos_r; - + // Translate back xs.push(center_x + rotated_x); ys.push(center_y + rotated_y); } - + match to_opt { None => generator.wpt_keep_in_polygon(from, xs, ys), Some(to) => generator.sgmt_keep_in_polygon(from, to, xs, ys), diff --git a/src-core/src/spec/trajectory.rs b/src-core/src/spec/trajectory.rs index 7512d8f4ca..1872a63e93 100644 --- a/src-core/src/spec/trajectory.rs +++ b/src-core/src/spec/trajectory.rs @@ -192,7 +192,13 @@ impl ConstraintData { y: y.snapshot(), r: r.snapshot(), }, - ConstraintData::KeepInRectangle { x, y, w, h, rotation } => ConstraintData::KeepInRectangle { + ConstraintData::KeepInRectangle { + x, + y, + w, + h, + rotation, + } => ConstraintData::KeepInRectangle { x: x.snapshot(), y: y.snapshot(), w: w.snapshot(), diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index 5eb9a10e28..19cc1d77e9 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -140,10 +140,10 @@ class KeepInRectangleOverlay extends Component< startRotation(event: any) { const data = this.props.data; this.initialRotation = data.serialize.props.rotation.val; - + const centerX = data.serialize.props.x.val + data.serialize.props.w.val / 2; const centerY = data.serialize.props.y.val + data.serialize.props.h.val / 2; - + // Store initial mouse angle relative to center const mouseX = event.x - centerX; const mouseY = event.y - centerY; @@ -154,17 +154,17 @@ class KeepInRectangleOverlay extends Component< const data = this.props.data; const centerX = data.serialize.props.x.val + data.serialize.props.w.val / 2; const centerY = data.serialize.props.y.val + data.serialize.props.h.val / 2; - + // Get current mouse position relative to center const mouseX = event.x - centerX; const mouseY = event.y - centerY; - + // Calculate current mouse angle const currentMouseAngle = Math.atan2(mouseY, mouseX); - + // Calculate the change in angle from initial mouse position const angleDelta = currentMouseAngle - this.initialMouseAngle; - + // Set the rotation as initial rotation plus the change data.rotation.set(this.initialRotation + angleDelta); } @@ -194,35 +194,37 @@ class KeepInRectangleOverlay extends Component< const w = data.props.w.val; const h = data.props.h.val; const rotation = data.props.rotation.val; - + // Calculate center and rotated corners const centerX = x + w / 2; const centerY = y + h / 2; const cos_r = Math.cos(rotation); const sin_r = Math.sin(rotation); - + // Original corner points relative to bottom-left origin const corners = [ - [x, y], // bottom-left - [x + w, y], // bottom-right + [x, y], // bottom-left + [x + w, y], // bottom-right [x + w, y + h], // top-right - [x, y + h], // top-left + [x, y + h] // top-left ]; - + // Apply rotation around center const rotatedCorners = corners.map(([corner_x, corner_y]) => { const rel_x = corner_x - centerX; const rel_y = corner_y - centerY; - + const rotated_x = rel_x * cos_r - rel_y * sin_r; const rotated_y = rel_x * sin_r + rel_y * cos_r; - + return [centerX + rotated_x, centerY + rotated_y]; }); - + // Create SVG polygon path - const polygonPoints = rotatedCorners.map(corner => corner.join(",")).join(" "); - + const polygonPoints = rotatedCorners + .map((corner) => corner.join(",")) + .join(" "); + return ( {/* Fill Polygon*/} @@ -250,10 +252,15 @@ class KeepInRectangleOverlay extends Component< r={DOT} fill={"green"} fillOpacity={1.0} - id={index === 0 ? "dragTarget-keepInRectangle" : - index === 1 ? "dragTarget-keepInRectangleW" : - index === 2 ? "dragTarget-keepInRectangleWH" : - "dragTarget-keepInRectangleH"} + id={ + index === 0 + ? "dragTarget-keepInRectangle" + : index === 1 + ? "dragTarget-keepInRectangleW" + : index === 2 + ? "dragTarget-keepInRectangleWH" + : "dragTarget-keepInRectangleH" + } /> ))} {/* Rotation Handle - show as a line from center to top-right */} From 7907cb4a25e1a3570dd021820c58ba4a8675d151 Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Wed, 6 Aug 2025 22:09:18 -0700 Subject: [PATCH 03/15] bump schema version and add migration for rotation --- .../py/choreo/util/traj_schema_version.py | 2 +- .../java/choreo/util/TrajSchemaVersion.java | 2 +- .../include/choreo/util/TrajSchemaVersion.h | 4 +- src-core/src/spec/traj_schema_version.rs | 2 +- src-core/src/spec/upgraders.rs | 56 +++++++++++++++++++ src/document/2025/TrajSchemaVersion.ts | 2 +- 6 files changed, 62 insertions(+), 6 deletions(-) diff --git a/choreolib/py/choreo/util/traj_schema_version.py b/choreolib/py/choreo/util/traj_schema_version.py index 5717e54176..a816b0e9ac 100644 --- a/choreolib/py/choreo/util/traj_schema_version.py +++ b/choreolib/py/choreo/util/traj_schema_version.py @@ -1,2 +1,2 @@ # Auto-generated by update_traj_schema.py -TRAJ_SCHEMA_VERSION = 1 +TRAJ_SCHEMA_VERSION = 2 diff --git a/choreolib/src/main/java/choreo/util/TrajSchemaVersion.java b/choreolib/src/main/java/choreo/util/TrajSchemaVersion.java index 978f692f26..208e4d58dc 100644 --- a/choreolib/src/main/java/choreo/util/TrajSchemaVersion.java +++ b/choreolib/src/main/java/choreo/util/TrajSchemaVersion.java @@ -7,7 +7,7 @@ /** Internal autogenerated class for storing the current trajectory schema version. */ public class TrajSchemaVersion { /** The current trajectory schema version. */ - public static final int TRAJ_SCHEMA_VERSION = 1; + public static final int TRAJ_SCHEMA_VERSION = 2; /** Utility class. */ private TrajSchemaVersion() {} diff --git a/choreolib/src/main/native/include/choreo/util/TrajSchemaVersion.h b/choreolib/src/main/native/include/choreo/util/TrajSchemaVersion.h index af7edfb183..c857f6da90 100644 --- a/choreolib/src/main/native/include/choreo/util/TrajSchemaVersion.h +++ b/choreolib/src/main/native/include/choreo/util/TrajSchemaVersion.h @@ -9,7 +9,7 @@ namespace choreo { [[deprecated("Use kTrajSchemaVersion.")]] -inline constexpr uint32_t kTrajSpecVersion = 1; -inline constexpr uint32_t kTrajSchemaVersion = 1; +inline constexpr uint32_t kTrajSpecVersion = 2; +inline constexpr uint32_t kTrajSchemaVersion = 2; } // namespace choreo diff --git a/src-core/src/spec/traj_schema_version.rs b/src-core/src/spec/traj_schema_version.rs index b2726375ba..455c5de2e1 100644 --- a/src-core/src/spec/traj_schema_version.rs +++ b/src-core/src/spec/traj_schema_version.rs @@ -1,2 +1,2 @@ // Auto-generated by update_traj_schema.py -pub const TRAJ_SCHEMA_VERSION: u32 = 1; +pub const TRAJ_SCHEMA_VERSION: u32 = 2; diff --git a/src-core/src/spec/upgraders.rs b/src-core/src/spec/upgraders.rs index 861b0cbf10..15a2197c19 100644 --- a/src-core/src/spec/upgraders.rs +++ b/src-core/src/spec/upgraders.rs @@ -17,6 +17,7 @@ mod traj_file { fn make_upgrader() -> Upgrader { let mut upgrader = Upgrader::new(TRAJ_SCHEMA_VERSION); upgrader.add_version_action(up_0_1); + upgrader.add_version_action(up_1_2); // Ensure the new upgrader is added here upgrader } @@ -34,6 +35,61 @@ mod traj_file { ) } + fn up_1_2(editor: &mut Editor) -> ChoreoResult<()> { + use crate::spec::Expr; + use serde_json::Value as JsonValue; + + // Add rotation field to all KeepInRectangle constraints in both snapshot and params + + // Handle snapshot constraints + if editor.has_path("snapshot.constraints") { + let snapshot_constraints: Vec = editor.get_path("snapshot.constraints")?; + let mut updated_constraints = Vec::new(); + + for mut constraint in snapshot_constraints { + if let Some(data_type) = constraint["data"]["type"].as_str() { + if data_type == "KeepInRectangle" { + // Add rotation field to props + if let Some(props) = constraint["data"]["props"].as_object_mut() { + props.insert( + "rotation".to_string(), + JsonValue::Number(serde_json::Number::from_f64(0.0).unwrap()), + ); + } + } + } + updated_constraints.push(constraint); + } + + editor.set_path_serialize("snapshot.constraints", updated_constraints)?; + } + + // Handle params constraints + if editor.has_path("params.constraints") { + let params_constraints: Vec = editor.get_path("params.constraints")?; + let mut updated_constraints = Vec::new(); + + for mut constraint in params_constraints { + if let Some(data_type) = constraint["data"]["type"].as_str() { + if data_type == "KeepInRectangle" { + // Add rotation field to props + if let Some(props) = constraint["data"]["props"].as_object_mut() { + props.insert( + "rotation".to_string(), + serde_json::to_value(Expr::new("0 deg", 0.0))?, + ); + } + } + } + updated_constraints.push(constraint); + } + + editor.set_path_serialize("params.constraints", updated_constraints)?; + } + + Ok(()) + } + #[cfg(test)] mod tests { use crate::spec::upgraders::testing_shared::{get_contents, FileType}; diff --git a/src/document/2025/TrajSchemaVersion.ts b/src/document/2025/TrajSchemaVersion.ts index c3c3a61fdd..26179d96f4 100644 --- a/src/document/2025/TrajSchemaVersion.ts +++ b/src/document/2025/TrajSchemaVersion.ts @@ -1,2 +1,2 @@ // Auto-generated by update_traj_schema.py -export const TRAJ_SCHEMA_VERSION = 1; +export const TRAJ_SCHEMA_VERSION = 2; From 8f984b4cf20d6bb4656f4a437bcfadbf04ffc839 Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Wed, 6 Aug 2025 22:34:47 -0700 Subject: [PATCH 04/15] fix tests --- choreolib/py/choreo/test/choreolib_test.py | 2 +- choreolib/py/choreo/test/resources/swerve_test.traj | 6 +++--- choreolib/src/test/java/choreo/ChoreoTests.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/choreolib/py/choreo/test/choreolib_test.py b/choreolib/py/choreo/test/choreolib_test.py index 5b5cd5ead8..507a4c9dd1 100644 --- a/choreolib/py/choreo/test/choreolib_test.py +++ b/choreolib/py/choreo/test/choreolib_test.py @@ -5,7 +5,7 @@ TRAJECTORY = """ { "name":"New Path", - "version":1, + "version":2, "snapshot":{ "waypoints":[ {"x":0.0, "y":0.0, "heading":0.0, "intervals":9, "split":false, "fixTranslation":true, "fixHeading":true, "overrideIntervals":false}, diff --git a/choreolib/py/choreo/test/resources/swerve_test.traj b/choreolib/py/choreo/test/resources/swerve_test.traj index 1e951285a1..6d5992ae05 100644 --- a/choreolib/py/choreo/test/resources/swerve_test.traj +++ b/choreolib/py/choreo/test/resources/swerve_test.traj @@ -1,6 +1,6 @@ { "name":"test", - "version":1, + "version":2, "snapshot":{ "waypoints":[ {"x":2.6185336112976074, "y":6.034867286682129, "heading":0.0, "intervals":16, "split":false, "fixTranslation":true, "fixHeading":true, "overrideIntervals":false}, @@ -8,7 +8,7 @@ "constraints":[ {"from":"first", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true}, {"from":"last", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true}, - {"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":0.0, "y":0.0, "w":16.54, "h":8.21}}, "enabled":true}], + {"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":0.0, "y":0.0, "w":16.54, "h":8.21, "rotation":0.0}}, "enabled":true}], "targetDt":0.05 }, "params":{ @@ -18,7 +18,7 @@ "constraints":[ {"from":"first", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true}, {"from":"last", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true}, - {"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":{"exp":"0 m", "val":0.0}, "y":{"exp":"0 m", "val":0.0}, "w":{"exp":"16.54 m", "val":16.54}, "h":{"exp":"8.21 m", "val":8.21}}}, "enabled":true}], + {"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":{"exp":"0 m", "val":0.0}, "y":{"exp":"0 m", "val":0.0}, "w":{"exp":"16.54 m", "val":16.54}, "h":{"exp":"8.21 m", "val":8.21}, "rotation":{"exp":"0 deg", "val":0.0}}}, "enabled":true}], "targetDt":{ "exp":"0.05 s", "val":0.05 diff --git a/choreolib/src/test/java/choreo/ChoreoTests.java b/choreolib/src/test/java/choreo/ChoreoTests.java index 90046a8653..cfa3b53264 100644 --- a/choreolib/src/test/java/choreo/ChoreoTests.java +++ b/choreolib/src/test/java/choreo/ChoreoTests.java @@ -15,7 +15,7 @@ public class ChoreoTests { """ { "name":"New Path", - "version":1, + "version":2, "snapshot":{ "waypoints":[ {"x":0.0, "y":0.0, "heading":0.0, "intervals":9, "split":false, "fixTranslation":true, "fixHeading":true, "overrideIntervals":false}, From fdcfaa93bd9e5ec7805090d878f33a15ede568a5 Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Thu, 7 Aug 2025 20:40:43 -0700 Subject: [PATCH 05/15] Change rectangle center --- .../py/choreo/test/resources/swerve_test.traj | 4 +- .../generation/transformers/constraints.rs | 32 +-- src-core/src/spec/upgraders.rs | 56 ++++- .../KeepInRectangleOverlay.tsx | 193 ++++++++++++------ src/document/ConstraintDefinitions.tsx | 6 +- 5 files changed, 209 insertions(+), 82 deletions(-) diff --git a/choreolib/py/choreo/test/resources/swerve_test.traj b/choreolib/py/choreo/test/resources/swerve_test.traj index 6d5992ae05..05503e29df 100644 --- a/choreolib/py/choreo/test/resources/swerve_test.traj +++ b/choreolib/py/choreo/test/resources/swerve_test.traj @@ -8,7 +8,7 @@ "constraints":[ {"from":"first", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true}, {"from":"last", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true}, - {"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":0.0, "y":0.0, "w":16.54, "h":8.21, "rotation":0.0}}, "enabled":true}], + {"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":8.27, "y":4.105, "w":16.54, "h":8.21, "rotation":0.0}}, "enabled":true}], "targetDt":0.05 }, "params":{ @@ -18,7 +18,7 @@ "constraints":[ {"from":"first", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true}, {"from":"last", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true}, - {"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":{"exp":"0 m", "val":0.0}, "y":{"exp":"0 m", "val":0.0}, "w":{"exp":"16.54 m", "val":16.54}, "h":{"exp":"8.21 m", "val":8.21}, "rotation":{"exp":"0 deg", "val":0.0}}}, "enabled":true}], + {"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":{"exp":"8.27 m", "val":8.27}, "y":{"exp":"4.105 m", "val":4.105}, "w":{"exp":"16.54 m", "val":16.54}, "h":{"exp":"8.21 m", "val":8.21}, "rotation":{"exp":"0 deg", "val":0.0}}}, "enabled":true}], "targetDt":{ "exp":"0.05 s", "val":0.05 diff --git a/src-core/src/generation/transformers/constraints.rs b/src-core/src/generation/transformers/constraints.rs index ee54aaf8c0..8860451983 100644 --- a/src-core/src/generation/transformers/constraints.rs +++ b/src-core/src/generation/transformers/constraints.rs @@ -132,16 +132,16 @@ impl SwerveGenerationTransformer for ConstraintSetter { Some(to) => generator.sgmt_keep_in_circle(from, to, x, y, r), }, ConstraintData::KeepInRectangle { x, y, w, h, rotation } => { - // Calculate the center of the rectangle - let center_x = x + w / 2.0; - let center_y = y + h / 2.0; + // x, y now represent the center of the rectangle + let center_x = x; + let center_y = y; - // Original corner points relative to bottom-left origin + // Original corner points relative to center let corners = vec![ - (x, y), // bottom-left - (x + w, y), // bottom-right - (x + w, y + h), // top-right - (x, y + h), // top-left + (center_x - w / 2.0, center_y - h / 2.0), // bottom-left + (center_x + w / 2.0, center_y - h / 2.0), // bottom-right + (center_x + w / 2.0, center_y + h / 2.0), // top-right + (center_x - w / 2.0, center_y + h / 2.0), // top-left ]; // Apply rotation around center @@ -241,16 +241,16 @@ impl DifferentialGenerationTransformer for ConstraintSetter { Some(to) => generator.sgmt_keep_in_circle(from, to, x, y, r), }, ConstraintData::KeepInRectangle { x, y, w, h, rotation } => { - // Calculate the center of the rectangle - let center_x = x + w / 2.0; - let center_y = y + h / 2.0; + // x, y now represent the center of the rectangle + let center_x = x; + let center_y = y; - // Original corner points relative to bottom-left origin + // Original corner points relative to center let corners = vec![ - (x, y), // bottom-left - (x + w, y), // bottom-right - (x + w, y + h), // top-right - (x, y + h), // top-left + (center_x - w / 2.0, center_y - h / 2.0), // bottom-left + (center_x + w / 2.0, center_y - h / 2.0), // bottom-right + (center_x + w / 2.0, center_y + h / 2.0), // top-right + (center_x - w / 2.0, center_y + h / 2.0), // top-left ]; // Apply rotation around center diff --git a/src-core/src/spec/upgraders.rs b/src-core/src/spec/upgraders.rs index 15a2197c19..61218fbe34 100644 --- a/src-core/src/spec/upgraders.rs +++ b/src-core/src/spec/upgraders.rs @@ -49,8 +49,23 @@ mod traj_file { for mut constraint in snapshot_constraints { if let Some(data_type) = constraint["data"]["type"].as_str() { if data_type == "KeepInRectangle" { - // Add rotation field to props + // Add rotation field and convert coordinates from bottom-left to center if let Some(props) = constraint["data"]["props"].as_object_mut() { + // Get existing values + let x = props.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0); + let y = props.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0); + let w = props.get("w").and_then(|v| v.as_f64()).unwrap_or(1.0); + let h = props.get("h").and_then(|v| v.as_f64()).unwrap_or(1.0); + + // Convert from bottom-left to center coordinates + let center_x = x + w / 2.0; + let center_y = y + h / 2.0; + + // Update x,y to be center coordinates + props.insert("x".to_string(), JsonValue::Number(serde_json::Number::from_f64(center_x).unwrap())); + props.insert("y".to_string(), JsonValue::Number(serde_json::Number::from_f64(center_y).unwrap())); + + // Add rotation field props.insert( "rotation".to_string(), JsonValue::Number(serde_json::Number::from_f64(0.0).unwrap()), @@ -72,8 +87,45 @@ mod traj_file { for mut constraint in params_constraints { if let Some(data_type) = constraint["data"]["type"].as_str() { if data_type == "KeepInRectangle" { - // Add rotation field to props + // Add rotation field and convert coordinates from bottom-left to center if let Some(props) = constraint["data"]["props"].as_object_mut() { + // Get existing values from Expr objects + let x_val = props.get("x") + .and_then(|v| v.get("val")) + .and_then(|v| v.as_f64()) + .unwrap_or(0.0); + let y_val = props.get("y") + .and_then(|v| v.get("val")) + .and_then(|v| v.as_f64()) + .unwrap_or(0.0); + let w_val = props.get("w") + .and_then(|v| v.get("val")) + .and_then(|v| v.as_f64()) + .unwrap_or(1.0); + let h_val = props.get("h") + .and_then(|v| v.get("val")) + .and_then(|v| v.as_f64()) + .unwrap_or(1.0); + + // Convert from bottom-left to center coordinates + let center_x = x_val + w_val / 2.0; + let center_y = y_val + h_val / 2.0; + + // Update x,y to be center coordinates (preserve expression strings but update values) + if let Some(x_expr) = props.get_mut("x") { + if let Some(x_obj) = x_expr.as_object_mut() { + x_obj.insert("val".to_string(), JsonValue::Number(serde_json::Number::from_f64(center_x).unwrap())); + x_obj.insert("exp".to_string(), JsonValue::String(format!("{} m", center_x))); + } + } + if let Some(y_expr) = props.get_mut("y") { + if let Some(y_obj) = y_expr.as_object_mut() { + y_obj.insert("val".to_string(), JsonValue::Number(serde_json::Number::from_f64(center_y).unwrap())); + y_obj.insert("exp".to_string(), JsonValue::String(format!("{} m", center_y))); + } + } + + // Add rotation field props.insert( "rotation".to_string(), serde_json::to_value(Expr::new("0 deg", 0.0))?, diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index 19cc1d77e9..5728e77b44 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -122,12 +122,82 @@ class KeepInRectangleOverlay extends Component< dragPointTranslate(event: any, xOffset: boolean, yOffset: boolean) { const data = this.props.data; - console.log(xOffset, yOffset); - data.x.set(data.serialize.props.x.val + event.dx * (xOffset ? 0.0 : 1.0)); - data.y.set(data.serialize.props.y.val + event.dy * (yOffset ? 0.0 : 1.0)); - - data.w.set(data.serialize.props.w.val - event.dx * (xOffset ? -1.0 : 1.0)); - data.h.set(data.serialize.props.h.val - event.dy * (yOffset ? -1.0 : 1.0)); + const rotation = data.serialize.props.rotation.val; + const centerX = data.serialize.props.x.val; // x,y are now center coordinates + const centerY = data.serialize.props.y.val; + const w = data.serialize.props.w.val; + const h = data.serialize.props.h.val; + + const center: [number, number] = [centerX, centerY]; + + // Calculate current rotated corners in world coordinates + const corners: [number, number][] = [ + [centerX - w / 2, centerY - h / 2], // bottom-left (index 0) + [centerX + w / 2, centerY - h / 2], // bottom-right (index 1) + [centerX + w / 2, centerY + h / 2], // top-right (index 2) + [centerX - w / 2, centerY + h / 2] // top-left (index 3) + ]; + const rotatedCorners = corners.map((corner) => this.rotate_around(corner, center, rotation)); + + // Determine which corner we're dragging and which should stay fixed + let draggedCornerIndex: number; + let fixedCornerIndex: number; + + if (!xOffset && !yOffset) { + // bottom-left corner drag + draggedCornerIndex = 0; + fixedCornerIndex = 2; // top-right stays fixed + } else if (xOffset && !yOffset) { + // bottom-right corner drag + draggedCornerIndex = 1; + fixedCornerIndex = 3; // top-left stays fixed + } else if (xOffset && yOffset) { + // top-right corner drag + draggedCornerIndex = 2; + fixedCornerIndex = 0; // bottom-left stays fixed + } else { + // top-left corner drag + draggedCornerIndex = 3; + fixedCornerIndex = 1; // bottom-right stays fixed + } + + // Move the dragged corner by the drag delta + const newDraggedCorner: [number, number] = [ + rotatedCorners[draggedCornerIndex][0] + event.dx, + rotatedCorners[draggedCornerIndex][1] + event.dy + ]; + + // Fixed corner stays in place + const fixedCorner = rotatedCorners[fixedCornerIndex]; + + // Calculate new center and dimensions from the diagonal corners + const newCenterX = (newDraggedCorner[0] + fixedCorner[0]) / 2; + const newCenterY = (newDraggedCorner[1] + fixedCorner[1]) / 2; + + // Calculate dimensions by transforming corners to the rectangle's local coordinate system + const cos_r = Math.cos(-rotation); + const sin_r = Math.sin(-rotation); + + // Transform both corners to local coordinates relative to new center + const draggedRelX = newDraggedCorner[0] - newCenterX; + const draggedRelY = newDraggedCorner[1] - newCenterY; + const fixedRelX = fixedCorner[0] - newCenterX; + const fixedRelY = fixedCorner[1] - newCenterY; + + const draggedLocalX = draggedRelX * cos_r - draggedRelY * sin_r; + const draggedLocalY = draggedRelX * sin_r + draggedRelY * cos_r; + const fixedLocalX = fixedRelX * cos_r - fixedRelY * sin_r; + const fixedLocalY = fixedRelX * sin_r + fixedRelY * cos_r; + + // Calculate new width and height + const newW = Math.abs(draggedLocalX - fixedLocalX); + const newH = Math.abs(draggedLocalY - fixedLocalY); + + // Update rectangle parameters (center-based) + data.x.set(newCenterX); + data.y.set(newCenterY); + data.w.set(newW); + data.h.set(newH); } dragRegionTranslate(event: any) { @@ -141,8 +211,8 @@ class KeepInRectangleOverlay extends Component< const data = this.props.data; this.initialRotation = data.serialize.props.rotation.val; - const centerX = data.serialize.props.x.val + data.serialize.props.w.val / 2; - const centerY = data.serialize.props.y.val + data.serialize.props.h.val / 2; + const centerX = data.serialize.props.x.val; // x,y are now center coordinates + const centerY = data.serialize.props.y.val; // Store initial mouse angle relative to center const mouseX = event.x - centerX; @@ -152,8 +222,8 @@ class KeepInRectangleOverlay extends Component< dragRotation(event: any) { const data = this.props.data; - const centerX = data.serialize.props.x.val + data.serialize.props.w.val / 2; - const centerY = data.serialize.props.y.val + data.serialize.props.h.val / 2; + const centerX = data.serialize.props.x.val; // x,y are now center coordinates + const centerY = data.serialize.props.y.val; // Get current mouse position relative to center const mouseX = event.x - centerX; @@ -170,55 +240,49 @@ class KeepInRectangleOverlay extends Component< } fixWidthHeight() { + // With center-based coordinates, just ensure width and height are positive if (this.props.data.serialize.props.w.val < 0.0) { - this.props.data.x.set( - this.props.data.serialize.props.x.val + - this.props.data.serialize.props.w.val - ); this.props.data.w.set(-this.props.data.serialize.props.w.val); } if (this.props.data.serialize.props.h.val < 0.0) { - this.props.data.y.set( - this.props.data.serialize.props.y.val + - this.props.data.serialize.props.h.val - ); this.props.data.h.set(-this.props.data.serialize.props.h.val); } } + rotate_around(point: [number, number], center: [number, number], angle: number): [number, number] { + const cos_r = Math.cos(angle); + const sin_r = Math.sin(angle); + + const rel_x = point[0] - center[0]; + const rel_y = point[1] - center[1]; + + const rotated_x = rel_x * cos_r - rel_y * sin_r; + const rotated_y = rel_x * sin_r + rel_y * cos_r; + + return [center[0] + rotated_x, center[1] + rotated_y]; + } + render() { const data = this.props.data.serialize as DataMap["KeepInRectangle"]; - const x = data.props.x.val; - const y = data.props.y.val; + const centerX = data.props.x.val; // x,y now represent center + const centerY = data.props.y.val; const w = data.props.w.val; const h = data.props.h.val; const rotation = data.props.rotation.val; - // Calculate center and rotated corners - const centerX = x + w / 2; - const centerY = y + h / 2; - const cos_r = Math.cos(rotation); - const sin_r = Math.sin(rotation); + const center: [number, number] = [centerX, centerY]; - // Original corner points relative to bottom-left origin - const corners = [ - [x, y], // bottom-left - [x + w, y], // bottom-right - [x + w, y + h], // top-right - [x, y + h] // top-left + // Original corner points relative to center + const corners: [number, number][] = [ + [centerX - w / 2, centerY - h / 2], // bottom-left + [centerX + w / 2, centerY - h / 2], // bottom-right + [centerX + w / 2, centerY + h / 2], // top-right + [centerX - w / 2, centerY + h / 2] // top-left ]; - // Apply rotation around center - const rotatedCorners = corners.map(([corner_x, corner_y]) => { - const rel_x = corner_x - centerX; - const rel_y = corner_y - centerY; - - const rotated_x = rel_x * cos_r - rel_y * sin_r; - const rotated_y = rel_x * sin_r + rel_y * cos_r; - - return [centerX + rotated_x, centerY + rotated_y]; - }); + // Apply rotation around center using rotate_around method + const rotatedCorners = corners.map((corner) => this.rotate_around(corner, center, rotation)); // Create SVG polygon path const polygonPoints = rotatedCorners @@ -263,24 +327,35 @@ class KeepInRectangleOverlay extends Component< } /> ))} - {/* Rotation Handle - show as a line from center to top-right */} - - + {/* Rotation Handle - triangle at center of top edge */} + {(() => { + // Calculate center of top edge + const topLeftCorner = rotatedCorners[3]; + const topRightCorner = rotatedCorners[2]; + const topEdgeCenterX = (topLeftCorner[0] + topRightCorner[0]) / 2; + const topEdgeCenterY = (topLeftCorner[1] + topRightCorner[1]) / 2; + + // Triangle dimensions (matching waypoint style) + const triangleSize = DOT * 3; + const triangleHeight = triangleSize * 0.866; // √3/2 for equilateral triangle + + // Calculate angle for the triangle rotation (perpendicular to edge) + const edgeVectorX = topRightCorner[0] - topLeftCorner[0]; + const edgeVectorY = topRightCorner[1] - topLeftCorner[1]; + const edgeAngle = Math.atan2(edgeVectorY, edgeVectorX); + const triangleAngle = edgeAngle + Math.PI / 2; // perpendicular to edge + + return ( + + ); + })()} ); } diff --git a/src/document/ConstraintDefinitions.tsx b/src/document/ConstraintDefinitions.tsx index c09132a674..f1fcd4e4be 100644 --- a/src/document/ConstraintDefinitions.tsx +++ b/src/document/ConstraintDefinitions.tsx @@ -223,14 +223,14 @@ export const ConstraintDefinitions: defs = { x: { name: "X", description: - "The x coordinate of the bottom left of the keep-in region", + "The x coordinate of the center of the keep-in region", dimension: Dimensions.Length, defaultVal: { exp: "0 m", val: 0 } }, y: { name: "Y", description: - "The y coordinate of the bottom left of the keep-in region", + "The y coordinate of the center of the keep-in region", dimension: Dimensions.Length, defaultVal: { exp: "0 m", val: 0 } }, @@ -247,7 +247,7 @@ export const ConstraintDefinitions: defs = { defaultVal: { exp: "1 m", val: 1 } }, rotation: { - name: "Rotation", + name: "R", description: "The rotation angle of the rectangle around its center", dimension: Dimensions.Angle, defaultVal: { exp: "0 deg", val: 0 } From 453b40441be31ddc69d20e2f700cc05d4a2aacf4 Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Thu, 7 Aug 2025 20:56:44 -0700 Subject: [PATCH 06/15] fmt --- src-core/src/spec/upgraders.rs | 58 ++++++++++++++----- .../KeepInRectangleOverlay.tsx | 58 +++++++++++-------- src/document/ConstraintDefinitions.tsx | 6 +- 3 files changed, 77 insertions(+), 45 deletions(-) diff --git a/src-core/src/spec/upgraders.rs b/src-core/src/spec/upgraders.rs index 61218fbe34..44eb7e899f 100644 --- a/src-core/src/spec/upgraders.rs +++ b/src-core/src/spec/upgraders.rs @@ -56,15 +56,21 @@ mod traj_file { let y = props.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0); let w = props.get("w").and_then(|v| v.as_f64()).unwrap_or(1.0); let h = props.get("h").and_then(|v| v.as_f64()).unwrap_or(1.0); - + // Convert from bottom-left to center coordinates let center_x = x + w / 2.0; let center_y = y + h / 2.0; - + // Update x,y to be center coordinates - props.insert("x".to_string(), JsonValue::Number(serde_json::Number::from_f64(center_x).unwrap())); - props.insert("y".to_string(), JsonValue::Number(serde_json::Number::from_f64(center_y).unwrap())); - + props.insert( + "x".to_string(), + JsonValue::Number(serde_json::Number::from_f64(center_x).unwrap()), + ); + props.insert( + "y".to_string(), + JsonValue::Number(serde_json::Number::from_f64(center_y).unwrap()), + ); + // Add rotation field props.insert( "rotation".to_string(), @@ -90,41 +96,61 @@ mod traj_file { // Add rotation field and convert coordinates from bottom-left to center if let Some(props) = constraint["data"]["props"].as_object_mut() { // Get existing values from Expr objects - let x_val = props.get("x") + let x_val = props + .get("x") .and_then(|v| v.get("val")) .and_then(|v| v.as_f64()) .unwrap_or(0.0); - let y_val = props.get("y") + let y_val = props + .get("y") .and_then(|v| v.get("val")) .and_then(|v| v.as_f64()) .unwrap_or(0.0); - let w_val = props.get("w") + let w_val = props + .get("w") .and_then(|v| v.get("val")) .and_then(|v| v.as_f64()) .unwrap_or(1.0); - let h_val = props.get("h") + let h_val = props + .get("h") .and_then(|v| v.get("val")) .and_then(|v| v.as_f64()) .unwrap_or(1.0); - + // Convert from bottom-left to center coordinates let center_x = x_val + w_val / 2.0; let center_y = y_val + h_val / 2.0; - + // Update x,y to be center coordinates (preserve expression strings but update values) if let Some(x_expr) = props.get_mut("x") { if let Some(x_obj) = x_expr.as_object_mut() { - x_obj.insert("val".to_string(), JsonValue::Number(serde_json::Number::from_f64(center_x).unwrap())); - x_obj.insert("exp".to_string(), JsonValue::String(format!("{} m", center_x))); + x_obj.insert( + "val".to_string(), + JsonValue::Number( + serde_json::Number::from_f64(center_x).unwrap(), + ), + ); + x_obj.insert( + "exp".to_string(), + JsonValue::String(format!("{} m", center_x)), + ); } } if let Some(y_expr) = props.get_mut("y") { if let Some(y_obj) = y_expr.as_object_mut() { - y_obj.insert("val".to_string(), JsonValue::Number(serde_json::Number::from_f64(center_y).unwrap())); - y_obj.insert("exp".to_string(), JsonValue::String(format!("{} m", center_y))); + y_obj.insert( + "val".to_string(), + JsonValue::Number( + serde_json::Number::from_f64(center_y).unwrap(), + ), + ); + y_obj.insert( + "exp".to_string(), + JsonValue::String(format!("{} m", center_y)), + ); } } - + // Add rotation field props.insert( "rotation".to_string(), diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index 5728e77b44..adceab2544 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -127,72 +127,74 @@ class KeepInRectangleOverlay extends Component< const centerY = data.serialize.props.y.val; const w = data.serialize.props.w.val; const h = data.serialize.props.h.val; - + const center: [number, number] = [centerX, centerY]; - + // Calculate current rotated corners in world coordinates const corners: [number, number][] = [ [centerX - w / 2, centerY - h / 2], // bottom-left (index 0) - [centerX + w / 2, centerY - h / 2], // bottom-right (index 1) + [centerX + w / 2, centerY - h / 2], // bottom-right (index 1) [centerX + w / 2, centerY + h / 2], // top-right (index 2) - [centerX - w / 2, centerY + h / 2] // top-left (index 3) + [centerX - w / 2, centerY + h / 2] // top-left (index 3) ]; - const rotatedCorners = corners.map((corner) => this.rotate_around(corner, center, rotation)); - + const rotatedCorners = corners.map((corner) => + this.rotate_around(corner, center, rotation) + ); + // Determine which corner we're dragging and which should stay fixed let draggedCornerIndex: number; let fixedCornerIndex: number; - + if (!xOffset && !yOffset) { // bottom-left corner drag draggedCornerIndex = 0; fixedCornerIndex = 2; // top-right stays fixed } else if (xOffset && !yOffset) { - // bottom-right corner drag + // bottom-right corner drag draggedCornerIndex = 1; fixedCornerIndex = 3; // top-left stays fixed } else if (xOffset && yOffset) { // top-right corner drag - draggedCornerIndex = 2; + draggedCornerIndex = 2; fixedCornerIndex = 0; // bottom-left stays fixed } else { // top-left corner drag draggedCornerIndex = 3; fixedCornerIndex = 1; // bottom-right stays fixed } - + // Move the dragged corner by the drag delta const newDraggedCorner: [number, number] = [ rotatedCorners[draggedCornerIndex][0] + event.dx, rotatedCorners[draggedCornerIndex][1] + event.dy ]; - + // Fixed corner stays in place const fixedCorner = rotatedCorners[fixedCornerIndex]; - + // Calculate new center and dimensions from the diagonal corners const newCenterX = (newDraggedCorner[0] + fixedCorner[0]) / 2; const newCenterY = (newDraggedCorner[1] + fixedCorner[1]) / 2; - + // Calculate dimensions by transforming corners to the rectangle's local coordinate system const cos_r = Math.cos(-rotation); const sin_r = Math.sin(-rotation); - + // Transform both corners to local coordinates relative to new center const draggedRelX = newDraggedCorner[0] - newCenterX; const draggedRelY = newDraggedCorner[1] - newCenterY; const fixedRelX = fixedCorner[0] - newCenterX; const fixedRelY = fixedCorner[1] - newCenterY; - + const draggedLocalX = draggedRelX * cos_r - draggedRelY * sin_r; const draggedLocalY = draggedRelX * sin_r + draggedRelY * cos_r; const fixedLocalX = fixedRelX * cos_r - fixedRelY * sin_r; const fixedLocalY = fixedRelX * sin_r + fixedRelY * cos_r; - + // Calculate new width and height const newW = Math.abs(draggedLocalX - fixedLocalX); const newH = Math.abs(draggedLocalY - fixedLocalY); - + // Update rectangle parameters (center-based) data.x.set(newCenterX); data.y.set(newCenterY); @@ -250,16 +252,20 @@ class KeepInRectangleOverlay extends Component< } } - rotate_around(point: [number, number], center: [number, number], angle: number): [number, number] { + rotate_around( + point: [number, number], + center: [number, number], + angle: number + ): [number, number] { const cos_r = Math.cos(angle); const sin_r = Math.sin(angle); - + const rel_x = point[0] - center[0]; const rel_y = point[1] - center[1]; - + const rotated_x = rel_x * cos_r - rel_y * sin_r; const rotated_y = rel_x * sin_r + rel_y * cos_r; - + return [center[0] + rotated_x, center[1] + rotated_y]; } @@ -282,7 +288,9 @@ class KeepInRectangleOverlay extends Component< ]; // Apply rotation around center using rotate_around method - const rotatedCorners = corners.map((corner) => this.rotate_around(corner, center, rotation)); + const rotatedCorners = corners.map((corner) => + this.rotate_around(corner, center, rotation) + ); // Create SVG polygon path const polygonPoints = rotatedCorners @@ -334,17 +342,17 @@ class KeepInRectangleOverlay extends Component< const topRightCorner = rotatedCorners[2]; const topEdgeCenterX = (topLeftCorner[0] + topRightCorner[0]) / 2; const topEdgeCenterY = (topLeftCorner[1] + topRightCorner[1]) / 2; - + // Triangle dimensions (matching waypoint style) const triangleSize = DOT * 3; const triangleHeight = triangleSize * 0.866; // √3/2 for equilateral triangle - + // Calculate angle for the triangle rotation (perpendicular to edge) const edgeVectorX = topRightCorner[0] - topLeftCorner[0]; const edgeVectorY = topRightCorner[1] - topLeftCorner[1]; const edgeAngle = Math.atan2(edgeVectorY, edgeVectorX); const triangleAngle = edgeAngle + Math.PI / 2; // perpendicular to edge - + return ( Date: Thu, 7 Aug 2025 21:06:27 -0700 Subject: [PATCH 07/15] Refactor into rotate_around --- .../generation/transformers/constraints.rs | 92 ++++++++++--------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/src-core/src/generation/transformers/constraints.rs b/src-core/src/generation/transformers/constraints.rs index 8860451983..101f5d6de9 100644 --- a/src-core/src/generation/transformers/constraints.rs +++ b/src-core/src/generation/transformers/constraints.rs @@ -15,6 +15,36 @@ fn fix_scope(idx: usize, removed_idxs: &[usize]) -> usize { idx - to_subtract } +/// Rotates a point around another point in 2D space. +/// +/// ```text +/// [x_new] [rot.cos, -rot.sin][x - other.x] [other.x] +/// [y_new] = [rot.sin, rot.cos][y - other.y] + [other.y] +/// ``` +/// +/// # Arguments +/// * `point` - The point to rotate as (x, y) +/// * `center` - The center point to rotate around as (x, y) +/// * `rotation` - The rotation angle in radians +/// +/// # Returns +/// The new rotated point as (x, y) +fn rotate_around(point: (f64, f64), center: (f64, f64), rotation: f64) -> (f64, f64) { + let cos_r = rotation.cos(); + let sin_r = rotation.sin(); + + // Translate to origin (center of rotation) + let rel_x = point.0 - center.0; + let rel_y = point.1 - center.1; + + // Apply rotation + let rotated_x = rel_x * cos_r - rel_y * sin_r; + let rotated_y = rel_x * sin_r + rel_y * cos_r; + + // Translate back + (center.0 + rotated_x, center.1 + rotated_y) +} + pub struct ConstraintSetter { guess_points: Vec, constraint_idx: Vec>, @@ -133,36 +163,23 @@ impl SwerveGenerationTransformer for ConstraintSetter { }, ConstraintData::KeepInRectangle { x, y, w, h, rotation } => { // x, y now represent the center of the rectangle - let center_x = x; - let center_y = y; + let center = (x, y); // Original corner points relative to center let corners = vec![ - (center_x - w / 2.0, center_y - h / 2.0), // bottom-left - (center_x + w / 2.0, center_y - h / 2.0), // bottom-right - (center_x + w / 2.0, center_y + h / 2.0), // top-right - (center_x - w / 2.0, center_y + h / 2.0), // top-left + (x - w / 2.0, y - h / 2.0), // bottom-left + (x + w / 2.0, y - h / 2.0), // bottom-right + (x + w / 2.0, y + h / 2.0), // top-right + (x - w / 2.0, y + h / 2.0), // top-left ]; - // Apply rotation around center - let cos_r = rotation.cos(); - let sin_r = rotation.sin(); - let mut xs = Vec::new(); let mut ys = Vec::new(); - for (corner_x, corner_y) in corners { - // Translate to origin (center of rectangle) - let rel_x = corner_x - center_x; - let rel_y = corner_y - center_y; - - // Apply rotation - let rotated_x = rel_x * cos_r - rel_y * sin_r; - let rotated_y = rel_x * sin_r + rel_y * cos_r; - - // Translate back - xs.push(center_x + rotated_x); - ys.push(center_y + rotated_y); + for corner in corners { + let (rotated_x, rotated_y) = rotate_around(corner, center, rotation); + xs.push(rotated_x); + ys.push(rotated_y); } match to_opt { @@ -242,36 +259,23 @@ impl DifferentialGenerationTransformer for ConstraintSetter { }, ConstraintData::KeepInRectangle { x, y, w, h, rotation } => { // x, y now represent the center of the rectangle - let center_x = x; - let center_y = y; + let center = (x, y); // Original corner points relative to center let corners = vec![ - (center_x - w / 2.0, center_y - h / 2.0), // bottom-left - (center_x + w / 2.0, center_y - h / 2.0), // bottom-right - (center_x + w / 2.0, center_y + h / 2.0), // top-right - (center_x - w / 2.0, center_y + h / 2.0), // top-left + (x - w / 2.0, y - h / 2.0), // bottom-left + (x + w / 2.0, y - h / 2.0), // bottom-right + (x + w / 2.0, y + h / 2.0), // top-right + (x - w / 2.0, y + h / 2.0), // top-left ]; - // Apply rotation around center - let cos_r = rotation.cos(); - let sin_r = rotation.sin(); - let mut xs = Vec::new(); let mut ys = Vec::new(); - for (corner_x, corner_y) in corners { - // Translate to origin (center of rectangle) - let rel_x = corner_x - center_x; - let rel_y = corner_y - center_y; - - // Apply rotation - let rotated_x = rel_x * cos_r - rel_y * sin_r; - let rotated_y = rel_x * sin_r + rel_y * cos_r; - - // Translate back - xs.push(center_x + rotated_x); - ys.push(center_y + rotated_y); + for corner in corners { + let (rotated_x, rotated_y) = rotate_around(corner, center, rotation); + xs.push(rotated_x); + ys.push(rotated_y); } match to_opt { From 2bf63454a37e81b89ab7514bf3f96fa19797c921 Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Sat, 9 Aug 2025 11:53:27 -0700 Subject: [PATCH 08/15] Set rect min size and fix full field rect coordinates --- .../KeepInRectangleOverlay.tsx | 67 ++++++++++++++++--- src/document/PathListStore.ts | 10 ++- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index adceab2544..d7f4217523 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -195,11 +195,45 @@ class KeepInRectangleOverlay extends Component< const newW = Math.abs(draggedLocalX - fixedLocalX); const newH = Math.abs(draggedLocalY - fixedLocalY); + // Get robot dimensions (with bumpers) as minimum size + const minWidth = doc.robotConfig.bumper.length; + const minHeight = doc.robotConfig.bumper.width; + + // Apply minimum size constraints + const constrainedW = Math.max(newW, minWidth); + const constrainedH = Math.max(newH, minHeight); + + // If dimensions were constrained, recalculate center to keep fixed corner in place + let finalCenterX = newCenterX; + let finalCenterY = newCenterY; + + if (constrainedW !== newW || constrainedH !== newH) { + // Convert constrained dimensions back to local coordinates for the dragged corner + const constrainedDraggedLocalX = + ((draggedLocalX >= 0 ? 1 : -1) * constrainedW) / 2; + const constrainedDraggedLocalY = + ((draggedLocalY >= 0 ? 1 : -1) * constrainedH) / 2; + + // Transform constrained dragged corner back to world coordinates using rotate_around + const constrainedDraggedWorld = this.rotate_around( + [ + centerX + constrainedDraggedLocalX, + centerY + constrainedDraggedLocalY + ], + center, + rotation + ); + + // Recalculate center with constrained dragged corner and original fixed corner + finalCenterX = (constrainedDraggedWorld[0] + fixedCorner[0]) / 2; + finalCenterY = (constrainedDraggedWorld[1] + fixedCorner[1]) / 2; + } + // Update rectangle parameters (center-based) - data.x.set(newCenterX); - data.y.set(newCenterY); - data.w.set(newW); - data.h.set(newH); + data.x.set(finalCenterX); + data.y.set(finalCenterY); + data.w.set(constrainedW); + data.h.set(constrainedH); } dragRegionTranslate(event: any) { @@ -242,14 +276,27 @@ class KeepInRectangleOverlay extends Component< } fixWidthHeight() { - // With center-based coordinates, just ensure width and height are positive - if (this.props.data.serialize.props.w.val < 0.0) { - this.props.data.w.set(-this.props.data.serialize.props.w.val); - } + // Get robot dimensions (with bumpers) as minimum size + const minWidth = doc.robotConfig.bumper.length; + const minHeight = doc.robotConfig.bumper.width; + + // Ensure width and height are positive and meet minimum requirements + let width = this.props.data.serialize.props.w.val; + let height = this.props.data.serialize.props.h.val; - if (this.props.data.serialize.props.h.val < 0.0) { - this.props.data.h.set(-this.props.data.serialize.props.h.val); + if (width < 0.0) { + width = -width; } + if (height < 0.0) { + height = -height; + } + + // Apply minimum size constraints + width = Math.max(width, minWidth); + height = Math.max(height, minHeight); + + this.props.data.w.set(width); + this.props.data.h.set(height); } rotate_around( diff --git a/src/document/PathListStore.ts b/src/document/PathListStore.ts index f38ed586b0..9808b48bab 100644 --- a/src/document/PathListStore.ts +++ b/src/document/PathListStore.ts @@ -131,8 +131,14 @@ export const PathListStore = types "first", "last", { - x: { exp: "0 m", val: 0.0 }, - y: { exp: "0 m", val: 0.0 }, + x: { + exp: `${FieldDimensions.FIELD_LENGTH / 2} m`, + val: FieldDimensions.FIELD_LENGTH / 2 + }, + y: { + exp: `${FieldDimensions.FIELD_WIDTH / 2} m`, + val: FieldDimensions.FIELD_WIDTH / 2 + }, w: { exp: `${FieldDimensions.FIELD_LENGTH} m`, val: FieldDimensions.FIELD_LENGTH From e314ee8df12a4f2cb39b39636ae1d1518377e51a Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Sat, 9 Aug 2025 12:19:55 -0700 Subject: [PATCH 09/15] Fix resizing when 1 dimension is constrained by robot size --- .../KeepInRectangleOverlay.tsx | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index d7f4217523..3e8f410356 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -208,25 +208,30 @@ class KeepInRectangleOverlay extends Component< let finalCenterY = newCenterY; if (constrainedW !== newW || constrainedH !== newH) { - // Convert constrained dimensions back to local coordinates for the dragged corner - const constrainedDraggedLocalX = - ((draggedLocalX >= 0 ? 1 : -1) * constrainedW) / 2; - const constrainedDraggedLocalY = - ((draggedLocalY >= 0 ? 1 : -1) * constrainedH) / 2; - - // Transform constrained dragged corner back to world coordinates using rotate_around - const constrainedDraggedWorld = this.rotate_around( - [ - centerX + constrainedDraggedLocalX, - centerY + constrainedDraggedLocalY - ], - center, - rotation - ); - - // Recalculate center with constrained dragged corner and original fixed corner - finalCenterX = (constrainedDraggedWorld[0] + fixedCorner[0]) / 2; - finalCenterY = (constrainedDraggedWorld[1] + fixedCorner[1]) / 2; + // When dimensions are constrained, keep the fixed corner in place + // and calculate the center position based on the constrained dimensions + + // Determine the local coordinates for the fixed corner in the constrained rectangle + const constrainedFixedLocalX = + ((fixedLocalX >= 0 ? 1 : -1) * constrainedW) / 2; + const constrainedFixedLocalY = + ((fixedLocalY >= 0 ? 1 : -1) * constrainedH) / 2; + + // Transform the fixed corner position in constrained rectangle back to world coords + const cos_r = Math.cos(rotation); + const sin_r = Math.sin(rotation); + + // Calculate center position that keeps the fixed corner in its original position + const fixedWorldX = fixedCorner[0]; + const fixedWorldY = fixedCorner[1]; + + // The center is offset from the fixed corner by the local coordinates + finalCenterX = + fixedWorldX - + (constrainedFixedLocalX * cos_r - constrainedFixedLocalY * sin_r); + finalCenterY = + fixedWorldY - + (constrainedFixedLocalX * sin_r + constrainedFixedLocalY * cos_r); } // Update rectangle parameters (center-based) From 2f569fd986bdf7483457204b2db4349ae15e4eba Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Sat, 9 Aug 2025 12:32:20 -0700 Subject: [PATCH 10/15] Use rotate_around --- .../KeepInRectangleOverlay.tsx | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index 3e8f410356..a638cd89b1 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -217,21 +217,23 @@ class KeepInRectangleOverlay extends Component< const constrainedFixedLocalY = ((fixedLocalY >= 0 ? 1 : -1) * constrainedH) / 2; - // Transform the fixed corner position in constrained rectangle back to world coords - const cos_r = Math.cos(rotation); - const sin_r = Math.sin(rotation); - - // Calculate center position that keeps the fixed corner in its original position - const fixedWorldX = fixedCorner[0]; - const fixedWorldY = fixedCorner[1]; - - // The center is offset from the fixed corner by the local coordinates - finalCenterX = - fixedWorldX - - (constrainedFixedLocalX * cos_r - constrainedFixedLocalY * sin_r); - finalCenterY = - fixedWorldY - - (constrainedFixedLocalX * sin_r + constrainedFixedLocalY * cos_r); + // Calculate where the center should be to keep the fixed corner in place + // The center is at a local offset from the fixed corner + const localCenterOffset: [number, number] = [ + -constrainedFixedLocalX, + -constrainedFixedLocalY + ]; + + // Rotate this offset by the rectangle's rotation to get world coordinates + const worldCenterOffset = this.rotate_around( + localCenterOffset, + [0, 0], + rotation + ); + + // Calculate the final center position + finalCenterX = fixedCorner[0] + worldCenterOffset[0]; + finalCenterY = fixedCorner[1] + worldCenterOffset[1]; } // Update rectangle parameters (center-based) From acaf7ad366a149531723e147c500517213f7b356 Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Sat, 9 Aug 2025 22:09:41 -0700 Subject: [PATCH 11/15] fix cursor drifting from corner --- .../field/svg/constraintDisplay/KeepInRectangleOverlay.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index a638cd89b1..8666cbc0d6 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -163,10 +163,10 @@ class KeepInRectangleOverlay extends Component< fixedCornerIndex = 1; // bottom-right stays fixed } - // Move the dragged corner by the drag delta + // Position the dragged corner at the absolute cursor position const newDraggedCorner: [number, number] = [ - rotatedCorners[draggedCornerIndex][0] + event.dx, - rotatedCorners[draggedCornerIndex][1] + event.dy + event.x, + event.y ]; // Fixed corner stays in place From 84abc5299b463b3fd25a2c003fd6388d87f7126e Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Mon, 18 Aug 2025 20:17:35 -0700 Subject: [PATCH 12/15] Pin rectangle corners --- .../KeepInRectangleOverlay.tsx | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index 8666cbc0d6..627cf66786 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -141,25 +141,20 @@ class KeepInRectangleOverlay extends Component< this.rotate_around(corner, center, rotation) ); - // Determine which corner we're dragging and which should stay fixed - let draggedCornerIndex: number; + // Determine which corner should stay fixed let fixedCornerIndex: number; if (!xOffset && !yOffset) { // bottom-left corner drag - draggedCornerIndex = 0; fixedCornerIndex = 2; // top-right stays fixed } else if (xOffset && !yOffset) { // bottom-right corner drag - draggedCornerIndex = 1; fixedCornerIndex = 3; // top-left stays fixed } else if (xOffset && yOffset) { // top-right corner drag - draggedCornerIndex = 2; fixedCornerIndex = 0; // bottom-left stays fixed } else { // top-left corner drag - draggedCornerIndex = 3; fixedCornerIndex = 1; // bottom-right stays fixed } @@ -236,11 +231,39 @@ class KeepInRectangleOverlay extends Component< finalCenterY = fixedCorner[1] + worldCenterOffset[1]; } - // Update rectangle parameters (center-based) - data.x.set(finalCenterX); - data.y.set(finalCenterY); - data.w.set(constrainedW); - data.h.set(constrainedH); + // Calculate all new corner positions with the proposed center and dimensions + const newCorners: [number, number][] = [ + [finalCenterX - constrainedW / 2, finalCenterY - constrainedH / 2], // bottom-left + [finalCenterX + constrainedW / 2, finalCenterY - constrainedH / 2], // bottom-right + [finalCenterX + constrainedW / 2, finalCenterY + constrainedH / 2], // top-right + [finalCenterX - constrainedW / 2, finalCenterY + constrainedH / 2] // top-left + ]; + + // Rotate all corners to world coordinates + const newRotatedCorners = newCorners.map((corner) => + this.rotate_around(corner, [finalCenterX, finalCenterY], rotation) + ); + + // Check if any corner (except the fixed corner itself) is nearly at the same spot as the fixed corner + const hasCornerCollision = newRotatedCorners.some((corner, index) => { + // Skip the fixed corner itself + if (index === fixedCornerIndex) return false; + + const distance = Math.sqrt( + Math.pow(corner[0] - fixedCorner[0], 2) + + Math.pow(corner[1] - fixedCorner[1], 2) + ); + return distance < 0.1; // tolerance for corner collision + }); + + // Only update if no corner would collapse to the fixed corner position + if (!hasCornerCollision) { + // Update rectangle parameters (center-based) + data.x.set(finalCenterX); + data.y.set(finalCenterY); + data.w.set(constrainedW); + data.h.set(constrainedH); + } } dragRegionTranslate(event: any) { From 24814496da4ab7c928a032edfa01804c879ed948 Mon Sep 17 00:00:00 2001 From: TheTripleV Date: Mon, 18 Aug 2025 20:20:32 -0700 Subject: [PATCH 13/15] fmt --- .../KeepInRectangleOverlay.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index 627cf66786..d15e0f4f8c 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -159,10 +159,7 @@ class KeepInRectangleOverlay extends Component< } // Position the dragged corner at the absolute cursor position - const newDraggedCorner: [number, number] = [ - event.x, - event.y - ]; + const newDraggedCorner: [number, number] = [event.x, event.y]; // Fixed corner stays in place const fixedCorner = rotatedCorners[fixedCornerIndex]; @@ -236,26 +233,26 @@ class KeepInRectangleOverlay extends Component< [finalCenterX - constrainedW / 2, finalCenterY - constrainedH / 2], // bottom-left [finalCenterX + constrainedW / 2, finalCenterY - constrainedH / 2], // bottom-right [finalCenterX + constrainedW / 2, finalCenterY + constrainedH / 2], // top-right - [finalCenterX - constrainedW / 2, finalCenterY + constrainedH / 2] // top-left + [finalCenterX - constrainedW / 2, finalCenterY + constrainedH / 2] // top-left ]; - + // Rotate all corners to world coordinates const newRotatedCorners = newCorners.map((corner) => this.rotate_around(corner, [finalCenterX, finalCenterY], rotation) ); - + // Check if any corner (except the fixed corner itself) is nearly at the same spot as the fixed corner const hasCornerCollision = newRotatedCorners.some((corner, index) => { // Skip the fixed corner itself if (index === fixedCornerIndex) return false; - + const distance = Math.sqrt( Math.pow(corner[0] - fixedCorner[0], 2) + - Math.pow(corner[1] - fixedCorner[1], 2) + Math.pow(corner[1] - fixedCorner[1], 2) ); return distance < 0.1; // tolerance for corner collision }); - + // Only update if no corner would collapse to the fixed corner position if (!hasCornerCollision) { // Update rectangle parameters (center-based) From 86a81be29b4e11891bcb739b1cc263638c8aeecb Mon Sep 17 00:00:00 2001 From: Vasista Vovveti Date: Mon, 18 Aug 2025 21:55:54 -0700 Subject: [PATCH 14/15] Update KeepInRectangleOverlay.tsx Co-authored-by: Tyler Veness --- .../field/svg/constraintDisplay/KeepInRectangleOverlay.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index d15e0f4f8c..36346284ae 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -246,10 +246,7 @@ class KeepInRectangleOverlay extends Component< // Skip the fixed corner itself if (index === fixedCornerIndex) return false; - const distance = Math.sqrt( - Math.pow(corner[0] - fixedCorner[0], 2) + - Math.pow(corner[1] - fixedCorner[1], 2) - ); + const distance = Math.hypot(corner[0] - fixedCorner[0], corner[1] - fixedCorner[1]); return distance < 0.1; // tolerance for corner collision }); From 52f2d9050cb1987bfc7a3aa8581af9b935fb08fa Mon Sep 17 00:00:00 2001 From: Tyler Veness Date: Mon, 3 Nov 2025 20:22:37 -0800 Subject: [PATCH 15/15] Run fmtJs --- .../field/svg/constraintDisplay/KeepInRectangleOverlay.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx index 36346284ae..9cb09962da 100644 --- a/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx +++ b/src/components/field/svg/constraintDisplay/KeepInRectangleOverlay.tsx @@ -246,7 +246,10 @@ class KeepInRectangleOverlay extends Component< // Skip the fixed corner itself if (index === fixedCornerIndex) return false; - const distance = Math.hypot(corner[0] - fixedCorner[0], corner[1] - fixedCorner[1]); + const distance = Math.hypot( + corner[0] - fixedCorner[0], + corner[1] - fixedCorner[1] + ); return distance < 0.1; // tolerance for corner collision });