tsp
This commit is contained in:
19
projects/tsp/canvas.js
Normal file
19
projects/tsp/canvas.js
Normal file
@@ -0,0 +1,19 @@
|
||||
export function setupCanvas(gl, canvas) {
|
||||
canvas.width = canvas.clientWidth * window.devicePixelRatio;
|
||||
canvas.height = canvas.clientHeight * window.devicePixelRatio;
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
|
||||
return {
|
||||
canvas,
|
||||
gl,
|
||||
aspectRatio: canvas.width / canvas.height
|
||||
};
|
||||
}
|
||||
|
||||
export function resizeCanvas(gl, canvas, program) {
|
||||
canvas.width = canvas.clientWidth * window.devicePixelRatio;
|
||||
canvas.height = canvas.clientHeight * window.devicePixelRatio;
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
gl.useProgram(program);
|
||||
gl.uniform2f(gl.getUniformLocation(program, "u_resolution"), canvas.width, canvas.height);
|
||||
}
|
||||
74
projects/tsp/index.html
Normal file
74
projects/tsp/index.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Travelling Salesman Problem</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="/#projects" class="back-button">← Back</a>
|
||||
|
||||
<header>
|
||||
<h1>Travelling Salesman Problem</h1>
|
||||
<p class="subtitle">An interactive route visualiser built with WebGL and WebAssembly.</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<p>
|
||||
After writing one of my first tutorials on the Travelling Salesman Problem, I wanted to revisit it with a slightly more technical stack.
|
||||
The backend is written in Zig (compiled to WebAssembly), and the rendering is done directly in WebGL instead of p5.js or Raylib.
|
||||
</p>
|
||||
<p>
|
||||
This project was also inspired by <a href="https://www.youtube.com/@sphaerophoria">sphaerophoria</a> who started a <a href="https://www.youtube.com/watch?v=RiLjPBci6Og&list=PL980gcR1LE3L8RoIMSNBFfw4dFfS3rrsk">series</a> on creating his own map.
|
||||
</p>
|
||||
|
||||
<div class="info-box">
|
||||
<p>
|
||||
Drag points around the canvas and watch the route update in real time.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="canvas-container">
|
||||
<div class="canvas-toolbar">
|
||||
<div class="control-group" aria-label="TSP controls">
|
||||
<label for="pointCountInput">Points</label>
|
||||
<input id="pointCountInput" type="number" min="5" max="100" step="1" value="36" />
|
||||
<button id="applyPointCountButton" class="action-button" type="button">Regenerate</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="canvas-shell">
|
||||
<canvas id="tsp-canvas" aria-label="Travelling Salesman Problem canvas">
|
||||
HTML5 canvas not supported in browser
|
||||
</canvas>
|
||||
</div>
|
||||
|
||||
<div class="status-row">
|
||||
<div id="pointCountStatus" class="status">36 points</div>
|
||||
<p class="canvas-note">Tip: click and drag a point to recompute the route.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h3>Technical Details</h3>
|
||||
<p>
|
||||
The backend is written in Zig, and the route is built in two passes: a nearest-neighbour pass followed by a 2-opt local search.
|
||||
</p>
|
||||
<p>
|
||||
Point coordinates are stored in WebAssembly memory, and the route order is recalculated whenever the layout changes.
|
||||
Rendering is handled in WebGL to keep interaction smooth as the point count increases.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>Built with Zig, WebGL2 and WebAssembly</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="./point.js"></script>
|
||||
<script type="module" src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
245
projects/tsp/main.js
Normal file
245
projects/tsp/main.js
Normal file
@@ -0,0 +1,245 @@
|
||||
import { initWasm } from "./wasm.js";
|
||||
import { setupCanvas, resizeCanvas } from "./canvas.js";
|
||||
import { createShaderProgram } from "./shaders.js";
|
||||
import { draw } from "./tsp-gl.js";
|
||||
import { initPoints } from "./utils.js";
|
||||
|
||||
const DEFAULT_POINT_COUNT = 36;
|
||||
const MIN_POINTS = 4;
|
||||
const MAX_POINTS = 96;
|
||||
const RADIUS_SIZE = 0.006;
|
||||
|
||||
function clampPointCount(value) {
|
||||
if (Number.isNaN(value)) {
|
||||
return DEFAULT_POINT_COUNT;
|
||||
}
|
||||
|
||||
return Math.max(MIN_POINTS, Math.min(MAX_POINTS, value));
|
||||
}
|
||||
|
||||
function createPointFragmentShader(pointCount) {
|
||||
return `
|
||||
precision mediump float;
|
||||
|
||||
uniform vec2 u_resolution;
|
||||
uniform vec2 u_centers[${pointCount}];
|
||||
|
||||
void main() {
|
||||
vec2 position = gl_FragCoord.xy / u_resolution;
|
||||
float aspect = u_resolution.x / u_resolution.y;
|
||||
vec2 scaledPosition = vec2(position.x * aspect, position.y);
|
||||
float radius = ${RADIUS_SIZE};
|
||||
|
||||
for (int i = 0; i < ${pointCount}; i++) {
|
||||
vec2 center = u_centers[i];
|
||||
vec2 scaledCenter = vec2(center.x * aspect, center.y);
|
||||
float distance = length(scaledPosition - scaledCenter) * (1.0 / aspect);
|
||||
|
||||
if (distance <= radius) {
|
||||
gl_FragColor = vec4(0.5, center, 1.0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(0.1, 0.1, 0.1, 1.0);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function createLineFragmentShader() {
|
||||
return `
|
||||
precision mediump float;
|
||||
|
||||
void main(void) {
|
||||
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const canvas = document.getElementById("tsp-canvas");
|
||||
const pointCountInput = document.getElementById("pointCountInput");
|
||||
const applyPointCountButton = document.getElementById("applyPointCountButton");
|
||||
const pointCountStatus = document.getElementById("pointCountStatus");
|
||||
|
||||
if (!canvas || !pointCountInput || !applyPointCountButton || !pointCountStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gl = canvas.getContext("webgl2");
|
||||
if (!gl) {
|
||||
pointCountStatus.textContent = "WebGL2 unavailable in this browser.";
|
||||
return;
|
||||
}
|
||||
|
||||
gl.clearColor(0.1, 0.1, 0.1, 1);
|
||||
|
||||
const {
|
||||
initialisePoints,
|
||||
getPointOrder,
|
||||
memory
|
||||
} = await initWasm();
|
||||
|
||||
const vertexCode = `
|
||||
attribute vec2 a_position;
|
||||
void main(void) {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
setupCanvas(gl, canvas);
|
||||
|
||||
const positionBuffer = gl.createBuffer();
|
||||
const lineBuffer = gl.createBuffer();
|
||||
|
||||
if (!positionBuffer || !lineBuffer) {
|
||||
pointCountStatus.textContent = "Unable to initialise WebGL buffers.";
|
||||
return;
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
const positions = [-1, -1, 3, -1, -1, 3];
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
||||
|
||||
const state = {
|
||||
scene: null,
|
||||
draggingIndex: -1
|
||||
};
|
||||
|
||||
function updateStatus(pointCount) {
|
||||
pointCountStatus.textContent = `${pointCount} point${pointCount === 1 ? "" : "s"}`;
|
||||
}
|
||||
|
||||
function recalculateRoute(scene) {
|
||||
const pointOrderPtr = getPointOrder(scene.pointsPtr, scene.pointCount);
|
||||
scene.pointOrder = Array.from(new Uint32Array(memory.buffer, pointOrderPtr, scene.pointCount));
|
||||
}
|
||||
|
||||
function syncPointsToMemory(scene) {
|
||||
scene.pointArray = new Float32Array(memory.buffer, scene.pointsPtr, scene.pointCount * 2);
|
||||
|
||||
for (let i = 0; i < scene.points.length; i++) {
|
||||
scene.pointArray[i * 2] = scene.points[i].cx;
|
||||
scene.pointArray[i * 2 + 1] = scene.points[i].cy;
|
||||
}
|
||||
}
|
||||
|
||||
function initialiseScene(rawPointCount) {
|
||||
const pointCount = clampPointCount(rawPointCount);
|
||||
|
||||
if (state.scene) {
|
||||
gl.deleteProgram(state.scene.program);
|
||||
gl.deleteProgram(state.scene.lineProgram);
|
||||
}
|
||||
|
||||
const pointsPtr = initialisePoints(pointCount, Date.now());
|
||||
const pointArray = new Float32Array(memory.buffer, pointsPtr, pointCount * 2);
|
||||
const points = initPoints(pointArray);
|
||||
const program = createShaderProgram(gl, vertexCode, createPointFragmentShader(pointCount));
|
||||
const lineProgram = createShaderProgram(gl, vertexCode, createLineFragmentShader());
|
||||
|
||||
state.scene = {
|
||||
pointCount,
|
||||
pointsPtr,
|
||||
pointArray,
|
||||
points,
|
||||
pointOrder: [],
|
||||
program,
|
||||
lineProgram
|
||||
};
|
||||
|
||||
recalculateRoute(state.scene);
|
||||
resizeCanvas(gl, canvas, program);
|
||||
updateStatus(pointCount);
|
||||
pointCountInput.value = String(pointCount);
|
||||
}
|
||||
|
||||
canvas.addEventListener("mousedown", (e) => {
|
||||
if (!state.scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
const y = 1 - ((e.clientY - rect.top) / rect.height) * 2;
|
||||
|
||||
for (let i = 0; i < state.scene.points.length; i++) {
|
||||
const point = state.scene.points[i];
|
||||
const dx = (0.5 * x + 0.5) - point.cx;
|
||||
const dy = (0.5 * y + 0.5) - point.cy;
|
||||
const distance = Math.hypot(dx, dy);
|
||||
|
||||
if (distance <= RADIUS_SIZE) {
|
||||
state.draggingIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener("mousemove", (e) => {
|
||||
if (!state.scene || state.draggingIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
state.scene.points[state.draggingIndex].cx = (e.clientX - rect.left) / rect.width;
|
||||
state.scene.points[state.draggingIndex].cy = 1 - (e.clientY - rect.top) / rect.height;
|
||||
|
||||
syncPointsToMemory(state.scene);
|
||||
recalculateRoute(state.scene);
|
||||
});
|
||||
|
||||
function stopDragging() {
|
||||
state.draggingIndex = -1;
|
||||
}
|
||||
|
||||
canvas.addEventListener("mouseup", stopDragging);
|
||||
canvas.addEventListener("mouseleave", stopDragging);
|
||||
|
||||
function applyPointCount() {
|
||||
const nextPointCount = clampPointCount(Number.parseInt(pointCountInput.value, 10));
|
||||
initialiseScene(nextPointCount);
|
||||
}
|
||||
|
||||
applyPointCountButton.addEventListener("click", applyPointCount);
|
||||
pointCountInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
applyPointCount();
|
||||
}
|
||||
});
|
||||
|
||||
pointCountInput.addEventListener("blur", () => {
|
||||
pointCountInput.value = String(clampPointCount(Number.parseInt(pointCountInput.value, 10)));
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
if (!state.scene) {
|
||||
return;
|
||||
}
|
||||
|
||||
resizeCanvas(gl, canvas, state.scene.program);
|
||||
});
|
||||
|
||||
initialiseScene(DEFAULT_POINT_COUNT);
|
||||
|
||||
function render() {
|
||||
if (state.scene) {
|
||||
draw(
|
||||
gl,
|
||||
state.scene.points,
|
||||
state.scene.program,
|
||||
state.scene.lineProgram,
|
||||
positionBuffer,
|
||||
lineBuffer,
|
||||
state.scene.pointOrder,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
|
||||
render();
|
||||
}
|
||||
|
||||
await run();
|
||||
BIN
projects/tsp/main.wasm
Executable file
BIN
projects/tsp/main.wasm
Executable file
Binary file not shown.
6
projects/tsp/point.js
Normal file
6
projects/tsp/point.js
Normal file
@@ -0,0 +1,6 @@
|
||||
class Point {
|
||||
constructor(x, y) {
|
||||
this.cx = x;
|
||||
this.cy = y;
|
||||
}
|
||||
}
|
||||
20
projects/tsp/shaders.js
Normal file
20
projects/tsp/shaders.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// shaders.js
|
||||
export function loadShader(gl, type, source) {
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
export function createShaderProgram(gl, vertexCode, fragCode) {
|
||||
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vertexCode);
|
||||
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fragCode);
|
||||
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
gl.linkProgram(program);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
202
projects/tsp/style.css
Normal file
202
projects/tsp/style.css
Normal file
@@ -0,0 +1,202 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
color: black;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.8rem;
|
||||
margin: 20px 0 15px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.3rem;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px 15px;
|
||||
background: #f0f0f0;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.back-button:hover,
|
||||
.action-button:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #333;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.canvas-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.canvas-toolbar p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
color: #555;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.control-group input {
|
||||
width: 90px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font: inherit;
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 8px 12px;
|
||||
background: #f0f0f0;
|
||||
color: black;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.canvas-shell {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 500px;
|
||||
overflow: auto;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
#tsp-canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 560px;
|
||||
max-width: 100%;
|
||||
background: black;
|
||||
outline: none;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.status {
|
||||
min-height: 24px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canvas-note {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ccc;
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.canvas-shell {
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
#tsp-canvas {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
46
projects/tsp/tsp-gl.js
Normal file
46
projects/tsp/tsp-gl.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// tsp-gl.js
|
||||
import { updateLines } from "./utils.js";
|
||||
|
||||
export function draw(gl, points, program, lineProgram, positionBuffer, lineBuffer, pointOrder, aspectRatio) {
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
// Draw Circles
|
||||
updateCircleUniforms(gl, points, program, positionBuffer);
|
||||
|
||||
// Draw Lines
|
||||
const lineVertices = updateLines(points, pointOrder);
|
||||
drawLines(gl, lineProgram, lineBuffer, lineVertices);
|
||||
}
|
||||
|
||||
function updateCircleUniforms(gl, points, program, positionBuffer) {
|
||||
const centerArray = new Float32Array(points.length * 2);
|
||||
|
||||
points.forEach((point, i) => {
|
||||
centerArray[i * 2] = point.cx;
|
||||
centerArray[i * 2 + 1] = point.cy;
|
||||
});
|
||||
|
||||
gl.useProgram(program);
|
||||
|
||||
gl.uniform2fv(gl.getUniformLocation(program, "u_centers"), centerArray);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
const positionAttribLoc = gl.getAttribLocation(program, "a_position");
|
||||
gl.enableVertexAttribArray(positionAttribLoc);
|
||||
gl.vertexAttribPointer(positionAttribLoc, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 3);
|
||||
}
|
||||
|
||||
function drawLines(gl, program, buffer, lineVertices) {
|
||||
gl.useProgram(program);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, lineVertices, gl.DYNAMIC_DRAW);
|
||||
|
||||
const positionAttribLoc = gl.getAttribLocation(program, "a_position");
|
||||
gl.enableVertexAttribArray(positionAttribLoc);
|
||||
gl.vertexAttribPointer(positionAttribLoc, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
gl.drawArrays(gl.LINES, 0, lineVertices.length / 2);
|
||||
}
|
||||
19
projects/tsp/utils.js
Normal file
19
projects/tsp/utils.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// utils.js
|
||||
export function initPoints(points) {
|
||||
let cs = [];
|
||||
for (let i = 0; i < points.length; i += 2) {
|
||||
cs.push(new Point(points[i], points[i + 1]));
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
|
||||
export function updateLines(points, pointOrder) {
|
||||
const lineVertices = [];
|
||||
for (let i = 0; i < pointOrder.length - 1; i++) {
|
||||
const j = pointOrder[i];
|
||||
const k = pointOrder[i + 1];
|
||||
lineVertices.push(points[j].cx * 2 - 1, points[j].cy * 2 - 1);
|
||||
lineVertices.push(points[k].cx * 2 - 1, points[k].cy * 2 - 1);
|
||||
}
|
||||
return new Float32Array(lineVertices);
|
||||
}
|
||||
24
projects/tsp/wasm.js
Normal file
24
projects/tsp/wasm.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export async function initWasm() {
|
||||
|
||||
let wasmMemory = new WebAssembly.Memory({
|
||||
initial: 256,
|
||||
maximum: 256
|
||||
});
|
||||
|
||||
let importObject = {
|
||||
env: {
|
||||
memory: wasmMemory,
|
||||
}
|
||||
};
|
||||
|
||||
const response = await fetch("./main.wasm");
|
||||
const wasmModule = await WebAssembly.instantiateStreaming(response, importObject);
|
||||
const { initialisePoints, getPointOrder, memory } = wasmModule.instance.exports;
|
||||
|
||||
return {
|
||||
initialisePoints,
|
||||
getPointOrder,
|
||||
memory,
|
||||
wasmMemory
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user