正在開發新遊戲:星•球

分享身边的点滴趣事
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

maketheball181

程式代碼都分布至this.injectCSS(),this.renderDOM(),this.cacheDOM(),this.bindEvents(),this.initApp()。
附件
ejsolid181.js
(13.35 KiB) 已下载 5 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

對ejsolid181.js改進:

一,把所有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/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

對ejsolid181.js改進:
在增加動畫時,處於背面的面將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/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

ejsolid182

立體旋轉時背面隱藏。為Animation增加輸入框設置
附件
ejsolid182.js
(22.21 KiB) 已下载 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

參照上傳的html,對ejsolid182.js進行改進:
一,增加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/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

ejsolid183

修正旋轉邏輯,增加canvas size和content size。
附件
ejsolid183.js
(25.21 KiB) 已下载 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

改進:
在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/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

代码: 全选

<!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/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

ejsolid185
去掉Round decimals以及相關的邏輯代碼。
附件
ejsolid185.js
(26.83 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

gemini fast已經夠用了:

代码: 全选

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/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

當第一次點擊ejs-anim-toggle打勾時,動畫的初始並不是當前的狀態。取消打勾又再次打勾,才是當前狀態。查找原因。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

代码: 全选

要實現「打勾時動畫從 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/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

當動畫開始播放時,ejsolid-right的最上方將增加一個由svg animate製作的進度條,它將跟svg-container的動畫一起setCurrentTime(0),當一輪播完,進度條將從左正好走到最右邊。

代码: 全选

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/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5556
注册时间: 2022年 11月 18日 17:36
为圈友点赞: 179 次
被圈友点赞: 207 次
联系:

Re: 正在開發新遊戲:星•球

帖子 ejsoon »

ejsolid186

當轉動畫時從零開始,就不會以為它不從當前狀態開始旋轉。
附件
ejsolid186.js
(26.66 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复

在线用户

正浏览此版面之用户: 没有注册用户 和 1 访客