not sure
This commit is contained in:
parent
560398fefb
commit
80ed37298b
73 changed files with 3619 additions and 1295 deletions
|
|
@ -12,19 +12,27 @@ interface IntroTitleAgentOptions {
|
|||
count: number;
|
||||
width: number;
|
||||
height: number;
|
||||
progress?: number;
|
||||
seed?: number;
|
||||
}
|
||||
|
||||
type RandomSource = () => number;
|
||||
|
||||
const INTRO_TITLE = appConfig.simulation.intro.title;
|
||||
|
||||
export const createIntroTitleAgents = ({
|
||||
count,
|
||||
width,
|
||||
height,
|
||||
progress = 0,
|
||||
seed,
|
||||
}: IntroTitleAgentOptions): Float32Array => {
|
||||
if (count <= 0) {
|
||||
return new Float32Array();
|
||||
}
|
||||
|
||||
const random = seed === undefined ? Math.random : createSeededRandom(seed);
|
||||
const introProgress = clamp(progress, 0, 1);
|
||||
const safeWidth = Math.max(1, width);
|
||||
const safeHeight = Math.max(1, height);
|
||||
const points = createIntroTitlePoints(safeWidth, safeHeight);
|
||||
|
|
@ -62,14 +70,14 @@ export const createIntroTitleAgents = ({
|
|||
);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const point = points[Math.floor(Math.random() * points.length)];
|
||||
const point = points[Math.floor(random() * points.length)];
|
||||
const targetX = Math.max(
|
||||
0,
|
||||
Math.min(safeWidth - 1, point.x + (Math.random() - 0.5) * targetJitter)
|
||||
Math.min(safeWidth - 1, point.x + (random() - 0.5) * targetJitter)
|
||||
);
|
||||
const targetY = Math.max(
|
||||
0,
|
||||
Math.min(safeHeight - 1, point.y + (Math.random() - 0.5) * targetJitter)
|
||||
Math.min(safeHeight - 1, point.y + (random() - 0.5) * targetJitter)
|
||||
);
|
||||
const [startX, startY] = getIntroRadialStart(
|
||||
targetX,
|
||||
|
|
@ -77,7 +85,8 @@ export const createIntroTitleAgents = ({
|
|||
safeWidth,
|
||||
safeHeight,
|
||||
introCircleRadius,
|
||||
entryJitter
|
||||
entryJitter,
|
||||
random
|
||||
);
|
||||
const approachAngle = Math.atan2(targetY - startY, targetX - startX);
|
||||
let targetAngle = point.tangent ?? approachAngle;
|
||||
|
|
@ -87,21 +96,32 @@ export const createIntroTitleAgents = ({
|
|||
|
||||
const distanceFraction =
|
||||
Math.hypot(targetX - startX, targetY - startY) / Math.hypot(safeWidth, safeHeight);
|
||||
const introDelay = Math.min(
|
||||
appConfig.simulation.intro.targetDelayMax,
|
||||
distanceFraction * appConfig.simulation.intro.targetDelayDistanceMultiplier +
|
||||
random() * appConfig.simulation.intro.targetDelayRandomMultiplier
|
||||
);
|
||||
const pathProgress = getIntroAgentPathProgress(introProgress, introDelay);
|
||||
const initialAngle =
|
||||
approachAngle + (random() - 0.5) * appConfig.simulation.intro.angleJitterRadians;
|
||||
const currentAngle = mixAngle(
|
||||
initialAngle,
|
||||
targetAngle,
|
||||
smoothstep(
|
||||
appConfig.simulation.intro.angleEaseStart,
|
||||
appConfig.simulation.intro.angleEaseEnd,
|
||||
pathProgress
|
||||
)
|
||||
);
|
||||
const base = i * AGENT_FLOAT_COUNT;
|
||||
data[base] = startX;
|
||||
data[base + 1] = startY;
|
||||
data[base + 2] =
|
||||
approachAngle +
|
||||
(Math.random() - 0.5) * appConfig.simulation.intro.angleJitterRadians;
|
||||
data[base] = mix(startX, targetX, pathProgress);
|
||||
data[base + 1] = mix(startY, targetY, pathProgress);
|
||||
data[base + 2] = currentAngle;
|
||||
data[base + 3] = point.colorIndex;
|
||||
data[base + 4] = targetX;
|
||||
data[base + 5] = targetY;
|
||||
data[base + 6] = targetAngle;
|
||||
data[base + 7] = Math.min(
|
||||
appConfig.simulation.intro.targetDelayMax,
|
||||
distanceFraction * appConfig.simulation.intro.targetDelayDistanceMultiplier +
|
||||
Math.random() * appConfig.simulation.intro.targetDelayRandomMultiplier
|
||||
);
|
||||
data[base + 7] = introDelay;
|
||||
}
|
||||
|
||||
return data;
|
||||
|
|
@ -113,7 +133,8 @@ const getIntroRadialStart = (
|
|||
width: number,
|
||||
height: number,
|
||||
radius: number,
|
||||
jitter: number
|
||||
jitter: number,
|
||||
random: RandomSource
|
||||
): [number, number] => {
|
||||
const centerX = width / 2;
|
||||
const centerY = height * appConfig.simulation.intro.verticalAnchor;
|
||||
|
|
@ -121,14 +142,16 @@ const getIntroRadialStart = (
|
|||
const offsetY = targetY - centerY;
|
||||
const length = Math.hypot(offsetX, offsetY);
|
||||
const angle =
|
||||
length > 0.001 ? Math.atan2(offsetY, offsetX) : Math.random() * Math.PI * 2;
|
||||
length > appConfig.simulation.intro.radialStartEpsilon
|
||||
? Math.atan2(offsetY, offsetX)
|
||||
: random() * Math.PI * 2;
|
||||
const directionX = Math.cos(angle);
|
||||
const directionY = Math.sin(angle);
|
||||
const tangentX = -directionY;
|
||||
const tangentY = directionX;
|
||||
const tangentJitter = (Math.random() - 0.5) * jitter;
|
||||
const tangentJitter = (random() - 0.5) * jitter;
|
||||
const radialJitter =
|
||||
(Math.random() - 0.5) * jitter * appConfig.simulation.intro.radialJitterRatio;
|
||||
(random() - 0.5) * jitter * appConfig.simulation.intro.radialJitterRatio;
|
||||
const startX =
|
||||
centerX + directionX * (radius + radialJitter) + tangentX * tangentJitter;
|
||||
const startY =
|
||||
|
|
@ -154,7 +177,7 @@ const createIntroTitlePoints = (
|
|||
|
||||
const fontSize = getIntroTitleFontSize(context, width, height);
|
||||
context.clearRect(0, 0, width, height);
|
||||
context.font = `${fontSize}px "Open Sans", sans-serif`;
|
||||
context.font = `${fontSize}px ${appConfig.simulation.intro.fontFamily}`;
|
||||
context.textAlign = 'center';
|
||||
context.textBaseline = 'middle';
|
||||
context.fillStyle = '#fff';
|
||||
|
|
@ -302,7 +325,7 @@ const getIntroTitleFontSize = (
|
|||
);
|
||||
|
||||
while (fontSize > appConfig.simulation.intro.minFontSizePx) {
|
||||
context.font = `${fontSize}px "Open Sans", sans-serif`;
|
||||
context.font = `${fontSize}px ${appConfig.simulation.intro.fontFamily}`;
|
||||
const metrics = context.measureText(INTRO_TITLE);
|
||||
const measuredHeight =
|
||||
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent || fontSize;
|
||||
|
|
@ -352,3 +375,53 @@ const getMaskAlpha = (
|
|||
const clampedY = Math.max(0, Math.min(height - 1, Math.round(y)));
|
||||
return data[(clampedY * width + clampedX) * 4 + 3];
|
||||
};
|
||||
|
||||
const getIntroAgentPathProgress = (introProgress: number, introDelay: number): number => {
|
||||
if (introProgress <= introDelay) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const activeProgress =
|
||||
(introProgress - introDelay) /
|
||||
Math.max(appConfig.simulation.intro.pathProgressEpsilon, 1 - introDelay);
|
||||
return easePathProgress(clamp(activeProgress, 0, 1));
|
||||
};
|
||||
|
||||
const createSeededRandom = (seed: number): RandomSource => {
|
||||
let state = seed >>> 0;
|
||||
|
||||
return () => {
|
||||
let value = (state += 0x6d2b79f5);
|
||||
value = Math.imul(value ^ (value >>> 15), value | 1);
|
||||
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
|
||||
return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
};
|
||||
|
||||
const mix = (from: number, to: number, amount: number): number =>
|
||||
from + (to - from) * amount;
|
||||
|
||||
const mixAngle = (from: number, to: number, amount: number): number => {
|
||||
const delta = Math.atan2(Math.sin(to - from), Math.cos(to - from));
|
||||
return from + delta * amount;
|
||||
};
|
||||
|
||||
const smoothstep = (edge0: number, edge1: number, value: number): number => {
|
||||
const t = clamp((value - edge0) / (edge1 - edge0), 0, 1);
|
||||
return t * t * (3 - 2 * t);
|
||||
};
|
||||
|
||||
const easePathProgress = (amount: number): number => {
|
||||
if (appConfig.simulation.intro.pathEasing === 'linear') {
|
||||
return amount;
|
||||
}
|
||||
|
||||
return easeOutQuad(amount);
|
||||
};
|
||||
|
||||
const easeOutQuad = (value: number): number => value * (2 - value);
|
||||
|
||||
const clamp = (value: number, min: number, max: number): number => {
|
||||
const safeValue = Number.isFinite(value) ? value : min;
|
||||
return Math.min(max, Math.max(min, safeValue));
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue