File size: 6,361 Bytes
9d3c32a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a9d18a
 
9d3c32a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a9d18a
9d3c32a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import {
  LoadingManager,
  Object3D,
  PerspectiveCamera,
  Vector3,
  Color,
  AmbientLight,
  DirectionalLight,
  Scene,
} from "three";
import { toast } from "@/components/ui/sonner";
import { loadMeshFile } from "./meshLoaders";

// Define the interface for the URDF viewer element
export interface URDFViewerElement extends HTMLElement {
  setJointValue: (joint: string, value: number) => void;
  loadMeshFunc?: (
    path: string,
    manager: LoadingManager,
    done: (result: Object3D | null, err?: Error) => void
  ) => void;

  // Extended properties for camera fitting
  camera: PerspectiveCamera;
  controls: {
    target: Vector3;
    update: () => void;
  };
  robot: Object3D;
  redraw: () => void;
  up: string;
  scene: Scene;
}

/**
 * Creates and configures a URDF viewer element
 */
export function createUrdfViewer(
  container: HTMLDivElement,
  isDarkMode: boolean
): URDFViewerElement {
  // Clear any existing content
  container.innerHTML = "";

  // Create the urdf-viewer element
  const viewer = document.createElement("urdf-viewer") as URDFViewerElement;
  viewer.classList.add("w-full", "h-full");

  // Add the element to the container
  container.appendChild(viewer);

  // Set initial viewer properties
  viewer.setAttribute("up", "Z");
  setViewerColor(viewer, isDarkMode ? "#2c2b3a" : "#eff4ff");
  viewer.setAttribute("highlight-color", isDarkMode ? "#df6dd4" : "#b05ffe");
  viewer.setAttribute("auto-redraw", "true");
  // viewer.setAttribute("display-shadow", ""); // Enable shadows

  // Add ambient light to the scene
  const ambientLight = new AmbientLight(0xd6d6d6, 1); // Increased intensity to 0.4
  viewer.scene.add(ambientLight);

  // Add directional light for better shadows and depth
  const directionalLight = new DirectionalLight(0xffffff, 0.8);
  directionalLight.position.set(5, 30, 5);
  directionalLight.castShadow = true;
  viewer.scene.add(directionalLight);

  // Camera position is no longer adjusted automatically to prevent auto-zooming.
  // The user can control the view with the mouse.

  return viewer;
}

/**
 * Setup mesh loading function for URDF viewer
 */
export function setupMeshLoader(
  viewer: URDFViewerElement,
  urlModifierFunc: ((url: string) => string) | null
): void {
  if ("loadMeshFunc" in viewer) {
    viewer.loadMeshFunc = (
      path: string,
      manager: LoadingManager,
      done: (result: Object3D | null, err?: Error) => void
    ) => {
      // Apply URL modifier if available (for custom uploads)
      const modifiedPath = urlModifierFunc ? urlModifierFunc(path) : path;

      // If loading fails, log the error but continue
      try {
        loadMeshFile(modifiedPath, manager, (result, err) => {
          if (err) {
            console.warn(`Error loading mesh ${modifiedPath}:`, err);
            // Try to continue with other meshes
            done(null);
          } else {
            done(result);
          }
        });
      } catch (err) {
        console.error(`Exception loading mesh ${modifiedPath}:`, err);
        done(null, err as Error);
      }
    };
  }
}

/**
 * Setup event handlers for joint highlighting
 */
export function setupJointHighlighting(
  viewer: URDFViewerElement,
  setHighlightedJoint: (joint: string | null) => void
): () => void {
  const onJointMouseover = (e: Event) => {
    const customEvent = e as CustomEvent;
    setHighlightedJoint(customEvent.detail);
  };

  const onJointMouseout = () => {
    setHighlightedJoint(null);
  };

  // Add event listeners
  viewer.addEventListener("joint-mouseover", onJointMouseover);
  viewer.addEventListener("joint-mouseout", onJointMouseout);

  // Return cleanup function
  return () => {
    viewer.removeEventListener("joint-mouseover", onJointMouseover);
    viewer.removeEventListener("joint-mouseout", onJointMouseout);
  };
}

/**
 * Setup model loading and error handling
 */
export function setupModelLoading(
  viewer: URDFViewerElement,
  urdfPath: string,
  packagePath: string,
  setCustomUrdfPath: (path: string) => void,
  alternativeRobotModels: string[] = [] // Add parameter for alternative models
): () => void {
  // Add XML content type hint for blob URLs
  const loadPath =
    urdfPath.startsWith("blob:") && !urdfPath.includes("#.")
      ? urdfPath + "#.urdf" // Add extension hint if it's a blob URL
      : urdfPath;

  // Set the URDF path
  viewer.setAttribute("urdf", loadPath);
  viewer.setAttribute("package", packagePath);

  // Handle error loading
  const onLoadError = () => {
    // toast.error("Failed to load model", {
    //   description: "There was an error loading the URDF model.",
    //   duration: 3000,
    // });

    // Use the provided alternativeRobotModels instead of the global window object
    if (alternativeRobotModels.length > 0) {
      const nextModel = alternativeRobotModels[0];
      if (nextModel) {
        setCustomUrdfPath(nextModel);
        toast.info("Trying alternative model...", {
          description: `First model failed to load. Trying ${
            nextModel.split("/").pop() || "alternative model"
          }`,
          duration: 2000,
        });
      }
    }
  };

  viewer.addEventListener("error", onLoadError);
  // The 'urdf-processed' event that handled auto-zooming has been removed.

  // Return cleanup function
  return () => {
    viewer.removeEventListener("error", onLoadError);
  };
}

/**
 * Sets the background color of the URDF viewer
 */
export function setViewerColor(viewer: URDFViewerElement, color: string): void {
  // Set the ambient color for the scene
  // viewer.setAttribute("ambient-color", color);

  // Set the background color on the viewer's parent container
  const container = viewer.parentElement;
  if (container) {
    container.style.backgroundColor = color;
  }
}

/**
 * Updates the viewer's colors based on the current theme
 */
export function updateViewerTheme(
  viewer: URDFViewerElement,
  isDarkMode: boolean
): void {
  // Update the ambient color
  setViewerColor(viewer, isDarkMode ? "#2c2b3a" : "#eff4ff");
  viewer.setAttribute("highlight-color", isDarkMode ? "#df6dd4" : "#b05ffe");

  // // Update the ambient light intensity based on theme
  // viewer.scene.traverse((object) => {
  //   if (object instanceof AmbientLight) {
  //     object.intensity = isDarkMode ? 0.4 : 0.6; // Brighter in light mode
  //   }
  // });
}