Revert "Update packages. Add quadtree (still need update for ID handling instead of index)."

This reverts commit 685bd47fc4.
This commit is contained in:
Peaceultime 2025-01-28 10:27:34 +01:00
parent 685bd47fc4
commit 3f04bb3d0c
9 changed files with 22 additions and 3132 deletions

2585
bun.lock

File diff suppressed because it is too large Load Diff

BIN
bun.lockb Normal file

Binary file not shown.

View File

@ -1,12 +1,10 @@
<script lang="ts">
import type { Position } from '#shared/canvas.util';
import { Quadtree } from '#shared/physics.util';
import type CanvasNodeEditor from './canvas/CanvasNodeEditor.vue';
import type CanvasEdgeEditor from './canvas/CanvasEdgeEditor.vue';
export type Element = { type: 'node' | 'edge', id: string };
interface ActionMap
{
interface ActionMap {
remove: CanvasNode | CanvasEdge | undefined;
create: CanvasNode | CanvasEdge | undefined;
property: CanvasNode | CanvasEdge;
@ -66,22 +64,6 @@ import { clamp } from '#shared/general.utils';
import type { CanvasContent, CanvasEdge, CanvasNode } from '~/types/canvas';
const canvas = defineModel<CanvasContent>({ required: true, });
const props = defineProps<{
path: string
}>();
const quadtree = new Quadtree({ x1: -10240, x2: 10240, y1: -10240, y2: 10240 }, canvas, 12, 6);
watch(props, () => {
quadtree.clear();
if(!canvas.value.nodes)
return;
for(const node of canvas.value.nodes)
{
quadtree.insert(node.id, { x1: node.x, x2: node.x + node.width, y1: node.y, y2: node.y + node.height });
}
}, { immediate: true, });
const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
const focusing = ref<Element>(), editing = ref<Element>();

View File

@ -1,30 +1,3 @@
<template>
<span class="text-accent-blue inline-flex items-center" :class="class">
<HoverCard class="max-w-[600px] max-h-[600px] w-full overflow-auto z-[45]" :class="{'overflow-hidden !p-0': overview?.type === 'canvas'}" :disabled="!overview">
<template #content>
<Markdown v-if="overview?.type === 'markdown'" class="!px-6" :path="pathname" :filter="hash.substring(1)" popover />
<template v-else-if="overview?.type === 'canvas'"><div class="w-[600px] h-[600px] relative"><Canvas :path="pathname" /></div></template>
<span class="text-accent-blue inline-flex items-center cursor-pointer hover:text-opacity-85"><slot v-bind="$attrs"></slot></span>
</template>
<span>
<slot v-bind="$attrs"></slot>
<Icon class="w-4 h-4 inline-block" v-if="overview && overview.type !== 'markdown'" :icon="iconByType[overview.type]" />
</span>
</HoverCard>
</span>
</template>
<script setup lang="ts">
import { parseURL } from 'ufo';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { iconByType } from '#shared/general.utils';
const { href } = defineProps<{
href: string
class?: string
}>();
const { hash, pathname } = parseURL(href);
const { content } = useContent();
const overview = computed(() => content.value.find(e => e.path === pathname));
</script>

View File

@ -11,20 +11,20 @@
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
"@iconify/vue": "^4.3.0",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/sitemap": "^7.2.3",
"@nuxtjs/tailwindcss": "^6.13.1",
"@nuxtjs/sitemap": "^7.0.1",
"@nuxtjs/tailwindcss": "^6.12.2",
"@vueuse/gesture": "^2.0.0",
"@vueuse/math": "^12.5.0",
"@vueuse/nuxt": "^12.5.0",
"@vueuse/math": "^11.3.0",
"@vueuse/nuxt": "^11.3.0",
"codemirror": "^6.0.1",
"drizzle-orm": "^0.38.4",
"drizzle-orm": "^0.35.3",
"hast": "^1.0.0",
"hast-util-heading": "^3.0.0",
"hast-util-heading-rank": "^3.0.0",
"lodash.capitalize": "^4.2.1",
"mdast-util-find-and-replace": "^3.0.2",
"nodemailer": "^6.10.0",
"nuxt": "3.15.1",
"nodemailer": "^6.9.16",
"nuxt": "^3.15.0",
"nuxt-security": "^2.1.5",
"radix-vue": "^1.9.12",
"rehype-raw": "^7.0.0",
@ -37,18 +37,18 @@
"rollup-plugin-vue": "^6.0.0",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"vue": "latest",
"vue-router": "latest",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/bun": "^1.2.0",
"@types/bun": "^1.1.14",
"@types/lodash.capitalize": "^4.2.9",
"@types/nodemailer": "^6.4.17",
"@types/unist": "^3.0.3",
"better-sqlite3": "^11.8.1",
"bun-types": "^1.2.0",
"drizzle-kit": "^0.30.2",
"better-sqlite3": "^11.7.0",
"bun-types": "^1.1.42",
"drizzle-kit": "^0.26.2",
"mdast-util-to-string": "^4.0.0",
"rehype-stringify": "^10.0.1"
}

View File

@ -148,7 +148,7 @@
<div class="flex flex-row justify-between items-center gap-x-4">
<div v-if="selected.customPath" class="flex lg:items-center truncate">
<pre class="md:text-base text-sm truncate" style="direction: rtl">/{{ selected.parent !== '' ? selected.parent + '/' : '' }}</pre>
<TextInput v-model="selected.name" @input="() => {
<TextInput v-model="selected.name" @input="(e) => {
if(selected && selected.customPath)
{
selected.name = parsePath(selected.name);
@ -186,13 +186,13 @@
</SplitterGroup>
</template>
<template v-else-if="selected.type === 'canvas'">
<CanvasEditor v-if="selected.content" :modelValue="selected.content" :path="getPath(selected)" />
<CanvasEditor v-if="selected.content" :modelValue="selected.content" />
</template>
<template v-else-if="selected.type === 'map'">
<span class="flex flex-1 justify-center items-center"><ProseH3>Editeur de carte en cours de développement</ProseH3></span>
</template>
<template v-else-if="selected.type === 'file'">
<span>Modifier le contenu :</span><input type="file" @change="(e: Event) => console.log((e.target as HTMLInputElement).files?.length)" />
<span>Modifier le contenu :</span><input type="file" @change="(e) => console.log((e.target as HTMLInputElement).files?.length)" />
</template>
</div>
</div>

View File

@ -1,5 +1,5 @@
import type { CanvasContent } from '~/types/canvas';
import type { FileType } from '~/types/content';
import type { ContentMap, FileType } from '~/types/content';
export function unifySlug(slug: string | string[]): string
{

View File

@ -1,478 +0,0 @@
import type { CanvasContent } from "~/types/canvas";
import { clamp } from "./general.utils";
export function intersects(aLeft: number, aTop: number, aRight: number, aBottom: number, bLeft: number, bTop: number, bRight: number, bBottom: number): boolean
{
return aLeft <= bRight && aRight >= bLeft && aTop <= bBottom && aBottom >= bTop;
}
enum QuadConsts
{
ENodeINext = 0,
ENodeIElmt = 1,
ElmtILft = 0, ElmtIRgt = 1, ElmtITop = 2, ElmtIBtm = 3,
ElmtIId = 4,
NodeIFirst = 0,
NodeICount = 1,
PropCount = 6,
PropILft = 0, PropIRgt = 1, PropITop = 2, PropIBtm = 3,
PropIIdx = 4,
PropIDpt = 5,
}
const DEFAULT_SIZE = 128;
const MAX_BYTE_LENGTH = 1024*1024*16;
export interface AABB {
x1: number;
x2: number;
y1: number;
y2: number;
}
export class IntList
{
#data: Int32Array;
#fields: number;
#capacity: number = DEFAULT_SIZE;
#length: number = 0;
#free: number = -1;
constructor(fields: number)
{
if(fields <= 0)
throw new Error("Invalid field count");
this.#data = new Int32Array(new ArrayBuffer(this.#capacity * fields * Int32Array.BYTES_PER_ELEMENT, { maxByteLength: MAX_BYTE_LENGTH }));
this.#fields = fields;
}
get length(): number
{
return this.#length;
}
get(index: number, field: number): number
{
if(index >= this.#length || index < 0)
throw new Error("Invalid index");
if(field >= this.#fields || field < 0)
throw new Error("Invalid field");
return this.#data[index*this.#fields + field];
}
set(index: number, field: number, value: number): void
{
if(index >= this.#length || index < 0)
throw new Error("Invalid index");
if(field >= this.#fields || field < 0)
throw new Error("Invalid field");
this.#data[index*this.#fields + field] = value;
}
clear(): void
{
//Never edit the array during clear, we'll keep every data as they *must* be override before being read.
this.#length = 0;
this.#free = -1;
}
push(): number
{
const pos = (this.#length + 1) * this.#fields;
if(pos > this.#capacity * this.#fields)
{
this.#capacity *= 2;
if((this.#data.buffer as ArrayBuffer).resizable)
{
(this.#data.buffer as ArrayBuffer).resize(clamp(this.#capacity * this.#fields * Int32Array.BYTES_PER_ELEMENT, 1, MAX_BYTE_LENGTH));
}
else
{
throw new Error("Cannot resize the buffer");
}
}
return this.#length++;
}
pop(): void
{
if(this.#length <= 0)
return;
--this.#length;
}
insert(): number
{
if(this.#free !== -1)
{
const index = this.#free;
this.#free = this.#data[index * this.#fields];
return index;
}
else
return this.push();
}
erase(index: number): void
{
if(index >= this.#length || index < 0)
throw new Error("Invalid index");
this.#data[index * this.#fields] = this.#free;
this.#free = index;
}
toArray(): number[]
{
return [...this.#data.slice(0, this.#length)];
}
//DEBUG
/* printReadable(names: string[] = []): void
{
const size = this.#length * this.#fields;
for(let i = 0; i < size; i)
{
const obj: Record<string, number> = {};
for(let j = 0; j < this.#fields; ++i, ++j)
{
obj[names[j] ?? j] = this.#data[i];
}
console.log(obj);
}
} */
}
const _process = new IntList(1);
const _nodes = new IntList(QuadConsts.PropCount);
/**
* A node that contains elments is called a leaf. Its count is equals to the amount of children it holds.
* A node that contains other nodes is called a branch. Its count is equals to -1.
* The AABB of each node isn't stored and is computed on the go.
*/
export class Quadtree
{
#bounds: AABB;
#maxDepth: number = 8;
#maxElmts: number = 4;
#nodes: IntList = new IntList(2);
#enodes: IntList = new IntList(2);
#content: IntList = new IntList(5);
#ids: string[] = [];
ref: Ref<CanvasContent>;
constructor(bounds: AABB, ref: Ref<CanvasContent>, maxDepth?: number, maxElmts?: number)
{
this.#bounds = bounds;
this.#bounds.x1 = Math.round(this.#bounds.x1);
this.#bounds.x2 = Math.round(this.#bounds.x2);
this.#bounds.y1 = Math.round(this.#bounds.y1);
this.#bounds.y2 = Math.round(this.#bounds.y2);
this.#maxDepth = maxDepth ?? this.#maxDepth;
this.#maxElmts = maxElmts ?? this.#maxElmts;
this.ref = ref;
this.#nodes.insert();
this.#nodes.set(0, QuadConsts.NodeIFirst, -1);
this.#nodes.set(0, QuadConsts.NodeICount, 0);
}
fetch(x: number, y: number): string[]
{
return this.query({x1: x, x2: x, y1: y, y2: y});
}
query(aabb: AABB): string[]
{
_process.clear();
const leaves = this.#findLeaves(0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2, Math.floor(aabb.x1), Math.floor(aabb.x2), Math.floor(aabb.y1), Math.floor(aabb.y2));
const tmp: Record<number, boolean> = {};
for(let i = 0; i < leaves.length; ++i)
{
const index = leaves.get(i, QuadConsts.PropIIdx);
let node = this.#nodes.get(index, QuadConsts.NodeIFirst);
while(node !== -1)
{
const elmt = this.#enodes.get(node, QuadConsts.ENodeIElmt);
const idIndex = this.#content.get(elmt, QuadConsts.ElmtIId);
const id = this.#ids[idIndex];
const canvasNode = this.ref.value.nodes?.find(e => e.id === id)!;
if (!tmp[elmt] && intersects(aabb.x1, aabb.y1, aabb.x2, aabb.y2, canvasNode.x, canvasNode.y, canvasNode.x + canvasNode.width, canvasNode.y + canvasNode.height))
{
_process.set(_process.push(), 0, idIndex);
tmp[elmt] = true;
}
node = this.#enodes.get(node, QuadConsts.ENodeINext);
}
}
return _process.toArray().map(e => this.#ids[e]);
}
insert(id: string, aabb: AABB): number
{
const index = this.#content.insert();
const idIndex = this.#ids.push(id) - 1;
this.#content.set(index, QuadConsts.ElmtILft, Math.floor(aabb.x1));
this.#content.set(index, QuadConsts.ElmtIRgt, Math.floor(aabb.x2));
this.#content.set(index, QuadConsts.ElmtITop, Math.floor(aabb.y1));
this.#content.set(index, QuadConsts.ElmtIBtm, Math.floor(aabb.y2));
this.#content.set(index, QuadConsts.ElmtIId, idIndex);
this.#insertNode(0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2, index);
return index;
}
remove(index: number): void
{
if(index >= this.#content.length || index < 0)
throw new Error("Provided index is out of bounds.");
const leaves = this.#findLeaves(0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2, this.#content.get(index, QuadConsts.ElmtILft), this.#content.get(index, QuadConsts.ElmtIRgt), this.#content.get(index, QuadConsts.ElmtITop), this.#content.get(index, QuadConsts.ElmtIBtm))
for(let i = 0; i < leaves.length; ++i)
{
const prop = leaves.get(i, QuadConsts.PropIIdx);
let node = this.#nodes.get(prop, QuadConsts.NodeIFirst), prev = -1;
while(node !== -1 && this.#enodes.get(node, QuadConsts.ENodeIElmt) !== index)
{
prev = node;
node = this.#enodes.get(node, QuadConsts.ENodeINext);
}
if(node !== -1)
{
const next = this.#enodes.get(node, QuadConsts.ENodeINext);
if(prev == -1)
this.#nodes.set(prop, QuadConsts.NodeIFirst, next);
else
this.#enodes.set(prev, QuadConsts.ENodeINext, next);
this.#nodes.set(prop, QuadConsts.NodeICount, this.#nodes.get(prop, QuadConsts.NodeICount) - 1);
}
}
this.#content.erase(index);
}
clear(): void
{
this.#nodes.clear();
this.#content.clear();
this.#enodes.clear();
this.#ids.length = 0;
this.#nodes.insert();
this.#nodes.set(0, QuadConsts.NodeIFirst, -1);
this.#nodes.set(0, QuadConsts.NodeICount, 0);
}
cleanup(): void
{
let updated = false;
_process.clear();
if(this.#nodes.get(0, QuadConsts.NodeICount) === -1)
_process.set(_process.push(), 0, 0);
while(_process.length > 0)
{
const node = _process.get(_process.length - 1, 0);
const fc = this.#nodes.get(node, QuadConsts.NodeIFirst);
let empty = 0;
_process.pop();
for(let i = 0; i < 4; ++i)
{
const current = fc + i;
const count = this.#nodes.get(current, QuadConsts.NodeICount);
if(count === 0) //Count the amount of empty leaves
++empty
else if(count === -1) //Add this node to the check process if it's a branch
_process.set(_process.push(), 0, current);
}
if(empty === 4)
{
//Because of the way the IntList is made, it's preferable to erase in the reversed order as the last erased index becomes the first available index.
this.#nodes.erase(fc + 3);
this.#nodes.erase(fc + 2);
this.#nodes.erase(fc + 1);
this.#nodes.erase(fc + 0);
//The branch becomes a empty leaf
this.#nodes.set(node, QuadConsts.NodeICount, 0);
this.#nodes.set(node, QuadConsts.NodeIFirst, -1);
updated = true;
}
}
}
traverse(cb: (index: number, depth: number, left: number, top: number, right: number, bottom: number, leaf: boolean) => void): void
{
_nodes.clear();
insert(_nodes, 0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2);
while(_nodes.length > 0)
{
const last = _nodes.length - 1;
const node = _nodes.get(last, QuadConsts.PropIIdx);
const left = _nodes.get(last, QuadConsts.PropILft);
const right = _nodes.get(last, QuadConsts.PropIRgt);
const top = _nodes.get(last, QuadConsts.PropITop);
const bottom = _nodes.get(last, QuadConsts.PropIBtm);
const depth = _nodes.get(last, QuadConsts.PropIDpt);
_nodes.pop();
const count = this.#nodes.get(node, QuadConsts.NodeICount);
cb(node, depth, left, top, right, bottom, count !== -1);
//If it's a branch
if (count === -1)
{
const fc = this.#nodes.get(node, QuadConsts.NodeIFirst);
const mx = left + (right - left) / 2, my = top + (bottom - top) / 2;
insert(_nodes, fc + 0, depth + 1, left, mx, top, my);
insert(_nodes, fc + 1, depth + 1, mx, right, top, my);
insert(_nodes, fc + 2, depth + 1, left, mx, my, bottom);
insert(_nodes, fc + 3, depth + 1, mx, right, my, bottom);
}
}
}
#findLeaves(index: number, depth: number, lleft: number, lright: number, ltop: number, lbottom: number, eleft: number, eright: number, etop: number, ebottom: number): IntList
{
const leaves = new IntList(QuadConsts.PropCount);
_nodes.clear();
insert(_nodes, index, depth, lleft, lright, ltop, lbottom);
while (_nodes.length > 0)
{
const last = _nodes.length - 1;
const nodeLeft = _nodes.get(last, QuadConsts.PropILft);
const nodeRight = _nodes.get(last, QuadConsts.PropIRgt);
const nodeTop = _nodes.get(last, QuadConsts.PropITop);
const nodeBottom = _nodes.get(last, QuadConsts.PropIBtm);
const nodeIndex = _nodes.get(last, QuadConsts.PropIIdx);
const nodeDepth = _nodes.get(last, QuadConsts.PropIDpt);
_nodes.pop();
if(this.#nodes.get(nodeIndex, QuadConsts.NodeICount) !== -1)
insert(leaves, nodeIndex, nodeDepth, nodeLeft, nodeRight, nodeTop, nodeBottom);
else
{
const fc = this.#nodes.get(nodeIndex, QuadConsts.NodeIFirst);
const mx = nodeLeft + (nodeRight - nodeLeft) / 2, my = nodeTop + (nodeBottom - nodeTop) / 2;
if(etop <= my)
{
if(eleft <= mx) //Add a new
insert(_nodes, fc + 0, nodeDepth + 1, nodeLeft, mx, nodeTop, my);
if(eright > mx)
insert(_nodes, fc + 1, nodeDepth + 1, mx, nodeRight, nodeTop, my);
}
if(ebottom > my)
{
if(eleft <= mx)
insert(_nodes, fc + 2, nodeDepth + 1, nodeLeft, mx, my, nodeBottom);
if(eright > mx)
insert(_nodes, fc + 3, nodeDepth + 1, mx, nodeRight, my, nodeBottom);
}
}
}
return leaves;
}
#insertLeaf(index: number, depth: number, left: number, top: number, right: number, bottom: number, elmt: number): void
{
const fc = this.#nodes.get(index, QuadConsts.NodeIFirst);
this.#nodes.set(index, QuadConsts.NodeIFirst, this.#enodes.insert());
this.#enodes.set(this.#nodes.get(index, QuadConsts.NodeIFirst), QuadConsts.ENodeINext, fc);
this.#enodes.set(this.#nodes.get(index, QuadConsts.NodeIFirst), QuadConsts.ENodeIElmt, elmt);
if(this.#nodes.get(index, QuadConsts.NodeICount) === this.#maxElmts && depth < this.#maxDepth)
{
_process.clear();
//For each element of the leaf
while(this.#nodes.get(index, QuadConsts.NodeIFirst) !== -1)
{
const current = this.#nodes.get(index, QuadConsts.NodeIFirst);
const next = this.#enodes.get(current, QuadConsts.ENodeINext);
const value = this.#enodes.get(current, QuadConsts.ENodeIElmt);
//Remove the element
this.#nodes.set(index, QuadConsts.NodeIFirst, next);
this.#enodes.erase(current);
//Store it for reprocess
_process.set(_process.push(), 0, value);
}
const newFc = this.#nodes.insert();
this.#nodes.set(newFc + 0, QuadConsts.NodeIFirst, -1);
this.#nodes.set(newFc + 0, QuadConsts.NodeICount, 0);
this.#nodes.insert();
this.#nodes.set(newFc + 1, QuadConsts.NodeIFirst, -1);
this.#nodes.set(newFc + 1, QuadConsts.NodeICount, 0);
this.#nodes.insert();
this.#nodes.set(newFc + 2, QuadConsts.NodeIFirst, -1);
this.#nodes.set(newFc + 2, QuadConsts.NodeICount, 0);
this.#nodes.insert();
this.#nodes.set(newFc + 3, QuadConsts.NodeIFirst, -1);
this.#nodes.set(newFc + 3, QuadConsts.NodeICount, 0);
this.#nodes.set(index, QuadConsts.NodeIFirst, newFc);
this.#nodes.set(index, QuadConsts.NodeICount, -1);
for(let i = 0; i < _process.length; ++i)
this.#insertNode(index, depth, left, right, top, bottom, _process.get(i, 0));
}
else
{
this.#nodes.set(index, QuadConsts.NodeICount, this.#nodes.get(index, QuadConsts.NodeICount) + 1);
}
}
#insertNode(index: number, depth: number, left: number, right: number, top: number, bottom: number, elmt: number): void
{
const leaves = this.#findLeaves(index, depth, left, right, top, bottom, this.#content.get(elmt, QuadConsts.ElmtILft), this.#content.get(elmt, QuadConsts.ElmtIRgt), this.#content.get(elmt, QuadConsts.ElmtITop), this.#content.get(elmt, QuadConsts.ElmtIBtm));
for(let i = 0; i < leaves.length; ++i)
this.#insertLeaf(leaves.get(i, QuadConsts.PropIIdx), leaves.get(i, QuadConsts.PropIDpt), leaves.get(i, QuadConsts.PropILft), leaves.get(i, QuadConsts.PropITop), leaves.get(i, QuadConsts.PropIRgt), leaves.get(i, QuadConsts.PropIBtm), elmt);
}
}
function insert(list: IntList, index: number, depth: number, left: number, right: number, top: number, bottom: number): void
{
const idx = list.push();
list.set(idx, QuadConsts.PropILft, left);
list.set(idx, QuadConsts.PropIRgt, right);
list.set(idx, QuadConsts.PropITop, top);
list.set(idx, QuadConsts.PropIBtm, bottom);
list.set(idx, QuadConsts.PropIIdx, index);
list.set(idx, QuadConsts.PropIDpt, depth);
}

6
types/canvas.d.ts vendored
View File

@ -1,5 +1,3 @@
import type { Direction } from "~/shared/canvas.util";
export interface CanvasContent {
nodes?: CanvasNode[];
edges?: CanvasEdge[];
@ -24,9 +22,9 @@ export interface CanvasNode {
export interface CanvasEdge {
id: string;
fromNode: string;
fromSide: Direction;
fromSide: 'bottom' | 'top' | 'left' | 'right';
toNode: string;
toSide: Direction;
toSide: 'bottom' | 'top' | 'left' | 'right';
color?: CanvasColor;
label?: string;
};