还凑合的一个版本
This commit is contained in:
+198
-160
@@ -3,44 +3,62 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Unity坐标转换:计算过程演示</title>
|
||||
<title>Unity坐标系演示 (Z轴向里 + 数值输入)</title>
|
||||
<style>
|
||||
body { margin: 0; overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #1e1e1e; color: white; }
|
||||
|
||||
/* 左侧 UI 容器 */
|
||||
/* UI 容器 */
|
||||
#ui-container {
|
||||
position: absolute;
|
||||
top: 10px; left: 10px;
|
||||
width: 340px;
|
||||
width: 360px;
|
||||
max-height: 95vh;
|
||||
overflow-y: auto; /* 如果屏幕小,允许滚动 */
|
||||
background: rgba(40, 40, 40, 0.95);
|
||||
overflow-y: auto;
|
||||
background: rgba(35, 35, 35, 0.95);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.6);
|
||||
border: 1px solid #444;
|
||||
pointer-events: auto;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
/* 滚动条 */
|
||||
#ui-container::-webkit-scrollbar { width: 6px; }
|
||||
#ui-container::-webkit-scrollbar-thumb { background: #666; border-radius: 3px; }
|
||||
|
||||
h3 { margin-top: 0; color: #4db8ff; border-bottom: 1px solid #555; padding-bottom: 8px; font-size: 1.1em; }
|
||||
h4 { margin: 10px 0 5px 0; color: #ffcc00; font-size: 0.9em; border-left: 3px solid #ffcc00; padding-left: 8px; }
|
||||
h4 { margin: 15px 0 5px 0; color: #ffcc00; font-size: 0.9em; border-left: 3px solid #ffcc00; padding-left: 8px; }
|
||||
|
||||
.control-group { margin-bottom: 10px; }
|
||||
.control-group label { display: flex; justify-content: space-between; font-size: 0.85em; margin-bottom: 2px; }
|
||||
.control-group input[type=range] { width: 100%; cursor: pointer; }
|
||||
/* 控制组样式:Flex布局对齐滑块和输入框 */
|
||||
.control-group { margin-bottom: 12px; }
|
||||
.control-label { display: block; font-size: 0.85em; color: #ccc; margin-bottom: 4px; }
|
||||
.input-row { display: flex; align-items: center; gap: 10px; }
|
||||
|
||||
/* 计算过程面板样式 */
|
||||
/* 滑块 */
|
||||
input[type=range] { flex-grow: 1; cursor: pointer; }
|
||||
|
||||
/* 输入框 */
|
||||
input[type=number] {
|
||||
width: 60px;
|
||||
background: #111;
|
||||
border: 1px solid #555;
|
||||
color: #fff;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
text-align: right;
|
||||
font-family: 'Consolas', monospace;
|
||||
}
|
||||
input[type=number]:focus { outline: 1px solid #4db8ff; border-color: #4db8ff; }
|
||||
|
||||
/* 计算过程盒子 */
|
||||
.calc-box {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 0.8em; /* 字体稍微改小以便显示更多公式 */
|
||||
line-height: 1.5em;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.6em;
|
||||
color: #dcdcdc;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
@@ -48,70 +66,86 @@
|
||||
.calc-step { margin-bottom: 8px; border-bottom: 1px dashed #444; padding-bottom: 8px; }
|
||||
.calc-step:last-child { border-bottom: none; margin-bottom: 0; }
|
||||
|
||||
.var { color: #9cdcfe; font-weight: bold; } /* 变量颜色 */
|
||||
.num { color: #b5cea8; } /* 数字颜色 */
|
||||
.res-x { color: #ff6666; font-weight: bold; } /* 结果X颜色 */
|
||||
.res-y { color: #66ff66; font-weight: bold; } /* 结果Y颜色 */
|
||||
.res-z { color: #6666ff; font-weight: bold; } /* 结果Z颜色 */
|
||||
.comment { color: #6a9955; font-style: italic; } /* 注释颜色 */
|
||||
.var { color: #9cdcfe; font-weight: bold; }
|
||||
.num { color: #b5cea8; }
|
||||
.res-x { color: #ff6666; font-weight: bold; }
|
||||
.res-y { color: #66ff66; font-weight: bold; }
|
||||
.res-z { color: #6666ff; font-weight: bold; }
|
||||
.comment { color: #6a9955; font-style: italic; }
|
||||
|
||||
/* 结果展示格 */
|
||||
.result-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 5px; text-align: center; margin-top: 5px; }
|
||||
.res-box { padding: 5px; border-radius: 4px; font-size: 0.9em; font-weight: bold; font-family: 'Consolas', monospace; }
|
||||
|
||||
#scene-tooltip {
|
||||
position: absolute;
|
||||
bottom: 20px; width: 100%;
|
||||
text-align: center; color: #888;
|
||||
pointer-events: none; z-index: 5;
|
||||
user-select: none;
|
||||
text-shadow: 1px 1px 2px black;
|
||||
position: absolute; bottom: 20px; width: 100%;
|
||||
text-align: center; color: #888; pointer-events: none;
|
||||
z-index: 5; user-select: none; text-shadow: 1px 1px 2px black;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 引用 -->
|
||||
<script src="https://unpkg.com/three@0.128.0/build/three.min.js"></script>
|
||||
<script src="https://unpkg.com/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="ui-container">
|
||||
<h3>Unity 球坐标演示</h3>
|
||||
<h3>Unity 坐标系演示</h3>
|
||||
<div style="font-size:0.8em; color:#aaa; margin-bottom:10px;">
|
||||
视觉设定: X右, Y上, <span style="color:#6666ff; font-weight:bold;">Z向屏幕里(Forward)</span>
|
||||
</div>
|
||||
|
||||
<!-- 滑块控制区 -->
|
||||
<!-- Theta -->
|
||||
<div class="control-group">
|
||||
<label><span>Theta (水平角/Yaw)</span> <span id="val-theta">45°</span></label>
|
||||
<input type="range" id="slider-theta" min="-180" max="180" value="45" step="1">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label><span>Phi (仰角/Pitch)</span> <span id="val-phi">30°</span></label>
|
||||
<input type="range" id="slider-phi" min="-90" max="90" value="30" step="1">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label><span>Radius (半径 r)</span> <span id="val-r">5</span></label>
|
||||
<input type="range" id="slider-r" min="2" max="10" value="5" step="0.1">
|
||||
<span class="control-label">Theta (水平角/Yaw) [-180 ~ 180]</span>
|
||||
<div class="input-row">
|
||||
<input type="range" id="slider-theta" min="-180" max="180" value="45" step="1">
|
||||
<input type="number" id="input-theta" min="-180" max="180" value="45">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 核心显示区 -->
|
||||
<h4>实时计算推导过程</h4>
|
||||
<!-- Phi -->
|
||||
<div class="control-group">
|
||||
<span class="control-label">Phi (仰角/Pitch) [-90 ~ 90]</span>
|
||||
<div class="input-row">
|
||||
<input type="range" id="slider-phi" min="-90" max="90" value="30" step="1">
|
||||
<input type="number" id="input-phi" min="-90" max="90" value="30">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Radius -->
|
||||
<div class="control-group">
|
||||
<span class="control-label">Radius (半径 r) [1 ~ 15]</span>
|
||||
<div class="input-row">
|
||||
<input type="range" id="slider-r" min="1" max="15" value="8" step="0.1">
|
||||
<input type="number" id="input-r" min="1" max="15" value="8" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 计算过程 -->
|
||||
<h4>计算过程推导</h4>
|
||||
<div class="calc-box" id="calc-content">
|
||||
<!-- 这里的内容会由 JS 动态生成 -->
|
||||
<!-- JS 生成 -->
|
||||
</div>
|
||||
|
||||
<h4>结果验证</h4>
|
||||
<div style="font-size: 0.85em; display:grid; grid-template-columns: 1fr 1fr 1fr; gap:5px; text-align:center;">
|
||||
<div style="background:#330000; border:1px solid #ff0000; border-radius:4px;">X: <span id="final-x">0</span></div>
|
||||
<div style="background:#003300; border:1px solid #00ff00; border-radius:4px;">Y: <span id="final-y">0</span></div>
|
||||
<div style="background:#000033; border:1px solid #0000ff; border-radius:4px;">Z: <span id="final-z">0</span></div>
|
||||
<h4>最终坐标 (Unity)</h4>
|
||||
<div class="result-grid">
|
||||
<div class="res-box" style="background:#330000; border:1px solid #ff4444; color:#ffcccc;">X: <span id="final-x">0</span></div>
|
||||
<div class="res-box" style="background:#003300; border:1px solid #44ff44; color:#ccffcc;">Y: <span id="final-y">0</span></div>
|
||||
<div class="res-box" style="background:#000033; border:1px solid #4444ff; color:#ccccff;">Z: <span id="final-z">0</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="scene-tooltip">左键旋转 · 右键平移 · 滚轮缩放</div>
|
||||
|
||||
<script>
|
||||
// --- 1. 初始化场景 ---
|
||||
// --- 1. 初始化 ---
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x222222);
|
||||
scene.background = new THREE.Color(0x1a1a1a);
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
|
||||
camera.position.set(-9, 7, -9);
|
||||
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||
// 调整相机位置:放在正前方偏右上的位置,这样看 Z 轴就是“向里”延伸的
|
||||
camera.position.set(0, 5, 20);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
@@ -121,17 +155,18 @@
|
||||
const controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
|
||||
// --- 2. 辅助与坐标轴 ---
|
||||
const gridHelper = new THREE.GridHelper(20, 20, 0x444444, 0x333333);
|
||||
// --- 2. 坐标系绘制 (X右, Y上, Z入) ---
|
||||
// 网格
|
||||
const gridHelper = new THREE.GridHelper(30, 30, 0x444444, 0x222222);
|
||||
scene.add(gridHelper);
|
||||
|
||||
// 绘制线段的辅助函数
|
||||
// 绘制轴线的函数
|
||||
function createLine(start, end, color, dashed=false) {
|
||||
const points = [start, end];
|
||||
const geo = new THREE.BufferGeometry().setFromPoints(points);
|
||||
let mat;
|
||||
if(dashed) {
|
||||
mat = new THREE.LineDashedMaterial({ color: color, dashSize: 0.4, gapSize: 0.2, scale: 1 });
|
||||
mat = new THREE.LineDashedMaterial({ color: color, dashSize: 0.5, gapSize: 0.3, scale: 1 });
|
||||
} else {
|
||||
mat = new THREE.LineBasicMaterial({ color: color, linewidth: 2 });
|
||||
}
|
||||
@@ -140,14 +175,19 @@
|
||||
return line;
|
||||
}
|
||||
|
||||
// 绘制坐标轴 (模拟 Unity 左手系颜色)
|
||||
const axisLen = 8;
|
||||
scene.add(createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(axisLen,0,0), 0xff0000)); // X Red
|
||||
scene.add(createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(0,axisLen,0), 0x00ff00)); // Y Green
|
||||
scene.add(createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,axisLen), 0x0000ff)); // Z Blue
|
||||
const axisLen = 12;
|
||||
// X轴: (0,0,0) -> (12, 0, 0) [红色, 向右]
|
||||
scene.add(createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(axisLen,0,0), 0xff0000));
|
||||
|
||||
// Y轴: (0,0,0) -> (0, 12, 0) [绿色, 向上]
|
||||
scene.add(createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(0,axisLen,0), 0x00ff00));
|
||||
|
||||
// Z轴: (0,0,0) -> (0, 0, -12) [蓝色, 向屏幕里]
|
||||
// 注意:在 WebGL 中,Z- 是向里的。我们视觉上模拟 Unity Z+ 向前,所以我们画向 Z- 方向
|
||||
scene.add(createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,-axisLen), 0x0066ff));
|
||||
|
||||
// 添加文字标签
|
||||
function addLabel(text, x, y, z, color) {
|
||||
// 轴标签 Sprites
|
||||
function addLabel(text, pos, color) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = 64; canvas.height = 64;
|
||||
@@ -157,157 +197,155 @@
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(text, 32, 32);
|
||||
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: new THREE.CanvasTexture(canvas) }));
|
||||
sprite.position.set(x, y, z);
|
||||
sprite.scale.set(1, 1, 1);
|
||||
sprite.position.copy(pos);
|
||||
sprite.scale.set(1.5, 1.5, 1.5);
|
||||
scene.add(sprite);
|
||||
}
|
||||
addLabel("X", axisLen+0.5, 0, 0, "#ff5555");
|
||||
addLabel("Y", 0, axisLen+0.5, 0, "#55ff55");
|
||||
addLabel("Z", 0, 0, axisLen+0.5, "#5555ff");
|
||||
addLabel("X", new THREE.Vector3(axisLen+1, 0, 0), "#ff5555");
|
||||
addLabel("Y", new THREE.Vector3(0, axisLen+1, 0), "#55ff55");
|
||||
addLabel("Z", new THREE.Vector3(0, 0, -axisLen-1), "#5588ff"); // Z标签放在远处
|
||||
|
||||
// --- 3. 演示对象 ---
|
||||
// --- 3. 场景对象 ---
|
||||
// 黄球
|
||||
const sphereMesh = new THREE.Mesh(
|
||||
new THREE.SphereGeometry(0.3, 32, 32),
|
||||
new THREE.SphereGeometry(0.4, 32, 32),
|
||||
new THREE.MeshBasicMaterial({ color: 0xffff00 })
|
||||
);
|
||||
scene.add(sphereMesh);
|
||||
|
||||
// 动态线段
|
||||
// 辅助线
|
||||
const radiusLine = createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,0), 0xffff00, true);
|
||||
scene.add(radiusLine);
|
||||
const projLine = createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,0), 0x00aa00); // 绿色投影线
|
||||
const projLine = createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,0), 0x00aa00);
|
||||
scene.add(projLine);
|
||||
const heightLine = createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,0), 0x00aaff); // 蓝色高度线
|
||||
const heightLine = createLine(new THREE.Vector3(0,0,0), new THREE.Vector3(0,0,0), 0x00aaff);
|
||||
scene.add(heightLine);
|
||||
|
||||
// 弧线 (Theta)
|
||||
const thetaCurveGeo = new THREE.BufferGeometry();
|
||||
const thetaCurve = new THREE.Line(thetaCurveGeo, new THREE.LineBasicMaterial({ color: 0xffaaaa }));
|
||||
scene.add(thetaCurve);
|
||||
|
||||
// --- 4. 计算与更新逻辑 ---
|
||||
const ui = {
|
||||
theta: document.getElementById('slider-theta'),
|
||||
phi: document.getElementById('slider-phi'),
|
||||
r: document.getElementById('slider-r'),
|
||||
valTheta: document.getElementById('val-theta'),
|
||||
valPhi: document.getElementById('val-phi'),
|
||||
valR: document.getElementById('val-r'),
|
||||
calcContent: document.getElementById('calc-content'),
|
||||
finalX: document.getElementById('final-x'),
|
||||
finalY: document.getElementById('final-y'),
|
||||
finalZ: document.getElementById('final-z'),
|
||||
// --- 4. 逻辑绑定 ---
|
||||
const els = {
|
||||
sTheta: document.getElementById('slider-theta'),
|
||||
iTheta: document.getElementById('input-theta'),
|
||||
sPhi: document.getElementById('slider-phi'),
|
||||
iPhi: document.getElementById('input-phi'),
|
||||
sR: document.getElementById('slider-r'),
|
||||
iR: document.getElementById('input-r'),
|
||||
calc: document.getElementById('calc-content'),
|
||||
resX: document.getElementById('final-x'),
|
||||
resY: document.getElementById('final-y'),
|
||||
resZ: document.getElementById('final-z'),
|
||||
};
|
||||
|
||||
// 数据同步:滑块 <-> 输入框
|
||||
function bindInput(slider, input) {
|
||||
slider.addEventListener('input', () => {
|
||||
input.value = slider.value;
|
||||
update();
|
||||
});
|
||||
input.addEventListener('input', () => {
|
||||
// 限制输入范围
|
||||
let val = parseFloat(input.value);
|
||||
if(input.min && val < parseFloat(input.min)) val = parseFloat(input.min);
|
||||
if(input.max && val > parseFloat(input.max)) val = parseFloat(input.max);
|
||||
slider.value = val;
|
||||
update();
|
||||
});
|
||||
}
|
||||
bindInput(els.sTheta, els.iTheta);
|
||||
bindInput(els.sPhi, els.iPhi);
|
||||
bindInput(els.sR, els.iR);
|
||||
|
||||
function update() {
|
||||
// 获取输入
|
||||
const r = parseFloat(ui.r.value);
|
||||
const theta = parseFloat(ui.theta.value);
|
||||
const phi = parseFloat(ui.phi.value);
|
||||
// 1. 获取数值
|
||||
const r = parseFloat(els.iR.value);
|
||||
const theta = parseFloat(els.iTheta.value);
|
||||
const phi = parseFloat(els.iPhi.value);
|
||||
|
||||
// 更新标签
|
||||
ui.valTheta.innerText = theta + "°";
|
||||
ui.valPhi.innerText = phi + "°";
|
||||
ui.valR.innerText = r;
|
||||
|
||||
// --- 步骤 1: 角度转弧度 ---
|
||||
// 2. 数学计算 (Unity Logic)
|
||||
const radTheta = theta * Math.PI / 180;
|
||||
const radPhi = phi * Math.PI / 180;
|
||||
|
||||
// 为了显示友好,预先计算三角函数值
|
||||
const sinTheta = Math.sin(radTheta);
|
||||
const cosTheta = Math.cos(radTheta);
|
||||
const sinPhi = Math.sin(radPhi);
|
||||
const cosPhi = Math.cos(radPhi); // 水平分量系数
|
||||
const sinT = Math.sin(radTheta);
|
||||
const cosT = Math.cos(radTheta);
|
||||
const sinP = Math.sin(radPhi);
|
||||
const cosP = Math.cos(radPhi);
|
||||
|
||||
// --- 步骤 2: 计算坐标 (Unity 坐标系) ---
|
||||
// 投影到XZ平面的长度: r_xz = r * cos(phi)
|
||||
const r_xz = r * cosPhi;
|
||||
// Unity公式:
|
||||
// Z (Forward) = r * cos(phi) * cos(theta)
|
||||
// X (Right) = r * cos(phi) * sin(theta)
|
||||
// Y (Up) = r * sin(phi)
|
||||
|
||||
// x = r_xz * sin(theta) (因为0度指向Z轴,X轴是Right,所以是sin)
|
||||
const x = r_xz * sinTheta;
|
||||
|
||||
// y = r * sin(phi) (高度)
|
||||
const y = r * sinPhi;
|
||||
|
||||
// z = r_xz * cos(theta)
|
||||
const z = r_xz * cosTheta;
|
||||
const r_xz = r * cosP; // 投影长度
|
||||
const valX = r_xz * sinT;
|
||||
const valY = r * sinP;
|
||||
const valZ = r_xz * cosT; // 这是 Unity 中的 Z 值
|
||||
|
||||
// --- 3D场景更新 ---
|
||||
sphereMesh.position.set(x, y, z);
|
||||
sphereMesh.lookAt(0, 0, 0);
|
||||
// 3. 渲染映射 (关键步骤)
|
||||
// Unity Z+ (Forward) 对应 WebGL Z- (Into Screen)
|
||||
// 所以我们在 ThreeJS 里设置位置时,Z 坐标取反
|
||||
const renderX = valX;
|
||||
const renderY = valY;
|
||||
const renderZ = -valZ;
|
||||
|
||||
// 更新线段
|
||||
sphereMesh.position.set(renderX, renderY, renderZ);
|
||||
|
||||
// 更新辅助线
|
||||
const origin = new THREE.Vector3(0,0,0);
|
||||
const target = new THREE.Vector3(x,y,z);
|
||||
const ground = new THREE.Vector3(x,0,z);
|
||||
|
||||
const target = new THREE.Vector3(renderX, renderY, renderZ);
|
||||
const ground = new THREE.Vector3(renderX, 0, renderZ);
|
||||
|
||||
radiusLine.geometry.setFromPoints([origin, target]);
|
||||
radiusLine.computeLineDistances();
|
||||
|
||||
projLine.geometry.setFromPoints([origin, ground]);
|
||||
heightLine.geometry.setFromPoints([ground, target]);
|
||||
|
||||
// 更新弧线
|
||||
const curvePts = [];
|
||||
const segments = 20;
|
||||
const arcR = r * 0.3;
|
||||
for(let i=0; i<=segments; i++) {
|
||||
const t = (i/segments) * radTheta;
|
||||
curvePts.push(new THREE.Vector3(arcR * Math.sin(t), 0, arcR * Math.cos(t)));
|
||||
}
|
||||
thetaCurveGeo.setFromPoints(curvePts);
|
||||
// 4. 更新UI显示
|
||||
els.resX.innerText = valX.toFixed(2);
|
||||
els.resY.innerText = valY.toFixed(2);
|
||||
els.resZ.innerText = valZ.toFixed(2);
|
||||
|
||||
// --- 生成计算过程 HTML ---
|
||||
// 使用 toFixed 保持小数位整洁
|
||||
// 5. 生成计算过程 HTML
|
||||
const s_r = r.toString();
|
||||
const s_cosP = cosPhi.toFixed(3);
|
||||
const s_sinP = sinPhi.toFixed(3);
|
||||
const s_cosT = cosTheta.toFixed(3);
|
||||
const s_sinT = sinTheta.toFixed(3);
|
||||
const s_cosP = cosP.toFixed(3);
|
||||
const s_sinP = sinP.toFixed(3);
|
||||
const s_cosT = cosT.toFixed(3);
|
||||
const s_sinT = sinT.toFixed(3);
|
||||
const s_rxz = r_xz.toFixed(3);
|
||||
|
||||
const html = `
|
||||
|
||||
els.calc.innerHTML = `
|
||||
<div class="calc-step">
|
||||
<div class="comment">// 1. 角度转弧度 & 三角函数</div>
|
||||
<div>θ = ${theta}° ⇒ sin: <span class="num">${s_sinT}</span>, cos: <span class="num">${s_cosT}</span></div>
|
||||
<div>φ = ${phi}° ⇒ sin: <span class="num">${s_sinP}</span>, cos: <span class="num">${s_cosP}</span></div>
|
||||
<div class="comment">// 1. 角度转弧度 (Deg -> Rad)</div>
|
||||
<div>θ=${theta}° ⇒ sin:${s_sinT}, cos:${s_cosT}</div>
|
||||
<div>φ=${phi}° ⇒ sin:${s_sinP}, cos:${s_cosP}</div>
|
||||
</div>
|
||||
|
||||
<div class="calc-step">
|
||||
<div class="comment">// 2. 计算地面投影半径 (XZ平面)</div>
|
||||
<div class="comment">// 2. XZ平面投影半径</div>
|
||||
<div class="var">r_xz</div> = r * cos(φ)
|
||||
<br> = ${s_r} * ${s_cosP} = <span class="num">${s_rxz}</span>
|
||||
</div>
|
||||
|
||||
<div class="calc-step">
|
||||
<div class="comment">// 3. 计算最终坐标</div>
|
||||
<span class="res-y">Y (高度)</span> = r * sin(φ)
|
||||
<br> = ${s_r} * ${s_sinP} = <span class="res-y">${y.toFixed(2)}</span>
|
||||
<div class="comment">// 3. 计算 Unity 坐标</div>
|
||||
<span class="res-y">Y (Up)</span> = r * sin(φ)
|
||||
<br> = ${s_r} * ${s_sinP} = <span class="res-y">${valY.toFixed(2)}</span>
|
||||
<br>
|
||||
<span class="res-x">X (右侧)</span> = <span class="var">r_xz</span> * sin(θ)
|
||||
<br> = ${s_rxz} * ${s_sinT} = <span class="res-x">${x.toFixed(2)}</span>
|
||||
<span class="res-x">X (Right)</span> = <span class="var">r_xz</span> * sin(θ)
|
||||
<br> = ${s_rxz} * ${s_sinT} = <span class="res-x">${valX.toFixed(2)}</span>
|
||||
<br>
|
||||
<span class="res-z">Z (前方)</span> = <span class="var">r_xz</span> * cos(θ)
|
||||
<br> = ${s_rxz} * ${s_cosT} = <span class="res-z">${z.toFixed(2)}</span>
|
||||
<span class="res-z">Z (Forward)</span> = <span class="var">r_xz</span> * cos(θ)
|
||||
<br> = ${s_rxz} * ${s_cosT} = <span class="res-z">${valZ.toFixed(2)}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
ui.calcContent.innerHTML = html;
|
||||
ui.finalX.innerText = x.toFixed(2);
|
||||
ui.finalY.innerText = y.toFixed(2);
|
||||
ui.finalZ.innerText = z.toFixed(2);
|
||||
}
|
||||
|
||||
// 监听
|
||||
ui.theta.addEventListener('input', update);
|
||||
ui.phi.addEventListener('input', update);
|
||||
ui.r.addEventListener('input', update);
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
});
|
||||
|
||||
// 启动
|
||||
update();
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
Reference in New Issue
Block a user