├── .gitignore
├── style.css
├── index.html
└── sketch.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.wav
2 | *.mp4
3 | *.exe
4 | node_modules/
5 | dist/
6 | *.log
7 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 | canvas {
6 | display: block;
7 | }
8 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/sketch.js:
--------------------------------------------------------------------------------
1 | let particles = [];
2 | let speedSlider, huePosSlider, densitySlider;
3 | let targetDensity;
4 | let noiseOffset = 0;
5 | let manualCheckbox;
6 |
7 |
8 | let curHR, curMF, curLF;
9 | let result;
10 | function preload() {
11 |
12 | result = loadStrings("Starwalk.txt");
13 | }
14 |
15 | function setup() {
16 | createCanvas(windowWidth, windowHeight, WEBGL);
17 | colorMode(HSB, 360, 100, 100, 100);
18 | noStroke();
19 |
20 | // 速度控制
21 | speedSlider = createSlider(0.1, 8, 2, 0.1);
22 | speedSlider.position(20, 20);
23 |
24 | // 顏色位置控制(0 ~ 1)
25 | huePosSlider = createSlider(0, 1, 0.5, 0.01);
26 | huePosSlider.position(20, 50);
27 |
28 | // 密度控制
29 | densitySlider = createSlider(200, 3000, 1500, 10);
30 | densitySlider.position(20, 80);
31 |
32 | // 手動模式
33 | manualCheckbox = createCheckbox('Manual (use sliders)', false);
34 | manualCheckbox.position(20, 110);
35 |
36 | targetDensity = densitySlider.value();
37 | addParticles(targetDensity);
38 | // 啟動資料讀取輪詢
39 | setTimeout(dataPreceed, 1000);
40 |
41 | // 強制禁用快取
42 | if (typeof window !== 'undefined') {
43 | window.addEventListener('beforeunload', function() {
44 | // 清除快取
45 | if ('caches' in window) {
46 | caches.keys().then(function(names) {
47 | for (let name of names) caches.delete(name);
48 | });
49 | }
50 | });
51 | }
52 | }
53 |
54 | function draw() {
55 | background(0, 0, 100); // 白色背景
56 |
57 | let useManual = manualCheckbox && manualCheckbox.checked();
58 |
59 | let Pspeed = useManual
60 | ? speedSlider.value()
61 | : ((curHR !== undefined && curHR !== null)
62 | ? constrain(map(float(curHR), 40, 180, 0.1, 5), 0.1, 5)
63 | : speedSlider.value());
64 | Pspeed *= 1.5;
65 |
66 | let huePos = useManual
67 | ? huePosSlider.value()
68 | : ((curLF !== undefined && curLF !== null)
69 | ? constrain(map(float(curLF), 0, 100, 0, 1), 0, 1)
70 | : huePosSlider.value());
71 |
72 | targetDensity = useManual
73 | ? densitySlider.value()
74 | : ((curMF !== undefined && curMF !== null)
75 | ? Math.round(constrain(map(float(curMF), 0, 100, 200, 3000), 200, 3000))
76 | : densitySlider.value());
77 |
78 |
79 |
80 | // 漸補 / 漸減
81 | if (particles.length < targetDensity) {
82 | addParticles(min(50, targetDensity - particles.length));
83 | } else if (particles.length > targetDensity) {
84 | particles.splice(particles.length - min(50, particles.length - targetDensity));
85 | }
86 |
87 | noiseOffset += 0.005;
88 |
89 | // 計算顏色範圍(藍紫 250 → 綠 120 → 紅橙 20)
90 | let hueStart, hueEnd;
91 | if (huePos < 0.5) {
92 | // 藍紫到綠
93 | let t = huePos / 0.5;
94 | hueStart = lerp(250, 120, t);
95 | hueEnd = lerp(250, 120, t);
96 | } else {
97 | // 綠到「更橘紅」
98 | let t = pow((huePos - 0.5) / 0.5, 0.6); // ease-out,加速靠近紅橘
99 | hueStart = lerp(120, 5, t);
100 | hueEnd = lerp(120, 5, t);
101 | }
102 |
103 | for (let p of particles) {
104 | let hueShift = lerp(hueStart, hueEnd, (p.colorOffset + 30) / 60.0);
105 | fill(hueShift % 360, 85, 100, p.alpha);
106 |
107 | push();
108 | let nx = noise(p.x * 0.002, frameCount * 0.002 + p.noiseSeed) * 50 - 25;
109 | let ny = noise(p.y * 0.002, frameCount * 0.002 + p.noiseSeed + 100) * 50 - 25;
110 | translate(p.x + nx, p.y + ny, p.z);
111 | sphere(p.size);
112 | pop();
113 |
114 | p.z += Pspeed;
115 | if (p.z > 100) {
116 | p.z = -2000;
117 | p.x = random(-width, width);
118 | p.y = random(-height, height);
119 | p.noiseSeed = random(1000);
120 | }
121 | }
122 | }
123 |
124 | function addParticles(num) {
125 | for (let i = 0; i < num; i++) {
126 | particles.push({
127 | x: random(-width, width),
128 | y: random(-height, height),
129 | z: random(-2000, 100),
130 | size: random(2, 8),
131 | alpha: random(40, 80),
132 | colorOffset: random(-30, 30),
133 | noiseSeed: random(1000)
134 | });
135 | }
136 | }
137 |
138 | function dataPreceed(){
139 | try {
140 | // 加入時間戳避免快取
141 | const timestamp = Date.now();
142 | const url = `Starwalk.txt?t=${timestamp}`;
143 |
144 | loadStrings(url, function(data) {
145 | if (data && data.length > 0) {
146 | const text = Array.isArray(data) ? data.join(' ') : String(data);
147 | const tokens = text.split(/\s+/); // 以任意空白切分
148 |
149 | console.log("Raw data:", text); // 除錯用:看原始資料
150 | console.log("Tokens count:", tokens.length); // 除錯用:看切分後數量
151 |
152 | // 盡量維持原本邏輯,但加上安全檢查
153 | if (tokens.length > 16) {
154 | const t16 = String(tokens[16] || '');
155 | const parts = t16.split(';');
156 | if (parts[0]) {
157 | curHR = parts[0];
158 | console.log("Updated HR:", curHR);
159 | }
160 | }
161 |
162 | if (tokens.length > 9 && tokens[9] !== undefined) {
163 | curMF = tokens[9];
164 | console.log("Updated MF:", curMF);
165 | }
166 | if (tokens.length > 8 && tokens[8] !== undefined) {
167 | curLF = tokens[8];
168 | console.log("Updated LF:", curLF);
169 | }
170 |
171 | console.log("Final values - HR:"+curHR+", MF:"+curMF+", LF:"+curLF);
172 | } else {
173 | console.warn("Starwalk.txt is empty or failed to load");
174 | }
175 | });
176 | } catch (e) {
177 | console.error('dataPreceed error:', e);
178 | }
179 |
180 | // 設定下次執行
181 | setTimeout(dataPreceed, 1000);
182 | }
183 |
184 |
185 | function windowResized() {
186 | resizeCanvas(windowWidth, windowHeight);
187 | }
--------------------------------------------------------------------------------