mirror of
https://github.com/LazyDuchess/OpenTS2.git
synced 2025-01-23 00:31:47 -05:00
Merge pull request #43 from ammaraskar/full_scenegraph_render
Start rendering full scenegraph graphs with all objects
This commit is contained in:
commit
14a0617a14
12 changed files with 257 additions and 64 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}");
|
||||
}
|
||||
|
||||
|
|
|
@ -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())}]";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
3
Assets/Scripts/OpenTS2/Files/Formats/DBPF/Scenegraph/ScenegraphJsonDumper.cs.meta
generated
Normal file
3
Assets/Scripts/OpenTS2/Files/Formats/DBPF/Scenegraph/ScenegraphJsonDumper.cs.meta
generated
Normal file
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 34a4b8a00b984da881fc2b9d1814471c
|
||||
timeCreated: 1690682830
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue