Thomas Fransolet 0310d28b5e Canal VR Meta Quest : projet Unity, viewer web et documentation
Premier commit du cinquième front. Trois parties :

- `unity/MyInfoMateVR/` : le projet Unity (6000.0.83f1, URP, Meta XR SDK 205),
  un APK unique pour tous les clients. Menu flottant à sélection au regard,
  appairage, chargement de scène GLB, POI, cache de contenu, télémétrie.
- `unity-overlay/` : les mêmes scripts à recopier sur un projet Unity neuf, avec
  les pièges rencontrés consignés dans son README.
- `viewer/` : viewer et éditeur de scène web autonome (Vite, TypeScript,
  three.js), partagé avec les autres fronts.
- `docs/` : état des lieux, setup Unity, décisions d'architecture et plan
  d'exécution en 9 étapes.

La scène est décrite par un `scene.json` poussé par `adb push` : l'app le
préfère à celui embarqué dans l'APK.

Les binaires (GLB, textures de l'échantillon Sponza, DLL Meta XR) passent par
Git LFS dès ce premier commit — les y faire entrer après coup demanderait de
réécrire l'historique. Les artefacts régénérés par l'éditeur et par CMake
(`Library/`, `.utmp/`, Burst debug) sont ignorés.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 15:26:07 +02:00

124 lines
3.8 KiB
C#

using System.Collections.Generic;
namespace MyInfoMate.Vr.Menu
{
/// <summary>
/// Placement « bento » dense sur une grille de colonnes — le socle du menu flottant.
///
/// <b>Port C# de <c>myinfomate_layout</c></b> (Dart), qui sert au manager et à
/// mymuseum-visitapp ; le web refait le même placement en CSS Grid. Trois
/// implémentations, une seule règle : chaque item va à la première cellule libre,
/// parcours ligne par ligne, qui l'accueille — soit <c>grid-auto-flow: dense</c>.
///
/// <b>Porté plutôt que calculé par le serveur</b> : l'export reste une description du
/// contenu, pas une mise en page. Un placement calculé à distance ferait qu'un vieux
/// contenu en cache porterait une vieille mise en page.
/// </summary>
public static class BentoLayout
{
public struct Item
{
public string Id;
public int ColSpan;
public int RowSpan;
public Item(string id, int colSpan = 1, int rowSpan = 1)
{
Id = id;
ColSpan = colSpan;
RowSpan = rowSpan;
}
}
public struct Placement
{
public string Id;
public int Col;
public int Row;
public int ColSpan;
public int RowSpan;
}
public struct Result
{
public List<Placement> Placements;
public int RowCount;
}
public static Result Compute(IList<Item> items, int columns)
{
var cols = columns < 1 ? 1 : columns;
var occupied = new List<bool[]>();
void EnsureRow(int r)
{
while (occupied.Count <= r) occupied.Add(new bool[cols]);
}
bool Fits(int r, int c, int cs, int rs)
{
if (c + cs > cols) return false;
for (var rr = r; rr < r + rs; rr++)
{
EnsureRow(rr);
for (var cc = c; cc < c + cs; cc++)
if (occupied[rr][cc]) return false;
}
return true;
}
void Fill(int r, int c, int cs, int rs)
{
for (var rr = r; rr < r + rs; rr++)
{
EnsureRow(rr);
for (var cc = c; cc < c + cs; cc++) occupied[rr][cc] = true;
}
}
var placements = new List<Placement>();
foreach (var item in items)
{
var cs = item.ColSpan < 1 ? 1 : (item.ColSpan > cols ? cols : item.ColSpan);
var rs = item.RowSpan < 1 ? 1 : item.RowSpan;
var placed = false;
var r = 0;
while (!placed)
{
for (var c = 0; c + cs <= cols; c++)
{
if (!Fits(r, c, cs, rs)) continue;
Fill(r, c, cs, rs);
placements.Add(new Placement
{
Id = item.Id,
Col = c,
Row = r,
ColSpan = cs,
RowSpan = rs,
});
placed = true;
break;
}
if (!placed) r++;
}
}
var rowCount = occupied.Count;
while (rowCount > 0 && IsEmpty(occupied[rowCount - 1])) rowCount--;
return new Result { Placements = placements, RowCount = rowCount };
}
static bool IsEmpty(bool[] row)
{
foreach (var cell in row)
if (cell) return false;
return true;
}
}
}