/// <summary> /// Post destroy handling: destroy all sub-part objects. /// </summary> /// <param name="obj"></param> /// <param name="msg"></param> /// <param name="packet"></param> /// <param name="reader"></param> /// <returns></returns> protected override Error PostHandleMessage(DestroyMessage msg, ShapeCache cache, int shapeIndex) { if (shapeIndex < 0) { // Nothing to delete. // Delete of invalid object is allowed. return(new Error()); } PartSet partSet = cache.GetShapeDataByIndex <PartSet>(shapeIndex); if (partSet != null) { // Remove from the registered mesh list. for (int i = 0; i < partSet.MeshIDs.Length; ++i) { List <PartSet> parts; if (_registeredParts.TryGetValue(partSet.MeshIDs[i], out parts)) { // Remove from the list. parts.RemoveAll((PartSet cmp) => { return(cmp == partSet); }); } } } return(new Error()); }
/// <summary> /// Overridden to read optional point cloud indices. /// </summary> /// <param name="msg">Message header.</param> /// <param name="packet">Data packet.</param> /// <param name="reader">Data packet reader.</param> /// <returns></returns> protected override Error HandleMessage(DataMessage msg, PacketBuffer packet, BinaryReader reader) { ShapeCache cache = (msg.ObjectID == 0) ? _transientCache : _shapeCache; int shapeIndex = (msg.ObjectID == 0) ? _lastTransientIndex : cache.GetShapeIndex(msg.ObjectID); if (shapeIndex < 0) { return(new Error(ErrorCode.InvalidObjectID, msg.ObjectID)); } PointsComponent pointsComp = cache.GetShapeDataByIndex <PointsComponent>(shapeIndex); // Read index offset and count. uint offset = reader.ReadUInt32(); uint count = reader.ReadUInt32(); int[] indices = pointsComp.Indices; for (uint i = 0; i < count; ++i) { indices[i + offset] = reader.ReadInt32(); } pointsComp.IndicesDirty = true; if (pointsComp.IndexCount != 0 && offset + count >= pointsComp.IndexCount) { // Done. Register for the mesh. RegisterForMesh(pointsComp); } return(new Error()); }
/// <summary> /// Creates a text shape for serialisation. /// </summary> /// <param name="shapeComponent">The component to create a shape for.</param> /// <returns>A shape instance suitable for configuring to generate serialisation messages.</returns> protected override Shapes.Shape CreateSerialisationShape(ShapeCache cache, int shapeIndex, CreateMessage shape) { TextShapeData textData = cache.GetShapeDataByIndex <TextShapeData>(shapeIndex); // Note: initialise position to zero. SetAttributes() below will overwrite this. var textShape = new Shapes.Text3D(textData.Text, shape.ObjectID, shape.Category, Maths.Vector3.Zero); textShape.SetAttributes(shape.Attributes); return(textShape); }
/// <summary> /// Handle additional <see cref="UpdateMessage"/> data. /// </summary> /// <param name="obj"></param> /// <param name="msg"></param> /// <param name="packet"></param> /// <param name="reader"></param> /// <returns></returns> protected override Error PostHandleMessage(UpdateMessage msg, PacketBuffer packet, BinaryReader reader, ShapeCache cache, int shapeIndex) { TextShapeData textData = cache.GetShapeDataByIndex <TextShapeData>(shapeIndex); // textData.Mesh.fontSize = (int)msg.Attributes.ScaleZ; // textData.Mesh.color = Maths.ColourExt.ToUnity32(new Maths.Colour(msg.Attributes.Colour)); textData.ScreenFacing = (msg.Flags & (ushort)Text3DFlag.ScreenFacing) != 0; cache.SetShapeDataByIndex(shapeIndex, textData); return(base.PostHandleMessage(msg, packet, reader, cache, shapeIndex)); }
/// <summary> /// Creates a mesh set shape for serialising the mesh and its resource references. /// </summary> /// <param name="shapeComponent">The component to create a shape for.</param> /// <returns>A shape instance suitable for configuring to generate serialisation messages.</returns> protected override Shapes.Shape CreateSerialisationShape(ShapeCache cache, int shapeIndex, CreateMessage shapeData) { // Start by building the resource list. PartSet partSet = cache.GetShapeDataByIndex <PartSet>(shapeIndex); Shapes.MeshSet meshSet = new Shapes.MeshSet(shapeData.ObjectID, shapeData.Category); meshSet.SetAttributes(shapeData.Attributes); for (int i = 0; i < partSet.MeshIDs.Length; ++i) { MeshResourcePlaceholder part = new MeshResourcePlaceholder(partSet.MeshIDs[i]); meshSet.AddPart(part, Tes.Maths.Matrix4Ext.FromUnity(partSet.Transforms[i])); } return(meshSet); }
/// <summary> /// Creates a point cloud shape for serialising <paramref name="shapeComponent"/> and its point data. /// </summary> /// <param name="shapeComponent">The component to create a shape for.</param> /// <returns>A shape instance suitable for configuring to generate serialisation messages.</returns> protected override Shapes.Shape CreateSerialisationShape(ShapeCache cache, int shapeIndex, CreateMessage shapeData) { PointsComponent pointsComp = cache.GetShapeDataByIndex <PointsComponent>(shapeIndex); Shapes.PointCloudShape points = new Shapes.PointCloudShape(new MeshResourcePlaceholder(pointsComp.MeshID), shapeData.ObjectID, shapeData.Category, pointsComp.PointScale); points.SetAttributes(shapeData.Attributes); if (pointsComp.IndexCount > 0) { points.SetIndices(pointsComp.Indices); } return(points); }
/// <summary> /// Post destroy message: destroy sub-objects. /// </summary> /// <param name="obj"></param> /// <param name="msg"></param> /// <param name="packet"></param> /// <param name="reader"></param> /// <returns></returns> protected override Error PostHandleMessage(DestroyMessage msg, ShapeCache cache, int shapeIndex) { PointsComponent pointsComp = cache.GetShapeDataByIndex <PointsComponent>(shapeIndex); if (pointsComp != null) { // Remove from the registered mesh list. List <PointsComponent> parts; if (_registeredParts.TryGetValue(pointsComp.MeshID, out parts)) { // Remove from the list. parts.RemoveAll((PointsComponent cmp) => { return(cmp == pointsComp); }); } pointsComp.Release(); } return(new Error()); }
/// <summary> /// Creates a mesh shape for serialising <paramref name="shapeComponent"/> and its associated mesh data. /// </summary> /// <param name="shapeComponent">The component to create a shape for.</param> /// <returns>A shape instance suitable for configuring to generate serialisation messages.</returns> protected override Shapes.Shape CreateSerialisationShape(ShapeCache cache, int shapeIndex, CreateMessage shapeData) { MeshEntry meshEntry = cache.GetShapeDataByIndex <MeshEntry>(shapeIndex); RenderMesh mesh = meshEntry.Mesh; Debug.Log($"serialise mesh. Normals? {mesh.HasNormals}"); Shapes.MeshShape meshShape = new Shapes.MeshShape(meshEntry.Mesh.DrawType, Maths.Vector3Ext.FromUnity(mesh.Vertices), mesh.Indices, shapeData.ObjectID, shapeData.Category); if (mesh.HasNormals && !meshEntry.CalculateNormals) { // We have normals which were not locally calculated. meshShape.Normals = Tes.Maths.Vector3Ext.FromUnity(mesh.Normals); } meshShape.SetAttributes(shapeData.Attributes); return(meshShape); }
protected void Render(CameraContext cameraContext, ShapeCache shapeCache) { // TODO: (KS) verify material setup. // TODO: (KS) incorporate the 3es scene transform. // TODO: (KS) handle multiple cameras (code only tailored to one). Vector3 cameraPosition = (Vector3)cameraContext.CameraToWorldTransform.GetColumn(3); CategoriesState categories = this.CategoriesState; // Walk the items in the shape cache. foreach (int shapeIndex in shapeCache.ShapeIndices) { // if (shapeCache.GetShapeDataByIndex<CreateMessage>(shapeIndex).Category) CreateMessage shape = shapeCache.GetShapeByIndex(shapeIndex); if (!categories.IsActive(shape.Category)) { // Category not enabled. continue; } // Get transform and text data. Matrix4x4 transform = shapeCache.GetShapeTransformByIndex(shapeIndex); TextShapeData textData = shapeCache.GetShapeDataByIndex <TextShapeData>(shapeIndex); if (textData.Mesh == null || textData.Material == null) { // No mesh/material. Try instantiate via the delegate. if (CreateTextMeshHandler != null) { CreateTextMeshHandler(textData.Text, textData.FontSize, Maths.ColourExt.ToUnity(new Maths.Colour(shape.Attributes.Colour)), ref textData.Mesh, ref textData.Material); } if (textData.Mesh == null || textData.Material == null) { continue; } // Newly creates mesh/material. Store the changes. shapeCache.SetShapeDataByIndex <TextShapeData>(shapeIndex, textData); } if (textData.Mesh == null || textData.Material == null) { continue; } if (textData.ScreenFacing) { Vector3 textPosition = cameraContext.TesSceneToWorldTransform * (Vector3)transform.GetColumn(3); Vector3 toCamera = cameraPosition - textPosition; // Remove any height component from the camera. Indexing using Unity's left handed, Y up system. toCamera[1] = 0; if (toCamera.sqrMagnitude > 1e-3f) { toCamera = toCamera.normalized; Vector3 side = Vector3.Cross(toCamera, Vector3.up); // Build new rotation axes using toCamera for forward and a new Up axis. transform.SetColumn(0, new Vector4(side.x, side.y, side.z)); transform.SetColumn(1, new Vector4(Vector3.up.x, Vector3.up.y, Vector3.up.z)); transform.SetColumn(2, new Vector4(toCamera.x, toCamera.y, toCamera.z)); transform.SetColumn(3, new Vector4(textPosition.x, textPosition.y, textPosition.z, 1.0f)); } // else too close to the camera to build a rotation. } else { // transform = cameraContext.TesSceneToWorldTransform * transform; // Just extract the text position. // TODO: (KS) will have to look at allowing users to orient the text from the server. Vector3 textPosition = cameraContext.TesSceneToWorldTransform * (Vector3)transform.GetColumn(3); transform = Matrix4x4.identity; transform.SetColumn(3, new Vector4(textPosition.x, textPosition.y, textPosition.z, 1.0f)); } cameraContext.TransparentBuffer.DrawMesh(textData.Mesh, transform, textData.Material); // TODO: (KS) resolve procedural rendering without a game object. Consider TextMeshPro. // TODO: (KS) select opaque layer. // Graphics.DrawMesh(textData.Mesh, transform, material, 0); } }
private void RenderMeshes(CameraContext cameraContext, ShapeCache cache, int shapeIndex) { CreateMessage shape = cache.GetShapeByIndex(shapeIndex); if (CategoriesState != null && !CategoriesState.IsActive(shape.Category)) { return; } Matrix4x4 shapeWorldTransform = cameraContext.TesSceneToWorldTransform * cache.GetShapeTransformByIndex(shapeIndex); PartSet parts = cache.GetShapeDataByIndex <PartSet>(shapeIndex); for (int i = 0; i < parts.Meshes.Length; ++i) { RenderMesh mesh = (parts.Meshes[i] != null) ? parts.Meshes[i].Mesh : null; if (mesh == null) { continue; } if (mesh.MaterialDirty) { mesh.UpdateMaterial(); } Material material = (parts.MaterialOverrides != null && parts.MaterialOverrides.Length > 0 && parts.MaterialOverrides[i]) ? parts.MaterialOverrides[i] : mesh.Material; if (material == null) { continue; } // TODO: (KS) use transparent queue for parts with transparency. CommandBuffer renderQueue = cameraContext.OpaqueBuffer; // Push the part transform. Matrix4x4 partWorldTransform = shapeWorldTransform * parts.Transforms[i] * mesh.LocalTransform; if (mesh.HasColours) { material.SetBuffer("_Colours", mesh.ColoursBuffer); } if (mesh.HasNormals) { material.SetBuffer("_Normals", mesh.NormalsBuffer); } // if (mesh.HasUVs) // { // material.SetBuffer("uvs", mesh.UvsBuffer); // } if (material.HasProperty("_Color")) { material.SetColor("_Color", new Maths.Colour(shape.Attributes.Colour).ToUnity32()); } if (material.HasProperty("_Tint")) { material.SetColor("_Tint", mesh.Tint.ToUnity32()); } if (material.HasProperty("_BackColour")) { material.SetColor("_BackColour", new Maths.Colour(shape.Attributes.Colour).ToUnity32()); } // Bind vertices and draw. material.SetBuffer("_Vertices", mesh.VertexBuffer); if (mesh.IndexBuffer != null) { renderQueue.DrawProcedural(mesh.IndexBuffer, partWorldTransform, material, 0, mesh.Topology, mesh.IndexCount); } else { renderQueue.DrawProcedural(partWorldTransform, material, 0, mesh.Topology, mesh.VertexCount); } } }
/// <summary> /// Release the mesh resources of <paramref name="obj"/>. /// </summary> /// <param name="obj"></param> void ResetObject(ShapeCache cache, int shapeIndex) { MeshEntry meshEntry = cache.GetShapeDataByIndex <MeshEntry>(shapeIndex); meshEntry.Mesh.ReleaseBuffers(); }
/// <summary> /// Overridden to handle triangle data in the <paramref name="msg"/> /// </summary> /// <param name="msg"></param> /// <param name="packet"></param> /// <param name="reader"></param> /// <returns></returns> protected override Error HandleMessage(DataMessage msg, PacketBuffer packet, BinaryReader reader) { ShapeCache cache = (msg.ObjectID == 0) ? _transientCache : _shapeCache; int shapeIndex = (msg.ObjectID == 0) ? _lastTransientIndex : cache.GetShapeIndex(msg.ObjectID); if (shapeIndex < 0) { return(new Error(ErrorCode.InvalidObjectID, msg.ObjectID)); } // Naive support for multiple packets. Assume: // - In order. MeshEntry meshEntry = cache.GetShapeDataByIndex <MeshEntry>(shapeIndex); // Well, this is confusing indirection... int readComponent = MeshShape.ReadDataComponentDeferred( reader, (uint)meshEntry.Mesh.VertexCount, (uint)meshEntry.Mesh.IndexCount, // Vertex handler. new MeshShape.ComponentBlockReader((MeshShape.SendDataType dataType, BinaryReader reader2, uint offset, uint count) => { return(ReadMeshVector3Data(reader2, offset, count, (Vector3[] buffer, int writeOffset, int writeCount) => { meshEntry.Mesh.SetVertices(buffer, 0, writeOffset, writeCount, true); })); }), // Index handler new MeshShape.ComponentBlockReader((MeshShape.SendDataType dataType, BinaryReader reader2, uint offset, uint count) => { return(ReadIndexComponent(reader2, offset, count, meshEntry.Mesh)); }), // Normals handler. new MeshShape.ComponentBlockReader((MeshShape.SendDataType dataType, BinaryReader reader2, uint offset, uint count) => { return(ReadMeshVector3Data(reader2, offset, count, (Vector3[] buffer, int writeOffset, int writeCount) => { if (dataType == MeshShape.SendDataType.UniformNormal) { // Only one normal for the whole mesh. // Fill the buffer and write in chunks. for (int i = 1; i < buffer.Length; ++i) { buffer[i] = buffer[0]; } int localOffset = 0; for (int i = 0; i < meshEntry.Mesh.VertexCount; i += buffer.Length) { int blockCount = Math.Min(buffer.Length, meshEntry.Mesh.VertexCount - localOffset); meshEntry.Mesh.SetNormals(buffer, 0, localOffset, blockCount); writeOffset += blockCount; } } else { meshEntry.Mesh.SetNormals(buffer, 0, writeOffset, writeCount); } })); }), // Colours handler. new MeshShape.ComponentBlockReader((MeshShape.SendDataType dataType, BinaryReader reader2, uint offset, uint count) => { return(ReadColourComponent(reader2, offset, count, meshEntry.Mesh)); }) ); if (readComponent == -1) { return(new Error(ErrorCode.MalformedMessage, DataMessage.MessageID)); } if (readComponent == (int)(MeshShape.SendDataType.Vertices | MeshShape.SendDataType.End)) { // Finalise the material. meshEntry.Material = CreateMaterial(cache.GetShapeByIndex(shapeIndex), meshEntry); cache.SetShapeDataByIndex(shapeIndex, meshEntry); } return(new Error()); }
protected void RenderObject(CameraContext cameraContext, ShapeCache cache, int shapeIndex) { CreateMessage shape = cache.GetShapeByIndex(shapeIndex); if (CategoriesState != null && !CategoriesState.IsActive(shape.Category)) { return; } MeshEntry meshEntry = cache.GetShapeDataByIndex <MeshEntry>(shapeIndex); Matrix4x4 transform = cache.GetShapeTransformByIndex(shapeIndex); // TODO: (KS) select command buffer for transparent rendering. CommandBuffer renderQueue = cameraContext.OpaqueBuffer; Material material = meshEntry.Material; RenderMesh mesh = meshEntry.Mesh; if (material == null || mesh == null) { return; } Matrix4x4 modelWorld = cameraContext.TesSceneToWorldTransform * transform; // Transform and cull bounds // FIXME: (KS) this really isn't how culling should be performed. Bounds bounds = GeometryUtility.CalculateBounds(new Vector3[] { mesh.MinBounds, mesh.MaxBounds }, modelWorld); if (!GeometryUtility.TestPlanesAABB(cameraContext.CameraFrustumPlanes, bounds)) { return; } if (mesh.HasColours) { material.SetBuffer("_Colours", mesh.ColoursBuffer); } if (mesh.HasNormals) { material.SetBuffer("_Normals", mesh.NormalsBuffer); } // if (mesh.HasUVs) // { // material.SetBuffer("uvs", mesh.UvsBuffer); // } if (material.HasProperty("_Color")) { material.SetColor("_Color", Maths.ColourExt.ToUnity(new Maths.Colour(shape.Attributes.Colour))); } if (material.HasProperty("_Tint")) { material.SetColor("_Tint", Maths.ColourExt.ToUnity(mesh.Tint)); } if (material.HasProperty("_BackColour")) { material.SetColor("_BackColour", Maths.ColourExt.ToUnity(new Maths.Colour(shape.Attributes.Colour))); } // TODO: (KS) Need to derive this from the shape properties. if (mesh.Topology == MeshTopology.Points) { // Set min/max shader values. if (material.HasProperty("_BoundsMin")) { material.SetVector("_BoundsMin", mesh.MinBounds); } if (material.HasProperty("_BoundsMax")) { material.SetVector("_BoundsMax", mesh.MaxBounds); } float pointScale = (meshEntry.DrawScale > 0) ? meshEntry.DrawScale : 1.0f; material.SetFloat("_PointSize", GlobalSettings.PointSize * pointScale); // Colour by height if we have a zero colour value. if (shape.Attributes.Colour == 0) { material.SetColor("_Color", Color.white); material.SetColor("_BackColour", Color.white); switch (CoordinateFrameUtil.AxisIndex(ServerInfo.CoordinateFrame, 2)) { case 0: material.EnableKeyword("WITH_COLOURS_RANGE_X"); break; case 1: material.EnableKeyword("WITH_COLOURS_RANGE_Y"); break; default: case 2: material.EnableKeyword("WITH_COLOURS_RANGE_Z"); break; } } } // Bind vertices and draw. material.SetBuffer("_Vertices", mesh.VertexBuffer); if (mesh.IndexBuffer != null) { renderQueue.DrawProcedural(mesh.IndexBuffer, modelWorld, material, 0, mesh.Topology, mesh.IndexCount); } else { renderQueue.DrawProcedural(modelWorld, material, 0, mesh.Topology, mesh.VertexCount); } }
void RenderPoints(CameraContext cameraContext, ShapeCache cache, int shapeIndex) { CreateMessage shape = cache.GetShapeByIndex(shapeIndex); if (CategoriesState != null && !CategoriesState.IsActive(shape.Category)) { return; } Matrix4x4 modelWorld = cameraContext.TesSceneToWorldTransform * cache.GetShapeTransformByIndex(shapeIndex); PointsComponent points = cache.GetShapeDataByIndex <PointsComponent>(shapeIndex); CommandBuffer renderQueue = cameraContext.OpaqueBuffer; RenderMesh mesh = points.Mesh != null ? points.Mesh.Mesh : null; if (mesh == null) { // No mesh. Debug.LogWarning($"Point cloud shape {shape.ObjectID} missing mesh with ID {points.MeshID}"); return; } if (points.Material == null) { // No mesh. Debug.LogWarning($"Point cloud shape {shape.ObjectID} missing material"); return; } // Check rendering with index buffer? GraphicsBuffer indexBuffer = null; int indexCount = 0; if (mesh.IndexCount > 0 || points.IndexCount > 0) { if ((int)points.IndexCount > 0) { indexBuffer = points.IndexBuffer; indexCount = (int)points.IndexCount; } // We only use the mesh index buffer if the mesh has points topology. // Otherwise we convert to points using vertices as is. else if (mesh.Topology == MeshTopology.Points) { indexBuffer = mesh.IndexBuffer; indexCount = mesh.IndexCount; } } if (mesh.HasColours) { points.Material.SetBuffer("_Colours", mesh.ColoursBuffer); } if (mesh.HasNormals) { points.Material.SetBuffer("_Normals", mesh.NormalsBuffer); } points.Material.SetBuffer("_Vertices", mesh.VertexBuffer); // Set min/max shader values. if (points.Material.HasProperty("_BoundsMin")) { points.Material.SetVector("_BoundsMin", points.Mesh.Mesh.MinBounds); } if (points.Material.HasProperty("_BoundsMax")) { points.Material.SetVector("_BoundsMax", points.Mesh.Mesh.MaxBounds); } float pointScale = (points.PointScale > 0) ? points.PointScale : 1.0f; points.Material.SetFloat("_PointSize", GlobalSettings.PointSize * pointScale); // Colour by height if we have a zero colour value. if (shape.Attributes.Colour == 0) { points.Material.SetColor("_Color", Color.white); points.Material.SetColor("_BackColour", Color.white); switch (CoordinateFrameUtil.AxisIndex(ServerInfo.CoordinateFrame, 2)) { case 0: points.Material.EnableKeyword("WITH_COLOURS_RANGE_X"); break; case 1: points.Material.EnableKeyword("WITH_COLOURS_RANGE_Y"); break; default: case 2: points.Material.EnableKeyword("WITH_COLOURS_RANGE_Z"); break; } } if (mesh.IndexBuffer != null) { renderQueue.DrawProcedural(mesh.IndexBuffer, modelWorld, points.Material, 0, mesh.Topology, mesh.IndexCount); } else { renderQueue.DrawProcedural(modelWorld, points.Material, 0, mesh.Topology, mesh.VertexCount); } }