/// <summary> /// Message handler for <see cref="CreateMessage"/> /// </summary> /// <param name="msg">The incoming message.</param> /// <param name="packet">The buffer containing the message.</param> /// <param name="reader">The reader from which the message came.</param> /// <returns>An error code on failure.</returns> /// <remarks> /// Creates either a persistent or transient object depending on the incoming /// message ObjectID. An ID of zero signifies a transient object. /// Solid or wire shapes are assigned according to the message flags. /// </remarks> protected virtual Error HandleMessage(CreateMessage msg, PacketBuffer packet, BinaryReader reader) { GameObject obj = null; if (msg.ObjectID == 0) { // Transient object. obj = CreateTransient(); } else { obj = CreateObject(msg.ObjectID); if (!obj) { // Object already exists. return(new Error(ErrorCode.DuplicateShape, msg.ObjectID)); } } ShapeComponent shapeComp = obj.GetComponent <ShapeComponent>(); shapeComp.Category = msg.Category; shapeComp.ObjectFlags = msg.Flags; shapeComp.Colour = ShapeComponent.ConvertColour(msg.Attributes.Colour); obj.transform.SetParent(_root.transform, false); DecodeTransform(msg.Attributes, obj.transform); InitialiseVisual(shapeComp, ShapeComponent.ConvertColour(msg.Attributes.Colour)); return(PostHandleMessage(obj, msg, packet, reader)); }
/// <summary> /// Handles <see cref="MeshRedefineMessage"/> /// </summary> /// <param name="packet"></param> /// <param name="reader"></param> /// <returns></returns> /// <remarks> /// The associated mesh is invalidated until another <see cref="MeshFinaliseMessage"/> arrives. /// </remarks> protected Error RedefineMesh(PacketBuffer packet, BinaryReader reader) { MeshRedefineMessage msg = new MeshRedefineMessage(); if (!msg.Read(reader)) { return(new Error(ErrorCode.MalformedMessage, MeshRedefineMessage.MessageID)); } MeshDetails meshDetails; if (!_meshes.TryGetValue(msg.MeshID, out meshDetails)) { return(new Error(ErrorCode.InvalidObjectID, msg.MeshID)); } meshDetails.Finalised = false; if (msg.VertexCount != meshDetails.VertexCount) { meshDetails.VertexCount = (int)msg.VertexCount; } if (msg.IndexCount != 0) { meshDetails.IndexCount = (int)msg.IndexCount; } meshDetails.ID = msg.MeshID; meshDetails.LocalPosition = new Vector3(msg.Attributes.X, msg.Attributes.Y, msg.Attributes.Z); meshDetails.LocalRotation = new Quaternion(msg.Attributes.RotationX, msg.Attributes.RotationY, msg.Attributes.RotationZ, msg.Attributes.RotationW); meshDetails.LocalScale = new Vector3(msg.Attributes.ScaleX, msg.Attributes.ScaleY, msg.Attributes.ScaleZ); meshDetails.Tint = ShapeComponent.ConvertColour(msg.Attributes.Colour); return(new Error()); }
/// <summary> /// Message handler for <see cref="UpdateMessage"/> /// </summary> /// <param name="msg">The incoming message.</param> /// <param name="packet">The buffer containing the message.</param> /// <param name="reader">The reader from which the message came.</param> /// <returns>An error code on failure.</returns> /// <remarks> /// Decodes the message transform and colour, updating the existing shape /// matching the message ObjectID. Only relevant for persistent shapes. /// </remarks> protected virtual Error HandleMessage(UpdateMessage msg, PacketBuffer packet, BinaryReader reader) { GameObject obj = FindObject(msg.ObjectID); if (obj == null) { return(new Error(ErrorCode.InvalidObjectID, msg.ObjectID)); } ushort flags = msg.Flags; DecodeTransform(msg.Attributes, obj.transform, flags); if ((flags & (ushort)UpdateFlag.UpdateMode) == 0 || (flags & (ushort)UpdateFlag.Colour) != 0) { ShapeComponent shapeComp = obj.GetComponent <ShapeComponent>(); if (shapeComp != null) { shapeComp.Colour = ShapeComponent.ConvertColour(msg.Attributes.Colour); } SetColour(obj, ShapeComponent.ConvertColour(msg.Attributes.Colour)); } return(new Error()); }
/// <summary> /// Called at end of <see cref="HandleMessage(CreateMessage, PacketBuffer, BinaryReader)"/>, only on success. /// </summary> /// <param name="obj">The newly created message.</param> /// <param name="msg">The incoming message.</param> /// <param name="packet">The buffer containing the message.</param> /// <param name="reader">The reader from which the message came.</param> /// <returns>An error code on failure.</returns> protected virtual Error PostHandleMessage(GameObject obj, CreateMessage msg, PacketBuffer packet, BinaryReader reader) { ShapeComponent shape = obj.GetComponent <ShapeComponent>(); if (shape != null && !CategoryCheck(shape.Category)) { obj.SetActive(false); } return(new Error()); }
/// <summary> /// A helper functio to configure the TES <paramref name="shape"/> to match the Unity object <paramref name="shapeComponent"/>. /// </summary> /// <param name="shape">The shape object to configure to match the Unity representation.</param> /// <param name="shapeComponent">The Unity shape representation.</param> protected void ConfigureShape(Shapes.Shape shape, ShapeComponent shapeComponent) { ObjectAttributes attr = new ObjectAttributes(); shape.ID = shapeComponent.ObjectID; shape.Category = shapeComponent.Category; shape.Flags = shapeComponent.ObjectFlags; EncodeAttributes(ref attr, shapeComponent.gameObject, shapeComponent); shape.SetAttributes(attr); }
/// <summary> /// Serialise messages to generate <paramref name="mesh"/>. /// </summary> /// <param name="mesh">The mesh of interest.</param> /// <param name="packet">Packet buffer to compose messages in</param> /// <param name="writer">Writer to export completed message packets to.</param> /// <returns></returns> /// <remarks> /// Writes: /// <list type="bullet"> /// <item><see cref="MeshCreateMessage"/></item> /// <item><see cref="MeshComponentMessage"/> for each component type from /// <see cref="MeshMessageType"/></item> /// <item><see cref="MeshFinaliseMessage"/> only when <paramref name="mesh"/> is already /// finalised.</item> /// </list> /// </remarks> protected Error Serialise(MeshDetails mesh, PacketBuffer packet, BinaryWriter writer) { // First write a create message. MeshCreateMessage msg = new MeshCreateMessage(); packet.Reset((ushort)RoutingID, (ushort)MeshCreateMessage.MessageID); msg.MeshID = mesh.ID; msg.VertexCount = (uint)mesh.Builder.VertexCount; msg.IndexCount = (uint)(mesh.Builder.ExplicitIndices ? mesh.Builder.IndexCount : 0); msg.DrawType = mesh.DrawType; msg.Attributes.X = mesh.LocalPosition.x; msg.Attributes.Y = mesh.LocalPosition.y; msg.Attributes.Z = mesh.LocalPosition.z; msg.Attributes.RotationX = mesh.LocalRotation.x; msg.Attributes.RotationY = mesh.LocalRotation.y; msg.Attributes.RotationZ = mesh.LocalRotation.z; msg.Attributes.RotationW = mesh.LocalRotation.w; msg.Attributes.ScaleX = mesh.LocalScale.x; msg.Attributes.ScaleY = mesh.LocalScale.y; msg.Attributes.ScaleZ = mesh.LocalScale.z; msg.Attributes.Colour = ShapeComponent.ConvertColour(mesh.Tint); msg.Write(packet); if (!packet.FinalisePacket()) { return(new Error(ErrorCode.SerialisationFailure)); } packet.ExportTo(writer); // Now use the MeshResource methods to complete serialisation. MeshSerialiser serialiser = new MeshSerialiser(mesh); TransferProgress prog = new TransferProgress(); prog.Reset(); while (!prog.Complete) { serialiser.Transfer(packet, 0, ref prog); if (!packet.FinalisePacket()) { return(new Error(ErrorCode.SerialisationFailure)); } packet.ExportTo(writer); } return(new Error()); }
/// <summary> /// Handles <see cref="MeshCreateMessage"/> /// </summary> /// <param name="packet"></param> /// <param name="reader"></param> /// <returns></returns> /// <remarks> /// Emits <see cref="OnMeshAdded"/>. /// </remarks> protected Error CreateMesh(PacketBuffer packet, BinaryReader reader) { MeshCreateMessage msg = new MeshCreateMessage(); if (!msg.Read(reader)) { return(new Error(ErrorCode.MalformedMessage, MeshCreateMessage.MessageID)); } if (_meshes.ContainsKey(msg.MeshID)) { return(new Error(ErrorCode.DuplicateShape, msg.MeshID)); } MeshDetails meshDetails = new MeshDetails(); meshDetails.VertexCount = (int)msg.VertexCount; meshDetails.IndexCount = (int)msg.IndexCount; meshDetails.DrawType = msg.DrawType; switch (msg.DrawType) { case (byte)MeshDrawType.Points: // No break. case (byte)MeshDrawType.Voxels: meshDetails.Builder.Topology = MeshTopology.Points; break; case (byte)MeshDrawType.Lines: meshDetails.Builder.Topology = MeshTopology.Lines; break; case (byte)MeshDrawType.Triangles: meshDetails.Builder.Topology = MeshTopology.Triangles; break; default: return(new Error(ErrorCode.UnsupportedFeature, msg.DrawType)); } meshDetails.ID = msg.MeshID; meshDetails.LocalPosition = new Vector3(msg.Attributes.X, msg.Attributes.Y, msg.Attributes.Z); meshDetails.LocalRotation = new Quaternion(msg.Attributes.RotationX, msg.Attributes.RotationY, msg.Attributes.RotationZ, msg.Attributes.RotationW); meshDetails.LocalScale = new Vector3(msg.Attributes.ScaleX, msg.Attributes.ScaleY, msg.Attributes.ScaleZ); meshDetails.Tint = ShapeComponent.ConvertColour(msg.Attributes.Colour); meshDetails.Finalised = false; _meshes.Add(meshDetails.ID, meshDetails); NotifyMeshAdded(meshDetails); return(new Error()); }
/// <summary> /// Instantiates a persistent object from the <see cref="ShapeCache"/>. /// The new object is assigned the given <paramref name="id"/> /// </summary> /// <param name="id">The ID of the new object. Must non be zero (for persistent objects).</param> /// <returns>The instantiated object or null on failure.</returns> protected virtual GameObject CreateObject(uint id) { GameObject obj = CreateObject(); obj.name = string.Format("{0}{1:D3}", Name, id); ShapeComponent shape = obj.GetComponent <ShapeComponent>(); shape.ObjectID = id; if (_shapeCache.Add(id, obj)) { return(obj); } // Creation failed. GameObject.Destroy(obj); return(null); }
/// <summary> /// Handles <see cref="MeshComponentMessage"/> of type <see cref="MeshMessageType.VertexColour"/>. /// </summary> /// <param name="packet"></param> /// <param name="reader"></param> /// <returns></returns> protected Error AddVertexColours(PacketBuffer packet, BinaryReader reader) { MeshComponentMessage msg = new MeshComponentMessage(); if (!msg.Read(reader)) { return(new Error(ErrorCode.MalformedMessage, (ushort)MeshMessageType.VertexColour)); } MeshDetails meshDetails; if (!_meshes.TryGetValue(msg.MeshID, out meshDetails)) { return(new Error(ErrorCode.InvalidObjectID, msg.MeshID)); } if (msg.Count == 0) { return(new Error()); } int voffset = (int)msg.Offset; // Bounds check. int vertexCount = (int)meshDetails.VertexCount; if (voffset >= vertexCount || voffset + msg.Count > vertexCount) { return(new Error(ErrorCode.IndexingOutOfRange, (ushort)MeshMessageType.VertexColour)); } // Check for settings initial bounds. bool ok = true; uint colour; for (int vInd = 0; ok && vInd < msg.Count; ++vInd) { colour = reader.ReadUInt32(); meshDetails.Builder.UpdateColour(vInd + voffset, ShapeComponent.ConvertColour(colour)); } if (!ok) { return(new Error(ErrorCode.MalformedMessage, (ushort)MeshMessageType.VertexColour)); } return(new Error()); }
/// <summary> /// Invoked when an object category changes active state. /// </summary> /// <param name="categoryId">The category changing state.</param> /// <param name="active">The new active state.</param> /// <remarks> /// Handlers should only ever visualise objects in active categories. /// </remarks> public override void OnCategoryChange(ushort categoryId, bool active) { foreach (GameObject obj in _transientCache.Objects) { ShapeComponent shape = obj.GetComponent <ShapeComponent>(); if (shape != null && shape.Category == categoryId) { obj.SetActive(active); } } foreach (GameObject obj in _shapeCache.Objects) { ShapeComponent shape = obj.GetComponent <ShapeComponent>(); if (shape != null && shape.Category == categoryId) { obj.SetActive(active); } } }
/// <summary> /// Lookup a material for the given <paramref name="shape"/> created by this handler. /// </summary> /// <param name="shape">The shape to lookup a material for.</param> /// <returns>The material for that shape.</returns> /// <remarks> /// Respects various <see cref="ObjectFlag"/> values. /// </remarks> public virtual Material LookupMaterialFor(ShapeComponent shape) { if (shape.Wireframe) { // Note: Wireframe triangles is not for rendering line shapes, but outlining triangles. // Therefore, not what we want here. return(Materials[MaterialLibrary.VertexColourUnlit]); } if (shape.Transparent) { return(Materials[MaterialLibrary.VertexColourTransparent]); } if (shape.TwoSided) { return(Materials[MaterialLibrary.VertexColourUnlitTwoSided]); } return(Materials[MaterialLibrary.VertexColourUnlit]); }
/// <summary> /// Sets the render meshes for <paramref name="obj"/> using <paramref name="sharedMeshes"/>. /// </summary> /// <param name="obj">The object to initialise visuals for.</param> /// <param name="sharedMeshes">The meshes to render with.</param> /// <param name="colour">Primary rendering colour.</param> /// <remarks> /// This function sets <paramref name="sharedMeshes"/> as a set of shared mesh resources /// to render <paramref name="obj"/> with. When there is one element in /// <paramref name="sharedMeshes"/>, the mesh and material are set on the /// <c>MeshFilter</c> and <c>MeshRenderer</c> belonging to <paramref name="obj"/> itself. /// When multiple items are given, the children of <paramref name="obj"/> are used instead. /// This means the number of children must match the number of elements in /// <paramref name="sharedMeshes"/>. /// /// The materials used are attained via <see cref="LookupMaterialFor(ShapeComponent)"/> /// where the <see cref="ShapeComponent"/> belongs to <paramref name="obj"/>. /// </remarks> protected virtual void InitialiseMesh(ShapeComponent obj, Mesh[] sharedMeshes, Color colour) { MeshFilter filter = obj.GetComponent <MeshFilter>(); MeshRenderer render = obj.GetComponent <MeshRenderer>(); ShapeComponent shape = obj.GetComponent <ShapeComponent>(); if (sharedMeshes.Length == 1) { // Single mesh. Set on this object. if (filter != null) { filter.sharedMesh = sharedMeshes[0]; if (render != null) { int componentCount = (filter.sharedMesh != null) ? filter.sharedMesh.subMeshCount : 0; SetMaterial(LookupMaterialFor(shape), render, componentCount, colour); } } } else { // Multiple meshes. Set on children. Transform child; for (int i = 0; i < sharedMeshes.Length; ++i) { child = obj.transform.GetChild(i); if ((filter = child.GetComponent <MeshFilter>()) != null) { filter.sharedMesh = sharedMeshes[i]; } if ((render = child.GetComponent <MeshRenderer>())) { SetMaterial(LookupMaterialFor(shape), render, sharedMeshes[i].subMeshCount, colour); } } } }
/// <summary> /// Called to extract the current object attributes from an existing object. /// </summary> /// <param name="attr">Modified to reflect the current state of <paramref name="obj"/></param> /// <param name="obj">The object to encode attributes for.</param> /// <param name="comp">The <see cref="ShapeComponent"/> of <paramref name="obj"/></param> /// <remarks> /// This extracts colour and performs the inverse operation of <see cref="DecodeTransform"/> /// This method must be overridden whenever <see cref="DecodeTransform"/> is overridden. /// </remarks> protected virtual void EncodeAttributes(ref ObjectAttributes attr, GameObject obj, ShapeComponent comp) { Transform transform = obj.transform; attr.X = transform.localPosition.x; attr.Y = transform.localPosition.y; attr.Z = transform.localPosition.z; attr.RotationX = transform.localRotation.x; attr.RotationY = transform.localRotation.y; attr.RotationZ = transform.localRotation.z; attr.RotationW = transform.localRotation.w; attr.ScaleX = transform.localScale.x; attr.ScaleY = transform.localScale.y; attr.ScaleZ = transform.localScale.z; if (comp != null) { attr.Colour = ShapeComponent.ConvertColour(comp.Colour); } else { attr.Colour = 0xffffffu; } }
protected override Shapes.Shape CreateSerialisationShape(Handlers.ShapeComponent shapeComponent) { Shapes.Shape shape = new Pyramid(); ConfigureShape(shape, shapeComponent); return(shape); }
/// <summary> /// A convenient overload accepting a single mesh argument. /// </summary> /// <param name="obj">The object to initialise visuals for.</param> /// <param name="sharedMesh">The mesh to render with.</param> /// <param name="colour">Primary rendering colour.</param> /// <remarks> /// Maps to the overload: <see cref="InitialiseMesh(ShapeComponent, Mesh[], Color)"/>. /// </remarks> protected virtual void InitialiseMesh(ShapeComponent obj, Mesh sharedMesh, Color colour) { InitialiseMesh(obj, new Mesh[] { sharedMesh }, colour); }
/// <summary> /// Initialise the visual components (e.g., meshes) for <paramref name="obj"/>. /// </summary> /// <param name="obj">The object to initialise visuals for.</param> /// <param name="colour">Primary rendering colour.</param> /// <remarks> /// The default implementation resolves a single mesh using <see cref="SolidMesh"/> /// or <see cref="WireframeMesh"/> (depending on <see cref="ShapeComponent.Wireframe"/>) /// and invokes <see cref="InitialiseMesh(ShapeComponent, Mesh, Color)"/>. /// </remarks> protected virtual void InitialiseVisual(ShapeComponent obj, Color colour) { Mesh sharedMesh = (!obj.Wireframe) ? SolidMesh : WireframeMesh; InitialiseMesh(obj, sharedMesh, colour); }
/// <summary> /// Create a dummy shape object used to generate serialisation messages. /// </summary> /// <param name="shapeComponent">The component to create a shape for.</param> /// <returns>A shape instance suitable for configuring to generate serialisation messages.</returns> /// <remarks> /// Base classes should implement this method to return an instance of the appropriate /// <see cref="Shapes.Shape"/> derivation. For example, the <see cref="Shape3D.SphereHandler"/> /// should return a <see cref="Shapes.Sphere"/> object. See /// <see cref="SerialiseObjects(BinaryWriter, IEnumerator<GameObject>, ref uint)"/> for further /// details. /// </remarks> protected abstract Shapes.Shape CreateSerialisationShape(ShapeComponent shapeComponent);
/// <summary> /// Called after serialising the creation message for an object. This allows follow up /// data messages to be serialised. /// </summary> /// <param name="packet"></param> /// <param name="writer"></param> /// <param name="shape"></param> /// <returns></returns> protected virtual Error PostSerialiseCreateObject(PacketBuffer packet, BinaryWriter writer, ShapeComponent shape) { return(new Error()); }