292 lines
5.5 KiB
HTML
292 lines
5.5 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8" />
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|||
|
|
<title>Live Snap Drag Window with Canvas</title>
|
|||
|
|
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
|
|||
|
|
<div class="top-panel">
|
|||
|
|
|
|||
|
|
<button id="graphExplorer">Graph Explorer</button>
|
|||
|
|
|
|||
|
|
<button id="resetParticlesButton">Reset Particles</button>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<div id="wireMenu" style="
|
|||
|
|
position: absolute;
|
|||
|
|
display: none;
|
|||
|
|
background: #333;
|
|||
|
|
color: #eee;
|
|||
|
|
padding: 8px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
font-family: Arial, sans-serif;
|
|||
|
|
font-size: 14px;
|
|||
|
|
z-index: 1000;
|
|||
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.8);
|
|||
|
|
">
|
|||
|
|
<button id="deleteWireBtn" style="background:#900; color:#fff; border:none; padding:4px 8px; cursor:pointer;">Delete Wire</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Node context menu -->
|
|||
|
|
<div id="nodeMenu" style="
|
|||
|
|
position: absolute;
|
|||
|
|
display: none;
|
|||
|
|
background: #333;
|
|||
|
|
color: #eee;
|
|||
|
|
padding: 8px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
font-family: Arial, sans-serif;
|
|||
|
|
font-size: 14px;
|
|||
|
|
z-index: 1000;
|
|||
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.8);
|
|||
|
|
">
|
|||
|
|
<button id="deleteNodeBtn" style="background:#900; color:#fff; border:none; padding:4px 8px; cursor:pointer;">Delete Node</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<link rel="stylesheet" href="./style/main.css" >
|
|||
|
|
<script type="module">
|
|||
|
|
function autoLayoutNodes( graphExplorer ) {
|
|||
|
|
|
|||
|
|
const placedNodes = new Set();
|
|||
|
|
const visited = new Set();
|
|||
|
|
const levels = new Map();
|
|||
|
|
|
|||
|
|
function dfs( node, level ) {
|
|||
|
|
|
|||
|
|
if ( visited.has( node ) ) return;
|
|||
|
|
|
|||
|
|
visited.add( node );
|
|||
|
|
|
|||
|
|
// Assign the current level (depth) if it’s deeper
|
|||
|
|
if ( !levels.has( node ) || levels.get( node ) < level ) {
|
|||
|
|
|
|||
|
|
levels.set( node, level );
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Traverse all outgoing edges
|
|||
|
|
for ( var i = 0; i < graphExplorer.edges.length; i++ ) {
|
|||
|
|
|
|||
|
|
const edge = graphExplorer.edges[i];
|
|||
|
|
|
|||
|
|
if ( edge.fromNode === node ) {
|
|||
|
|
|
|||
|
|
dfs( edge.toNode, level + 1 );
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Start from nodes with no incoming edges
|
|||
|
|
for ( var i = 0; i < graphExplorer.nodes.length; i++ ) {
|
|||
|
|
|
|||
|
|
const node = graphExplorer.nodes[i];
|
|||
|
|
|
|||
|
|
const hasIncoming = graphExplorer.edges.some( edge => edge.toNode === node );
|
|||
|
|
|
|||
|
|
if ( !hasIncoming ) {
|
|||
|
|
|
|||
|
|
dfs( node, 0 );
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Group nodes by level
|
|||
|
|
const layers = {};
|
|||
|
|
|
|||
|
|
for ( const [node, level] of levels ) {
|
|||
|
|
|
|||
|
|
if ( !layers[level] ) layers[level] = [];
|
|||
|
|
|
|||
|
|
layers[level].push( node );
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const xSpacing = 400;
|
|||
|
|
const yBase = 150;
|
|||
|
|
|
|||
|
|
for ( const levelEntry of Object.entries( layers ) ) {
|
|||
|
|
|
|||
|
|
const levelStr = levelEntry[0];
|
|||
|
|
const nodesAtLevel = levelEntry[1];
|
|||
|
|
const level = Number( levelStr );
|
|||
|
|
|
|||
|
|
const spacing = yBase + Math.max(...nodesAtLevel.map(n => n.inputs.length)) * 25;
|
|||
|
|
|
|||
|
|
for ( let i = 0; i < nodesAtLevel.length; i++ ) {
|
|||
|
|
|
|||
|
|
const node = nodesAtLevel[i];
|
|||
|
|
|
|||
|
|
const xJitter = ( Math.random() - 0.5 ) * 80; // wider jitter horizontally
|
|||
|
|
const yJitter = ( Math.random() - 0.5 ) * 80; // wider jitter vertically
|
|||
|
|
|
|||
|
|
node.x = level * xSpacing + xJitter;
|
|||
|
|
node.y = i * spacing + yJitter;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadJSON( pathName ) {
|
|||
|
|
|
|||
|
|
const response = await fetch( pathName );
|
|||
|
|
|
|||
|
|
if ( !response.ok ) {
|
|||
|
|
|
|||
|
|
throw new Error( `Failed to load shader: ${ pathName }` );
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return await response.json();
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getShaderByName( shaders, shaderName ) {
|
|||
|
|
|
|||
|
|
for (var i = 0; i < shaders.length; i++) {
|
|||
|
|
|
|||
|
|
var shader = shaders[i]
|
|||
|
|
|
|||
|
|
if( shader.name == shaderName ) {
|
|||
|
|
|
|||
|
|
return shader;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getPortIndex( bindings, variableName ) {
|
|||
|
|
|
|||
|
|
for (var i = 0; i < bindings.length; i++) {
|
|||
|
|
|
|||
|
|
var binding = bindings[i]
|
|||
|
|
|
|||
|
|
if( binding.varName == variableName ) {
|
|||
|
|
|
|||
|
|
return i;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
import { Node } from "./GraphExplorer.js"
|
|||
|
|
|
|||
|
|
import { Edge } from "./GraphExplorer.js"
|
|||
|
|
|
|||
|
|
import { GraphExplorer } from "./GraphExplorer.js"
|
|||
|
|
|
|||
|
|
import { WindowController } from "./WindowController.js"
|
|||
|
|
|
|||
|
|
|
|||
|
|
var graphExplorer = new GraphExplorer();
|
|||
|
|
|
|||
|
|
const windowController = new WindowController();
|
|||
|
|
|
|||
|
|
windowController.setGraphExplorer( graphExplorer );
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
var graphCanvas = document.createElement( "canvas" );
|
|||
|
|
|
|||
|
|
graphCanvas.id = "graphCanvas";
|
|||
|
|
|
|||
|
|
graphCanvas.width = windowController.startWidth;
|
|||
|
|
graphCanvas.height = windowController.startHeight;
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
console.log("set element", graphCanvas);
|
|||
|
|
|
|||
|
|
await windowController.setElement( graphCanvas );
|
|||
|
|
|
|||
|
|
await windowController.setup();
|
|||
|
|
|
|||
|
|
|
|||
|
|
windowController.maximizeWindow();
|
|||
|
|
|
|||
|
|
var nodes = await loadJSON("./nodes.json");
|
|||
|
|
|
|||
|
|
console.log(nodes);
|
|||
|
|
|
|||
|
|
var shaders = nodes.shaders;
|
|||
|
|
|
|||
|
|
for (var i = 0; i < shaders.length; i++) {
|
|||
|
|
|
|||
|
|
var shader = shaders[i]
|
|||
|
|
|
|||
|
|
var bindings = shader.bindings;
|
|||
|
|
|
|||
|
|
var newNode = new Node(shader.name, 50 + 240 * i, 100, [], []);
|
|||
|
|
|
|||
|
|
for (var j = 0; j < bindings.length; j++) {
|
|||
|
|
|
|||
|
|
var binding = bindings[j]
|
|||
|
|
|
|||
|
|
var varName = binding.varName;
|
|||
|
|
|
|||
|
|
newNode.inputs[j] = varName;
|
|||
|
|
|
|||
|
|
newNode.outputs[j] = varName;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
shader.node = newNode;
|
|||
|
|
|
|||
|
|
graphExplorer.addNode( newNode );
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
var edges = nodes.edges;
|
|||
|
|
|
|||
|
|
for (var i = 0; i < edges.length; i++) {
|
|||
|
|
|
|||
|
|
var edge = edges[i]
|
|||
|
|
|
|||
|
|
var fromShader = getShaderByName( shaders, edge.from );
|
|||
|
|
|
|||
|
|
var toShader = getShaderByName( shaders, edge.to );
|
|||
|
|
|
|||
|
|
var fromPortIndex = getPortIndex( fromShader.bindings, edge.fromBuffer );
|
|||
|
|
|
|||
|
|
var toPortIndex = getPortIndex( toShader.bindings, edge.buffer );
|
|||
|
|
|
|||
|
|
var edge = new Edge( fromShader.node, fromPortIndex, toShader.node, toPortIndex );
|
|||
|
|
|
|||
|
|
graphExplorer.addEdge( edge );
|
|||
|
|
|
|||
|
|
//console.log(edge, shaders, edge.from, fromShader);
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
autoLayoutNodes( graphExplorer );
|
|||
|
|
|
|||
|
|
graphExplorer.setup( graphCanvas );
|
|||
|
|
graphExplorer.inertiaLoop();
|
|||
|
|
graphExplorer.draw();
|
|||
|
|
|
|||
|
|
|
|||
|
|
document.querySelector("#graphExplorer").addEventListener("click", function () {
|
|||
|
|
|
|||
|
|
windowController.show();
|
|||
|
|
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
|
|||
|
|
windowController.resizeCanvas();
|
|||
|
|
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
|
|||
|
|
</body>
|
|||
|
|
</html>
|