diff --git a/src/diagram/diagramObject.ts b/src/diagram/diagramObject.ts index 0862846..2abe86c 100644 --- a/src/diagram/diagramObject.ts +++ b/src/diagram/diagramObject.ts @@ -1,6 +1,7 @@ import { AbstractActionManager, AbstractMesh, + Color3, Curve3, GreasedLineMesh, InstancedMesh, @@ -9,6 +10,7 @@ import { Observer, Ray, Scene, + StandardMaterial, TransformNode, Vector3 } from "@babylonjs/core"; @@ -20,6 +22,21 @@ import {createLabel} from "./functions/createLabel"; import {DiagramEventObserverMask} from "./types/diagramEventObserverMask"; import log, {Logger} from "loglevel"; import {xyztovec} from "./functions/vectorConversion"; +import {AnimatedLineTexture} from "../util/animatedLineTexture"; +import {getToolboxColors} from "../toolbox/toolbox"; +import {findClosestColor} from "../util/functions/findClosestColor"; + +/** + * Converts a Color3 to a hex color string + * @param color - BabylonJS Color3 + * @returns Hex color string (e.g., '#ff0000') + */ +function color3ToHex(color: Color3): string { + const r = Math.floor(color.r * 255).toString(16).padStart(2, '0'); + const g = Math.floor(color.g * 255).toString(16).padStart(2, '0'); + const b = Math.floor(color.b * 255).toString(16).padStart(2, '0'); + return `#${r}${g}${b}`; +} type DiagramObjectOptionsType = { diagramEntity?: DiagramEntity, @@ -375,6 +392,52 @@ export class DiagramObject { curve.setPoints([p]); this._baseTransform.position = c.getPoints()[Math.floor(c.getPoints().length / 2)]; + // Update connection texture color to match the "from" mesh using toolbox color + let hexColor: string | null = null; + + // Extract color using same priority system as toDiagramEntity + if (this._fromMesh.metadata?.color) { + // Priority 1: Explicit metadata color (most reliable) + hexColor = this._fromMesh.metadata.color; + } else if (this._fromMesh instanceof InstancedMesh && this._fromMesh.sourceMesh?.id) { + // Priority 2: Extract from tool mesh ID (e.g., "tool-#box-template-#FF0000") + const toolId = this._fromMesh.sourceMesh.id; + const parts = toolId.split('-'); + if (parts.length >= 3 && parts[0] === 'tool') { + const color = parts.slice(2).join('-'); // Handle colors with dashes + if (color.startsWith('#')) { + hexColor = color.toLowerCase(); // Normalize to lowercase + } + } + } else { + // Priority 3: Fallback to material extraction + const fromMaterial = this._fromMesh.material as StandardMaterial; + if (fromMaterial) { + const fromColor = fromMaterial.diffuseColor || fromMaterial.emissiveColor || Color3.White(); + hexColor = color3ToHex(fromColor); + } + } + + if (hexColor) { + // Find the closest toolbox color + const availableColors = getToolboxColors(); + const closestColor = findClosestColor(hexColor, availableColors); + + // Get or create material + const material = curve.material as StandardMaterial; + if (material) { + // Dispose old texture if it exists + if (material.emissiveTexture) { + AnimatedLineTexture.DisposeTexture(material.emissiveTexture); + } + + // Create new colored texture using the closest toolbox color + const coloredTexture = AnimatedLineTexture.CreateColoredTexture(closestColor); + material.emissiveTexture = coloredTexture; + material.opacityTexture = coloredTexture; + } + } + // Update cached positions after successful update this._lastFromPosition = this._fromMesh.getAbsolutePosition().clone(); this._lastToPosition = this._toMesh.getAbsolutePosition().clone(); diff --git a/src/util/animatedLineTexture.ts b/src/util/animatedLineTexture.ts index 7459525..08a3fed 100644 --- a/src/util/animatedLineTexture.ts +++ b/src/util/animatedLineTexture.ts @@ -1,19 +1,74 @@ import {Texture} from "@babylonjs/core"; import {DefaultScene} from "../defaultScene"; +/** + * Creates an SVG arrow as a data URL + * @param hexColor - Hex color string (e.g., '#00ff00') + * @returns Base64-encoded SVG data URL + */ +function createArrowSvg(hexColor: string): string { + const svg = ` + + `; + return `data:image/svg+xml;base64,${btoa(svg)}`; +} + export class AnimatedLineTexture { - private static _textureColors = new Uint8Array([10, 10, 10, 10, 10, 10, 25, 25, 25, 10, 10, 255]) private static _texture: Texture; + private static _animatedTextures: Set = new Set(); + private static _animationObserverAdded: boolean = false; public static Texture() { if (!AnimatedLineTexture._texture) { - this._texture = new Texture('/assets/textures/arrow.png', DefaultScene.Scene); + this._texture = new Texture(createArrowSvg('#00ff00'), DefaultScene.Scene); this._texture.name = 'connection-texture'; this._texture.uScale = 30; - DefaultScene.Scene.onBeforeRenderObservable.add(() => { - this._texture.uOffset -= 0.01 * DefaultScene.Scene.getAnimationRatio() - }); + this._animatedTextures.add(this._texture); + + if (!this._animationObserverAdded) { + DefaultScene.Scene.onBeforeRenderObservable.add(() => { + this._animatedTextures.forEach(texture => { + texture.uOffset -= 0.01 * DefaultScene.Scene.getAnimationRatio(); + }); + }); + this._animationObserverAdded = true; + } } return this._texture; } + + /** + * Creates a new texture with a specific color + * @param hexColor - Hex color string (e.g., '#ff0000') + * @returns A new texture instance with the specified color + */ + public static CreateColoredTexture(hexColor: string): Texture { + const texture = new Texture(createArrowSvg(hexColor), DefaultScene.Scene); + texture.name = `connection-texture-${hexColor}`; + texture.uScale = 30; + + // Track this texture for animation updates + this._animatedTextures.add(texture); + + // Ensure animation observer is set up + if (!this._animationObserverAdded) { + DefaultScene.Scene.onBeforeRenderObservable.add(() => { + this._animatedTextures.forEach(t => { + t.uOffset -= 0.01 * DefaultScene.Scene.getAnimationRatio(); + }); + }); + this._animationObserverAdded = true; + } + + return texture; + } + + /** + * Removes a texture from the animation set when disposed + * @param texture - The texture to stop animating + */ + public static DisposeTexture(texture: Texture): void { + this._animatedTextures.delete(texture); + texture.dispose(); + } } \ No newline at end of file