代码: 全选
Here are the necessary corrections. The inability to click the faces is caused by the hardcoded `pointer-events: none` CSS rules protecting the canvas for the drag-to-rotate functionality. We can fix this by dynamically overriding the `pointer-events` inline styles when a selection mode is active.
Additionally, the sorting issue was caused by polygons completely enclosing smaller ones, causing the simple vertex point-check to fail. We need a robust 3-way check (A inside B, B inside A, and crossing edges) to fully evaluate the overlap areas as requested.
### 1. Replace the SVG Click & Button Events in `bindEvents()`
Replace the previous depth test UI interactions inside your `bindEvents()` with this updated logic. It correctly toggles `pointer-events` so the polygons become clickable.
```javascript
// --- REPLACE the Depth Test UI Interactions in bindEvents() ---
const setSelectMode = (mode) => {
this.state.testMode.selecting = mode;
// Update Button Styles
if (this.dom.selectF1) this.dom.selectF1.style.boxShadow = mode === 1 ? '0 0 0 3px #eb2563' : 'none';
if (this.dom.selectF2) this.dom.selectF2.style.boxShadow = mode === 2 ? '0 0 0 3px #eb2563' : 'none';
// Override pointer-events so faces can actually be clicked
if (this.dom.svgContainer) {
this.dom.svgContainer.style.cursor = mode > 0 ? 'crosshair' : 'grab';
const svg = this.dom.svgContainer.querySelector('svg');
if (svg) {
svg.style.pointerEvents = mode > 0 ? 'auto' : 'none';
const polys = svg.querySelectorAll('polygon');
polys.forEach(el => { el.style.pointerEvents = mode > 0 ? 'auto' : 'none'; });
}
}
};
if (this.dom.selectF1) this.dom.selectF1.onclick = () => setSelectMode(1);
if (this.dom.selectF2) this.dom.selectF2.onclick = () => setSelectMode(2);
if (this.dom.debugBtn) {
this.dom.debugBtn.onclick = () => this.runDebugCycle();
}
if (this.dom.svgContainer) {
this.dom.svgContainer.addEventListener('click', (e) => {
if (this.state.testMode.selecting > 0 && e.target.tagName.toLowerCase() === 'polygon') {
const id = e.target.getAttribute('id');
if (id) {
const fill = e.target.getAttribute('fill') || e.target.style.fill;
if (this.state.testMode.selecting === 1) {
this.state.testMode.face1 = id;
this.dom.selectF1.style.background = fill;
this.dom.selectF1.style.color = '#fff';
this.dom.selectF1.textContent = `F1: ${id}`;
} else {
this.state.testMode.face2 = id;
this.dom.selectF2.style.background = fill;
this.dom.selectF2.style.color = '#fff';
this.dom.selectF2.textContent = `F2: ${id}`;
}
// Reset mode after successful click
setSelectMode(0);
this.updateTestPanel();
}
}
});
}
```
### 2. Add the Edge Intersection Helper Method
Add this completely new helper method directly beneath `isPointInsidePoly2D(...)` in your class to analyze the crossing edges:
```javascript
/**
* Calculates precise 2D edge intersections between two polygons.
* Required to detect faces that form a cross/star shape where vertices aren't enclosed.
*/
get2DEdgeIntersections(polyA, polyB) {
const intersections = [];
for (let i = 0; i < polyA.length; i++) {
const p1 = polyA[i], p2 = polyA[(i + 1) % polyA.length];
for (let j = 0; j < polyB.length; j++) {
const p3 = polyB[j], p4 = polyB[(j + 1) % polyB.length];
const denom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
if (Math.abs(denom) < 1e-6) continue; // Parallel edges
const tA = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / denom;
const tB = -((p1.x - p2.x) * (p1.y - p3.y) - (p1.y - p2.y) * (p1.x - p3.x)) / denom;
if (tA >= 0 && tA <= 1 && tB >= 0 && tB <= 1) {
intersections.push({
x: p1.x + tA * (p2.x - p1.x),
y: p1.y + tA * (p2.y - p1.y),
tA, tB, pA1: p1, pA2: p2, pB1: p3, pB2: p4
});
}
}
}
return intersections;
}
```
### 3. Replace the Sorting Logic in `draw()`
Replace the advanced sort block in `draw()` to include the reverse check and edge intersection check.
```javascript
// --- REPLACED SORTING LOGIC START ---
if (this.config.advancedSort) {
polys.sort((a, b) => {
// Rule 1: Distinguish Front and Back faces
if (a.isFront !== b.isFront) {
return a.isFront ? 1 : -1; // Front faces render after (on top of) back faces
}
// Standard Z-sort for backfaces
if (!a.isFront) {
return a.avgZ - b.avgZ;
}
// Rule 2 & 3: For overlapping front faces, deeply check geometry
if (this.check2DOverlap(a.pts, b.pts)) {
let aInFront = false;
let aBehind = false;
// Check 1: A's vertices inside B's 2D projection
for (let i = 0; i < a.pts.length; i++) {
if (this.isPointInsidePoly2D(a.pts[i], b.pts)) {
let dist = b.normal.x * a.pts[i].x3d + b.normal.y * a.pts[i].y3d + b.normal.z * a.pts[i].z3d - b.planeD;
if (dist > 1e-4) aInFront = true;
if (dist < -1e-4) aBehind = true;
}
}
// Check 2: B's vertices inside A's 2D projection (crucial for large polygons enclosing small ones)
for (let i = 0; i < b.pts.length; i++) {
if (this.isPointInsidePoly2D(b.pts[i], a.pts)) {
let dist = a.normal.x * b.pts[i].x3d + a.normal.y * b.pts[i].y3d + a.normal.z * b.pts[i].z3d - a.planeD;
if (dist > 1e-4) aBehind = true; // B is in front of A -> A is behind B
if (dist < -1e-4) aInFront = true;
}
}
// Check 3: Explicitly evaluate crossing edges in the overlapping region
const intersections = this.get2DEdgeIntersections(a.pts, b.pts);
for (let i = 0; i < intersections.length; i++) {
const I = intersections[i];
const zA = I.pA1.z3d + I.tA * (I.pA2.z3d - I.pA1.z3d);
const zB = I.pB1.z3d + I.tB * (I.pB2.z3d - I.pB1.z3d);
if (zA - zB > 1e-4) aInFront = true;
if (zB - zA > 1e-4) aBehind = true;
}
if (aInFront && !aBehind) return 1; // A strictly in front, place A after B
if (aBehind && !aInFront) return -1; // A strictly behind, place A before B
}
return a.avgZ - b.avgZ; // Fallback
});
} else {
polys.sort((a, b) => a.avgZ - b.avgZ);
}
this.state.currentPolys = polys;
this.state.renderedPolysOrder = polys.map(p => p.id);
// --- REPLACED SORTING LOGIC END ---
```
### 4. Update the Debug Test Panel
Finally, replace `updateTestPanel()` to make sure the debug readouts use the same robust 3-way evaluation logic.
```javascript
/**
* Updates the debug panel with relations between Face 1 and Face 2
*/
updateTestPanel() {
if (!this.state.testMode.face1 || !this.state.testMode.face2 || !this.dom.testOutput) return;
const f1Id = this.state.testMode.face1;
const f2Id = this.state.testMode.face2;
const p1 = this.state.currentPolys.find(p => p.id === f1Id);
const p2 = this.state.currentPolys.find(p => p.id === f2Id);
if (!p1 || !p2) return;
const overlaps = this.check2DOverlap(p1.pts, p2.pts);
const i1 = this.state.renderedPolysOrder.indexOf(f1Id);
const i2 = this.state.renderedPolysOrder.indexOf(f2Id);
// Higher index means rendered later (appears in front)
const currentOrder = i1 > i2 ? `${f1Id} is placed BEFORE (on top of) ${f2Id}` : `${f2Id} is placed BEFORE (on top of) ${f1Id}`;
let normalTestRes = "No overlap / No geometry interactions.";
let contradiction = false;
if (overlaps) {
let p1InFront = false, p1Behind = false;
for (let i = 0; i < p1.pts.length; i++) {
if (this.isPointInsidePoly2D(p1.pts[i], p2.pts)) {
let dist = p2.normal.x * p1.pts[i].x3d + p2.normal.y * p1.pts[i].y3d + p2.normal.z * p1.pts[i].z3d - p2.planeD;
if (dist > 1e-4) p1InFront = true;
if (dist < -1e-4) p1Behind = true;
}
}
for (let i = 0; i < p2.pts.length; i++) {
if (this.isPointInsidePoly2D(p2.pts[i], p1.pts)) {
let dist = p1.normal.x * p2.pts[i].x3d + p1.normal.y * p2.pts[i].y3d + p1.normal.z * p2.pts[i].z3d - p1.planeD;
if (dist > 1e-4) p1Behind = true;
if (dist < -1e-4) p1InFront = true;
}
}
const intersections = this.get2DEdgeIntersections(p1.pts, p2.pts);
for (let i = 0; i < intersections.length; i++) {
const I = intersections[i];
const z1 = I.pA1.z3d + I.tA * (I.pA2.z3d - I.pA1.z3d);
const z2 = I.pB1.z3d + I.tB * (I.pB2.z3d - I.pB1.z3d);
if (z1 - z2 > 1e-4) p1InFront = true;
if (z2 - z1 > 1e-4) p1Behind = true;
}
if (p1InFront && !p1Behind) normalTestRes = `${f1Id} is geometrically IN FRONT OF ${f2Id}.`;
else if (p1Behind && !p1InFront) normalTestRes = `${f1Id} is geometrically BEHIND ${f2Id}.`;
else if (p1InFront && p1Behind) normalTestRes = `${f1Id} INTERSECTS ${f2Id} in 3D.`;
// Check if standard rendering array ordering contradicts normal geometry
if (p1InFront && !p1Behind && i1 < i2) contradiction = true;
if (p1Behind && !p1InFront && i1 > i2) contradiction = true;
}
this.dom.testOutput.innerHTML = `
<div style="margin-bottom: 4px;"><b>Faces:</b> ${f1Id}, ${f2Id}</div>
<div style="margin-bottom: 4px;"><b>Overlap:</b> ${overlaps ? 'Yes' : 'No'}</div>
<div style="margin-bottom: 4px;"><b>Current Array Order:</b> ${currentOrder}</div>
<div><b>Normal Test:</b> ${normalTestRes}</div>
`;
this.dom.debugBtn.style.display = contradiction ? 'block' : 'none';
}
```