UNPKG

agentscript

Version:

AgentScript Model in Model/View architecture

1,633 lines (1,629 loc) 271 kB
function inMain() { return globalThis.document !== undefined } function inWorker() { return globalThis.WorkerGlobalScope !== undefined } 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(url, type = 'blob') { const types = ['arrayBuffer', 'blob', 'json', 'text']; if (!types.includes(type)) throw Error('fetchData: data must be one of ' + types.toString()) return fetch(url).then(res => res[type]()) } async function fetchJson(url) { return fetchData(url, 'json') } async function fetchText(url) { return fetchData(url, 'text') } function toDataURL(data, type = undefined) { 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 = 1000) { 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') { } const logOnceMsgSet = 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 fps = parseFloat((steps / (ms / 1000)).toFixed(2)); Object.assign(perf, { fps, 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; } } const PI$1 = 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.0, sigma = 1.0) { const [u1, u2] = [1.0 - Math.random(), Math.random()]; const norm = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * PI$1 * 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 } const isPowerOf2 = num => (num & (num - 1)) === 0; const nextPowerOf2 = num => Math.pow(2, Math.ceil(Math.log2(num))); function mod(v, n) { return ((v % n) + n) % n } const 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 } const isBetween = (val, min, max) => min <= val && val <= max; const 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) } const toDeg$1 = 180 / Math.PI; const toRad$1 = Math.PI / 180; function degToRad(degrees) { return mod2pi(degrees * toRad$1) } function radToDeg(radians) { return mod360(radians * toDeg$1) } const degToHeading = degrees => mod360(90 - degrees); const headingToDeg = heading => mod360(90 - heading); function mod360(degrees) { return mod(degrees, 360) } function mod2pi(radians) { return mod(radians, 2 * PI$1) } function mod180180(degrees) { let theta = mod360(degrees); if (theta > 180) theta -= 360; return theta } function radToHeading(radians) { const deg = radians * toDeg$1; return mod360(90 - deg) } function headingToRad(heading) { const deg = mod360(90 - heading); return deg * toRad$1 } function radToHeadingAngle(radians) { return -radToDeg(radians) } function headingAngleToRad(headingAngle) { return -degToRad(headingAngle) } function degreesEqual(deg1, deg2) { return mod360(deg1) === mod360(deg2) } function radsEqual(rads1, rads2) { return mod2pi(rads1) === mod2pi(rads2) } const headingsEq = degreesEqual; function subtractRadians(rad1, rad0) { let dr = mod2pi(rad1 - rad0); if (dr > PI$1) dr = dr - 2 * PI$1; 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)) } const sqDistance = (x, y, x1, y1) => (x - x1) ** 2 + (y - y1) ** 2; const distance = (x, y, x1, y1) => Math.sqrt(sqDistance(x, y, x1, y1)); const sqDistance3 = (x, y, z, x1, y1, z1) => (x - x1) ** 2 + (y - y1) ** 2 + (z - z1) ** 2; const 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 undefined } 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) } const identityFcn = o => o; const noopFcn = () => {}; const 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 } const 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, step, f) { for (let i = 0; i < n; i += step) 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 } const 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 } const oneKeyOf = obj => oneOf(Object.keys(obj)); const 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((obj, param) => obj[param], obj) } } const arrayLast = array => array[array.length - 1]; const arrayMax = array => array.reduce((a, b) => Math.max(a, b)); const arrayMin = array => array.reduce((a, b) => Math.min(a, b)); const arrayExtent = array => [arrayMin(array), arrayMax(array)]; const 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 } const matrixToArray = matrix => matrix.flat(); function isOofA(data) { if (!isObject$1(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 } const typeOf = obj => ({}.toString .call(obj) .match(/\s(\w+)/)[1] .toLowerCase()); const isType = (obj, string) => typeOf(obj) === string; const isOneOfTypes = (obj, array) => array.includes(typeOf(obj)); const isString = obj => isType(obj, 'string'); const isObject$1 = obj => isType(obj, 'object'); const isArray = obj => Array.isArray(obj); const isNumber$1 = obj => isType(obj, 'number'); const isInteger = n => Number.isInteger(n); const isFunction = obj => isType(obj, 'function'); const isImage = obj => isType(obj, 'image'); const isCanvas = obj => isOneOfTypes(obj, ['htmlcanvaselement', 'offscreencanvas']); const isImageable = obj => isOneOfTypes(obj, [ 'image', 'htmlimageelement', 'htmlcanvaselement', 'offscreencanvas', 'imagebitmap', ]); const isTypedArray = obj => typeOf(obj.buffer) === 'arraybuffer'; const isUintArray = obj => /^uint.*array$/.test(typeOf(obj)); const isIntArray = obj => /^int.*array$/.test(typeOf(obj)); const isFloatArray = obj => /^float.*array$/.test(typeOf(obj)); const isArrayLike = obj => isArray(obj) || isTypedArray(obj); const isColorLikeArray = obj => isArrayLike(obj) && [3, 4].includes(obj.length) && obj.every( i => (isInteger(i) && isBetween(i, 0, 255)) || (isNumber$1(i) && isBetween(i, 0, 1)) ); function isLittleEndian() { const d32 = new Uint32Array([0x01020304]); 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 url = typeOf(can) === 'string' ? can : can.toDataURL(type, quality); const link = document.createElement('a'); link.download = name; link.href = url; 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$1(blobable) || Array.isArray(blobable)) blobable = format ? JSON.stringify(blobable, null, 2) : JSON.stringify(blobable); const blob = typeOf(blobable) === 'blob' ? blobable : new Blob([blobable]); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.download = name; link.href = url; link.click(); URL.revokeObjectURL(url); } function downloadJson(json, name = 'json.js') { 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(url, 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 ${url}`); img.src = url; }) } else if (inWorker() || !preferDOM) { const blob = await fetch(url).then(response => response.blob()); return createImageBitmap(blob) } } async function fetchImage(url) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'Anonymous'; img.onload = () => resolve(img); img.onerror = () => reject(`Could not load image ${url}`); img.src = url; }) } async function fetchImageBitmap(url) { const blob = await fetchData(url, '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 }); } let 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 = 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, cssColor = undefined) { const { width, height } = ctx.canvas; setIdentity(ctx); if (!cssColor || cssColor === 'transparent') { ctx.clearRect(0, 0, width, height); } else { cssColor = cssColor.css || cssColor; ctx.fillStyle = cssColor; 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(url) { const link = document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('href', url); document.head.appendChild(link); } async function fetchCssStyle(url) { if (url.startsWith('../')) { console.log('fetchCssStyle relative url', url); url = import.meta.resolve(url); console.log(' absolute url', url); } const response = await fetch(url); if (!response.ok) throw Error(`fetchCssStyle: Not found: ${url}`) 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] } var util = /*#__PURE__*/Object.freeze({ __proto__: null, AsyncFunction: AsyncFunction, PI: PI$1, 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$1, isObject: isObject$1, 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$1, toJSON: toJSON, toOofA: toOofA, toRad: toRad$1, toWindow: toWindow, typeOf: typeOf, union: union, waitUntilDone: waitUntilDone, warn: warn, wrap: wrap }); async function toContext(img) { const type = typeOf(img); switch (type) { case 'string': img = await imagePromise(img); case 'htmlimageelement': return imageToCtx(img) case 'htmlcanvaselement': case 'offscreencanvas': return img.getContext('2d') 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); 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, ] } const bits = [ { shift: 5, msgMask: 0b00000111, dataMask: 0b11111000 }, { shift: 3, msgMask: 0b00000011, dataMask: 0b11111100 }, { shift: 0, msgMask: 0b00000111, dataMask: 0b11111000 }, ]; 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) } var steg = /*#__PURE__*/Object.freeze({ __proto__: null, decode: decode, encode: encode, stegMsgSize: stegMsgSize }); var earthRadius = 6371008.8; var factors = { centimeters: earthRadius * 100, centimetres: earthRadius * 100, degrees: earthRadius / 111325, feet: earthRadius * 3.28084, inches: earthRadius * 39.37, kilometers: earthRadius / 1000, kilometres: earthRadius / 1000, meters: earthRadius, metres: earthRadius, miles: earthRadius / 1609.344, millimeters: earthRadius * 1000, millimetres: earthRadius * 1000, nauticalmiles: earthRadius / 1852, radians: 1, yards: earthRadius * 1.0936, }; var areaFactors = { acres: 0.000247105, centimeters: 10000, centimetres: 10000, feet: 10.763910417, hectares: 0.0001, inches: 1550.003100006, kilometers: 0.000001, kilometres: 0.000001, meters: 1, metres: 1, miles: 3.86e-7, millimeters: 1000000, millimetres: 1000000, 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 (!isNumber(coordinates[0]) || !isNumber(coordinates[1])) { throw new Error("coordinates must contain numbers"); } var geom = { type: "Point", coordinates: 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: 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: 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: coordinates, }; return feature(geom, properties, options); } function multiPoint(coordinates, properties, options) { if (options === void 0) { options = {}; } var geom = { type: "MultiPoint", coordinates: coordinates, }; return feature(geom, properties, options); } function multiPolygon(coordinates, properties, options) { if (options === void 0) { options = {}; } var geom = { type: "MultiPolygon", coordinates: coordinates, }; return feature(geom, properties, options); } function geometryCollection(geometries, properties, options) { if (options === void 0) { options = {}; } var geom = { type: "GeometryCollection", geometries: geometries, }; return feature(geom, properties, options); } function radiansToLength(radians, units) { if (units === void 0) { units = "kilometers"; } var factor = factors[units]; if (!factor) { throw new Error(units + " units is invalid"); } return radians * factor; } function lengthToRadians(distance, units) { if (units === void 0) { units = "kilometers"; } var factor = factors[units]; if (!factor) { throw new Error(units + " units is invalid"); } return distance / factor; } function lengthToDegrees(distance, units) { return radiansToDegrees(lengthToRadians(distance, units)); } function bearingToAzimuth(bearing) { var angle = bearing % 360; if (angle < 0) { angle += 360; } return angle; } function radiansToDegrees(radians) { var degrees = radians % (2 * Math.PI); return (degrees * 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(area, originalUnit, finalUnit) { if (originalUnit === void 0) { originalUnit = "meters"; } if (finalUnit === void 0) { finalUnit = "kilometers"; } if (!(area >= 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 (area / startFactor) * finalFactor; } function isNumber(num) { return !isNaN(num) && num !== null && !Array.isArray(num); } function isObject(input) { return !!input && input.constructor === Object; } function coordEach(geojson, callback, excludeWrapCoord) { if (geojson === null) return; var j, k, l, geometry, 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; geometry = isGeometryCollection ? geometryMaybeCollection.geometries[geomIndex] : geometryMaybeCollection; if (geometry === null) continue; coords = geometry.coordinates; var geomType = geometry.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 < geometry.geometries.length; j++) if ( coordEach(geometry.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 === undefined) 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 === undefined) 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 === undefined) 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, geometry, 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 : undefined; featureId = isFeatureCollection ? geojson.features[i].id : isFeature ? geojson.id : undefined; isGeometryCollection = geometryMaybeCollection ? geometryMaybeCollection.type === "GeometryCollection" : false; stopG = isGeometryCollection ? geometryMaybeCollection.geometries.length : 1; for (g = 0; g < stopG; g++) { geometry = isGeometryCollection ? geometryMaybeCollection.geometries[g] : geometryMaybeCollection; if (geometry === null) { if ( callback( null, featureIndex, featureProperties, featureBBox, featureId ) === false ) return false; continue; } switch (geometry.type) { case "Point": case "LineString": case "MultiPoint": case "Polygon": case "MultiLineString": case "MultiPolygon": { if ( callback( geometry, featureIndex, featureProperties, featureBBox, featureId ) === false ) return false; break; } case "GeometryCollection": { for (j = 0; j < geometry.geometries.length; j++) { if ( callback( geometry.