/// \cond protected override void Synchronize() { base.Synchronize(); // Syncronize the mesh data. if (data != null) { if (data.volumeHandle.HasValue) { CubiquityDLL.UpdateVolumeMC(data.volumeHandle.Value); if (CubiquityDLL.HasRootOctreeNodeMC(data.volumeHandle.Value) == 1) { uint rootNodeHandle = CubiquityDLL.GetRootOctreeNodeMC(data.volumeHandle.Value); if (rootOctreeNodeGameObject == null) { rootOctreeNodeGameObject = OctreeNode.CreateOctreeNode(rootNodeHandle, gameObject); } OctreeNode rootOctreeNode = rootOctreeNodeGameObject.GetComponent <OctreeNode>(); int nodeSyncsPerformed = rootOctreeNode.syncNode(maxNodesPerSync, gameObject); // If no node were syncronized then the mesh data is up to // date and we can set the flag to convey this to the user. isMeshSyncronized = (nodeSyncsPerformed == 0); } } } }
/// \cond protected override void Synchronize() { base.Synchronize(); ColoredCubesVolumeRenderer volumeRenderer = gameObject.GetComponent <ColoredCubesVolumeRenderer>(); if (volumeRenderer != null) { if (volumeRenderer.material != null) { // We compute surface normals using derivative operations in the fragment shader, but for some reason // these are backwards on Linux. We can correct for this in the shader by setting the multiplier below. #if UNITY_STANDALONE_LINUX && !UNITY_EDITOR float normalMultiplier = -1.0f; #else float normalMultiplier = 1.0f; #endif volumeRenderer.material.SetFloat("normalMultiplier", normalMultiplier); } } // Syncronize the mesh data. if (data != null) { // Syncronize the mesh data. if (data.volumeHandle.HasValue) { CubiquityDLL.UpdateVolume(data.volumeHandle.Value); if (CubiquityDLL.HasRootOctreeNode(data.volumeHandle.Value) == 1) { uint rootNodeHandle = CubiquityDLL.GetRootOctreeNode(data.volumeHandle.Value); if (rootOctreeNodeGameObject == null) { rootOctreeNodeGameObject = OctreeNode.CreateOctreeNode(rootNodeHandle, gameObject); } OctreeNode rootOctreeNode = rootOctreeNodeGameObject.GetComponent <OctreeNode>(); int nodeSyncsPerformed = rootOctreeNode.syncNode(maxNodesPerSync, gameObject); // If no node were syncronized then the mesh data is up to // date and we can set the flag to convey this to the user. isMeshSyncronized = (nodeSyncsPerformed == 0); } } } }
public int syncNode(int availableNodeSyncs, GameObject voxelTerrainGameObject) { int nodeSyncsPerformed = 0; if (availableNodeSyncs <= 0) { return(nodeSyncsPerformed); } uint meshLastUpdated = CubiquityDLL.GetMeshLastUpdated(nodeHandle); if (meshLastSyncronised < meshLastUpdated) { if (CubiquityDLL.NodeHasMesh(nodeHandle) == 1) { // Set up the rendering mesh VolumeRenderer volumeRenderer = voxelTerrainGameObject.GetComponent <VolumeRenderer>(); if (volumeRenderer != null) { //Mesh renderingMesh = volumeRenderer.BuildMeshFromNodeHandle(nodeHandle); Mesh renderingMesh = null; if (voxelTerrainGameObject.GetComponent <Volume>().GetType() == typeof(TerrainVolume)) { renderingMesh = BuildMeshFromNodeHandleForTerrainVolume(nodeHandle); } else if (voxelTerrainGameObject.GetComponent <Volume>().GetType() == typeof(ColoredCubesVolume)) { renderingMesh = BuildMeshFromNodeHandleForColoredCubesVolume(nodeHandle); } MeshFilter meshFilter = gameObject.GetOrAddComponent <MeshFilter>() as MeshFilter; MeshRenderer meshRenderer = gameObject.GetOrAddComponent <MeshRenderer>() as MeshRenderer; if (meshFilter.sharedMesh != null) { DestroyImmediate(meshFilter.sharedMesh); } meshFilter.sharedMesh = renderingMesh; meshRenderer.sharedMaterial = volumeRenderer.material; #if UNITY_EDITOR EditorUtility.SetSelectedWireframeHidden(meshRenderer, true); #endif } // Set up the collision mesh VolumeCollider volumeCollider = voxelTerrainGameObject.GetComponent <VolumeCollider>(); if ((volumeCollider != null) && (Application.isPlaying)) { Mesh collisionMesh = volumeCollider.BuildMeshFromNodeHandle(nodeHandle); MeshCollider meshCollider = gameObject.GetOrAddComponent <MeshCollider>() as MeshCollider; meshCollider.sharedMesh = collisionMesh; } } // If there is no mesh in Cubiquity then we make sure there isn't on in Unity. else { MeshCollider meshCollider = gameObject.GetComponent <MeshCollider>() as MeshCollider; if (meshCollider) { DestroyImmediate(meshCollider); } MeshRenderer meshRenderer = gameObject.GetComponent <MeshRenderer>() as MeshRenderer; if (meshRenderer) { DestroyImmediate(meshRenderer); } MeshFilter meshFilter = gameObject.GetComponent <MeshFilter>() as MeshFilter; if (meshFilter) { DestroyImmediate(meshFilter); } } meshLastSyncronised = CubiquityDLL.GetCurrentTime(); availableNodeSyncs--; nodeSyncsPerformed++; } VolumeRenderer vr = voxelTerrainGameObject.GetComponent <VolumeRenderer>(); MeshRenderer mr = gameObject.GetComponent <MeshRenderer>(); if (vr != null && mr != null) { if (mr.enabled != vr.enabled) // Not sure we really need this check? { mr.enabled = vr.enabled; } if (lastSyncronisedWithVolumeRenderer < vr.lastModified) { mr.receiveShadows = vr.receiveShadows; mr.castShadows = vr.castShadows; lastSyncronisedWithVolumeRenderer = Clock.timestamp; } } VolumeCollider vc = voxelTerrainGameObject.GetComponent <VolumeCollider>(); MeshCollider mc = gameObject.GetComponent <MeshCollider>(); if (vc != null && mc != null) { if (mc.enabled != vc.enabled) // Not sure we really need this check? { mc.enabled = vc.enabled; } if (lastSyncronisedWithVolumeCollider < vc.lastModified) { // Actual syncronization to be filled in in the future when we have something to syncronize. lastSyncronisedWithVolumeCollider = Clock.timestamp; } } //Now syncronise any children for (uint z = 0; z < 2; z++) { for (uint y = 0; y < 2; y++) { for (uint x = 0; x < 2; x++) { if (CubiquityDLL.HasChildNode(nodeHandle, x, y, z) == 1) { uint childNodeHandle = CubiquityDLL.GetChildNode(nodeHandle, x, y, z); GameObject childGameObject = GetChild(x, y, z); if (childGameObject == null) { childGameObject = OctreeNode.CreateOctreeNode(childNodeHandle, gameObject); SetChild(x, y, z, childGameObject); } //syncNode(childNodeHandle, childGameObject); OctreeNode childOctreeNode = childGameObject.GetComponent <OctreeNode>(); int syncs = childOctreeNode.syncNode(availableNodeSyncs, voxelTerrainGameObject); availableNodeSyncs -= syncs; nodeSyncsPerformed += syncs; } } } } return(nodeSyncsPerformed); }
/// \cond protected override bool SynchronizeOctree(uint availableSyncOperations) { VolumeCollider volumeCollider = gameObject.GetComponent <VolumeCollider>(); ColoredCubesVolumeRenderer volumeRenderer = gameObject.GetComponent <ColoredCubesVolumeRenderer>(); Vector3 camPos = CameraUtils.getCurrentCameraPosition(); // This is messy - perhaps the LOD thresold shold not be a parameter to update. Instead it could be passed // as a parameter during traversal, so different traversal could retrieve differnt LODs. We then wouldn't // want a single 'renderThisNode' member of Cubiquity nodes, but instead some threshold we could compare to. float lodThreshold = GetComponent <VolumeRenderer>() ? GetComponent <VolumeRenderer>().lodThreshold : 0.0f; int minimumLOD = GetComponent <VolumeRenderer>() ? GetComponent <VolumeRenderer>().minimumLOD : 0; // Although the LOD system is partially functional I don't feel it's ready for release yet. // The following line disables it by forcing the highest level of detail to always be used. minimumLOD = 0; // Next line commented out so the system starts up with LOD disabled. //if (volumeRenderer != null && volumeRenderer.hasChanged) { CubiquityDLL.SetLodRange(data.volumeHandle.Value, minimumLOD, 0); } bool cubiquityUpToDate = CubiquityDLL.UpdateVolume(data.volumeHandle.Value, camPos.x, camPos.y, camPos.z, lodThreshold); if (CubiquityDLL.HasRootOctreeNode(data.volumeHandle.Value) == 1) { uint rootNodeHandle = CubiquityDLL.GetRootOctreeNode(data.volumeHandle.Value); if (rootOctreeNodeGameObject == null) { rootOctreeNodeGameObject = OctreeNode.CreateOctreeNode(rootNodeHandle, gameObject); } OctreeNode.syncNode(ref availableSyncOperations, rootOctreeNodeGameObject, rootNodeHandle, gameObject); if (volumeRenderer != null && volumeRenderer.hasChanged) { OctreeNode.syncNodeWithVolumeRenderer(rootOctreeNodeGameObject, volumeRenderer, true); } if (volumeCollider != null && volumeCollider.hasChanged) { OctreeNode.syncNodeWithVolumeCollider(rootOctreeNodeGameObject, volumeCollider, true); } } // These properties might have to be synced with the volume (e.g. LOD settings) or with components // (e.g. shadow/material settings). Therefore we don't clear the flags until all syncing is completed. if (volumeRenderer != null) { volumeRenderer.hasChanged = false; } if (volumeCollider != null) { volumeCollider.hasChanged = false; } // If there were still sync operations available then there was no more syncing to be done with the // Cubiquity octree. So if the Cubiquity octree was also up to date then we have synced everything. return(cubiquityUpToDate && availableSyncOperations > 0); }
public static void syncNode(ref uint availableSyncOperations, GameObject nodeGameObject, uint nodeHandle, GameObject voxelTerrainGameObject) { OctreeNode octreeNode = nodeGameObject.GetComponent <OctreeNode>(); CuOctreeNode cuOctreeNode = CubiquityDLL.GetOctreeNode(nodeHandle); //////////////////////////////////////////////////////////////////////////////// // Has anything in this node or its children changed? If so, we may need to syncronise the node's properties, mesh and // structure. Each of these can be tested against a timestamp. We may also need to do this recursively on child nodes. //////////////////////////////////////////////////////////////////////////////// if (cuOctreeNode.nodeOrChildrenLastChanged > octreeNode.nodeAndChildrenLastSynced) { bool resyncedProperties = false; // See comments where this is tested - it's a bit of a hack //////////////////////////////////////////////////////////////////////////////// // 1st test - Have the properties of the node changed? //////////////////////////////////////////////////////////////////////////////// if (cuOctreeNode.propertiesLastChanged > octreeNode.propertiesLastSynced) { octreeNode.renderThisNode = cuOctreeNode.renderThisNode != 0; octreeNode.height = cuOctreeNode.height; octreeNode.propertiesLastSynced = CubiquityDLL.GetCurrentTime(); resyncedProperties = true; } //////////////////////////////////////////////////////////////////////////////// // 2nd test - Has the mesh changed and do we have time to syncronise it? //////////////////////////////////////////////////////////////////////////////// if ((cuOctreeNode.meshLastChanged > octreeNode.meshLastSynced) && (availableSyncOperations > 0)) { if (cuOctreeNode.hasMesh == 1) { // Set up the rendering mesh VolumeRenderer volumeRenderer = voxelTerrainGameObject.GetComponent <VolumeRenderer>(); if (volumeRenderer != null) { MeshFilter meshFilter = nodeGameObject.GetOrAddComponent <MeshFilter>() as MeshFilter; if (meshFilter.sharedMesh == null) { meshFilter.sharedMesh = new Mesh(); } MeshRenderer meshRenderer = nodeGameObject.GetOrAddComponent <MeshRenderer>() as MeshRenderer; if (voxelTerrainGameObject.GetComponent <Volume>().GetType() == typeof(TerrainVolume)) { MeshConversion.BuildMeshFromNodeHandleForTerrainVolume(meshFilter.sharedMesh, nodeHandle, false); } else if (voxelTerrainGameObject.GetComponent <Volume>().GetType() == typeof(ColoredCubesVolume)) { MeshConversion.BuildMeshFromNodeHandleForColoredCubesVolume(meshFilter.sharedMesh, nodeHandle, false); } meshRenderer.enabled = volumeRenderer.enabled && octreeNode.renderThisNode; // For syncing materials, shadow properties, etc. syncNodeWithVolumeRenderer(nodeGameObject, volumeRenderer, false); } // Set up the collision mesh VolumeCollider volumeCollider = voxelTerrainGameObject.GetComponent <VolumeCollider>(); if (volumeCollider != null) { bool useCollider = volumeCollider.useInEditMode || Application.isPlaying; if (useCollider) { // I'm not quite comfortable with this. For some reason we have to create this new mesh, fill it, // and set it as the collider's shared mesh, whereas I would rather just pass the collider's sharedMesh // straight to the functon that fills it. For some reason that doesn't work properly, and we see // issues with objects falling through terrain or not updating when part of the terrain is deleted. // It's to be investigated further... perhaps we could try deleting and recreating the MeshCollider? // Still, the approach below seems to work properly. Mesh collisionMesh = new Mesh(); if (voxelTerrainGameObject.GetComponent <Volume>().GetType() == typeof(TerrainVolume)) { MeshConversion.BuildMeshFromNodeHandleForTerrainVolume(collisionMesh, nodeHandle, true); } else if (voxelTerrainGameObject.GetComponent <Volume>().GetType() == typeof(ColoredCubesVolume)) { MeshConversion.BuildMeshFromNodeHandleForColoredCubesVolume(collisionMesh, nodeHandle, true); } MeshCollider meshCollider = nodeGameObject.GetOrAddComponent <MeshCollider>() as MeshCollider; meshCollider.sharedMesh = collisionMesh; } } } // If there is no mesh in Cubiquity then we make sure there isn't one in Unity. else { MeshCollider meshCollider = nodeGameObject.GetComponent <MeshCollider>() as MeshCollider; if (meshCollider) { Utility.DestroyOrDestroyImmediate(meshCollider); } MeshRenderer meshRenderer = nodeGameObject.GetComponent <MeshRenderer>() as MeshRenderer; if (meshRenderer) { Utility.DestroyOrDestroyImmediate(meshRenderer); } MeshFilter meshFilter = nodeGameObject.GetComponent <MeshFilter>() as MeshFilter; if (meshFilter) { Utility.DestroyOrDestroyImmediate(meshFilter); } } octreeNode.meshLastSynced = CubiquityDLL.GetCurrentTime(); availableSyncOperations--; } // We want to syncronize the properties before the mesh, so that the enabled flag can be set correctly when the mesh // is created. But we also want to syncronize properties after the mesh, so we can apply the correct enabled flag to // existing meshes when the node's 'renderThisNode' flag has changed. Therefore we set the 'resyncedProperties' flag // previously to let ourseves know that we should come back an finish the propertiy syncing here. It's a bit of a hack. if (resyncedProperties) { VolumeRenderer volumeRenderer = voxelTerrainGameObject.GetComponent <VolumeRenderer>(); if (volumeRenderer != null) { syncNodeWithVolumeRenderer(nodeGameObject, volumeRenderer, false); } VolumeCollider volumeCollider = voxelTerrainGameObject.GetComponent <VolumeCollider>(); if (volumeCollider != null) { syncNodeWithVolumeCollider(nodeGameObject, volumeCollider, false); } } uint[, ,] childHandleArray = new uint[2, 2, 2]; childHandleArray[0, 0, 0] = cuOctreeNode.childHandle000; childHandleArray[0, 0, 1] = cuOctreeNode.childHandle001; childHandleArray[0, 1, 0] = cuOctreeNode.childHandle010; childHandleArray[0, 1, 1] = cuOctreeNode.childHandle011; childHandleArray[1, 0, 0] = cuOctreeNode.childHandle100; childHandleArray[1, 0, 1] = cuOctreeNode.childHandle101; childHandleArray[1, 1, 0] = cuOctreeNode.childHandle110; childHandleArray[1, 1, 1] = cuOctreeNode.childHandle111; //////////////////////////////////////////////////////////////////////////////// // 3rd test - Has the structure of the octree node changed (gained or lost children)? //////////////////////////////////////////////////////////////////////////////// if (cuOctreeNode.structureLastChanged > octreeNode.structureLastSynced) { //Now syncronise any children for (uint z = 0; z < 2; z++) { for (uint y = 0; y < 2; y++) { for (uint x = 0; x < 2; x++) { if (childHandleArray[x, y, z] != 0xFFFFFFFF) { uint childNodeHandle = childHandleArray[x, y, z]; if (octreeNode.GetChild(x, y, z) == null) { octreeNode.SetChild(x, y, z, OctreeNode.CreateOctreeNode(childNodeHandle, nodeGameObject)); } } else { if (octreeNode.GetChild(x, y, z)) { Utility.DestroyOrDestroyImmediate(octreeNode.GetChild(x, y, z)); octreeNode.SetChild(x, y, z, null); } } } } } octreeNode.structureLastSynced = CubiquityDLL.GetCurrentTime(); } //////////////////////////////////////////////////////////////////////////////// // The last step of syncronization is to apply it recursively to our children. //////////////////////////////////////////////////////////////////////////////// for (uint z = 0; z < 2; z++) { for (uint y = 0; y < 2; y++) { for (uint x = 0; x < 2; x++) { if (octreeNode.GetChild(x, y, z) != null && availableSyncOperations > 0) { OctreeNode.syncNode(ref availableSyncOperations, octreeNode.GetChild(x, y, z), childHandleArray[x, y, z], voxelTerrainGameObject); } } } } // We've reached the end of our syncronization process. If there are still sync operations available then // we did less work then we could have, which implies we finished. Therefore mark the whole tree as synced. if (availableSyncOperations > 0) { octreeNode.nodeAndChildrenLastSynced = CubiquityDLL.GetCurrentTime(); } } }