From 17ce5ef2facc9dd39183cf460f7ad1cb2db097ab Mon Sep 17 00:00:00 2001 From: kaj dijkstra Date: Thu, 25 Dec 2025 10:36:24 +0100 Subject: [PATCH] First commit --- GraphExplorer.js | 778 +++++++++++++++++++++++++++++++++++++++ WindowController.js | 518 ++++++++++++++++++++++++++ extract_nodes.js | 238 ++++++++++++ index.html | 291 +++++++++++++++ nodes.json | 876 ++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 250 +++++++++++++ package.json | 10 + 7 files changed, 2961 insertions(+) create mode 100644 GraphExplorer.js create mode 100644 WindowController.js create mode 100644 extract_nodes.js create mode 100644 index.html create mode 100644 nodes.json create mode 100644 package-lock.json create mode 100644 package.json diff --git a/GraphExplorer.js b/GraphExplorer.js new file mode 100644 index 0000000..0de0a89 --- /dev/null +++ b/GraphExplorer.js @@ -0,0 +1,778 @@ + + +if( typeof document != "undefined" ) { + + document.nodes = { }; + +} + + +const colors = { + background: "#222", + gridLine: "#444", + connectionWire: "#00aaff", + nodeBackground: "#2d2d2d", + nodeBorder: "#777", + nodeNameBackground: "#2d2d2d", + nodeNameText: "#fff", + inputPortFill: "#aaa", + outputPortFill: "#00aaff", +}; + +export class Node { + name = ""; + + x = 0; + + y = 0; + + inputs = new Array(); + + outputs = new Array(); + + width = 0; + + cornerRadius = 10; + + constructor(name, x, y, inputs, outputs) { + this.name = name; + this.x = x; + this.y = y; + this.inputs = inputs; + this.outputs = outputs; + } + + getHeight() { + return 40 + Math.max(this.inputs.length, this.outputs.length) * 20; + } + + updateWidth(ctx) { + + const textWidth = ctx.measureText(this.name).width; + + this.width = Math.max(200, textWidth + 40); + + } + + roundRect(ctx, x, y, w, h, r) { + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.lineTo(x + w - r, y); + ctx.quadraticCurveTo(x + w, y, x + w, y + r); + ctx.lineTo(x + w, y + h - r); + ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); + ctx.lineTo(x + r, y + h); + ctx.quadraticCurveTo(x, y + h, x, y + h - r); + ctx.lineTo(x, y + r); + ctx.quadraticCurveTo(x, y, x + r, y); + ctx.closePath(); + } + + draw( ctx ) { + + const nodeHeight = this.getHeight(); + + if( typeof document != "undefined" ) { + + document.nodes[ this.name ] = { x:this.x, y: this.y }; + + } + + this.roundRect(ctx, this.x, this.y, this.width, nodeHeight, this.cornerRadius); + ctx.fillStyle = colors.nodeBackground; + ctx.fill(); + + ctx.strokeStyle = colors.nodeBorder; + ctx.lineWidth = 2; + this.roundRect(ctx, this.x, this.y, this.width, nodeHeight, this.cornerRadius); + ctx.stroke(); + + ctx.fillStyle = colors.nodeNameText; + ctx.fillText(this.name, this.x + 20, this.y + 20); + + ctx.fillStyle = colors.inputPortFill; + for (let i = 0; i < this.inputs.length; i++) { + const pos = this.getInputPos(i); + ctx.beginPath(); + ctx.arc(pos.x, pos.y, 6, 0, Math.PI * 2); + ctx.fill(); + ctx.fillText(this.inputs[i], pos.x + 12, pos.y); + } + + ctx.fillStyle = colors.outputPortFill; + for (let i = 0; i < this.outputs.length; i++) { + const pos = this.getOutputPos(i); + ctx.beginPath(); + ctx.arc(pos.x, pos.y, 6, 0, Math.PI * 2); + ctx.fill(); + + const label = this.outputs[i]; + const labelWidth = ctx.measureText(label).width; + ctx.fillText(label, pos.x - 12 - labelWidth, pos.y); + } + } + + getInputPos(index) { + return { x: this.x, y: this.y + 40 + index * 20 }; + } + + getOutputPos(index) { + return { x: this.x + this.width, y: this.y + 40 + index * 20 }; + } + + containsPoint(x, y) { + const nodeHeight = this.getHeight(); + return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + nodeHeight; + } +} + +export class Edge { + fromNode = null; + fromIndex = 0; + toNode = null; + toIndex = 0; + toPos = null; + + constructor(fromNode, fromIndex, toNode = null, toIndex = null, toPos = null) { + this.fromNode = fromNode; + this.fromIndex = fromIndex; + this.toNode = toNode; + this.toIndex = toIndex; + this.toPos = toPos; + } + + draw(ctx) { + const fromPos = this.fromNode.getOutputPos(this.fromIndex); + + let toPos; + + if (this.toNode !== null && this.toIndex !== null) { + toPos = this.toNode.getInputPos(this.toIndex); + } else if (this.toPos !== null) { + toPos = this.toPos; + } else { + return; + } + + const cp1 = { x: fromPos.x + 50, y: fromPos.y }; + const cp2 = { x: toPos.x - 50, y: toPos.y }; + + ctx.beginPath(); + ctx.moveTo(fromPos.x, fromPos.y); + ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, toPos.x, toPos.y); + ctx.stroke(); + } + + + distance(a, b) { + return Math.hypot(a.x - b.x, a.y - b.y); + } + + pointNear(pt, tolerance) { + const fromPos = this.fromNode.getOutputPos(this.fromIndex); + + let toPos; + + if (this.toNode !== null && this.toIndex !== null) { + toPos = this.toNode.getInputPos(this.toIndex); + } else if (this.toPos !== null) { + toPos = this.toPos; + } else { + return false; + } + + const cp1 = { x: fromPos.x + 50, y: fromPos.y }; + const cp2 = { x: toPos.x - 50, y: toPos.y }; + + const samples = 20; + for (let i = 0; i <= samples; i++) { + const t = i / samples; + + const x = + Math.pow(1 - t, 3) * fromPos.x + + 3 * Math.pow(1 - t, 2) * t * cp1.x + + 3 * (1 - t) * Math.pow(t, 2) * cp2.x + + Math.pow(t, 3) * toPos.x; + + const y = + Math.pow(1 - t, 3) * fromPos.y + + 3 * Math.pow(1 - t, 2) * t * cp1.y + + 3 * (1 - t) * Math.pow(t, 2) * cp2.y + + Math.pow(t, 3) * toPos.y; + + if (this.distance(pt, { x, y }) < tolerance) { + return true; + } + } + return false; + } +} + + +export class GraphExplorer { + + isPanning = false; + + dragNode = null; + + isDrawScheduled = false; + + dragOffsetX = 0; + + dragOffsetY = 0; + + nodes = new Array(); + + edges = new Array(); + + dragWire = null; + + lastMousePos = null; + + panX = 40; + + panY = 20; + + + panStartMouse = null; + + panStartOffsetX = 0; + + panStartOffsetY = 0; + + // Inertia variables + velocityX = 0; + + velocityY = 0; + + friction = 0.93; + + velocityThreshold = 0.1; + + + zoom = 1.; + + minZoom = 0.1; + + maxZoom = 4; + + canvas; + + context; + + requestDraw() { + + if ( this.isDrawScheduled ) { + + return; + + } + + this.isDrawScheduled = true; + + requestAnimationFrame(() => { + + this.isDrawScheduled = false; + + this.draw(); + + }); + } + + draw( clear = true ) { + + this.context.setTransform(1, 0, 0, 1, 0, 0); + + if( clear ){ + + this.context.clearRect(0, 0, this.width, this.height); + + } + + + this.context.save(); + + this.context.translate(this.panX, this.panY); + + this.context.scale(this.zoom, this.zoom); + + this.drawGrid(this.context); + + for (const node of this.nodes) { + + node.updateWidth(this.context); + + node.draw(this.context); + + } + + for (const edge of this.edges) { + + edge.draw(this.context); + + } + + this.context.restore(); + } + + screenToWorld(x, y) { + return { + x: (x - this.panX) / this.zoom, + y: (y - this.panY) / this.zoom, + }; + } + + worldToScreen(x, y) { + return { + x: x * this.zoom + this.panX, + y: y * this.zoom + this.panY, + }; + } + + getNodeAtPoint(x, y) { + + const pt = this.screenToWorld(x, y); + + for (let i = this.nodes.length - 1; i >= 0; i--) { + if ( this.nodes[i].containsPoint(pt.x, pt.y) ) { + return this.nodes[i]; + } + } + + return null; + + } + + getWireAtPoint(x, y) { + + const pt = this.screenToWorld(x, y); + + for (let i = this.edges.length - 1; i >= 0; i--) { + if (this.edges[i].pointNear(pt, 10 / this.zoom)) { + return this.edges[i]; + } + } + + return null; + + } + + drawGrid( context ) { + + const gridSpacing = 50; + + const panWorldX = -this.panX / this.zoom; + const panWorldY = -this.panY / this.zoom; + + const widthWorld = this.context.canvas.width / this.zoom; + const heightWorld = this.context.canvas.height / this.zoom; + + const startX = Math.floor(panWorldX / gridSpacing) * gridSpacing; + const endX = panWorldX + widthWorld; + + const startY = Math.floor(panWorldY / gridSpacing) * gridSpacing; + const endY = panWorldY + heightWorld; + + this.context.strokeStyle = "#282727"; + this.context.lineWidth = 1 / this.zoom; + + this.context.beginPath(); + + // Draw vertical lines within viewport + for (let x = startX; x <= endX; x += gridSpacing) { + this.context.moveTo(x, panWorldY); + this.context.lineTo(x, panWorldY + heightWorld); + } + + // Draw horizontal lines within viewport + for (let y = startY; y <= endY; y += gridSpacing) { + this.context.moveTo(panWorldX, y); + this.context.lineTo(panWorldX + widthWorld, y); + } + + this.context.stroke(); + } + + getMousePos( e ) { + + const rect = this.canvas.getBoundingClientRect(); + + return { + x: (e.clientX - rect.left), + y: (e.clientY - rect.top), + }; + + } + + + setup( canvas ) { + + this.canvas = canvas; + + this.context = canvas.getContext("2d"); + + this.canvas.addEventListener("mousedown", this.onMouseDown.bind( this ) ); + + this.canvas.addEventListener("mousemove", this.onMouseMove.bind( this ) ); + + this.canvas.addEventListener("mouseup", this.onMouseUp.bind( this ) ); + + this.canvas.addEventListener("contextmenu", this.onContextMenu.bind( this ) ); + + window.addEventListener("resize", this.onResize.bind( this ) ); + + document.addEventListener("click", this.onClick.bind( this ) ); + + document.getElementById("deleteNodeBtn").addEventListener("click", this.deleteNodeClick.bind(this) ); + + document.getElementById("deleteWireBtn").addEventListener("click", this.deleteWireButton.bind( this ) ); + + canvas.addEventListener("wheel", this.onWheel.bind( this ) ); + + } + async wait( milliseconds ) { + + return new Promise(function( resolve ) { + + setTimeout( resolve, milliseconds ); + + }); + + } + + async onResize( windowWidth, windowHeight ) { + + const devicePixelRatio = window.devicePixelRatio || 1; + + this.width = windowWidth; + this.height = windowHeight; + + this.canvas.width = this.width * devicePixelRatio; + this.canvas.height = this.height * devicePixelRatio; + + this.canvas.style.width = this.width + "px"; + this.canvas.style.height = this.height + "px"; + + this.context.setTransform( 1, 0, 0, 1, 0, 0 ); + this.context.scale( devicePixelRatio, devicePixelRatio ); + + //await this.wait(100) + + this.draw( false ); + + } + + onMouseDown( e ) { + + const pos = this.getMousePos(e); + + const worldPos = this.screenToWorld( pos.x, pos.y ); + + if (e.button === 0) { + + const node = this.getNodeAtPoint( pos.x, pos.y ); + + if (node) { + // Check if clicked on output port to start wire drag + for (let i = 0; i < node.outputs.length; i++) { + + const outputPos = node.getOutputPos(i); + + const dist = Math.hypot(worldPos.x - outputPos.x, worldPos.y - outputPos.y); + + if (dist < 10) { // 10 is port radius threshold + this.dragWire = new Edge(node, i, null, null, worldPos); + this.edges.push(this.dragWire); + this.requestDraw(); + return; // Early return to prevent node dragging start + } + } + + // Otherwise start dragging node + this.dragNode = node; + this.dragOffsetX = worldPos.x - node.x; + this.dragOffsetY = worldPos.y - node.y; + + this.velocityX = 0; + this.velocityY = 0; + + } else { + // Start panning canvas if clicked empty background + this.isPanning = true; + this.panStartMouse = { x: pos.x, y: pos.y }; + this.panStartOffsetX = this.panX; + this.panStartOffsetY = this.panY; + + this.velocityX = 0; + this.velocityY = 0; + } + } + + this.lastMousePos = pos; + + this.requestDraw(); + } + + onMouseMove( e ) { + + console.log("test"); + + const pos = this.getMousePos(e); + const worldPos = this.screenToWorld(pos.x, pos.y); + + if (this.dragWire) { + // Update wire temporary end point in world coordinates + + this.dragWire.toPos = worldPos; + + this.requestDraw(); + + } else if (this.dragNode) { + + const prevX = this.dragNode.x; + const prevY = this.dragNode.y; + + this.dragNode.x = worldPos.x - this.dragOffsetX; + this.dragNode.y = worldPos.y - this.dragOffsetY; + + this.velocityX = (this.dragNode.x - prevX) / (e.movementX || 1); + this.velocityY = (this.dragNode.y - prevY) / (e.movementY || 1); + + this.requestDraw(); + + } else if (this.isPanning) { + const dx = pos.x - this.panStartMouse.x; + const dy = pos.y - this.panStartMouse.y; + + const prevPanX = this.panX; + const prevPanY = this.panY; + + this.panX = this.panStartOffsetX + dx; + this.panY = this.panStartOffsetY + dy; + + this.velocityX = this.panX - prevPanX; + this.velocityY = this.panY - prevPanY; + + this.requestDraw(); + } + + this.lastMousePos = pos; + + } + + onMouseUp( e ) { + + if ( e.button === 0) { + + if ( this.dragWire ) { + + const pos = this.getMousePos( e ); + + const worldPos = this.screenToWorld( pos.x, pos.y ); + + // Try to attach to input port of a node + let connected = false; + + for (const node of this.nodes) { + + for (let i = 0; i < node.inputs.length; i++) { + + const inputPos = node.getInputPos( i ); + + const dist = Math.hypot( worldPos.x - inputPos.x, worldPos.y - inputPos.y ); + + if (dist < 10) { + // Attach wire end to this input port + this.dragWire.toNode = node; + this.dragWire.toIndex = i; + this.dragWire.toPos = null; + connected = true; + break; + } + } + if (connected) break; + } + + if (!connected) { + // Remove the wire if not connected + const idx = this.edges.indexOf(this.dragWire); + if (idx >= 0) this.edges.splice(idx, 1); + } + + this.dragWire = null; + + this.requestDraw(); + + } else if (this.dragNode) { + this.dragNode = null; + } else if (this.isPanning) { + this.isPanning = false; + } + + } + + } + + onContextMenu(e) { + + const wireMenu = document.getElementById("wireMenu"); + const nodeMenu = document.getElementById("nodeMenu"); + + e.preventDefault(); + + const pos = this.getMousePos( e ); + const worldPos = this.screenToWorld( pos.x, pos.y ); + + // Hide both menus by default + wireMenu.style.display = "none"; + nodeMenu.style.display = "none"; + + const node = this.getNodeAtPoint( pos.x, pos.y ); + + if (node) { + // Show node context menu + nodeMenu.style.left = e.clientX + "px"; + nodeMenu.style.top = e.clientY + "px"; + nodeMenu.style.display = "block"; + + // Store clicked node reference for later use (e.g., deletion) + nodeMenu.currentNode = node; + + } else { + const wire = this.getWireAtPoint( pos.x, pos.y ); + + if (wire) { + // Show wire context menu + wireMenu.style.left = e.clientX + "px"; + wireMenu.style.top = e.clientY + "px"; + wireMenu.style.display = "block"; + + // Store clicked wire reference for later use + wireMenu.currentWire = wire; + } + } + } + + onClick( e ) { + + const wireMenu = document.getElementById("wireMenu"); + const nodeMenu = document.getElementById("nodeMenu"); + + // Hide context menus on any click outside them + if (e.target !== nodeMenu && !nodeMenu.contains(e.target)) { + nodeMenu.style.display = "none"; + nodeMenu.currentNode = null; + } + if (e.target !== wireMenu && !wireMenu.contains(e.target)) { + wireMenu.style.display = "none"; + wireMenu.currentWire = null; + } + } + + deleteNodeClick() { + + const wireMenu = document.getElementById("wireMenu"); + const nodeMenu = document.getElementById("nodeMenu"); + + + if (nodeMenu.currentNode) { + const index = this.nodes.indexOf(nodeMenu.currentNode); + if (index !== -1) { + // Remove edges connected to this node + for (let i = this.edges.length - 1; i >= 0; i--) { + if (this.edges[i].fromNode === nodeMenu.currentNode || this.edges[i].toNode === nodeMenu.currentNode) { + this.edges.splice(i, 1); + } + } + + this.nodes.splice(index, 1); + } + + nodeMenu.style.display = "none"; + + nodeMenu.currentNode = null; + + this.requestDraw(); + } + } + + deleteWireButton() { + + const wireMenu = document.getElementById("wireMenu"); + + if (wireMenu.currentWire) { + const index = this.edges.indexOf(wireMenu.currentWire); + if (index !== -1) { + this.edges.splice(index, 1); + } + + wireMenu.style.display = "none"; + wireMenu.currentWire = null; + + this.requestDraw(); + } + + } + + onWheel( event ) { + + event.preventDefault(); + + const zoomFactor = event.deltaY < 0 ? 1.1 : 0.9; + + this.zoomAtCenter( zoomFactor, this.canvas.width, this.canvas.height); + + this.requestDraw(); + + } + + zoomAtCenter( zoomDelta, canvasWidth, canvasHeight ) { + + const oldZoom = this.zoom; + + const centerX = canvasWidth / 2; + + const centerY = canvasHeight / 2; + + // Clamp new this.zoom + this.zoom = Math.min(this.maxZoom, Math.max(this.minZoom, this.zoom * zoomDelta)); + + // Adjust pan so this.zoom happens at canvas center (keep world pos under center fixed) + this.panX = centerX - (centerX - this.panX) * (this.zoom / oldZoom); + this.panY = centerY - (centerY - this.panY) * (this.zoom / oldZoom); + + } + + inertiaLoop() { + if (!this.isPanning && !this.dragNode) { + // Apply this.friction + this.velocityX *= this.friction; + this.velocityY *= this.friction; + + this.panX += this.velocityX; + this.panY += this.velocityY; + + if (Math.abs(this.velocityX) > this.velocityThreshold || Math.abs(this.velocityY) > this.velocityThreshold) { + this.draw(); + requestAnimationFrame(this.inertiaLoop.bind(this)); + } + } + else { + requestAnimationFrame(this.inertiaLoop.bind(this)); + } + + } + + addNode( node ) { + + this.nodes.push( node ); + + } + + addEdge( edge ) { + + this.edges.push( edge ); + + } + +} diff --git a/WindowController.js b/WindowController.js new file mode 100644 index 0000000..863dab4 --- /dev/null +++ b/WindowController.js @@ -0,0 +1,518 @@ + +var orderIndex = 1000; + +export class WindowController { + + win = null; + titlebar = null; + canvas = null; + resizers = null; + btnClose = null; + btnMaximize = null; + + isSnapped = false; + isMaximized = false; + isDragging = false; + dragType = null; + currentResizer = null; + + savedPosition = null; + savedBeforeMaximize = null; + + order = orderIndex++; + + startX = 0; + startY = 0; + startLeft = 100; + startTop = 100; + startWidth = 500; + startHeight = 500; + + width = this.startWidth; + + height = this.startHeight; + + constructor() { + + + + + } + + setElement( element ) { + + this.canvas = element; + + console.log("set element",element); + + } + + create( element ) { + + var windowFrame = document.createElement( "div" ); + + windowFrame.id = "win" + + windowFrame.style.width = this.startWidth + "px"; + + windowFrame.style.height = this.startHeight + "px"; + + + windowFrame.style.left = this.startLeft + "px"; + + windowFrame.style.top = this.startTop + "px"; + + + // Title bar + var titleBar = document.createElement( "div" ); + titleBar.className = "titlebar"; + titleBar.textContent = "Source Code Graph"; + + var buttons = document.createElement( "div" ); + buttons.className = "buttons"; + + var btnMaximize = document.createElement( "button" ); + btnMaximize.id = "btnMaximize"; + btnMaximize.title = "Maximize"; + btnMaximize.textContent = "m"; + + var btnClose = document.createElement( "button" ); + btnClose.id = "btnClose"; + btnClose.title = "Close"; + btnClose.textContent = "×"; + + buttons.appendChild( btnMaximize ); + buttons.appendChild( btnClose ); + titleBar.appendChild( buttons ); + windowFrame.appendChild( titleBar ); + + // Content + var content = document.createElement( "div" ); + content.className = "content"; + + + console.log(this.canvas); + + content.appendChild( this.canvas ); + + + + windowFrame.appendChild( content ); + + // Resizers + var resizerClasses = [ + "top-left", "top", "top-right", "right", + "bottom-right", "bottom", "bottom-left", "left" + ]; + + this.resizers = new Array(); + + for (var i = 0; i < resizerClasses.length; i++) { + + var resizer = document.createElement( "div" ); + + resizer.className = "resizer " + resizerClasses[i]; + + windowFrame.appendChild( resizer ); + + this.resizers.push(resizer) + + } + + this.win = windowFrame; + this.titlebar = titleBar; + + this.btnClose = btnClose; + this.btnMaximize = btnMaximize; + + this.titlebar.addEventListener("mousedown", this.onTitlebarMouseDown.bind(this)); + + this.resizers.forEach(this.bindResizer.bind(this)); + + this.btnClose.addEventListener("click", this.onCloseClick.bind(this)); + this.btnMaximize.addEventListener("click", this.onMaximizeClick.bind(this)); + + document.body.appendChild( windowFrame ); + + this.win.addEventListener("mousedown", this.reOrder.bind( this ) ); + + } + + setup() { + + this.create(); + + } + + setGraphExplorer( graphExplorer ) { + + this.graphExplorer = graphExplorer; + + } + + saveWindowState() { + + this.savedPosition = { + left: this.win.offsetLeft, + top: this.win.offsetTop, + width: this.win.offsetWidth, + height: this.win.offsetHeight + }; + + } + + hide() { + + this.win.style.display = "none" + + } + + show() { + + this.win.style.display = "flex" + + } + + restoreWindowState() { + + if (!this.savedPosition) return; + + this.win.style.left = this.savedPosition.left + "px"; + this.win.style.top = this.savedPosition.top + "px"; + this.win.style.width = this.savedPosition.width + "px"; + this.win.style.height = this.savedPosition.height + "px"; + + this.savedPosition = null; + this.isSnapped = false; + + } + + resizeCanvas() { + + const style = getComputedStyle(this.win); + const width = parseInt(style.width); + const height = parseInt(style.height); + + //this.canvas.width = width; + //this.canvas.height = height; + //this.canvas.style.width = width + "px"; + //this.canvas.style.height = height + "px"; + + //console.log(this.graphExplorer); + + if(this.graphExplorer) + this.graphExplorer.onResize( width, height ); + //this.draw(); + + } + + + + snapWindow(mouseX, mouseY) { + + if (this.isMaximized) return; + + const snapMargin = 30; + const vw = window.innerWidth; + const vh = window.innerHeight; + + const nearLeft = mouseX <= snapMargin; + const nearRight = mouseX >= vw - snapMargin; + const nearTop = mouseY <= snapMargin; + const nearBottom = mouseY >= vh - snapMargin; + + if (!this.isSnapped && (nearLeft || nearRight || nearTop || nearBottom)) { + + this.saveWindowState(); + this.isSnapped = true; + + } else if (!nearLeft && !nearRight && !nearTop && !nearBottom && this.isSnapped) { + + this.restoreWindowState(); + return; + + } + + if (!this.isSnapped) return; + + if (nearTop && nearLeft) { + + this.setWindowRect(0, 0, vw / 2, vh / 2); + + } else if (nearTop && nearRight) { + + this.setWindowRect(vw / 2, 0, vw / 2, vh / 2); + + } else if (nearBottom && nearLeft) { + + this.setWindowRect(0, vh / 2, vw / 2, vh / 2); + + } else if (nearBottom && nearRight) { + + this.setWindowRect(vw / 2, vh / 2, vw / 2, vh / 2); + + } else if (nearTop) { + + this.setWindowRect(0, 0, vw, vh / 2); + + } else if (nearBottom) { + + this.setWindowRect(0, vh / 2, vw, vh / 2); + + } else if (nearLeft) { + + this.setWindowRect(0, 0, vw / 2, vh); + + } else if (nearRight) { + + this.setWindowRect(vw / 2, 0, vw / 2, vh); + + } + + this.resizeCanvas(); + + } + + setWindowRect(left, top, width, height) { + + this.win.style.left = Math.floor(left) + "px"; + this.win.style.top = Math.floor(top) + "px"; + this.win.style.width = Math.floor(width) + "px"; + this.win.style.height = Math.floor(height) + "px"; + + } + + onTitlebarMouseDown(event) { + + event.preventDefault(); + + this.dragType = "move"; + + const offsetX = event.clientX - this.win.offsetLeft; + const offsetY = event.clientY - this.win.offsetTop; + + if (this.isSnapped) { + + this.restoreWindowState(); + + this.startX = event.clientX; + this.startY = event.clientY; + this.startLeft = event.clientX - 100; + this.startTop = event.clientY - offsetY; + + this.win.style.left = this.startLeft + "px"; + this.win.style.top = this.startTop + "px"; + + } else { + + this.startX = event.clientX; + this.startY = event.clientY; + this.startLeft = this.win.offsetLeft; + this.startTop = this.win.offsetTop; + + } + + this.isDragging = true; + + document.addEventListener("mousemove", this.onDragMove); + document.addEventListener("mouseup", this.onDragEnd); + + } + + onDragMove = (event) => { + + + + + event.preventDefault(); + + if (!this.isDragging) return; + + const dx = event.clientX - this.startX; + const dy = event.clientY - this.startY; + + if (this.dragType === "move") { + + let newLeft = this.startLeft + dx; + let newTop = this.startTop + dy; + + newLeft = Math.max(0, Math.min(window.innerWidth - this.win.offsetWidth, newLeft)); + newTop = Math.max(0, Math.min(window.innerHeight - this.win.offsetHeight, newTop)); + + this.win.style.left = newLeft + "px"; + this.win.style.top = newTop + "px"; + + this.snapWindow(event.clientX, event.clientY); + + } else if (this.dragType === "resize") { + + let newWidth = this.startWidth; + let newHeight = this.startHeight; + let newLeft = this.startLeft; + let newTop = this.startTop; + + switch (this.currentResizer) { + case "top": + newHeight = this.startHeight - dy; + newTop = this.startTop + dy; + break; + case "bottom": + newHeight = this.startHeight + dy; + break; + case "left": + newWidth = this.startWidth - dx; + newLeft = this.startLeft + dx; + break; + case "right": + newWidth = this.startWidth + dx; + break; + case "top-left": + newWidth = this.startWidth - dx; + newLeft = this.startLeft + dx; + newHeight = this.startHeight - dy; + newTop = this.startTop + dy; + break; + case "top-right": + newWidth = this.startWidth + dx; + newHeight = this.startHeight - dy; + newTop = this.startTop + dy; + break; + case "bottom-left": + newWidth = this.startWidth - dx; + newLeft = this.startLeft + dx; + newHeight = this.startHeight + dy; + break; + case "bottom-right": + newWidth = this.startWidth + dx; + newHeight = this.startHeight + dy; + break; + } + + if (newWidth > 100) { + this.win.style.width = newWidth + "px"; + this.win.style.left = newLeft + "px"; + } + + if (newHeight > 100) { + this.win.style.height = newHeight + "px"; + this.win.style.top = newTop + "px"; + } + + } + + this.resizeCanvas(); + + }; + + onDragEnd = () => { + + this.isDragging = false; + this.dragType = null; + this.currentResizer = null; + + document.removeEventListener("mousemove", this.onDragMove); + document.removeEventListener("mouseup", this.onDragEnd); + + }; + + reOrder() { + + orderIndex++; + + this.win.style['z-index'] = orderIndex; + + + console.log("this.win.style['z-index']", this.win.style['z-index']); + } + + onResizerMouseDown(event) { + + + + + event.preventDefault(); + + this.startX = event.clientX; + this.startY = event.clientY; + this.startLeft = this.win.offsetLeft; + this.startTop = this.win.offsetTop; + this.startWidth = this.win.offsetWidth; + this.startHeight = this.win.offsetHeight; + + this.isDragging = true; + this.dragType = "resize"; + this.currentResizer = event.target.classList[1]; + + document.addEventListener("mousemove", this.onDragMove); + document.addEventListener("mouseup", this.onDragEnd); + + } + + bindResizer(resizerElement) { + + resizerElement.addEventListener("mousedown", this.onResizerMouseDown.bind(this)); + + } + + maximizeWindow() { + + if (this.isMaximized) return; + + this.savedBeforeMaximize = { + left: this.win.offsetLeft, + top: this.win.offsetTop, + width: this.win.offsetWidth, + height: this.win.offsetHeight + }; + + this.setWindowRect(0, 0, window.innerWidth, window.innerHeight); + + this.resizeCanvas(); + + this.isMaximized = true; + this.isSnapped = false; + this.savedPosition = null; + + } + + restoreFromMaximize() { + + if (!this.isMaximized || !this.savedBeforeMaximize) return; + + this.setWindowRect( + this.savedBeforeMaximize.left, + this.savedBeforeMaximize.top, + this.savedBeforeMaximize.width, + this.savedBeforeMaximize.height + ); + + this.resizeCanvas(); + + this.isMaximized = false; + this.savedBeforeMaximize = null; + + } + + onMaximizeClick() { + + + + if (this.isMaximized) { + + this.restoreFromMaximize(); + + } else { + + this.maximizeWindow(); + + } + + } + + onCloseClick() { + + this.win.style.display = "none"; + + } + +} diff --git a/extract_nodes.js b/extract_nodes.js new file mode 100644 index 0000000..03eb58b --- /dev/null +++ b/extract_nodes.js @@ -0,0 +1,238 @@ +import { parse } from "@babel/parser"; +import * as babelTraverse from "@babel/traverse"; +import generator from "@babel/generator"; +const generate = generator.default; + + +import Shader from "../framework/WebGpu.js"; + +import { readFile } from "fs/promises"; +import { writeFile } from "fs/promises"; + +const traverse = babelTraverse.default.default; + +async function loadFile( pathName ) { + + const content = await readFile( pathName, "utf-8" ); + + return content; + +} + +async function saveToJsonFile( pathName, object ) { + + const jsonContent = JSON.stringify( object, null, 4 ); + + await writeFile( pathName, jsonContent, "utf-8" ); + +} + +function parseBindings( wgsl ) { + + var bindings = []; + + const regex = /@group\((\d+)\)\s+@binding\((\d+)\)\s+var<(\w+)(?:,\s*(\w+))?>\s+(\w+)\s*:\s*([^;]+);/g; + + let match; + + while ( ( match = regex.exec( wgsl ) ) !== null ) { + + const group = Number( match[1] ); + const binding = Number( match[2] ); + const storageClass = match[3]; + const access = match[4]; + const varName = match[5]; + + let type; + + if ( storageClass === "storage" ) { + + type = access === "read" ? "read-only-storage" : "storage"; + + } else if ( storageClass === "uniform" ) { + + type = "uniform"; + + } else { + + type = "storage"; + + } + + const varType = match[6]; + + bindings.push({ group, binding, type, varName, varType }); + + } + + bindings.sort( ( a, b ) => a.binding - b.binding ); + + return bindings; + +} + +function extractShaderEdges(code) { + + const shaderInstances = new Map(); + const edges = []; + const ifStatements = []; + const definitions = []; + + const ast = parse(code, { + sourceType: "module", + plugins: ["classProperties"] + }); + + traverse(ast, { + + IfStatement(path) { + + const { test, consequent, alternate, start, end } = path.node; + + ifStatements.push({ + test: code.slice(test.start, test.end), + consequent: code.slice(consequent.start, consequent.end), + alternate: alternate ? code.slice(alternate.start, alternate.end) : null, + start, + end + }); + }, + + AssignmentExpression(path) { + + const node = path.node; + + if (node.left.type === "MemberExpression") { + + const shaderName = node.left.property.name; + + if (node.right.type === "NewExpression" && node.right.callee.name === "Shader") { + + const args = node.right.arguments; + + if (args.length >= 2 && args[1].type === "StringLiteral") { + + const shaderPath = args[1].value; + + // Store more precise location info for matching + shaderInstances.set(shaderName, { + name: shaderName, + path: shaderPath, + start: path.parentPath.node.start, + end: path.parentPath.node.end + }); + } + } + } + }, + + CallExpression(path) { + + const node = path.node; + + if (node.callee.type === "MemberExpression") { + + const object = node.callee.object; + const method = node.callee.property.name; + + if (object.type === "MemberExpression" && object.object.type === "ThisExpression") { + + const toShaderName = object.property.name; + + if (method === "setBuffer" && node.arguments.length === 2) { + + const bufferName = node.arguments[0].type === "StringLiteral" + ? node.arguments[0].value + : null; + + const arg = node.arguments[1]; + + if ( + arg.type === "CallExpression" && + arg.callee.type === "MemberExpression" && + arg.callee.property.name === "getBuffer" && + arg.callee.object.type === "MemberExpression" && + arg.callee.object.object.type === "ThisExpression" + ) { + + const fromShaderName = arg.callee.object.property.name; + + const fromBufferName = arg.arguments.length === 1 && arg.arguments[0].type === "StringLiteral" + ? arg.arguments[0].value + : null; + + edges.push({ + from: fromShaderName, + to: toShaderName, + buffer: bufferName, + fromBuffer: fromBufferName + }); + } + } + } + } + }, + + VariableDeclarator(path) { + const name = path.node.id.name; + const init = path.node.init ? generate(path.node.init).code : null; + definitions.push({ + name, + init, + start: path.node.start, + end: path.node.end + }); + }, + + ClassProperty(path) { + const name = path.node.key.name; + const init = path.node.value ? generate(path.node.value).code : null; + definitions.push({ + name, + init, + start: path.node.start, + end: path.node.end + }); + } + + }); + + // Attach ifStatements to shaders they are declared within + for (const shader of shaderInstances.values()) { + + for (const ifStmt of ifStatements) { + + if (shader.start >= ifStmt.start && shader.end <= ifStmt.end) { + + if (!shader.ifStatements) shader.ifStatements = []; + + shader.ifStatements.push(ifStmt); + } + } + } + + return { + shaders: Array.from(shaderInstances.values()), + edges: edges, + definitions: definitions + }; +} + +const code = await loadFile("../Graphics/demo.js"); + +const result = extractShaderEdges(code); + +for (var i = 0; i < result.shaders.length; i++) { + + var shaderInfo = result.shaders[i]; + + var source = await loadFile(shaderInfo.path); + + var bindings = parseBindings(source); + + shaderInfo.bindings = bindings; +} + +await saveToJsonFile("nodes.json", result); + +console.log(result); diff --git a/index.html b/index.html new file mode 100644 index 0000000..6bdcd0d --- /dev/null +++ b/index.html @@ -0,0 +1,291 @@ + + + + + +Live Snap Drag Window with Canvas + + + + +
+ + + + + +
+ + +
+ +
+ + +
+ +
+ + + + + + + diff --git a/nodes.json b/nodes.json new file mode 100644 index 0000000..8e56f69 --- /dev/null +++ b/nodes.json @@ -0,0 +1,876 @@ +{ + "shaders": [ + { + "name": "gravityShader", + "path": "../shaders/gravity.wgsl", + "start": 1750, + "end": 1826, + "bindings": [ + { + "group": 0, + "binding": 0, + "type": "storage", + "varName": "positions", + "varType": "array>" + }, + { + "group": 0, + "binding": 1, + "type": "storage", + "varName": "velocities", + "varType": "array>" + }, + { + "group": 0, + "binding": 2, + "type": "storage", + "varName": "distances", + "varType": "array" + }, + { + "group": 0, + "binding": 3, + "type": "storage", + "varName": "indices", + "varType": "array" + }, + { + "group": 0, + "binding": 4, + "type": "uniform", + "varName": "deltaTimeSeconds", + "varType": "f32" + }, + { + "group": 0, + "binding": 5, + "type": "uniform", + "varName": "cameraPosition", + "varType": "vec3" + }, + { + "group": 0, + "binding": 6, + "type": "uniform", + "varName": "updateDistancesAndIndices", + "varType": "u32" + }, + { + "group": 0, + "binding": 7, + "type": "uniform", + "varName": "cellCount", + "varType": "u32" + }, + { + "group": 0, + "binding": 8, + "type": "uniform", + "varName": "gravity", + "varType": "f32" + } + ] + }, + { + "name": "copyIndicesShader", + "path": "../shaders/copyBuffer.wgsl", + "start": 2067, + "end": 2149, + "bindings": [ + { + "group": 0, + "binding": 0, + "type": "read-only-storage", + "varName": "indices", + "varType": "array" + }, + { + "group": 0, + "binding": 1, + "type": "storage", + "varName": "sortedIndices", + "varType": "array" + } + ] + }, + { + "name": "renderShader", + "path": "../shaders/points.wgsl", + "start": 2237, + "end": 2311, + "bindings": [ + { + "group": 0, + "binding": 0, + "type": "read-only-storage", + "varName": "positions", + "varType": "array" + }, + { + "group": 0, + "binding": 1, + "type": "read-only-storage", + "varName": "sortedIndices", + "varType": "array" + }, + { + "group": 0, + "binding": 2, + "type": "uniform", + "varName": "viewProjectionMatrix", + "varType": "mat4x4" + }, + { + "group": 0, + "binding": 3, + "type": "uniform", + "varName": "cameraRight", + "varType": "BillboardAxis" + }, + { + "group": 0, + "binding": 4, + "type": "uniform", + "varName": "cameraUp", + "varType": "BillboardAxis" + } + ] + }, + { + "name": "findGridHashShader", + "path": "../shaders/findGridHash.wgsl", + "start": 2785, + "end": 2869, + "bindings": [ + { + "group": 0, + "binding": 0, + "type": "storage", + "varName": "positions", + "varType": "array>" + }, + { + "group": 0, + "binding": 1, + "type": "storage", + "varName": "gridHashes", + "varType": "array" + }, + { + "group": 0, + "binding": 2, + "type": "storage", + "varName": "indices", + "varType": "array" + }, + { + "group": 0, + "binding": 3, + "type": "uniform", + "varName": "cellCount", + "varType": "u32" + }, + { + "group": 0, + "binding": 4, + "type": "uniform", + "varName": "gridMin", + "varType": "vec3" + }, + { + "group": 0, + "binding": 5, + "type": "uniform", + "varName": "gridMax", + "varType": "vec3" + } + ] + }, + { + "name": "localSortShader", + "path": "../shaders/localSort.wgsl", + "start": 3401, + "end": 3478, + "ifStatements": [ + { + "test": "this.useLocalSort", + "consequent": "{\n\n\t\t\tthis.localSortShader = new Shader( this.device, \"../shaders/localSort.wgsl\");\n\n\t\t\tawait this.localSortShader.addStage(\"main\", GPUShaderStage.COMPUTE );\n\n\t\t\tthis.localSortShader.setBuffer( \"gridHashes\", this.findGridHashShader.getBuffer(\"gridHashes\" ) );\n\n\t\t\tthis.localSortShader.setBuffer( \"indices\", this.findGridHashShader.getBuffer(\"indices\" ) );\n\n\t\t\tthis.localSortShader.setVariable( \"totalCount\", this.particleCount );\n\n\t\t}", + "alternate": null, + "start": 3371, + "end": 3829 + } + ], + "bindings": [ + { + "group": 0, + "binding": 0, + "type": "storage", + "varName": "gridHashes", + "varType": "array" + }, + { + "group": 0, + "binding": 1, + "type": "storage", + "varName": "indices", + "varType": "array" + }, + { + "group": 0, + "binding": 2, + "type": "uniform", + "varName": "totalCount", + "varType": "u32" + } + ] + }, + { + "name": "bitonicSortGridHashShader", + "path": "../shaders/bitonicSortUIntMultiPass.wgsl", + "start": 3833, + "end": 3935, + "bindings": [ + { + "group": 0, + "binding": 0, + "type": "storage", + "varName": "gridHashes", + "varType": "array" + }, + { + "group": 0, + "binding": 1, + "type": "storage", + "varName": "threadPassIndices", + "varType": "array" + }, + { + "group": 0, + "binding": 2, + "type": "storage", + "varName": "kArray", + "varType": "array" + }, + { + "group": 0, + "binding": 3, + "type": "storage", + "varName": "jArray", + "varType": "array" + }, + { + "group": 0, + "binding": 4, + "type": "storage", + "varName": "indices", + "varType": "array" + }, + { + "group": 0, + "binding": 5, + "type": "uniform", + "varName": "totalCount", + "varType": "u32" + } + ] + }, + { + "name": "findGridHashRangeShader", + "path": "../shaders/findGridHashRanges.wgsl", + "start": 4714, + "end": 4809, + "bindings": [ + { + "group": 0, + "binding": 0, + "type": "read-only-storage", + "varName": "gridHashes", + "varType": "array" + }, + { + "group": 0, + "binding": 1, + "type": "read-only-storage", + "varName": "indices", + "varType": "array" + }, + { + "group": 0, + "binding": 2, + "type": "storage", + "varName": "startIndices", + "varType": "array" + }, + { + "group": 0, + "binding": 3, + "type": "storage", + "varName": "endIndices", + "varType": "array" + }, + { + "group": 0, + "binding": 4, + "type": "uniform", + "varName": "totalCount", + "varType": "u32" + } + ] + }, + { + "name": "collisionDetectionShader", + "path": "../shaders/collisionDetection.wgsl", + "start": 4911, + "end": 5006, + "bindings": [ + { + "group": 0, + "binding": 0, + "type": "storage", + "varName": "positions", + "varType": "array>" + }, + { + "group": 0, + "binding": 1, + "type": "storage", + "varName": "velocities", + "varType": "array>" + }, + { + "group": 0, + "binding": 2, + "type": "storage", + "varName": "gridHashes", + "varType": "array" + }, + { + "group": 0, + "binding": 3, + "type": "storage", + "varName": "hashSortedIndices", + "varType": "array" + }, + { + "group": 0, + "binding": 4, + "type": "uniform", + "varName": "cellCount", + "varType": "u32" + }, + { + "group": 0, + "binding": 5, + "type": "uniform", + "varName": "gridMin", + "varType": "vec3" + }, + { + "group": 0, + "binding": 6, + "type": "read-only-storage", + "varName": "startIndices", + "varType": "array" + }, + { + "group": 0, + "binding": 7, + "type": "read-only-storage", + "varName": "endIndices", + "varType": "array" + }, + { + "group": 0, + "binding": 8, + "type": "uniform", + "varName": "collisionRadius", + "varType": "f32" + }, + { + "group": 0, + "binding": 9, + "type": "uniform", + "varName": "deltaTimeSeconds", + "varType": "f32" + }, + { + "group": 0, + "binding": 10, + "type": "uniform", + "varName": "gridMax", + "varType": "vec3" + } + ] + }, + { + "name": "bitonicSortShader", + "path": "../shaders/bitonicSort.wgsl", + "start": 6436, + "end": 6516, + "bindings": [ + { + "group": 0, + "binding": 0, + "type": "storage", + "varName": "compare", + "varType": "array" + }, + { + "group": 0, + "binding": 1, + "type": "storage", + "varName": "indices", + "varType": "array" + }, + { + "group": 0, + "binding": 2, + "type": "uniform", + "varName": "k", + "varType": "u32" + }, + { + "group": 0, + "binding": 3, + "type": "uniform", + "varName": "j", + "varType": "u32" + }, + { + "group": 0, + "binding": 4, + "type": "uniform", + "varName": "totalCount", + "varType": "u32" + } + ] + } + ], + "edges": [ + { + "from": "gravityShader", + "to": "findGridHashShader", + "buffer": "positions", + "fromBuffer": "positions" + }, + { + "from": "findGridHashShader", + "to": "localSortShader", + "buffer": "gridHashes", + "fromBuffer": "gridHashes" + }, + { + "from": "findGridHashShader", + "to": "localSortShader", + "buffer": "indices", + "fromBuffer": "indices" + }, + { + "from": "localSortShader", + "to": "bitonicSortGridHashShader", + "buffer": "gridHashes", + "fromBuffer": "gridHashes" + }, + { + "from": "localSortShader", + "to": "bitonicSortGridHashShader", + "buffer": "indices", + "fromBuffer": "indices" + }, + { + "from": "findGridHashShader", + "to": "bitonicSortGridHashShader", + "buffer": "gridHashes", + "fromBuffer": "gridHashes" + }, + { + "from": "findGridHashShader", + "to": "bitonicSortGridHashShader", + "buffer": "indices", + "fromBuffer": "indices" + }, + { + "from": "gravityShader", + "to": "collisionDetectionShader", + "buffer": "positions", + "fromBuffer": "positions" + }, + { + "from": "gravityShader", + "to": "collisionDetectionShader", + "buffer": "velocities", + "fromBuffer": "velocities" + }, + { + "from": "gravityShader", + "to": "collisionDetectionShader", + "buffer": "positions", + "fromBuffer": "positions" + }, + { + "from": "gravityShader", + "to": "collisionDetectionShader", + "buffer": "velocities", + "fromBuffer": "velocities" + }, + { + "from": "findGridHashShader", + "to": "collisionDetectionShader", + "buffer": "gridHashes", + "fromBuffer": "gridHashes" + }, + { + "from": "localSortShader", + "to": "collisionDetectionShader", + "buffer": "hashSortedIndices", + "fromBuffer": "indices" + }, + { + "from": "bitonicSortGridHashShader", + "to": "collisionDetectionShader", + "buffer": "hashSortedIndices", + "fromBuffer": "indices" + }, + { + "from": "findGridHashRangeShader", + "to": "collisionDetectionShader", + "buffer": "startIndices", + "fromBuffer": "startIndices" + }, + { + "from": "findGridHashRangeShader", + "to": "collisionDetectionShader", + "buffer": "endIndices", + "fromBuffer": "endIndices" + }, + { + "from": "gravityShader", + "to": "bitonicSortShader", + "buffer": "compare", + "fromBuffer": "distances" + }, + { + "from": "gravityShader", + "to": "bitonicSortShader", + "buffer": "indices", + "fromBuffer": "indices" + }, + { + "from": "bitonicSortGridHashShader", + "to": "findGridHashRangeShader", + "buffer": "gridHashes", + "fromBuffer": "gridHashes" + }, + { + "from": "bitonicSortGridHashShader", + "to": "findGridHashRangeShader", + "buffer": "indices", + "fromBuffer": "indices" + }, + { + "from": "bitonicSortShader", + "to": "copyIndicesShader", + "buffer": "indices", + "fromBuffer": "indices" + }, + { + "from": "gravityShader", + "to": "renderShader", + "buffer": "positions", + "fromBuffer": "positions" + }, + { + "from": "copyIndicesShader", + "to": "renderShader", + "buffer": "sortedIndices", + "fromBuffer": "sortedIndices" + } + ], + "definitions": [ + { + "name": "canvas", + "init": null, + "start": 356, + "end": 363 + }, + { + "name": "device", + "init": null, + "start": 366, + "end": 373 + }, + { + "name": "camera", + "init": null, + "start": 376, + "end": 383 + }, + { + "name": "useLocalSort", + "init": "true", + "start": 386, + "end": 407 + }, + { + "name": "eventManager", + "init": "new EventManager()", + "start": 410, + "end": 445 + }, + { + "name": "frameCount", + "init": "0", + "start": 448, + "end": 466 + }, + { + "name": "context", + "init": "offscreenCanvas.getContext(\"webgpu\")", + "start": 692, + "end": 741 + }, + { + "name": "adapter", + "init": "await self.navigator.gpu.requestAdapter()", + "start": 881, + "end": 935 + }, + { + "name": "boundsMinimum", + "init": "new Vector3(-2, -2, -2)", + "start": 1108, + "end": 1151 + }, + { + "name": "boundsMaximum", + "init": "new Vector3(2, 2, 2)", + "start": 1160, + "end": 1199 + }, + { + "name": "cellsPerDimension", + "init": "12", + "start": 1208, + "end": 1231 + }, + { + "name": "gravity", + "init": "-2.3", + "start": 1240, + "end": 1257 + }, + { + "name": "jArray", + "init": "new Array()", + "start": 1377, + "end": 1401 + }, + { + "name": "kArray", + "init": "new Array()", + "start": 1410, + "end": 1434 + }, + { + "name": "startK", + "init": "this.workgroupSize * 2", + "start": 1473, + "end": 1504 + }, + { + "name": "startK", + "init": "2", + "start": 1563, + "end": 1573 + }, + { + "name": "k", + "init": "startK", + "start": 1595, + "end": 1605 + }, + { + "name": "j", + "init": "k >> 1", + "start": 1657, + "end": 1667 + }, + { + "name": "quadOffsets", + "init": "new Float32Array([\n// Triangle 1\n-1, -1,\n// bottom-left\n1, -1,\n// bottom-right\n1, 1,\n// top-right\n\n// Triangle 2\n-1, -1,\n// bottom-left\n1, 1,\n// top-right\n-1, 1 // top-left\n])", + "start": 2486, + "end": 2715 + }, + { + "name": "positions", + "init": "new Float32Array(this.particleCount * 3)", + "start": 7719, + "end": 7775 + }, + { + "name": "velocities", + "init": "new Float32Array(this.particleCount * 3)", + "start": 7786, + "end": 7843 + }, + { + "name": "i", + "init": "0", + "start": 7858, + "end": 7863 + }, + { + "name": "now", + "init": "performance.now()", + "start": 8329, + "end": 8355 + }, + { + "name": "commandEncoder", + "init": "this.device.createCommandEncoder()", + "start": 8488, + "end": 8539 + }, + { + "name": "passEncoder", + "init": "commandEncoder.beginComputePass()", + "start": 8606, + "end": 8653 + }, + { + "init": null, + "start": 8733, + "end": 8753 + }, + { + "name": "threadPassIndices", + "init": "new Uint32Array(this.particleCount)", + "start": 8986, + "end": 9044 + }, + { + "name": "i", + "init": "0", + "start": 9059, + "end": 9064 + }, + { + "name": "passEncoder", + "init": "commandEncoder.beginComputePass()", + "start": 9233, + "end": 9280 + }, + { + "init": null, + "start": 9370, + "end": 9390 + }, + { + "name": "startK", + "init": "this.workgroupSize * 2", + "start": 9546, + "end": 9577 + }, + { + "name": "startK", + "init": "2", + "start": 9601, + "end": 9611 + }, + { + "name": "k", + "init": "startK", + "start": 9632, + "end": 9642 + }, + { + "name": "j", + "init": "k >> 1", + "start": 9695, + "end": 9705 + }, + { + "name": "commandBuffer", + "init": "commandEncoder.finish()", + "start": 9839, + "end": 9878 + }, + { + "name": "k", + "init": "2", + "start": 10153, + "end": 10158 + }, + { + "name": "j", + "init": "k >> 1", + "start": 10209, + "end": 10219 + }, + { + "name": "commandBuffers", + "init": "new Array()", + "start": 10250, + "end": 10278 + }, + { + "name": "commandEncoder", + "init": "this.device.createCommandEncoder()", + "start": 10434, + "end": 10485 + }, + { + "name": "passEncoder", + "init": "commandEncoder.beginComputePass()", + "start": 10498, + "end": 10545 + }, + { + "init": null, + "start": 10629, + "end": 10649 + }, + { + "name": "viewMatrixData", + "init": "this.camera.getViewMatrix()", + "start": 11861, + "end": 11908 + }, + { + "name": "projectionMatrixData", + "init": "Matrix4.createProjectionMatrix(this.camera, this.canvas)", + "start": 11919, + "end": 12002 + }, + { + "name": "viewProjectionMatrix", + "init": "Matrix4.multiply(projectionMatrixData, viewMatrixData)", + "start": 12012, + "end": 12093 + }, + { + "name": "cameraWorldMatrix", + "init": "Matrix4.invert(viewMatrixData)", + "start": 12104, + "end": 12158 + }, + { + "name": "cameraRight", + "init": "Matrix4.getColumn(cameraWorldMatrix, 0)", + "start": 12171, + "end": 12229 + }, + { + "name": "cameraUp", + "init": "Matrix4.getColumn(cameraWorldMatrix, 1)", + "start": 12240, + "end": 12296 + }, + { + "name": "cameraPosition", + "init": "Matrix4.getColumn(cameraWorldMatrix, 3)", + "start": 12307, + "end": 12367 + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..485b102 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,250 @@ +{ + "name": "GraphExplorer", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.5", + "@babel/traverse": "^7.27.4", + "esprima": "^4.0.1", + "estraverse": "^5.3.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9228432 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "type": "module", + "dependencies": { + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.5", + "@babel/traverse": "^7.27.4", + "esprima": "^4.0.1", + "estraverse": "^5.3.0" + } +}