better controls
This commit is contained in:
@@ -15,6 +15,63 @@ const EXTENSION_FOLDER = (() => {
|
||||
|
||||
console.log("[GaussianPack] Loading extension...");
|
||||
|
||||
// Build a small WASD / arrows / Shift+Space+C keypad that lights up as keys
|
||||
// are pressed (driven by KEY_STATE messages the viewer iframe forwards). Handy
|
||||
// for screen-recording navigation. Returns { el, set(code,on), reset() }.
|
||||
function makeKeyPad() {
|
||||
const codes = {};
|
||||
const wrap = document.createElement("div");
|
||||
Object.assign(wrap.style, {
|
||||
display: "flex", gap: "10px", alignItems: "center",
|
||||
padding: "4px 10px", flexShrink: "0", background: "#1a1a1a",
|
||||
borderTop: "1px solid #444", borderLeft: "1px solid #333",
|
||||
});
|
||||
const keyEl = (label, code, w = 18) => {
|
||||
const k = document.createElement("div");
|
||||
k.textContent = label;
|
||||
Object.assign(k.style, {
|
||||
minWidth: w + "px", height: "18px", lineHeight: "16px", textAlign: "center",
|
||||
fontSize: "10px", fontFamily: "monospace", color: "#aaa",
|
||||
background: "#2a2a2a", border: "1px solid #444", borderRadius: "3px",
|
||||
padding: "0 3px", boxSizing: "border-box", transition: "all 0.04s",
|
||||
});
|
||||
codes[code] = k;
|
||||
return k;
|
||||
};
|
||||
const grid = (rows) => {
|
||||
const g = document.createElement("div");
|
||||
Object.assign(g.style, { display: "grid", gridTemplateColumns: "repeat(3, 18px)", gap: "2px" });
|
||||
rows.forEach(r => r.forEach(cell => g.appendChild(cell || document.createElement("div"))));
|
||||
return g;
|
||||
};
|
||||
const wasd = grid([
|
||||
[null, keyEl("W", "KeyW"), null],
|
||||
[keyEl("A", "KeyA"), keyEl("S", "KeyS"), keyEl("D", "KeyD")],
|
||||
]);
|
||||
const arrows = grid([
|
||||
[null, keyEl("↑", "ArrowUp"), null],
|
||||
[keyEl("←", "ArrowLeft"), keyEl("↓", "ArrowDown"), keyEl("→", "ArrowRight")],
|
||||
]);
|
||||
const extra = document.createElement("div");
|
||||
Object.assign(extra.style, { display: "flex", gap: "2px", alignItems: "center" });
|
||||
const shift = keyEl("⇧", "ShiftLeft", 24);
|
||||
codes["ShiftRight"] = shift; // both shifts light the same key
|
||||
extra.appendChild(shift);
|
||||
extra.appendChild(keyEl("Spc", "Space", 26));
|
||||
extra.appendChild(keyEl("C", "KeyC"));
|
||||
wrap.append(wasd, arrows, extra);
|
||||
|
||||
const set = (code, on) => {
|
||||
const el = codes[code];
|
||||
if (!el) return;
|
||||
el.style.background = on ? "#00e0ff" : "#2a2a2a";
|
||||
el.style.color = on ? "#002b30" : "#aaa";
|
||||
el.style.borderColor = on ? "#00e0ff" : "#444";
|
||||
};
|
||||
const reset = () => Object.keys(codes).forEach(c => set(c, false));
|
||||
return { el: wrap, set, reset };
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "gaussianpack.previewgaussians",
|
||||
|
||||
@@ -302,10 +359,19 @@ app.registerExtension({
|
||||
infoPanel.style.overflow = "hidden";
|
||||
infoPanel.innerHTML = '<span style="color: #888;">Gaussian splat info will appear here after execution</span>';
|
||||
|
||||
// Add iframe + info panel + loading-bar overlay to container.
|
||||
// Footer = info panel (left, grows) + key-press indicator (right).
|
||||
const keyPad = makeKeyPad();
|
||||
infoPanel.style.flex = "1 1 auto";
|
||||
const footer = document.createElement("div");
|
||||
Object.assign(footer.style, { display: "flex", alignItems: "stretch", flexShrink: "0" });
|
||||
footer.appendChild(infoPanel);
|
||||
footer.appendChild(keyPad.el);
|
||||
this.gaussianKeyPad = keyPad;
|
||||
|
||||
// Add iframe + footer + loading-bar overlay to container.
|
||||
// loadBar last so its overlay sits above the iframe.
|
||||
container.appendChild(iframe);
|
||||
container.appendChild(infoPanel);
|
||||
container.appendChild(footer);
|
||||
container.appendChild(loadBar);
|
||||
|
||||
// --- Persistent camera-state plumbing ---------------------
|
||||
@@ -397,6 +463,12 @@ app.registerExtension({
|
||||
// node's state.
|
||||
if (event.source !== iframe.contentWindow) return;
|
||||
|
||||
// Key-press indicator: light up the footer keypad.
|
||||
if (event.data?.type === "KEY_STATE") {
|
||||
this.gaussianKeyPad?.set(event.data.code, !!event.data.down);
|
||||
return;
|
||||
}
|
||||
|
||||
// Camera-state pushes from iframe -> persist on
|
||||
// node.properties["Camera Config"] (Load3D pattern).
|
||||
if (event.data?.type === "CAMERA_STATE" && event.data.state) {
|
||||
@@ -752,8 +824,17 @@ app.registerExtension({
|
||||
infoPanel.style.cssText = "background:#1a1a1a;border-top:1px solid #444;padding:6px 12px;font:10px monospace;color:#ccc;line-height:1.3;flex-shrink:0;overflow:hidden;";
|
||||
infoPanel.innerHTML = '<span style="color:#888;">Dual view info will appear after execution</span>';
|
||||
|
||||
// Footer = info panel (left) + key-press indicator (right).
|
||||
const keyPad = makeKeyPad();
|
||||
infoPanel.style.flex = "1 1 auto";
|
||||
const footer = document.createElement("div");
|
||||
Object.assign(footer.style, { display: "flex", alignItems: "stretch", flexShrink: "0" });
|
||||
footer.appendChild(infoPanel);
|
||||
footer.appendChild(keyPad.el);
|
||||
this.gaussianKeyPad = keyPad;
|
||||
|
||||
container.appendChild(iframe);
|
||||
container.appendChild(infoPanel);
|
||||
container.appendChild(footer);
|
||||
container.appendChild(lb1.bar);
|
||||
container.appendChild(lb2.bar);
|
||||
|
||||
@@ -775,6 +856,10 @@ app.registerExtension({
|
||||
this.properties = this.properties || {};
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.source !== iframe.contentWindow) return;
|
||||
if (event.data?.type === "KEY_STATE") {
|
||||
this.gaussianKeyPad?.set(event.data.code, !!event.data.down);
|
||||
return;
|
||||
}
|
||||
if (event.data?.type === "CAMERA_STATE" && event.data.state) {
|
||||
node.properties["Camera Config"] = {
|
||||
cameraType: "perspective",
|
||||
|
||||
@@ -257,6 +257,18 @@
|
||||
const VIEWER_MODE = _urlParams.get("mode") === "spectate" ? "spectate" : "trackball";
|
||||
const SPECTATE_INITIAL_SPEED = parseFloat(_urlParams.get("speed") || "2.0");
|
||||
|
||||
// Forward navigation key state to the parent node so it can light up its
|
||||
// on-screen keypad indicator (useful for screen recordings).
|
||||
(() => {
|
||||
const _down = new Set();
|
||||
const fwd = (code, down) => {
|
||||
try { window.parent.postMessage({ type: "KEY_STATE", code, down }, "*"); } catch (_) {}
|
||||
};
|
||||
window.addEventListener("keydown", (e) => { if (!_down.has(e.code)) { _down.add(e.code); fwd(e.code, true); } });
|
||||
window.addEventListener("keyup", (e) => { if (_down.delete(e.code)) fwd(e.code, false); });
|
||||
window.addEventListener("blur", () => { _down.forEach(c => fwd(c, false)); _down.clear(); });
|
||||
})();
|
||||
|
||||
// ============================================================
|
||||
// SpectateControls — TrackballControls-compatible API for fly-cam.
|
||||
//
|
||||
@@ -390,6 +402,9 @@
|
||||
|
||||
this._scratchFwd.set(0, 0, -1).applyQuaternion(this.camera.quaternion);
|
||||
this._scratchRight.set(1, 0, 0).applyQuaternion(this.camera.quaternion);
|
||||
// Up/down is relative to the camera's CURRENT orientation, not a
|
||||
// fixed world axis, so Space/C follow where you're looking now.
|
||||
this._scratchUp.set(0, 1, 0).applyQuaternion(this.camera.quaternion);
|
||||
|
||||
this._scratchMove.set(0, 0, 0);
|
||||
if (this._keys.has("KeyW")) this._scratchMove.add(this._scratchFwd);
|
||||
@@ -948,18 +963,21 @@
|
||||
|
||||
const fwd = new pc.Vec3();
|
||||
const right = new pc.Vec3();
|
||||
const up = new pc.Vec3();
|
||||
cam.getWorldTransform().getZ(fwd);
|
||||
cam.getWorldTransform().getX(right);
|
||||
cam.getWorldTransform().getY(up); // camera's CURRENT up
|
||||
fwd.mulScalar(-1);
|
||||
|
||||
// WASD: move camera
|
||||
// WASD: move camera. Space/C use the camera's current up axis, so
|
||||
// up/down is relative to where you're looking now, not world Y.
|
||||
let mx = 0, my = 0, mz = 0;
|
||||
if (this._keys.has("KeyW")) { mx += fwd.x; my += fwd.y; mz += fwd.z; }
|
||||
if (this._keys.has("KeyS")) { mx -= fwd.x; my -= fwd.y; mz -= fwd.z; }
|
||||
if (this._keys.has("KeyD")) { mx += right.x; my += right.y; mz += right.z; }
|
||||
if (this._keys.has("KeyA")) { mx -= right.x; my -= right.y; mz -= right.z; }
|
||||
if (this._keys.has("Space")) { my += 1; }
|
||||
if (this._keys.has("KeyC")) { my -= 1; }
|
||||
if (this._keys.has("Space")) { mx += up.x; my += up.y; mz += up.z; }
|
||||
if (this._keys.has("KeyC")) { mx -= up.x; my -= up.y; mz -= up.z; }
|
||||
|
||||
const lenSq = mx*mx + my*my + mz*mz;
|
||||
if (lenSq > 0) {
|
||||
|
||||
@@ -111,6 +111,18 @@ const LAYOUT = params.get("layout") || "side_by_side";
|
||||
const MODE = params.get("mode") === "spectate" ? "spectate" : "orbit";
|
||||
const SPECTATE_INITIAL_SPEED = parseFloat(params.get("speed") || "2.0");
|
||||
|
||||
// Forward navigation key state to the parent node so it can light up its
|
||||
// on-screen keypad indicator (useful for screen recordings).
|
||||
(() => {
|
||||
const _down = new Set();
|
||||
const fwd = (code, down) => {
|
||||
try { window.parent.postMessage({ type: "KEY_STATE", code, down }, "*"); } catch (_) {}
|
||||
};
|
||||
window.addEventListener("keydown", (e) => { if (!_down.has(e.code)) { _down.add(e.code); fwd(e.code, true); } });
|
||||
window.addEventListener("keyup", (e) => { if (_down.delete(e.code)) fwd(e.code, false); });
|
||||
window.addEventListener("blur", () => { _down.forEach(c => fwd(c, false)); _down.clear(); });
|
||||
})();
|
||||
|
||||
const container = document.getElementById('viewerContainer');
|
||||
const vp1 = document.getElementById('vp1');
|
||||
const vp2 = document.getElementById('vp2');
|
||||
@@ -420,6 +432,9 @@ class SpectateControls {
|
||||
|
||||
this._fwd.set(0, 0, -1).applyQuaternion(this.camera.quaternion);
|
||||
this._right.set(1, 0, 0).applyQuaternion(this.camera.quaternion);
|
||||
// Up/down relative to the camera's CURRENT orientation, not a fixed
|
||||
// world axis, so Space/C follow where you're looking now.
|
||||
this._up.set(0, 1, 0).applyQuaternion(this.camera.quaternion);
|
||||
this._move.set(0, 0, 0);
|
||||
if (this._keys.has("KeyW")) this._move.add(this._fwd);
|
||||
if (this._keys.has("KeyS")) this._move.sub(this._fwd);
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"id":"6662828f-82cb-4faa-8977-cd10a1b9d52b","revision":0,"last_node_id":2,"last_link_id":1,"nodes":[{"id":1,"type":"LoadPLY","pos":[-143.84952498754797,190.94188881558176],"size":[270,58],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"ply_file","name":"ply_file","type":"COMBO","widget":{"name":"ply_file"},"link":null}],"outputs":[{"localized_name":"ply_path","name":"ply_path","type":"STRING","links":[1]}],"properties":{"cnr_id":"ComfyUI-GaussianPack","ver":"aba5d58066450088421d87a680e57ae599d92a1b","Node name for S&R":"LoadPLY"},"widgets_values":["compressed_room.ply"]},{"id":2,"type":"PreviewGaussians","pos":[164.8117698509801,184.28127124793232],"size":[512,714],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"ply_path","name":"ply_path","type":"STRING","link":1},{"localized_name":"camera_mode","name":"camera_mode","type":"COMBO","widget":{"name":"camera_mode"},"link":null},{"localized_name":"fov_degrees","name":"fov_degrees","type":"FLOAT","widget":{"name":"fov_degrees"},"link":null},{"localized_name":"renderer","name":"renderer","type":"COMBO","widget":{"name":"renderer"},"link":null},{"localized_name":"transport_format","name":"transport_format","type":"COMBO","widget":{"name":"transport_format"},"link":null}],"outputs":[],"properties":{"cnr_id":"ComfyUI-GaussianPack","ver":"aba5d58066450088421d87a680e57ae599d92a1b","Node name for S&R":"PreviewGaussians","Widget Values":{"camera_mode":"spectate","fov_degrees":70,"renderer":"playcanvas","transport_format":"ply"},"Camera Config":{"cameraType":"perspective","fov":50,"state":"{\"v\":1,\"pos\":[0.5147809982299805,-0.3441455066204071,-0.2638316750526428],\"target\":[0,0,1],\"fov\":50,\"scale\":20,\"opacity\":1}"}},"widgets_values":["spectate",70,"playcanvas","ply",""]}],"links":[[1,1,0,2,0,"STRING"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.7970122433967322,"offset":[526.1427860582855,186.55364205821778]}},"version":0.4}
|
||||
{"id":"6662828f-82cb-4faa-8977-cd10a1b9d52b","revision":0,"last_node_id":2,"last_link_id":1,"nodes":[{"id":1,"type":"LoadPLY","pos":[-143.84952498754797,190.94188881558176],"size":[270,58],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"ply_file","name":"ply_file","type":"COMBO","widget":{"name":"ply_file"},"link":null}],"outputs":[{"localized_name":"ply_path","name":"ply_path","type":"STRING","links":[1]}],"properties":{"cnr_id":"ComfyUI-GaussianPack","ver":"aba5d58066450088421d87a680e57ae599d92a1b","Node name for S&R":"LoadPLY"},"widgets_values":["compressed_room.ply"]},{"id":2,"type":"PreviewGaussians","pos":[164.8117698509801,184.28127124793232],"size":[807.3472077244259,714],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"ply_path","name":"ply_path","type":"STRING","link":1},{"localized_name":"camera_mode","name":"camera_mode","type":"COMBO","widget":{"name":"camera_mode"},"link":null},{"localized_name":"fov_degrees","name":"fov_degrees","type":"FLOAT","widget":{"name":"fov_degrees"},"link":null},{"localized_name":"renderer","name":"renderer","type":"COMBO","widget":{"name":"renderer"},"link":null},{"localized_name":"transport_format","name":"transport_format","type":"COMBO","widget":{"name":"transport_format"},"link":null}],"outputs":[],"properties":{"cnr_id":"ComfyUI-GaussianPack","ver":"aba5d58066450088421d87a680e57ae599d92a1b","Node name for S&R":"PreviewGaussians","Widget Values":{"camera_mode":"spectate","fov_degrees":70,"renderer":"playcanvas","transport_format":"ply"},"Camera Config":{"cameraType":"perspective","fov":50,"state":"{\"v\":1,\"pos\":[-2.3301117420196533,0.8196620941162109,1.912135362625122],\"target\":[0,0,1],\"fov\":50,\"scale\":20,\"opacity\":1}"}},"widgets_values":["spectate",70,"playcanvas","ply",""]}],"links":[[1,1,0,2,0,"STRING"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.9404450261780105,"offset":[314.09923160447045,0.2480432962569592]}},"version":0.4}
|
||||
Reference in New Issue
Block a user