@johnmorrisdotca/dougu-sports 0.0.1 → 0.1.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 +24 -1
- package/LICENSES/Apache-2.0.txt +202 -55
- package/LICENSES/CC-BY-3.0.txt +319 -0
- package/LICENSES/README.md +4 -3
- package/LICENSES/upstream/game-icons.license.txt +45 -0
- package/LICENSES/upstream/noto-emoji-2D-svg.LICENSE +13 -0
- package/LICENSES/upstream/pictogrammers-mdi.LICENSE +20 -0
- package/NOTICE.md +92 -3
- package/README.md +104 -69
- package/dist/emblems/award/badge.d.ts +2 -0
- package/dist/emblems/award/badge.js +5 -0
- package/dist/emblems/award/crown.d.ts +2 -0
- package/dist/emblems/award/crown.js +5 -0
- package/dist/emblems/award/medal.d.ts +2 -2
- package/dist/emblems/award/medal.js +5 -19
- package/dist/emblems/award/shield.d.ts +2 -0
- package/dist/emblems/award/shield.js +5 -0
- package/dist/emblems/award/star.d.ts +2 -0
- package/dist/emblems/award/star.js +5 -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 +105 -0
- package/dist/emblems/ball/ball.core.js +66 -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 +39 -9
- package/dist/emblems/emblem.api.js +38 -10
- package/dist/emblems/emblem.core.d.ts +42 -6
- package/dist/emblems/emblem.core.js +55 -11
- package/dist/emblems/emblem.ids.d.ts +33 -27
- package/dist/emblems/emblem.ids.js +35 -16
- package/dist/emblems/emblem.lathe.d.ts +59 -0
- package/dist/emblems/emblem.lathe.js +404 -0
- package/dist/emblems/emblem.material.d.ts +9 -0
- package/dist/emblems/emblem.material.data.d.ts +34 -0
- package/dist/emblems/emblem.material.data.js +85 -0
- package/dist/emblems/emblem.material.js +23 -0
- package/dist/emblems/emblem.medal.d.ts +6 -0
- package/dist/emblems/emblem.medal.js +38 -0
- package/dist/emblems/emblem.registry.js +142 -4
- package/dist/emblems/emblem.shapes.d.ts +31 -12
- package/dist/emblems/emblem.shapes.js +99 -33
- package/dist/emblems/gear/gear.badmintonracket.d.ts +1 -0
- package/dist/emblems/gear/gear.badmintonracket.js +15 -0
- package/dist/emblems/gear/gear.baseballglove.d.ts +1 -0
- package/dist/emblems/gear/gear.baseballglove.js +15 -0
- package/dist/emblems/gear/gear.core.d.ts +83 -0
- package/dist/emblems/gear/gear.core.js +120 -0
- package/dist/emblems/gear/gear.cricketbat.d.ts +1 -0
- package/dist/emblems/gear/gear.cricketbat.js +15 -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 +15 -0
- package/dist/emblems/gear/gear.footballhelmet.d.ts +1 -0
- package/dist/emblems/gear/gear.footballhelmet.js +15 -0
- package/dist/emblems/gear/gear.goaliemask.d.ts +1 -0
- package/dist/emblems/gear/gear.goaliemask.js +17 -0
- package/dist/emblems/gear/gear.goalnet.d.ts +1 -0
- package/dist/emblems/gear/gear.goalnet.js +15 -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 +45 -0
- package/dist/emblems/gear/gear.lacrossestick.d.ts +1 -0
- package/dist/emblems/gear/gear.lacrossestick.js +15 -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.tabletennispaddle.d.ts +1 -0
- package/dist/emblems/gear/gear.tabletennispaddle.js +15 -0
- package/dist/emblems/gear/gear.tennisracket.d.ts +1 -0
- package/dist/emblems/gear/gear.tennisracket.js +15 -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/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 +5 -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 +5 -0
- package/dist/emblems/sport/ifaf.d.ts +2 -0
- package/dist/emblems/sport/ifaf.js +5 -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 +5 -0
- package/dist/emblems/sport/nba.d.ts +2 -0
- package/dist/emblems/sport/nba.js +5 -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 +2 -0
- package/dist/emblems/sport/nfl.js +5 -0
- package/dist/emblems/sport/nhl.d.ts +2 -0
- package/dist/emblems/sport/nhl.js +5 -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 +5 -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 +5 -0
- package/dist/emblems/sport/squash.d.ts +2 -0
- package/dist/emblems/sport/squash.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 +5 -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 +5 -0
- package/dist/emblems/sport/waterpolo.d.ts +2 -0
- package/dist/emblems/sport/waterpolo.js +15 -0
- package/dist/emblems/trophy/baseball.d.ts +2 -0
- package/dist/emblems/trophy/baseball.js +14 -0
- package/dist/emblems/trophy/basketball.d.ts +2 -0
- package/dist/emblems/trophy/basketball.js +16 -0
- package/dist/emblems/trophy/belt.d.ts +2 -0
- package/dist/emblems/trophy/belt.js +16 -0
- package/dist/emblems/trophy/bowl.d.ts +2 -0
- package/dist/emblems/trophy/bowl.js +7 -0
- package/dist/emblems/trophy/canadian-bowl.d.ts +2 -0
- package/dist/emblems/trophy/canadian-bowl.js +22 -0
- package/dist/emblems/trophy/cricket-urn.d.ts +2 -0
- package/dist/emblems/trophy/cricket-urn.js +22 -0
- package/dist/emblems/trophy/cup.d.ts +2 -0
- package/dist/emblems/trophy/cup.js +5 -0
- package/dist/emblems/trophy/football.d.ts +2 -0
- package/dist/emblems/trophy/football.js +10 -0
- package/dist/emblems/trophy/globe.d.ts +2 -0
- package/dist/emblems/trophy/globe.js +8 -0
- package/dist/emblems/trophy/hockey-cup.d.ts +2 -0
- package/dist/emblems/trophy/hockey-cup.js +21 -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 +20 -0
- package/dist/emblems/trophy/plaque.d.ts +2 -0
- package/dist/emblems/trophy/plaque.js +16 -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 +22 -0
- package/dist/emblems/trophy/salver.d.ts +2 -0
- package/dist/emblems/trophy/salver.js +8 -0
- package/dist/emblems/trophy/soccer-ball.d.ts +2 -0
- package/dist/emblems/trophy/soccer-ball.js +8 -0
- package/dist/emblems/trophy/soccer-globe.d.ts +2 -0
- package/dist/emblems/trophy/soccer-globe.js +17 -0
- package/dist/emblems/trophy/tennis-cup.d.ts +2 -0
- package/dist/emblems/trophy/tennis-cup.js +23 -0
- package/dist/emblems/trophy/tennis-plate.d.ts +2 -0
- package/dist/emblems/trophy/tennis-plate.js +15 -0
- package/dist/emblems-entry.d.ts +10 -6
- package/dist/emblems-entry.js +3 -2
- package/dist/index.d.ts +114 -1
- package/dist/index.js +116 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +28 -8
- 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,15 @@
|
|
|
1
|
+
// Roundnet ball: a small bright ball of eight panels, yellow and orange (our colouring).
|
|
2
|
+
import { defineBall } from "./ball.engine.js";
|
|
3
|
+
import { octants8 } from "./ball.family.panelled.js";
|
|
4
|
+
export const BALL_ROUNDNET = /* @__PURE__ */ defineBall({
|
|
5
|
+
id: "ball:roundnet",
|
|
6
|
+
en: "Roundnet ball",
|
|
7
|
+
ja: "ラウンドネットボール",
|
|
8
|
+
size: { extentMm: 105, diameterMm: 105, circumferenceMm: 330, massG: [80, 90], rule: "International Roundnet Federation (IRF) Rules, Chapter 1 Equipment and Court, 1.1.2 Set Dimensions, 1.1.4 Ball Characteristics, 1.1.5 Ball Inflation", note: "International Roundnet Federation rules (330 mm round, 10 mm either way); the rules give no diameter." },
|
|
9
|
+
spec: {
|
|
10
|
+
view: { yaw: 35, pitch: 28, roll: 0 },
|
|
11
|
+
base: "#f5dc16",
|
|
12
|
+
mat: { gloss: 0.6, shade: "#4a3a04", grain: { f: 1.4, a: 0.14 } },
|
|
13
|
+
draw: (s, base) => octants8(s, (sx, sy, sz) => (sx * sy * sz > 0 ? base : "#f08a1c"), "#7a5a10", 1.2),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BALL_RUGBY: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Rugby ball: a classic white ball, pointed at the ends, with four panels, a pebbled grip all over, and two bands of colour round it on each side: a wide one and a thin one. The colour is the ball's
|
|
2
|
+
// `tint` (a navy blue by default; the Laws do not set a colour and the ball carries no brand).
|
|
3
|
+
import { defineBall } from "./ball.engine.js";
|
|
4
|
+
import { seamAt, tipRing } from "./ball.family.prolate.js";
|
|
5
|
+
import { mix } from "./ball.core.js";
|
|
6
|
+
import { spread } from "./ball.math.js";
|
|
7
|
+
const GRIP = spread(260, 0.4);
|
|
8
|
+
const QUARTER = Math.PI / 2;
|
|
9
|
+
const WHITE = "#f4f2ec";
|
|
10
|
+
export const BALL_RUGBY = /* @__PURE__ */ defineBall({
|
|
11
|
+
id: "ball:rugby",
|
|
12
|
+
en: "Rugby ball",
|
|
13
|
+
ja: "ラグビーボール",
|
|
14
|
+
size: { extentMm: 290.0, lengthMm: 290.0, circumferenceMm: 600.0, longCircumferenceMm: 755.0, massG: [410, 460], rule: "World Rugby Laws of the Game (Rugby Union), Law 2 The ball, clauses 1-7" },
|
|
15
|
+
spec: {
|
|
16
|
+
axes: [1, 0.62, 0.62],
|
|
17
|
+
point: 0.72,
|
|
18
|
+
view: { yaw: -30, pitch: 20, roll: -30 },
|
|
19
|
+
base: "#1f4e9c",
|
|
20
|
+
ground: WHITE,
|
|
21
|
+
mat: { gloss: 0.4, shade: "#1a2030", grain: { f: 0.9, a: 0.18 } },
|
|
22
|
+
draw: (s, base) => {
|
|
23
|
+
let out = "";
|
|
24
|
+
// The grip: pebbles over the whole ball, a little darker than the white, each with its own highlight.
|
|
25
|
+
out += s.dots(GRIP, 0.03, mix(WHITE, -0.16), { opacity: 0.8, ink: false, light: false });
|
|
26
|
+
// The seams, tip to tip, then the bands over them.
|
|
27
|
+
out += s.strokes([0, 1, 2, 3].map((k) => seamAt(k * QUARTER + QUARTER / 2)), mix(WHITE, -0.3), 1.1);
|
|
28
|
+
for (const sign of [1, -1]) {
|
|
29
|
+
out += s.stroke(tipRing(sign, 1.02, 96), base, 9, { closed: true, ink: false });
|
|
30
|
+
out += s.stroke(tipRing(sign, 1.3, 96), base, 3, { closed: true, ink: false });
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BALL_SEPAK_TAKRAW: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Sepak takraw ball: woven rattan. Six bands, each of three strands, run round the ball as the six great circles that pass through the corners of an icosahedron, which leaves twelve
|
|
2
|
+
// pentagonal holes and twenty triangular ones (the ISTAF law gives 12 holes and 20 intersections). Where two bands cross one goes over: the weave is worked out, not drawn by hand.
|
|
3
|
+
import { defineBall } from "./ball.engine.js";
|
|
4
|
+
import { cross, dot, norm, smallCircle } from "./ball.math.js";
|
|
5
|
+
import { mix } from "./ball.core.js";
|
|
6
|
+
const PHI = (1 + Math.sqrt(5)) / 2;
|
|
7
|
+
const CORNERS = [[0, 1, PHI], [0, 1, -PHI], [1, PHI, 0], [1, -PHI, 0], [PHI, 0, 1], [-PHI, 0, 1]];
|
|
8
|
+
const AXES = CORNERS.map((v) => norm(v));
|
|
9
|
+
const SAMPLES = 48;
|
|
10
|
+
/** Half the width of a band, and the gap between its strands, in radians. */
|
|
11
|
+
const GAP = 0.034;
|
|
12
|
+
const ring = (a, d = 0) => smallCircle(a, Math.PI / 2 + d, SAMPLES);
|
|
13
|
+
const piece = (a, centre, d = 0, half = 0.2) => smallCircle(a, Math.PI / 2 + d, 24).filter((p) => dot(p, centre) > Math.cos(half));
|
|
14
|
+
/** The two points where band `i` crosses band `j`. */
|
|
15
|
+
const crossings = (i, j) => {
|
|
16
|
+
const c = norm(cross(AXES[i], AXES[j]));
|
|
17
|
+
return [c, [-c[0], -c[1], -c[2]]];
|
|
18
|
+
};
|
|
19
|
+
export const BALL_SEPAK_TAKRAW = /* @__PURE__ */ defineBall({
|
|
20
|
+
id: "ball:sepak-takraw",
|
|
21
|
+
en: "Sepak takraw ball",
|
|
22
|
+
ja: "セパタクローボール",
|
|
23
|
+
size: { extentMm: 133.7, diameterMm: 133.7, circumferenceMm: 420, massG: [170, 180], rule: "ISTAF Law of the Game (Rules of the Game), Law 4 The Ball (Sepaktakraw Ball), 4.1-4.5", note: "Men's ball (410 to 430 mm round); diameter worked out from the middle of the range. The 2013 edition of the law, the newest found." },
|
|
24
|
+
spec: {
|
|
25
|
+
view: { yaw: 14, pitch: 20, roll: 0 },
|
|
26
|
+
base: "#d3a24f",
|
|
27
|
+
ground: "#2c1a0a",
|
|
28
|
+
mat: { gloss: 0.35, shade: "#2a1604", rim: 0.9, grain: { f: 1.8, a: 0.4 } },
|
|
29
|
+
draw: (s, base) => {
|
|
30
|
+
const edge = mix(base, -0.5);
|
|
31
|
+
const band = (curves, closed) => s.strokes(curves, edge, 6.2, { closed }) + s.strokes(curves, base, 5.0, { closed, ink: false }) + s.strokes(curves.flatMap((c) => [c]), mix(base, 0.3), 0.5, { closed, deco: true, opacity: 0.5 });
|
|
32
|
+
const strand = (curves, closed) => s.strokes(curves, edge, 0.55, { closed, ink: false, opacity: 0.8 });
|
|
33
|
+
// Every band, then the strands' dividing lines.
|
|
34
|
+
let out = band(AXES.map((a) => ring(a)), true);
|
|
35
|
+
out += strand(AXES.flatMap((a) => [ring(a, GAP), ring(a, -GAP)]), true);
|
|
36
|
+
// The weave: at each crossing, the band chosen by parity is drawn again over the other.
|
|
37
|
+
const over = [];
|
|
38
|
+
for (let i = 0; i < AXES.length; i++) {
|
|
39
|
+
for (let j = i + 1; j < AXES.length; j++) {
|
|
40
|
+
crossings(i, j).forEach((c, k) => over.push([AXES[(i + j + k) % 2 === 0 ? i : j], c]));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
out += band(over.map(([a, c]) => piece(a, c)), false);
|
|
44
|
+
out += strand(over.flatMap(([a, c]) => [piece(a, c, GAP), piece(a, c, -GAP)]), false);
|
|
45
|
+
return out;
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Shuttlecocks: a cork base and a skirt of 16 feathers (a goose's, overlapped), or a molded nylon skirt. Drawn from its parts in true 3D: the feathers are blades that lean out from the base, painted
|
|
2
|
+
// far to near, so the shuttlecock turns like any ball. 16 feathers, 62 to 70 mm long, the skirt 58 to 68 mm across (the BWF Laws, Law 2).
|
|
3
|
+
import { defineObject } from "./ball.object.js";
|
|
4
|
+
import {} from "./ball.math.js";
|
|
5
|
+
import { blend, mix, num } from "./ball.core.js";
|
|
6
|
+
const FEATHERS = 16;
|
|
7
|
+
// One unit is 32 mm: the skirt is 2 units across at the top, the base 0.84 across and the whole 2.06 long.
|
|
8
|
+
const ROOT_R = 0.42;
|
|
9
|
+
const TIP_R = 1;
|
|
10
|
+
const ROOT_Y = -0.56;
|
|
11
|
+
const TIP_Y = 1.0;
|
|
12
|
+
const SLOPE = 0.4;
|
|
13
|
+
const drawShuttle = (c, p) => {
|
|
14
|
+
const line = c.opts.style === "line";
|
|
15
|
+
const tip = (a, r, y, k = 0) => [Math.cos(a) * r, y, Math.sin(a) * r + k];
|
|
16
|
+
const pts = (...v) => v.map((q) => c.at(q)).map((q) => `${num(q[0])} ${num(q[1])}`).join("L");
|
|
17
|
+
const blades = [];
|
|
18
|
+
const step = (2 * Math.PI) / FEATHERS;
|
|
19
|
+
const rootHalf = p.nylon ? step / 2 : step * 0.36;
|
|
20
|
+
const tipHalf = p.nylon ? step / 2 : step * 0.62;
|
|
21
|
+
for (let i = 0; i < FEATHERS; i++) {
|
|
22
|
+
const a = i * step;
|
|
23
|
+
const corners = [tip(a - rootHalf, ROOT_R, ROOT_Y), tip(a - tipHalf, TIP_R, TIP_Y), tip(a + tipHalf, TIP_R, TIP_Y), tip(a + rootHalf, ROOT_R, ROOT_Y)];
|
|
24
|
+
const mid = tip(a, (ROOT_R + TIP_R) / 2, (ROOT_Y + TIP_Y) / 2);
|
|
25
|
+
const m = c.at(mid);
|
|
26
|
+
// How squarely the blade faces the light (from the upper left): a blade whose outward normal points left and toward you is bright.
|
|
27
|
+
const nrm = [Math.cos(a), SLOPE * -0.5, Math.sin(a)];
|
|
28
|
+
const nv = c.at([nrm[0], nrm[1], nrm[2]]);
|
|
29
|
+
const origin = c.at([0, 0, 0]);
|
|
30
|
+
const lx = (nv[0] - origin[0]) / 46, ly = (nv[1] - origin[1]) / 46, lz = nv[2] - 0;
|
|
31
|
+
const lit = Math.max(0, Math.min(1, 0.55 + 0.4 * (-0.5 * lx + 0.55 * ly + 0.3 * lz)));
|
|
32
|
+
const fill = blend(mix(p.feather, -0.28), mix(p.feather, 0.1), lit);
|
|
33
|
+
const quill = `M${pts(tip(a, ROOT_R, ROOT_Y), tip(a, TIP_R, TIP_Y))}`;
|
|
34
|
+
const outline = `M${pts(...corners)}Z`;
|
|
35
|
+
const svg = line
|
|
36
|
+
? `<path d="${outline}${p.nylon ? "" : quill}"/>`
|
|
37
|
+
: `<path d="${outline}" fill="${fill}"${p.nylon ? ` fill-opacity=".9"` : ""} stroke="${mix(p.feather, -0.55)}" stroke-width="${num(Math.max(0.7, c.minWidth * 0.6), 2)}" stroke-linejoin="round"/>` +
|
|
38
|
+
(p.nylon ? "" : `<path d="${quill}" fill="none" stroke="${p.shaft}" stroke-width="${num(Math.max(0.45, c.minWidth * 0.5), 2)}" stroke-linecap="round" opacity=".8"/>`);
|
|
39
|
+
blades.push({ depth: m[2], svg });
|
|
40
|
+
}
|
|
41
|
+
blades.sort((x, y) => x.depth - y.depth);
|
|
42
|
+
const ring = (y, r) => {
|
|
43
|
+
const q = [];
|
|
44
|
+
for (let i = 0; i <= 36; i++)
|
|
45
|
+
q.push(`${i === 0 ? "M" : "L"}${pts(tip((i / 36) * 2 * Math.PI, r, y))}`);
|
|
46
|
+
return q.join("");
|
|
47
|
+
};
|
|
48
|
+
// Where the base is: below the skirt, so it is behind it when the tip leans toward you, in front of it when the cork leans toward you.
|
|
49
|
+
const base = c.at([0, ROOT_Y, 0]);
|
|
50
|
+
const top = c.at([0, TIP_Y, 0]);
|
|
51
|
+
const corkFirst = base[2] <= top[2];
|
|
52
|
+
const cx = c.at([0, ROOT_Y - 0.4, 0]);
|
|
53
|
+
const corkR = 46 * 0.43 * ROOT_R;
|
|
54
|
+
const defs = line
|
|
55
|
+
? ""
|
|
56
|
+
: `<radialGradient id="${c.id}k" cx=".36" cy=".3" r=".8"><stop offset="0" stop-color="${blend(p.cork, "#fff", 0.35)}"/><stop offset=".5" stop-color="${p.cork}"/><stop offset="1" stop-color="${mix(p.cork, -0.55)}"/></radialGradient>`;
|
|
57
|
+
const cork = line
|
|
58
|
+
? `<circle cx="${num(cx[0])}" cy="${num(cx[1])}" r="${num(corkR)}"/>`
|
|
59
|
+
: `<circle cx="${num(cx[0])}" cy="${num(cx[1])}" r="${num(corkR)}" fill="url(#${c.id}k)"/><path d="${ring(ROOT_Y + 0.02, ROOT_R)}" fill="none" stroke="${p.band}" stroke-width="${num(Math.max(1.4, c.minWidth), 2)}" opacity=".9"/>`;
|
|
60
|
+
const thread = `<path d="${ring(0.55, 0.72)}" fill="none" stroke="${line ? "currentColor" : p.thread}" stroke-width="${num(Math.max(0.5, c.minWidth * 0.7), 2)}"${line ? ' opacity=".7"' : ""}/>`;
|
|
61
|
+
const rim = `<path d="${ring(TIP_Y, TIP_R)}" fill="none" stroke="${line ? "currentColor" : mix(p.feather, -0.3)}" stroke-width="${num(Math.max(0.5, c.minWidth * 0.6), 2)}"${line ? ' opacity=".6"' : ' opacity=".5"'}/>`;
|
|
62
|
+
const feathers = blades.map((b) => b.svg).join("");
|
|
63
|
+
let body = corkFirst ? cork + feathers : feathers + cork;
|
|
64
|
+
body += thread;
|
|
65
|
+
if (line)
|
|
66
|
+
body = `<g fill="none" stroke="currentColor" stroke-width="${num(Math.max(2.4, c.minWidth * 1.4), 2)}" stroke-linejoin="round" stroke-linecap="round">${body}${rim}</g>`;
|
|
67
|
+
else
|
|
68
|
+
body += rim;
|
|
69
|
+
return { defs, body };
|
|
70
|
+
};
|
|
71
|
+
const VIEW = { yaw: 0, pitch: 22, roll: -32 };
|
|
72
|
+
export const BALL_SHUTTLECOCK_FEATHER = /* @__PURE__ */ defineObject({
|
|
73
|
+
id: "ball:shuttlecock-feather",
|
|
74
|
+
en: "Shuttlecock, feather",
|
|
75
|
+
ja: "シャトルコック(水鳥の羽根)",
|
|
76
|
+
size: { extentMm: 150, diameterMm: 63, lengthMm: 66, massG: [4.74, 5.5], rule: "BWF Laws of Badminton, Law 2 Shuttle (2.1 general, 2.2 Feathered shuttle 2.2.1-2.2.6, 2.3 Non-feathered shuttle, 2.4 modifications) and Laws 3 Testing a shuttle for speed", note: "62 to 70 mm long with the tips on a circle 58 to 68 mm across, on a base 25 to 28 mm across; the box is drawn at the scale of the shuttlecock, which fills a part of it." },
|
|
77
|
+
view: VIEW,
|
|
78
|
+
scale: 0.43,
|
|
79
|
+
shift: [0, 0.02, 0],
|
|
80
|
+
model: { feathers: FEATHERS },
|
|
81
|
+
draw: (c) => drawShuttle(c, { feather: "#fbfaf4", shaft: "#b5ac94", cork: "#c8925a", band: "#f6f2e8", thread: "#8a826a", nylon: false }),
|
|
82
|
+
});
|
|
83
|
+
export const BALL_SHUTTLECOCK_NYLON = /* @__PURE__ */ defineObject({
|
|
84
|
+
id: "ball:shuttlecock-nylon",
|
|
85
|
+
en: "Shuttlecock, nylon",
|
|
86
|
+
ja: "シャトルコック(ナイロン)",
|
|
87
|
+
size: { extentMm: 150, diameterMm: 63, lengthMm: 66, massG: [4.74, 5.5], rule: "BWF Laws of Badminton, Law 2 Shuttle (2.1 general, 2.2 Feathered shuttle 2.2.1-2.2.6, 2.3 Non-feathered shuttle, 2.4 modifications) and Laws 3 Testing a shuttle for speed", note: "A non-feathered shuttle follows the same measures, with a tolerance of 10 per cent (Law 2.3); the box is drawn at the scale of the shuttlecock, which fills a part of it." },
|
|
88
|
+
view: VIEW,
|
|
89
|
+
scale: 0.43,
|
|
90
|
+
shift: [0, 0.02, 0],
|
|
91
|
+
model: { feathers: FEATHERS },
|
|
92
|
+
draw: (c) => drawShuttle(c, { feather: "#f2f4f6", shaft: "#aab0b8", cork: "#f0eadd", band: "#e04a3c", thread: "#7a8088", nylon: true }),
|
|
93
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BALL_SLIOTAR: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Sliotar: a cork-cored leather ball for hurling, with a raised rib along its seam.
|
|
2
|
+
import { seamedBall } from "./ball.family.seamed.js";
|
|
3
|
+
export const BALL_SLIOTAR = /* @__PURE__ */ seamedBall({
|
|
4
|
+
id: "ball:sliotar",
|
|
5
|
+
en: "Sliotar",
|
|
6
|
+
ja: "シリター",
|
|
7
|
+
size: { extentMm: 70.5, diameterMm: 70.5, massG: [110, 116], rule: "GAA Official Guide Part 2, Rules of Specification, Rule 4.4(i)", note: "The rule gives a diameter that leaves out the raised rib." },
|
|
8
|
+
base: "#efe8d4",
|
|
9
|
+
b: 0.36,
|
|
10
|
+
view: { yaw: 22, pitch: 34, roll: 8 },
|
|
11
|
+
mat: { gloss: 0.4, shade: "#4a3a20", grain: { f: 1.3, a: 0.2 } },
|
|
12
|
+
strip: { colour: "#ded4b6", width: 2.4, raised: true },
|
|
13
|
+
stitch: { colour: "#8a7a58", count: 90, half: 0.05, width: 0.6, lean: 0.0 },
|
|
14
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const BALL_LACROSSE: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
|
|
2
|
+
export declare const BALL_RACQUETBALL: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
|
|
3
|
+
export declare const BALL_TABLE_TENNIS: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
|
|
4
|
+
export declare const BALL_SHOT: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Plain round balls: the lacrosse ball, the racquetball, the table-tennis ball and the shot. A faint seam where a real one has it.
|
|
2
|
+
import { defineBall } from "./ball.engine.js";
|
|
3
|
+
import { greatCircle } from "./ball.math.js";
|
|
4
|
+
export const BALL_LACROSSE = /* @__PURE__ */ defineBall({
|
|
5
|
+
id: "ball:lacrosse",
|
|
6
|
+
en: "Lacrosse ball",
|
|
7
|
+
ja: "ラクロスボール",
|
|
8
|
+
size: { extentMm: 63.7, diameterMm: 63.7, circumferenceMm: 200.05, massG: [141.75, 148.83], rule: "World Lacrosse Rules of Men's Field Lacrosse 2025-2027 (Ver 1.0), Section 2 Equipment, Rule 14 The Ball, 14.1", note: "Diameter worked out from the middle of the circumference range." },
|
|
9
|
+
spec: {
|
|
10
|
+
view: { yaw: 0, pitch: 0, roll: 0 },
|
|
11
|
+
base: "#f2791c",
|
|
12
|
+
mat: { gloss: 0.5, shade: "#5a2208", grain: { f: 1.5, a: 0.14 } },
|
|
13
|
+
draw: () => "",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
export const BALL_RACQUETBALL = /* @__PURE__ */ defineBall({
|
|
17
|
+
id: "ball:racquetball",
|
|
18
|
+
en: "Racquetball",
|
|
19
|
+
ja: "ラケットボール",
|
|
20
|
+
size: { extentMm: 57.1, diameterMm: 57.1, massG: [39.7, 39.7], rule: "USA Racquetball Rules, Rule 2.2 Ball Specifications (a)-(b); Rule 2.3 Ball Selection", note: "Nominal values; the rule gives no tolerance." },
|
|
21
|
+
spec: {
|
|
22
|
+
view: { yaw: 25, pitch: 30, roll: 0 },
|
|
23
|
+
base: "#2f63d2",
|
|
24
|
+
mat: { gloss: 0.55, shade: "#06143a", grain: { f: 1.5, a: 0.1 } },
|
|
25
|
+
draw: (s) => s.stroke(greatCircle([0.35, 0.9, 0.25], 72), "#10265e", 0.9, { closed: true, opacity: 0.5, deco: false }),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
export const BALL_TABLE_TENNIS = /* @__PURE__ */ defineBall({
|
|
29
|
+
id: "ball:table-tennis",
|
|
30
|
+
en: "Table-tennis ball",
|
|
31
|
+
ja: "卓球ボール",
|
|
32
|
+
size: { extentMm: 40.0, diameterMm: 40.0, massG: [2.7, 2.7], rule: "Laws of Table Tennis, 2.3 The Ball, 2.3.1-2.3.3" },
|
|
33
|
+
spec: {
|
|
34
|
+
view: { yaw: 25, pitch: 30, roll: 0 },
|
|
35
|
+
base: "#f6f5f0",
|
|
36
|
+
mat: { gloss: 0.32, shade: "#2e3550", grain: { f: 2, a: 0.05 } },
|
|
37
|
+
draw: (s) => s.stroke(greatCircle([0.3, 0.8, 0.5], 72), "#9aa0ac", 0.45, { closed: true, opacity: 0.8 }),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
export const BALL_SHOT = /* @__PURE__ */ defineBall({
|
|
41
|
+
id: "ball:shot",
|
|
42
|
+
en: "Shot",
|
|
43
|
+
ja: "砲丸",
|
|
44
|
+
size: { extentMm: 120, diameterMm: 120, massG: [7260, 7260], rule: "World Athletics Technical Rules (C2.1), Rule 33 Shot Put - implement specification ('Shot'); smooth finish N7 (Ra <= 1.6 micrometres)", note: "Men's shot, 110 to 130 mm across; 7.260 kg is a minimum." },
|
|
45
|
+
spec: {
|
|
46
|
+
view: { yaw: 0, pitch: 0, roll: 0 },
|
|
47
|
+
base: "#8c949e",
|
|
48
|
+
mat: { gloss: 0.95, shade: "#0c1018", metal: true, rim: 1 },
|
|
49
|
+
draw: () => "",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// The classic soccer ball: 32 panels, twelve black pentagons and twenty white hexagons, laid on a truncated icosahedron; and a coloured one with a motif of our own (a triangle on each hexagon
|
|
2
|
+
// pointing at its three pentagons). No brand, no tournament's pattern.
|
|
3
|
+
import { defineBall } from "./ball.engine.js";
|
|
4
|
+
import { truncatedIcosahedron } from "./ball.polyhedra.js";
|
|
5
|
+
import { add, mul, norm } from "./ball.math.js";
|
|
6
|
+
import { blend, mix } from "./ball.core.js";
|
|
7
|
+
const SEAM = "#6b7180";
|
|
8
|
+
export const BALL_SOCCER = /* @__PURE__ */ defineBall({
|
|
9
|
+
id: "ball:soccer",
|
|
10
|
+
en: "Soccer ball",
|
|
11
|
+
ja: "サッカーボール",
|
|
12
|
+
size: { extentMm: 219.6, diameterMm: 219.6, circumferenceMm: 690.0, massG: [410, 450], rule: "IFAB Laws of the Game, Law 2 The Ball, section 2.1 Qualities and measurements", note: "Diameter worked out from the middle of the circumference range." },
|
|
13
|
+
spec: {
|
|
14
|
+
view: { yaw: 30, pitch: 24, roll: 8 },
|
|
15
|
+
base: "#1e2025",
|
|
16
|
+
ground: "#f8f8f6",
|
|
17
|
+
mat: { gloss: 0.8, shade: "#2a3450", grain: { f: 1.1, a: 0.16 } },
|
|
18
|
+
draw(s, base) {
|
|
19
|
+
const t = truncatedIcosahedron();
|
|
20
|
+
let out = "";
|
|
21
|
+
for (const p of t.pentagons)
|
|
22
|
+
out += s.panel(p, base, { ink: true });
|
|
23
|
+
out += s.strokes(t.edges, SEAM, 1.15);
|
|
24
|
+
out += s.strokes(t.edges, "#fff", 0.45, { deco: true, opacity: 0.5, dx: 0.3, dy: 0.35 });
|
|
25
|
+
return out;
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
export const BALL_SOCCER_COLOUR = /* @__PURE__ */ defineBall({
|
|
30
|
+
id: "ball:soccer-colour",
|
|
31
|
+
en: "Soccer ball, coloured",
|
|
32
|
+
ja: "サッカーボール(カラー)",
|
|
33
|
+
size: { extentMm: 219.6, diameterMm: 219.6, circumferenceMm: 690.0, massG: [410, 450], rule: "IFAB Laws of the Game, Law 2 The Ball, section 2.1 Qualities and measurements", note: "Diameter worked out from the middle of the circumference range." },
|
|
34
|
+
spec: {
|
|
35
|
+
view: { yaw: 30, pitch: 24, roll: 8 },
|
|
36
|
+
base: "#d4283c",
|
|
37
|
+
ground: "#f8f8f6",
|
|
38
|
+
mat: { gloss: 0.8, shade: "#3a2030", grain: { f: 1.1, a: 0.16 } },
|
|
39
|
+
draw(s, base) {
|
|
40
|
+
const t = truncatedIcosahedron();
|
|
41
|
+
const light = blend(base, "#ffffff", 0.45);
|
|
42
|
+
let out = "";
|
|
43
|
+
t.hexagons.forEach((_, i) => {
|
|
44
|
+
const c = t.hexagonCentres[i];
|
|
45
|
+
out += s.panel(t.hexagonPentagonSides[i].map((m) => norm(add(mul(c, 0.38), mul(m, 0.62)))), light, { n: 2 });
|
|
46
|
+
});
|
|
47
|
+
for (const p of t.pentagons)
|
|
48
|
+
out += s.panel(p, base, { ink: true });
|
|
49
|
+
out += s.strokes(t.edges, mix(base, -0.35), 1.1);
|
|
50
|
+
out += s.strokes(t.edges, "#fff", 0.45, { deco: true, opacity: 0.45, dx: 0.3, dy: 0.35 });
|
|
51
|
+
return out;
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BALL_SOFTBALL: import("./ball.core.ts").Piece<import("./ball.engine.ts").BallSpec>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Softball: an optic-yellow ball with red stitches along the seam (88 here, which the rule leaves free).
|
|
2
|
+
import { seamedBall } from "./ball.family.seamed.js";
|
|
3
|
+
export const BALL_SOFTBALL = /* @__PURE__ */ seamedBall({
|
|
4
|
+
id: "ball:softball",
|
|
5
|
+
en: "Softball",
|
|
6
|
+
ja: "ソフトボール",
|
|
7
|
+
size: { extentMm: 97.1, diameterMm: 97.1, circumferenceMm: 305.0, massG: [178.0, 198.4], rule: "WBSC Official Rules of Softball Slow Pitch 2026-2029, Appendix 3 (Ball Specifications), B. Dimensions and Specifications and 'Ball standards' table; Fast Pitch 2026-2029 Appendix 3 gives the same 12 in ball", note: "The 12 in ball (the 11 in ball is 279.5 mm round). Diameter worked out from the middle of the circumference range." },
|
|
8
|
+
base: "#d8e22a",
|
|
9
|
+
b: 0.4,
|
|
10
|
+
view: { yaw: 18, pitch: 28, roll: -10 },
|
|
11
|
+
mat: { gloss: 0.35, shade: "#4c5a10", grain: { f: 1.4, a: 0.12 } },
|
|
12
|
+
groove: "#8a9420",
|
|
13
|
+
stitch: { colour: "#d0262c", count: 88, half: 0.085, width: 0.95, lean: 0.04 },
|
|
14
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Piece } from "./ball.core.ts";
|
|
2
|
+
import type { BallSpec } from "./ball.engine.ts";
|
|
3
|
+
export declare const BALL_SQUASH_DOUBLE_YELLOW: Piece<BallSpec>;
|
|
4
|
+
export declare const BALL_SQUASH_YELLOW: Piece<BallSpec>;
|
|
5
|
+
export declare const BALL_SQUASH_RED: Piece<BallSpec>;
|
|
6
|
+
export declare const BALL_SQUASH_BLUE: Piece<BallSpec>;
|
|
7
|
+
export declare const BALL_SQUASH_GREEN: Piece<BallSpec>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Squash balls: small hollow black rubber balls with a coloured dot (or two) that says how lively the ball is: double yellow (the competition ball), yellow (club), green (high altitude), red
|
|
2
|
+
// (medium) and blue (fast). World Squash's specification recommends the dots; it does not require them.
|
|
3
|
+
import { defineBall } from "./ball.engine.js";
|
|
4
|
+
import { norm } from "./ball.math.js";
|
|
5
|
+
function squash(id, en, ja, colour, count, size) {
|
|
6
|
+
return defineBall({
|
|
7
|
+
id,
|
|
8
|
+
en,
|
|
9
|
+
ja,
|
|
10
|
+
size,
|
|
11
|
+
spec: {
|
|
12
|
+
view: { yaw: -18, pitch: 18, roll: 0 },
|
|
13
|
+
base: "#1f2023",
|
|
14
|
+
mat: { gloss: 0.5, shade: "#000000", grain: { f: 1.6, a: 0.12 } },
|
|
15
|
+
draw: (s) => {
|
|
16
|
+
const centres = count === 2 ? [norm([-0.28, 0.12, 1]), norm([0.34, 0.1, 1])] : [norm([0.04, 0.1, 1])];
|
|
17
|
+
return s.dots(centres, count === 2 ? 0.2 : 0.28, colour, { ink: true });
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const SIZE = { extentMm: 40.0, diameterMm: 40.0, massG: [23.0, 25.0], rule: "World Squash 'Specifications for Squash Balls' (Recommended standards approved by the WSF; updated WSF AGM 2021, rebranded 2025), sections 1-3 and Notes 2-4", note: "Size and weight are set for the double yellow, yellow and green dot balls; the red and blue dot balls carry no limits in the specification." };
|
|
23
|
+
export const BALL_SQUASH_DOUBLE_YELLOW = /* @__PURE__ */ squash("ball:squash", "Squash ball, double yellow dot", "スカッシュボール(ダブルイエロードット)", "#f4d41c", 2, SIZE);
|
|
24
|
+
export const BALL_SQUASH_YELLOW = /* @__PURE__ */ squash("ball:squash-yellow", "Squash ball, yellow dot", "スカッシュボール(イエロードット)", "#f4d41c", 1, SIZE);
|
|
25
|
+
export const BALL_SQUASH_RED = /* @__PURE__ */ squash("ball:squash-red", "Squash ball, red dot", "スカッシュボール(レッドドット)", "#d9262c", 1, SIZE);
|
|
26
|
+
export const BALL_SQUASH_BLUE = /* @__PURE__ */ squash("ball:squash-blue", "Squash ball, blue dot", "スカッシュボール(ブルードット)", "#2b6fd6", 1, SIZE);
|
|
27
|
+
export const BALL_SQUASH_GREEN = /* @__PURE__ */ squash("ball:squash-green", "Squash ball, green dot", "スカッシュボール(グリーンドット)", "#2aa84a", 1, SIZE);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ObjectContext } from "./ball.object.ts";
|
|
2
|
+
export interface Slice {
|
|
3
|
+
y0: number;
|
|
4
|
+
y1: number;
|
|
5
|
+
/** The radius at the lower end and at the upper end (one number for a plain cylinder). */
|
|
6
|
+
r: number | readonly [number, number];
|
|
7
|
+
colour: string;
|
|
8
|
+
/** Drawn in the line style (the others are left out so a line drawing keeps to the outline). */
|
|
9
|
+
outline?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface StackSpec {
|
|
12
|
+
slices: readonly Slice[];
|
|
13
|
+
top: string;
|
|
14
|
+
bottom: string;
|
|
15
|
+
gloss: number;
|
|
16
|
+
shade: string;
|
|
17
|
+
grain?: {
|
|
18
|
+
f: number;
|
|
19
|
+
a: number;
|
|
20
|
+
};
|
|
21
|
+
/** Lines down the side: how many round the whole slice, on the slices that have `grooved`. */
|
|
22
|
+
grooves?: {
|
|
23
|
+
count: number;
|
|
24
|
+
slices: readonly number[];
|
|
25
|
+
colour: string;
|
|
26
|
+
opacity: number;
|
|
27
|
+
};
|
|
28
|
+
/** A ring on the top face: radius as a fraction, colour. */
|
|
29
|
+
topRing?: {
|
|
30
|
+
r: number;
|
|
31
|
+
colour: string;
|
|
32
|
+
};
|
|
33
|
+
/** What lies in the bottom face when it faces you: a dish. */
|
|
34
|
+
dish?: {
|
|
35
|
+
r: number;
|
|
36
|
+
colour: string;
|
|
37
|
+
};
|
|
38
|
+
/** Drawn on top of the stack, in the stack's own space (a handle). `up` is the screen position of the top face's centre. */
|
|
39
|
+
extra?: (c: ObjectContext, top: {
|
|
40
|
+
cx: number;
|
|
41
|
+
cy: number;
|
|
42
|
+
nz: number;
|
|
43
|
+
}) => string;
|
|
44
|
+
}
|
|
45
|
+
export declare function drawStack(c: ObjectContext, spec: StackSpec): {
|
|
46
|
+
defs: string;
|
|
47
|
+
body: string;
|
|
48
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// A stack of round slices on one axis, drawn in true 3D by the rules of an orthographic view: each end of a slice is an ellipse (its minor axis along the projected axis), the side between them
|
|
2
|
+
// is a band as wide as the slice, and what is nearer is painted last. A puck is one slice; a curling stone is six and a handle.
|
|
3
|
+
import {} from "./ball.object.js";
|
|
4
|
+
import { apply } from "./ball.math.js";
|
|
5
|
+
import { blend, grainDef, mix, num } from "./ball.core.js";
|
|
6
|
+
const LIGHT = [-0.55, -0.72, 0];
|
|
7
|
+
const ellipsePath = (cx, cy, major, minor, deg) => {
|
|
8
|
+
const rad = (deg * Math.PI) / 180;
|
|
9
|
+
const ax = Math.cos(rad) * major, ay = Math.sin(rad) * major;
|
|
10
|
+
const arcs = `${num(major, 2)} ${num(Math.max(minor, 0.02), 2)} ${num(deg, 1)} 1 0`;
|
|
11
|
+
return `M${num(cx + ax, 2)} ${num(cy + ay, 2)}A${arcs} ${num(cx - ax, 2)} ${num(cy - ay, 2)}A${arcs} ${num(cx + ax, 2)} ${num(cy + ay, 2)}Z`;
|
|
12
|
+
};
|
|
13
|
+
/** The half of an ellipse on one side: through the point `side` (-1 or 1) of the way along `d` from the centre. */
|
|
14
|
+
const halfEllipse = (cx, cy, major, minor, deg, side) => {
|
|
15
|
+
const rad = (deg * Math.PI) / 180;
|
|
16
|
+
const ax = Math.cos(rad) * major, ay = Math.sin(rad) * major;
|
|
17
|
+
const sweep = side < 0 ? 1 : 0;
|
|
18
|
+
return `M${num(cx + ax, 2)} ${num(cy + ay, 2)}A${num(major, 2)} ${num(Math.max(minor, 0.02), 2)} ${num(deg, 1)} 0 ${sweep} ${num(cx - ax, 2)} ${num(cy - ay, 2)}`;
|
|
19
|
+
};
|
|
20
|
+
export function drawStack(c, spec) {
|
|
21
|
+
const line = c.opts.style === "line";
|
|
22
|
+
const n = apply(c.rot, [0, 1, 0]);
|
|
23
|
+
const nz = n[2];
|
|
24
|
+
const ax = 46 * n[0], ay = -46 * n[1];
|
|
25
|
+
const dl = Math.hypot(ax, ay);
|
|
26
|
+
const d = dl < 1e-3 ? [1, 0] : [ax / dl, ay / dl];
|
|
27
|
+
const perp = [-d[1], d[0]];
|
|
28
|
+
const perpDeg = (Math.atan2(perp[1], perp[0]) * 180) / Math.PI;
|
|
29
|
+
const at = (y) => [50 + y * ax, 50 + y * ay];
|
|
30
|
+
const topVisible = nz >= 0;
|
|
31
|
+
const shadeEnd = (col) => mix(col, -0.55);
|
|
32
|
+
const pl = perp[0] * LIGHT[0] + perp[1] * LIGHT[1];
|
|
33
|
+
const u = Math.min(0.78, Math.max(0.22, 0.5 + 0.34 * pl));
|
|
34
|
+
const radius = (s, end) => (typeof s.r === "number" ? s.r : s.r[end]);
|
|
35
|
+
let defs = "";
|
|
36
|
+
const slices = spec.slices;
|
|
37
|
+
const maxR = Math.max(...slices.map((s) => Math.max(radius(s, 0), radius(s, 1))));
|
|
38
|
+
const mid = at((slices[0].y0 + slices.at(-1).y1) / 2);
|
|
39
|
+
const g0 = [mid[0] - perp[0] * 46 * maxR, mid[1] - perp[1] * 46 * maxR];
|
|
40
|
+
const g1 = [mid[0] + perp[0] * 46 * maxR, mid[1] + perp[1] * 46 * maxR];
|
|
41
|
+
const side = (s, i) => {
|
|
42
|
+
const gid = `${c.id}s${i}`;
|
|
43
|
+
const base = s.colour;
|
|
44
|
+
defs += `<linearGradient id="${gid}" gradientUnits="userSpaceOnUse" x1="${num(g0[0], 2)}" y1="${num(g0[1], 2)}" x2="${num(g1[0], 2)}" y2="${num(g1[1], 2)}"><stop offset="0" stop-color="${shadeEnd(base)}"/><stop offset="${num(Math.max(0.03, u - 0.26), 2)}" stop-color="${base}"/><stop offset="${num(u, 2)}" stop-color="${blend(base, "#ffffff", 0.18 + spec.gloss * 0.3)}"/><stop offset="${num(Math.min(0.97, u + 0.28), 2)}" stop-color="${base}"/><stop offset="1" stop-color="${shadeEnd(base)}"/></linearGradient>`;
|
|
45
|
+
const [x0, y0] = at(s.y0), [x1, y1] = at(s.y1);
|
|
46
|
+
const rb = 46 * radius(s, 0), rt = 46 * radius(s, 1);
|
|
47
|
+
const pts = [[x0 + perp[0] * rb, y0 + perp[1] * rb], [x1 + perp[0] * rt, y1 + perp[1] * rt], [x1 - perp[0] * rt, y1 - perp[1] * rt], [x0 - perp[0] * rb, y0 - perp[1] * rb]];
|
|
48
|
+
const d4 = `M${pts.map((p) => `${num(p[0], 2)} ${num(p[1], 2)}`).join("L")}Z`;
|
|
49
|
+
if (line) {
|
|
50
|
+
if (s.outline !== true)
|
|
51
|
+
return "";
|
|
52
|
+
const lines = `M${num(pts[0][0], 2)} ${num(pts[0][1], 2)}L${num(pts[1][0], 2)} ${num(pts[1][1], 2)}M${num(pts[2][0], 2)} ${num(pts[2][1], 2)}L${num(pts[3][0], 2)} ${num(pts[3][1], 2)}`;
|
|
53
|
+
const rim = (y, r, side) => {
|
|
54
|
+
const [cx, cy] = at(y);
|
|
55
|
+
return halfEllipse(cx, cy, 46 * r, 46 * r * Math.abs(nz), perpDeg, side);
|
|
56
|
+
};
|
|
57
|
+
const near = topVisible ? -1 : 1;
|
|
58
|
+
return `<path d="${lines}${rim(s.y0, radius(s, 0), near)}${i === slices.length - 1 ? "" : rim(s.y1, radius(s, 1), near)}" fill="none"/>`;
|
|
59
|
+
}
|
|
60
|
+
let out = `<path d="${d4}" fill="url(#${gid})"/>`;
|
|
61
|
+
const gr = spec.grooves;
|
|
62
|
+
if (!line && gr !== undefined && gr.slices.includes(i)) {
|
|
63
|
+
let marks = "";
|
|
64
|
+
for (let k = 0; k < gr.count; k++) {
|
|
65
|
+
const t = ((k + 0.5) / gr.count) * Math.PI;
|
|
66
|
+
const uu = Math.cos(t);
|
|
67
|
+
const facing = Math.sin(t);
|
|
68
|
+
if (facing < 0.15)
|
|
69
|
+
continue;
|
|
70
|
+
marks += `M${num(x0 + perp[0] * rb * uu, 2)} ${num(y0 + perp[1] * rb * uu, 2)}L${num(x1 + perp[0] * rt * uu, 2)} ${num(y1 + perp[1] * rt * uu, 2)}`;
|
|
71
|
+
}
|
|
72
|
+
out += `<path d="${marks}" stroke="${gr.colour}" stroke-width="${num(Math.max(0.5, c.minWidth * 0.5), 2)}" opacity="${gr.opacity}" fill="none"/>`;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
};
|
|
76
|
+
const cap = (y, r, colour, i, topFace) => {
|
|
77
|
+
const [cx, cy] = at(y);
|
|
78
|
+
const major = 46 * r;
|
|
79
|
+
const minor = Math.max(major * Math.abs(nz), 0.02);
|
|
80
|
+
const dpath = ellipsePath(cx, cy, major, minor, perpDeg);
|
|
81
|
+
if (line)
|
|
82
|
+
return (topFace && (i === `t${slices.length - 1}` || i === `d0`)) || i === "dish" ? `<path d="${dpath}" fill="none"/>` : "";
|
|
83
|
+
const gid = `${c.id}c${i}`;
|
|
84
|
+
defs += `<radialGradient id="${gid}" cx=".36" cy=".3" r=".85"><stop offset="0" stop-color="${blend(colour, "#ffffff", 0.2 + spec.gloss * 0.34)}"/><stop offset=".45" stop-color="${colour}"/><stop offset="1" stop-color="${mix(colour, topFace ? -0.34 : -0.5)}"/></radialGradient>`;
|
|
85
|
+
return `<path d="${dpath}" fill="url(#${gid})"/>`;
|
|
86
|
+
};
|
|
87
|
+
const ordered = topVisible ? slices.map((s, i) => [s, i]) : slices.map((s, i) => [s, i]).reverse();
|
|
88
|
+
let body = "";
|
|
89
|
+
if (topVisible) {
|
|
90
|
+
const s0 = slices[0];
|
|
91
|
+
body += cap(s0.y0, radius(s0, 0), mix(s0.colour, -0.25), "b", false);
|
|
92
|
+
for (const [s, i] of ordered) {
|
|
93
|
+
body += side(s, i);
|
|
94
|
+
body += cap(s.y1, radius(s, 1), i === slices.length - 1 ? spec.top : s.colour, `t${i}`, true);
|
|
95
|
+
}
|
|
96
|
+
if (spec.topRing !== undefined) {
|
|
97
|
+
const [cx, cy] = at(slices.at(-1).y1);
|
|
98
|
+
const r = 46 * radius(slices.at(-1), 1) * spec.topRing.r;
|
|
99
|
+
body += `<path d="${ellipsePath(cx, cy, r, r * Math.abs(nz), perpDeg)}" fill="none" stroke="${line ? "currentColor" : spec.topRing.colour}" stroke-width="${num(Math.max(0.6, c.minWidth * 0.6), 2)}" opacity=".7"/>`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const sT = slices.at(-1);
|
|
104
|
+
body += cap(sT.y1, radius(sT, 1), mix(sT.colour, -0.25), "u", false);
|
|
105
|
+
for (const [s, i] of ordered) {
|
|
106
|
+
body += side(s, i);
|
|
107
|
+
body += cap(s.y0, radius(s, 0), i === 0 ? spec.bottom : s.colour, `d${i}`, true);
|
|
108
|
+
}
|
|
109
|
+
if (spec.dish !== undefined) {
|
|
110
|
+
const [cx, cy] = at(slices[0].y0);
|
|
111
|
+
const r = 46 * radius(slices[0], 0) * spec.dish.r;
|
|
112
|
+
const gid = `${c.id}dish`;
|
|
113
|
+
defs += `<radialGradient id="${gid}" cx=".64" cy=".7" r=".9"><stop offset="0" stop-color="${blend(spec.dish.colour, "#ffffff", 0.3)}"/><stop offset=".5" stop-color="${spec.dish.colour}"/><stop offset="1" stop-color="${mix(spec.dish.colour, -0.6)}"/></radialGradient>`;
|
|
114
|
+
body += `<path d="${ellipsePath(cx, cy, r, r * Math.abs(nz), perpDeg)}" fill="${line ? "none" : `url(#${gid})`}"/>`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const [tx, ty] = at(slices.at(-1).y1);
|
|
118
|
+
if (spec.extra !== undefined)
|
|
119
|
+
body += spec.extra(c, { cx: tx, cy: ty, nz });
|
|
120
|
+
const gr = c.opts.grain ?? (c.opts.size >= 48 && !c.turning);
|
|
121
|
+
const grain = !line && gr && spec.grain !== undefined;
|
|
122
|
+
if (grain)
|
|
123
|
+
defs += grainDef(c.id, spec.grain.f, spec.grain.a);
|
|
124
|
+
if (line)
|
|
125
|
+
return { defs: "", body: `<g fill="none" stroke="currentColor" stroke-width="${num(Math.max(2.4, c.minWidth * 1.4), 2)}" stroke-linejoin="round" stroke-linecap="round">${body}</g>` };
|
|
126
|
+
return { defs, body: grain ? `<g filter="url(#${c.id}n)">${body}</g>` : body };
|
|
127
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type V3 } from "./ball.math.ts";
|
|
2
|
+
/** Marks across a curve: `count` marks evenly along it; a mark runs `half` radians either side of the seam and leans along it by `lean`. A `v` mark has two legs that meet on the seam (a baseball's stitch). */
|
|
3
|
+
export declare function stitches(pts: readonly V3[], closed: boolean, count: number, half: number, o?: {
|
|
4
|
+
lean?: number;
|
|
5
|
+
v?: boolean;
|
|
6
|
+
offset?: number;
|
|
7
|
+
phase?: number;
|
|
8
|
+
}): V3[][];
|
|
9
|
+
/** A dashed row: segments of a curve, each `dash` of every `period` long as a fraction of the curve. */
|
|
10
|
+
export declare function dashes(pts: readonly V3[], closed: boolean, count: number, fill?: number): V3[][];
|