22import { Separator } from "@/components/ui/separator" ;
33import { Card } from "@/components/ui/card" ;
44import { Button } from "@/components/ui/button" ;
5- import { useState , useEffect } from "react"
5+ import { useState , useRef , useEffect } from "react"
66import { useTheme } from "next-themes" ;
77import { Toaster , toast } from "sonner" ;
88import { connectToSpike , sendCodeToSpike , readResponseFromSpike } from "@/components/pybricks/tools" ;
@@ -19,7 +19,9 @@ from pybricks.parameters import Port
1919motor = Motor(Port.A)
2020motor.run_time(100, 2000)
2121`
22- const motorOptions = [ "A" , "B" , "C" , "D" ]
22+ const motor_options = [ "A" , "B" , "C" , "D" ]
23+ const game_board_width = 2362.2 // mm
24+ const game_board_height = 1143.0 // mm
2325
2426class DriveBase {
2527 left_motor : "A" | "B" | "C" | "D" ;
@@ -70,10 +72,10 @@ class Run {
7072 points : Point [ ] ;
7173 actions : Action [ ] ;
7274
73- constructor ( name : string , points : [ number , number ] [ ] , actions : { point : [ number , number ] , function : string , args : any [ ] } [ ] = [ ] ) {
75+ constructor ( name : string , points : Point [ ] , actions : Action [ ] = [ ] ) {
7476 this . name = name ;
75- this . points = points . map ( ( point ) => new Point ( point [ 0 ] , point [ 1 ] ) ) ;
76- this . actions = actions . map ( ( action ) => new Action ( new Point ( action . point [ 0 ] , action . point [ 1 ] ) , action . function , action . args ) ) ;
77+ this . points = points ;
78+ this . actions = actions
7779 }
7880}
7981
@@ -85,49 +87,52 @@ class SplanContent {
8587 constructor ( data : any ) {
8688 this . name = data . name ;
8789 this . drive_base = new DriveBase ( data . drive_base . left_motor , data . drive_base . right_motor , data . drive_base . wheel_diameter , data . drive_base . axle_track ) ;
88- this . runs = data . runs . map ( ( run : any ) => new Run ( run . name , run . points , run . actions ) ) ;
90+ this . runs = data . runs . map ( ( run : any ) => new Run ( run . name , run . points . map ( ( point : any ) => new Point ( point [ 0 ] , point [ 1 ] ) ) , run . actions . map ( ( action : any ) => new Action ( new Point ( action . point [ 0 ] , action . point [ 1 ] ) , action . function , action . args ) ) ) ) ;
8991 }
9092}
9193
9294// Custom PySplanner B-Spline algorithm
93- const GetCurvePoints = ( pts : number [ ] , tension = 0.5 , isClosed = false , numOfSegments = 16 ) => {
95+ const GetCurvePoints = ( pts : number [ ] , tension = 0.5 , isClosed = false , segmentFactor = 0.035 ) => {
9496 let _pts = pts . slice ( 0 ) ; // Copy the array of points
9597 let res = [ ] , x , y , t1x , t2x , t1y , t2y , c1 , c2 , c3 , c4 , st , t ;
9698
99+ // Helper function to compute Euclidean distance
100+ const getDistance = ( x1 : number , y1 : number , x2 : number , y2 : number ) =>
101+ Math . sqrt ( ( x2 - x1 ) ** 2 + ( y2 - y1 ) ** 2 ) ;
102+
97103 // Handle closed vs open curves by adding control points at the ends
98104 if ( isClosed ) {
99- _pts . unshift ( pts [ pts . length - 2 ] , pts [ pts . length - 1 ] ) ; // Repeat last point at the start
100- _pts . push ( pts [ 0 ] , pts [ 1 ] ) ; // Repeat first point at the end
105+ _pts . unshift ( pts [ pts . length - 2 ] , pts [ pts . length - 1 ] ) ;
106+ _pts . push ( pts [ 0 ] , pts [ 1 ] ) ;
101107 } else {
102- // Add mirrored control points at start and end to prevent sharp edges
103- _pts . unshift ( 2 * pts [ 0 ] - pts [ 2 ] , 2 * pts [ 1 ] - pts [ 3 ] ) ;
104- _pts . push ( 2 * pts [ pts . length - 2 ] - pts [ pts . length - 4 ] , 2 * pts [ pts . length - 1 ] - pts [ pts . length - 3 ] ) ;
108+ _pts . unshift ( 2 * pts [ 0 ] - pts [ 2 ] , 2 * pts [ 1 ] - pts [ 3 ] ) ;
109+ _pts . push ( 2 * pts [ pts . length - 2 ] - pts [ pts . length - 4 ] , 2 * pts [ pts . length - 1 ] - pts [ pts . length - 3 ] ) ;
105110 }
106111
107112 // Loop through each segment of the points
108113 for ( let i = 2 ; i < ( _pts . length - 4 ) ; i += 2 ) {
109- for ( t = 0 ; t <= numOfSegments ; t ++ ) { // Interpolate points for each segment
110- // Calculate tangents at the start and end of the segment
114+ const segmentLength = getDistance ( _pts [ i ] , _pts [ i + 1 ] , _pts [ i + 2 ] , _pts [ i + 3 ] ) ;
115+ const numOfSegments = Math . max ( 2 , Math . floor ( segmentLength * segmentFactor ) ) ; // At least 2 segments per section
116+
117+ for ( t = 0 ; t <= numOfSegments ; t ++ ) {
111118 t1x = ( _pts [ i + 2 ] - _pts [ i - 2 ] ) * tension ;
112119 t2x = ( _pts [ i + 4 ] - _pts [ i ] ) * tension ;
113120 t1y = ( _pts [ i + 3 ] - _pts [ i - 1 ] ) * tension ;
114121 t2y = ( _pts [ i + 5 ] - _pts [ i + 1 ] ) * tension ;
115- st = t / numOfSegments ; // Parameter for interpolation between 0 and 1
116-
117- // Catmull-Rom spline basis functions
122+ st = t / numOfSegments ;
123+
118124 c1 = 2 * st ** 3 - 3 * st ** 2 + 1 ;
119125 c2 = - 2 * st ** 3 + 3 * st ** 2 ;
120126 c3 = st ** 3 - 2 * st ** 2 + st ;
121127 c4 = st ** 3 - st ** 2 ;
122128
123- // Calculate the x and y coordinates using the basis functions
124129 x = c1 * _pts [ i ] + c2 * _pts [ i + 2 ] + c3 * t1x + c4 * t2x ;
125130 y = c1 * _pts [ i + 1 ] + c2 * _pts [ i + 3 ] + c3 * t1y + c4 * t2y ;
126-
127- res . push ( { x, y } ) ; // Store the interpolated point
131+
132+ res . push ( { x, y } ) ;
128133 }
129134 }
130- return res ; // Return the array of curve points
135+ return res ;
131136} ;
132137
133138export default function App ( ) {
@@ -137,6 +142,17 @@ export default function App() {
137142 const [ spike_server , SetSpikeServer ] = useState < BluetoothRemoteGATTServer | null > ( null )
138143 const [ pysplan_handler , SetPySplanHandler ] = useState < SplanContent | null > ( null )
139144 const [ run_index , SetRunIndex ] = useState ( 0 )
145+ const img_size_ref = useRef ( null )
146+ const [ img_dimensions , SetImageDimensions ] = useState ( { width : 0 , height : 0 } ) ;
147+
148+ useEffect ( ( ) => {
149+ if ( img_size_ref . current ) {
150+ // @ts -ignore | Fix getBoundingClient due to initial 'null' value
151+ const { width, height } = img_size_ref . current . getBoundingClientRect ( ) ;
152+ SetImageDimensions ( { width, height } ) ;
153+ console . log ( img_dimensions . width , img_dimensions . height ) ;
154+ }
155+ } , [ pysplan_handler ] ) ;
140156
141157 const HandleLoadSplan = ( ) => {
142158 const input = document . createElement ( 'input' ) ;
@@ -180,7 +196,7 @@ export default function App() {
180196 const AddPoint = ( e : React . MouseEvent ) => {
181197 if ( ! pysplan_handler ) { toast . error ( "No run selected" , { duration : 5000 } ) ; return ; }
182198 const rect = e . currentTarget . getBoundingClientRect ( ) ;
183- const new_point = new Point ( e . clientX - rect . left , e . clientY - rect . top ) ;
199+ const new_point = new Point ( e . clientX - rect . left , rect . height - ( e . clientY - rect . top ) ) ;
184200 const new_points = [ ...pysplan_handler . runs [ run_index ] . points , new_point ] ;
185201
186202 if ( new_points . length === 25 ) {
@@ -190,8 +206,8 @@ export default function App() {
190206 return
191207 }
192208
193- pysplan_handler . runs [ run_index ] . points = new_points ;
194- SetPySplanHandler ( pysplan_handler ) ;
209+ const new_run = new Run ( pysplan_handler . runs [ run_index ] . name , new_points , pysplan_handler . runs [ run_index ] . actions ) ;
210+ SetPySplanHandler ( { ... pysplan_handler , runs : [ ... pysplan_handler . runs . slice ( 0 , run_index ) , new_run , ... pysplan_handler . runs . slice ( run_index + 1 ) ] } ) ;
195211 } ;
196212
197213 const flat_points = pysplan_handler ?. runs [ run_index ] . points . flatMap ( p => [ p . x , p . y ] )
@@ -282,14 +298,14 @@ export default function App() {
282298 { pysplan_handler ? (
283299 < div className = "relative flex items-center justify-center w-full h-full border rounded-lg ml-4" >
284300 < div className = "relative" >
285- < img src = { mat_img } className = "w-auto h-auto max-h-[85vh] max-w-[85vw] object-contain" />
301+ < img src = { mat_img } className = "w-auto h-auto max-h-[85vh] max-w-[85vw] object-contain" ref = { img_size_ref } />
286302 < div className = "absolute inset-0 flex items-center justify-center" >
287303 < div className = "w-full h-full relative" onClick = { AddPoint } >
288304 { pysplan_handler . runs [ run_index ] . points . map ( ( p , idx ) => (
289- < div key = { idx } className = "absolute bg-green-500 w-2 h-2 rounded-full" style = { { left : `${ p . x } px` , top : `${ p . y } px` } } />
305+ < div key = { idx } className = "absolute bg-green-500 w-2 h-2 rounded-full" style = { { left : `${ p . x } px` , bottom : `${ p . y } px` } } />
290306 ) ) }
291307 { spline_points . map ( ( p , idx ) => (
292- < div key = { idx } className = "absolute bg-green-400 w-1 h-1 rounded-full" style = { { left : `${ p . x } px` , top : `${ p . y } px` } } />
308+ < div key = { idx } className = "absolute bg-green-400 w-1 h-1 rounded-full" style = { { left : `${ p . x } px` , bottom : `${ p . y } px` } } />
293309 ) ) }
294310 </ div >
295311 </ div >
0 commit comments