正在開發新遊戲:星•球
Re: 正在開發新遊戲:星•球
程式代碼都分布至this.injectCSS(),this.renderDOM(),this.cacheDOM(),this.bindEvents(),this.initApp()。
- 附件
-
ejsolid181.js- (13.35 KiB) 已下载 5 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
一,把所有polygon放到一個<g stroke-linejoin=round>中。
二,參照maketheball171.html,svg-container內的立體應顯示後面。但不用「顯示前面和後面」這兩個勾選框。
三,補回Animation功能。同時,animate應以當前的狀態作為開始,並且不要去掉polygon的points。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
gemini pro加油:
代码: 全选
這三個要求都可以透過修改 `EjSolid` 類別內的 `draw()` 函式來達成。
以下是只需要替換的 `draw()` 函式完整代碼。程式碼及註解已依照您的要求全部使用英文撰寫:
```javascript
draw() {
const data = this.dom.objInput.value;
const vertices = [], faces = [];
data.split('\n').forEach(line => {
const parts = line.trim().split(/\s+/);
if (parts[0] === 'v') vertices.push(parts.slice(1, 4).map(Number));
if (parts[0] === 'f') faces.push(parts.slice(1).map(p => parseInt(p.split('/')[0]) - 1));
});
if (!vertices.length) return;
const size = 480, scale = 150, offset = size / 2;
const op = this.dom.opacity.value;
const sw = this.dom.strokeWidth.value;
this.dom.opVal.textContent = op;
this.dom.swVal.textContent = sw;
// Project vertices for the current static frame
const projected = vertices.map(v => {
const rx = v[0] * this.state.rotMatrix[0] + v[1] * this.state.rotMatrix[1] + v[2] * this.state.rotMatrix[2];
const ry = v[0] * this.state.rotMatrix[3] + v[1] * this.state.rotMatrix[4] + v[2] * this.state.rotMatrix[5];
const rz = v[0] * this.state.rotMatrix[6] + v[1] * this.state.rotMatrix[7] + v[2] * this.state.rotMatrix[8];
const f = this.dom.perspective.checked ? 4 / (4 - rz) : 1;
return { x: rx * scale * f + offset, y: -ry * scale * f + offset, z: rz };
});
const polys = [];
faces.forEach(fIdx => {
const pts = fIdx.map(i => projected[i]);
// Remove cross-product (cp) check so both front and back faces are drawn
// Store fIdx to recalculate future frames for the animation
polys.push({ pts, avgZ: pts.reduce((s, p) => s + p.z, 0) / pts.length, sides: pts.length, fIdx });
});
// Depth sort based on current Z state
polys.sort((a, b) => a.avgZ - b.avgZ);
// Handle Animation Frames if toggle is checked
const doAnim = this.dom.animToggle.checked;
const animPolysData = [];
if (doAnim) {
const T = 7.0; // Fixed duration for animation
const fps = 12;
const totalFrames = Math.round(T * fps);
// Default animation axis and angle
const alphaRad = (120 * Math.PI) / 180;
const axisX = -Math.sin(alphaRad);
const axisY = Math.cos(alphaRad);
// Initialize empty arrays to store coordinate strings for each polygon
polys.forEach(() => animPolysData.push([]));
for (let i = 0; i <= totalFrames; i++) {
const angle = (i / totalFrames) * 2 * Math.PI;
const c = Math.cos(angle), s = Math.sin(angle), t = 1 - c;
// Transform matrix for this specific animation frame
const rAnim = [
t * axisX * axisX + c, t * axisX * axisY, s * axisY,
t * axisX * axisY, t * axisY * axisY + c, -s * axisX,
-s * axisY, s * axisX, c
];
// Multiply animation matrix by CURRENT rotMatrix to start from current dragged state
const curRot = [
rAnim[0] * this.state.rotMatrix[0] + rAnim[1] * this.state.rotMatrix[3] + rAnim[2] * this.state.rotMatrix[6],
rAnim[0] * this.state.rotMatrix[1] + rAnim[1] * this.state.rotMatrix[4] + rAnim[2] * this.state.rotMatrix[7],
rAnim[0] * this.state.rotMatrix[2] + rAnim[1] * this.state.rotMatrix[5] + rAnim[2] * this.state.rotMatrix[8],
rAnim[3] * this.state.rotMatrix[0] + rAnim[4] * this.state.rotMatrix[3] + rAnim[5] * this.state.rotMatrix[6],
rAnim[3] * this.state.rotMatrix[1] + rAnim[4] * this.state.rotMatrix[4] + rAnim[5] * this.state.rotMatrix[7],
rAnim[3] * this.state.rotMatrix[2] + rAnim[4] * this.state.rotMatrix[5] + rAnim[5] * this.state.rotMatrix[8],
rAnim[6] * this.state.rotMatrix[0] + rAnim[7] * this.state.rotMatrix[3] + rAnim[8] * this.state.rotMatrix[6],
rAnim[6] * this.state.rotMatrix[1] + rAnim[7] * this.state.rotMatrix[4] + rAnim[8] * this.state.rotMatrix[7],
rAnim[6] * this.state.rotMatrix[2] + rAnim[7] * this.state.rotMatrix[5] + rAnim[8] * this.state.rotMatrix[8]
];
const frameProjected = vertices.map(v => {
const rx = v[0] * curRot[0] + v[1] * curRot[1] + v[2] * curRot[2];
const ry = v[0] * curRot[3] + v[1] * curRot[4] + v[2] * curRot[5];
const rz = v[0] * curRot[6] + v[1] * curRot[7] + v[2] * curRot[8];
const f = this.dom.perspective.checked ? 4 / (4 - rz) : 1;
return { x: rx * scale * f + offset, y: -ry * scale * f + offset };
});
// Store generated points for each frame
polys.forEach((p, pIndex) => {
const ptsStr = p.fIdx.map(idx => `${frameProjected[idx].x.toFixed(2)},${frameProjected[idx].y.toFixed(2)}`).join(' ');
animPolysData[pIndex].push(ptsStr);
});
}
}
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}">\n`;
// Wrap all polygons in a grouped tag with stroke-linejoin set to round
svg += ` <g stroke-linejoin="round">\n`;
polys.forEach((p, i) => {
const pStr = p.pts.map(pt => `${pt.x.toFixed(2)},${pt.y.toFixed(2)}`).join(' ');
const fill = this.container.querySelector(`.hex-${p.sides}`)?.value || '#ccc';
if (doAnim) {
const valuesStr = animPolysData[i].join(';');
// Render polygon with explicit "points" attribute retained, plus embedded animate tag
svg += ` <polygon points="${pStr}" fill="${fill}" fill-opacity="${op}" stroke="${this.dom.strokeColor.value}" stroke-width="${sw}">\n`;
svg += ` <animate attributeName="points" values="${valuesStr}" dur="7s" repeatCount="indefinite" />\n`;
svg += ` </polygon>\n`;
} else {
svg += ` <polygon points="${pStr}" fill="${fill}" fill-opacity="${op}" stroke="${this.dom.strokeColor.value}" stroke-width="${sw}" />\n`;
}
});
svg += ` </g>\n</svg>`;
this.dom.svgContainer.innerHTML = svg;
this.state.lastSvg = svg;
this.dom.fileSize.textContent = `File Size: ${new Blob([svg]).size} Bytes`;
}
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
在增加動畫時,處於背面的面將set visibility=hidden,回到正面時再設回visible。正面與背面交換的時機,是一個面的所有端點都共線。
添加以下輸入框作為設置:
滑動與數字輸入框During Time(s),始終保留一位小數,默認為7.0;
滑動與數字輸入框Rotate Angle,整數,範圍是-180到180,以右方為0度,逆時針為正;
滑動與數字輸入框Set round to n decimal places,整數,set visibility的時間保留多少位小數,默認3;
滑動與數字輸入框Animate每n秒一幀,保留兩位小數,默認為0.05。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
gemini pro:
代码: 全选
Here are the exact modifications needed to implement the requested animation features, including the new control inputs, visibility toggles based on collinear points, and the preservation of the initial `points` attribute.
### 1. Update `renderDOM()`
Replace the `<h2>Animation</h2>` block in the `this.container.innerHTML` assignment with the following HTML:
```javascript
// ... [Keep the previous HTML up to <h2>Face Colors</h2>] ...
<h2>Animation</h2>
<div class="ejsolid-control-group">
<label><input type="checkbox" class="ejs-anim-toggle" /> Rotation Animation</label>
<div class="ejsolid-row">
<span>Duration (s)</span>
<input type="range" class="ejs-anim-time-range" min="0.1" max="48.0" step="0.1" value="7.0" />
<input type="number" class="ejs-anim-time" min="0.1" max="48.0" step="0.1" value="7.0" style="width: 50px" />
</div>
<div class="ejsolid-row">
<span>Rotate Angle (°)</span>
<input type="range" class="ejs-anim-angle-range" min="-180" max="180" step="1" value="0" />
<input type="number" class="ejs-anim-angle" min="-180" max="180" step="1" value="0" style="width: 50px" />
</div>
<div class="ejsolid-row">
<span>Round decimals</span>
<input type="range" class="ejs-time-prec-range" min="0" max="6" step="1" value="3" />
<input type="number" class="ejs-time-prec" min="0" max="6" step="1" value="3" style="width: 50px" />
</div>
<div class="ejsolid-row">
<span>Frame interval (s)</span>
<input type="range" class="ejs-frame-int-range" min="0.01" max="1.00" step="0.01" value="0.05" />
<input type="number" class="ejs-frame-int" min="0.01" max="1.00" step="0.01" value="0.05" style="width: 50px" />
</div>
</div>
</div>
// ... [Keep the right panel HTML] ...
```
### 2. Update `cacheDOM()`
Add the new input elements to the `this.dom` object:
```javascript
cacheDOM() {
this.dom = {
// ... [Keep existing cached elements] ...
animToggle: this.container.querySelector('.ejs-anim-toggle'),
animTimeRange: this.container.querySelector('.ejs-anim-time-range'),
animTime: this.container.querySelector('.ejs-anim-time'),
animAngleRange: this.container.querySelector('.ejs-anim-angle-range'),
animAngle: this.container.querySelector('.ejs-anim-angle'),
timePrecRange: this.container.querySelector('.ejs-time-prec-range'),
timePrec: this.container.querySelector('.ejs-time-prec'),
frameIntRange: this.container.querySelector('.ejs-frame-int-range'),
frameInt: this.container.querySelector('.ejs-frame-int'),
svgContainer: this.container.querySelector('#ejs-svg-container'),
// ... [Keep the rest] ...
};
}
```
### 3. Update `bindEvents()`
Add synchronization for the new range and number inputs, and trigger the redraw:
```javascript
bindEvents() {
// ... [Keep existing events for colors, file upload, etc.] ...
// Helper to sync range and number inputs and redraw
const syncInputs = (rangeNode, numNode, isFloat, decimals) => {
const update = () => this.draw();
rangeNode.oninput = () => {
numNode.value = isFloat ? parseFloat(rangeNode.value).toFixed(decimals) : rangeNode.value;
update();
};
numNode.oninput = () => {
rangeNode.value = numNode.value;
update();
};
numNode.onblur = () => {
let val = parseFloat(numNode.value);
if (isNaN(val)) val = parseFloat(rangeNode.value);
numNode.value = isFloat ? val.toFixed(decimals) : Math.round(val);
rangeNode.value = numNode.value;
update();
};
};
// Bind new animation inputs
syncInputs(this.dom.animTimeRange, this.dom.animTime, true, 1);
syncInputs(this.dom.animAngleRange, this.dom.animAngle, false, 0);
syncInputs(this.dom.timePrecRange, this.dom.timePrec, false, 0);
syncInputs(this.dom.frameIntRange, this.dom.frameInt, true, 2);
// ... [Keep the existing mouse interactions and other oninput events] ...
}
```
### 4. Update Animation Logic in `draw()`
Replace the section inside `draw()` that handles the animation and SVG string building with this updated code:
```javascript
// ... [Keep the existing polygon array projection and depth sort logic up to polys.sort(...)] ...
// Handle Animation Frames
const doAnim = this.dom.animToggle.checked;
const animPolysData = [];
if (doAnim) {
const duration = parseFloat(this.dom.animTime.value) || 7.0;
const angleDeg = parseInt(this.dom.animAngle.value) || 0;
const timePrec = parseInt(this.dom.timePrec.value) || 3;
const frameInterval = parseFloat(this.dom.frameInt.value) || 0.05;
const totalFrames = Math.round(duration / frameInterval);
// Calculate axis of rotation (0 is right, counter-clockwise is positive)
const alphaRad = (angleDeg * Math.PI) / 180;
const axisX = Math.cos(alphaRad);
const axisY = -Math.sin(alphaRad);
let startAssigned = false;
// Initialize animation data structure
polys.forEach(p => {
animPolysData.push({
pointsValues: [],
sets: [],
lastCp: 0,
currentVis: false,
fIdx: p.fIdx
});
});
for (let i = 0; i <= totalFrames; i++) {
const angle = (i / totalFrames) * 2 * Math.PI;
const c = Math.cos(angle), s = Math.sin(angle), t = 1 - c;
// Transform matrix for this specific animation frame
const rAnim = [
t * axisX * axisX + c, t * axisX * axisY, s * axisY,
t * axisX * axisY, t * axisY * axisY + c, -s * axisX,
-s * axisY, s * axisX, c
];
// Combine animation matrix with current state to start seamlessly
const curRot = [
rAnim[0] * this.state.rotMatrix[0] + rAnim[1] * this.state.rotMatrix[3] + rAnim[2] * this.state.rotMatrix[6],
rAnim[0] * this.state.rotMatrix[1] + rAnim[1] * this.state.rotMatrix[4] + rAnim[2] * this.state.rotMatrix[7],
rAnim[0] * this.state.rotMatrix[2] + rAnim[1] * this.state.rotMatrix[5] + rAnim[2] * this.state.rotMatrix[8],
rAnim[3] * this.state.rotMatrix[0] + rAnim[4] * this.state.rotMatrix[3] + rAnim[5] * this.state.rotMatrix[6],
rAnim[3] * this.state.rotMatrix[1] + rAnim[4] * this.state.rotMatrix[4] + rAnim[5] * this.state.rotMatrix[7],
rAnim[3] * this.state.rotMatrix[2] + rAnim[4] * this.state.rotMatrix[5] + rAnim[5] * this.state.rotMatrix[8],
rAnim[6] * this.state.rotMatrix[0] + rAnim[7] * this.state.rotMatrix[3] + rAnim[8] * this.state.rotMatrix[6],
rAnim[6] * this.state.rotMatrix[1] + rAnim[7] * this.state.rotMatrix[4] + rAnim[8] * this.state.rotMatrix[7],
rAnim[6] * this.state.rotMatrix[2] + rAnim[7] * this.state.rotMatrix[5] + rAnim[8] * this.state.rotMatrix[8]
];
const frameProjected = vertices.map(v => {
const rx = v[0] * curRot[0] + v[1] * curRot[1] + v[2] * curRot[2];
const ry = v[0] * curRot[3] + v[1] * curRot[4] + v[2] * curRot[5];
const rz = v[0] * curRot[6] + v[1] * curRot[7] + v[2] * curRot[8];
const f = this.dom.perspective.checked ? 4 / (4 - rz) : 1;
return { x: rx * scale * f + offset, y: -ry * scale * f + offset };
});
animPolysData.forEach((animP) => {
const pts = animP.fIdx.map(idx => frameProjected[idx]);
// Calculate cross product for back-face culling check
let cp = 0;
for (let j = 0; j < pts.length; j++) {
let k = (j + 1) % pts.length;
cp += pts[j].x * pts[k].y - pts[k].x * pts[j].y;
}
const isFront = cp < 0;
const ptsStr = pts.map(pt => `${pt.x.toFixed(2)},${pt.y.toFixed(2)}`).join(' ');
animP.pointsValues.push(ptsStr);
// Configure visibility <set> tags based on cross product switching (collinear moment)
if (i === 0) {
animP.currentVis = isFront;
animP.lastCp = cp;
const initialVisStr = isFront ? 'visible' : 'hidden';
if (!startAssigned) {
animP.sets.push(`<set id="start" attributeName="visibility" to="${initialVisStr}" begin="0;start.begin+${duration.toFixed(1)}s" />`);
startAssigned = true;
} else {
animP.sets.push(`<set attributeName="visibility" to="${initialVisStr}" begin="start.begin" />`);
}
} else {
if (isFront !== animP.currentVis) {
animP.currentVis = isFront;
const visValue = isFront ? 'visible' : 'hidden';
// Linear interpolation of the cross-product to find exact collinear time
let fraction = (Math.abs(animP.lastCp - cp) > 1e-6) ? Math.abs(animP.lastCp / (animP.lastCp - cp)) : 0.5;
let exactTime = ((i - 1 + fraction) * frameInterval).toFixed(timePrec);
animP.sets.push(`<set attributeName="visibility" to="${visValue}" begin="start.begin+${exactTime}s" />`);
}
animP.lastCp = cp;
}
});
}
}
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}">\n`;
svg += ` <g stroke-linejoin="round">\n`;
polys.forEach((p, i) => {
const pStr = p.pts.map(pt => `${pt.x.toFixed(2)},${pt.y.toFixed(2)}`).join(' ');
const fill = this.container.querySelector(`.hex-${p.sides}`)?.value || '#ccc';
if (doAnim) {
const animP = animPolysData[i];
const valuesStr = animP.pointsValues.join(';');
const durationStr = (parseFloat(this.dom.animTime.value) || 7.0).toFixed(1);
// Retain original 'points' attribute as requested to start with current state
svg += ` <polygon points="${pStr}" fill="${fill}" fill-opacity="${op}" stroke="${this.dom.strokeColor.value}" stroke-width="${sw}">\n`;
svg += ` <animate attributeName="points" values="${valuesStr}" dur="${durationStr}s" begin="start.begin" repeatCount="indefinite" />\n`;
animP.sets.forEach(setTag => {
svg += ` ${setTag}\n`;
});
svg += ` </polygon>\n`;
} else {
svg += ` <polygon points="${pStr}" fill="${fill}" fill-opacity="${op}" stroke="${this.dom.strokeColor.value}" stroke-width="${sw}" />\n`;
}
});
svg += ` </g>\n</svg>`;
this.dom.svgContainer.innerHTML = svg;
// ... [Keep the final file size logic] ...
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
立體旋轉時背面隱藏。為Animation增加輸入框設置
- 附件
-
ejsolid182.js- (22.21 KiB) 已下载 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
一,增加Dimensions以及裡面的Canvas Size和Content Size滑動塊和輸入框。
二,Raw Data內的元素樣式改成跟html中一樣。
三,增加功能:當處於「旋轉動畫」狀態時,點擊svg-container,移動後放開,則以按下點到放開點作為旋轉方向改動Rotate Angle。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
gemini pro來啦:
代码: 全选
Here are the specific modifications required for `ejsolid182.js` to implement the requested features. All changes are provided in English.
### 1. Update `injectCSS()`
Add specific styles for the upload button to match the raw data styling from the HTML, overriding the default button styles.
```javascript
injectCSS() {
if (document.getElementById('ejsolid-styles')) return;
const styleSheet = document.createElement('style');
styleSheet.id = 'ejsolid-styles';
styleSheet.innerText = `
/* ... (keep existing CSS) ... */
/* Add these new styles for the upload trigger */
.ejsolid-wrapper button.ejs-upload-trigger {
background-color: #6c757d;
border: 2px solid transparent;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 7px;
border-radius: 7px;
font-size: 14px;
width: fit-content;
}
.ejsolid-wrapper button.ejs-upload-trigger:hover {
background-color: #4a4e69;
}
`;
document.head.appendChild(styleSheet);
}
```
### 2. Update `renderDOM()`
Insert the new "Dimensions" section containing the Canvas Size and Content Size controls just before the "Animation" section.
```javascript
renderDOM() {
this.container.innerHTML = `
<h2>Face Colors</h2>
<div class="ejsolid-color-list ejs-poly-colors"></div>
<h2>Dimensions</h2>
<div class="ejsolid-control-group">
<div class="ejsolid-row">
<span>Canvas Size</span>
<input type="range" class="ejs-canvas-range" min="60" max="1200" step="1" value="480" />
<input type="number" class="ejs-canvas-num" min="60" max="1200" value="480" style="width: 50px" />
</div>
<div class="ejsolid-row">
<span>Content Size</span>
<input type="range" class="ejs-content-range" min="60" max="1200" step="1" value="360" />
<input type="number" class="ejs-content-num" min="60" max="1200" value="360" style="width: 50px" />
</div>
</div>
<h2>Animation</h2>
`;
// ... (keep the rest of renderDOM)
}
```
### 3. Update `cacheDOM()`
Cache the newly added dimension inputs.
```javascript
cacheDOM() {
this.dom = {
// ... (keep existing cached elements) ...
timePrec: this.container.querySelector('.ejs-time-prec'),
frameIntRange: this.container.querySelector('.ejs-frame-int-range'),
frameInt: this.container.querySelector('.ejs-frame-int'),
// Add these 4 new properties
canvasRange: this.container.querySelector('.ejs-canvas-range'),
canvasNum: this.container.querySelector('.ejs-canvas-num'),
contentRange: this.container.querySelector('.ejs-content-range'),
contentNum: this.container.querySelector('.ejs-content-num'),
svgContainer: this.container.querySelector('#ejs-svg-container'),
// ...
};
}
```
### 4. Update `bindEvents()`
Synchronize the new range/number inputs, track the starting drag coordinates, and update the rotation angle on mouseup when animating.
```javascript
bindEvents() {
// ... (keep existing event bindings) ...
syncInputs(this.dom.animTimeRange, this.dom.animTime, true, 1);
syncInputs(this.dom.animAngleRange, this.dom.animAngle, false, 0);
syncInputs(this.dom.timePrecRange, this.dom.timePrec, false, 0);
syncInputs(this.dom.frameIntRange, this.dom.frameInt, true, 2);
// Add bindings for the new dimensions
syncInputs(this.dom.canvasRange, this.dom.canvasNum, false, 0);
syncInputs(this.dom.contentRange, this.dom.contentNum, false, 0);
// Update the mousedown interaction to track starting positions
this.dom.svgContainer.onmousedown = e => {
this.state.isDragging = true;
this.state.lastX = e.clientX;
this.state.lastY = e.clientY;
this.state.startX = e.clientX; // Track start X for angle calculation
this.state.startY = e.clientY; // Track start Y for angle calculation
};
// ... (keep mousemove as is) ...
// Update mouseup to calculate angle based on drag vector
window.addEventListener('mouseup', e => {
if (!this.state.isDragging) return;
this.state.isDragging = false;
// Update rotation angle based on swipe vector ONLY when animating
if (this.dom.animToggle.checked) {
if (e.clientX !== this.state.startX || e.clientY !== this.state.startY) {
const dx = e.clientX - this.state.startX;
const dy = e.clientY - this.state.startY;
const angle = Math.round((Math.atan2(dy, dx) * 180) / Math.PI);
this.dom.animAngle.value = angle;
this.dom.animAngleRange.value = angle;
this.draw();
}
}
});
// ... (keep other input updates) ...
}
```
### 5. Update `draw()`
Update the calculations to incorporate center offset, max radius scaling, and the dynamic sizes from the new inputs.
```javascript
draw() {
// ... (keep vertex parsing code) ...
if (!vertices.length) return;
// REPLACE hardcoded size and scale with the dynamic block below:
// Calculate center and maximum radius to properly scale the content
let cx = 0, cy = 0, cz = 0;
vertices.forEach(v => { cx += v[0]; cy += v[1]; cz += v[2]; });
cx /= vertices.length; cy /= vertices.length; cz /= vertices.length;
let maxRadiusSq = 0;
vertices.forEach(v => {
const dx = v[0] - cx, dy = v[1] - cy, dz = v[2] - cz;
maxRadiusSq = Math.max(maxRadiusSq, dx * dx + dy * dy + dz * dz);
});
const maxRadius = Math.sqrt(maxRadiusSq) || 1;
// Fetch dynamic sizes from DOM
const size = parseInt(this.dom.canvasNum.value) || 480;
const contentSize = parseInt(this.dom.contentNum.value) || 360;
const scale = contentSize / (maxRadius * 2);
const offset = size / 2;
const op = this.dom.opacity.value;
const sw = this.dom.strokeWidth.value;
// ...
// UPDATE the static projected vertices mapping to apply centering:
const projected = vertices.map(v => {
const x = v[0] - cx, y = v[1] - cy, z = v[2] - cz;
const rx = x * this.state.rotMatrix[0] + y * this.state.rotMatrix[1] + z * this.state.rotMatrix[2];
const ry = x * this.state.rotMatrix[3] + y * this.state.rotMatrix[4] + z * this.state.rotMatrix[5];
const rz = x * this.state.rotMatrix[6] + y * this.state.rotMatrix[7] + z * this.state.rotMatrix[8];
const f = this.dom.perspective.checked ? 4 / (4 - rz) : 1;
return { x: rx * scale * f + offset, y: -ry * scale * f + offset, z: rz };
});
// ... (keep faces mapping and sort) ...
if (doAnim) {
// ... (keep animation variables) ...
for (let i = 0; i <= totalFrames; i++) {
// ... (keep matrix logic) ...
// UPDATE the animation projected vertices mapping to apply centering:
const frameProjected = vertices.map(v => {
const x = v[0] - cx, y = v[1] - cy, z = v[2] - cz;
const rx = x * curRot[0] + y * curRot[1] + z * curRot[2];
const ry = x * curRot[3] + y * curRot[4] + z * curRot[5];
const rz = x * curRot[6] + y * curRot[7] + z * curRot[8];
const f = this.dom.perspective.checked ? 4 / (4 - rz) : 1;
return { x: rx * scale * f + offset, y: -ry * scale * f + offset };
});
// ... (keep rest of the animation logic) ...
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
修正旋轉邏輯,增加canvas size和content size。
- 附件
-
ejsolid183.js- (25.21 KiB) 已下载 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
在Style控制板下方的Opacity和Stroke改成跟下方一樣,即右邊是input輸入框,滑塊變動輸入框的值也會變動。color也改成跟下方一樣,點選顏色的左方有一個input輸入框。
只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
gemini fast:
代码: 全选
To update the UI elements for **Opacity**, **Stroke**, and **Stroke Color** to match the dual-input style (range/color + number/text) used in the other sections, please apply the following modifications to your `renderDOM`, `cacheDOM`, and `bindEvents` methods.
### 1. Update `renderDOM()`
Replace the "Style" section HTML with this structure:
```javascript
<h2>Style</h2>
<div class="ejsolid-control-group">
<div class="ejsolid-row">
<span>Opacity</span>
<input type="range" class="ejs-opacity-range" min="0" max="1" step="0.01" value="0.7" />
<input type="number" class="ejs-opacity-num" min="0" max="1" step="0.01" value="0.7" style="width: 50px" />
</div>
<div class="ejsolid-row">
<span>Stroke</span>
<input type="range" class="ejs-stroke-width-range" min="0" max="10" step="0.1" value="1.5" />
<input type="number" class="ejs-stroke-width-num" min="0" max="10" step="0.1" value="1.5" style="width: 50px" />
</div>
<div class="ejsolid-row">
<span>Color</span>
<input type="text" class="ejs-stroke-color-hex" value="#03045e" style="width: 70px" />
<input type="color" class="ejs-stroke-color-picker" value="#03045e" />
</div>
</div>
```
### 2. Update `cacheDOM()`
Update the references to point to the new range, number, and text inputs:
```javascript
cacheDOM() {
this.dom = {
// ... (keep previous keys, update these specific ones)
opacityRange: this.container.querySelector('.ejs-opacity-range'),
opacityNum: this.container.querySelector('.ejs-opacity-num'),
strokeWidthRange: this.container.querySelector('.ejs-stroke-width-range'),
strokeWidthNum: this.container.querySelector('.ejs-stroke-width-num'),
strokeColorHex: this.container.querySelector('.ejs-stroke-color-hex'),
strokeColorPicker: this.container.querySelector('.ejs-stroke-color-picker'),
// ... (rest of the keys)
};
}
```
### 3. Update `bindEvents()`
Add the logic to sync these new inputs and ensure `draw()` uses the correct values:
```javascript
// Inside bindEvents()
// Sync Opacity and Stroke
syncInputs(this.dom.opacityRange, this.dom.opacityNum, true, 2);
syncInputs(this.dom.strokeWidthRange, this.dom.strokeWidthNum, true, 1);
// Sync Stroke Color (Text + Picker)
this.dom.strokeColorHex.oninput = () => {
this.dom.strokeColorPicker.value = this.dom.strokeColorHex.value;
this.draw();
};
this.dom.strokeColorPicker.oninput = () => {
this.dom.strokeColorHex.value = this.dom.strokeColorPicker.value;
this.draw();
};
// Update the batch listener at the bottom of bindEvents
[
this.dom.objInput,
this.dom.opacityRange, // Changed
this.dom.strokeWidthRange, // Changed
this.dom.perspective,
this.dom.animToggle
].forEach(el => (el.oninput = () => this.draw()));
```
### 4. Update `draw()`
Ensure the values are pulled from the correct DOM elements:
```javascript
// Inside draw()
const op = this.dom.opacityNum.value; // Updated
const sw = this.dom.strokeWidthNum.value; // Updated
const strokeColor = this.dom.strokeColorPicker.value; // Updated
// In the loop where <polygon> is generated:
// ... stroke="${strokeColor}" stroke-width="${sw}" ...
```https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
代码: 全选
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ejsolid - 3D SVG Program</title>
</head>
<body>
<div class="ejsolid" mode="main"></div>
<script src="ejsolid184.js"></script>
</body>
</html>下一步是去掉Round decimals,svg加上按寬變窄。
- 附件
-
ejsolid184.js- (26.51 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
去掉Round decimals以及相關的邏輯代碼。
- 附件
-
ejsolid185.js- (26.83 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
代码: 全选
To remove the **"Round decimals"** feature and its associated logic, follow these modifications in your `ejsolid185.js` file:
### 1. Remove from `renderDOM()`
Locate the "Animation" section in the HTML template and remove the "Round decimals" row:
```javascript
// Remove this block from renderDOM():
<div class="ejsolid-row">
<span>Round decimals</span>
<input type="range" class="ejs-time-prec-range" min="0" max="6" step="1" value="3" />
<input type="number" class="ejs-time-prec" min="0" max="6" step="1" value="3" style="width: 50px" />
</div>
```
---
### 2. Remove from `cacheDOM()`
Remove the references to the decimal precision inputs:
```javascript
// Remove these properties from the this.dom object:
timePrecRange: this.container.querySelector('.ejs-time-prec-range'),
timePrec: this.container.querySelector('.ejs-time-prec'),
```
---
### 3. Remove from `bindEvents()`
Remove the input synchronization logic:
```javascript
// Remove this line:
syncInputs(this.dom.timePrecRange, this.dom.timePrec, false, 0);
```
---
### 4. Update `draw()` Logic
Update the animation loop to remove the dynamic precision variable and replace the `.toFixed()` call with a standard precision (e.g., 3 or 4 decimal places):
```javascript
// Inside the draw() method, under "Handle Animation Frames":
// 1. Remove this line:
const timePrec = parseInt(this.dom.timePrec.value) || 3;
// 2. Modify the exactTime calculation:
// Change from:
let exactTime = ((i - 1 + fraction) * frameInterval).toFixed(timePrec);
// To:
let exactTime = ((i - 1 + fraction) * frameInterval).toFixed(3);
```
### Summary of Changes
* **UI**: The slider and number input for "Round decimals" are deleted.
* **State**: The app no longer queries the DOM for a precision value.
* **Logic**: The SVG `<set>` tag's `begin` attribute now defaults to a fixed precision of 3 decimal places for the time calculation.https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
代码: 全选
要實現「打勾時動畫從 0 秒開始播放」,你需要修改 `bindEvents` 方法中處理 `animToggle` 的邏輯,並利用 SVG 的 SMIL API `setCurrentTime(0)` 來強制重置時間軸。
以下是具體的修改建議:
### 1. 修改 `bindEvents` 中的監聽器
在 `bindEvents()` 中,找到處理 `animToggle` 的地方(大約在代碼末尾),將原本簡單的 `draw()` 調用擴展為包含時間重置的邏輯:
```javascript
// 尋找這一行:
// [this.dom.objInput, this.dom.perspective, this.dom.animToggle].forEach(el => (el.oninput = () => this.draw()));
// 建議修改為:
[this.dom.objInput, this.dom.perspective].forEach(el => (el.oninput = () => this.draw()));
this.dom.animToggle.oninput = () => {
this.draw(); // 先渲染包含 <animate> 的新 SVG
if (this.dom.animToggle.checked) {
const svgElement = this.dom.svgContainer.querySelector('svg');
// 檢查瀏覽器是否支持 setCurrentTime API
if (svgElement && typeof svgElement.setCurrentTime === 'function') {
try {
svgElement.setCurrentTime(0); // 強制將動畫時間軸撥回 0 秒
} catch (e) {
console.error("SVG SMIL control not supported or failed:", e);
}
}
}
};
```
---
### 2. 為什麼這樣有效?
* **同步渲染與播放**:當你調用 `this.draw()` 時,程式會生成新的 SVG 字符串並通過 `innerHTML` 插入 DOM。此時瀏覽器的 SMIL(動畫引擎)會立即啟動。
* **消除滯後**:有時候瀏覽器在插入 HTML 後,內部計時器可能已經跑了幾個毫秒,或者因為之前的緩存導致時間點不準確。調用 `setCurrentTime(0)` 可以確保動畫從 `values` 屬性定義的第一幀座標開始。
* **解決「第二次才生效」的問題**:手動重置時間軸打破了瀏覽器對舊 SVG 動畫狀態的記憶,強制它重新對齊當前模型狀態。
---
### 3. 注意事項
* **執行順序**:必須先執行 `this.draw()` 讓 `svg` 標籤出現在 DOM 中,接著才能選取到該元素並調用 `setCurrentTime`。
* **瀏覽器兼容性**:現代瀏覽器(Chrome, Firefox, Safari)均支持 `setCurrentTime` 用於 SVG 控制。
這樣修改後,每當你打勾 `Rotation Animation`,模型都會立即從當前的靜態角度平滑地開始旋轉動畫。https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
代码: 全选
To implement a synchronized progress bar, we will add a new SVG element at the top of the `.ejsolid-right` panel. This progress bar will use an `<animate>` tag to ensure it follows the same SMIL timing as the 3D model, allowing `setCurrentTime(0)` to reset both simultaneously.
---
### 1. Update `renderDOM()`
Add the progress bar container at the top of the `ejsolid-right` div.
```javascript
// Inside renderDOM(), update the ejsolid-right section:
<div class="ejsolid-panel ejsolid-right">
<div id="ejs-progress-wrap" style="width: 100%; height: 4px; background: #eee; margin-bottom: 10px; border-radius: 2px; overflow: hidden;">
<svg id="ejs-progress-svg" width="100%" height="100%" preserveAspectRatio="none">
<rect id="ejs-progress-bar" x="0" y="0" width="0%" height="100%" fill="#3a86ff">
</rect>
</svg>
</div>
<div id="ejs-svg-container"></div>
...
</div>
```
---
### 2. Update `cacheDOM()`
Add the progress bar elements to your DOM cache.
```javascript
// Inside cacheDOM():
this.dom.progressBar = this.container.querySelector('#ejs-progress-bar');
this.dom.progressWrap = this.container.querySelector('#ejs-progress-wrap');
this.dom.progressSvg = this.container.querySelector('#ejs-progress-svg');
```
---
### 3. Update `draw()` Logic
Modify the `draw()` method to generate the progress bar animation when `doAnim` is true.
```javascript
// Inside draw(), locate the "Handle Animation Frames" block:
const doAnim = this.dom.animToggle.checked;
if (doAnim) {
const duration = parseFloat(this.dom.animTime.value) || 7.0;
const durationStr = duration.toFixed(1);
// Update Progress Bar Animation
this.dom.progressWrap.style.display = 'block';
this.dom.progressBar.innerHTML = `
<animate
attributeName="width"
from="0%"
to="100%"
dur="${durationStr}s"
begin="0s"
repeatCount="indefinite"
/>`;
// ... existing animPolysData logic ...
} else {
// Hide progress bar when not animating
this.dom.progressWrap.style.display = 'none';
this.dom.progressBar.innerHTML = '';
}
```
---
### 4. Update `bindEvents()` for Synchronization
Update the `animToggle.oninput` logic to reset both the main model SVG and the progress bar SVG.
```javascript
this.dom.animToggle.oninput = () => {
this.draw();
if (this.dom.animToggle.checked) {
const modelSvg = this.dom.svgContainer.querySelector('svg');
const progressSvg = this.dom.progressSvg;
// Synchronize both SVGs to time 0
[modelSvg, progressSvg].forEach(svg => {
if (svg && typeof svg.setCurrentTime === 'function') {
try {
svg.setCurrentTime(0);
} catch (e) {
console.warn("SMIL reset failed:", e);
}
}
});
}
};
```
---
### How it works:
* **Visual Integration**: The progress bar is placed inside the `.ejsolid-right` panel, appearing just above the 3D model.
* **SMIL Synchronization**: Since both the 3D model's `points` animation and the bar's `width` animation have the same `dur` (duration) and `begin="0s"`, they stay perfectly in sync.
* **Manual Reset**: When you check the animation box, `setCurrentTime(0)` is called on both SVG documents, ensuring the progress bar starts from the left exactly as the model begins its rotation cycle.https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
Re: 正在開發新遊戲:星•球
當轉動畫時從零開始,就不會以為它不從當前狀態開始旋轉。
- 附件
-
ejsolid186.js- (26.66 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
弈趣極光:享受思維樂趣
在线用户
正浏览此版面之用户: 没有注册用户 和 1 访客
