feat: add camera switch, fix click event/dialog resize/map state sync

- fix(engine): adapt click handler to base engine array format data[0].url/ids
- feat(toolbar): add perspective/orthographic camera switch button with dynamic icon
- fix(dialog): clamp resize to container bounds to prevent overflow
- feat(demo): add auto-combine feature with robust URL parsing and validation
- fix(walk): close minimap on walk exit and sync map state between toolbar and walk panel
- fix(engine): correct MiniMap getstate() casing to match base engine API
- build: rebuild demo libs
This commit is contained in:
yuding
2026-03-05 17:43:50 +08:00
parent b96e5f3262
commit 507112fcf9
18 changed files with 6890 additions and 6501 deletions

View File

@@ -200,6 +200,7 @@
<button class="primary" onclick="loadModel()">加载模型</button>
<button onclick="switchModel()">切换模型</button>
<button onclick="loadCombinedModel()">组合模型</button>
<button onclick="openCombineDialog()">自动组合</button>
</div>
<div class="btn-container" style="margin-top: 8px;">
<button onclick="pauseRendering()">暂停渲染</button>
@@ -216,6 +217,19 @@
<div id="app"></div>
</main>
<!-- 自动组合弹窗 -->
<div id="combine-overlay" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.45); z-index:9999; display:none; justify-content:center; align-items:center;">
<div style="background:#fff; border-radius:10px; padding:24px; width:520px; max-width:90vw; box-shadow:0 8px 32px rgba(0,0,0,.18);">
<h3 style="margin:0 0 12px; font-size:1.1rem; color:#333;">自动组合加载</h3>
<p style="margin:0 0 10px; font-size:.85rem; color:#888;">请输入模型 URL每行一个或用英文逗号分隔</p>
<textarea id="combine-urls" rows="6" style="width:100%; padding:10px; font-size:.85rem; border:1px solid #ddd; border-radius:6px; resize:vertical; font-family:monospace;" placeholder="https://example.com/model1/&#10;https://example.com/model2/"></textarea>
<div style="display:flex; justify-content:flex-end; gap:8px; margin-top:14px;">
<button onclick="closeCombineDialog()" style="min-width:72px;">取消</button>
<button class="primary" onclick="confirmCombineLoad()" style="min-width:72px;">确定加载</button>
</div>
</div>
</div>
<script>
let engine = null;
let isToolbarVisible = true;
@@ -577,6 +591,84 @@
}
}
}
// --- 自动组合加载 ---
function openCombineDialog() {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎!');
return;
}
const overlay = document.getElementById('combine-overlay');
overlay.style.display = 'flex';
}
function closeCombineDialog() {
const overlay = document.getElementById('combine-overlay');
overlay.style.display = 'none';
}
function confirmCombineLoad() {
let raw = document.getElementById('combine-urls').value.trim();
if (!raw) {
alert('请输入至少一个模型 URL');
return;
}
// 1) 统一清洗去除智能引号、零宽字符、BOM 等不可见字符
raw = raw
.replace(/[\u2018\u2019]/g, "'") // “” → '
.replace(/[\u201C\u201D]/g, '"') // “” → "
.replace(/[\u200B\uFEFF\u00A0]/g, '') // 零宽空格、BOM、不换行空格
.trim();
// 2) 尝试当 JSON 数组解析
let urls = [];
try {
const jsonStr = raw.replace(/'/g, '"');
const parsed = JSON.parse(jsonStr);
if (Array.isArray(parsed)) {
urls = parsed.map(s => String(s).trim()).filter(Boolean);
}
} catch(e) {
// 3) JSON 失败,走分割逻辑
urls = raw.split(/[,;\n\r]+/)
.map(s => s.trim().replace(/^['"|\[\]\s]+|['"|\[\]\s]+$/g, ''))
.filter(Boolean);
}
// 4) 只保留 http/https 开头的合法 URL
urls = urls.filter(s => /^https?:\/\//i.test(s));
if (urls.length === 0) {
alert('未解析到有效的 URL请确保以 http:// 或 https:// 开头');
return;
}
// 5) 逐个校验 URL 是否合法
for (let i = 0; i < urls.length; i++) {
try {
new URL(urls[i]);
} catch(e) {
alert('第 ' + (i+1) + ' 个 URL 无效:\n' + urls[i] + '\n\n请检查是否包含特殊字符');
console.error('❌ 无效 URL原始字符:', JSON.stringify(urls[i]));
return;
}
}
try {
engine.engine.loadModel(urls, {
position: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1]
});
console.log('✅ 自动组合加载已发送:', urls);
closeCombineDialog();
document.getElementById('combine-urls').value = '';
} catch (error) {
console.error('❌ 自动组合加载错误:', error);
}
}
</script>
</body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long