├── .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 | } --------------------------------------------------------------------------------