Skip to content

Commit aa9104d

Browse files
committed
fix(benchmark): castle scene labels — readable (not mirrored), no overlap, clean names
- Billboard each 3D label to face the framing camera via LookRotation instead of a fixed 180 Y-flip, which was reversing the glyphs (verified by rendering the scene). - Strip the routing-provider prefix from the label (opencode/codex/zai/lmstudio) so it names the model, not the gateway. - Widen each grid cell to fit the longest label so adjacent names never collide, and enlarge the labels.
1 parent d2a5802 commit aa9104d

1 file changed

Lines changed: 47 additions & 2 deletions

File tree

Assets/CoreAiUnity/Tests/EditMode/CastleComparisonWindow.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,13 @@ private void BuildScene(IEnumerable<ModelEntry> selection)
338338
return;
339339
}
340340

341-
// 2) Uniform grid sized to the widest / deepest castle so nothing overlaps.
341+
// 2) Uniform grid sized so neither the castles NOR their (often wider) 3D name labels overlap.
342342
float cellWidth = placements.Max(p => p.Bounds.size.x) + _gap;
343+
float maxLabelScale = placements.Max(p => Mathf.Max(0.15f, p.Bounds.size.x * 0.035f) * _labelScale);
344+
int longestLabel = placements.Max(p => (p.Model.DisplayName + " 00/100").Length);
345+
// Rough TextMesh advance ~2.3 world units per character at scale 1.0: widen the cell just enough
346+
// to fit the longest label so adjacent names never collide, without pushing castles far apart.
347+
cellWidth = Mathf.Max(cellWidth, longestLabel * maxLabelScale * 2.3f + _gap);
343348
float cellDepth = placements.Max(p => p.Bounds.size.z) + _gap;
344349
float maxHeight = placements.Max(p => p.Bounds.size.y);
345350
int columns = Mathf.Clamp(Mathf.Min(_maxPerRow, placements.Count), 1, placements.Count);
@@ -381,6 +386,29 @@ private void BuildScene(IEnumerable<ModelEntry> selection)
381386

382387
// 3) Frame the whole comparison for the user.
383388
FrameCamera(overall);
389+
390+
// Billboard every label to actually FACE the framing camera (readable, not mirrored). A fixed
391+
// 180° Y-flip reverses the glyphs; LookRotation toward the camera keeps a correct right-vector.
392+
Camera frameCam = Camera.main;
393+
if (frameCam != null)
394+
{
395+
foreach (Transform child in root.transform)
396+
{
397+
if (!child.name.StartsWith("Label_"))
398+
{
399+
continue;
400+
}
401+
402+
// TextMesh glyphs read correctly when the mesh's +Z points AWAY from the viewer
403+
// (camera looks along the text's +Z). Pointing +Z at the camera mirrors the glyphs.
404+
Vector3 awayFromCam = child.position - frameCam.transform.position;
405+
if (awayFromCam.sqrMagnitude > 0.0001f)
406+
{
407+
child.rotation = Quaternion.LookRotation(awayFromCam, Vector3.up);
408+
}
409+
}
410+
}
411+
384412
Selection.activeGameObject = root;
385413
if (SceneView.lastActiveSceneView != null)
386414
{
@@ -519,7 +547,24 @@ private sealed class ModelEntry
519547
public string[] CastleLabels = Array.Empty<string>();
520548

521549
// Folder names use underscores as separators; present them as spaces for readability.
522-
public string DisplayName => ModelName.Replace('_', ' ');
550+
public string DisplayName
551+
{
552+
get
553+
{
554+
string s = ModelName.Replace('_', ' ');
555+
// Drop the routing-provider prefix (e.g. "opencode ", "codex ") so the label shows the
556+
// MODEL, not the gateway -- shorter labels also stop long names overlapping their neighbours.
557+
foreach (string prefix in new[] { "opencode ", "codex ", "zai ", "lmstudio " })
558+
{
559+
if (s.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
560+
{
561+
return s.Substring(prefix.Length);
562+
}
563+
}
564+
565+
return s;
566+
}
567+
}
523568

524569
public void RebuildLabels()
525570
{

0 commit comments

Comments
 (0)