Add some hills to the waves page. (#177)

This minimally fixes #143 by adding optional hills to the waves page (they default to being disabled but can be enabled via a checkbox).  It also adds haze to the waves, so that they are blended with the color of the sky as they recede into the distance.
pull/200/head
Atul Varma 2021-07-04 16:34:17 -04:00 zatwierdzone przez GitHub
rodzic 0056e005ad
commit f7bc1bdc56
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
3 zmienionych plików z 110 dodań i 2 usunięć

Wyświetl plik

@ -1,3 +1,5 @@
import { lerp } from "./util";
/**
* Clamp the given number to be between 0 and 255, then
* convert it to hexadecimal.
@ -32,3 +34,15 @@ export function parseHexColor(value: string): [number, number, number] {
const blue = parseInt(value.substring(5, 7), 16);
return [red, green, blue];
}
/**
* Mix two colors together. The first color will be tinted by
* the second color by the given percentage (from 0 to 1).
*/
export function mixColor(a: string, b: string, amount: number) {
const aRGB = parseHexColor(a);
const bRGB = parseHexColor(b);
return clampedBytesToRGBColor(
aRGB.map((aValue, i) => Math.floor(lerp(aValue, bRGB[i], amount)))
);
}

Wyświetl plik

@ -1,8 +1,11 @@
import React, { useState } from "react";
import { Checkbox } from "../checkbox";
import { mixColor } from "../color-util";
import { ColorWidget } from "../color-widget";
import { NumericSlider } from "../numeric-slider";
import { Page } from "../page";
import { Random } from "../random";
import { lerp, range } from "../util";
const WAVE_STROKE = "#79beda";
const WAVE_FILL = "#2b7c9e";
@ -57,7 +60,9 @@ const Wave: React.FC<{
</>
);
const NUM_WAVES = 8;
const BG_COLOR = "#FFFFFF";
const BG_MAX_BLEND = 0.33;
const NUM_WAVES = 10;
const WAVE_DURATION = 1;
const WAVE_PARALLAX_SCALE_START = 1.2;
const WAVE_PARALLAX_TRANSLATE_START = 10;
@ -65,7 +70,51 @@ const WAVE_PARALLAX_SCALE_VELOCITY = 1.25;
const WAVE_PARALLAX_TRANSLATE_VELOCITY = 30;
const WAVE_PARALLAX_TRANSLATE_ACCEL = 10;
type HillProps = {
idPrefix: string;
xScale: number;
yScale: number;
cx: number;
cy: number;
r: number;
highlight: string;
shadow: string;
};
const DEFAULT_HILL_PROPS: HillProps = {
idPrefix: "",
xScale: 1,
yScale: 1,
cx: 50,
cy: 50,
r: 50,
highlight: "#aeb762",
shadow: "#616934",
};
const Hill: React.FC<Partial<HillProps>> = (props) => {
const { idPrefix, xScale, yScale, cx, cy, r, highlight, shadow } = {
...DEFAULT_HILL_PROPS,
...props,
};
const gradientId = `${idPrefix}HillGradient`;
const gradientUrl = `url(#${gradientId})`;
return (
<g transform={`translate(${cx} ${cy}) scale(${xScale} ${yScale})`}>
<radialGradient id={gradientId}>
<stop offset="75%" stopColor={highlight} />
<stop offset="100%" stopColor={shadow} />
</radialGradient>
<circle cx={0} cy={0} r={r} fill={gradientUrl} />
</g>
);
};
const Waves: React.FC<{}> = () => {
const [randomSeed, setRandomSeed] = useState<number>(Date.now());
const newRandomSeed = () => setRandomSeed(Date.now());
const rng = new Random(randomSeed);
const [stroke, setStroke] = useState(WAVE_STROKE);
const [fill, setFill] = useState(WAVE_FILL);
const [numWaves, setNumWaves] = useState(NUM_WAVES);
@ -73,6 +122,7 @@ const Waves: React.FC<{}> = () => {
const [initialYVel, setInitialYVel] = useState(
WAVE_PARALLAX_TRANSLATE_VELOCITY
);
const [showHills, setShowHills] = useState(false);
const [yAccel, setYAccel] = useState(WAVE_PARALLAX_TRANSLATE_ACCEL);
const [scaleVel, setScaleVel] = useState(WAVE_PARALLAX_SCALE_VELOCITY);
const [useMask, setUseMask] = useState(false);
@ -83,10 +133,40 @@ const Waves: React.FC<{}> = () => {
let waves: JSX.Element[] = [];
for (let i = 0; i < numWaves; i++) {
const numHills = Math.floor(rng.inInterval({ min: 0, max: numWaves - i }));
const hazeAmt = lerp(
0,
BG_MAX_BLEND,
// The furthest-away waves (the first ones we draw) should be the
// most hazy. Scale the amount quadratically so that the waves in
// front tend to be less hazy.
Math.pow(1 - i / Math.max(numWaves - 1, 1), 2)
);
const blendedFill = mixColor(fill, BG_COLOR, hazeAmt);
const blendedStroke = mixColor(stroke, BG_COLOR, hazeAmt);
waves.push(
<g key={i} transform={`translate(0 ${y}) scale(${scale} ${scale})`}>
{showHills &&
range(numHills).map((j) => {
return (
<Hill
key={j}
idPrefix={`wave${i}_${j}_`}
cx={rng.inInterval({ min: 0, max: 1280 / scale })}
r={rng.inInterval({ min: 50, max: 100 })}
xScale={rng.inInterval({ min: 1, max: 1.25 })}
highlight={mixColor(
DEFAULT_HILL_PROPS.highlight,
BG_COLOR,
hazeAmt
)}
shadow={mixColor(DEFAULT_HILL_PROPS.shadow, BG_COLOR, hazeAmt)}
/>
);
})}
<g>
<Wave fill={fill} stroke={stroke} />
<Wave fill={blendedFill} stroke={blendedStroke} />
<animateTransform
attributeName="transform"
type="translate"
@ -172,6 +252,12 @@ const Waves: React.FC<{}> = () => {
value={useMask}
onChange={setUseMask}
/>
<Checkbox label="Hills" value={showHills} onChange={setShowHills} />
{showHills && (
<button accessKey="r" onClick={newRandomSeed}>
<u>R</u>andomize hills!
</button>
)}
</div>
</>
);

Wyświetl plik

@ -124,3 +124,11 @@ export function withoutNulls<T>(arr: (T | null)[]): T[] {
return result;
}
/**
* Linearly interpolate between the given numbers by the
* given percentage (from 0 to 1).
*/
export function lerp(a: number, b: number, amount: number) {
return a + amount * (b - a);
}