using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine;
namespace MyInfoMate.Vr.Manifest
{
///
/// Miroir exact du schéma §2.2 de la conception. Ce fichier et
/// viewer/src/manifest.ts décrivent la même chose : modifier l'un
/// sans l'autre casse le test croisé de S3.
///
/// Les coordonnées sont ici telles qu'écrites dans le manifeste, donc en
/// convention glTF. Elles ne deviennent des coordonnées Unity qu'en passant par
/// — nulle part ailleurs.
///
public class SceneManifest
{
public const int SupportedManifestVersion = 1;
public const string ExpectedCoordinateSystem = "gltf/y-up/right-handed/meters";
/// Version du format. Distincte de .
public int ManifestVersion = 1;
public string SceneId;
public string InstanceId;
public string ConfigurationId;
/// Version du contenu : +1 à chaque publication. C'est elle que le
/// cache compare pour savoir qu'il y a du nouveau.
public int Version;
public string PublishedAt;
public List Languages = new List();
public string DefaultLanguage;
public string CoordinateSystem;
public NavigationManifest Navigation = new NavigationManifest();
public EnvironmentManifest Environment = new EnvironmentManifest();
public WorldManifest World;
public List Objects = new List();
public List Personas = new List();
public List Hotspots = new List();
public List Assets = new List();
public BudgetManifest Budget;
public ProvenanceManifest Provenance;
[JsonIgnore] Dictionary _assetsById;
///
/// assets[] est la seule liste que le téléchargeur parcourt (§2.3) : tout
/// le reste ne porte que des identifiants. D'où cet index.
///
public SceneAsset FindAsset(string assetId)
{
if (string.IsNullOrEmpty(assetId)) return null;
if (_assetsById == null)
{
_assetsById = new Dictionary();
foreach (var asset in Assets)
if (asset != null && !string.IsNullOrEmpty(asset.Id))
_assetsById[asset.Id] = asset;
}
return _assetsById.TryGetValue(assetId, out var found) ? found : null;
}
}
///
/// Position + rotation + échelle en convention glTF. Les accesseurs Unity passent
/// tous par : c'est ce qui garantit qu'il n'existe
/// qu'une conversion dans le projet.
///
public class TransformManifest
{
public float[] Position = { 0f, 0f, 0f };
public float[] Rotation = { 0f, 0f, 0f, 1f };
public float[] Scale = { 1f, 1f, 1f };
public Vector3 UnityPosition => Scene.GltfSpace.Position(Position ?? new[] { 0f, 0f, 0f });
public Quaternion UnityRotation =>
Scene.GltfSpace.Rotation(Rotation ?? new[] { 0f, 0f, 0f, 1f });
public Vector3 UnityScale => Scene.GltfSpace.Scale(Scale ?? new[] { 1f, 1f, 1f });
public void ApplyTo(Transform target)
{
target.localPosition = UnityPosition;
target.localRotation = UnityRotation;
target.localScale = UnityScale;
}
}
public class NavigationManifest
{
/// Le plafond dur du §2.2, appliqué serveur et client.
public const float MaxRadiusMeters = 3f;
public TransformManifest Spawn = new TransformManifest();
public float RadiusMeters = MaxRadiusMeters;
public bool ShowBoundary = true;
public float ClampedRadiusMeters => Mathf.Clamp(RadiusMeters, 0.5f, MaxRadiusMeters);
}
public class EnvironmentManifest
{
public string LightingPreset = "neutral-indoor";
public string HdriAssetId;
public float Exposure = 1f;
}
///
/// kind est la couture V2 : la V1 ne lit que et
/// refuse le reste explicitement. Un refus lisible vaut mieux qu'une scène
/// vide (§2.3).
///
public class WorldManifest
{
public string Kind = "mesh";
public string AssetId;
public TransformManifest Transform = new TransformManifest();
public World3DKind ParsedKind =>
Kind switch
{
"mesh" => World3DKind.Mesh,
"splat" => World3DKind.Splat,
"panorama" => World3DKind.Panorama,
_ => World3DKind.Unknown
};
}
public enum World3DKind
{
Mesh,
Splat,
Panorama,
Unknown
}
public class SceneObject
{
public string Id;
public string Label;
public string AssetId;
public TransformManifest Transform = new TransformManifest();
}
public class ScenePersona
{
public string Id;
/// Null en V1 : la couture vers l'entité Persona du lot 7 Studio.
public string PersonaId;
public string Name;
public string AssetId;
public TransformManifest Transform = new TransformManifest();
public AnimationManifest Animation = new AnimationManifest();
public bool GazeAtVisitor = true;
public List Audio = new List();
public List Script = new List();
}
public class AnimationManifest
{
public string IdleClip = "Idle";
public bool Loop = true;
}
public class SceneHotspot
{
public string Id;
/// Un hotspot est un GeoPoint côté serveur (§3.2).
public int GeoPointId;
public TransformManifest Transform = new TransformManifest();
public List Title = new List();
public List Description = new List();
public List Contents = new List();
}
public class HotspotContent
{
public int Order;
public List Title = new List();
public List Description = new List();
public string AssetId;
}
///
/// Toutes les langues arrivent d'un coup : un casque en borne change de langue à
/// chaud, l'export mono-langue existant ne suffit pas (§2.3).
///
public class LocalizedText
{
public string Language;
public string Value;
}
public class LocalizedAsset
{
public string Language;
public string AssetId;
}
public class SceneAsset
{
public string Id;
public string ResourceId;
public string Url;
public string MimeType;
public long SizeBytes;
/// Le seul moyen d'un delta réel côté casque (§2.4).
public string Sha256;
/// KTX2 / Meshopt viendront ici, sans casser le contrat.
public List Variants = new List();
}
public class AssetVariant
{
public string Kind;
public string Url;
public string MimeType;
public long SizeBytes;
public string Sha256;
}
public class BudgetManifest
{
public long TotalBytes;
public long LimitBytes;
}
public class ProvenanceManifest
{
public bool AiGenerated;
public string RightsHolder;
public List Sources = new List();
}
}