Initial commit: RHINE LAB · ANALYSIS OS:三维界面与动效实验(本仓库不含个人归档索引数据)
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Rhine Lab · 模型细节对照</title>
|
||||
<link rel="stylesheet" href="/reference/wave-compare.css" />
|
||||
<style>
|
||||
#original {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.caption span {
|
||||
max-width: 60%;
|
||||
text-align: right;
|
||||
}
|
||||
.shot[aria-pressed="true"] {
|
||||
background: #353b30;
|
||||
color: #f4f0e9;
|
||||
}
|
||||
.scene {
|
||||
pointer-events: none;
|
||||
}
|
||||
#status {
|
||||
font-size: 12px;
|
||||
color: #666b5d;
|
||||
margin: 18px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
.view::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
background:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
#eae5e1 0%,
|
||||
rgba(234, 229, 225, 0.82) 11%,
|
||||
transparent 40%
|
||||
),
|
||||
linear-gradient(0deg, rgba(234, 229, 225, 0.94) 0%, transparent 19%),
|
||||
radial-gradient(
|
||||
ellipse at 48% 48%,
|
||||
transparent 35%,
|
||||
rgba(234, 229, 225, 0.38) 85%
|
||||
);
|
||||
}
|
||||
#original {
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
<main>
|
||||
<header>
|
||||
<div>
|
||||
<span class="eyebrow">RHINE LAB / MODEL STUDY</span>
|
||||
<h1>模型细节对照</h1>
|
||||
</div>
|
||||
<a href="/?scene=archive" target="_blank" rel="noopener"
|
||||
>体验当前版本 ↗</a
|
||||
>
|
||||
</header>
|
||||
<p class="intro">
|
||||
放大比较原片、细化前与细化后。两份实时场景使用相同镜头、基线光照和运动。
|
||||
</p>
|
||||
<section class="toolbar">
|
||||
<label
|
||||
>左侧
|
||||
<select id="source">
|
||||
<option value="original">原视频</option>
|
||||
<option value="baseline">细化前</option>
|
||||
</select></label
|
||||
><button class="shot" data-frame="690">27.60 秒 · 阵列入场</button
|
||||
><button class="shot" data-frame="729">29.16 秒 · 波峰</button
|
||||
><button class="shot" data-frame="787" aria-pressed="true">
|
||||
31.48 秒 · 阵列</button
|
||||
><button class="shot" data-frame="975">39.00 秒 · 特写</button>
|
||||
<label
|
||||
>放大
|
||||
<select id="magnify">
|
||||
<option value="1">完整画面</option>
|
||||
<option value="2">2× 模型上半部</option>
|
||||
<option value="3">3× 压槽与螺钉</option>
|
||||
</select></label
|
||||
>
|
||||
</section>
|
||||
<section class="views">
|
||||
<article>
|
||||
<div class="caption">
|
||||
<h2 id="left-title">原视频</h2>
|
||||
<span>同一关键帧</span>
|
||||
</div>
|
||||
<div class="view" id="before">
|
||||
<div class="scene"></div>
|
||||
<img
|
||||
id="original"
|
||||
src="/reference/motion/0975.jpg"
|
||||
alt="原视频第 787 帧"
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
<article>
|
||||
<div class="caption">
|
||||
<h2>细化后</h2>
|
||||
<span>相同模型与镜头</span>
|
||||
</div>
|
||||
<div class="view" id="after"><div class="scene"></div></div>
|
||||
</article>
|
||||
</section>
|
||||
<p id="status" role="status">正在加载两份实时场景…</p>
|
||||
<footer>
|
||||
可切换左侧为细化前;放大观察顶部与圆环。原片仅用于本地审阅。<a
|
||||
href="/reference/wave-compare.html"
|
||||
>运动版本对比 ↗</a
|
||||
>
|
||||
</footer>
|
||||
</main>
|
||||
<script type="module">
|
||||
import { ArchiveScene } from "/src/scene.ts";
|
||||
import { baselineSelectionWave, smooth } from "/src/motion.ts";
|
||||
const $ = (q) => document.querySelector(q),
|
||||
scenes = [
|
||||
new ArchiveScene(
|
||||
$("#before .scene"),
|
||||
baselineSelectionWave,
|
||||
false,
|
||||
"baseline",
|
||||
),
|
||||
new ArchiveScene(
|
||||
$("#after .scene"),
|
||||
baselineSelectionWave,
|
||||
false,
|
||||
"baseline",
|
||||
),
|
||||
];
|
||||
const draws = scenes.map((s) => s.composer.render.bind(s.composer));
|
||||
scenes.forEach((s) => (s.composer.render = () => {}));
|
||||
let current = 975,
|
||||
ready = false;
|
||||
function render() {
|
||||
const shot = current / 25 - 5,
|
||||
zoom =
|
||||
0.55 * smooth((shot - 27.3) / 1.65) +
|
||||
0.45 * smooth((shot - 29) / 5);
|
||||
scenes.forEach((s, i) => {
|
||||
s.update(100, { reveal: 1, lift: 0, zoom, time: shot });
|
||||
if (i === 1 || $("#source").value === "baseline") draws[i]();
|
||||
});
|
||||
$("#original").src =
|
||||
`/reference/motion/${String(current).padStart(4, "0")}.jpg`;
|
||||
$("#original").alt = `原视频第 ${current} 帧`;
|
||||
$("#original").hidden = $("#source").value !== "original";
|
||||
$("#left-title").textContent =
|
||||
$("#source").value === "original" ? "原视频" : "细化前";
|
||||
document
|
||||
.querySelectorAll("[data-frame]")
|
||||
.forEach((b) =>
|
||||
b.setAttribute(
|
||||
"aria-pressed",
|
||||
String(Number(b.dataset.frame) === current),
|
||||
),
|
||||
);
|
||||
const delta =
|
||||
scenes[0].camera.position.distanceTo(scenes[1].camera.position) +
|
||||
scenes[0].model.position.distanceTo(scenes[1].model.position);
|
||||
$("#status").textContent =
|
||||
`${(current / 25).toFixed(2)} 秒 · 模型位置与相机${delta < 1e-8 ? "一致" : "存在偏差"}。比较环形阶梯、顶部压槽、螺钉与边框接缝。`;
|
||||
}
|
||||
function resize() {
|
||||
scenes.forEach((s) => {
|
||||
s.resize();
|
||||
s.renderer.setPixelRatio(1);
|
||||
s.composer.setPixelRatio(1);
|
||||
s.renderer.setSize(1920, 1080, false);
|
||||
s.composer.setSize(1920, 1080);
|
||||
s.renderer.domElement.style.width = "100%";
|
||||
s.renderer.domElement.style.height = "100%";
|
||||
});
|
||||
if (ready) render();
|
||||
}
|
||||
$("#magnify").onchange = () => {
|
||||
const scale = Number($("#magnify").value);
|
||||
document.querySelectorAll(".scene, #original").forEach((el) => {
|
||||
el.style.transformOrigin = scale === 3 ? "57% 25%" : "55% 38%";
|
||||
el.style.transform = `scale(${scale})`;
|
||||
});
|
||||
};
|
||||
$("#source").onchange = () => {
|
||||
if (ready) render();
|
||||
};
|
||||
document.querySelectorAll("[data-frame]").forEach(
|
||||
(b) =>
|
||||
(b.onclick = () => {
|
||||
current = Number(b.dataset.frame);
|
||||
if (ready) render();
|
||||
}),
|
||||
);
|
||||
addEventListener("resize", resize);
|
||||
try {
|
||||
await document.fonts.ready;
|
||||
await Promise.all(
|
||||
scenes.map((s, i) =>
|
||||
s.load(
|
||||
i === 0
|
||||
? "/reference/clear-study/archive-before.glb"
|
||||
: "/assets/archive-cassette.glb",
|
||||
),
|
||||
),
|
||||
);
|
||||
scenes.forEach((s) => {
|
||||
s.setMode("hidden");
|
||||
s.select(0);
|
||||
});
|
||||
resize();
|
||||
ready = true;
|
||||
render();
|
||||
} catch (e) {
|
||||
$("#status").textContent = `加载失败:${e.message}`;
|
||||
console.error(e);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user