First commit
This commit is contained in:
778
GraphExplorer.js
Normal file
778
GraphExplorer.js
Normal file
@@ -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 );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
518
WindowController.js
Normal file
518
WindowController.js
Normal file
@@ -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";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
238
extract_nodes.js
Normal file
238
extract_nodes.js
Normal file
@@ -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);
|
||||||
291
index.html
Normal file
291
index.html
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
<!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>
|
||||||
876
nodes.json
Normal file
876
nodes.json
Normal file
@@ -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<vec3<f32>>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 1,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "velocities",
|
||||||
|
"varType": "array<vec3<f32>>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 2,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "distances",
|
||||||
|
"varType": "array<f32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 3,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "indices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 4,
|
||||||
|
"type": "uniform",
|
||||||
|
"varName": "deltaTimeSeconds",
|
||||||
|
"varType": "f32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 5,
|
||||||
|
"type": "uniform",
|
||||||
|
"varName": "cameraPosition",
|
||||||
|
"varType": "vec3<f32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 1,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "sortedIndices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "renderShader",
|
||||||
|
"path": "../shaders/points.wgsl",
|
||||||
|
"start": 2237,
|
||||||
|
"end": 2311,
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 0,
|
||||||
|
"type": "read-only-storage",
|
||||||
|
"varName": "positions",
|
||||||
|
"varType": "array<Point>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 1,
|
||||||
|
"type": "read-only-storage",
|
||||||
|
"varName": "sortedIndices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 2,
|
||||||
|
"type": "uniform",
|
||||||
|
"varName": "viewProjectionMatrix",
|
||||||
|
"varType": "mat4x4<f32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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<vec3<f32>>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 1,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "gridHashes",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 2,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "indices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 3,
|
||||||
|
"type": "uniform",
|
||||||
|
"varName": "cellCount",
|
||||||
|
"varType": "u32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 4,
|
||||||
|
"type": "uniform",
|
||||||
|
"varName": "gridMin",
|
||||||
|
"varType": "vec3<f32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 5,
|
||||||
|
"type": "uniform",
|
||||||
|
"varName": "gridMax",
|
||||||
|
"varType": "vec3<f32>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 1,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "indices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 1,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "threadPassIndices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 2,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "kArray",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 3,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "jArray",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 4,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "indices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 1,
|
||||||
|
"type": "read-only-storage",
|
||||||
|
"varName": "indices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 2,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "startIndices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 3,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "endIndices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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<vec3<f32>>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 1,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "velocities",
|
||||||
|
"varType": "array<vec3<f32>>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 2,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "gridHashes",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 3,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "hashSortedIndices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 4,
|
||||||
|
"type": "uniform",
|
||||||
|
"varName": "cellCount",
|
||||||
|
"varType": "u32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 5,
|
||||||
|
"type": "uniform",
|
||||||
|
"varName": "gridMin",
|
||||||
|
"varType": "vec3<f32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 6,
|
||||||
|
"type": "read-only-storage",
|
||||||
|
"varName": "startIndices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 7,
|
||||||
|
"type": "read-only-storage",
|
||||||
|
"varName": "endIndices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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<f32>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bitonicSortShader",
|
||||||
|
"path": "../shaders/bitonicSort.wgsl",
|
||||||
|
"start": 6436,
|
||||||
|
"end": 6516,
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 0,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "compare",
|
||||||
|
"varType": "array<f32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": 0,
|
||||||
|
"binding": 1,
|
||||||
|
"type": "storage",
|
||||||
|
"varName": "indices",
|
||||||
|
"varType": "array<u32>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
250
package-lock.json
generated
Normal file
250
package-lock.json
generated
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
package.json
Normal file
10
package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user