// 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 ', `#include { 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(); }, }); })();