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
|
* Clamp the given number to be between 0 and 255, then
|
||||||
* convert it to hexadecimal.
|
* convert it to hexadecimal.
|
||||||
|
@ -32,3 +34,15 @@ export function parseHexColor(value: string): [number, number, number] {
|
||||||
const blue = parseInt(value.substring(5, 7), 16);
|
const blue = parseInt(value.substring(5, 7), 16);
|
||||||
return [red, green, blue];
|
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 React, { useState } from "react";
|
||||||
import { Checkbox } from "../checkbox";
|
import { Checkbox } from "../checkbox";
|
||||||
|
import { mixColor } from "../color-util";
|
||||||
import { ColorWidget } from "../color-widget";
|
import { ColorWidget } from "../color-widget";
|
||||||
import { NumericSlider } from "../numeric-slider";
|
import { NumericSlider } from "../numeric-slider";
|
||||||
import { Page } from "../page";
|
import { Page } from "../page";
|
||||||
|
import { Random } from "../random";
|
||||||
|
import { lerp, range } from "../util";
|
||||||
|
|
||||||
const WAVE_STROKE = "#79beda";
|
const WAVE_STROKE = "#79beda";
|
||||||
const WAVE_FILL = "#2b7c9e";
|
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_DURATION = 1;
|
||||||
const WAVE_PARALLAX_SCALE_START = 1.2;
|
const WAVE_PARALLAX_SCALE_START = 1.2;
|
||||||
const WAVE_PARALLAX_TRANSLATE_START = 10;
|
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_VELOCITY = 30;
|
||||||
const WAVE_PARALLAX_TRANSLATE_ACCEL = 10;
|
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 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 [stroke, setStroke] = useState(WAVE_STROKE);
|
||||||
const [fill, setFill] = useState(WAVE_FILL);
|
const [fill, setFill] = useState(WAVE_FILL);
|
||||||
const [numWaves, setNumWaves] = useState(NUM_WAVES);
|
const [numWaves, setNumWaves] = useState(NUM_WAVES);
|
||||||
|
@ -73,6 +122,7 @@ const Waves: React.FC<{}> = () => {
|
||||||
const [initialYVel, setInitialYVel] = useState(
|
const [initialYVel, setInitialYVel] = useState(
|
||||||
WAVE_PARALLAX_TRANSLATE_VELOCITY
|
WAVE_PARALLAX_TRANSLATE_VELOCITY
|
||||||
);
|
);
|
||||||
|
const [showHills, setShowHills] = useState(false);
|
||||||
const [yAccel, setYAccel] = useState(WAVE_PARALLAX_TRANSLATE_ACCEL);
|
const [yAccel, setYAccel] = useState(WAVE_PARALLAX_TRANSLATE_ACCEL);
|
||||||
const [scaleVel, setScaleVel] = useState(WAVE_PARALLAX_SCALE_VELOCITY);
|
const [scaleVel, setScaleVel] = useState(WAVE_PARALLAX_SCALE_VELOCITY);
|
||||||
const [useMask, setUseMask] = useState(false);
|
const [useMask, setUseMask] = useState(false);
|
||||||
|
@ -83,10 +133,40 @@ const Waves: React.FC<{}> = () => {
|
||||||
let waves: JSX.Element[] = [];
|
let waves: JSX.Element[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < numWaves; i++) {
|
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(
|
waves.push(
|
||||||
<g key={i} transform={`translate(0 ${y}) scale(${scale} ${scale})`}>
|
<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>
|
<g>
|
||||||
<Wave fill={fill} stroke={stroke} />
|
<Wave fill={blendedFill} stroke={blendedStroke} />
|
||||||
<animateTransform
|
<animateTransform
|
||||||
attributeName="transform"
|
attributeName="transform"
|
||||||
type="translate"
|
type="translate"
|
||||||
|
@ -172,6 +252,12 @@ const Waves: React.FC<{}> = () => {
|
||||||
value={useMask}
|
value={useMask}
|
||||||
onChange={setUseMask}
|
onChange={setUseMask}
|
||||||
/>
|
/>
|
||||||
|
<Checkbox label="Hills" value={showHills} onChange={setShowHills} />
|
||||||
|
{showHills && (
|
||||||
|
<button accessKey="r" onClick={newRandomSeed}>
|
||||||
|
<u>R</u>andomize hills!
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -124,3 +124,11 @@ export function withoutNulls<T>(arr: (T | null)[]): T[] {
|
||||||
|
|
||||||
return result;
|
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