?model= param, witch demo asset, Blockbench plugin: PS1 viewport toggle + one-click Open in renderang
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
38857c7fc1
commit
2a5449cc24
BIN
assets/witch.glb
Normal file
BIN
assets/witch.glb
Normal file
Binary file not shown.
120
blockbench/renderang_preview.js
Normal file
120
blockbench/renderang_preview.js
Normal file
@ -0,0 +1,120 @@
|
||||
// renderang_preview.js — Blockbench plugin: PS1-ify the viewport + one-click
|
||||
// "walk my model in renderang".
|
||||
// Install: Blockbench > File > Plugins > Load Plugin from File > pick this file.
|
||||
(function () {
|
||||
const RENDERANG_DIR = '/Users/jing/Documents/renderang';
|
||||
const RENDERANG_URL = 'http://localhost:8930';
|
||||
|
||||
let ps1On = false;
|
||||
const saved = { pixelRatios: new Map(), canvasCss: new Map() };
|
||||
|
||||
function eachPreview(fn) {
|
||||
try { (Preview.all || []).forEach(fn); } catch (e) {}
|
||||
}
|
||||
|
||||
function patchMaterials(on) {
|
||||
// Best-effort: Blockbench cube shaders are custom, but mesh/gltf-style
|
||||
// materials accept onBeforeCompile. Failures are silently fine — the
|
||||
// pixelation + nearest textures carry most of the look regardless.
|
||||
try {
|
||||
scene.traverse((obj) => {
|
||||
if (!obj.isMesh || !obj.material) return;
|
||||
const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
|
||||
mats.forEach((m) => {
|
||||
if (!m || typeof m.onBeforeCompile === 'undefined') return;
|
||||
if (on && !m.userData.__renderang) {
|
||||
m.userData.__renderang = true;
|
||||
m.onBeforeCompile = (shader) => {
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
'#include <project_vertex>',
|
||||
`#include <project_vertex>
|
||||
{
|
||||
vec2 grid = vec2(160.0, 120.0);
|
||||
vec2 ndc = gl_Position.xy / gl_Position.w;
|
||||
gl_Position.xy = (floor(ndc * grid + 0.5) / grid) * gl_Position.w;
|
||||
}`);
|
||||
};
|
||||
m.needsUpdate = true;
|
||||
} else if (!on && m.userData.__renderang) {
|
||||
delete m.userData.__renderang;
|
||||
m.onBeforeCompile = () => {};
|
||||
m.needsUpdate = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) { console.warn('renderang: material patch skipped', e); }
|
||||
}
|
||||
|
||||
function setPS1(on) {
|
||||
ps1On = on;
|
||||
eachPreview((p) => {
|
||||
try {
|
||||
const r = p.renderer;
|
||||
if (on) {
|
||||
if (!saved.pixelRatios.has(p)) saved.pixelRatios.set(p, r.getPixelRatio());
|
||||
r.setPixelRatio(0.28);
|
||||
saved.canvasCss.set(p, p.canvas.style.imageRendering);
|
||||
p.canvas.style.imageRendering = 'pixelated';
|
||||
} else {
|
||||
r.setPixelRatio(saved.pixelRatios.get(p) || window.devicePixelRatio);
|
||||
p.canvas.style.imageRendering = saved.canvasCss.get(p) || '';
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
patchMaterials(on);
|
||||
try { Canvas.updateAll(); } catch (e) {}
|
||||
Blockbench.showQuickMessage(on ? 'PS1 preview ON' : 'PS1 preview OFF', 1200);
|
||||
}
|
||||
|
||||
async function openInRenderang() {
|
||||
try {
|
||||
const codec = Codecs.gltf;
|
||||
if (!codec) throw new Error('gltf codec not found');
|
||||
const content = await codec.compile({ binary: true });
|
||||
const dest = RENDERANG_DIR + '/assets/preview.glb';
|
||||
if (typeof require === 'function') {
|
||||
require('fs').writeFileSync(dest, Buffer.from(content));
|
||||
Blockbench.showQuickMessage('sent to renderang', 1500);
|
||||
Blockbench.openLink(RENDERANG_URL + '/?model=preview.glb');
|
||||
} else {
|
||||
// web app fallback: download, user drops it into renderang/assets
|
||||
Blockbench.export({
|
||||
resource_id: 'gltf', type: 'GLB model', extensions: ['glb'],
|
||||
name: 'preview', savetype: 'binary', content,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
Blockbench.showQuickMessage('renderang export failed: ' + e.message, 3000);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
let togglePS1, sendAction;
|
||||
Plugin.register('renderang_preview', {
|
||||
title: 'renderang preview',
|
||||
author: 'MonsterRobot',
|
||||
description: 'PS1-ify the viewport, and open the current model walking in renderang.',
|
||||
icon: 'videogame_asset',
|
||||
version: '0.1.0',
|
||||
variant: 'both',
|
||||
onload() {
|
||||
togglePS1 = new Action('renderang_ps1_toggle', {
|
||||
name: 'PS1 Preview',
|
||||
icon: 'tv',
|
||||
click: () => setPS1(!ps1On),
|
||||
});
|
||||
sendAction = new Action('renderang_send', {
|
||||
name: 'Open in renderang',
|
||||
icon: 'videogame_asset',
|
||||
click: openInRenderang,
|
||||
});
|
||||
MenuBar.addAction(togglePS1, 'view');
|
||||
MenuBar.addAction(sendAction, 'file.export');
|
||||
},
|
||||
onunload() {
|
||||
if (ps1On) setPS1(false);
|
||||
togglePS1 && togglePS1.delete();
|
||||
sendAction && sendAction.delete();
|
||||
},
|
||||
});
|
||||
})();
|
||||
@ -81,7 +81,8 @@ for (let i = 0; i < 14; i++) {
|
||||
let mixer = null;
|
||||
let actions = {};
|
||||
let current = null;
|
||||
new GLTFLoader().load('assets/suit.glb', (gltf) => {
|
||||
const modelUrl = new URLSearchParams(location.search).get('model') || 'suit.glb';
|
||||
new GLTFLoader().load('assets/' + modelUrl, (gltf) => {
|
||||
const char = retrofyObject(gltf.scene);
|
||||
scene.add(char);
|
||||
mixer = new THREE.AnimationMixer(char);
|
||||
@ -94,8 +95,8 @@ new GLTFLoader().load('assets/suit.glb', (gltf) => {
|
||||
o.value = o.textContent = clip.name;
|
||||
sel.appendChild(o);
|
||||
});
|
||||
play('Walk');
|
||||
sel.value = 'Walk';
|
||||
const first = actions['Walk'] ? 'Walk' : (gltf.animations[0] ? gltf.animations[0].name : null);
|
||||
if (first) { play(first); sel.value = first; }
|
||||
sel.onchange = () => play(sel.value);
|
||||
});
|
||||
function play(name) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user