diff --git a/app/src/App.tsx b/app/src/App.tsx index af5f9ed..d45dd26 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -5,6 +5,7 @@ import { Counter } from "./features/counter/Counter" import { Quotes } from "./features/quotes/Quotes" import { Bike } from "./features/bike/Bike" import logo from "./logo.svg" +import { Map, CompassControl } from 'react-mapycz'; const App = () => { @@ -18,7 +19,7 @@ const App = () => { setSpinAngle(0) } } - const interval = setInterval(() => incrementAngle(), 1); + const interval = setInterval(() => incrementAngle(), 5); return () => { clearInterval(interval); }; @@ -82,6 +83,9 @@ const App = () => { + + + ) } diff --git a/app/src/features/bike/Bike.tsx b/app/src/features/bike/Bike.tsx index 88edf20..041a722 100644 --- a/app/src/features/bike/Bike.tsx +++ b/app/src/features/bike/Bike.tsx @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { BikeGeometry } from "../../utils/bike-geometry"; import { Wheel } from "../../utils/wheel"; -export const Bike = ({spinAngle}: {spinAngle: number}) => { +export const Bike = ({ spinAngle }: { spinAngle: number }) => { const bike = useMemo(() => new BikeGeometry( { reachLength: 389, @@ -30,7 +30,7 @@ export const Bike = ({spinAngle}: {spinAngle: number}) => { riderArmLength: 690, riderSpineLength: 700, effectiveSeatTubeAngle: 72.5, - handleBarReach: 76, + handleBarReach: 106, handleBarHeight: 0, spinAngle: spinAngle, } @@ -43,39 +43,65 @@ export const Bike = ({spinAngle}: {spinAngle: number}) => { - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } + +// todo +// shape, see bikecad +// feet, rotate +// head size \ No newline at end of file diff --git a/app/src/utils/bike-geometry.ts b/app/src/utils/bike-geometry.ts index fd00da7..dc725cc 100644 --- a/app/src/utils/bike-geometry.ts +++ b/app/src/utils/bike-geometry.ts @@ -288,10 +288,73 @@ class Fork extends Segment { } } -class LowerBody extends Segment { +class RoundedSegment extends Segment { + + protected readonly radius: number; + + constructor({start, end, radius}: {start:Coordinates, end:Coordinates, radius:number}){ + super({start, end}); + this.radius = radius; + } + + draw(): string { + const aboveStart = { + x: this.start.x + this.radius , + y: this.start.y + this.radius / 2, // above in horizontal + } + const aboveEnd = { + x: this.end.x + this.radius / 2, // above in vertical + y: this.end.y + this.radius + } + const angle = Math.atan2(aboveEnd.y - aboveStart.y, aboveEnd.x - aboveStart.x); + const offsetX = this.radius * Math.sin(angle); + const offsetY = this.radius * Math.cos(angle); + + return ` + M${aboveStart.x - offsetX},${aboveStart.y + offsetY} + A${this.radius},${this.radius} 0 0,1 ${aboveStart.x + offsetX},${aboveStart.y - offsetY} + L${aboveEnd.x + offsetX},${aboveEnd.y - offsetY} + A${this.radius},${this.radius} 0 0,1 ${aboveEnd.x - offsetX},${aboveEnd.y + offsetY} + Z + `; + } +} + +class SvgDrawing { + private readonly _path: string; + private readonly _start: Coordinates; + private readonly _end: Coordinates; + + constructor({start, end, path}:{start: Coordinates, end: Coordinates, path: string}){ + this._path = path; + this._start = start; + this._end = end ; + } + + transform(newStart: Coordinates, newEnd: Coordinates): string{ + let result = `translate(${newStart.x}, ${newStart.y}) ` + result += `rotate(${(- Math.atan2((this._end.y - this._start.y), (this._end.x - this._start.x))) * 180 / Math.PI}) ` + result += `scale(${distance(newStart, newEnd) / distance(this._start, this._end)}) ` + result += `rotate(${(( Math.atan2((newEnd.y - newStart.y), (newEnd.x - newStart.x))) * 180 / Math.PI)}) ` + result += `translate(${-this._start.x}, ${-this._start.y})` + return result + } + get path(){ + return this._path; + } + +} + +class LowerBody { private readonly __brand = "LowerBody"; - private readonly knee: Coordinates; - private readonly heel: Coordinates; + private readonly upperLeg: Segment; + private readonly upperLegDrawing: SvgDrawing; + private readonly lowerLeg: Segment; + private readonly lowerLegDrawing: SvgDrawing; + private readonly feet: Segment; + private readonly feetDrawing: SvgDrawing; + private readonly _knee: Coordinates; + private readonly _end: Coordinates; constructor({ bottomBracket, @@ -316,7 +379,6 @@ class LowerBody extends Segment { x: crank.end.x - riderFootLength * 2 / 3, y: crank.end.y } - let knee: Coordinates; @@ -336,29 +398,56 @@ class LowerBody extends Segment { y: Math.abs(-lowerLeg * Math.sin(theta - gamma)) + heel.y }; } - - const end = { + this._end = { x: heel.x + riderFootLength, y: heel.y } - super({start: seatPost.start, end}); - this.knee = knee; - this.heel = heel; + this._knee = knee; + this.upperLeg = new Segment({start: seatPost.start, end: knee}); + this.upperLegDrawing = new SvgDrawing({start: {x: 1594, y: 484}, end: {x:479, y:1900}, path: "M671 368C253.5 994 143.923 1267.02.5 1918c34.388 327.13 387.5 238.5 506.5 0S1495 857.001 1610 472c115-385-521.5-730-939-104Z"}) + this.lowerLeg= new Segment({start: knee, end: heel}); + this.lowerLegDrawing = new SvgDrawing({start: {x:467, y:178} , end: {x: 1087, y: 2102}, path: "M79 415C36.5 231.5 23.27 163.365 69 107c86-106 310.839-133.474 403 0 261 378 247 774 281 976s224 702 201.5 750.5-287.5 54.5-298.5 0S121.5 598.5 79 415Z"}) + this.feet = new Segment({start: heel, end: this._end}); + this.feetDrawing = new SvgDrawing({start: {x: 1306, y: 381}, end: {x:3, y: 719}, path: "M514 259C366.818 382.995 131.999 395 50 425c-82 30-44 128 16 160 60 31.999 276.409 78.931 414 42 137.59-36.931 220.98-80.143 394-154 76.928-33.565 150.75-32.65 256-48 80.39-15.522 120.89-28.533 142-100 40.12-58.305-28.99-315.54-90-322-56.24 21.285-51.78 29.893-52 46-21.69 106.773-127.23 169.389-460 24-27.5 84.715-83.497 124.919-156 186Z"}) + + } draw(): string { - return d3.line()([ - [this.start.x, this.start.y], - [this.knee.x, this.knee.y], - [this.heel.x, this.heel.y], - [this.end.x, this.end.y], - ]) ?? ""; + return this.upperLeg.draw() + " " + this.lowerLeg.draw() + " " + this.feet.draw() + } + + upperLegTransform(): string { + return this.upperLegDrawing.transform(this.upperLeg.start, this.upperLeg.end); + } + lowerLegTransform(): string{ + return this.lowerLegDrawing.transform(this.lowerLeg.start, this.lowerLeg.end); + } + feetTransform(): string{ + return this.feetDrawing.transform(this.lowerLeg.end, this._end); + } + + getfeetPath(): string { + return this.feetDrawing.path; + } + + getUpperLegPath(){ + return this.upperLegDrawing.path; + } + + getLowerLegPath(){ + return this.lowerLegDrawing.path; } } -class UpperBody extends Segment { +class UpperBody { private readonly __brand = "UpperBody"; private readonly shoulder: Coordinates; + private readonly spine: Segment; + private readonly spineDrawing: SvgDrawing; + private readonly arm: Segment; + private readonly armDrawing: SvgDrawing; + private readonly headDrawing: SvgDrawing; constructor({ seatPost, @@ -385,20 +474,45 @@ class UpperBody extends Segment { y = m * x + k; } - super({start: seatPost.start, end: handleBar.coordinates}); - this.shoulder = { x: x, y: y }; + + this.spine = new RoundedSegment({start: seatPost.start, end: this.shoulder, radius:45}); + this.spineDrawing = new SvgDrawing({start: {x:2545, y:2222}, end: {x:617, y:1}, path: "M482.5 1C340.094 266.091 306.455 285.78 1.152 388 64 500 53.63 626.733 114 774c132 322 286.916 552.85 460.001 618C1286 1660 1452 1798 1624 1996s499.86 428.23 721.5 375c221.65-53.23 308.5-223 283.5-462C2610 1562 1968.68 648.966 482.5 1Z"}) + this.arm= new RoundedSegment({start: this.shoulder, end: handleBar.coordinates, radius: 35}); + this.armDrawing = new SvgDrawing({start: {x:2244 , y:0}, end: {x:412, y:2246}, path: "M993.334 1698.26c161.796-158.66 235.826-212.3 357.876-373.01 182.26-239.99 312.78-564.56 354.07-685.251 41.28-120.69 93.19-226.603 122.98-275.113 29.8-48.511 91.91-99.872 185.4-114.771C2221.3 217.021 2381.5 476.854 2328.28 640c-53.23 163.146-141.57 240.184-194.8 300.683L1619.61 1539.6c-44.57 51-211.56 237.45-310.28 340.94-98.72 103.5-495.311 336.85-538.24 393.26-42.929 56.41-51.873 87.35-93.752 97.9-19.512 3.37-257.953 45.96-369.296 71.31-111.343 25.34-151.592 64.19-196.545 163.29-6.663 23.63-6.602 37.88-29.982 42.2-12.841 1.37-20.292.83-36.168-13.51 0 0-13.325-19.95-13.325-42.19s-28.554-47.26-28.554-64.14c0-16.88 10.659-52.05 9.112-85.65-1.08-23.48-9.112-43.72-9.112-59.5 0-15.78 2.476-36.35 28.554-70.04 10.89-16.28 49.493-64.62 49.493-64.62s12.082-54.74 58.06-68.72c45.977-13.99 253.177 17.72 292.2 10.13 39.024-7.6 39.535-5.57 62.819-16.04 25.749-11.57 60.915-37.13 60.915-37.13 27.381-29.19 276.028-280.18 437.825-438.83Z"}) + this.headDrawing = new SvgDrawing({start: {x:1138, y:747}, end: {x:1141, y:807}, path: "M92.323 432.507s2.201 35.318 15.893 62.078l46.896 45.198 3.899 56.752 26.081 144.283-62.26 96.344c-21.062 44.888-17.33 57.797 10.102 64.467l77.698 25.534c26.967 2.66-30.412 57.058-7.392 68.1 23.019 11.037 39.779-7.211 41.257 4.479.895 7.068-46.503 20.228-21.792 33.518 24.71 13.29 53.365 68.19 52.206 98.21-1.159 30.01-6.59 51.75 14.665 73.84 21.256 22.09 40.289 58.44 170.002 18.45 40.527-11.47 63.469-19.92 99.416 10.79l44.493 39.54L792 1371c164 24 455.06-147.24 402-375.737-1.94-8.334-81.52-92.041-78.28-121.866l2.37-21.872c31.68-11.499 36.83-25.326 44.04-44.696l.06-.181c7.25-19.464 45.78-4.713 45.78-4.713l39.09-45.045-5.8-20.746c-7.74-11.737-7.61-20.278 4.42-40.636l51.2 5.558c11.43 1.241 19.97-26.499 19.97-26.499l-24.52-66.031s-29.53-114.406-11.35-108.486c18.17 5.921 35.08-41.872 29.71-60.647-2.09-7.33-5.06-8.947-5.44-14.673-.61-8.939 2.19-20.255 2.19-20.255l-295.99-238.462S387.687-199.941 131.06 395.344c-2.07 7.944-38.737 37.163-38.737 37.163Z"}) + } + + spineDrawingTransform(): string{ + return this.spineDrawing.transform(this.spine.start, this.spine.end); + } + + getSpineDrawingPath(): string{ + return this.spineDrawing.path; + } + + getArmDrawingPath(): string{ + return this.armDrawing.path + } + + armDrawingTransform(): string{ + return this.armDrawing.transform(this.arm.start, this.arm.end); + } + + headTransform(): string { + return this.headDrawing.transform({x: this.spine.end.x + 45, y: this.spine.end.y + 70}, + {x: this.spine.end.x + 45, y: this.spine.end.y - (this.spine.end.y - this.spine.start.y) / 44 + 70 } ) + } + + getHeadDrawing(): string{ + return this.headDrawing.path; } draw(): string { - return d3.line()([ - [this.start.x, this.start.y], - [this.shoulder.x, this.shoulder.y], - [this.end.x, this.end.y], - ]) ?? ""; + return this.arm.draw() } }