@johnmorrisdotca/dougu-sports 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -1
- package/LICENSES/README.md +4 -3
- package/NOTICE.md +54 -14
- package/README.md +251 -80
- package/dist/emblems/award/badge.d.ts +2 -0
- package/dist/emblems/award/badge.js +14 -0
- package/dist/emblems/award/crown.d.ts +2 -0
- package/dist/emblems/award/crown.js +15 -0
- package/dist/emblems/award/medal.d.ts +1 -1
- package/dist/emblems/award/medal.js +10 -15
- package/dist/emblems/award/shield.d.ts +2 -0
- package/dist/emblems/award/shield.js +11 -0
- package/dist/emblems/award/star.d.ts +2 -0
- package/dist/emblems/award/star.js +11 -0
- package/dist/emblems/ball/ball.afl.d.ts +1 -0
- package/dist/emblems/ball/ball.afl.js +24 -0
- package/dist/emblems/ball/ball.baseball.d.ts +1 -0
- package/dist/emblems/ball/ball.baseball.js +14 -0
- package/dist/emblems/ball/ball.basketball.d.ts +7 -0
- package/dist/emblems/ball/ball.basketball.js +32 -0
- package/dist/emblems/ball/ball.basketball3x3.d.ts +1 -0
- package/dist/emblems/ball/ball.basketball3x3.js +10 -0
- package/dist/emblems/ball/ball.basketball6.d.ts +1 -0
- package/dist/emblems/ball/ball.basketball6.js +10 -0
- package/dist/emblems/ball/ball.core.d.ts +111 -0
- package/dist/emblems/ball/ball.core.js +79 -0
- package/dist/emblems/ball/ball.cricket.d.ts +5 -0
- package/dist/emblems/ball/ball.cricket.js +31 -0
- package/dist/emblems/ball/ball.curlingstone.d.ts +3 -0
- package/dist/emblems/ball/ball.curlingstone.js +46 -0
- package/dist/emblems/ball/ball.discus.d.ts +1 -0
- package/dist/emblems/ball/ball.discus.js +24 -0
- package/dist/emblems/ball/ball.engine.d.ts +114 -0
- package/dist/emblems/ball/ball.engine.js +342 -0
- package/dist/emblems/ball/ball.family.dotted.d.ts +27 -0
- package/dist/emblems/ball/ball.family.dotted.js +20 -0
- package/dist/emblems/ball/ball.family.panelled.d.ts +7 -0
- package/dist/emblems/ball/ball.family.panelled.js +60 -0
- package/dist/emblems/ball/ball.family.prolate.d.ts +16 -0
- package/dist/emblems/ball/ball.family.prolate.js +29 -0
- package/dist/emblems/ball/ball.family.seamed.d.ts +31 -0
- package/dist/emblems/ball/ball.family.seamed.js +26 -0
- package/dist/emblems/ball/ball.fieldhockey.d.ts +1 -0
- package/dist/emblems/ball/ball.fieldhockey.js +15 -0
- package/dist/emblems/ball/ball.football.d.ts +3 -0
- package/dist/emblems/ball/ball.football.js +51 -0
- package/dist/emblems/ball/ball.gaelic.d.ts +2 -0
- package/dist/emblems/ball/ball.gaelic.js +30 -0
- package/dist/emblems/ball/ball.golf.d.ts +1 -0
- package/dist/emblems/ball/ball.golf.js +15 -0
- package/dist/emblems/ball/ball.hammer.d.ts +1 -0
- package/dist/emblems/ball/ball.hammer.js +31 -0
- package/dist/emblems/ball/ball.handball.d.ts +1 -0
- package/dist/emblems/ball/ball.handball.js +25 -0
- package/dist/emblems/ball/ball.index.d.ts +48 -0
- package/dist/emblems/ball/ball.index.js +103 -0
- package/dist/emblems/ball/ball.math.d.ts +38 -0
- package/dist/emblems/ball/ball.math.js +105 -0
- package/dist/emblems/ball/ball.object.d.ts +38 -0
- package/dist/emblems/ball/ball.object.js +20 -0
- package/dist/emblems/ball/ball.padel.d.ts +1 -0
- package/dist/emblems/ball/ball.padel.js +13 -0
- package/dist/emblems/ball/ball.pickleball.d.ts +2 -0
- package/dist/emblems/ball/ball.pickleball.js +26 -0
- package/dist/emblems/ball/ball.polyhedra.d.ts +15 -0
- package/dist/emblems/ball/ball.polyhedra.js +63 -0
- package/dist/emblems/ball/ball.puck.d.ts +3 -0
- package/dist/emblems/ball/ball.puck.js +22 -0
- package/dist/emblems/ball/ball.roundnet.d.ts +1 -0
- package/dist/emblems/ball/ball.roundnet.js +15 -0
- package/dist/emblems/ball/ball.rugby.d.ts +1 -0
- package/dist/emblems/ball/ball.rugby.js +35 -0
- package/dist/emblems/ball/ball.sepaktakraw.d.ts +1 -0
- package/dist/emblems/ball/ball.sepaktakraw.js +48 -0
- package/dist/emblems/ball/ball.shuttlecock.d.ts +6 -0
- package/dist/emblems/ball/ball.shuttlecock.js +93 -0
- package/dist/emblems/ball/ball.sliotar.d.ts +1 -0
- package/dist/emblems/ball/ball.sliotar.js +14 -0
- package/dist/emblems/ball/ball.smooth.d.ts +4 -0
- package/dist/emblems/ball/ball.smooth.js +51 -0
- package/dist/emblems/ball/ball.soccer.d.ts +2 -0
- package/dist/emblems/ball/ball.soccer.js +54 -0
- package/dist/emblems/ball/ball.softball.d.ts +1 -0
- package/dist/emblems/ball/ball.softball.js +14 -0
- package/dist/emblems/ball/ball.squash.d.ts +7 -0
- package/dist/emblems/ball/ball.squash.js +27 -0
- package/dist/emblems/ball/ball.stack.d.ts +48 -0
- package/dist/emblems/ball/ball.stack.js +127 -0
- package/dist/emblems/ball/ball.stitch.d.ts +10 -0
- package/dist/emblems/ball/ball.stitch.js +59 -0
- package/dist/emblems/ball/ball.tennis.d.ts +1 -0
- package/dist/emblems/ball/ball.tennis.js +13 -0
- package/dist/emblems/ball/ball.teqball.d.ts +1 -0
- package/dist/emblems/ball/ball.teqball.js +27 -0
- package/dist/emblems/ball/ball.volleyball.d.ts +3 -0
- package/dist/emblems/ball/ball.volleyball.js +56 -0
- package/dist/emblems/ball/ball.waterpolo.d.ts +1 -0
- package/dist/emblems/ball/ball.waterpolo.js +31 -0
- package/dist/emblems/crest/diamond.d.ts +2 -0
- package/dist/emblems/crest/diamond.js +18 -0
- package/dist/emblems/crest/pennant.d.ts +2 -0
- package/dist/emblems/crest/pennant.js +18 -0
- package/dist/emblems/crest/roundel.d.ts +2 -0
- package/dist/emblems/crest/roundel.js +18 -0
- package/dist/emblems/crest/shield.d.ts +2 -0
- package/dist/emblems/crest/shield.js +17 -0
- package/dist/emblems/emblem.api.d.ts +77 -9
- package/dist/emblems/emblem.api.js +56 -10
- package/dist/emblems/emblem.compose.d.ts +31 -0
- package/dist/emblems/emblem.compose.js +91 -0
- package/dist/emblems/emblem.core.d.ts +89 -7
- package/dist/emblems/emblem.core.js +85 -11
- package/dist/emblems/emblem.descriptions.d.ts +7 -0
- package/dist/emblems/emblem.descriptions.js +60 -0
- package/dist/emblems/emblem.display.d.ts +75 -0
- package/dist/emblems/emblem.display.js +214 -0
- package/dist/emblems/emblem.engrave.d.ts +50 -0
- package/dist/emblems/emblem.engrave.js +145 -0
- package/dist/emblems/emblem.equipment.d.ts +24 -0
- package/dist/emblems/emblem.equipment.js +57 -0
- package/dist/emblems/emblem.flip.d.ts +10 -0
- package/dist/emblems/emblem.flip.js +45 -0
- package/dist/emblems/emblem.ids.d.ts +44 -30
- package/dist/emblems/emblem.ids.js +51 -17
- package/dist/emblems/emblem.lathe.d.ts +62 -0
- package/dist/emblems/emblem.lathe.js +426 -0
- package/dist/emblems/emblem.medal.d.ts +6 -0
- package/dist/emblems/emblem.medal.js +38 -0
- package/dist/emblems/emblem.pennant.d.ts +3 -0
- package/dist/emblems/emblem.pennant.js +79 -0
- package/dist/emblems/emblem.registry.js +162 -4
- package/dist/emblems/emblem.shapes.d.ts +48 -12
- package/dist/emblems/emblem.shapes.js +138 -31
- package/dist/emblems/emblem.sprite.d.ts +38 -0
- package/dist/emblems/emblem.sprite.js +65 -0
- package/dist/emblems/emblem.uri.d.ts +22 -0
- package/dist/emblems/emblem.uri.js +29 -0
- package/dist/emblems/gear/gear.badmintonracket.d.ts +1 -0
- package/dist/emblems/gear/gear.badmintonracket.js +19 -0
- package/dist/emblems/gear/gear.baseballglove.d.ts +1 -0
- package/dist/emblems/gear/gear.baseballglove.js +21 -0
- package/dist/emblems/gear/gear.budo.d.ts +17 -0
- package/dist/emblems/gear/gear.budo.js +60 -0
- package/dist/emblems/gear/gear.core.d.ts +64 -0
- package/dist/emblems/gear/gear.core.js +127 -0
- package/dist/emblems/gear/gear.cricketbat.d.ts +1 -0
- package/dist/emblems/gear/gear.cricketbat.js +17 -0
- package/dist/emblems/gear/gear.field.d.ts +6 -0
- package/dist/emblems/gear/gear.field.js +81 -0
- package/dist/emblems/gear/gear.fieldhockeystick.d.ts +1 -0
- package/dist/emblems/gear/gear.fieldhockeystick.js +16 -0
- package/dist/emblems/gear/gear.footballhelmet.d.ts +1 -0
- package/dist/emblems/gear/gear.footballhelmet.js +16 -0
- package/dist/emblems/gear/gear.goaliemask.d.ts +1 -0
- package/dist/emblems/gear/gear.goaliemask.js +23 -0
- package/dist/emblems/gear/gear.goalnet.d.ts +1 -0
- package/dist/emblems/gear/gear.goalnet.js +25 -0
- package/dist/emblems/gear/gear.hockeystick.d.ts +1 -0
- package/dist/emblems/gear/gear.hockeystick.js +15 -0
- package/dist/emblems/gear/gear.index.d.ts +22 -0
- package/dist/emblems/gear/gear.index.js +49 -0
- package/dist/emblems/gear/gear.kendo.d.ts +2 -0
- package/dist/emblems/gear/gear.kendo.js +41 -0
- package/dist/emblems/gear/gear.kyudo.d.ts +2 -0
- package/dist/emblems/gear/gear.kyudo.js +31 -0
- package/dist/emblems/gear/gear.lacrossestick.d.ts +1 -0
- package/dist/emblems/gear/gear.lacrossestick.js +26 -0
- package/dist/emblems/gear/gear.rackets.d.ts +4 -0
- package/dist/emblems/gear/gear.rackets.js +60 -0
- package/dist/emblems/gear/gear.skates.d.ts +3 -0
- package/dist/emblems/gear/gear.skates.js +39 -0
- package/dist/emblems/gear/gear.sticks.d.ts +3 -0
- package/dist/emblems/gear/gear.sticks.js +34 -0
- package/dist/emblems/gear/gear.sumo.d.ts +2 -0
- package/dist/emblems/gear/gear.sumo.js +43 -0
- package/dist/emblems/gear/gear.tabletennispaddle.d.ts +1 -0
- package/dist/emblems/gear/gear.tabletennispaddle.js +16 -0
- package/dist/emblems/gear/gear.tennisracket.d.ts +1 -0
- package/dist/emblems/gear/gear.tennisracket.js +17 -0
- package/dist/emblems/gear/gear.track.d.ts +4 -0
- package/dist/emblems/gear/gear.track.js +45 -0
- package/dist/emblems/gear/gear.water.d.ts +2 -0
- package/dist/emblems/gear/gear.water.js +25 -0
- package/dist/emblems/medal/bronze.d.ts +2 -0
- package/dist/emblems/medal/bronze.js +4 -0
- package/dist/emblems/medal/gold.d.ts +2 -0
- package/dist/emblems/medal/gold.js +4 -0
- package/dist/emblems/medal/place-1.d.ts +2 -0
- package/dist/emblems/medal/place-1.js +4 -0
- package/dist/emblems/medal/place-2.d.ts +2 -0
- package/dist/emblems/medal/place-2.js +4 -0
- package/dist/emblems/medal/place-3.d.ts +2 -0
- package/dist/emblems/medal/place-3.js +4 -0
- package/dist/emblems/medal/silver.d.ts +2 -0
- package/dist/emblems/medal/silver.js +4 -0
- package/dist/emblems/pennant/championship.d.ts +2 -0
- package/dist/emblems/pennant/championship.js +4 -0
- package/dist/emblems/sport/afl.d.ts +2 -0
- package/dist/emblems/sport/afl.js +12 -0
- package/dist/emblems/sport/artisticswimming.d.ts +2 -0
- package/dist/emblems/sport/artisticswimming.js +16 -0
- package/dist/emblems/sport/athletics.d.ts +2 -0
- package/dist/emblems/sport/athletics.js +12 -0
- package/dist/emblems/sport/badminton.d.ts +2 -0
- package/dist/emblems/sport/badminton.js +13 -0
- package/dist/emblems/sport/cfl.d.ts +2 -0
- package/dist/emblems/sport/cfl.js +5 -0
- package/dist/emblems/sport/cricket.d.ts +2 -0
- package/dist/emblems/sport/cricket.js +14 -0
- package/dist/emblems/sport/curling.d.ts +2 -0
- package/dist/emblems/sport/curling.js +14 -0
- package/dist/emblems/sport/diving.d.ts +2 -0
- package/dist/emblems/sport/diving.js +18 -0
- package/dist/emblems/sport/fieldhockey.d.ts +2 -0
- package/dist/emblems/sport/fieldhockey.js +11 -0
- package/dist/emblems/sport/gaelic.d.ts +2 -0
- package/dist/emblems/sport/gaelic.js +11 -0
- package/dist/emblems/sport/handball.d.ts +2 -0
- package/dist/emblems/sport/handball.js +15 -0
- package/dist/emblems/sport/ifaf.d.ts +2 -0
- package/dist/emblems/sport/ifaf.js +5 -0
- package/dist/emblems/sport/judo.d.ts +2 -0
- package/dist/emblems/sport/judo.js +13 -0
- package/dist/emblems/sport/karate.d.ts +2 -0
- package/dist/emblems/sport/karate.js +13 -0
- package/dist/emblems/sport/kendo.d.ts +2 -0
- package/dist/emblems/sport/kendo.js +14 -0
- package/dist/emblems/sport/kyudo.d.ts +2 -0
- package/dist/emblems/sport/kyudo.js +15 -0
- package/dist/emblems/sport/lacrosse.d.ts +2 -0
- package/dist/emblems/sport/lacrosse.js +13 -0
- package/dist/emblems/sport/mlb.d.ts +2 -0
- package/dist/emblems/sport/mlb.js +28 -0
- package/dist/emblems/sport/nba.d.ts +2 -0
- package/dist/emblems/sport/nba.js +12 -0
- package/dist/emblems/sport/netball.d.ts +2 -0
- package/dist/emblems/sport/netball.js +14 -0
- package/dist/emblems/sport/nfl.d.ts +3 -0
- package/dist/emblems/sport/nfl.js +15 -0
- package/dist/emblems/sport/nhl.d.ts +2 -0
- package/dist/emblems/sport/nhl.js +13 -0
- package/dist/emblems/sport/padel.d.ts +2 -0
- package/dist/emblems/sport/padel.js +14 -0
- package/dist/emblems/sport/pickleball.d.ts +2 -0
- package/dist/emblems/sport/pickleball.js +13 -0
- package/dist/emblems/sport/racquetball.d.ts +2 -0
- package/dist/emblems/sport/racquetball.js +13 -0
- package/dist/emblems/sport/roundnet.d.ts +2 -0
- package/dist/emblems/sport/roundnet.js +15 -0
- package/dist/emblems/sport/rugby.d.ts +2 -0
- package/dist/emblems/sport/rugby.js +12 -0
- package/dist/emblems/sport/sepaktakraw.d.ts +2 -0
- package/dist/emblems/sport/sepaktakraw.js +11 -0
- package/dist/emblems/sport/skating.d.ts +2 -0
- package/dist/emblems/sport/skating.js +14 -0
- package/dist/emblems/sport/soccer.d.ts +2 -0
- package/dist/emblems/sport/soccer.js +31 -0
- package/dist/emblems/sport/squash.d.ts +2 -0
- package/dist/emblems/sport/squash.js +15 -0
- package/dist/emblems/sport/sumo.d.ts +2 -0
- package/dist/emblems/sport/sumo.js +15 -0
- package/dist/emblems/sport/swimming.d.ts +2 -0
- package/dist/emblems/sport/swimming.js +15 -0
- package/dist/emblems/sport/tabletennis.d.ts +2 -0
- package/dist/emblems/sport/tabletennis.js +12 -0
- package/dist/emblems/sport/tennis.d.ts +2 -0
- package/dist/emblems/sport/tennis.js +15 -0
- package/dist/emblems/sport/teqball.d.ts +2 -0
- package/dist/emblems/sport/teqball.js +15 -0
- package/dist/emblems/sport/volleyball.d.ts +2 -0
- package/dist/emblems/sport/volleyball.js +25 -0
- package/dist/emblems/sport/waterpolo.d.ts +2 -0
- package/dist/emblems/sport/waterpolo.js +15 -0
- package/dist/emblems/trophy/banner.d.ts +2 -0
- package/dist/emblems/trophy/banner.js +20 -0
- package/dist/emblems/trophy/baseball.d.ts +2 -0
- package/dist/emblems/trophy/baseball.js +15 -0
- package/dist/emblems/trophy/basketball.d.ts +2 -0
- package/dist/emblems/trophy/basketball.js +17 -0
- package/dist/emblems/trophy/belt.d.ts +2 -0
- package/dist/emblems/trophy/belt.js +17 -0
- package/dist/emblems/trophy/bowl.d.ts +2 -0
- package/dist/emblems/trophy/bowl.js +8 -0
- package/dist/emblems/trophy/canadian-bowl.d.ts +2 -0
- package/dist/emblems/trophy/canadian-bowl.js +23 -0
- package/dist/emblems/trophy/championship-banner.d.ts +2 -0
- package/dist/emblems/trophy/championship-banner.js +67 -0
- package/dist/emblems/trophy/cricket-urn.d.ts +2 -0
- package/dist/emblems/trophy/cricket-urn.js +23 -0
- package/dist/emblems/trophy/cup.d.ts +2 -0
- package/dist/emblems/trophy/cup.js +16 -0
- package/dist/emblems/trophy/football.d.ts +2 -0
- package/dist/emblems/trophy/football.js +11 -0
- package/dist/emblems/trophy/globe.d.ts +2 -0
- package/dist/emblems/trophy/globe.js +9 -0
- package/dist/emblems/trophy/hockey-cup.d.ts +2 -0
- package/dist/emblems/trophy/hockey-cup.js +22 -0
- package/dist/emblems/trophy/laurel.d.ts +2 -0
- package/dist/emblems/trophy/laurel.js +40 -0
- package/dist/emblems/trophy/lidded-cup.d.ts +2 -0
- package/dist/emblems/trophy/lidded-cup.js +21 -0
- package/dist/emblems/trophy/plaque.d.ts +2 -0
- package/dist/emblems/trophy/plaque.js +17 -0
- package/dist/emblems/trophy/ring.d.ts +2 -0
- package/dist/emblems/trophy/ring.js +30 -0
- package/dist/emblems/trophy/rosette.d.ts +2 -0
- package/dist/emblems/trophy/rosette.js +27 -0
- package/dist/emblems/trophy/rugby-cup.d.ts +2 -0
- package/dist/emblems/trophy/rugby-cup.js +23 -0
- package/dist/emblems/trophy/salver.d.ts +2 -0
- package/dist/emblems/trophy/salver.js +9 -0
- package/dist/emblems/trophy/soccer-ball.d.ts +2 -0
- package/dist/emblems/trophy/soccer-ball.js +9 -0
- package/dist/emblems/trophy/soccer-globe.d.ts +2 -0
- package/dist/emblems/trophy/soccer-globe.js +18 -0
- package/dist/emblems/trophy/sumo-cup.d.ts +2 -0
- package/dist/emblems/trophy/sumo-cup.js +28 -0
- package/dist/emblems/trophy/tennis-cup.d.ts +2 -0
- package/dist/emblems/trophy/tennis-cup.js +24 -0
- package/dist/emblems/trophy/tennis-plate.d.ts +2 -0
- package/dist/emblems/trophy/tennis-plate.js +16 -0
- package/dist/emblems-entry.d.ts +20 -7
- package/dist/emblems-entry.js +8 -2
- package/dist/index.d.ts +131 -1
- package/dist/index.js +131 -3
- package/dist/turn-control.d.ts +101 -0
- package/dist/turn-control.js +363 -0
- package/dist/turn-entry.d.ts +2 -0
- package/dist/turn-entry.js +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +33 -7
- package/LICENSES/Apache-2.0.txt +0 -55
- package/LICENSES/CC-BY-3.0.md +0 -15
- package/dist/emblems/ball/soccer.d.ts +0 -2
- package/dist/emblems/ball/soccer.js +0 -9
- package/dist/emblems/emblem.ball.d.ts +0 -28
- package/dist/emblems/emblem.ball.js +0 -70
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// A body of revolution: the shape of a cup, a bowl, an urn, a column or a globe on a stand. A profile says the radius at each height; the parts that stand on it (handles, balls, flags, rings and
|
|
2
|
+
// grooves round the body, a pattern on a dish) are placed by the angle they are turned to. One model draws the still emblem (yaw 0) and the emblem turned (`frame`), so the 2D and the turning view
|
|
3
|
+
// come from the same data. The silhouette is the profile at every yaw; the light, the handles, the grooves and the seams move to where `sin(yaw)` puts them, and are drawn back to front. Plain SVG.
|
|
4
|
+
// Imports only the core.
|
|
5
|
+
import { cylinderGradient, defsOf, hexOf, labelled, mainOf, mix, orbGradient, readColour, resolve, wrap, } from "./emblem.core.js";
|
|
6
|
+
import { cylinderText, engravingInk, engravingOf, flatText, sideText } from "./emblem.engrave.js";
|
|
7
|
+
const CX = 32;
|
|
8
|
+
const RAD = Math.PI / 180;
|
|
9
|
+
const f = (n) => String(Math.round(n * 10) / 10 + 0);
|
|
10
|
+
const pt = (x, y) => `${f(x)} ${f(y)}`;
|
|
11
|
+
/** The radius of the body at a height. */
|
|
12
|
+
export function radiusAt(profile, y) {
|
|
13
|
+
if (y <= profile[0][0])
|
|
14
|
+
return profile[0][1];
|
|
15
|
+
for (let i = 1; i < profile.length; i++) {
|
|
16
|
+
const [y1, r1] = profile[i];
|
|
17
|
+
const [y0, r0] = profile[i - 1];
|
|
18
|
+
if (y <= y1)
|
|
19
|
+
return y1 === y0 ? r1 : r0 + ((r1 - r0) * (y - y0)) / (y1 - y0);
|
|
20
|
+
}
|
|
21
|
+
return profile[profile.length - 1][1];
|
|
22
|
+
}
|
|
23
|
+
/** The edge of the body on one side, smoothed through its vertices, as path segments that continue from the first point. */
|
|
24
|
+
function edge(points) {
|
|
25
|
+
let d = "";
|
|
26
|
+
let cur = points[0];
|
|
27
|
+
const last = points.length - 1;
|
|
28
|
+
for (let i = 1; i <= last; i++) {
|
|
29
|
+
const p = points[i];
|
|
30
|
+
if (i === last) {
|
|
31
|
+
d += `L${pt(p[0], p[1])}`;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
const before = points[i - 1];
|
|
35
|
+
const after = points[i + 1];
|
|
36
|
+
const m0 = [(before[0] + p[0]) / 2, (before[1] + p[1]) / 2];
|
|
37
|
+
const m1 = [(p[0] + after[0]) / 2, (p[1] + after[1]) / 2];
|
|
38
|
+
if (Math.abs(cur[0] - m0[0]) > 0.05 || Math.abs(cur[1] - m0[1]) > 0.05)
|
|
39
|
+
d += `L${pt(m0[0], m0[1])}`;
|
|
40
|
+
d += `Q${pt(p[0], p[1])} ${pt(m1[0], m1[1])}`;
|
|
41
|
+
cur = m1;
|
|
42
|
+
}
|
|
43
|
+
return d;
|
|
44
|
+
}
|
|
45
|
+
/** The pieces of a profile, as lists of vertex indexes: a new piece starts at a sharp corner and after a step (two points at one height). */
|
|
46
|
+
function piecesOf(p) {
|
|
47
|
+
const out = [];
|
|
48
|
+
let cur = [0];
|
|
49
|
+
for (let i = 1; i < p.length; i++) {
|
|
50
|
+
if (p[i][0] === p[i - 1][0]) {
|
|
51
|
+
out.push(cur);
|
|
52
|
+
cur = [i];
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
cur.push(i);
|
|
56
|
+
if (p[i][2] === 1 && i < p.length - 1 && p[i + 1][0] !== p[i][0]) {
|
|
57
|
+
out.push(cur);
|
|
58
|
+
cur = [i];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
out.push(cur);
|
|
62
|
+
return out.filter((c) => c.length > 1);
|
|
63
|
+
}
|
|
64
|
+
/** A front arc of the rim of radius `r` at height `y`, from the right to the left, sagging toward the eye by `r * s`. */
|
|
65
|
+
const arc = (y, r, s, toLeft) => `Q${pt(CX, y + 2 * r * s)} ${pt(CX + (toLeft ? -r : r), y)}`;
|
|
66
|
+
const ellipse = (cx, cy, rx, ry, fill, extra = "") => `<ellipse cx="${f(cx)}" cy="${f(cy)}" rx="${f(rx)}" ry="${f(Math.max(ry, 0.25))}" fill="${fill}"${extra}/>`;
|
|
67
|
+
/** The geometry and paint of one drawing of the model at a yaw and a pitch. */
|
|
68
|
+
function draw(model, r, o, yaw, pitch) {
|
|
69
|
+
const line = r.style === "line";
|
|
70
|
+
const flat = r.style === "color";
|
|
71
|
+
const elevation = Math.max(0, Math.min(75, (model.tilt ?? 12) + pitch));
|
|
72
|
+
const s = Math.sin(elevation * RAD);
|
|
73
|
+
const spin = Math.sin(yaw * RAD);
|
|
74
|
+
const shift = 0.2 * spin;
|
|
75
|
+
const grads = [];
|
|
76
|
+
const tones = new Map();
|
|
77
|
+
const tone = (name = "main") => {
|
|
78
|
+
const known = tones.get(name);
|
|
79
|
+
if (known !== undefined)
|
|
80
|
+
return known;
|
|
81
|
+
const rgb = name === "main" ? (r.tint ?? readColour(model.color) ?? mainOf(model.metal, undefined)) : name === "accent" && r.accent !== undefined ? readColour(r.accent) : (readColour(model.tones?.[name]) ?? mainOf(model.metal, undefined));
|
|
82
|
+
const g = cylinderGradient(rgb, shift);
|
|
83
|
+
if (!line && !flat)
|
|
84
|
+
grads.push(g);
|
|
85
|
+
const made = { rgb, fill: line ? "none" : flat ? hexOf(rgb) : g.fill, light: line ? "none" : mix(rgb, 0.4), deep: line ? "none" : mix(rgb, -0.55), edge: mix(rgb, -0.55) };
|
|
86
|
+
tones.set(name, made);
|
|
87
|
+
return made;
|
|
88
|
+
};
|
|
89
|
+
const main = tone("main");
|
|
90
|
+
const back = [];
|
|
91
|
+
const mid = [];
|
|
92
|
+
const front = [];
|
|
93
|
+
const own = (t) => (t === main || line ? "" : ` stroke="${t.edge}"`);
|
|
94
|
+
// The point of the surface of a body of revolution at a radius, a height and an angle.
|
|
95
|
+
const at = (R, y, deg) => [CX + R * Math.sin(deg * RAD), y + s * R * Math.cos(deg * RAD), Math.cos(deg * RAD)];
|
|
96
|
+
const where = (R, y, deg) => pt(...at(R, y, deg).slice(0, 2));
|
|
97
|
+
for (const solid of model.solids) {
|
|
98
|
+
const p = solid.p;
|
|
99
|
+
const list = piecesOf(p);
|
|
100
|
+
const base = tone(solid.tone);
|
|
101
|
+
const top = p[0];
|
|
102
|
+
for (let k = list.length - 1; k >= 0; k--) {
|
|
103
|
+
const idx = list[k];
|
|
104
|
+
const a = p[idx[0]];
|
|
105
|
+
const b = p[idx[idx.length - 1]];
|
|
106
|
+
const t = tone(a[3] ?? solid.tone);
|
|
107
|
+
const rows = idx.map((i) => p[i]);
|
|
108
|
+
const right = rows.map((q) => [CX + q[1], q[0]]);
|
|
109
|
+
const left = rows.map((q) => [CX - q[1], q[0]]).reverse();
|
|
110
|
+
const height = b[0] - a[0];
|
|
111
|
+
if (solid.sides === undefined) {
|
|
112
|
+
// In the line style a piece is open at the top (the piece above, or the rim, draws that edge), and a small step between pieces is a short line, not a ring, so that a tall stack stays a clean outline.
|
|
113
|
+
const below = list[k + 1] === undefined ? undefined : p[list[k + 1][0]];
|
|
114
|
+
const small = below !== undefined && Math.abs(below[1] - b[1]) < 1.4 && height < 8;
|
|
115
|
+
const d = line
|
|
116
|
+
? `M${pt(right[0][0], right[0][1])}${edge(right)}${small ? `M${pt(CX - b[1], b[0])}` : arc(b[0], b[1], s, true)}${edge(left)}${k === 0 ? arc(a[0], a[1], s, false) : ""}${below !== undefined && below[1] !== b[1] ? `M${pt(CX + b[1], b[0])}H${f(CX + below[1])}M${pt(CX - b[1], b[0])}H${f(CX - below[1])}` : ""}`
|
|
117
|
+
: `M${pt(right[0][0], right[0][1])}${edge(right)}${arc(b[0], b[1], s, true)}${edge(left)}${arc(a[0], a[1], s, false)}z`;
|
|
118
|
+
mid.push(`<path d="${d}" fill="${t.fill}"${own(t)}/>`);
|
|
119
|
+
if (!line && height >= 6 && Math.min(a[1], b[1]) >= 2.2) {
|
|
120
|
+
const c = 0.4 - 2 * shift;
|
|
121
|
+
const l1 = rows.map((q) => pt(CX - (c + 0.09) * q[1], q[0] + 0.6));
|
|
122
|
+
const l2 = rows.map((q) => pt(CX - (c - 0.09) * q[1], q[0] + 0.6)).reverse();
|
|
123
|
+
if (!flat)
|
|
124
|
+
mid.push(`<path d="M${l1.join("L")}L${l2.join("L")}z" fill="#fff" fill-opacity=".34" stroke="none"/>`);
|
|
125
|
+
else {
|
|
126
|
+
const e1 = rows.map((q) => pt(CX + 0.3 * q[1], q[0]));
|
|
127
|
+
const e2 = rows.map((q) => pt(CX + 0.96 * q[1], q[0])).reverse();
|
|
128
|
+
mid.push(`<path d="M${e1.join("L")}L${e2.join("L")}z" fill="${mix(t.rgb, -0.22)}" stroke="none"/>`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// A body with flat faces: each face that looks toward the eye is a quadrilateral, lit by which way it faces.
|
|
134
|
+
const n = solid.sides;
|
|
135
|
+
for (let face = 0; face < n; face++) {
|
|
136
|
+
const a0 = (360 * face) / n + yaw;
|
|
137
|
+
const a1 = (360 * (face + 1)) / n + yaw;
|
|
138
|
+
const m = (a0 + a1) / 2;
|
|
139
|
+
if (Math.cos(m * RAD) <= 0)
|
|
140
|
+
continue;
|
|
141
|
+
const lit = -Math.sin(m * RAD);
|
|
142
|
+
const colour = line ? "none" : mix(t.rgb, 0.18 + 0.36 * lit);
|
|
143
|
+
mid.push(`<path d="M${where(a[1], a[0], a0)}L${where(a[1], a[0], a1)}L${where(b[1], b[0], a1)}L${where(b[1], b[0], a0)}z" fill="${colour}"${own(t)}/>`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// The top of a piece that steps out beyond the one above it, or the cap of the body, shows as a lighter ring.
|
|
147
|
+
const above = k > 0 ? p[list[k - 1][list[k - 1].length - 1]] : undefined;
|
|
148
|
+
if (!line && ((above === undefined && solid.open !== true && a[1] > 1) || (above !== undefined && a[1] > above[1] + 0.3))) {
|
|
149
|
+
const sides = solid.sides;
|
|
150
|
+
mid.push(sides === undefined ? ellipse(CX, a[0], a[1], a[1] * s, t.light) : `<path d="M${Array.from({ length: sides }, (_, i) => where(a[1], a[0], (360 * i) / sides + yaw)).join("L")}z" fill="${t.light}"${own(t)}/>`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (solid.open === true) {
|
|
154
|
+
const lip = top[1];
|
|
155
|
+
const inner = Math.max(lip - 1.3, lip * 0.7);
|
|
156
|
+
mid.push(ellipse(CX, top[0], lip, lip * s, base.light));
|
|
157
|
+
mid.push(ellipse(CX, top[0] + 0.1, inner, inner * s, base.deep, ' stroke="none"'));
|
|
158
|
+
if (!line && s > 0.12)
|
|
159
|
+
mid.push(ellipse(CX, top[0] + inner * s * 0.35, inner * 0.8, inner * s * 0.55, mix(base.rgb, -0.3), ' stroke="none"'));
|
|
160
|
+
}
|
|
161
|
+
// What is drawn on the surface.
|
|
162
|
+
for (const d of solid.decor ?? []) {
|
|
163
|
+
const kind = d[0];
|
|
164
|
+
if (kind === "ring") {
|
|
165
|
+
const R = radiusAt(p, d[1]);
|
|
166
|
+
mid.push(`<path d="M${pt(CX - R, d[1])}${arc(d[1], R, s, false)}" stroke-width="${line ? 2 : 0.8}"/>`);
|
|
167
|
+
if (!line)
|
|
168
|
+
mid.push(`<path d="M${pt(CX - R, d[1] + 0.9)}${arc(d[1] + 0.9, R, s, false)}" stroke="${mix(base.rgb, 0.5)}" stroke-width=".6" stroke-opacity=".7"/>`);
|
|
169
|
+
}
|
|
170
|
+
else if (line) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
else if (kind === "mer") {
|
|
174
|
+
const [, y0, y1, n, off = 0] = d;
|
|
175
|
+
let m = "";
|
|
176
|
+
for (let i = 0; i < n; i++) {
|
|
177
|
+
const deg = yaw + off + (360 * i) / n;
|
|
178
|
+
if (Math.cos(deg * RAD) <= 0.08)
|
|
179
|
+
continue;
|
|
180
|
+
m += `M${where(radiusAt(p, y0), y0, deg)}L${where(radiusAt(p, y1), y1, deg)}`;
|
|
181
|
+
}
|
|
182
|
+
if (m !== "")
|
|
183
|
+
mid.push(`<path d="${m}" stroke-width=".7"/>`);
|
|
184
|
+
}
|
|
185
|
+
else if (kind === "helix") {
|
|
186
|
+
const [, y0, y1, n, turns] = d;
|
|
187
|
+
let m = "";
|
|
188
|
+
for (let i = 0; i < n; i++) {
|
|
189
|
+
let pen = false;
|
|
190
|
+
for (let j = 0; j <= 16; j++) {
|
|
191
|
+
const u = j / 16;
|
|
192
|
+
const y = y0 + (y1 - y0) * u;
|
|
193
|
+
const deg = yaw + (360 * i) / n + 360 * turns * u;
|
|
194
|
+
if (Math.cos(deg * RAD) <= 0.05) {
|
|
195
|
+
pen = false;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
m += `${pen ? "L" : "M"}${where(radiusAt(p, y), y, deg)}`;
|
|
199
|
+
pen = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (m !== "") {
|
|
203
|
+
mid.push(`<path d="${m}" stroke-width="1.3" stroke-opacity=".75"/>`);
|
|
204
|
+
mid.push(`<path d="${m}" transform="translate(.9 -.5)" stroke="${mix(base.rgb, 0.6)}" stroke-width=".8" stroke-opacity=".7"/>`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (kind === "net") {
|
|
208
|
+
const [, y0, y1, n] = d;
|
|
209
|
+
let m = "";
|
|
210
|
+
for (let i = 0; i < n; i++) {
|
|
211
|
+
const deg = yaw + (360 * i) / n;
|
|
212
|
+
for (const to of [deg + 360 / n, deg - 360 / n])
|
|
213
|
+
if (Math.cos(((deg + to) / 2) * RAD) > 0.05)
|
|
214
|
+
m += `M${where(radiusAt(p, y0), y0, deg)}L${where(radiusAt(p, y1), y1, to)}`;
|
|
215
|
+
}
|
|
216
|
+
if (m !== "")
|
|
217
|
+
mid.push(`<path d="${m}" stroke-width=".7"/>`);
|
|
218
|
+
}
|
|
219
|
+
else if (kind === "dots") {
|
|
220
|
+
const [, y, n, size, off = 0] = d;
|
|
221
|
+
let m = "";
|
|
222
|
+
for (let i = 0; i < n; i++) {
|
|
223
|
+
const deg = yaw + off + (360 * i) / n;
|
|
224
|
+
if (Math.cos(deg * RAD) > 0.1)
|
|
225
|
+
m += `M${where(radiusAt(p, y), y, deg)}h0`;
|
|
226
|
+
}
|
|
227
|
+
if (m !== "")
|
|
228
|
+
mid.push(`<path d="${m}" stroke-width="${size}" stroke="${mix(base.rgb, 0.45)}"/>`);
|
|
229
|
+
}
|
|
230
|
+
else if (kind === "disc") {
|
|
231
|
+
const t = tone(d[2]);
|
|
232
|
+
mid.push(ellipse(CX, top[0], d[1], d[1] * s, t.fill, ` stroke="${t.edge}"`));
|
|
233
|
+
}
|
|
234
|
+
else if (kind === "pills") {
|
|
235
|
+
const [, rho, n, length, width, name] = d;
|
|
236
|
+
const t = tone(name);
|
|
237
|
+
let m = "";
|
|
238
|
+
for (let i = 0; i < n; i++) {
|
|
239
|
+
const deg = yaw + (360 * i) / n;
|
|
240
|
+
m += `M${where(rho, top[0], deg - length / 2)}L${where(rho, top[0], deg + length / 2)}`;
|
|
241
|
+
}
|
|
242
|
+
mid.push(`<path d="${m}" stroke-width="${width}" stroke="${t.edge}"/><path d="${m}" stroke-width="${width - 0.9}" stroke="${t.light}"/>`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Handles: a tube through the points of its plane, behind the body when it is on the far side.
|
|
247
|
+
for (const h of model.handles ?? []) {
|
|
248
|
+
const deg = h.a + yaw;
|
|
249
|
+
const t = tone(h.tone);
|
|
250
|
+
const sin = Math.sin(deg * RAD);
|
|
251
|
+
const cos = Math.cos(deg * RAD);
|
|
252
|
+
if (Math.abs(cos) < 0.02 && Math.abs(sin) < 0.1)
|
|
253
|
+
continue;
|
|
254
|
+
const pts = h.pts.map(([R, y]) => [CX + R * sin, y + s * R * cos]);
|
|
255
|
+
let d = `M${pt(pts[0][0], pts[0][1])}`;
|
|
256
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
257
|
+
const p0 = pts[Math.max(0, i - 1)];
|
|
258
|
+
const p1 = pts[i];
|
|
259
|
+
const p2 = pts[i + 1];
|
|
260
|
+
const p3 = pts[Math.min(pts.length - 1, i + 2)];
|
|
261
|
+
d += `C${pt(p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6)} ${pt(p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6)} ${pt(p2[0], p2[1])}`;
|
|
262
|
+
}
|
|
263
|
+
const w = h.w ?? 2.6;
|
|
264
|
+
const piece = line ? `<path d="${d}" stroke-width="${f(w + 0.6)}"/>` : `<path d="${d}" stroke-width="${f(w + 1.1)}"${own(t)}/><path d="${d}" stroke="${flat ? t.fill : mix(t.rgb, 0.05)}" stroke-width="${f(w)}"/>`;
|
|
265
|
+
(cos >= 0 ? front : back).push(piece);
|
|
266
|
+
}
|
|
267
|
+
// Poles standing round a ring, each with a pennant at its top: those on the far side behind, those on the near side in front.
|
|
268
|
+
if (model.poles !== undefined) {
|
|
269
|
+
const o = model.poles;
|
|
270
|
+
const pole = tone("pole");
|
|
271
|
+
const flag = tone("accent");
|
|
272
|
+
const lines = { back: "", front: "" };
|
|
273
|
+
const flags = { back: "", front: "" };
|
|
274
|
+
for (let i = 0; i < o.n; i++) {
|
|
275
|
+
const deg = yaw + (360 * i) / o.n;
|
|
276
|
+
const near = Math.cos(deg * RAD) >= 0 ? "front" : "back";
|
|
277
|
+
const topY = o.top1 + ((o.top2 - o.top1) * i) / o.n;
|
|
278
|
+
const [x0, y0] = at(o.r0, o.y0, deg);
|
|
279
|
+
const [x1, y1] = at(o.r1, topY, deg);
|
|
280
|
+
lines[near] += `M${pt(x0, y0)}L${pt(x1, y1)}`;
|
|
281
|
+
const dir = Math.cos(deg * RAD) >= 0 ? 1 : -1;
|
|
282
|
+
const len = o.size * (0.5 + 0.9 * Math.abs(Math.cos(deg * RAD)));
|
|
283
|
+
flags[near] += `M${pt(x1, y1 + 0.2)}l${f(dir * len)} ${f(o.size * 0.28)} ${f(-dir * len)} ${f(o.size * 0.28)}z`;
|
|
284
|
+
}
|
|
285
|
+
const stick = line ? 1.2 : 1;
|
|
286
|
+
for (const near of ["back", "front"]) {
|
|
287
|
+
const out = near === "back" ? back : front;
|
|
288
|
+
out.push(`<path d="${lines[near]}" stroke="${line ? "currentColor" : mix(pole.rgb, -0.12)}" stroke-width="${stick}"/>`);
|
|
289
|
+
out.push(`<path d="${flags[near]}" fill="${line ? "none" : flat ? flag.fill : flag.fill}" stroke-width="${line ? 1.2 : 0.5}"${line ? "" : ` stroke="${flag.edge}"`}/>`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Balls (and the pineapple): a sphere with its seams, which turn with the yaw.
|
|
293
|
+
for (const ball of model.balls ?? []) {
|
|
294
|
+
const t = tone(ball.tone);
|
|
295
|
+
const rx = ball.r;
|
|
296
|
+
const ry = ball.ry ?? ball.r;
|
|
297
|
+
const cx = ball.cx ?? CX;
|
|
298
|
+
const cy = ball.cy;
|
|
299
|
+
const spherical = line || flat || ball.kind === "pine" ? t.fill : (() => {
|
|
300
|
+
const g = orbGradient(t.rgb);
|
|
301
|
+
grads.push(g);
|
|
302
|
+
return g.fill;
|
|
303
|
+
})();
|
|
304
|
+
const parts = [];
|
|
305
|
+
if (ball.kind === "football") {
|
|
306
|
+
const k = 1.36;
|
|
307
|
+
const shape = `M0 ${f(-ry)}C${f(k * rx)} ${f(-0.25 * ry)} ${f(k * rx)} ${f(0.25 * ry)} 0 ${f(ry)}C${f(-k * rx)} ${f(0.25 * ry)} ${f(-k * rx)} ${f(-0.25 * ry)} 0 ${f(-ry)}z`;
|
|
308
|
+
const sx = Math.sin(yaw * RAD) * rx * 0.55;
|
|
309
|
+
const lit = Math.cos(yaw * RAD) > 0.1;
|
|
310
|
+
const laces = lit ? `M${f(sx)} ${f(-ry * 0.5)}V${f(ry * 0.5)}${[-2, -1, 0, 1, 2].map((i) => `M${f(sx - 1.3)} ${f(i * ry * 0.19)}h2.6`).join("")}` : "";
|
|
311
|
+
parts.push(`<g transform="translate(${f(cx)} ${f(cy)}) rotate(${f(ball.rot ?? 0)})"><path d="${shape}" fill="${spherical}"/>${line || flat ? "" : `<path d="M${f(-rx * 0.62)} ${f(-ry * 0.42)}Q${f(-rx * 0.5)} ${f(-ry * 0.1)} ${f(-rx * 0.54)} ${f(ry * 0.3)}" stroke="#fff" stroke-opacity=".5" stroke-width="1.6" fill="none"/>`}<path d="${laces}" stroke-width="1.1"/></g>`);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
parts.push(ellipse(cx, cy, rx, ry, spherical));
|
|
315
|
+
let seams = "";
|
|
316
|
+
const meridian = (lon) => {
|
|
317
|
+
const l = ((((lon + 90) % 360) + 360) % 360) - 90;
|
|
318
|
+
if (l >= 90 && l <= 270)
|
|
319
|
+
return "";
|
|
320
|
+
const a = l > 90 ? l - 360 : l;
|
|
321
|
+
const sin = Math.sin(a * RAD);
|
|
322
|
+
return `M${pt(cx, cy - ry)}A${f(Math.abs(rx * sin))} ${f(ry)} 0 0 ${sin >= 0 ? 1 : 0} ${pt(cx, cy + ry)}`;
|
|
323
|
+
};
|
|
324
|
+
const equator = (k, dy = 0) => `M${pt(cx - rx * Math.sqrt(Math.max(0, 1 - (dy / ry) ** 2)), cy + dy)}Q${pt(cx, cy + dy + 2 * rx * s * Math.sqrt(Math.max(0, 1 - (dy / ry) ** 2)) * k)} ${pt(cx + rx * Math.sqrt(Math.max(0, 1 - (dy / ry) ** 2)), cy + dy)}`;
|
|
325
|
+
let patches = "";
|
|
326
|
+
switch (ball.kind) {
|
|
327
|
+
case "basket":
|
|
328
|
+
seams += equator(1);
|
|
329
|
+
for (let i = 0; i < 4; i++)
|
|
330
|
+
seams += meridian(yaw + i * 90);
|
|
331
|
+
break;
|
|
332
|
+
case "globe":
|
|
333
|
+
seams += equator(1) + equator(1, -ry * 0.5) + equator(1, ry * 0.5);
|
|
334
|
+
for (let i = 0; i < 6; i++)
|
|
335
|
+
seams += meridian(yaw + i * 30);
|
|
336
|
+
break;
|
|
337
|
+
case "base":
|
|
338
|
+
seams += meridian(yaw + 62) + meridian(yaw - 62);
|
|
339
|
+
break;
|
|
340
|
+
case "pine":
|
|
341
|
+
for (let i = 0; i < 6; i++)
|
|
342
|
+
seams += meridian(yaw + i * 30);
|
|
343
|
+
seams += equator(1, -ry * 0.45) + equator(1, 0) + equator(1, ry * 0.45);
|
|
344
|
+
break;
|
|
345
|
+
case "soccer":
|
|
346
|
+
for (const [lat, lon] of PENTAGONS) {
|
|
347
|
+
const la = lat * RAD;
|
|
348
|
+
const lo = (lon + yaw) * RAD;
|
|
349
|
+
const z = Math.cos(la) * Math.cos(lo);
|
|
350
|
+
if (z < 0.12)
|
|
351
|
+
continue;
|
|
352
|
+
const px = cx + rx * Math.cos(la) * Math.sin(lo);
|
|
353
|
+
const py = cy - rx * Math.sin(la) + s * rx * 0.0;
|
|
354
|
+
const dx = px - cx;
|
|
355
|
+
const dy = py - cy;
|
|
356
|
+
const dist = Math.hypot(dx, dy);
|
|
357
|
+
const ux = dist < 0.01 ? 1 : dx / dist;
|
|
358
|
+
const uy = dist < 0.01 ? 0 : dy / dist;
|
|
359
|
+
const squash = Math.max(0.28, z);
|
|
360
|
+
let d = "";
|
|
361
|
+
for (let i = 0; i < 5; i++) {
|
|
362
|
+
const ang = (i * 72 - 90) * RAD;
|
|
363
|
+
const lx = rx * 0.36 * Math.cos(ang);
|
|
364
|
+
const ly = rx * 0.36 * Math.sin(ang);
|
|
365
|
+
const along = lx * ux + ly * uy;
|
|
366
|
+
const across = -lx * uy + ly * ux;
|
|
367
|
+
d += `${i === 0 ? "M" : "L"}${pt(px + along * squash * ux - across * uy, py + along * squash * uy + across * ux)}`;
|
|
368
|
+
}
|
|
369
|
+
patches += `${d}z`;
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
default:
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
if (patches !== "")
|
|
376
|
+
parts.push(`<path d="${patches}" fill="${line ? "none" : t.deep}" stroke-width="1"/>`);
|
|
377
|
+
if (seams !== "") {
|
|
378
|
+
if (ball.kind === "base")
|
|
379
|
+
parts.push(`<path d="${seams}" stroke="${line ? "currentColor" : "#c0392b"}" stroke-width="${line ? 1.6 : 1.1}" stroke-dasharray="${line ? "none" : "1.4 1"}"/>`);
|
|
380
|
+
else
|
|
381
|
+
parts.push(`<path d="${seams}" stroke-width="${line ? 1.5 : ball.kind === "pine" ? 0.7 : 1}"${ball.kind === "basket" && !line ? ` stroke="${mix(t.rgb, -0.55)}"` : ""}/>`);
|
|
382
|
+
}
|
|
383
|
+
if (!line && !flat && ball.kind !== "pine")
|
|
384
|
+
parts.push(`<ellipse cx="${f(cx - rx * 0.36)}" cy="${f(cy - ry * 0.4)}" rx="${f(rx * 0.3)}" ry="${f(ry * 0.2)}" fill="#fff" fill-opacity=".38" stroke="none" transform="rotate(-30 ${f(cx - rx * 0.36)} ${f(cy - ry * 0.4)})"/>`);
|
|
385
|
+
}
|
|
386
|
+
mid.push(...parts);
|
|
387
|
+
}
|
|
388
|
+
// The engraving on the plinth: letters set round the body (or along one flat side), foreshortened and hidden as they turn away.
|
|
389
|
+
const plate = model.plate;
|
|
390
|
+
const words = plate === undefined ? "" : flatText(engravingOf(o));
|
|
391
|
+
if (plate !== undefined && words !== "") {
|
|
392
|
+
const y = (plate.y0 + plate.y1) / 2;
|
|
393
|
+
const solid = model.solids.find((q) => q.p[0][0] <= y && y <= q.p[q.p.length - 1][0]) ?? model.solids[0];
|
|
394
|
+
const radius = radiusAt(solid.p, y);
|
|
395
|
+
const ink = engravingInk(r.style, tone(plate.tone ?? solid.tone).rgb);
|
|
396
|
+
if (solid.sides === undefined)
|
|
397
|
+
mid.push(cylinderText(words, radius, plate.y1 - plate.y0, ink, yaw, plate.at ?? 0, (deg) => at(radius, y, deg)));
|
|
398
|
+
else {
|
|
399
|
+
const n = solid.sides;
|
|
400
|
+
const k = plate.face ?? 0;
|
|
401
|
+
const a0 = (360 * k) / n + yaw;
|
|
402
|
+
const a1 = (360 * (k + 1)) / n + yaw;
|
|
403
|
+
const [ax, ay] = at(radius, y, a0);
|
|
404
|
+
const [bx, by] = at(radius, y, a1);
|
|
405
|
+
mid.push(sideText(words, 2 * radius * Math.sin(Math.PI / n), plate.y1 - plate.y0, ink, [ax, ay], [bx, by], Math.cos(((a0 + a1) / 2) * RAD)));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return { body: back.join("") + mid.join("") + front.join(""), defs: line || flat ? "" : defsOf(grads), edge: main.edge };
|
|
409
|
+
}
|
|
410
|
+
/** The twelve pentagons of a ball, as the centres of a sphere's faces: the poles and two rings of five. */
|
|
411
|
+
const PENTAGONS = [
|
|
412
|
+
[90, 0], [26.57, 0], [26.57, 72], [26.57, 144], [26.57, 216], [26.57, 288],
|
|
413
|
+
[-26.57, 36], [-26.57, 108], [-26.57, 180], [-26.57, 252], [-26.57, 324], [-90, 0],
|
|
414
|
+
];
|
|
415
|
+
/** A lathe emblem: the object with its names and its model, the still drawing and the turned one. */
|
|
416
|
+
export function defineLathe(id, en, ja, source, model) {
|
|
417
|
+
const frame = (o) => {
|
|
418
|
+
const r = resolve(o);
|
|
419
|
+
const yaw = typeof o?.yaw === "number" && Number.isFinite(o.yaw) ? o.yaw % 360 : 0;
|
|
420
|
+
const pitch = typeof o?.pitch === "number" && Number.isFinite(o.pitch) ? Math.max(-40, Math.min(60, o.pitch)) : 0;
|
|
421
|
+
const d = draw(model, r, o, yaw, pitch);
|
|
422
|
+
return wrap({ title: en, size: r.size, style: r.style, theme: r.theme, defs: d.defs, body: d.body, edge: d.edge, width: r.style === "line" ? 2.3 : 0.6 });
|
|
423
|
+
};
|
|
424
|
+
const still = labelled(en, ja, (o) => frame(o));
|
|
425
|
+
return { id, en, ja, source, kind: "lathe", model, svg: still, frame: labelled(en, ja, frame) };
|
|
426
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Emblem, Metal } from "./emblem.core.ts";
|
|
2
|
+
import { type FlatModel } from "./emblem.shapes.ts";
|
|
3
|
+
/** A medal on a ribbon. */
|
|
4
|
+
export declare function defineMedal(id: string, en: string, ja: string, metal: Extract<Metal, "gold" | "silver" | "bronze">): Emblem<FlatModel>;
|
|
5
|
+
/** A plain badge for a place: a bevelled disc with its numeral. */
|
|
6
|
+
export declare function definePlace(id: string, en: string, ja: string, place: 1 | 2 | 3, metal: Extract<Metal, "gold" | "silver" | "bronze">): Emblem<FlatModel>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { circlePath, defineFlat, starPath } from "./emblem.shapes.js";
|
|
2
|
+
/** The colours of the ribbon's stripes, in the order of the five colours a games uses; the ribbon carries no rings. */
|
|
3
|
+
const STRIPES = ["#0085c7", "#f4c300", "#1b1b1b", "#009f3d", "#df0024"];
|
|
4
|
+
const lerp = (a, b, t) => Math.round((a + (b - a) * t) * 10) / 10;
|
|
5
|
+
/** The two straps of the ribbon, crossing above the clasp, each cut into the five stripes. */
|
|
6
|
+
function ribbon() {
|
|
7
|
+
const out = [];
|
|
8
|
+
STRIPES.forEach((colour, k) => {
|
|
9
|
+
const [t0, t1] = [k / 5, (k + 1) / 5];
|
|
10
|
+
// The left strap runs from the top left to the clasp, the right one the other way.
|
|
11
|
+
out.push(["f", `M${lerp(10, 25, t0)} 2L${lerp(10, 25, t1)} 2L${lerp(26, 38, t1)} 28L${lerp(26, 38, t0)} 28z`, colour]);
|
|
12
|
+
out.push(["f", `M${lerp(54, 39, t0)} 2L${lerp(54, 39, t1)} 2L${lerp(38, 26, t1)} 28L${lerp(38, 26, t0)} 28z`, colour]);
|
|
13
|
+
});
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
/** A medal on a ribbon. */
|
|
17
|
+
export function defineMedal(id, en, ja, metal) {
|
|
18
|
+
const disc = [
|
|
19
|
+
["m", "M27.5 23h9v8h-9z"],
|
|
20
|
+
["o", circlePath(32, 43, 17.5)],
|
|
21
|
+
["e", circlePath(32, 43, 15.2), "1.8"],
|
|
22
|
+
];
|
|
23
|
+
const front = [
|
|
24
|
+
...ribbon(),
|
|
25
|
+
...disc,
|
|
26
|
+
["M", starPath(32, 43, 9, 3.9)],
|
|
27
|
+
["l", "M19.2 40a13.4 13.4 0 0 1 9.4-10.4 14 14 0 0 0-6.6 12.6z"],
|
|
28
|
+
];
|
|
29
|
+
const back = [...disc, ["E", circlePath(32, 43, 8.4), "1.4"]];
|
|
30
|
+
return defineFlat(id, en, ja, "ours", { metal, front, back, rim: 3, engrave: { front: { x: 32, y: 43, w: 19, h: 15 }, back: { x: 32, y: 43, w: 20, h: 13 } } });
|
|
31
|
+
}
|
|
32
|
+
/** The numerals 1, 2 and 3 as strokes. */
|
|
33
|
+
const NUMERALS = { 1: "M26 28l7-5v20", 2: "M25 28c1-9 15-9 15 0 0 6-14 9-15 15h16", 3: "M25 26c4-6 14-4 13 2-1 5-6 6-9 6 3 0 9 1 9 7 0 8-14 9-16 2" };
|
|
34
|
+
/** A plain badge for a place: a bevelled disc with its numeral. */
|
|
35
|
+
export function definePlace(id, en, ja, place, metal) {
|
|
36
|
+
const base = [["o", circlePath(32, 32, 27.5)], ["e", circlePath(32, 32, 24.2), "2.2"]];
|
|
37
|
+
return defineFlat(id, en, ja, "ours", { metal, front: [...base, ["D", NUMERALS[place]], ["l", "M10.4 28a22 22 0 0 1 16.6-17 24 24 0 0 0-11 22z"]], back: [...base, ["E", circlePath(32, 32, 14), "1.6"]], rim: 3, engrave: { front: { x: 32, y: 32, w: 30, h: 28 }, back: { x: 32, y: 32, w: 30, h: 20 }, rank: String(place) } });
|
|
38
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// A championship pennant: a long triangle of felt on a dowel, with an edge sewn in a second colour and the words and the year in felt letters. Drawn felt-flat in `metal` and `color` (the fibres are a
|
|
2
|
+
// faint speckle, the `metal` style adds a soft light), and as one outline in `line`. Turned, it flips like a coin and shows its back, which is plain felt and the stitching, with the dowel on the other
|
|
3
|
+
// side. Imports the core, the text widths of the engraving helpers and the coin-flip turn.
|
|
4
|
+
import { degrees, escape, hexOf, mix, n2, readColour, resolve, wrap } from "./emblem.core.js";
|
|
5
|
+
import { engravingOf, widthOf } from "./emblem.engrave.js";
|
|
6
|
+
import { flipSvg } from "./emblem.flip.js";
|
|
7
|
+
const FELT = "#8f1d2c";
|
|
8
|
+
const CREAM = "#f4f0e4";
|
|
9
|
+
const FONT = "system-ui,-apple-system,'Segoe UI','Hiragino Sans','Noto Sans JP','Yu Gothic',sans-serif";
|
|
10
|
+
/** The felt: a wide left edge on the dowel, a point at the right, edges that sag a little. */
|
|
11
|
+
const SHAPE = "M8 15Q34 22 60 32Q34 42 8 49Z";
|
|
12
|
+
const STITCH = "M10.2 18Q34 24.6 55.6 32Q34 39.4 10.2 46Z";
|
|
13
|
+
const ROD = "M4.6 11.5h3.4v41h-3.4z";
|
|
14
|
+
/** The half height of the felt at x. */
|
|
15
|
+
const half = (x) => 17 - ((x - 8) * 17) / 52;
|
|
16
|
+
/** The lines of lettering, each as large as fits inside the triangle from the left edge: the main words, then the year. */
|
|
17
|
+
function lettering(rows) {
|
|
18
|
+
if (rows.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
const x0 = 12.5;
|
|
21
|
+
for (let f = 11; f >= 0.8; f -= 0.2) {
|
|
22
|
+
const sizes = rows.map((_, i) => (i === 0 ? f : f * 0.55));
|
|
23
|
+
const total = sizes.reduce((sum, s) => sum + s * 1.12, 0);
|
|
24
|
+
let top = 32 - total / 2;
|
|
25
|
+
const placed = rows.map((t, i) => {
|
|
26
|
+
const size = sizes[i];
|
|
27
|
+
const y = top + size * 0.86;
|
|
28
|
+
top += size * 1.12;
|
|
29
|
+
return { text: t, x: x0, y, size, width: widthOf(t) * size };
|
|
30
|
+
});
|
|
31
|
+
const fits = placed.every((p) => {
|
|
32
|
+
const end = p.x + p.width;
|
|
33
|
+
const reach = half(end) - 3;
|
|
34
|
+
return end <= 56 && p.y - p.size * 0.8 >= 32 - reach && p.y + p.size * 0.04 <= 32 + reach;
|
|
35
|
+
});
|
|
36
|
+
if (fits)
|
|
37
|
+
return placed;
|
|
38
|
+
}
|
|
39
|
+
return rows.map((t, i) => ({ text: t, x: x0, y: 32 + i * 5, size: 2.2, width: widthOf(t) * 2.2 }));
|
|
40
|
+
}
|
|
41
|
+
/** One side of the pennant as a document. */
|
|
42
|
+
function draw(title, o, back) {
|
|
43
|
+
const r = resolve(o);
|
|
44
|
+
const line = r.style === "line";
|
|
45
|
+
const felt = r.tint ?? readColour(FELT);
|
|
46
|
+
const trim = r.accent ?? CREAM;
|
|
47
|
+
const ink = hexOf(readColour(o?.numberColor) ?? readColour(trim));
|
|
48
|
+
// The lettering is the engraving of the options: the free text (one line a row) or the rank and label, then the year, in capitals.
|
|
49
|
+
const e = engravingOf(o);
|
|
50
|
+
const rows = [...(e.free !== undefined ? e.free.split("\n") : [[e.rank, e.label].filter((part) => part !== undefined).join(" ")]), e.year ?? ""].map((t) => t.toUpperCase()).filter((t) => t !== "");
|
|
51
|
+
const flatFelt = hexOf(felt);
|
|
52
|
+
const pid = `dgpf${flatFelt.slice(1)}`;
|
|
53
|
+
let body = `<g${back ? ' transform="translate(64 0) scale(-1 1)"' : ""}>`;
|
|
54
|
+
body += `<path d="${ROD}" fill="${line ? "none" : "#a8743c"}"/>`;
|
|
55
|
+
body += `<path d="${SHAPE}" fill="${line ? "none" : r.style === "metal" ? `url(#${pid})` : flatFelt}"/>`;
|
|
56
|
+
if (!line && r.style === "metal")
|
|
57
|
+
body += `<path d="${SHAPE}" fill="url(#dgsp)" stroke="none"/>`;
|
|
58
|
+
body += `<path d="${STITCH}" fill="none" stroke="${line ? "currentColor" : trim}" stroke-width="${line ? 1.2 : 0.7}" stroke-dasharray="1.9 1.5" stroke-linecap="butt"/>`;
|
|
59
|
+
const text = back ? [] : lettering(rows);
|
|
60
|
+
if (text.length > 0) {
|
|
61
|
+
body += `<g font-family="${FONT}" font-weight="800" fill="${line ? "currentColor" : ink}" stroke="none">${text
|
|
62
|
+
.map((t) => `<text x="${n2(t.x)}" y="${n2(t.y)}" font-size="${n2(t.size)}" textLength="${n2(t.width)}" lengthAdjust="spacingAndGlyphs">${escape(t.text)}</text>`)
|
|
63
|
+
.join("")}</g>`;
|
|
64
|
+
}
|
|
65
|
+
body += "</g>";
|
|
66
|
+
const defs = r.style !== "metal"
|
|
67
|
+
? ""
|
|
68
|
+
: `<defs><pattern id="${pid}" width="2.4" height="2.4" patternUnits="userSpaceOnUse"><rect width="2.4" height="2.4" fill="${flatFelt}" stroke="none"/><circle cx=".6" cy=".7" r=".32" fill="#fff" fill-opacity=".14" stroke="none"/><circle cx="1.8" cy="1.6" r=".3" fill="#000" fill-opacity=".14" stroke="none"/></pattern><linearGradient id="dgsp" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fff" stop-opacity=".22"/><stop offset=".5" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".28"/></linearGradient></defs>`;
|
|
69
|
+
return wrap({ title, size: r.size, style: r.style, theme: r.theme, defs, body, edge: mix(felt, -0.5), width: 0.8 });
|
|
70
|
+
}
|
|
71
|
+
/** A pennant: its front with the lettering, turned like a coin to its plain back. */
|
|
72
|
+
export function definePennant(id, en, ja) {
|
|
73
|
+
const svg = (o) => draw(en, o, o?.view === "back");
|
|
74
|
+
const frame = (o) => {
|
|
75
|
+
const back = o?.view === "back";
|
|
76
|
+
return flipSvg(draw(en, o, back), { yaw: degrees(o?.yaw), pitch: degrees(o?.pitch), line: o?.style === "line", back: draw(en, o, !back), thick: 0.016 });
|
|
77
|
+
};
|
|
78
|
+
return { id, en, ja, source: "ours", kind: "flat", model: null, svg, frame };
|
|
79
|
+
}
|