UNPKG

agentscript

Version:

AgentScript Model in Model/View architecture

1,625 lines (1,622 loc) 1.46 MB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/utils.js var utils_exports = {}; __export(utils_exports, { AsyncFunction: () => AsyncFunction, PI: () => PI, RESTapi: () => RESTapi, addCssLink: () => addCssLink, addCssStyle: () => addCssStyle, arrayExtent: () => arrayExtent, arrayLast: () => arrayLast, arrayMax: () => arrayMax, arrayMin: () => arrayMin, arrayToMatrix: () => arrayToMatrix, arraysDiff: () => arraysDiff, arraysEqual: () => arraysEqual, arraysToString: () => arraysToString, blobToData: () => blobToData, blobsEqual: () => blobsEqual, checkArg: () => checkArg, checkArgs: () => checkArgs, clamp: () => clamp, classHasStartup: () => classHasStartup, clearCtx: () => clearCtx, cloneCanvas: () => cloneCanvas, concatArrays: () => concatArrays, convertArrayType: () => convertArrayType, createCanvas: () => createCanvas, createCtx: () => createCtx, cssTrace: () => cssTrace, ctxImageColors: () => ctxImageColors, ctxImageData: () => ctxImageData, ctxImagePixels: () => ctxImagePixels, degToHeading: () => degToHeading, degToRad: () => degToRad, degreesEqual: () => degreesEqual, degreesTowardXY: () => degreesTowardXY, difference: () => difference, distance: () => distance, distance3: () => distance3, downloadBlob: () => downloadBlob, downloadCanvas: () => downloadCanvas, downloadJson: () => downloadJson, downloadJsonModule: () => downloadJsonModule, drawText: () => drawText, dump: () => dump, fetchCssStyle: () => fetchCssStyle, fetchData: () => fetchData, fetchImage: () => fetchImage, fetchImageBitmap: () => fetchImageBitmap, fetchJson: () => fetchJson, fetchText: () => fetchText, fillCtxWithImage: () => fillCtxWithImage, floatRamp: () => floatRamp, forLoop: () => forLoop, fps: () => fps, getEventXY: () => getEventXY, getQueryString: () => getQueryString, grep: () => grep, hasCanvas: () => hasCanvas, headingAngleToRad: () => headingAngleToRad, headingToDeg: () => headingToDeg, headingToRad: () => headingToRad, headingTowardXY: () => headingTowardXY, headingsEq: () => headingsEq, identityFcn: () => identityFcn, imagePromise: () => imagePromise, imageToCanvas: () => imageToCanvas, imageToCtx: () => imageToCtx, inCone: () => inCone, inDeno: () => inDeno, inMain: () => inMain, inNode: () => inNode, inWorker: () => inWorker, integerRamp: () => integerRamp, intersection: () => intersection, isArray: () => isArray, isArrayLike: () => isArrayLike, isBetween: () => isBetween, isCanvas: () => isCanvas, isColorLikeArray: () => isColorLikeArray, isDataSet: () => isDataSet, isFloatArray: () => isFloatArray, isFunction: () => isFunction, isImage: () => isImage, isImageable: () => isImageable, isIntArray: () => isIntArray, isInteger: () => isInteger, isLittleEndian: () => isLittleEndian, isNumber: () => isNumber, isObject: () => isObject, isOneOfTypes: () => isOneOfTypes, isOofA: () => isOofA, isPowerOf2: () => isPowerOf2, isString: () => isString, isType: () => isType, isTypedArray: () => isTypedArray, isUintArray: () => isUintArray, lerp: () => lerp, lerpScale: () => lerpScale, logAll: () => logAll, logOnce: () => logOnce, matrixToArray: () => matrixToArray, mod: () => mod, mod180180: () => mod180180, mod2pi: () => mod2pi, mod360: () => mod360, nestedProperty: () => nestedProperty, nextPowerOf2: () => nextPowerOf2, noopFcn: () => noopFcn, objectLength: () => objectLength, objectToString: () => objectToString, objectsEqual: () => objectsEqual, oneKeyOf: () => oneKeyOf, oneOf: () => oneOf, oneValOf: () => oneValOf, oofaBuffers: () => oofaBuffers, oofaObject: () => oofaObject, otherOneOf: () => otherOneOf, parseQueryString: () => parseQueryString, pause: () => pause, pps: () => pps, precision: () => precision, printToPage: () => printToPage, propFcn: () => propFcn, radToDeg: () => radToDeg, radToHeading: () => radToHeading, radToHeadingAngle: () => radToHeadingAngle, radiansTowardXY: () => radiansTowardXY, radsEqual: () => radsEqual, randomCentered: () => randomCentered, randomFloat: () => randomFloat, randomFloat2: () => randomFloat2, randomInt: () => randomInt, randomInt2: () => randomInt2, randomNormal: () => randomNormal, randomSeed: () => randomSeed, range: () => range, removeArrayItem: () => removeArrayItem, repeat: () => repeat, resizeCtx: () => resizeCtx, runModel: () => runModel, sampleModel: () => sampleModel, setCanvasSize: () => setCanvasSize, setCtxImage: () => setCtxImage, setIdentity: () => setIdentity, setTextProperties: () => setTextProperties, shuffle: () => shuffle, sortNums: () => sortNums, sortObjs: () => sortObjs, sqDistance: () => sqDistance, sqDistance3: () => sqDistance3, step: () => step, stringMetrics: () => stringMetrics, subtractDegrees: () => subtractDegrees, subtractHeadings: () => subtractHeadings, subtractRadians: () => subtractRadians, timeit: () => timeit, timeoutLoop: () => timeoutLoop, toAofO: () => toAofO, toDataURL: () => toDataURL, toDeg: () => toDeg, toJSON: () => toJSON, toOofA: () => toOofA, toRad: () => toRad, toWindow: () => toWindow, typeOf: () => typeOf, union: () => union, waitUntilDone: () => waitUntilDone, warn: () => warn, wrap: () => wrap }); function inMain() { return globalThis.document !== void 0; } function inWorker() { return globalThis.WorkerGlobalScope !== void 0; } function inNode() { return typeof globalThis.require !== "undefined"; } function inDeno() { return typeof globalThis.Deno !== "undefined"; } function hasCanvas() { return globalThis.canvas !== "undefined"; } function AsyncFunction(argsArray, fcnBody) { const ctor = Object.getPrototypeOf(async function() { }).constructor; const asyncFcn = new ctor(...argsArray, fcnBody); return asyncFcn; } function blobToData(blob, type = "dataURL") { type = type[0].toUpperCase() + type.slice(1); const types = ["Text", "ArrayBuffer", "DataURL"]; if (!types.includes(type)) throw Error("blobToData: data must be one of " + types.toString()); const reader = new FileReader(); return new Promise((resolve, reject) => { reader.addEventListener("load", () => resolve(reader.result)); reader.addEventListener("error", (e) => reject(e)); reader["readAs" + type](blob); }); } async function fetchData(url2, type = "blob") { const types = ["arrayBuffer", "blob", "json", "text"]; if (!types.includes(type)) throw Error("fetchData: data must be one of " + types.toString()); return fetch(url2).then((res) => res[type]()); } async function fetchJson(url2) { return fetchData(url2, "json"); } async function fetchText(url2) { return fetchData(url2, "text"); } function toDataURL(data, type = void 0) { if (data.toDataURL) return data.toDataURL(type, type); if (!type) type = "text/plain;charset=US-ASCII"; return `data:${type};base64,${btoa(data)}}`; } async function blobsEqual(blob0, blob1) { const text0 = await blob0.text(); const text1 = await blob1.text(); return text0 === text1; } function pause(ms = 1e3) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function timeoutLoop(fcn, steps = -1, ms = 0) { let i = 0; while (i++ !== steps) { fcn(i - 1); await pause(ms); } } function waitUntilDone(done, ms = 10) { return new Promise((resolve) => { function waitOnDone() { if (done()) return resolve(); else setTimeout(waitOnDone, ms); } waitOnDone(); }); } function checkArg(arg, type = "number", name = "Function") { } function checkArgs(argsArray, type = "number", name = "Function") { } var logOnceMsgSet = /* @__PURE__ */ new Set(); function logOnce(msg, useWarn = false) { if (!logOnceMsgSet.has(msg)) { if (useWarn) { console.warn(msg); } else { console.log(msg); } logOnceMsgSet.add(msg); } } function warn(msg) { logOnce(msg, true); } function timeit(f, runs = 1e5, name = "test") { name = name + "-" + runs; console.time(name); for (let i = 0; i < runs; i++) f(i); console.timeEnd(name); } function fps() { const timer = typeof performance === "undefined" ? Date : performance; const start = timer.now(); let steps = 0; function perf() { steps++; const ms = timer.now() - start; const fps2 = parseFloat((steps / (ms / 1e3)).toFixed(2)); Object.assign(perf, { fps: fps2, ms, start, steps }); } perf.steps = 0; return perf; } function pps(obj, title = "") { if (title) console.log(title); let count = 1; let str = ""; while (obj) { if (typeof obj === "function") { str = obj.constructor.toString(); } else { const okeys = Object.keys(obj); str = okeys.length > 0 ? `[${okeys.join(", ")}]` : `[${obj.constructor.name}]`; } console.log(`[${count++}]: ${str}`); obj = Object.getPrototypeOf(obj); } } function logAll(obj) { Object.keys(obj).forEach((key) => console.log(" ", key, obj[key])); } function cssTrace(elementName, names = ["position", "cursor", "display", "width", "height"]) { let element = document.querySelector(elementName); while (element) { const styles = window.getComputedStyle(element); console.log("element:", element); console.log("tag:", element.tagName); names.forEach((name) => console.log(name + ":", styles[name])); console.log("------------------------"); element = element.parentElement; } } var PI = Math.PI; function randomInt(max) { return Math.floor(Math.random() * max); } function randomInt2(min, max) { return min + Math.floor(Math.random() * (max - min)); } function randomFloat(max) { return Math.random() * max; } function randomFloat2(min, max) { return min + Math.random() * (max - min); } function randomCentered(r) { return randomFloat2(-r / 2, r / 2); } function randomNormal(mean = 0, sigma = 1) { const [u1, u2] = [1 - Math.random(), Math.random()]; const norm = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * PI * u2); return norm * sigma + mean; } function randomSeed(seed = 123456) { seed = seed % 2147483647; Math.random = () => { seed = seed * 16807 % 2147483647; return (seed - 1) / 2147483646; }; } function precision(num, digits = 4) { if (Math.abs(num) === 0) return 0; if (Array.isArray(num)) return num.map((val) => precision(val, digits)); const mult = 10 ** digits; return Math.round(num * mult) / mult; } var isPowerOf2 = (num) => (num & num - 1) === 0; var nextPowerOf2 = (num) => Math.pow(2, Math.ceil(Math.log2(num))); function mod(v, n) { return (v % n + n) % n; } var wrap = (v, min, max) => min + mod(v - min, max - min); function clamp(v, min, max) { if (v < min) return min; if (v > max) return max; return v; } var isBetween = (val, min, max) => min <= val && val <= max; var lerp = (lo, hi, scale) => lo <= hi ? lo + (hi - lo) * scale : lo - (lo - hi) * scale; function lerpScale(number, lo, hi) { if (lo === hi) throw Error("lerpScale: lo === hi"); number = clamp(number, lo, hi); return (number - lo) / (hi - lo); } var toDeg = 180 / Math.PI; var toRad = Math.PI / 180; function degToRad(degrees2) { return mod2pi(degrees2 * toRad); } function radToDeg(radians2) { return mod360(radians2 * toDeg); } var degToHeading = (degrees2) => mod360(90 - degrees2); var headingToDeg = (heading) => mod360(90 - heading); function mod360(degrees2) { return mod(degrees2, 360); } function mod2pi(radians2) { return mod(radians2, 2 * PI); } function mod180180(degrees2) { let theta = mod360(degrees2); if (theta > 180) theta -= 360; return theta; } function radToHeading(radians2) { const deg = radians2 * toDeg; return mod360(90 - deg); } function headingToRad(heading) { const deg = mod360(90 - heading); return deg * toRad; } function radToHeadingAngle(radians2) { return -radToDeg(radians2); } function headingAngleToRad(headingAngle) { return -degToRad(headingAngle); } function degreesEqual(deg1, deg2) { return mod360(deg1) === mod360(deg2); } function radsEqual(rads1, rads2) { return mod2pi(rads1) === mod2pi(rads2); } var headingsEq = degreesEqual; function subtractRadians(rad1, rad0) { let dr = mod2pi(rad1 - rad0); if (dr > PI) dr = dr - 2 * PI; return dr; } function subtractDegrees(deg1, deg0) { let dAngle = mod360(deg1 - deg0); if (dAngle > 180) dAngle = dAngle - 360; return dAngle; } function subtractHeadings(head1, head0) { return -subtractDegrees(head1, head0); } function radiansTowardXY(x, y, x1, y1) { return Math.atan2(y1 - y, x1 - x); } function headingTowardXY(x, y, x1, y1) { return radToHeading(radiansTowardXY(x, y, x1, y1)); } function degreesTowardXY(x, y, x1, y1) { return radToDeg(radiansTowardXY(x, y, x1, y1)); } function inCone(x, y, radius, coneAngle, direction, x0, y0) { if (sqDistance(x0, y0, x, y) > radius * radius) return false; const angle12 = radiansTowardXY(x0, y0, x, y); return coneAngle / 2 >= Math.abs(subtractRadians(direction, angle12)); } var sqDistance = (x, y, x1, y1) => (x - x1) ** 2 + (y - y1) ** 2; var distance = (x, y, x1, y1) => Math.sqrt(sqDistance(x, y, x1, y1)); var sqDistance3 = (x, y, z, x1, y1, z1) => (x - x1) ** 2 + (y - y1) ** 2 + (z - z1) ** 2; var distance3 = (x, y, z, x1, y1, z1) => Math.sqrt(sqDistance3(x, y, z, x1, y1, z1)); async function runModel(model, steps = 500, useSeed = true) { console.log("runModel: model", model); if (useSeed) randomSeed(); if (isString(model)) model = (await import(model)).default; if (isFunction(model)) model = new model(); await model.startup(); model.setup(); if (inMain()) { await timeoutLoop(() => { model.step(); }, steps); } else { for (let i = 0; i < steps; i++) model.step(); } return model; } function classHasStartup(Class) { console.log("classHasStartup?", Class); const str = Class.toString(); let lines = str.split("\n"); lines = lines.filter((line) => !/^ *\/\//.test(line)); lines = lines.filter((line) => /startup\(\)/.test(line)); return lines.length > 0; } function toJSON(obj, indent = 0, topLevelArrayOK = true) { let firstCall = topLevelArrayOK; const blackList = ["rectCache"]; const json = JSON.stringify( obj, (key, val) => { if (blackList.includes(key)) { return void 0; } const isAgentArray = Array.isArray(val) && val.length > 0 && Number.isInteger(val[0].id); if (isAgentArray && !firstCall) { return val.map((v) => v.id); } firstCall = false; return val; }, indent ); return json; } function sampleModel(model) { const obj = { ticks: model.ticks, model: Object.keys(model), patches: model.patches.length, patch: model.patches.oneOf(), turtles: model.turtles.length, turtle: model.turtles.oneOf(), links: model.links.length, link: model.links.oneOf() }; const json = toJSON(obj); return JSON.parse(json); } var identityFcn = (o) => o; var noopFcn = () => { }; var propFcn = (prop) => (o) => o[prop]; function arraysEqual(a1, a2) { if (a1.length !== a2.length) return false; for (let i = 0; i < a1.length; i++) { if (a1[i] !== a2[i]) return false; } return true; } function removeArrayItem(array, item) { const ix = array.indexOf(item); if (ix !== -1) { array.splice(ix, 1); } else { throw Error(`removeArrayItem: ${item} not in array`); } return array; } var arraysToString = (arrays) => arrays.map((a) => `${a}`).join(","); function forLoop(arrayOrObj, fcn) { if (arrayOrObj.slice) { for (let i = 0, len = arrayOrObj.length; i < len; i++) { fcn(arrayOrObj[i], i, arrayOrObj); } } else { Object.keys(arrayOrObj).forEach((k) => fcn(arrayOrObj[k], k, arrayOrObj)); } } function repeat(n, f, a = []) { for (let i = 0; i < n; i++) f(i, a); return a; } function step(n, step2, f) { for (let i = 0; i < n; i += step2) f(i); } function range(length) { return repeat(length, (i, a) => { a[i] = i; }); } function grep(array, regex) { return array.reduce((acc, val) => { if (regex.test(val)) acc.push(val); return acc; }, []); } function concatArrays(array1, array2) { const Type = array1.constructor; if (Type === Array) { return array1.concat(convertArrayType(array2, Array)); } const array = new Type(array1.length + array2.length); array.set(array1); array.set(array2, array1.length); return array; } function objectToString(obj, indent = 2, jsKeys = true) { let str = JSON.stringify(obj, null, indent); if (jsKeys) str = str.replace(/"([^"]+)":/gm, "$1:"); return str; } function objectLength(obj) { return Object.keys(obj).length; } var objectsEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); function oneOf(array) { return array[randomInt(array.length)]; } function otherOneOf(array, item) { if (array.length < 2) throw Error("otherOneOf: array.length < 2"); let other; do { other = oneOf(array); } while (item === other); return other; } var oneKeyOf = (obj) => oneOf(Object.keys(obj)); var oneValOf = (obj) => obj[oneKeyOf(obj)]; function sortNums(array, ascending = true) { return array.sort((a, b) => ascending ? a - b : b - a); } function sortObjs(array, fcn, ascending = true) { if (typeof fcn === "string") fcn = propFcn(fcn); const comp = (a, b) => fcn(a) - fcn(b); return array.sort((a, b) => ascending ? comp(a, b) : -comp(a, b)); } function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { const j = randomInt(i); [array[j], array[i]] = [array[i], array[j]]; } return array; } function union(a1, a2) { return Array.from(new Set(a1.concat(a2))); } function intersection(a1, a2) { const set2 = new Set(a2); return a1.filter((x) => set2.has(x)); } function difference(a1, a2) { const set2 = new Set(a2); return a1.filter((x) => !set2.has(x)); } function floatRamp(start, stop, numItems) { if (numItems <= 1) throw Error("floatRamp: numItems must be > 1"); const a = []; for (let i = 0; i < numItems; i++) { a.push(start + (stop - start) * (i / (numItems - 1))); } return a; } function integerRamp(start, stop, numItems = stop - start + 1) { return floatRamp(start, stop, numItems).map((a) => Math.round(a)); } function nestedProperty(obj, path) { if (typeof path === "string") path = path.split("."); switch (path.length) { case 1: return obj[path[0]]; case 2: return obj[path[0]][path[1]]; case 3: return obj[path[0]][path[1]][path[2]]; case 4: return obj[path[0]][path[1]][path[2]][path[3]]; default: return path.reduce((obj2, param) => obj2[param], obj); } } var arrayLast = (array) => array[array.length - 1]; var arrayMax = (array) => array.reduce((a, b) => Math.max(a, b)); var arrayMin = (array) => array.reduce((a, b) => Math.min(a, b)); var arrayExtent = (array) => [arrayMin(array), arrayMax(array)]; var arraysDiff = (a1, a2, ifcn = (i) => i) => { if (a1.length !== a2.length) return console.log("lengths differ", a1.length, a2.length); const diffs = []; for (let i = 0; i < a1.length; i++) { if (a1[i] !== a2[i]) diffs.push([ifcn(i), a1[i], a2[i]]); } return diffs; }; function arrayToMatrix(array, width, height) { if (array.length !== width * height) throw Error("arrayToMatrix: length !== width * height"); const matrix = []; for (let i = 0; i < height; i++) { const row = array.slice(i * width, (i + 1) * width); matrix.push(row); } return matrix; } var matrixToArray = (matrix) => matrix.flat(); function isOofA(data) { if (!isObject(data)) return false; return Object.values(data).every((v) => isTypedArray(v)); } function toOofA(aofo, spec) { const length = aofo.length; const keys = Object.keys(spec); const oofa = {}; keys.forEach((k) => { oofa[k] = new spec[k](length); }); forLoop(aofo, (o, i) => { keys.forEach((key) => oofa[key][i] = o[key]); }); return oofa; } function oofaObject(oofa, i, keys) { const obj = {}; keys.forEach((key) => { obj[key] = oofa[key][i]; }); return obj; } function toAofO(oofa, keys = Object.keys(oofa)) { const length = oofa[keys[0]].length; const aofo = new Array(length); forLoop(aofo, (val, i) => { aofo[i] = oofaObject(oofa, i, keys); }); return aofo; } function oofaBuffers(postData) { const buffers = []; forLoop(postData, (obj) => forLoop(obj, (a) => buffers.push(a.buffer))); return buffers; } var typeOf = (obj) => ({}).toString.call(obj).match(/\s(\w+)/)[1].toLowerCase(); var isType = (obj, string) => typeOf(obj) === string; var isOneOfTypes = (obj, array) => array.includes(typeOf(obj)); var isString = (obj) => isType(obj, "string"); var isObject = (obj) => isType(obj, "object"); var isArray = (obj) => Array.isArray(obj); var isNumber = (obj) => isType(obj, "number"); var isInteger = (n) => Number.isInteger(n); var isFunction = (obj) => isType(obj, "function"); var isImage = (obj) => isType(obj, "image"); var isCanvas = (obj) => isOneOfTypes(obj, ["htmlcanvaselement", "offscreencanvas"]); var isImageable = (obj) => isOneOfTypes(obj, [ "image", "htmlimageelement", "htmlcanvaselement", "offscreencanvas", "imagebitmap" ]); var isTypedArray = (obj) => typeOf(obj.buffer) === "arraybuffer"; var isUintArray = (obj) => /^uint.*array$/.test(typeOf(obj)); var isIntArray = (obj) => /^int.*array$/.test(typeOf(obj)); var isFloatArray = (obj) => /^float.*array$/.test(typeOf(obj)); var isArrayLike = (obj) => isArray(obj) || isTypedArray(obj); var isColorLikeArray = (obj) => isArrayLike(obj) && [3, 4].includes(obj.length) && obj.every( (i) => isInteger(i) && isBetween(i, 0, 255) || isNumber(i) && isBetween(i, 0, 1) ); function isLittleEndian() { const d32 = new Uint32Array([16909060]); return new Uint8ClampedArray(d32.buffer)[0] === 4; } function convertArrayType(array, Type) { const Type0 = array.constructor; if (Type0 === Type) return array; return Type.from(array); } function isDataSet(obj) { return typeOf(obj) === "object" && obj.width && obj.height && obj.data; } function downloadCanvas(can, name = "download.png", quality = null) { if (!(name.endsWith(".png") || name.endsWith(".jpeg"))) name = name + ".png"; const type = name.endsWith(".png") ? "image/png" : "image/jpeg"; const url2 = typeOf(can) === "string" ? can : can.toDataURL(type, quality); const link = document.createElement("a"); link.download = name; link.href = url2; link.click(); } function downloadBlob(blobable, name = "download", format = true) { if (isDataSet(blobable) && !Array.isArray(blobable.data)) blobable.data = Array.from(blobable.data); if (isTypedArray(blobable)) blobable = Array.from(blobable); if (isObject(blobable) || Array.isArray(blobable)) blobable = format ? JSON.stringify(blobable, null, 2) : JSON.stringify(blobable); const blob = typeOf(blobable) === "blob" ? blobable : new Blob([blobable]); const url2 = URL.createObjectURL(blob); const link = document.createElement("a"); link.download = name; link.href = url2; link.click(); URL.revokeObjectURL(url2); } function downloadJson(json, name = "download.json") { downloadBlob(json, name); } function downloadJsonModule(json, name = "json.js") { const string = JSON.stringify(json, null, 2); const module = `const json = ${string} export default json`; downloadBlob(module, name); } async function imagePromise(url2, preferDOM = true) { if (inMain() && preferDOM || inDeno()) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "Anonymous"; img.onload = () => resolve(img); img.onerror = () => reject(`Could not load image ${url2}`); img.src = url2; }); } else if (inWorker() || !preferDOM) { const blob = await fetch(url2).then((response) => response.blob()); return createImageBitmap(blob); } } async function fetchImage(url2) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "Anonymous"; img.onload = () => resolve(img); img.onerror = () => reject(`Could not load image ${url2}`); img.src = url2; }); } async function fetchImageBitmap(url2) { const blob = await fetchData(url2, "blob"); return createImageBitmap(blob); } function createCanvas(width, height, preferDOM = true) { if (inMain() && preferDOM) { const can = document.createElement("canvas"); can.width = width; can.height = height; return can; } else if (inDeno()) { return globalThis.createCanvas(width, height); } else if (inWorker() || !preferDOM) { return new OffscreenCanvas(width, height); } } function createCtx(width, height, preferDOM = true, attrs = {}) { const can = createCanvas(width, height, preferDOM); const ctx = can.getContext("2d", attrs); if (inDeno()) { const ctxObj = { canvas: can }; Object.setPrototypeOf(ctxObj, ctx); return ctxObj; } else { return ctx; } } function cloneCanvas(can, preferDOM = true) { const ctx = createCtx(can.width, can.height, preferDOM); ctx.drawImage(can, 0, 0); return ctx.canvas; } function resizeCtx(ctx, width, height) { const copy = cloneCanvas(ctx.canvas); ctx.canvas.width = width; ctx.canvas.height = height; ctx.drawImage(copy, 0, 0); } function setCanvasSize(can, width, height) { if (can.width !== width || can.height != height) { can.width = width; can.height = height; } } function setIdentity(ctx) { ctx.save(); ctx.resetTransform(); } function setTextProperties(ctx, font, textAlign = "center", textBaseline = "middle") { Object.assign(ctx, { font, textAlign, textBaseline }); } var bboxCtx; function stringMetrics(string, font, textAlign = "center", textBaseline = "middle") { if (!bboxCtx) bboxCtx = createCtx(0, 0); setTextProperties(bboxCtx, font, textAlign, textBaseline); const metrics = bboxCtx.measureText(string); metrics.height = // not sure how safe this is but.. metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; return metrics; } function drawText(ctx, string, x, y, color, useIdentity = true) { if (useIdentity) setIdentity(ctx); ctx.fillStyle = color.css || color; ctx.fillText(string, x, y); if (useIdentity) ctx.restore(); } function ctxImageData(ctx) { return ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); } function ctxImageColors(ctx) { const typedArray = ctxImageData(ctx).data; const colors = []; step(typedArray.length, 4, (i) => colors.push(typedArray.subarray(i, i + 4))); return colors; } function ctxImagePixels(ctx) { const imageData = ctxImageData(ctx); const pixels = new Uint32Array(imageData.data.buffer); return pixels; } function clearCtx(ctx, cssColor3 = void 0) { const { width, height } = ctx.canvas; setIdentity(ctx); if (!cssColor3 || cssColor3 === "transparent") { ctx.clearRect(0, 0, width, height); } else { cssColor3 = cssColor3.css || cssColor3; ctx.fillStyle = cssColor3; ctx.fillRect(0, 0, width, height); } ctx.restore(); } function imageToCtx(img) { const { width, height } = img; const ctx = createCtx(width, height); fillCtxWithImage(ctx, img); return ctx; } function imageToCanvas(img) { return imageToCtx(img).canvas; } function fillCtxWithImage(ctx, img) { setIdentity(ctx); ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height); ctx.restore(); } function setCtxImage(ctx, img) { setCanvasSize(ctx.canvas, img.width, img.height); fillCtxWithImage(ctx, img); } function toWindow(obj) { Object.assign(window, obj); console.log("toWindow:", Object.keys(obj).join(", ")); } function dump(model = window.model) { const { patches: ps, turtles: ts, links: ls } = model; Object.assign(window, { ps, ts, ls }); window.p = ps.length > 0 ? ps.oneOf() : {}; window.t = ts.length > 0 ? ts.oneOf() : {}; window.l = ls.length > 0 ? ls.oneOf() : {}; console.log("debug: ps, ts, ls, p, t, l dumped to window"); } function addCssLink(url2) { const link = document.createElement("link"); link.setAttribute("rel", "stylesheet"); link.setAttribute("href", url2); document.head.appendChild(link); } async function fetchCssStyle(url2) { if (url2.startsWith("/")) { console.log("fetchCssStyle relative url", url2); url2 = import.meta.resolve(url2); console.log(" absolute url", url2); } const response = await fetch(url2); if (!response.ok) throw Error(`fetchCssStyle: Not found: ${url2}`); const css = await response.text(); addCssStyle(css); return css; } function addCssStyle(css) { const style = document.createElement("style"); style.innerHTML = css; document.head.appendChild(style); } function getQueryString() { return window.location.search.substr(1); } function parseQueryString(paramsString = getQueryString()) { const results = {}; const searchParams = new URLSearchParams(paramsString); for (const pair of searchParams.entries()) { let [key, val] = pair; if (val.match(/^[0-9.]+$/) || val.match(/^[0-9.]+e[0-9]+$/)) val = Number(val); if (["true", "t", ""].includes(val)) val = true; if (["false", "f"].includes(val)) val = false; results[key] = val; } return results; } function RESTapi(parameters) { return Object.assign(parameters, parseQueryString()); } function printToPage(msg, element = document.body) { if (typeof msg === "object") { msg = JSON.stringify(msg, null, 2); } msg = "<pre>" + msg + "</pre>"; if (typeof element === "string") { element = document.getElementById(element); } element.style.fontFamily = "monospace"; element.innerHTML += msg; } function getEventXY(element, evt) { const rect = element.getBoundingClientRect(); return [evt.clientX - rect.left, evt.clientY - rect.top]; } // src/steg.js var steg_exports = {}; __export(steg_exports, { decode: () => decode, encode: () => encode, stegMsgSize: () => stegMsgSize }); async function toContext(img) { const type = typeOf(img); switch (type) { // image url or html element case "string": img = await imagePromise(img); case "htmlimageelement": return imageToCtx(img); // canvas case "htmlcanvaselement": case "offscreencanvas": return img.getContext("2d"); // image 2D context case "canvasrenderingcontext2d": return img; default: throw Error("toContext: bad img type: " + type); } } function toUint8Array(msg) { const type = typeOf(msg); switch (type) { case "number": msg = String.fromCharCode(msg); // drop thru to 'string' case "string": return new TextEncoder().encode(msg); case "uint8array": case "uint8clampedarray": return msg; default: throw Error("toUint8Array: bad msg type: " + type); } } function charToBits(char) { return [ char >> bits[0].shift, char >> bits[1].shift & bits[1].msgMask, char & bits[2].msgMask ]; } var bits = [ { shift: 5, msgMask: 7, dataMask: 248 }, // bits: 3 { shift: 3, msgMask: 3, dataMask: 252 }, // bits: 2 { shift: 0, msgMask: 7, dataMask: 248 } // bits: 3 ]; function checkSize(msg, width, height) { const imgSize = width * height; if (imgSize < msg.length) throw Error(`encode: image size < msg.length: ${imgSize} ${msg.length}`); } function stegMsgSize(imgData) { for (let i = 3; i < imgData.length; i = i + 4) { if (imgData[i] === 254) return (i - 3) / 4; } throw Error( `decode: no message terminator in image data, length = ${imgData.length}` ); } async function encode(img, msg) { const ctx = await toContext(img); const { width, height } = ctx.canvas; checkSize(msg, width, height); const msgArray = toUint8Array(msg); console.log("msg buffer", msgArray); const imageData = ctx.getImageData(0, 0, width, height); const data = imageData.data; console.log("imgageData.data", data); let ix; msgArray.forEach((char, i) => { const [ch0, ch1, ch2] = charToBits(char); ix = i * 4; data[ix] = (data[ix++] & bits[0].dataMask) + ch0; data[ix] = (data[ix++] & bits[1].dataMask) + ch1; data[ix] = (data[ix++] & bits[2].dataMask) + ch2; data[ix] = 255; }); data[ix + 4] = 254; console.log("encoded imgageData.data", data); ctx.putImageData(imageData, 0, 0); console.log("msg length", msg.length); console.log("encode: embedded msg size", stegMsgSize(data)); return ctx; } async function decode(img, returnU8 = false) { const ctx = await toContext(img); const { width, height } = ctx.canvas; const data = ctx.getImageData(0, 0, width, height).data; const msgSize = stegMsgSize(data); console.log("decode: embedded msg size", msgSize); const msgArray = new Uint8Array(msgSize); msgArray.forEach((char, i) => { let ix = i * 4; const ch0 = (bits[0].msgMask & data[ix++]) << bits[0].shift; const ch1 = (bits[1].msgMask & data[ix++]) << bits[1].shift; const ch2 = (bits[2].msgMask & data[ix++]) << bits[2].shift; msgArray[i] = ch0 + ch1 + ch2; }); console.log("decode msgArray", msgArray); if (returnU8) return msgArray; return new TextDecoder().decode(msgArray); } // vendor/turfImports.js var turfImports_exports = {}; __export(turfImports_exports, { bbox: () => bbox, bboxPolygon: () => bboxPolygon, bearingToAzimuth: () => bearingToAzimuth, booleanPointInPolygon: () => booleanPointInPolygon, collectionOf: () => collectionOf, containsNumber: () => containsNumber, convertArea: () => convertArea, convertLength: () => convertLength, coordAll: () => coordAll, coordEach: () => coordEach, coordReduce: () => coordReduce, feature: () => feature, featureCollection: () => featureCollection, featureEach: () => featureEach, featureOf: () => featureOf, featureReduce: () => featureReduce, findPoint: () => findPoint, findSegment: () => findSegment, flattenEach: () => flattenEach, flattenReduce: () => flattenReduce, geojsonType: () => geojsonType, geomEach: () => geomEach, geomReduce: () => geomReduce, geometry: () => geometry, geometryCollection: () => geometryCollection, getCoord: () => getCoord, getCoords: () => getCoords, getGeom: () => getGeom, getType: () => getType, lengthToDegrees: () => lengthToDegrees, lengthToRadians: () => lengthToRadians, lineEach: () => lineEach, lineReduce: () => lineReduce, lineString: () => lineString, lineStrings: () => lineStrings, multiLineString: () => multiLineString, multiPoint: () => multiPoint, multiPolygon: () => multiPolygon, point: () => point, points: () => points, polygon: () => polygon, polygons: () => polygons, propEach: () => propEach, propReduce: () => propReduce, radiansToLength: () => radiansToLength, segmentEach: () => segmentEach, segmentReduce: () => segmentReduce }); var earthRadius = 63710088e-1; var factors = { centimeters: earthRadius * 100, centimetres: earthRadius * 100, degrees: earthRadius / 111325, feet: earthRadius * 3.28084, inches: earthRadius * 39.37, kilometers: earthRadius / 1e3, kilometres: earthRadius / 1e3, meters: earthRadius, metres: earthRadius, miles: earthRadius / 1609.344, millimeters: earthRadius * 1e3, millimetres: earthRadius * 1e3, nauticalmiles: earthRadius / 1852, radians: 1, yards: earthRadius * 1.0936 }; var areaFactors = { acres: 247105e-9, centimeters: 1e4, centimetres: 1e4, feet: 10.763910417, hectares: 1e-4, inches: 1550.003100006, kilometers: 1e-6, kilometres: 1e-6, meters: 1, metres: 1, miles: 386e-9, millimeters: 1e6, millimetres: 1e6, yards: 1.195990046 }; function feature(geom, properties, options) { if (options === void 0) { options = {}; } var feat = { type: "Feature" }; if (options.id === 0 || options.id) { feat.id = options.id; } if (options.bbox) { feat.bbox = options.bbox; } feat.properties = properties || {}; feat.geometry = geom; return feat; } function geometry(type, coordinates, _options) { switch (type) { case "Point": return point(coordinates).geometry; case "LineString": return lineString(coordinates).geometry; case "Polygon": return polygon(coordinates).geometry; case "MultiPoint": return multiPoint(coordinates).geometry; case "MultiLineString": return multiLineString(coordinates).geometry; case "MultiPolygon": return multiPolygon(coordinates).geometry; default: throw new Error(type + " is invalid"); } } function point(coordinates, properties, options) { if (options === void 0) { options = {}; } if (!coordinates) { throw new Error("coordinates is required"); } if (!Array.isArray(coordinates)) { throw new Error("coordinates must be an Array"); } if (coordinates.length < 2) { throw new Error("coordinates must be at least 2 numbers long"); } if (!isNumber2(coordinates[0]) || !isNumber2(coordinates[1])) { throw new Error("coordinates must contain numbers"); } var geom = { type: "Point", coordinates }; return feature(geom, properties, options); } function points(coordinates, properties, options) { if (options === void 0) { options = {}; } return featureCollection(coordinates.map(function(coords) { return point(coords, properties); }), options); } function polygon(coordinates, properties, options) { if (options === void 0) { options = {}; } for (var _i = 0, coordinates_1 = coordinates; _i < coordinates_1.length; _i++) { var ring = coordinates_1[_i]; if (ring.length < 4) { throw new Error("Each LinearRing of a Polygon must have 4 or more Positions."); } for (var j = 0; j < ring[ring.length - 1].length; j++) { if (ring[ring.length - 1][j] !== ring[0][j]) { throw new Error("First and last Position are not equivalent."); } } } var geom = { type: "Polygon", coordinates }; return feature(geom, properties, options); } function polygons(coordinates, properties, options) { if (options === void 0) { options = {}; } return featureCollection(coordinates.map(function(coords) { return polygon(coords, properties); }), options); } function lineString(coordinates, properties, options) { if (options === void 0) { options = {}; } if (coordinates.length < 2) { throw new Error("coordinates must be an array of two or more positions"); } var geom = { type: "LineString", coordinates }; return feature(geom, properties, options); } function lineStrings(coordinates, properties, options) { if (options === void 0) { options = {}; } return featureCollection(coordinates.map(function(coords) { return lineString(coords, properties); }), options); } function featureCollection(features, options) { if (options === void 0) { options = {}; } var fc = { type: "FeatureCollection" }; if (options.id) { fc.id = options.id; } if (options.bbox) { fc.bbox = options.bbox; } fc.features = features; return fc; } function multiLineString(coordinates, properties, options) { if (options === void 0) { options = {}; } var geom = { type: "MultiLineString", coordinates }; return feature(geom, properties, options); } function multiPoint(coordinates, properties, options) { if (options === void 0) { options = {}; } var geom = { type: "MultiPoint", coordinates }; return feature(geom, properties, options); } function multiPolygon(coordinates, properties, options) { if (options === void 0) { options = {}; } var geom = { type: "MultiPolygon", coordinates }; return feature(geom, properties, options); } function geometryCollection(geometries3, properties, options) { if (options === void 0) { options = {}; } var geom = { type: "GeometryCollection", geometries: geometries3 }; return feature(geom, properties, options); } function radiansToLength(radians2, units) { if (units === void 0) { units = "kilometers"; } var factor = factors[units]; if (!factor) { throw new Error(units + " units is invalid"); } return radians2 * factor; } function lengthToRadians(distance2, units) { if (units === void 0) { units = "kilometers"; } var factor = factors[units]; if (!factor) { throw new Error(units + " units is invalid"); } return distance2 / factor; } function lengthToDegrees(distance2, units) { return radiansToDegrees(lengthToRadians(distance2, units)); } function bearingToAzimuth(bearing) { var angle = bearing % 360; if (angle < 0) { angle += 360; } return angle; } function radiansToDegrees(radians2) { var degrees2 = radians2 % (2 * Math.PI); return degrees2 * 180 / Math.PI; } function convertLength(length, originalUnit, finalUnit) { if (originalUnit === void 0) { originalUnit = "kilometers"; } if (finalUnit === void 0) { finalUnit = "kilometers"; } if (!(length >= 0)) { throw new Error("length must be a positive number"); } return radiansToLength(lengthToRadians(length, originalUnit), finalUnit); } function convertArea(area2, originalUnit, finalUnit) { if (originalUnit === void 0) { originalUnit = "meters"; } if (finalUnit === void 0) { finalUnit = "kilometers"; } if (!(area2 >= 0)) { throw new Error("area must be a positive number"); } var startFactor = areaFactors[originalUnit]; if (!startFactor) { throw new Error("invalid original units"); } var finalFactor = areaFactors[finalUnit]; if (!finalFactor) { throw new Error("invalid final units"); } return area2 / startFactor * finalFactor; } function isNumber2(num) { return !isNaN(num) && num !== null && !Array.isArray(num); } function isObject2(input) { return !!input && input.constructor === Object; } function coordEach(geojson, callback, excludeWrapCoord) { if (geojson === null) return; var j, k, l, geometry2, stopG, coords, geometryMaybeCollection, wrapShrink = 0, coordIndex = 0, isGeometryCollection, type = geojson.type, isFeatureCollection = type === "FeatureCollection", isFeature = type === "Feature", stop = isFeatureCollection ? geojson.features.length : 1; for (var featureIndex = 0; featureIndex < stop; featureIndex++) { geometryMaybeCollection = isFeatureCollection ? geojson.features[featureIndex].geometry : isFeature ? geojson.geometry : geojson; isGeometryCollection = geometryMaybeCollection ? geometryMaybeCollection.type === "GeometryCollection" : false; stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1; for (var geomIndex = 0; geomIndex < stopG; geomIndex++) { var multiFeatureIndex = 0; var geometryIndex = 0; geometry2 = isGeometryCollection ? geometryMaybeCollection.geometries[geomIndex] : geometryMaybeCollection; if (geometry2 === null) continue; coords = geometry2.coordinates; var geomType = geometry2.type; wrapShrink = excludeWrapCoord && (geomType === "Polygon" || geomType === "MultiPolygon") ? 1 : 0; switch (geomType) { case null: break; case "Point": if (callback( coords, coordIndex, featureIndex, multiFeatureIndex, geometryIndex ) === false) return false; coordIndex++; multiFeatureIndex++; break; case "LineString": case "MultiPoint": for (j = 0; j < coords.length; j++) { if (callback( coords[j], coordIndex, featureIndex, multiFeatureIndex, geometryIndex ) === false) return false; coordIndex++; if (geomType === "MultiPoint") multiFeatureIndex++; } if (geomType === "LineString") multiFeatureIndex++; break; case "Polygon": case "MultiLineString": for (j = 0; j < coords.length; j++) { for (k = 0; k < coords[j].length - wrapShrink; k++) { if (callback( coords[j][k], coordIndex, featureIndex, multiFeatureIndex, geometryIndex ) === false) return false; coordIndex++; } if (geomType === "MultiLineString") multiFeatureIndex++; if (geomType === "Polygon") geometryIndex++; } if (geomType === "Polygon") multiFeatureIndex++; break; case "MultiPolygon": for (j = 0; j < coords.length; j++) { geometryIndex = 0; for (k = 0; k < coords[j].length; k++) { for (l = 0; l < coords[j][k].length - wrapShrink; l++) { if (callback( coords[j][k][l], coordIndex, featureIndex, multiFeatureIndex, geometryIndex ) === false) return false; coordIndex++; } geometryIndex++; } multiFeatureIndex++; } break; case "GeometryCollection": for (j = 0; j < geometry2.geometries.length; j++) if (coordEach(geometry2.geometries[j], callback, excludeWrapCoord) === false) return false; break; default: throw new Error("Unknown Geometry Type"); } } } } function coordReduce(geojson, callback, initialValue, excludeWrapCoord) { var previousValue = initialValue; coordEach( geojson, function(currentCoord, coordIndex, featureIndex, multiFeatureIndex, geometryIndex) { if (coordIndex === 0 && initialValue === void 0) previousValue = currentCoord; else previousValue = callback( previousValue, currentCoord, coordIndex, featureIndex, multiFeatureIndex, geometryIndex ); }, excludeWrapCoord ); return previousValue; } function propEach(geojson, callback) { var i; switch (geojson.type) { case "FeatureCollection": for (i = 0; i < geojson.features.length; i++) { if (callback(geojson.features[i].properties, i) === false) break; } break; case "Feature": callback(geojson.properties, 0); break; } } function propReduce(geojson, callback, initialValue) { var previousValue = initialValue; propEach(geojson, function(currentProperties, featureIndex) { if (featureIndex === 0 && initialValue === void 0) previousValue = currentProperties; else previousValue = callback(previousValue, currentProperties, featureIndex); }); return previousValue; } function featureEach(geojson, callback) { if (geojson.type === "Feature") { callback(geojson, 0); } else if (geojson.type === "FeatureCollection") { for (var i = 0; i < geojson.features.length; i++) { if (callback(geojson.features[i], i) === false) break; } } } function featureReduce(geojson, callback, initialValue) { var previousValue = initialValue; featureEach(geojson, function(currentFeature, featureIndex) { if (featureIndex === 0 && initialValue === void 0) previousValue = currentFeature; else previousValue = callback(previousValue, currentFeature, featureIndex); }); return previousValue; } function coordAll(geojson) { var coords = []; coordEach(geojson, function(coord) { coords.push(coord); }); return coords; } function geomEach(geojson, callback) { var i, j, g, geometry2, stopG, geometryMaybeCollection, isGeometryCollection, featureProperties, featureBBox, featureId, featureIndex = 0, isFeatureCollection = geojson.type === "FeatureCollection", isFeature = geojson.type === "Feature", stop = isFeatureCollection ? geojson.features.length : 1; for (i = 0; i < stop; i++) { geometryMaybeCollection = isFeatureCollection ? geojson.features[i].geometry : isFeature ? geojson.geometry : geojson; featureProperties = isFeatureCollection ? geojson.features[i].properties : isFeature ? geojson.properties : {}; featureBBox = isFeatureCollection ? geojson.features[i].bbox : isFeature ? geojson.bbox : void 0; featureId = isFeatureCollection ? geojson.features[i].id : isFeature ? geojson.id : void 0; isGeometryCollection = geometryMaybeCollection ? geometryMaybeCollection.type === "GeometryCollection" : false; stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1; for (g = 0; g < stopG; g++) { geometry2 = isGeometryCollection ? geometryMaybeCollection.geometries[g] : geometryMaybeCollection; if (geometry2 === null) { if (callback( null, featureIndex, featureProperties, featureBBox, featureId ) === false) return false; continue; } switch (geometry2.type) { case "Point": case "LineString": case "MultiPoint": case "Polygon": case "MultiLineString": case "MultiPolygon": { if (callback( geometry2, featureIndex, featureProperties, featureBBox, featureId ) === false) return false; break; } case "GeometryCollection": { for (j = 0; j < geometry2.geometries.length; j++) { if (callback( geometry2.geometries[j], featureIndex, featureProperties, featureBBox, featureId ) === false) return false; } break; } default: throw new Error("Unknown Geometry Type");