extract css
This commit is contained in:
323
index.html
323
index.html
@@ -3,229 +3,128 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Pipeline Test</title>
|
<title>Pipeline Test</title>
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background: #222;
|
|
||||||
color: #eee;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
<link rel="stylesheet" href="style.css">
|
||||||
width: 80ch;
|
<link rel="stylesheet" href="./src/components.css">
|
||||||
margin-inline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.figure {
|
<script type="module" src="./src/components.js"></script>
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 20em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.figure > canvas {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<h1>Visualizer</h1>
|
<h1>Visualizer</h1>
|
||||||
|
|
||||||
<h2>Dynamic</h2>
|
<h2>Dynamic Pulses</h2>
|
||||||
|
<mc-diorama radius="4">
|
||||||
|
<mc-timeline loop autoplay controls="false"></mc-timeline>
|
||||||
|
<mc-world>
|
||||||
|
p 0 0 0 observer facing=south powered=false
|
||||||
|
p 0 0 1 observer facing=east powered=false
|
||||||
|
p 1 0 1 observer facing=north powered=false
|
||||||
|
p 1 0 0 observer facing=west powered=false
|
||||||
|
t 0
|
||||||
|
p 0 0 1 powered=false
|
||||||
|
p 0 0 0 powered=true
|
||||||
|
t 2
|
||||||
|
p 0 0 0 powered=false
|
||||||
|
p 1 0 0 powered=true
|
||||||
|
t 4
|
||||||
|
p 1 0 0 powered=false
|
||||||
|
p 1 0 1 powered=true
|
||||||
|
t 6
|
||||||
|
p 1 0 1 powered=false
|
||||||
|
p 0 0 1 powered=true
|
||||||
|
t 8
|
||||||
|
p 0 0 0
|
||||||
|
</mc-world>
|
||||||
|
</mc-diorama>
|
||||||
|
|
||||||
<div class="figure" id="demo-pulses"></div>
|
<h2>Static multi-view scene</h2>
|
||||||
|
|
||||||
<div class="figure" id="demo-piston"></div>
|
<mc-frame id="nonsense">
|
||||||
|
<mc-camera orbit></mc-camera>
|
||||||
|
<mc-world>
|
||||||
|
p -1 0 0 redstone_wire east=side west=none north=none south=none power=0
|
||||||
|
p 0 0 0 repeater facing=east delay=1 locked=false powered=false
|
||||||
|
p 2 0 0 oak_trapdoor facing=east half=bottom open=true
|
||||||
|
p 0 -1 0 lodestone
|
||||||
|
p 0 -2 0 piston facing=north extended=false
|
||||||
|
</mc-world>
|
||||||
|
</mc-frame>
|
||||||
|
|
||||||
<h2>Static Sub-Figures</h2>
|
<div class="grid-2col">
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr;">
|
<mc-diorama frameid="nonsense" theta="180" phi="90"></mc-diorama>
|
||||||
<div class="figure" id="demo-top-static"></div>
|
<mc-diorama frameid="nonsense">
|
||||||
<div class="figure" id="demo-iso"></div>
|
<mc-camera orbit></mc-camera>
|
||||||
|
</mc-diorama>
|
||||||
|
<mc-diorama frameid="nonsense" theta="180" phi="0"></mc-diorama>
|
||||||
|
<mc-diorama frameid="nonsense" theta="270" phi="0"></mc-diorama>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="atlas-container" style="position: absolute; right: 0; top: 0"></div>
|
<h2>Dynamic Timeline Control</h2>
|
||||||
|
<mc-diorama>
|
||||||
|
<mc-camera orbit></mc-camera>
|
||||||
|
<mc-timeline loop subticks></mc-timeline>
|
||||||
|
<mc-world>
|
||||||
|
p 0 -1 0 smooth_stone_slab type=top
|
||||||
|
p 1 -1 0 smooth_stone_slab type=top
|
||||||
|
p 0 0 0 redstone_wire power=0 east=side west=none north=none south=none
|
||||||
|
p 1 0 0 repeater facing=west powered=false locked=false delay=2
|
||||||
|
p 2 0 0 sticky_piston facing=east extended=false
|
||||||
|
p 2 -1 0 piston facing=east extended=false
|
||||||
|
p 3 0 0 slime_block
|
||||||
|
p 4 0 0 slime_block
|
||||||
|
t 2
|
||||||
|
p 0 0 0 power=15
|
||||||
|
t 6
|
||||||
|
p 1 0 0 powered=true
|
||||||
|
l begin-extend
|
||||||
|
t 7
|
||||||
|
p 2 0 0 extended=true
|
||||||
|
p 4.0 0 0 air &
|
||||||
|
p 4.5 0 0 slime_block
|
||||||
|
p 3.0 0 0 air &
|
||||||
|
p 3.5 0 0 slime_block
|
||||||
|
p 2.5 0 0 piston_head facing=east short=true type=sticky
|
||||||
|
p 2 -1 0 extended=true
|
||||||
|
p 2.5 -1 0 piston_head facing=east short=true type=normal
|
||||||
|
t 8
|
||||||
|
p 4.5 0 0 air &
|
||||||
|
p 5.0 0 0 slime_block
|
||||||
|
p 3.5 0 0 air &
|
||||||
|
p 4.0 0 0 slime_block
|
||||||
|
p 2.5 0 0 air &
|
||||||
|
p 3.0 0 0 piston_head facing=east short=false type=sticky
|
||||||
|
p 2.5 -1 0 air &
|
||||||
|
p 3.0 -1 0 piston_head facing=east short=false type=normal
|
||||||
|
l end-extend
|
||||||
|
t 12
|
||||||
|
p 0 0 0 power=0
|
||||||
|
t 16
|
||||||
|
p 1 0 0 powered=false
|
||||||
|
l begin-retract
|
||||||
|
t 17
|
||||||
|
p 3 0 0 air &
|
||||||
|
p 2.5 0 0 piston_head facing=east short=true type=sticky
|
||||||
|
p 4.0 0 0 air &
|
||||||
|
p 3.5 0 0 slime_block
|
||||||
|
p 5.0 0 0 air &
|
||||||
|
p 4.5 0 0 slime_block
|
||||||
|
p 3.0 -1 0 air &
|
||||||
|
p 2.5 -1 0 piston_head facing=east short=true type=normal
|
||||||
|
t 18
|
||||||
|
p 2.5 0 0 air
|
||||||
|
p 3.5 0 0 air &
|
||||||
|
p 3.0 0 0 slime_block
|
||||||
|
p 4.5 0 0 air &
|
||||||
|
p 4.0 0 0 slime_block
|
||||||
|
p 2 0 0 extended=false
|
||||||
|
p 2.5 -1 0 air
|
||||||
|
p 2 -1 0 extended=false
|
||||||
|
l end-retract
|
||||||
|
</mc-world>
|
||||||
|
</mc-diorama>
|
||||||
|
|
||||||
|
<div id="atlas-container"></div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import {Engine} from './src/engine.js';
|
|
||||||
import {Diorama} from './src/diorama.js';
|
|
||||||
import {World, WorldFrame} from './src/world.js';
|
|
||||||
import {SpacetimeController} from "./src/controller.js";
|
|
||||||
|
|
||||||
async function initDocument() {
|
|
||||||
try {
|
|
||||||
const engine = new Engine();
|
|
||||||
document.getElementById('atlas-container')?.appendChild(engine.atlas.canvas);
|
|
||||||
|
|
||||||
const w_static = new World(`
|
|
||||||
p -1 0 0 redstone_wire east=side west=none north=none south=none power=0
|
|
||||||
p 0 0 0 repeater facing=east delay=1 locked=false powered=false
|
|
||||||
p 2 0 0 oak_trapdoor facing=east half=bottom open=true
|
|
||||||
p 0 -1 0 lodestone
|
|
||||||
p 0 -2 0 piston facing=north extended=false
|
|
||||||
`)
|
|
||||||
const f_static = new WorldFrame(w_static);
|
|
||||||
const d_static_top = new Diorama('demo-top-static', f_static, () => engine.requestRender());
|
|
||||||
d_static_top.radius = 5;
|
|
||||||
d_static_top.theta = 180;
|
|
||||||
d_static_top.phi = 90;
|
|
||||||
d_static_top.centerView();
|
|
||||||
d_static_top.saveState();
|
|
||||||
engine.addDiorama(d_static_top)
|
|
||||||
const d_static_iso = new Diorama('demo-iso', f_static, () => engine.requestRender());
|
|
||||||
d_static_iso.radius = 5;
|
|
||||||
d_static_iso.centerView();
|
|
||||||
d_static_iso.saveState();
|
|
||||||
engine.addDiorama(d_static_iso);
|
|
||||||
const c_static_iso = new SpacetimeController(d_static_iso, {
|
|
||||||
timeline: false,
|
|
||||||
subticks: false,
|
|
||||||
autoplay: false,
|
|
||||||
loop: false,
|
|
||||||
zoom: false,
|
|
||||||
pan: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const w_pulses = new World(`
|
|
||||||
p 0 0 0 observer facing=south powered=false
|
|
||||||
p 0 0 1 observer facing=east powered=false
|
|
||||||
p 1 0 1 observer facing=north powered=false
|
|
||||||
p 1 0 0 observer facing=west powered=false
|
|
||||||
t 0
|
|
||||||
p 0 0 1 powered=false
|
|
||||||
p 0 0 0 powered=true
|
|
||||||
t 2
|
|
||||||
p 0 0 0 powered=false
|
|
||||||
p 1 0 0 powered=true
|
|
||||||
t 4
|
|
||||||
p 1 0 0 powered=false
|
|
||||||
p 1 0 1 powered=true
|
|
||||||
t 6
|
|
||||||
p 1 0 1 powered=false
|
|
||||||
p 0 0 1 powered=true
|
|
||||||
t 8
|
|
||||||
p 0 0 0
|
|
||||||
`);
|
|
||||||
const f_pulses = new WorldFrame(w_pulses);
|
|
||||||
const d_pulses = new Diorama('demo-pulses', f_pulses, () => engine.requestRender());
|
|
||||||
d_pulses.radius = 4;
|
|
||||||
d_pulses.centerView()
|
|
||||||
d_pulses.saveState()
|
|
||||||
engine.addDiorama(d_pulses);
|
|
||||||
const c_pulses = new SpacetimeController(d_pulses, {
|
|
||||||
timeline: false,
|
|
||||||
subticks: false,
|
|
||||||
loop: true,
|
|
||||||
autoplay: true,
|
|
||||||
pan: false,
|
|
||||||
zoom: false,
|
|
||||||
orbit: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const w_piston = new World(`
|
|
||||||
p 0 0 0 redstone_wire power=0 east=side west=none north=none south=none
|
|
||||||
p 1 0 0 repeater facing=west powered=false locked=false delay=2
|
|
||||||
p 2 0 0 sticky_piston facing=east extended=false
|
|
||||||
p 2 -1 0 piston facing=east extended=false
|
|
||||||
p 3 0 0 slime_block
|
|
||||||
p 4 0 0 slime_block
|
|
||||||
t 2
|
|
||||||
p 0 0 0 power=15
|
|
||||||
t 6
|
|
||||||
p 1 0 0 powered=true
|
|
||||||
l begin-extend
|
|
||||||
t 7
|
|
||||||
p 2 0 0 extended=true
|
|
||||||
p 4.0 0 0 air &
|
|
||||||
p 4.5 0 0 slime_block
|
|
||||||
p 3.0 0 0 air &
|
|
||||||
p 3.5 0 0 slime_block
|
|
||||||
p 2.5 0 0 piston_head facing=east short=true type=sticky
|
|
||||||
p 2 -1 0 extended=true
|
|
||||||
p 2.5 -1 0 piston_head facing=east short=true type=normal
|
|
||||||
t 8
|
|
||||||
p 4.5 0 0 air &
|
|
||||||
p 5.0 0 0 slime_block
|
|
||||||
p 3.5 0 0 air &
|
|
||||||
p 4.0 0 0 slime_block
|
|
||||||
p 2.5 0 0 air &
|
|
||||||
p 3.0 0 0 piston_head facing=east short=false type=sticky
|
|
||||||
p 2.5 -1 0 air &
|
|
||||||
p 3.0 -1 0 piston_head facing=east short=false type=normal
|
|
||||||
l end-extend
|
|
||||||
t 12
|
|
||||||
p 0 0 0 power=0
|
|
||||||
t 16
|
|
||||||
p 1 0 0 powered=false
|
|
||||||
l begin-retract
|
|
||||||
t 17
|
|
||||||
p 3 0 0 air &
|
|
||||||
p 2.5 0 0 piston_head facing=east short=true type=sticky
|
|
||||||
p 4.0 0 0 air &
|
|
||||||
p 3.5 0 0 slime_block
|
|
||||||
p 5.0 0 0 air &
|
|
||||||
p 4.5 0 0 slime_block
|
|
||||||
p 3.0 -1 0 air &
|
|
||||||
p 2.5 -1 0 piston_head facing=east short=true type=normal
|
|
||||||
t 18
|
|
||||||
p 2.5 0 0 air
|
|
||||||
p 3.5 0 0 air &
|
|
||||||
p 3.0 0 0 slime_block
|
|
||||||
p 4.5 0 0 air &
|
|
||||||
p 4.0 0 0 slime_block
|
|
||||||
p 2 0 0 extended=false
|
|
||||||
p 2.5 -1 0 air
|
|
||||||
p 2 -1 0 extended=false
|
|
||||||
l end-retract
|
|
||||||
`);
|
|
||||||
const f_piston_t = new WorldFrame(w_piston);
|
|
||||||
const d_piston_t = new Diorama('demo-piston', f_piston_t, () => engine.requestRender());
|
|
||||||
d_piston_t.radius = 4;
|
|
||||||
d_piston_t.centerView();
|
|
||||||
d_piston_t.saveState();
|
|
||||||
engine.addDiorama(d_piston_t);
|
|
||||||
const c_piston = new SpacetimeController(d_piston_t, {
|
|
||||||
loop: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Fetch and build everything concurrently!
|
|
||||||
await engine.updateAll();
|
|
||||||
engine.requestRender();
|
|
||||||
|
|
||||||
|
|
||||||
// const factor = 2.5;
|
|
||||||
// const update_time = () => {
|
|
||||||
// for (let i = 0; i < 20; i++) {
|
|
||||||
// setTimeout(() => f_piston_t.seek(i), i * factor * 50)
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// setInterval(update_time, factor * 1000)
|
|
||||||
// update_time();
|
|
||||||
//
|
|
||||||
// let i = 0;
|
|
||||||
// let n = w_piston.eventCount;
|
|
||||||
// const update_state = () => {
|
|
||||||
// f_piston_s.seekIndex(i);
|
|
||||||
// i = (i + 1) % n;
|
|
||||||
// };
|
|
||||||
// setInterval(update_state, 250);
|
|
||||||
// update_state()
|
|
||||||
//
|
|
||||||
// const update_pulses = () => {
|
|
||||||
// for (let i = 0; i < 8; i++) {
|
|
||||||
// setTimeout(() => f_pulses.seek(i), i * factor * 50)
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// setInterval(update_pulses, factor * 8 * 50)
|
|
||||||
// update_pulses();
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Renderer Initialization Failed:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initDocument();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// autoplayer.js
|
|
||||||
|
|
||||||
export class AutoPlayer {
|
|
||||||
constructor(diorama, speed = 1.0) {
|
|
||||||
this.frame = diorama.frame;
|
|
||||||
this.speed = speed;
|
|
||||||
|
|
||||||
this.lastFrameTime = performance.now();
|
|
||||||
this.playheadTime = 0;
|
|
||||||
this.isRunning = true;
|
|
||||||
|
|
||||||
this._loop = this.loop.bind(this);
|
|
||||||
requestAnimationFrame(this._loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
getMaxTime() {
|
|
||||||
const evs = this.frame.world.events;
|
|
||||||
return evs.length > 0 ? evs[evs.length - 1].time : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
loop(time) {
|
|
||||||
if (!this.isRunning) return;
|
|
||||||
|
|
||||||
const dt = (time - this.lastFrameTime) / 1000;
|
|
||||||
this.lastFrameTime = time;
|
|
||||||
const maxTime = this.getMaxTime();
|
|
||||||
|
|
||||||
// Only animate if there are actually forward events in the timeline
|
|
||||||
if (maxTime > 0) {
|
|
||||||
this.playheadTime += dt * 20 * this.speed;
|
|
||||||
|
|
||||||
// Loop wrap-around
|
|
||||||
if (this.playheadTime >= maxTime) {
|
|
||||||
this.playheadTime = this.playheadTime % maxTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.frame.seek(this.playheadTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(this._loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this if you ever need to destroy the diorama and stop the memory leak
|
|
||||||
stop() {
|
|
||||||
this.isRunning = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
194
src/components.css
Normal file
194
src/components.css
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
/* --- Component structural styles --- */
|
||||||
|
mc-world, mc-timeline, mc-camera, mc-frame {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
mc-diorama {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mc-diorama canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- UI Controls styles --- */
|
||||||
|
.spacetime-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-btn {
|
||||||
|
pointer-events: auto;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
color: white;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
cursor: pointer;
|
||||||
|
display: none;
|
||||||
|
font-family: sans-serif;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-btn:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.visualizer-ui {
|
||||||
|
pointer-events: auto;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background: rgba(20, 20, 20, 0.85);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
color: white;
|
||||||
|
user-select: none;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrubber-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 4ch;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-inner {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-bg {
|
||||||
|
position: absolute;
|
||||||
|
left: -10px;
|
||||||
|
right: -10px;
|
||||||
|
top: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-fill {
|
||||||
|
position: absolute;
|
||||||
|
left: -10px;
|
||||||
|
top: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #f00;
|
||||||
|
width: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-handle {
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
top: 12px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 3;
|
||||||
|
box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
height: 8px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-event {
|
||||||
|
width: 2px;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-label {
|
||||||
|
width: 4px;
|
||||||
|
background: gold;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Hover Visibility Logic --- */
|
||||||
|
.spacetime-hover-reveal {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacetime-container:hover .spacetime-hover-reveal {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacetime-indicator {
|
||||||
|
position: absolute;
|
||||||
|
left: 33px;
|
||||||
|
bottom: 29px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacetime-container:hover .spacetime-indicator {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
133
src/components.js
Normal file
133
src/components.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// components.js
|
||||||
|
import {Engine} from './engine.js';
|
||||||
|
import {World, WorldFrame} from './world.js';
|
||||||
|
import {Diorama} from './diorama.js';
|
||||||
|
import {SpacetimeController} from './controller.js';
|
||||||
|
|
||||||
|
// Global Engine Singleton
|
||||||
|
let engineInstance = null;
|
||||||
|
|
||||||
|
function getEngine() {
|
||||||
|
if (!engineInstance) {
|
||||||
|
engineInstance = new Engine();
|
||||||
|
document.body.appendChild(engineInstance.canvas)
|
||||||
|
}
|
||||||
|
return engineInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MCWorldElement extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
if (this.world) return;
|
||||||
|
// Read text content directly, allowing inline scripting
|
||||||
|
const script = this.textContent || '';
|
||||||
|
this.world = new World(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MCFrameElement extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
// Use a microtask to ensure children are parsed
|
||||||
|
setTimeout(() => this.init(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (this.frame) return;
|
||||||
|
|
||||||
|
let world;
|
||||||
|
const worldId = this.getAttribute('worldid');
|
||||||
|
|
||||||
|
if (worldId) {
|
||||||
|
world = document.getElementById(worldId)?.world;
|
||||||
|
} else {
|
||||||
|
const worldEl = this.querySelector('mc-world');
|
||||||
|
world = worldEl?.world;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world) {
|
||||||
|
this.frame = new WorldFrame(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MCDioramaElement extends HTMLElement {
|
||||||
|
connectedCallback() {
|
||||||
|
setTimeout(() => this.init(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
const engine = getEngine();
|
||||||
|
let frame;
|
||||||
|
|
||||||
|
// 1. Resolve the Frame context
|
||||||
|
const frameId = this.getAttribute('frameid');
|
||||||
|
const worldId = this.getAttribute('worldid');
|
||||||
|
|
||||||
|
if (frameId) {
|
||||||
|
frame = document.getElementById(frameId)?.frame;
|
||||||
|
} else if (worldId) {
|
||||||
|
const world = document.getElementById(worldId)?.world;
|
||||||
|
if (world) frame = new WorldFrame(world);
|
||||||
|
} else {
|
||||||
|
const frameEl = this.querySelector('mc-frame');
|
||||||
|
if (frameEl) {
|
||||||
|
frame = frameEl.frame;
|
||||||
|
} else {
|
||||||
|
const worldEl = this.querySelector('mc-world');
|
||||||
|
if (worldEl) frame = new WorldFrame(worldEl.world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frame) {
|
||||||
|
console.error("mc-diorama requires a resolved frame or world.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Build the Diorama
|
||||||
|
const diorama = new Diorama(this, frame, () => engine.requestRender());
|
||||||
|
|
||||||
|
// Process spatial attributes
|
||||||
|
if (this.hasAttribute('radius')) diorama.radius = parseFloat(this.getAttribute('radius'));
|
||||||
|
if (this.hasAttribute('theta')) diorama.theta = parseFloat(this.getAttribute('theta'));
|
||||||
|
if (this.hasAttribute('phi')) diorama.phi = parseFloat(this.getAttribute('phi'));
|
||||||
|
|
||||||
|
diorama.centerView();
|
||||||
|
diorama.saveState(); // Lock this in for the Reset button
|
||||||
|
|
||||||
|
// 3. Resolve the Controllers
|
||||||
|
const timelineEl = this.querySelector('mc-timeline');
|
||||||
|
const cameraEl = this.querySelector('mc-camera');
|
||||||
|
|
||||||
|
// Default to fully static
|
||||||
|
const opts = {
|
||||||
|
timeline: !!timelineEl,
|
||||||
|
orbit: !!cameraEl,
|
||||||
|
pan: !!cameraEl,
|
||||||
|
zoom: !!cameraEl,
|
||||||
|
autoplay: false,
|
||||||
|
loop: false,
|
||||||
|
subticks: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (timelineEl) {
|
||||||
|
if (timelineEl.hasAttribute('autoplay')) opts.autoplay = timelineEl.getAttribute('autoplay') !== 'false';
|
||||||
|
if (timelineEl.hasAttribute('loop')) opts.loop = timelineEl.getAttribute('loop') !== 'false';
|
||||||
|
if (timelineEl.hasAttribute('subticks')) opts.subticks = timelineEl.getAttribute('subticks') !== 'false';
|
||||||
|
if (timelineEl.hasAttribute('controls')) opts.timeline = timelineEl.getAttribute('controls') !== 'false';
|
||||||
|
if (timelineEl.hasAttribute('speed')) opts.speed = parseFloat(timelineEl.getAttribute('speed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cameraEl) {
|
||||||
|
if (cameraEl.hasAttribute('orbit')) opts.orbit = cameraEl.getAttribute('orbit') !== 'false';
|
||||||
|
if (cameraEl.hasAttribute('pan')) opts.pan = cameraEl.getAttribute('pan') !== 'false';
|
||||||
|
if (cameraEl.hasAttribute('zoom')) opts.zoom = cameraEl.getAttribute('zoom') !== 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.controller = new SpacetimeController(diorama, opts);
|
||||||
|
engine.addDiorama(diorama);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register Elements
|
||||||
|
customElements.define('mc-world', MCWorldElement);
|
||||||
|
customElements.define('mc-frame', MCFrameElement);
|
||||||
|
customElements.define('mc-diorama', MCDioramaElement);
|
||||||
@@ -1,37 +1,5 @@
|
|||||||
// controller.js
|
// controller.js
|
||||||
|
|
||||||
const UI_CSS = `
|
|
||||||
.spacetime-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; display: flex; flex-direction: column; justify-content: space-between; box-sizing: border-box; padding: 12px; z-index: 10; }
|
|
||||||
|
|
||||||
.reset-wrapper { display: flex; justify-content: flex-end; width: 100%; }
|
|
||||||
.reset-btn { pointer-events: auto; padding: 6px 12px; background: rgba(0,0,0,0.6); color: white; border: 1px solid rgba(255,255,255,0.3); cursor: pointer; display: none; font-family: sans-serif; border-radius: 4px; font-weight: bold; backdrop-filter: blur(4px); }
|
|
||||||
.reset-btn:hover { background: rgba(0,0,0,0.8); }
|
|
||||||
|
|
||||||
.visualizer-ui { pointer-events: auto; font-family: sans-serif; background: rgba(20,20,20,0.85); padding: 12px; border-radius: 8px; display: flex; flex-direction: column; gap: 12px; color: white; user-select: none; backdrop-filter: blur(4px); border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 4px 12px rgba(0,0,0,0.5); }
|
|
||||||
.scrubber-row { display: flex; gap: 12px; align-items: center; }
|
|
||||||
|
|
||||||
.btn { background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: white; padding: 4px 8px; border-radius: 4px; cursor: pointer; min-width: 4ch; text-align: center; font-weight: bold; transition: background 0.1s; }
|
|
||||||
.btn:hover { background: rgba(255,255,255,0.2); }
|
|
||||||
|
|
||||||
.track-container { flex-grow: 1; height: 24px; padding: 0 10px; cursor: pointer; display: flex; align-items: center; }
|
|
||||||
.track-inner { position: relative; width: 100%; height: 100%; pointer-events: none; }
|
|
||||||
|
|
||||||
.track-bg { position: absolute; left: -10px; right: -10px; top: 8px; height: 8px; background: rgba(255,255,255,0.2); border-radius: 4px; }
|
|
||||||
.track-fill { position: absolute; left: -10px; top: 8px; height: 8px; background: #f00; width: 10px; border-radius: 4px; }
|
|
||||||
.track-handle { position: absolute; width: 16px; height: 16px; background: #fff; border-radius: 50%; top: 12px; transform: translate(-50%, -50%); z-index: 3; box-shadow: 0 0 4px rgba(0,0,0,0.5); }
|
|
||||||
|
|
||||||
.marker { position: absolute; top: 8px; height: 8px; transform: translateX(-50%); pointer-events: none; z-index: 1; }
|
|
||||||
.marker-event { width: 2px; background: rgba(255,255,255,0.5); }
|
|
||||||
.marker-label { width: 4px; background: gold; z-index: 2; }
|
|
||||||
|
|
||||||
/* --- Hover Visibility Logic --- */
|
|
||||||
.spacetime-hover-reveal { opacity: 0; pointer-events: none; transition: opacity 0.2s ease; }
|
|
||||||
.spacetime-container:hover .spacetime-hover-reveal { opacity: 1; pointer-events: auto; }
|
|
||||||
|
|
||||||
.spacetime-indicator { position: absolute; left: 33px; bottom: 29px; display: flex; gap: 12px; color: rgba(255,255,255,0.4); font-family: monospace; font-size: 14px; pointer-events: none; opacity: 1; transition: opacity 0.2s ease; }
|
|
||||||
.spacetime-container:hover .spacetime-indicator { opacity: 0; }
|
|
||||||
`;
|
|
||||||
|
|
||||||
export class SpacetimeController {
|
export class SpacetimeController {
|
||||||
constructor(diorama, options = {}) {
|
constructor(diorama, options = {}) {
|
||||||
this.diorama = diorama;
|
this.diorama = diorama;
|
||||||
@@ -66,18 +34,8 @@ export class SpacetimeController {
|
|||||||
this.lastFrameTime = performance.now();
|
this.lastFrameTime = performance.now();
|
||||||
this.playbackAccumulator = 0;
|
this.playbackAccumulator = 0;
|
||||||
|
|
||||||
if (getComputedStyle(this.container).position === 'static') {
|
|
||||||
this.container.style.position = 'relative';
|
|
||||||
}
|
|
||||||
this.container.classList.add('spacetime-container');
|
this.container.classList.add('spacetime-container');
|
||||||
|
|
||||||
if (!document.getElementById('spacetime-ui-styles')) {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.id = 'spacetime-ui-styles';
|
|
||||||
style.textContent = UI_CSS;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.buildUI();
|
this.buildUI();
|
||||||
this.attachEvents();
|
this.attachEvents();
|
||||||
|
|
||||||
@@ -210,7 +168,10 @@ export class SpacetimeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachEvents() {
|
attachEvents() {
|
||||||
this.canvas.addEventListener('contextmenu', e => e.preventDefault());
|
this.canvas.addEventListener('contextmenu', e => {
|
||||||
|
if (!this.hasSpatial) return;
|
||||||
|
e.preventDefault()
|
||||||
|
});
|
||||||
|
|
||||||
// Correctly set or remove the grab cursor
|
// Correctly set or remove the grab cursor
|
||||||
this.canvas.style.cursor = this.hasSpatial ? 'grab' : 'default';
|
this.canvas.style.cursor = this.hasSpatial ? 'grab' : 'default';
|
||||||
@@ -262,7 +223,7 @@ export class SpacetimeController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.canvas.addEventListener('click', () => {
|
this.canvas.addEventListener('click', () => {
|
||||||
// hasDragged will always be false if hasSpatial is false,
|
// hasDragged will always be false if hasSpatial is false,
|
||||||
// ensuring static scenes still act as tap-to-play toggles.
|
// ensuring static scenes still act as tap-to-play toggles.
|
||||||
if (!this.hasDragged && this.opts.timeline) {
|
if (!this.hasDragged && this.opts.timeline) {
|
||||||
this.playMode = this.playMode === 'paused' ? 'realtime' : 'paused';
|
this.playMode = this.playMode === 'paused' ? 'realtime' : 'paused';
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
|
// diorama.js
|
||||||
import {mat4Ortho, mat4LookAt, mat4Multiply} from './math.js';
|
import {mat4Ortho, mat4LookAt, mat4Multiply} from './math.js';
|
||||||
|
|
||||||
export class Diorama {
|
export class Diorama {
|
||||||
constructor(elementId, frame, requestRenderCallback) {
|
constructor(element, frame, requestRenderCallback) {
|
||||||
this.element = document.getElementById(elementId);
|
this.element = element;
|
||||||
this.element.style.position = 'relative';
|
|
||||||
|
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = document.createElement('canvas');
|
||||||
this.element.appendChild(this.canvas);
|
this.element.appendChild(this.canvas);
|
||||||
this.ctx2d = this.canvas.getContext('2d');
|
this.ctx2d = this.canvas.getContext('2d');
|
||||||
this.frame = frame;
|
this.frame = frame;
|
||||||
|
|
||||||
this.dirty = true; // Start dirty to guarantee the first render
|
this.dirty = true; // Start dirty to guarantee the first render
|
||||||
this.requestEngineRender = requestRenderCallback;
|
this.requestEngineRender = requestRenderCallback;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user