protected virtual void RenderInstances(CameraContext cameraContext, CommandBuffer renderQueue, Mesh mesh, List <Matrix4x4> transforms, List <Matrix4x4> parentTransforms, List <CreateMessage> shapes, Material material) { CategoriesState categories = this.CategoriesState; // Handle instancing block size limits. for (int i = 0; i < transforms.Count; i += _instanceTransforms.Length) { MaterialPropertyBlock materialProperties = new MaterialPropertyBlock(); int itemCount = 0; _instanceColours.Clear(); for (int j = 0; j < _instanceTransforms.Length && j + i < transforms.Count; ++j) { if (categories == null || categories.IsActive(shapes[i + j].Category)) { _instanceTransforms[itemCount] = cameraContext.TesSceneToWorldTransform * parentTransforms[i + j] * transforms[i + j]; Maths.Colour colour = new Maths.Colour(shapes[i + j].Attributes.Colour); _instanceColours.Add(Maths.ColourExt.ToUnityVector4(colour)); ++itemCount; } } if (itemCount > 0) { materialProperties.SetVectorArray("_Color", _instanceColours); renderQueue.DrawMeshInstanced(mesh, 0, material, 0, _instanceTransforms, itemCount, materialProperties); } } }
/// <summary> /// Clear all current objects. /// </summary> public override void Reset() { CategoriesState.Reset(); _categories.Clear(); if (OnClearCategories != null) { OnClearCategories(); } // FIXME: localisation. AddCategory(0, 0, "Default", true); }
/// <summary> /// Add a category to the list of known categories. /// </summary> /// <param name="id">The category ID</param> /// <param name="parentId">The parent ID. Zero is none (even though 0 is a valid category).</param> /// <param name="name">The category display name.</param> /// <param name="active">The default active state.</param> public void AddCategory(ushort id, ushort parentId, string name, bool active) { Category cat = new Category(); cat.ID = id; cat.ParentID = parentId; cat.Name = name; cat.Active = active; _categories[cat.ID] = cat; bool parentActive = parentId == 0 || CategoriesState.IsActive(parentId); CategoriesState.SetActive(cat.ID, cat.Active && parentActive); NotifyNewCategory(cat); }
/// <summary> /// Set the active state of all descendants of the category identified by <paramref name="id"/>. /// </summary> /// <param name="id">The parent category ID.</param> /// <param name="active">The new active state.</param> private void PropagateActiveToChildren(ushort id, bool active) { // Skip default category. if (id == 0) { return; } // Also affect all children. foreach (Category cat in ChildCategories(id)) { if (CategoriesState.IsActive(cat.ID) != active) { CategoriesState.SetActive(cat.ID, active); } PropagateActiveToChildren(cat.ID, active); } }
/// <summary> /// Set the active state of a category. /// </summary> /// <param name="id">The category ID.</param> /// <param name="active">The desired active state.</param> /// <remarks> /// This invokes the <see cref="OnActivationChange"/> event when <paramref name="active"/> /// does not match the category state. /// /// Unknown <paramref name="id"/> values are ignored. /// </remarks> public void SetActive(ushort id, bool active) { Category cat; if (_categories.TryGetValue(id, out cat)) { if (cat.Active != active) { cat.Active = active; // Update the dictionary. _categories[cat.ID] = cat; // Check hierarchy to determin final state. bool mergedActive = active && CheckHierarchyActive(cat.ParentID); CategoriesState.SetActive(cat.ID, mergedActive); PropagateActiveToChildren(id, mergedActive); if (OnActivationChange != null) { OnActivationChange(id, active); } } } }
/// <summary> /// Check if a category is active. /// </summary> /// <param name="id">The category ID.</param> /// <returns>True if active or unknown/</returns> public bool IsActive(ushort id) { return(CategoriesState.IsActive(id)); }
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); } } }
protected override void RenderInstances(CameraContext cameraContext, CommandBuffer renderQueue, Mesh mesh, List <Matrix4x4> transforms, List <Matrix4x4> parentTransforms, List <CreateMessage> shapes, Material material) { // Work out which mesh set we are rendering from the parent call: solid or wireframe. We could also look at // the first CreateMessage flags. Mesh[] meshes = (shapes.Count > 0 && (shapes[0].Flags & (ushort)ObjectFlag.Wireframe) != 0) ? _wireframeMeshes : _solidMeshes; CategoriesState categories = this.CategoriesState; // Handle instancing block size limits. for (int i = 0; i < transforms.Count; i += _instanceTransforms.Length) { MaterialPropertyBlock materialProperties = new MaterialPropertyBlock(); int itemCount = 0; _instanceColours.Clear(); for (int j = 0; j < _instanceTransforms.Length && j + i < transforms.Count; ++j) { if (categories != null && !categories.IsActive(shapes[i + j].Category)) { continue; } // Build the end cap transforms. Matrix4x4 modelToSceneTransform = cameraContext.TesSceneToWorldTransform * parentTransforms[i + j]; Matrix4x4 transform = transforms[i + j]; // Work out the scaling. We only want to lengthen each line. Vector3 scale = Vector3.zero; scale.x = transform.GetColumn(0).magnitude; scale.y = transform.GetColumn(1).magnitude; scale.z = transform.GetColumn(2).magnitude; // Remove scaling. transform.SetColumn(0, transform.GetColumn(0) / scale.x); transform.SetColumn(1, transform.GetColumn(1) / scale.y); transform.SetColumn(2, transform.GetColumn(2) / scale.z); // Scale each mesh. Matrix4x4 transform2 = transform; transform2.SetColumn(0, transform2.GetColumn(0) * scale.x); _instanceTransforms[itemCount] = modelToSceneTransform * transform2; transform2 = transform; transform2.SetColumn(1, transform2.GetColumn(1) * scale.y); _axis1Transforms[itemCount] = modelToSceneTransform * transform2; transform2 = transform; transform2.SetColumn(2, transform2.GetColumn(2) * scale.z); _axis2Transforms[itemCount] = modelToSceneTransform * transform2; Maths.Colour colour = new Maths.Colour(shapes[i + j].Attributes.Colour); _instanceColours.Add(Maths.ColourExt.ToUnityVector4(colour)); ++itemCount; } if (itemCount > 0) { materialProperties.SetVectorArray("_Color", _instanceColours); // Render body. renderQueue.DrawMeshInstanced(meshes[0], 0, material, 0, _instanceTransforms, itemCount, materialProperties); // Render end caps. renderQueue.DrawMeshInstanced(meshes[1], 0, material, 0, _axis1Transforms, itemCount, materialProperties); renderQueue.DrawMeshInstanced(meshes[2], 0, material, 0, _axis2Transforms, itemCount, materialProperties); } } }
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); } }
protected override void RenderInstances(CameraContext cameraContext, CommandBuffer renderQueue, Mesh mesh, List <Matrix4x4> transforms, List <Matrix4x4> parentTransforms, List <CreateMessage> shapes, Material material) { // Work out which mesh set we are rendering from the parent call: solid or wireframe. We could also look at // the first CreateMessage flags. Mesh[] meshes = (shapes.Count > 0 && (shapes[0].Flags & (ushort)ObjectFlag.Wireframe) != 0) ? _wireframeMeshes : _solidMeshes; CategoriesState categories = this.CategoriesState; // Handle instancing block size limits. for (int i = 0; i < transforms.Count; i += _instanceTransforms.Length) { MaterialPropertyBlock materialProperties = new MaterialPropertyBlock(); int itemCount = 0; _instanceColours.Clear(); for (int j = 0; j < _instanceTransforms.Length && j + i < transforms.Count; ++j) { if (categories != null && !categories.IsActive(shapes[i + j].Category)) { continue; } // Build the end cap transforms. Matrix4x4 modelToSceneTransform = cameraContext.TesSceneToWorldTransform * parentTransforms[i + j]; Matrix4x4 transform = transforms[i + j]; _instanceTransforms[itemCount] = modelToSceneTransform * transform; // Extract radius and length to position the end caps. float radius = transform.GetColumn(0).magnitude; Vector4 zAxis = transform.GetColumn(2); float length = zAxis.magnitude; zAxis *= 1.0f / (length != 0 ? length : 1.0f); // Scale the length axis to match the other two as the end caps are spheres. transform.SetColumn(2, zAxis * radius); // Adjust position for the first end cap. Vector4 tAxis = transform.GetColumn(3); tAxis += -0.5f * length * zAxis; transform.SetColumn(3, tAxis); _cap1Transforms[j] = modelToSceneTransform * transform; // Adjust position for the second end cap. tAxis += length * zAxis; transform.SetColumn(3, tAxis); _cap2Transforms[j] = modelToSceneTransform * transform; Maths.Colour colour = new Maths.Colour(shapes[i + j].Attributes.Colour); _instanceColours.Add(Maths.ColourExt.ToUnityVector4(colour)); ++itemCount; } if (itemCount > 0) { materialProperties.SetVectorArray("_Color", _instanceColours); // Render body. renderQueue.DrawMeshInstanced(meshes[0], 0, material, 0, _instanceTransforms, itemCount, materialProperties); // Render end caps. renderQueue.DrawMeshInstanced(meshes[1], 0, material, 0, _cap1Transforms, itemCount, materialProperties); renderQueue.DrawMeshInstanced(meshes[2], 0, material, 0, _cap2Transforms, itemCount, materialProperties); } } }
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); } }