Merge pull request #43 from ammaraskar/full_scenegraph_render

Start rendering full scenegraph graphs with all objects
This commit is contained in:
Nahuel Rocchetti 2023-07-30 15:41:44 -03:00 committed by GitHub
commit 14a0617a14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 257 additions and 64 deletions

View file

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using OpenTS2.Common;
using OpenTS2.Components;
using OpenTS2.Files.Formats.DBPF;
@ -15,20 +16,124 @@ namespace OpenTS2.Content.DBPF.Scenegraph
public ScenegraphResourceAsset(ScenegraphResourceCollection resourceCollection) =>
(ResourceCollection) = (resourceCollection);
/// <summary>
/// This renders the full scenegraph graph with the current asset being the root object. This will traverse
/// the full graph and render any sub-resources with their proper transformations etc.
///
/// The returned game object carries a transform to convert it from sims coordinate space to unity space.
/// </summary>
public GameObject CreateRootGameObject()
{
var gameObject = CreateGameObject();
var simsTransform = new GameObject(gameObject.name + "_transform");
// Apply a transformation to convert from the sims coordinate space to unity.
simsTransform.transform.Rotate(-90, 0, 0);
simsTransform.transform.localScale = new Vector3(1, -1, 1);
gameObject.transform.SetParent(simsTransform.transform, worldPositionStays:false);
return gameObject;
}
/// <summary>
/// Creates a unity game object rendering this scenegraph resource.
/// Same as `CreateRootGameObject` except it doesn't apply the transform from sims to unity space.
/// </summary>
public GameObject CreateGameObjectForShape()
public GameObject CreateGameObject()
{
var firstResourceNode = ResourceCollection.Blocks.OfType<ResourceNodeBlock>().First();
var resourceName = firstResourceNode.ResourceName;
var shapeRef = ResourceCollection.GetBlockOfType<ShapeRefNodeBlock>();
var gameObject = new GameObject(resourceName, typeof(AssetReferenceComponent));
// Traverse the graph if present and render out any sub-resources.
if (firstResourceNode.Tree != null)
{
RenderCompositionTree(gameObject, firstResourceNode.Tree);
}
return gameObject;
}
private void RenderCompositionTree(GameObject parent, CompositionTreeNodeBlock tree)
{
foreach (var reference in tree.References)
{
switch (reference)
{
case InternalReference internalRef:
RenderInternalCompositionTreeChild(parent, internalRef);
break;
case ExternalReference externalRef:
RenderExternalCompositionTreeChild(parent, externalRef);
break;
case NullReference nullRef:
throw new ArgumentException("Got null reference in CompositionTree");
}
}
}
private void RenderInternalCompositionTreeChild(GameObject parent, InternalReference reference)
{
var block = ResourceCollection.Blocks[reference.BlockIndex];
switch (block)
{
case ShapeRefNodeBlock shapeRef:
RenderShapeRefNode(parent, shapeRef);
break;
case TransformNodeBlock transformNode:
RenderTransformNode(parent, transformNode);
break;
case ResourceNodeBlock resourceNode:
RenderResourceNode(parent, resourceNode);
break;
default:
throw new ArgumentException($"Unsupported block type in render composition tree {block}");
}
}
private void RenderExternalCompositionTreeChild(GameObject parent, ExternalReference reference)
{
var resourceKey = ResourceCollection.FileLinks[reference.FileLinksIndex];
switch (resourceKey.TypeID)
{
default:
throw new ArgumentException($"Unsupported external type in render composition tree: {resourceKey}");
}
}
private void RenderTransformNode(GameObject parent, TransformNodeBlock transformNode)
{
var transform = new GameObject("transform");
RenderCompositionTree(transform, transformNode.CompositionTree);
transform.transform.localRotation = transformNode.Rotation;
transform.transform.position = transformNode.Transform;
transform.transform.SetParent(parent.transform, worldPositionStays:false);
}
private void RenderResourceNode(GameObject parent, ResourceNodeBlock resource)
{
// TODO: handle non-external resources, maybe merge with the `CreateRootGameObject` code.
var resourceRef = resource.ResourceLocation;
Debug.Assert(resourceRef is ExternalReference);
var key = ResourceCollection.FileLinks[((ExternalReference)resourceRef).FileLinksIndex];
var resourceAsset = ContentProvider.Get().GetAsset<ScenegraphResourceAsset>(key);
var resourceObject = resourceAsset.CreateGameObject();
resourceObject.transform.SetParent(parent.transform, worldPositionStays:false);
}
private void RenderShapeRefNode(GameObject parent, ShapeRefNodeBlock shapeRef)
{
var shapeTransform = shapeRef.Renderable.Bounded.Transform;
// TODO: handle multiple shapes here.
var shapeKey = ResourceCollection.FileLinks[shapeRef.Shapes[0].Index];
if (shapeRef.Shapes.Length == 0)
{
return;
}
Debug.Assert(shapeRef.Shapes[0] is ExternalReference);
var shapeKey = ResourceCollection.FileLinks[((ExternalReference) shapeRef.Shapes[0]).FileLinksIndex];
if (shapeKey.GroupID == GroupIDs.Local)
{
// Use our groupId if the reference has a local group id.
@ -36,28 +141,17 @@ namespace OpenTS2.Content.DBPF.Scenegraph
}
var shape = ContentProvider.Get().GetAsset<ScenegraphShapeAsset>(shapeKey);
// Hold a strong reference to the shape.
parent.GetComponent<AssetReferenceComponent>().AddReference(shape);
shape.LoadModelsAndMaterials();
var gameObject = new GameObject(resourceName, typeof(AssetReferenceComponent));
// Apply a transformation to convert from the sims coordinate space to unity.
gameObject.transform.Rotate(-90, 0, 0);
gameObject.transform.localScale = new Vector3(1, -1, 1);
// Keeps a strong reference to the Shape asset.
gameObject.GetComponent<AssetReferenceComponent>().AddReference(shape);
// This is the component that holds rotations from sims space. All rotations from the game such as applying
// quaternions and angles should be performed on it or components under it.
var simsRotation = new GameObject("simsRotations");
// Render out each model.
foreach (var model in shape.Models)
{
foreach (var primitive in model.Primitives)
{
// Create an object for the primitive and parent it to the root game object.
var primitiveObject = new GameObject($"{resourceName}_{primitive.Key}", typeof(MeshFilter), typeof(MeshRenderer))
var primitiveObject = new GameObject(primitive.Key, typeof(MeshFilter), typeof(MeshRenderer))
{
transform =
{
@ -72,12 +166,9 @@ namespace OpenTS2.Content.DBPF.Scenegraph
primitiveObject.GetComponent<MeshRenderer>().material = material.GetAsUnityMaterial();
}
primitiveObject.transform.SetParent(simsRotation.transform);
}
}
simsRotation.transform.SetParent(gameObject.transform, worldPositionStays:false);
return gameObject;
primitiveObject.transform.SetParent(parent.transform, worldPositionStays:false);
}
}
}
}
}

View file

@ -46,7 +46,7 @@ namespace OpenTS2.Content
{
throw new KeyNotFoundException("Lot does not have imposter");
}
var gameObject = imposter.CreateGameObjectForShape();
var gameObject = imposter.CreateRootGameObject();
gameObject.transform.position = new Vector3(
_lotInfo.LocationX * NeighborhoodTerrainAsset.TerrainGridSize,
_lotInfo.NeighborhoodToLotHeightOffset,

View file

@ -24,6 +24,6 @@ public class LotImposterGMDCTest : MonoBehaviour
var lotImposterResource = contentProvider.GetAssetsOfType<ScenegraphResourceAsset>(TypeIDs.SCENEGRAPH_CRES)[0];
var gameObject = lotImposterResource.CreateGameObjectForShape();
var gameObject = lotImposterResource.CreateRootGameObject();
}
}

View file

@ -19,7 +19,7 @@ public class ScenegraphGMDCTest : MonoBehaviour
new ResourceKey("ufoCrash_cres", GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_CRES));
Debug.Log($"scenegraphModel: {resource.GlobalTGI}");
var gameObject = resource.CreateGameObjectForShape();
var gameObject = resource.CreateRootGameObject();
Debug.Log($"gameObject: {gameObject}");
}

View file

@ -1,4 +1,5 @@
using OpenTS2.Files.Utils;
using System.Linq;
using OpenTS2.Files.Utils;
using UnityEngine;
namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Block
@ -8,18 +9,32 @@ namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Block
/// </summary>
public class CompositionTreeNodeBlock
{
public PersistTypeInfo TypeInfo { get; }
public ObjectReference[] References { get; }
public ObjectGraphNodeBlock Graph { get; }
public CompositionTreeNodeBlock(PersistTypeInfo typeInfo, ObjectReference[] references, ObjectGraphNodeBlock graph) =>
(TypeInfo, References, Graph) = (typeInfo, references, graph);
public static CompositionTreeNodeBlock Deserialize(IoBuffer reader)
{
var typeInfo = PersistTypeInfo.Deserialize(reader);
var graph = ObjectGraphNodeBlock.Deserialize(reader);
var numberOfReferences = reader.ReadUInt32();
for (var i = 0; i < numberOfReferences; i++)
var references = new ObjectReference[reader.ReadUInt32()];
for (var i = 0; i < references.Length; i++)
{
var reference = ObjectReference.Deserialize(reader);
references[i] = ObjectReference.Deserialize(reader);
}
return new CompositionTreeNodeBlock();
return new CompositionTreeNodeBlock(typeInfo, references, graph);
}
public override string ToString()
{
return $"Type={TypeInfo} References=[{string.Join(", ", References.GetEnumerator())}]";
}
}
}

View file

@ -1,6 +1,4 @@
using System;
using OpenTS2.Files.Utils;
using UnityEngine;
using OpenTS2.Files.Utils;
namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Block
{
@ -15,8 +13,21 @@ namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Block
public string ResourceName { get; }
public ResourceNodeBlock(PersistTypeInfo blockTypeInfo, string resourceName) : base(blockTypeInfo) =>
(ResourceName) = (resourceName);
/// <summary>
/// Where this resource is stored, if the reference is missing (index of -1) then the current ResourceCollection
/// has the resource. If this is set, it could be an external reference.
/// </summary>
public ObjectReference ResourceLocation { get; }
/// <summary>
/// May be null and graph set depending on the resource node.
/// </summary>
public CompositionTreeNodeBlock Tree { get; }
public ObjectGraphNodeBlock Graph { get; }
public ResourceNodeBlock(PersistTypeInfo blockTypeInfo, string resourceName, ObjectReference resourceLocation, CompositionTreeNodeBlock tree, ObjectGraphNodeBlock graph) : base(blockTypeInfo) =>
(ResourceName, ResourceLocation, Tree, Graph) = (resourceName, resourceLocation, tree, graph);
public override string ToString()
{
@ -31,23 +42,26 @@ namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Block
var hasTreeNode = reader.ReadByte() != 0;
string resourceName;
CompositionTreeNodeBlock tree = null;
ObjectGraphNodeBlock graph = null;
if (hasTreeNode)
{
var resource = ScenegraphResource.Deserialize(reader);
var compositionTree = CompositionTreeNodeBlock.Deserialize(reader);
resourceName = resource.ResourceName;
tree = compositionTree;
}
else
{
var graph = ObjectGraphNodeBlock.Deserialize(reader);
graph = ObjectGraphNodeBlock.Deserialize(reader);
resourceName = graph.Tag;
}
var reference = ObjectReference.Deserialize(reader);
var resourceLocation = ObjectReference.Deserialize(reader);
var skinType = reader.ReadUInt32();
return new ResourceNodeBlock(blockTypeInfo, resourceName);
return new ResourceNodeBlock(blockTypeInfo, resourceName, resourceLocation, tree, graph);
}
}
}

View file

@ -4,6 +4,7 @@ using OpenTS2.Content;
using OpenTS2.Content.DBPF.Scenegraph;
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
using OpenTS2.Files.Utils;
using UnityEngine;
namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Codecs
{
@ -19,7 +20,9 @@ namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Codecs
var geometryBlock = scenegraphCollection.GetBlockOfType<GeometryNodeBlock>();
// Look up the GMDC that this GMND points to.
var gmdcKey = scenegraphCollection.FileLinks[geometryBlock.GeometryDataReference.Index];
Debug.Assert(geometryBlock.GeometryDataReference is ExternalReference);
var gmdcRef = (ExternalReference)geometryBlock.GeometryDataReference;
var gmdcKey = scenegraphCollection.FileLinks[gmdcRef.FileLinksIndex];
return new ScenegraphGeometryNodeAsset(gmdcKey);
}

View file

@ -3,26 +3,47 @@
namespace OpenTS2.Files.Formats.DBPF.Scenegraph
{
/// <summary>
/// A reference to a scenegraph object in the FileLinks of a ScenegraphResourceCollection.
/// A reference to a scenegraph object. This could be stored locally in the scenegraph resource collection or
/// have an external reference through the FileLinks of a ScenegraphResourceCollection.
/// </summary>
public struct ObjectReference
public abstract class ObjectReference
{
public int Index;
public static ObjectReference Deserialize(IoBuffer reader)
{
var referenceMissing = reader.ReadByte() == 0;
if (referenceMissing)
{
return new ObjectReference() { Index = -1 };
return new NullReference();
}
// This internally changes the reference between cIGZPersistSerializableReferent when the value is 0 or
// cIGZPersistResource2 when the value is anything else. We don't care about that yet but we might in the
// future.
var referenceType = reader.ReadByte();
var isInternal = reader.ReadByte() == 0;
var index = reader.ReadInt32();
return new ObjectReference() { Index = index };
if (isInternal)
{
return new InternalReference(index);
}
return new ExternalReference(index);
}
}
/// <summary>
/// Not sure if this is null or represents a reference from the current class.
/// </summary>
public class NullReference : ObjectReference
{
}
public class InternalReference : ObjectReference
{
public int BlockIndex;
public InternalReference(int blockIndex) => (BlockIndex) = (blockIndex);
}
public class ExternalReference : ObjectReference
{
public int FileLinksIndex;
public ExternalReference(int fileLinksIndex) => (FileLinksIndex) = (fileLinksIndex);
}
}

View file

@ -0,0 +1,46 @@
using System;
using Unity.Plastic.Newtonsoft.Json;
using UnityEngine;
namespace OpenTS2.Files.Formats.DBPF.Scenegraph
{
/// <summary>
/// This is a utility class to dump OpenTS2 scenegraph objects into a json format for debugging purposes.
/// </summary>
public class ScenegraphJsonDumper
{
public static string DumpCollection(ScenegraphResourceCollection resourceCollection)
{
return JsonConvert.SerializeObject(resourceCollection, Formatting.None, new Vector3Converter(),
new QuaternionConverter());
}
}
internal class Vector3Converter : JsonConverter<Vector3>
{
public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
internal class QuaternionConverter : JsonConverter<Quaternion>
{
public override void WriteJson(JsonWriter writer, Quaternion value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override Quaternion ReadJson(JsonReader reader, Type objectType, Quaternion existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 34a4b8a00b984da881fc2b9d1814471c
timeCreated: 1690682830

View file

@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using OpenTS2.Common;
using OpenTS2.Content;
using OpenTS2.Content.DBPF.Scenegraph;
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
using OpenTS2.Files.Utils;
using UnityEngine;
@ -40,9 +42,7 @@ namespace OpenTS2.Files.Formats.DBPF.Scenegraph
public override string ToString()
{
return "ScenegraphResourceCollection\n" +
"FileLinks: " + string.Join("\n ", FileLinks) + "\n" +
"Blocks: " + string.Join("\n ", Blocks);
return ScenegraphJsonDumper.DumpCollection(this);
}
/// <summary>

View file

@ -175,11 +175,11 @@ namespace OpenTS2.Scenes
var model = ContentProvider.Get().GetAsset<ScenegraphResourceAsset>(new ResourceKey(bridge.ResourceName,
GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_CRES));
var bridgeObject = model.CreateGameObjectForShape();
var bridgeObject = model.CreateRootGameObject();
bridgeObject.transform.position = (bridge.Road.Position.Position + bridge.PositionOffset);
// TODO: put this in a helper MonoBehavior or something.
var simsRotation = bridgeObject.transform.Find("simsRotations");
var simsRotation = bridgeObject.transform.GetChild(0);
simsRotation.localRotation = bridge.ModelOrientation;
// Parent to this component.
@ -200,11 +200,11 @@ namespace OpenTS2.Scenes
var model = ContentProvider.Get().GetAsset<ScenegraphResourceAsset>(new ResourceKey(resourceName,
GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_CRES));
var decorationObject = model.CreateGameObjectForShape();
var decorationObject = model.CreateRootGameObject();
decorationObject.transform.position = decoration.Position.Position;
// TODO: put this in a helper MonoBehavior or something.
var simsRotation = decorationObject.transform.Find("simsRotations");
var simsRotation = decorationObject.transform.GetChild(0);
simsRotation.Rotate(0, 0, decoration.Rotation);
// Parent to this component.