From 85be1887bdace47888219bd0dac30e5367ae98ed Mon Sep 17 00:00:00 2001 From: awesumdude42 Date: Tue, 30 Jun 2026 07:40:55 -0700 Subject: [PATCH] bspline class created, with a Vector interface! --- src/app/page.tsx | 3 +- src/components/path-overlay.tsx | 8 +- src/lib/bspline.tsx | 201 ++++++++++++++++++++++++++++++++ types/poses.ts | 7 ++ 4 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 src/lib/bspline.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index 4ab7067..c5aa705 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,6 +6,7 @@ import { Paths, Poses } from "@/hooks/use-visualizer"; import PoseControls from "@/components/pose-controls"; import DrawPaths from "@/components/path-overlay"; + export default function Home() { const { poses, @@ -57,7 +58,7 @@ export default function Home() { id="field-canvas" /> - + diff --git a/src/components/path-overlay.tsx b/src/components/path-overlay.tsx index 092c7cc..449ff29 100644 --- a/src/components/path-overlay.tsx +++ b/src/components/path-overlay.tsx @@ -1,12 +1,16 @@ 'use client'; -import { useEffect, useRef } from "react"; +import { bspline } from "@/lib/bspline"; +import { useEffect, useRef, useState } from "react"; interface PathDrawProps { poses: Pose[]; + paths: Path[]; } -export default function DrawPaths({ poses }: PathDrawProps) { +export default function DrawPaths({ poses, paths}: PathDrawProps) { + + const canvasRef = useRef(null); useEffect(() => { diff --git a/src/lib/bspline.tsx b/src/lib/bspline.tsx new file mode 100644 index 0000000..eb850cf --- /dev/null +++ b/src/lib/bspline.tsx @@ -0,0 +1,201 @@ +import { useState } from "react"; + +export function bspline(poses:Pose[]){ + + + const CMatrix = [ + [-1.0 / 6.0, 3.0 / 6.0, -3.0 / 6.0, 1.0 / 6.0], + [3.0 / 6.0, -6.0 / 6.0, 3.0 / 6.0, 0.0], + [-3.0 / 6.0, 0.0, 3.0 / 6.0, 0.0], + [1.0 / 6.0, 4.0 / 6.0, 1.0 / 6.0, 0.0] + ] + + //first, make sure all of the poses have valid values(kinda copied from path-overlay) + + poses.forEach((pose) => { + if (pose.x === null || pose.y === null) return; + }); + + //then, make sure the list has a valid amount of poses + + if(poses.length<2) return; + + //convert all poses to vectors + + const [poseVector,setPoseVector] = useState([]); + poses.forEach((pose) => { + + const temp:Vector = { + x:pose.x ?? 0, // if pose.x is null, give a value of 0. Our filter above will automatically + // return if any of the values are null, so this will never happen, it just lets the Vector class have pure number types instead + // of number | null + y:pose.y ?? 0 + }; + + setPoseVector((prevVectors)=>{ + + return [...prevVectors, temp] + }) + + }); + + + //the first thing we need to do is to get the ghost points + + //the formula is P_0+(-1(P_1-P_0)) where P_0 is the axis point(that you reflect across, otherwise known as the endpoint) + // and P_1 is the point being reflected(otherwise known as the second, or second to last point) + + + + + const [ghostPoints,setGhostPoints] = useState([]);// make a list for the ghost points + + const neg1 = poses.length-1 + const neg2 = poses.length-2 + + //will give error cus the value "might be null" even though we filtered for it already, so just add an if statement + if((poseVector[0].x!=null && poseVector[1].x!=null && poseVector[neg1].x!=null && poseVector[neg2].x!=null + && poseVector[0].y!=null && poseVector[1].y!=null && poseVector[neg1].y!=null && poseVector[neg2].y!=null) + ){ + setGhostPoints([ + { + x:poseVector[0].x+((poseVector[1].x - poseVector[0].x) * -1), + y:poseVector[0].y+((poseVector[1].y - poseVector[0].y) * -1) + }, + { + x:poseVector[neg1].x+((poseVector[neg2].x - poseVector[neg1].x) * -1), + y:poseVector[neg1].y+((poseVector[neg2].y - poseVector[neg1].y) * -1) + + } + ]) + } + + //now that our ghost points are sorted out, we can make the array that will be put into the spline + const [splinePoints,setSplinePoints] = useState([]); + // loop twice more than the poseVector array because we have 2 ghost points + for(let i = 0; i{ + + return[...prevPoints,ghostPoints[0]] + + } + ) + } + //adds ghost points at the end + else if(i == poseVector.length+1){// because its 0 indexed, the last loop will be when i is 1 less than poseVector.length+2 + + setSplinePoints( + (prevPoints)=>{ + + return [...prevPoints,ghostPoints[1]] + + } + ) + } + else{ + + setSplinePoints( + (prevPoints)=>{ + + return [...prevPoints,poseVector[i-1]]//since arrays are 0-indexed, and when the value of i is 0, + // the ghost point is added, we need to subtract one to add all elements in the array + + } + ) + } + } + + + + //now, the control points are in order. Let's make the actual b spline evaluate. + + + const numSegments = splinePoints.length-3 + const [cx,setCx] = useState([]) + const [cy,setCy] = useState([]) + + + //create the b spline segments, 4 control points per segment, each contribute to the whole spline, keeping it 4th degree + //as joel's bspline class states in the pathing repo, "they are evaluated using a sliding 4-point window" + + for(let i = 0; i[...prevCx,tempx] + ) + const tempy:number[] = multiplyMatrices(CMatrix,yWindow) + setCy( + (prevCy)=>[...prevCy,tempy] + ) + + + + } + + + //evaluate function + // copied from joels thing + const evaluate = (t:number)=>{ + + if (t >= 1.0) t = 0.999999; + if (t < 0.0) t = 0.0; + + const continuousIndex = t * numSegments + const segment = Math.trunc(continuousIndex) //should do the same thing as type casting to int + const localT = continuousIndex - segment + + + const cX = cx[segment] + const cY = cy[segment] + + + const x = ((cX[0] * localT + cX[1]) * localT + cX[2]) * localT + cX[3] + const y = ((cY[0] * localT + cY[1]) * localT + cY[2]) * localT + cY[3] + + const returnValue:Vector = { + x:x, + y:y + } + return returnValue; + } + + return {evaluate} +} + + +//this function is made specifically for multiplying the Cmatrix and the windows +function multiplyMatrices(matrix:number[][], window:number[]){ + + + const [result,setResult] = useState([]); + + for (let i = 0; i < matrix.length; i++) { + let sum:number = 0.0; + for (let j = 0; j < matrix[0].length; j++) { + sum += matrix[i][j] * window[j]; + } + setResult((prevResults)=>{ + return[...prevResults,sum] + }) + } + return result; + + +} diff --git a/types/poses.ts b/types/poses.ts index 8a54b50..ea1b11b 100644 --- a/types/poses.ts +++ b/types/poses.ts @@ -7,4 +7,11 @@ interface Pose { radius: number | null arcPose: boolean local: boolean +} + +//this interface is for calculating the bspline, +// as it doesn't need the other things for poses which will just be plugged into the b spline +interface Vector{ + x:number //these two values are null to match the type of the poses, it will never actually be null + y:number } \ No newline at end of file