Skip to content

Commit bbcc4c9

Browse files
committed
core: 考虑到美观程度去掉了定时更换控制点的代码
core: 整理了随机控制点生成器代码
1 parent 9424b18 commit bbcc4c9

File tree

4 files changed

+231
-275
lines changed

4 files changed

+231
-275
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
"@biomejs/biome": "^1.9.4",
77
"lerna": "^8.1.9",
88
"typescript": "^5.7.3"
9-
}
10-
}
9+
},
10+
"packageManager": "pnpm@10.2.1+sha512.398035c7bd696d0ba0b10a688ed558285329d27ea994804a52bad9167d8e3a72bcb993f9699585d3ca25779ac64949ef422757a6c31102c12ab932e5cbe5cc92"
11+
}
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/**
2+
* @fileoverview
3+
* 实验性的随机控制点生成函数算法
4+
* 目的是取代原先大量的预设控制点代码
5+
*/
6+
7+
import {
8+
type ControlPointConf,
9+
type ControlPointPreset,
10+
p,
11+
preset,
12+
} from "./cp-presets.ts";
13+
14+
const randomRange = (min: number, max: number): number =>
15+
Math.random() * (max - min) + min;
16+
17+
function clamp(x: number, min: number, max: number): number {
18+
return Math.min(Math.max(x, min), max);
19+
}
20+
21+
function smoothstep(edge0: number, edge1: number, x: number): number {
22+
const t = clamp((x - edge0) / (edge1 - edge0), 0, 1);
23+
return t * t * (3 - 2 * t);
24+
}
25+
26+
function smoothifyControlPoints(
27+
conf: ControlPointConf[],
28+
w: number,
29+
h: number,
30+
iterations = 2,
31+
factor = 0.5,
32+
factorIterationModifier = 0.1,
33+
): void {
34+
let grid: ControlPointConf[][] = [];
35+
let f = factor;
36+
37+
for (let j = 0; j < h; j++) {
38+
grid[j] = [];
39+
for (let i = 0; i < w; i++) {
40+
grid[j][i] = conf[j * w + i];
41+
}
42+
}
43+
44+
const kernel = [
45+
[1, 2, 1],
46+
[2, 4, 2],
47+
[1, 2, 1],
48+
];
49+
const kernelSum = 16;
50+
51+
for (let iter = 0; iter < iterations; iter++) {
52+
const newGrid: ControlPointConf[][] = [];
53+
for (let j = 0; j < h; j++) {
54+
newGrid[j] = [];
55+
for (let i = 0; i < w; i++) {
56+
if (i === 0 || i === w - 1 || j === 0 || j === h - 1) {
57+
newGrid[j][i] = grid[j][i];
58+
continue;
59+
}
60+
let sumX = 0;
61+
let sumY = 0;
62+
let sumUR = 0;
63+
let sumVR = 0;
64+
let sumUP = 0;
65+
let sumVP = 0;
66+
for (let dj = -1; dj <= 1; dj++) {
67+
for (let di = -1; di <= 1; di++) {
68+
const weight = kernel[dj + 1][di + 1];
69+
const nb = grid[j + dj][i + di];
70+
sumX += nb.x * weight;
71+
sumY += nb.y * weight;
72+
sumUR += nb.ur * weight;
73+
sumVR += nb.vr * weight;
74+
sumUP += nb.up * weight;
75+
sumVP += nb.vp * weight;
76+
}
77+
}
78+
const avgX = sumX / kernelSum;
79+
const avgY = sumY / kernelSum;
80+
const avgUR = sumUR / kernelSum;
81+
const avgVR = sumVR / kernelSum;
82+
const avgUP = sumUP / kernelSum;
83+
const avgVP = sumVP / kernelSum;
84+
85+
const cur = grid[j][i];
86+
const newX = cur.x * (1 - f) + avgX * f;
87+
const newY = cur.y * (1 - f) + avgY * f;
88+
const newUR = cur.ur * (1 - f) + avgUR * f;
89+
const newVR = cur.vr * (1 - f) + avgVR * f;
90+
const newUP = cur.up * (1 - f) + avgUP * f;
91+
const newVP = cur.vp * (1 - f) + avgVP * f;
92+
newGrid[j][i] = p(i, j, newX, newY, newUR, newVR, newUP, newVP);
93+
}
94+
}
95+
grid = newGrid;
96+
f = Math.min(1, Math.max(f + factorIterationModifier, 0));
97+
}
98+
99+
for (let j = 0; j < h; j++) {
100+
for (let i = 0; i < w; i++) {
101+
conf[j * w + i] = grid[j][i];
102+
}
103+
}
104+
}
105+
106+
function noise(x: number, y: number): number {
107+
return fract(Math.sin(x * 12.9898 + y * 78.233) * 43758.5453);
108+
}
109+
110+
function fract(x: number): number {
111+
return x - Math.floor(x);
112+
}
113+
114+
function smoothNoise(x: number, y: number): number {
115+
const x0 = Math.floor(x);
116+
const y0 = Math.floor(y);
117+
const x1 = x0 + 1;
118+
const y1 = y0 + 1;
119+
120+
const xf = x - x0;
121+
const yf = y - y0;
122+
123+
const u = xf * xf * (3 - 2 * xf);
124+
const v = yf * yf * (3 - 2 * yf);
125+
126+
const n00 = noise(x0, y0);
127+
const n10 = noise(x1, y0);
128+
const n01 = noise(x0, y1);
129+
const n11 = noise(x1, y1);
130+
131+
const nx0 = n00 * (1 - u) + n10 * u;
132+
const nx1 = n01 * (1 - u) + n11 * u;
133+
134+
return nx0 * (1 - v) + nx1 * v;
135+
}
136+
137+
function computeNoiseGradient(
138+
perlinFn: (x: number, y: number) => number,
139+
x: number,
140+
y: number,
141+
epsilon = 0.001,
142+
): [number, number] {
143+
const n1 = perlinFn(x + epsilon, y);
144+
const n2 = perlinFn(x - epsilon, y);
145+
const n3 = perlinFn(x, y + epsilon);
146+
const n4 = perlinFn(x, y - epsilon);
147+
const dx = (n1 - n2) / (2 * epsilon);
148+
const dy = (n3 - n4) / (2 * epsilon);
149+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
150+
return [dx / len, dy / len];
151+
}
152+
153+
export function generateControlPoints(
154+
width: number,
155+
height: number,
156+
variationFraction = randomRange(0.4, 0.6), // = 0.2,
157+
normalOffset = randomRange(0.3, 0.6), // = 0.3,
158+
blendFactor = 0.8,
159+
smoothIters = Math.floor(randomRange(3, 5)), // = 3,
160+
smoothFactor = randomRange(0.2, 0.3), // = 0.3,
161+
smoothModifier = randomRange(-0.1, -0.05), // = -0.05,
162+
): ControlPointPreset {
163+
const w = width ?? Math.floor(randomRange(3, 6));
164+
const h = height ?? Math.floor(randomRange(3, 6));
165+
166+
const conf: ControlPointConf[] = [];
167+
const dx = w === 1 ? 0 : 2 / (w - 1);
168+
const dy = h === 1 ? 0 : 2 / (h - 1);
169+
170+
for (let j = 0; j < h; j++) {
171+
for (let i = 0; i < w; i++) {
172+
const baseX = (w === 1 ? 0 : i / (w - 1)) * 2 - 1;
173+
const baseY = (h === 1 ? 0 : j / (h - 1)) * 2 - 1;
174+
175+
const isBorder = i === 0 || i === w - 1 || j === 0 || j === h - 1;
176+
const pertX = isBorder
177+
? 0
178+
: randomRange(-variationFraction * dx, variationFraction * dx);
179+
const pertY = isBorder
180+
? 0
181+
: randomRange(-variationFraction * dy, variationFraction * dy);
182+
let x = baseX + pertX;
183+
let y = baseY + pertY;
184+
185+
const ur = isBorder ? 0 : randomRange(-60, 60);
186+
const vr = isBorder ? 0 : randomRange(-60, 60);
187+
const up = isBorder ? 1 : randomRange(0.8, 1.2);
188+
const vp = isBorder ? 1 : randomRange(0.8, 1.2);
189+
190+
if (!isBorder) {
191+
const uNorm = (baseX + 1) / 2;
192+
const vNorm = (baseY + 1) / 2;
193+
194+
const [nx, ny] = computeNoiseGradient(smoothNoise, uNorm, vNorm, 0.001);
195+
let offsetX = nx * normalOffset;
196+
let offsetY = ny * normalOffset;
197+
198+
const distToBorder = Math.min(uNorm, 1 - uNorm, vNorm, 1 - vNorm); // in [0,0.5]
199+
200+
const weight = smoothstep(0, 1.0, distToBorder);
201+
offsetX *= weight;
202+
offsetY *= weight;
203+
204+
x = x * (1 - blendFactor) + (x + offsetX) * blendFactor;
205+
y = y * (1 - blendFactor) + (y + offsetY) * blendFactor;
206+
}
207+
conf.push(p(i, j, x, y, ur, vr, up, vp));
208+
}
209+
}
210+
211+
smoothifyControlPoints(conf, w, h, smoothIters, smoothFactor, smoothModifier);
212+
213+
return preset(w, h, conf);
214+
}

0 commit comments

Comments
 (0)