feat: стереометрия — усечённая пирамида, правильные многогранники, скрещивающиеся прямые
- Добавлены фигуры: усечённая пирамида, октаэдр, икосаэдр, додекаэдр - Формулы V, S, r_вп, R_оп для всех новых фигур - Инструмент '∠ скрещ. прям.' — угол и расстояние между скрещивающимися прямыми (4 клика) - Для икосаэдра/додекаэдра — THREE.IcosahedronGeometry/DodecahedronGeometry с извлечением рёбер - Вписанная/описанная сфера поддерживает октаэдр, икосаэдр, додекаэдр - Параметр n добавлен для пирамиды Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+285
-3
@@ -423,6 +423,67 @@ class StereoSim {
|
||||
formulas: [`V = S_осн·h = ${r(sBase*h)}`, `S_осн = ${r(sBase)}`, `S_бок = nah = ${r(n*a*h)}`,
|
||||
`r_вп = ${rIn}`, `R_оп = ${rOut}`] };
|
||||
}
|
||||
case 'truncpyramid': {
|
||||
const { a, b, h, n } = p;
|
||||
const sLow = n * a**2 / (4 * Math.tan(PI/n));
|
||||
const sUp = n * b**2 / (4 * Math.tan(PI/n));
|
||||
const apLow = a / (2 * Math.tan(PI/n));
|
||||
const apUp = b / (2 * Math.tan(PI/n));
|
||||
const slant = Math.sqrt(h**2 + (apLow - apUp)**2);
|
||||
const sLat = n * (a + b) * slant / 2;
|
||||
const V = h * (sLow + sUp + Math.sqrt(sLow * sUp)) / 3;
|
||||
return { V: r(V), S: r(sLow + sUp + sLat), S_side: r(sLat), d: null, h,
|
||||
formulas: [
|
||||
`V = h(S₁+S₂+√(S₁·S₂))/3 = ${r(V)}`,
|
||||
`S₁ = ${r(sLow)}, S₂ = ${r(sUp)}`,
|
||||
`l (апоф.) = ${r(slant)}`,
|
||||
`S_бок = n(a+b)l/2 = ${r(sLat)}`,
|
||||
`S_полн = ${r(sLow + sUp + sLat)}`,
|
||||
]};
|
||||
}
|
||||
case 'octahedron': {
|
||||
const a = p.a;
|
||||
const V_val = r(a**3 * Math.SQRT2 / 3);
|
||||
const S_val = r(2 * a**2 * Math.sqrt(3));
|
||||
return { V: V_val, S: S_val, S_side: null, d: null, h: r(a * Math.SQRT2),
|
||||
formulas: [
|
||||
`V = a³√2/3 = ${V_val}`,
|
||||
`S = 2a²√3 = ${S_val}`,
|
||||
`h = a√2 = ${r(a * Math.SQRT2)}`,
|
||||
`r_вп = a√6/6 = ${r(a * Math.sqrt(6) / 6)}`,
|
||||
`R_оп = a√2/2 = ${r(a * Math.SQRT2 / 2)}`,
|
||||
]};
|
||||
}
|
||||
case 'icosahedron': {
|
||||
const a = p.a;
|
||||
const phi = (1 + Math.sqrt(5)) / 2;
|
||||
const V_val = r(5 * a**3 * (3 + Math.sqrt(5)) / 12);
|
||||
const S_val = r(5 * a**2 * Math.sqrt(3));
|
||||
const rIn = r(a * phi**2 / (2 * Math.sqrt(3)));
|
||||
const rOut = r(a * Math.sqrt(10 + 2*Math.sqrt(5)) / 4);
|
||||
return { V: V_val, S: S_val, S_side: null, d: null, h: r(a * Math.sqrt(10 + 2*Math.sqrt(5)) / 2),
|
||||
formulas: [
|
||||
`V = 5a³(3+√5)/12 = ${V_val}`,
|
||||
`S = 5a²√3 = ${S_val}`,
|
||||
`r_вп ≈ ${rIn}`,
|
||||
`R_оп = a√(10+2√5)/4 ≈ ${rOut}`,
|
||||
]};
|
||||
}
|
||||
case 'dodecahedron': {
|
||||
const a = p.a;
|
||||
const phi = (1 + Math.sqrt(5)) / 2;
|
||||
const V_val = r(a**3 * (15 + 7*Math.sqrt(5)) / 4);
|
||||
const S_val = r(3 * a**2 * Math.sqrt(25 + 10*Math.sqrt(5)));
|
||||
const rIn = r(a / 2 * Math.sqrt((25 + 11*Math.sqrt(5)) / 10));
|
||||
const rOut = r(a * Math.sqrt(3) * phi / 2);
|
||||
return { V: V_val, S: S_val, S_side: null, d: null, h: r(a * Math.sqrt(3) * phi),
|
||||
formulas: [
|
||||
`V = a³(15+7√5)/4 = ${V_val}`,
|
||||
`S = 3a²√(25+10√5) = ${S_val}`,
|
||||
`r_вп ≈ ${rIn}`,
|
||||
`R_оп = a√3·φ/2 ≈ ${rOut}`,
|
||||
]};
|
||||
}
|
||||
default: return { V: 0, S: 0, S_side: 0, d: 0, h: 0, formulas: [] };
|
||||
}
|
||||
}
|
||||
@@ -493,6 +554,10 @@ class StereoSim {
|
||||
trunccone: () => this._buildTruncCone(),
|
||||
sphere: () => this._buildSphere(),
|
||||
prism: () => this._buildPrism(),
|
||||
truncpyramid: () => this._buildTruncPyramid(),
|
||||
octahedron: () => this._buildOctahedron(),
|
||||
icosahedron: () => this._buildIcosahedron(),
|
||||
dodecahedron: () => this._buildDodecahedron(),
|
||||
};
|
||||
(builders[this.figureType] || builders.cube)();
|
||||
|
||||
@@ -779,6 +844,124 @@ class StereoSim {
|
||||
this._addVerticesAndLabels();
|
||||
}
|
||||
|
||||
/* ── TRUNCATED PYRAMID ── */
|
||||
_buildTruncPyramid() {
|
||||
const { a, b, h, n } = this.params;
|
||||
const baseVerts = this._regularPolygon(n, a);
|
||||
const topVerts = this._regularPolygon(n, b).map(v => new THREE.Vector3(v.x, h, v.z));
|
||||
const labels = 'ABCDEFGH'.split('');
|
||||
|
||||
this._vertices = baseVerts.map((pos, i) => ({ pos, label: labels[i] || `P${i}` }));
|
||||
topVerts.forEach((pos, i) => this._vertices.push({ pos, label: (labels[i] || `P${i}`) + '₁' }));
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
this._edges.push({ from: baseVerts[i], to: baseVerts[(i+1)%n] });
|
||||
this._edges.push({ from: topVerts[i], to: topVerts[(i+1)%n] });
|
||||
this._edges.push({ from: baseVerts[i], to: topVerts[i] });
|
||||
}
|
||||
|
||||
this._faces.push([...baseVerts]);
|
||||
this._faces.push([...topVerts]);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const j = (i+1) % n;
|
||||
this._faces.push([baseVerts[i], baseVerts[j], topVerts[j], topVerts[i]]);
|
||||
}
|
||||
|
||||
this._addMeshFromFaces(0xF59E0B);
|
||||
this._addEdges();
|
||||
this._addVerticesAndLabels();
|
||||
}
|
||||
|
||||
/* ── OCTAHEDRON ── */
|
||||
_buildOctahedron() {
|
||||
const a = this.params.a;
|
||||
const R = a / Math.SQRT2; // equatorial radius (dist from center to eq. vertex)
|
||||
const v = [
|
||||
new THREE.Vector3(0, 0, 0), // 0 S₁ (bottom)
|
||||
new THREE.Vector3( R, R, 0), // 1 A
|
||||
new THREE.Vector3( 0, R, R), // 2 B
|
||||
new THREE.Vector3(-R, R, 0), // 3 C
|
||||
new THREE.Vector3( 0, R, -R), // 4 D
|
||||
new THREE.Vector3( 0, a * Math.SQRT2, 0), // 5 S₂ (top)
|
||||
];
|
||||
const labels = ['S₁','A','B','C','D','S₂'];
|
||||
this._vertices = v.map((pos, i) => ({ pos, label: labels[i] }));
|
||||
|
||||
const edgeIdx = [[0,1],[0,2],[0,3],[0,4],[5,1],[5,2],[5,3],[5,4],[1,2],[2,3],[3,4],[4,1]];
|
||||
this._edges = edgeIdx.map(([i, j]) => ({ from: v[i], to: v[j] }));
|
||||
|
||||
this._faces = [
|
||||
[v[0],v[1],v[2]], [v[0],v[2],v[3]], [v[0],v[3],v[4]], [v[0],v[4],v[1]],
|
||||
[v[5],v[2],v[1]], [v[5],v[3],v[2]], [v[5],v[4],v[3]], [v[5],v[1],v[4]],
|
||||
];
|
||||
|
||||
this._addMeshFromFaces(0xF15BB5);
|
||||
this._addEdges();
|
||||
this._addVerticesAndLabels();
|
||||
}
|
||||
|
||||
/* ── ICOSAHEDRON ── */
|
||||
_buildIcosahedron() {
|
||||
const a = this.params.a;
|
||||
const circR = a * Math.sqrt(10 + 2 * Math.sqrt(5)) / 4;
|
||||
const geo = new THREE.IcosahedronGeometry(circR, 0);
|
||||
|
||||
const posArr = geo.getAttribute('position').array;
|
||||
let minY = Infinity;
|
||||
for (let i = 1; i < posArr.length; i += 3) minY = Math.min(minY, posArr[i]);
|
||||
geo.translate(0, -minY, 0);
|
||||
|
||||
const mat = new THREE.MeshPhysicalMaterial({
|
||||
color: 0x06D6E0, transparent: true, opacity: this.opacity,
|
||||
side: THREE.DoubleSide, metalness: 0.05, roughness: 0.4, clearcoat: 0.2, depthWrite: false,
|
||||
});
|
||||
this._figGroup.add(new THREE.Mesh(geo, mat));
|
||||
|
||||
const verts = this._extractUniqueVerts(geo);
|
||||
const labels = 'ABCDEFGHIJKL'.split('');
|
||||
this._vertices = verts.map((pos, i) => ({ pos, label: labels[i] || `V${i+1}` }));
|
||||
|
||||
for (let i = 0; i < verts.length; i++)
|
||||
for (let j = i + 1; j < verts.length; j++)
|
||||
if (verts[i].distanceTo(verts[j]) <= a * 1.06)
|
||||
this._edges.push({ from: verts[i], to: verts[j] });
|
||||
|
||||
this._addEdges();
|
||||
this._addVerticesAndLabels();
|
||||
}
|
||||
|
||||
/* ── DODECAHEDRON ── */
|
||||
_buildDodecahedron() {
|
||||
const a = this.params.a;
|
||||
const phi = (1 + Math.sqrt(5)) / 2;
|
||||
const circR = a * Math.sqrt(3) * phi / 2;
|
||||
const geo = new THREE.DodecahedronGeometry(circR, 0);
|
||||
|
||||
const posArr = geo.getAttribute('position').array;
|
||||
let minY = Infinity;
|
||||
for (let i = 1; i < posArr.length; i += 3) minY = Math.min(minY, posArr[i]);
|
||||
geo.translate(0, -minY, 0);
|
||||
|
||||
const mat = new THREE.MeshPhysicalMaterial({
|
||||
color: 0x9B5DE5, transparent: true, opacity: this.opacity,
|
||||
side: THREE.DoubleSide, metalness: 0.05, roughness: 0.4, clearcoat: 0.2, depthWrite: false,
|
||||
});
|
||||
this._figGroup.add(new THREE.Mesh(geo, mat));
|
||||
|
||||
const edgeLen = a * 2 / phi; // dodecahedron edge length relative to circumradius
|
||||
const verts = this._extractUniqueVerts(geo);
|
||||
const labels = 'ABCDEFGHIJKLMNOPQRST'.split('');
|
||||
this._vertices = verts.map((pos, i) => ({ pos, label: labels[i] || `V${i+1}` }));
|
||||
|
||||
for (let i = 0; i < verts.length; i++)
|
||||
for (let j = i + 1; j < verts.length; j++)
|
||||
if (verts[i].distanceTo(verts[j]) <= a * 1.06)
|
||||
this._edges.push({ from: verts[i], to: verts[j] });
|
||||
|
||||
this._addEdges();
|
||||
this._addVerticesAndLabels();
|
||||
}
|
||||
|
||||
/* ════════════════ GEOMETRY HELPERS ════════════════ */
|
||||
|
||||
_regularPolygon(n, sideLength) {
|
||||
@@ -791,6 +974,21 @@ class StereoSim {
|
||||
return pts;
|
||||
}
|
||||
|
||||
/* Extract deduplicated vertex list from a THREE.BufferGeometry */
|
||||
_extractUniqueVerts(geo) {
|
||||
const arr = geo.getAttribute('position').array;
|
||||
const map = new Map();
|
||||
const verts = [];
|
||||
for (let i = 0; i < arr.length; i += 3) {
|
||||
const key = `${(arr[i]*1000)|0},${(arr[i+1]*1000)|0},${(arr[i+2]*1000)|0}`;
|
||||
if (!map.has(key)) {
|
||||
map.set(key, verts.length);
|
||||
verts.push(new THREE.Vector3(arr[i], arr[i+1], arr[i+2]));
|
||||
}
|
||||
}
|
||||
return verts;
|
||||
}
|
||||
|
||||
_addMeshFromFaces(color) {
|
||||
// Build a single mesh from faces
|
||||
const positions = [];
|
||||
@@ -915,10 +1113,13 @@ class StereoSim {
|
||||
switch (this.figureType) {
|
||||
case 'cube': return p.a;
|
||||
case 'parallelepiped': return p.c;
|
||||
case 'pyramid': case 'prism': return p.h;
|
||||
case 'pyramid': case 'prism': case 'truncpyramid': return p.h;
|
||||
case 'tetrahedron': return p.a * Math.sqrt(2/3);
|
||||
case 'cylinder': case 'cone': case 'trunccone': return p.h;
|
||||
case 'sphere': return p.r * 2;
|
||||
case 'octahedron': return p.a * Math.SQRT2;
|
||||
case 'icosahedron': return p.a * Math.sqrt(10 + 2*Math.sqrt(5)) / 2;
|
||||
case 'dodecahedron': { const phi = (1+Math.sqrt(5))/2; return p.a * Math.sqrt(3) * phi; }
|
||||
default: return p.h || p.a || 4;
|
||||
}
|
||||
}
|
||||
@@ -1188,11 +1389,13 @@ class StereoSim {
|
||||
return 3 * V / (sBase + sLat);
|
||||
}
|
||||
case 'prism': {
|
||||
// r_in = apothem of base (if h >= 2*apothem), else h/2
|
||||
const { a, h, n } = p;
|
||||
const apothem = a / (2 * Math.tan(PI / n));
|
||||
return Math.min(apothem, h / 2);
|
||||
}
|
||||
case 'octahedron': return p.a * Math.sqrt(6) / 6;
|
||||
case 'icosahedron': { const phi = (1+Math.sqrt(5))/2; return p.a * phi**2 / (2*Math.sqrt(3)); }
|
||||
case 'dodecahedron': return p.a / 2 * Math.sqrt((25 + 11*Math.sqrt(5)) / 10);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -1225,11 +1428,13 @@ class StereoSim {
|
||||
return (Rb ** 2 + h ** 2) / (2 * h);
|
||||
}
|
||||
case 'prism': {
|
||||
// R = sqrt(R_base² + (h/2)²)
|
||||
const { a, h, n } = p;
|
||||
const Rb = a / (2 * Math.sin(PI / n));
|
||||
return Math.sqrt(Rb ** 2 + (h / 2) ** 2);
|
||||
}
|
||||
case 'octahedron': return p.a * Math.SQRT2 / 2;
|
||||
case 'icosahedron': return p.a * Math.sqrt(10 + 2*Math.sqrt(5)) / 4;
|
||||
case 'dodecahedron': { const phi = (1+Math.sqrt(5))/2; return p.a * Math.sqrt(3) * phi / 2; }
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -1259,6 +1464,9 @@ class StereoSim {
|
||||
return 3 * V / (sBase + sLat); // r_in
|
||||
}
|
||||
case 'prism': return p.h / 2;
|
||||
case 'octahedron': return p.a * Math.SQRT2 / 2; // center of octahedron
|
||||
case 'icosahedron': return p.a * Math.sqrt(10 + 2*Math.sqrt(5)) / 4; // circumR = half-height approx
|
||||
case 'dodecahedron': { const phi = (1+Math.sqrt(5))/2; return p.a * Math.sqrt(3) * phi / 2; }
|
||||
default: return this._figureHeight() / 2;
|
||||
}
|
||||
}
|
||||
@@ -1294,6 +1502,9 @@ class StereoSim {
|
||||
return h - R; // from base
|
||||
}
|
||||
case 'prism': return p.h / 2;
|
||||
case 'octahedron': return p.a * Math.SQRT2 / 2;
|
||||
case 'icosahedron': return p.a * Math.sqrt(10 + 2*Math.sqrt(5)) / 4;
|
||||
case 'dodecahedron': { const phi = (1+Math.sqrt(5))/2; return p.a * Math.sqrt(3) * phi / 2; }
|
||||
default: return this._figureHeight() / 2;
|
||||
}
|
||||
}
|
||||
@@ -1880,9 +2091,80 @@ class StereoSim {
|
||||
this._onDihedralAngleClick(e);
|
||||
} else if (this._angleMode === 'pointPlane') {
|
||||
this._onPointPlaneClick(e);
|
||||
} else if (this._angleMode === 'skewLines') {
|
||||
this._onSkewLinesClick(e);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Skew lines: pick P1, P2 (line 1) then P3, P4 (line 2) ── */
|
||||
_onSkewLinesClick(e) {
|
||||
const pick = this._pickNearestPoint(e);
|
||||
if (!pick) return;
|
||||
|
||||
const step = this._anglePicks.length;
|
||||
this._anglePicks.push(pick);
|
||||
|
||||
const colors = [0xFFD166, 0xFFD166, 0x06D6E0, 0x06D6E0];
|
||||
this._highlightPick(pick.pos, colors[step] || 0xffffff);
|
||||
|
||||
// After 2 picks: draw line 1
|
||||
if (this._anglePicks.length === 2) {
|
||||
const [P1, P2] = this._anglePicks;
|
||||
this._drawAngleLine(P1.pos, P2.pos, '#FFD166');
|
||||
}
|
||||
|
||||
if (this._anglePicks.length < 4) return;
|
||||
|
||||
const [P1, P2, P3, P4] = this._anglePicks;
|
||||
const d1 = new THREE.Vector3().subVectors(P2.pos, P1.pos);
|
||||
const d2 = new THREE.Vector3().subVectors(P4.pos, P3.pos);
|
||||
|
||||
// Draw line 2
|
||||
this._drawAngleLine(P3.pos, P4.pos, '#06D6E0');
|
||||
|
||||
// Angle between lines (acute)
|
||||
const cosA = Math.abs(d1.dot(d2)) / (d1.length() * d2.length());
|
||||
const angleDeg = Math.acos(Math.min(1, cosA)) * 180 / Math.PI;
|
||||
|
||||
// Common perpendicular (distance between skew lines)
|
||||
const normal = new THREE.Vector3().crossVectors(d1, d2);
|
||||
let dist = 0;
|
||||
if (normal.length() > 1e-9) {
|
||||
// Lines are skew → project onto normal
|
||||
dist = Math.abs(new THREE.Vector3().subVectors(P3.pos, P1.pos).dot(normal.clone().normalize()));
|
||||
|
||||
// Find foot points of common perpendicular
|
||||
const w = new THREE.Vector3().subVectors(P1.pos, P3.pos);
|
||||
const b1 = d1.dot(d1), b2 = d2.dot(d2), d12 = d1.dot(d2);
|
||||
const det = b1 * b2 - d12 * d12;
|
||||
if (Math.abs(det) > 1e-9) {
|
||||
const t1 = (d12 * d2.dot(w) - b2 * d1.dot(w)) / det;
|
||||
const t2 = (b1 * d2.dot(w) - d12 * d1.dot(w)) / det;
|
||||
const Q1 = P1.pos.clone().addScaledVector(d1, t1);
|
||||
const Q2 = P3.pos.clone().addScaledVector(d2, t2);
|
||||
this._drawAngleLine(Q1, Q2, '#F9A8D4', true);
|
||||
this._highlightPick(Q1, 0xF9A8D4);
|
||||
this._highlightPick(Q2, 0xF9A8D4);
|
||||
}
|
||||
} else {
|
||||
// Parallel lines
|
||||
const cross = new THREE.Vector3().crossVectors(d1, new THREE.Vector3().subVectors(P3.pos, P1.pos));
|
||||
dist = cross.length() / d1.length();
|
||||
}
|
||||
|
||||
// Label near midpoint between the two lines
|
||||
const mid = new THREE.Vector3().addVectors(P1.pos, P3.pos).multiplyScalar(0.5);
|
||||
mid.y += 0.5;
|
||||
const lbl = this._makeTextSprite(
|
||||
`∠ = ${angleDeg.toFixed(1)}° d = ${dist.toFixed(2)}`, '#F9A8D4', 36
|
||||
);
|
||||
lbl.position.copy(mid);
|
||||
lbl.scale.set(2.8, 0.55, 1);
|
||||
this._angleGroup.add(lbl);
|
||||
|
||||
this._anglePicks = [];
|
||||
}
|
||||
|
||||
/* ── Edge angle: pick 3 points (B is vertex, angle ∠ABC) ── */
|
||||
_onEdgeAngleClick(e) {
|
||||
const pick = this._pickNearestPoint(e);
|
||||
|
||||
+13
-2
@@ -3559,6 +3559,10 @@
|
||||
<button class="gp-btn stereo-fig-btn" onclick="setStereoFigure('trunccone',this)">Усечённый конус</button>
|
||||
<button class="gp-btn stereo-fig-btn" onclick="setStereoFigure('sphere',this)">Сфера</button>
|
||||
<button class="gp-btn stereo-fig-btn" onclick="setStereoFigure('prism',this)">Призма</button>
|
||||
<button class="gp-btn stereo-fig-btn" onclick="setStereoFigure('truncpyramid',this)">Усеч. пирамида</button>
|
||||
<button class="gp-btn stereo-fig-btn" onclick="setStereoFigure('octahedron',this)">Октаэдр</button>
|
||||
<button class="gp-btn stereo-fig-btn" onclick="setStereoFigure('icosahedron',this)">Икосаэдр</button>
|
||||
<button class="gp-btn stereo-fig-btn" onclick="setStereoFigure('dodecahedron',this)">Додекаэдр</button>
|
||||
</div>
|
||||
|
||||
<div class="gp-section-title">Параметры</div>
|
||||
@@ -3656,6 +3660,7 @@
|
||||
<button class="gp-btn" id="stereo-angle-lp-btn" onclick="stereoAngleMode('linePlane', this)">∠ прям.–пл.</button>
|
||||
<button class="gp-btn" id="stereo-angle-dih-btn" onclick="stereoAngleMode('dihedral', this)">∠ двугранный</button>
|
||||
<button class="gp-btn" id="stereo-angle-pp-btn" onclick="stereoAngleMode('pointPlane', this)">d(т<svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>пл)</button>
|
||||
<button class="gp-btn" id="stereo-angle-skew-btn" onclick="stereoAngleMode('skewLines', this)">∠ скрещ. прям.</button>
|
||||
<button class="gp-btn" id="stereo-angle-clear-btn" onclick="stereoAngleClear()">Очистить</button>
|
||||
</div>
|
||||
<div id="angle-hint" style="font-size:0.65rem;color:rgba(255,255,255,0.4);margin-bottom:4px"></div>
|
||||
@@ -3674,6 +3679,7 @@
|
||||
∠ прям.–пл.: 2 точки (прямая), затем грань<br>
|
||||
∠ двугранный: 2 точки общего ребра<br>
|
||||
d(т<svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>пл): точка, затем грань — перпендикуляр<br>
|
||||
∠ скрещ.: 4 точки — P1,P2 (пр.1), P3,P4 (пр.2)<br>
|
||||
Координаты: наведите на вершину
|
||||
</div>
|
||||
</div>
|
||||
@@ -8053,13 +8059,17 @@
|
||||
const STEREO_PARAM_MAP = {
|
||||
cube: ['a'],
|
||||
parallelepiped: ['a','b','c'],
|
||||
pyramid: ['a','h'],
|
||||
pyramid: ['a','n','h'],
|
||||
tetrahedron: ['a'],
|
||||
cylinder: ['r','h'],
|
||||
cone: ['r','h'],
|
||||
trunccone: ['R','r','h'],
|
||||
sphere: ['r'],
|
||||
prism: ['a','n','h'],
|
||||
truncpyramid: ['a','b','n','h'],
|
||||
octahedron: ['a'],
|
||||
icosahedron: ['a'],
|
||||
dodecahedron: ['a'],
|
||||
};
|
||||
|
||||
function _openStereo() {
|
||||
@@ -8167,7 +8177,7 @@
|
||||
|
||||
function _stereoDeactivateTools() {
|
||||
['stereo-measure-btn','stereo-point-btn','stereo-connect-btn',
|
||||
'stereo-angle-edge-btn','stereo-angle-lp-btn','stereo-angle-dih-btn','stereo-angle-pp-btn'].forEach(id => {
|
||||
'stereo-angle-edge-btn','stereo-angle-lp-btn','stereo-angle-dih-btn','stereo-angle-pp-btn','stereo-angle-skew-btn'].forEach(id => {
|
||||
document.getElementById(id)?.classList.remove('active');
|
||||
});
|
||||
if (stereoSim) {
|
||||
@@ -8216,6 +8226,7 @@
|
||||
linePlane: 'Кликните 2 точки (прямая), затем — грань',
|
||||
dihedral: 'Кликните 2 точки общего ребра двух граней',
|
||||
pointPlane: 'Кликните точку, затем — грань',
|
||||
skewLines: 'P1, P2 (прямая 1) → P3, P4 (прямая 2): угол и расстояние',
|
||||
};
|
||||
|
||||
function stereoAngleMode(mode, btn) {
|
||||
|
||||
Reference in New Issue
Block a user