Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {

Expand All @@ -18,7 +19,7 @@ const App = () => {
setSpinAngle(0)
}
}
const interval = setInterval(() => incrementAngle(), 1);
const interval = setInterval(() => incrementAngle(), 5);
return () => {
clearInterval(interval);
};
Expand Down Expand Up @@ -82,6 +83,9 @@ const App = () => {
</span>
</header>
<Bike spinAngle={spinAngle} />
<Map>
<CompassControl />
</Map>
</div>
)
}
Expand Down
78 changes: 52 additions & 26 deletions app/src/features/bike/Bike.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
}
Expand All @@ -43,39 +43,65 @@ export const Bike = ({spinAngle}: {spinAngle: number}) => {
<svg width="1500" height="1500">
<defs>
<linearGradient id="rainbow" >
<stop offset="0%" stopColor="rgba(255, 0, 0, 1)"/>
<stop offset="10%" stopColor="rgba(255, 154, 0, 1)"/>
<stop offset="20%" stopColor="rgba(208, 222, 33, 1)"/>
<stop offset="30%" stopColor="rgba(79, 220, 74, 1)"/>
<stop offset="40%" stopColor="rgba(63, 218, 216, 1)"/>
<stop offset="50%" stopColor="rgba(47, 201, 226, 1)"/>
<stop offset="60%" stopColor="rgba(28, 127, 238, 1)"/>
<stop offset="70%" stopColor="rgba(95, 21, 242, 1)"/>
<stop offset="80%" stopColor="rgba(186, 12, 248, 1)"/>
<stop offset="90%" stopColor="rgba(251, 7, 217, 1)"/>
<stop offset="100%" stopColor="rgba(255, 0, 0, 1)"/>
<stop offset="0%" stopColor="rgba(255, 0, 0, 1)" />
<stop offset="10%" stopColor="rgba(255, 154, 0, 1)" />
<stop offset="20%" stopColor="rgba(208, 222, 33, 1)" />
<stop offset="30%" stopColor="rgba(79, 220, 74, 1)" />
<stop offset="40%" stopColor="rgba(63, 218, 216, 1)" />
<stop offset="50%" stopColor="rgba(47, 201, 226, 1)" />
<stop offset="60%" stopColor="rgba(28, 127, 238, 1)" />
<stop offset="70%" stopColor="rgba(95, 21, 242, 1)" />
<stop offset="80%" stopColor="rgba(186, 12, 248, 1)" />
<stop offset="90%" stopColor="rgba(251, 7, 217, 1)" />
<stop offset="100%" stopColor="rgba(255, 0, 0, 1)" />
</linearGradient>
</defs>



<g transform="translate(750 750), scale(0.5), scale(-1 1), rotate(180)">
<circle transform={`rotate(${-spinAngle}, ${bike.fork.end.x}, ${bike.fork.end.y})`} fill="url(#rainbow)" fillOpacity="0.5" cx={bike.fork.end.x} cy={bike.fork.end.y} r={frontWheel.radiusWithTire} />
<circle fill="white" cx={bike.fork.end.x} cy={bike.fork.end.y} r={frontWheel.radius} />
<circle transform={`rotate(${-spinAngle}, ${bike.chainStay.start.x}, ${bike.chainStay.start.y})`} fill="url(#rainbow)" fillOpacity="0.5" cx={bike.chainStay.start.x} cy={bike.chainStay.start.y} r={rearWheel.radiusWithTire} />
<circle fill="white" cx={bike.chainStay.start.x} cy={bike.chainStay.start.y} r={rearWheel.radius} />
<path d={bike.headTube.draw()} stroke="blue" strokeWidth="5" fill="none" />
<path d={bike.fork.draw()} stroke="blue" strokeWidth="5" fill="none" />
<path d={bike.downTube.draw()} stroke="blue" strokeWidth="5" fill="none" />
<path d={bike.seatTube.draw()} stroke="blue" strokeWidth="5" fill="none" />
<path d={bike.chainStay.draw()} stroke="blue" strokeWidth="5" fill="none" />
<path d={bike.seatStay.draw()} stroke="blue" strokeWidth="5" fill="none" />
<path d={bike.topTube.draw()} stroke="blue" strokeWidth="5" fill="none" />
<path d={bike.crank.draw()} stroke="red" strokeWidth="5" fill="none" />
<path d={bike.spacers.draw()} stroke="red" strokeWidth="5" fill="none" />
<path d={bike.stem.draw()} stroke="red" strokeWidth="5" fill="none" />
<path d={bike.seatPost.draw()} stroke="red" strokeWidth="5" fill="none" />
<path d={bike.lowerBody.draw()} stroke="green" strokeWidth="5" fill="none" strokeOpacity=".25" />
<path d={bike.upperBody.draw()} stroke="green" strokeWidth="5" fill="none" strokeOpacity=".25" />
<path d={bike.headTube.draw()} stroke="blue" strokeWidth="15" fill="none" />
<path d={bike.fork.draw()} stroke="blue" strokeWidth="15" fill="none" />
<path d={bike.downTube.draw()} stroke="blue" strokeWidth="15" fill="none" />
<path d={bike.seatTube.draw()} stroke="blue" strokeWidth="15" fill="none" />
<path d={bike.chainStay.draw()} stroke="blue" strokeWidth="15" fill="none" />
<path d={bike.seatStay.draw()} stroke="blue" strokeWidth="15" fill="none" />
<path d={bike.topTube.draw()} stroke="blue" strokeWidth="15" fill="none" />
<path d={bike.crank.draw()} stroke="red" strokeWidth="15" fill="none" />
<path d={bike.spacers.draw()} stroke="red" strokeWidth="15" fill="none" />
<path d={bike.stem.draw()} stroke="red" strokeWidth="15" fill="none" />
<path d={bike.seatPost.draw()} stroke="red" strokeWidth="15" fill="none" />


<g transform={bike.upperBody.spineDrawingTransform()}>
<path fill="#4B0082" d={bike.upperBody.getSpineDrawingPath()}/>
</g>
<g transform={bike.lowerBody.lowerLegTransform()}>
<path fill="#4B0082" d={bike.lowerBody.getLowerLegPath()}/>
</g>
<g transform={bike.lowerBody.feetTransform()}>
<path fill="url(#rainbow)" d={bike.lowerBody.getfeetPath()}/>
</g>
<g transform={bike.lowerBody.upperLegTransform()}>
<path fill="#4B0082" d={bike.lowerBody.getUpperLegPath()}/>
</g>
<g transform={bike.upperBody.headTransform()}>
<path fill="#4B0082" d={bike.upperBody.getHeadDrawing()}/>
</g>
<g transform={bike.upperBody.armDrawingTransform()}>
<path fill="#4B0082" d={bike.upperBody.getArmDrawingPath()}/>
</g>

</g>
</svg>
)
}

// todo
// shape, see bikecad
// feet, rotate
// head size
160 changes: 137 additions & 23 deletions app/src/utils/bike-geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -316,7 +379,6 @@ class LowerBody extends Segment {
x: crank.end.x - riderFootLength * 2 / 3,
y: crank.end.y
}


let knee: Coordinates;

Expand All @@ -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,
Expand All @@ -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()
}
}

Expand Down