feat: improve performance by cachine edges
This commit is contained in:
parent
2df3035855
commit
5f2b2f59be
@ -21,7 +21,9 @@
|
|||||||
"@threlte/flex": "^1.0.1",
|
"@threlte/flex": "^1.0.1",
|
||||||
"@types/three": "^0.159.0",
|
"@types/three": "^0.159.0",
|
||||||
"jsondiffpatch": "^0.6.0",
|
"jsondiffpatch": "^0.6.0",
|
||||||
"three": "^0.159.0"
|
"meshline": "^3.2.0",
|
||||||
|
"three": "^0.159.0",
|
||||||
|
"three.meshline": "^1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@histoire/plugin-svelte": "^0.17.9",
|
"@histoire/plugin-svelte": "^0.17.9",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<script context="module">
|
<script context="module" lang="ts">
|
||||||
const color = new Color(0x202020);
|
const color = new Color(0x202020);
|
||||||
color.convertLinearToSRGB();
|
color.convertLinearToSRGB();
|
||||||
|
|
||||||
@ -9,63 +9,72 @@
|
|||||||
color,
|
color,
|
||||||
toneMapped: false,
|
toneMapped: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const lineCache = new Map<number, BufferGeometry>();
|
||||||
|
|
||||||
|
const curve = new CubicBezierCurve(
|
||||||
|
new Vector2(0, 0),
|
||||||
|
new Vector2(0, 0),
|
||||||
|
new Vector2(0, 0),
|
||||||
|
new Vector2(0, 0),
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { T } from "@threlte/core";
|
import { T } from "@threlte/core";
|
||||||
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
|
import { MeshLineMaterial } from "@threlte/extras";
|
||||||
import { MeshBasicMaterial, type Mesh, LineBasicMaterial } from "three";
|
import { BufferGeometry, MeshBasicMaterial, Vector3 } from "three";
|
||||||
import { Color } from "three/src/math/Color.js";
|
import { Color } from "three/src/math/Color.js";
|
||||||
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
|
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
|
||||||
import { Vector3 } from "three/src/math/Vector3.js";
|
|
||||||
import { Vector2 } from "three/src/math/Vector2.js";
|
import { Vector2 } from "three/src/math/Vector2.js";
|
||||||
|
import { createEdgeGeometry } from "./createEdgeGeometry";
|
||||||
|
|
||||||
export let from: { x: number; y: number };
|
export let from: { x: number; y: number };
|
||||||
export let to: { x: number; y: number };
|
export let to: { x: number; y: number };
|
||||||
|
|
||||||
let samples = 5;
|
let samples = 5;
|
||||||
|
|
||||||
const curve = new CubicBezierCurve(
|
let geometry: BufferGeometry;
|
||||||
new Vector2(from.x, from.y),
|
|
||||||
new Vector2(from.x + 2, from.y),
|
|
||||||
new Vector2(to.x - 2, to.y),
|
|
||||||
new Vector2(to.x, to.y),
|
|
||||||
);
|
|
||||||
|
|
||||||
let points: Vector3[] = [];
|
let lastId: number | null = null;
|
||||||
|
|
||||||
let last_from_x = 0;
|
const primeA = 31;
|
||||||
let last_from_y = 0;
|
const primeB = 37;
|
||||||
|
|
||||||
let mesh: Mesh;
|
export const update = function () {
|
||||||
|
const new_x = to.x - from.x;
|
||||||
export const update = function (force = false) {
|
const new_y = to.y - from.y;
|
||||||
if (!force) {
|
const curveId = new_x * primeA + new_y * primeB;
|
||||||
const new_x = from.x + to.x;
|
if (lastId === curveId) {
|
||||||
const new_y = from.y + to.y;
|
|
||||||
if (last_from_x === new_x && last_from_y === new_y) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
last_from_x = new_x;
|
|
||||||
last_from_y = new_y;
|
const mid = new Vector2(new_x / 2, new_y / 2);
|
||||||
|
|
||||||
|
if (lineCache.has(curveId)) {
|
||||||
|
geometry = lineCache.get(curveId)!;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mid = new Vector2((from.x + to.x) / 2, (from.y + to.y) / 2);
|
|
||||||
|
|
||||||
const length = Math.floor(
|
const length = Math.floor(
|
||||||
Math.sqrt(Math.pow(to.x - from.x, 2) + Math.pow(to.y - from.y, 2)) / 4,
|
Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4,
|
||||||
);
|
);
|
||||||
samples = Math.min(Math.max(10, length), 100);
|
samples = Math.min(Math.max(10, length), 60);
|
||||||
|
|
||||||
curve.v0.set(from.x, from.y);
|
curve.v0.set(0, 0);
|
||||||
curve.v1.set(mid.x, from.y);
|
curve.v1.set(mid.x, 0);
|
||||||
curve.v2.set(mid.x, to.y);
|
curve.v2.set(mid.x, new_y);
|
||||||
curve.v3.set(to.x, to.y);
|
curve.v3.set(new_x, new_y);
|
||||||
|
|
||||||
points = curve.getPoints(samples).map((p) => new Vector3(p.x, 0, p.y));
|
const points = curve
|
||||||
|
.getPoints(samples)
|
||||||
|
.map((p) => new Vector3(p.x, 0, p.y))
|
||||||
|
.flat();
|
||||||
|
|
||||||
|
geometry = createEdgeGeometry(points);
|
||||||
|
lineCache.set(curveId, geometry);
|
||||||
};
|
};
|
||||||
|
|
||||||
update();
|
|
||||||
$: if (from || to) {
|
$: if (from || to) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
@ -91,14 +100,13 @@
|
|||||||
<T.CircleGeometry args={[0.3, 16]} />
|
<T.CircleGeometry args={[0.3, 16]} />
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
|
||||||
<T.Mesh position.y={0.5} bind:ref={mesh}>
|
{#if geometry}
|
||||||
{#key samples}
|
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
|
||||||
<MeshLineGeometry {points} />
|
|
||||||
{/key}
|
|
||||||
<MeshLineMaterial
|
<MeshLineMaterial
|
||||||
width={4}
|
width={4}
|
||||||
attenuate={false}
|
attenuate={false}
|
||||||
color={color2}
|
color={color2}
|
||||||
toneMapped={false}
|
toneMapped={false}
|
||||||
/>
|
/>
|
||||||
</T.Mesh>
|
</T.Mesh>
|
||||||
|
{/if}
|
||||||
|
100
frontend/src/lib/components/edges/createEdgeGeometry.ts
Normal file
100
frontend/src/lib/components/edges/createEdgeGeometry.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { BufferGeometry, Vector3, BufferAttribute } from 'three'
|
||||||
|
import { setXY, setXYZ, setXYZW, setXYZXYZ } from './utils'
|
||||||
|
|
||||||
|
|
||||||
|
export function createEdgeGeometry(points: Vector3[]) {
|
||||||
|
|
||||||
|
let shape = 'none'
|
||||||
|
let shapeFunction = (p: number) => 1
|
||||||
|
|
||||||
|
// When the component first runs we create the buffer geometry and allocate the buffer attributes
|
||||||
|
let pointCount = points.length
|
||||||
|
let counters: number[] = []
|
||||||
|
let counterIndex = 0
|
||||||
|
let side: number[] = []
|
||||||
|
let widthArray: number[] = []
|
||||||
|
let doubleIndex = 0
|
||||||
|
let uvArray: number[] = []
|
||||||
|
let uvIndex = 0
|
||||||
|
let indices: number[] = []
|
||||||
|
let indicesIndex = 0
|
||||||
|
|
||||||
|
if (shape === 'taper') {
|
||||||
|
shapeFunction = (p: number) => 1 * Math.pow(4 * p * (1 - p), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < pointCount; j++) {
|
||||||
|
const c = j / points.length
|
||||||
|
counters[counterIndex + 0] = c
|
||||||
|
counters[counterIndex + 1] = c
|
||||||
|
counterIndex += 2
|
||||||
|
|
||||||
|
setXY(side, doubleIndex, 1, -1)
|
||||||
|
let width = shape === 'none' ? 1 : shapeFunction(j / (pointCount - 1))
|
||||||
|
setXY(widthArray, doubleIndex, width, width)
|
||||||
|
doubleIndex += 2
|
||||||
|
|
||||||
|
setXYZW(uvArray, uvIndex, j / (pointCount - 1), 0, j / (pointCount - 1), 1)
|
||||||
|
uvIndex += 4
|
||||||
|
|
||||||
|
if (j < pointCount - 1) {
|
||||||
|
const n = j * 2
|
||||||
|
setXYZ(indices, indicesIndex, n + 0, n + 1, n + 2)
|
||||||
|
setXYZ(indices, indicesIndex + 3, n + 2, n + 1, n + 3)
|
||||||
|
indicesIndex += 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const geometry = new BufferGeometry()
|
||||||
|
// create these buffer attributes at the correct length but leave them empty for now
|
||||||
|
geometry.setAttribute('position', new BufferAttribute(new Float32Array(pointCount * 6), 3))
|
||||||
|
geometry.setAttribute('previous', new BufferAttribute(new Float32Array(pointCount * 6), 3))
|
||||||
|
geometry.setAttribute('next', new BufferAttribute(new Float32Array(pointCount * 6), 3))
|
||||||
|
// create and populate these buffer attributes
|
||||||
|
geometry.setAttribute('counters', new BufferAttribute(new Float32Array(counters), 1))
|
||||||
|
geometry.setAttribute('side', new BufferAttribute(new Float32Array(side), 1))
|
||||||
|
geometry.setAttribute('width', new BufferAttribute(new Float32Array(widthArray), 1))
|
||||||
|
geometry.setAttribute('uv', new BufferAttribute(new Float32Array(uvArray), 2))
|
||||||
|
geometry.setIndex(new BufferAttribute(new Uint16Array(indices), 1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let positions: number[] = []
|
||||||
|
let previous: number[] = []
|
||||||
|
let next: number[] = []
|
||||||
|
let positionIndex = 0
|
||||||
|
let previousIndex = 0
|
||||||
|
let nextIndex = 0
|
||||||
|
setXYZXYZ(previous, previousIndex, points[0].x, points[0].y, points[0].z)
|
||||||
|
previousIndex += 6
|
||||||
|
for (let j = 0; j < pointCount; j++) {
|
||||||
|
const p = points[j]
|
||||||
|
setXYZXYZ(positions, positionIndex, p.x, p.y, p.z)
|
||||||
|
positionIndex += 6
|
||||||
|
if (j < pointCount - 1) {
|
||||||
|
setXYZXYZ(previous, previousIndex, p.x, p.y, p.z)
|
||||||
|
previousIndex += 6
|
||||||
|
}
|
||||||
|
if (j > 0 && j + 1 <= pointCount) {
|
||||||
|
setXYZXYZ(next, nextIndex, p.x, p.y, p.z)
|
||||||
|
nextIndex += 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setXYZXYZ(
|
||||||
|
next,
|
||||||
|
nextIndex,
|
||||||
|
points[pointCount - 1].x,
|
||||||
|
points[pointCount - 1].y,
|
||||||
|
points[pointCount - 1].z
|
||||||
|
)
|
||||||
|
const positionAttribute = (geometry.getAttribute('position') as BufferAttribute).set(positions)
|
||||||
|
const previousAttribute = (geometry.getAttribute('previous') as BufferAttribute).set(previous)
|
||||||
|
const nextAttribute = (geometry.getAttribute('next') as BufferAttribute).set(next)
|
||||||
|
positionAttribute.needsUpdate = true
|
||||||
|
previousAttribute.needsUpdate = true
|
||||||
|
nextAttribute.needsUpdate = true
|
||||||
|
geometry.computeBoundingSphere()
|
||||||
|
|
||||||
|
return geometry;
|
||||||
|
|
||||||
|
}
|
34
frontend/src/lib/components/edges/utils.ts
Normal file
34
frontend/src/lib/components/edges/utils.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
export const setXYZXYZ = (array: number[], location: number, x: number, y: number, z: number) => {
|
||||||
|
array[location + 0] = x
|
||||||
|
array[location + 1] = y
|
||||||
|
array[location + 2] = z
|
||||||
|
|
||||||
|
array[location + 3] = x
|
||||||
|
array[location + 4] = y
|
||||||
|
array[location + 5] = z
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setXY = (array: number[], location: number, x: number, y: number) => {
|
||||||
|
array[location + 0] = x
|
||||||
|
array[location + 1] = y
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setXYZ = (array: number[], location: number, x: number, y: number, z: number) => {
|
||||||
|
array[location + 0] = x
|
||||||
|
array[location + 1] = y
|
||||||
|
array[location + 2] = z
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setXYZW = (
|
||||||
|
array: number[],
|
||||||
|
location: number,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
w: number
|
||||||
|
) => {
|
||||||
|
array[location + 0] = x
|
||||||
|
array[location + 1] = y
|
||||||
|
array[location + 2] = z
|
||||||
|
array[location + 3] = w
|
||||||
|
}
|
@ -749,9 +749,7 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#key $graphId}
|
|
||||||
<GraphView {nodes} {edges} {cameraPosition} />
|
<GraphView {nodes} {edges} {cameraPosition} />
|
||||||
{/key}
|
|
||||||
{:else if $status === "loading"}
|
{:else if $status === "loading"}
|
||||||
<span>Loading</span>
|
<span>Loading</span>
|
||||||
{:else if $status === "error"}
|
{:else if $status === "error"}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
const socketId = `${node.id}-${id}`;
|
const socketId = `${node.id}-${id}`;
|
||||||
|
|
||||||
const graph = getGraphManager();
|
const graph = getGraphManager();
|
||||||
|
const graphId = graph.id;
|
||||||
const inputSockets = graph.inputSockets;
|
const inputSockets = graph.inputSockets;
|
||||||
|
|
||||||
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
||||||
@ -67,11 +68,12 @@
|
|||||||
class="wrapper"
|
class="wrapper"
|
||||||
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
|
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
|
||||||
>
|
>
|
||||||
|
{#key id && graphId}
|
||||||
<div class="content" class:disabled={$inputSockets.has(socketId)}>
|
<div class="content" class:disabled={$inputSockets.has(socketId)}>
|
||||||
<NodeInput {node} {input} {id} />
|
<NodeInput {node} {input} {id} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if node.tmp?.type?.inputs?.[id].internal !== true}
|
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
|
||||||
<div
|
<div
|
||||||
class="large target"
|
class="large target"
|
||||||
on:mousedown={handleMouseDown}
|
on:mousedown={handleMouseDown}
|
||||||
@ -85,6 +87,7 @@
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/key}
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -137,6 +137,9 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load(graph: Graph) {
|
async load(graph: Graph) {
|
||||||
|
|
||||||
|
const a = performance.now();
|
||||||
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.graph = graph;
|
this.graph = graph;
|
||||||
this.status.set("loading");
|
this.status.set("loading");
|
||||||
@ -161,6 +164,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
this.status.set("idle");
|
this.status.set("idle");
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
logger.log(`Graph loaded in ${performance.now() - a}ms`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -296,7 +300,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
|
|
||||||
const nodeType = this.nodeRegistry.getNode(type);
|
const nodeType = this.nodeRegistry.getNode(type);
|
||||||
if (!nodeType) {
|
if (!nodeType) {
|
||||||
console.error(`Node type not found: ${type}`);
|
logger.error(`Node type not found: ${type}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,8 +321,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
// check if this exact edge already exists
|
// check if this exact edge already exists
|
||||||
const existingEdge = existingEdges.find(e => e[0].id === from.id && e[1] === fromSocket && e[3] === toSocket);
|
const existingEdge = existingEdges.find(e => e[0].id === from.id && e[1] === fromSocket && e[3] === toSocket);
|
||||||
if (existingEdge) {
|
if (existingEdge) {
|
||||||
console.log("Edge already exists");
|
logger.error("Edge already exists", existingEdge);
|
||||||
console.log(existingEdge)
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -327,7 +330,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
const toSocketType = to.tmp?.type?.inputs?.[toSocket]?.type;
|
const toSocketType = to.tmp?.type?.inputs?.[toSocket]?.type;
|
||||||
|
|
||||||
if (fromSocketType !== toSocketType) {
|
if (fromSocketType !== toSocketType) {
|
||||||
console.error(`Socket types do not match: ${fromSocketType} !== ${toSocketType}`);
|
logger.error(`Socket types do not match: ${fromSocketType} !== ${toSocketType}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,11 +399,9 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
|||||||
getParentsOfNode(node: Node) {
|
getParentsOfNode(node: Node) {
|
||||||
const parents = [];
|
const parents = [];
|
||||||
const stack = node.tmp?.parents?.slice(0);
|
const stack = node.tmp?.parents?.slice(0);
|
||||||
|
|
||||||
|
|
||||||
while (stack?.length) {
|
while (stack?.length) {
|
||||||
if (parents.length > 1000000) {
|
if (parents.length > 1000000) {
|
||||||
console.log("Infinite loop detected")
|
logger.warn("Infinite loop detected")
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const parent = stack.pop();
|
const parent = stack.pop();
|
||||||
|
@ -117,7 +117,6 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
const inputNode = node.tmp.inputNodes?.[key];
|
const inputNode = node.tmp.inputNodes?.[key];
|
||||||
if (inputNode) {
|
if (inputNode) {
|
||||||
if (results[inputNode.id] === undefined) {
|
if (results[inputNode.id] === undefined) {
|
||||||
console.log(inputNode, node)
|
|
||||||
throw new Error("Input node has no result");
|
throw new Error("Input node has no result");
|
||||||
}
|
}
|
||||||
inputs[key] = results[inputNode.id];
|
inputs[key] = results[inputNode.id];
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<br />
|
<br />
|
||||||
<button
|
<button
|
||||||
on:click={() =>
|
on:click={() =>
|
||||||
graphManager.load(graphManager.createTemplate("grid", 5, 5))}
|
graphManager.load(graphManager.createTemplate("grid", 10, 10))}
|
||||||
>load grid</button
|
>load grid</button
|
||||||
>
|
>
|
||||||
<br />
|
<br />
|
||||||
|
@ -38,9 +38,15 @@ importers:
|
|||||||
jsondiffpatch:
|
jsondiffpatch:
|
||||||
specifier: ^0.6.0
|
specifier: ^0.6.0
|
||||||
version: 0.6.0
|
version: 0.6.0
|
||||||
|
meshline:
|
||||||
|
specifier: ^3.2.0
|
||||||
|
version: 3.2.0(three@0.159.0)
|
||||||
three:
|
three:
|
||||||
specifier: ^0.159.0
|
specifier: ^0.159.0
|
||||||
version: 0.159.0
|
version: 0.159.0
|
||||||
|
three.meshline:
|
||||||
|
specifier: ^1.4.0
|
||||||
|
version: 1.4.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@histoire/plugin-svelte':
|
'@histoire/plugin-svelte':
|
||||||
specifier: ^0.17.9
|
specifier: ^0.17.9
|
||||||
@ -2832,6 +2838,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
/meshline@3.2.0(three@0.159.0):
|
||||||
|
resolution: {integrity: sha512-ZaJkC967GTuef7UBdO0rGPX544oIWaNo7tYedVHSoR2lje6RR16fX8IsgMxgxoYYERtjqsRWIYBSPBxG4QR84Q==}
|
||||||
|
peerDependencies:
|
||||||
|
three: '>=0.137'
|
||||||
|
dependencies:
|
||||||
|
three: 0.159.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/meshoptimizer@0.18.1:
|
/meshoptimizer@0.18.1:
|
||||||
resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
|
resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -4002,6 +4016,10 @@ packages:
|
|||||||
tweakpane: 3.1.10
|
tweakpane: 3.1.10
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/three.meshline@1.4.0:
|
||||||
|
resolution: {integrity: sha512-A8IsiMrWP8zmHisGDAJ76ZD7t/dOF/oCe/FUKNE6Bu01ZYEx8N6IlU/1Plb2aOZtAuWM2A8s8qS3hvY0OFuvOw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/three@0.159.0:
|
/three@0.159.0:
|
||||||
resolution: {integrity: sha512-eCmhlLGbBgucuo4VEA9IO3Qpc7dh8Bd4VKzr7WfW4+8hMcIfoAVi1ev0pJYN9PTTsCslbcKgBwr2wNZ1EvLInA==}
|
resolution: {integrity: sha512-eCmhlLGbBgucuo4VEA9IO3Qpc7dh8Bd4VKzr7WfW4+8hMcIfoAVi1ev0pJYN9PTTsCslbcKgBwr2wNZ1EvLInA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
Loading…
Reference in New Issue
Block a user