「視覺最大寬度」與其「隨機總長度」成正比。
// 在你的 drawSpikyBubble 函數中,循環繪製每個尖刺之前
const baseSpikeLength = parseFloat(spikeLenInput.value); // 這是你輸入的基礎長度
const baseSpikeVisualMaxWidth = 5; // << 新增:設定一個基礎視覺寬度,例如5px
// ... 進入 for 循環 ...
for (let i = 0; i < numSpikes; i++) {
// ... (計算 angle, currentSpikeTotalLength, anchorX/Y, dx/dy, startX/Y, endX/Y 的邏輯不變) ...
let currentSpikeTotalLength = (Math.random() + 0.2) * baseSpikeLength;
currentSpikeTotalLength = Math.min(baseSpikeLength, currentSpikeTotalLength);
currentSpikeTotalLength = Math.max(baseSpikeLength * 0.1, currentSpikeTotalLength); // 確保一個最小長度,例如基礎的10%
// --- 新增:計算當前尖刺的視覺最大寬度 ---
let currentSpikeVisualMaxWidth = (currentSpikeTotalLength / baseSpikeLength) * baseSpikeVisualMaxWidth;
// (可選) 給 currentSpikeVisualMaxWidth 設定一個最小值和最大值
currentSpikeVisualMaxWidth = Math.max(1, currentSpikeVisualMaxWidth); // 例如,至少1px寬
currentSpikeVisualMaxWidth = Math.min(baseSpikeVisualMaxWidth, currentSpikeVisualMaxWidth); // 最多不超過基礎視覺寬度
// ... (anchorX, anchorY, dx, dy, startX, startY, endX, endY 的計算) ...
// 假設你將 drawTaperedLine 的邏輯直接整合或調用
// 繪製這一根錐形尖刺,使用 currentSpikeVisualMaxWidth
// 這是 drawTaperedLine 的核心邏輯,應用到當前尖刺的 centerline (startX,startY) 到 (endX,endY)
const spikeDX = endX - startX;
const spikeDY = endY - startY;
const spikeLen = currentSpikeTotalLength; // Math.sqrt(spikeDX * spikeDX + spikeDY * spikeDY); // 其實就是 currentSpikeTotalLength
if (spikeLen > 0) { // 避免除以零
const spikeUX = spikeDX / spikeLen;
const spikeUY = spikeDY / spikeLen;
const spikeNX = -spikeUY; // 法向量
const spikeNY = spikeUX;
const spikeMidX = (startX + endX) / 2;
const spikeMidY = (startY + endY) / 2;
// 構成錐形/菱形的頂點
const p1x = startX;
const p1y = startY;
const p2x = spikeMidX + spikeNX * (currentSpikeVisualMaxWidth / 2);
const p2y = spikeMidY + spikeNY * (currentSpikeVisualMaxWidth / 2);
const p3x = endX;
const p3y = endY;
const p4x = spikeMidX - spikeNX * (currentSpikeVisualMaxWidth / 2);
const p4y = spikeMidY - spikeNY * (currentSpikeVisualMaxWidth / 2);
// 將這些點加入到一個大的路徑中,或者為每個尖刺單獨 beginPath/fill
// 為了效能,如果尖刺很多,可以考慮將所有尖刺的四邊形都 add 到一個 path,最後 fill
// 但如果顏色固定,為每個尖刺單獨 fill 也可以
context.beginPath(); // 為每個尖刺創建一個獨立的路徑
context.moveTo(p1x, p1y);
context.lineTo(p2x, p2y);
context.lineTo(p3x, p3y);
context.lineTo(p4x, p4y);
context.closePath();
context.fillStyle = spikeLineColor; // 使用你設定的尖刺顏色
context.fill();
}
}
// ... 結束 for 循環 ...
// 如果之前是將所有尖刺路徑收集起來,則在這裡 fill
// context.fillStyle = spikeLineColor;
// context.fill();
function drawSpikyBubble(context, x, y, width, height, baseSpikeLength, spikeLengthVariation, numSpikes, innerFillColor, spikeLineColor /*, spikeLineWidth (這個可能不需要了) */) {
const ellipseCenterX = x + width / 2;
const ellipseCenterY = y + height / 2;
const radiusX = width / 2;
const radiusY = height / 2;
const baseSpikeVisualMaxWidth = 6; // << 新增:設定一個基礎視覺寬度 (例如6px)
// 你可以把它也做成一個輸入參數
context.save();
context.clearRect(0, 0, canvas.width, canvas.height);
// 1. 繪製中心的白色橢圓
context.beginPath();
context.ellipse(ellipseCenterX, ellipseCenterY, radiusX, radiusY, 0, 0, 2 * Math.PI);
context.fillStyle = innerFillColor;
context.fill();
// 2. 繪製放射狀的錐形尖刺
// context.beginPath(); // 如果想一次性 fill 所有尖刺 (但顏色單一)
for (let i = 0; i < numSpikes; i++) {
const angle = (i / numSpikes) * 2 * Math.PI;
// 計算當前尖刺的實際總長度 (這是你之前的邏輯,可以保留或調整)
let currentSpikeTotalLength = (Math.random() * (1 - 0.2) + 0.2) * baseSpikeLength; // 長度在基礎的20%到100%之間
// currentSpikeTotalLength = Math.min(baseSpikeLength, currentSpikeTotalLength); // 這行其實被上面的重寫了
currentSpikeTotalLength = Math.max(baseSpikeLength * 0.15, currentSpikeTotalLength); // 確保一個最小長度
// 計算當前尖刺的視覺最大寬度
let currentSpikeVisualMaxWidth = (currentSpikeTotalLength / baseSpikeLength) * baseSpikeVisualMaxWidth;
currentSpikeVisualMaxWidth = Math.max(1.5, currentSpikeVisualMaxWidth); // 至少1.5px寬
currentSpikeVisualMaxWidth = Math.min(baseSpikeVisualMaxWidth * 1.2, currentSpikeVisualMaxWidth); // 最多不超過基礎寬度的1.2倍 (可選)
const anchorX = ellipseCenterX + radiusX * Math.cos(angle);
const anchorY = ellipseCenterY + radiusY * Math.sin(angle);
const dx = Math.cos(angle);
const dy = Math.sin(angle);
const startX = anchorX - dx * (currentSpikeTotalLength / 2);
const startY = anchorY - dy * (currentSpikeTotalLength / 2);
const endX = anchorX + dx * (currentSpikeTotalLength / 2);
const endY = anchorY + dy * (currentSpikeTotalLength / 2);
// --- 繪製錐形尖刺的邏輯 ---
const spikeDX = endX - startX;
const spikeDY = endY - startY;
const spikeLen = currentSpikeTotalLength;
if (spikeLen > 0.1) { // 避免長度過小導致問題
const spikeUX = spikeDX / spikeLen;
const spikeUY = spikeDY / spikeLen;
const spikeNX = -spikeUY;
const spikeNY = spikeUX;
const spikeMidX = (startX + endX) / 2;
const spikeMidY = (startY + endY) / 2;
const p1x = startX;
const p1y = startY;
const p2x = spikeMidX + spikeNX * (currentSpikeVisualMaxWidth / 2);
const p2y = spikeMidY + spikeNY * (currentSpikeVisualMaxWidth / 2);
const p3x = endX;
const p3y = endY;
const p4x = spikeMidX - spikeNX * (currentSpikeVisualMaxWidth / 2);
const p4y = spikeMidY - spikeNY * (currentSpikeVisualMaxWidth / 2);
context.beginPath(); // 為每個尖刺獨立路徑和填充
context.moveTo(p1x, p1y);
context.lineTo(p2x, p2y);
context.lineTo(p3x, p3y);
context.lineTo(p4x, p4y);
context.closePath();
context.fillStyle = spikeLineColor;
context.fill();
}
}
// if (fillingAllAtOnce) {
// context.fillStyle = spikeLineColor;
// context.fill();
// }
context.restore();
}
引入 baseSpikeVisualMaxWidth 作為參考。 根據 currentSpikeTotalLength 和 baseSpikeLength 的比例,計算出 currentSpikeVisualMaxWidth。 對 currentSpikeVisualMaxWidth 進行合理的 clamp (限制最小值和最大值)。 使用這個動態計算出來的 currentSpikeVisualMaxWidth 來繪製每個尖刺的四邊形。
留言
張貼留言