1+ import fs from 'fs' ;
2+ import readline from 'readline' ;
3+ import path from 'path' ;
4+
5+ // Add definition for __dirname in ES modules
6+ // const __dirname = path.dirname(new URL(import.meta.url).pathname);
7+
8+ // Get file path from command line arguments
9+ const filePath = process . argv [ 2 ] ;
10+
11+ // Set the environment dimensions
12+ const ball_diameter_mm = 10 ;
13+ const table_diameter_mm = 406 ;
14+
15+ // Calculate the normalized resolution (where 1.0 is the full radius of the table)
16+ const norm_distance_threshold = ( ( 0.5 * ball_diameter_mm ) / ( 0.5 * table_diameter_mm ) ) . toFixed ( 3 ) ;
17+
18+ if ( ! filePath ) {
19+ console . error ( 'Usage: node optimize-thr.js <path-to-file.thr>' ) ;
20+ process . exit ( 1 ) ;
21+ }
22+
23+ console . log ( `Ball Diameter: ${ ball_diameter_mm } mm` ) ;
24+ console . log ( `Table Diameter: ${ table_diameter_mm } mm` ) ;
25+ console . log ( `Normalized Threshold: ${ norm_distance_threshold } ` ) ;
26+
27+ // Create a readable stream and readline interface
28+ const rl = readline . createInterface ( {
29+ input : fs . createReadStream ( filePath ) ,
30+ crlfDelay : Infinity
31+ } ) ;
32+
33+ let prevTheta = null ;
34+ let prevRho = null ;
35+ let old_instruction_count = 0 ;
36+ let new_instruction_count = 0 ;
37+
38+ let new_path = [ ] ;
39+
40+ rl . on ( 'line' , ( line ) => {
41+
42+ // Preserve comment lines
43+ if ( line . startsWith ( '#' ) ) {
44+ new_path . push ( [ line ] ) ;
45+ return ;
46+ }
47+
48+ // Skip lines that aren't position data
49+ const match = line . match ( / ^ ( [ + - ] ? \d * \. ? \d + ) \s + ( [ + - ] ? \d * \. ? \d + ) / ) ;
50+ if ( ! match ) {
51+ new_path . push ( [ line ] ) ;
52+ return ;
53+ }
54+
55+ // Count old lines
56+ old_instruction_count ++ ;
57+
58+ // Parse data from line
59+ const [ , num1Str , num2Str ] = match ;
60+ const theta = parseFloat ( num1Str ) ;
61+ const rho = parseFloat ( num2Str ) ;
62+
63+ // First data point
64+ if ( prevTheta === null && prevRho === null ) {
65+ new_path . push ( [ theta , rho ] ) ;
66+ new_instruction_count ++ ;
67+ prevTheta = theta ;
68+ prevRho = rho ;
69+ return ;
70+ }
71+
72+ // Calculate distance between two polar points
73+ const distance = Math . sqrt (
74+ prevRho * prevRho + rho * rho - 2 * prevRho * rho * Math . cos ( theta - prevTheta )
75+ ) ;
76+
77+ if ( distance > norm_distance_threshold ) {
78+ new_path . push ( [ theta , rho ] ) ;
79+ new_instruction_count ++ ;
80+ prevTheta = theta ;
81+ prevRho = rho ;
82+ }
83+ } ) ;
84+
85+ rl . on ( 'close' , ( ) => {
86+
87+ // Transform new_path: combine each coordinate pair into a string with a space separator.
88+ // Comments are already stored as single-element arrays, so they'll just be output as-is
89+ const combinedPath = new_path . map ( pair => pair . length === 1 ? pair [ 0 ] : pair . join ( ' ' ) ) ;
90+
91+ // Determine output file path: same directory as input file but with a .opt.thr extension.
92+ const parsedPath = path . parse ( filePath ) ;
93+ const outputFilePath = path . join ( parsedPath . dir , parsedPath . name + '.opt.thr' ) ;
94+
95+ // Write one line per coordinate pair with "number space number"
96+ fs . writeFileSync ( outputFilePath , combinedPath . join ( '\n' ) , 'utf8' ) ;
97+
98+ // Print info
99+ console . log ( `Original Point Count: ${ old_instruction_count . toLocaleString ( ) } ` ) ;
100+ console . log ( `Optimized Point Count: ${ new_instruction_count . toLocaleString ( ) } ` ) ;
101+ console . log ( `Saved to ${ outputFilePath } ` ) ;
102+ } ) ;
0 commit comments