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
rodzic
0056e005ad
commit
f7bc1bdc56
|
@ -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)))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue