Last Updated: Apr 29, 2017
Tutorial Accomplishments
- Create a Manager that knows about all the Holograms to instantiate
- Update the Object Placer to instantiate Holograms based on the Manager
This section of the tutorial will focus on using the techniques for placing and scaling a hologram from the previous tutorial and create a manager to instantiate many objects of different sizes. At the end of this tutorial you will have an entire scene calculated with Spatial Understanding and the holograms placed and scaled in the real world.
Create the Object Collection Manger
Open the project in Unity. Create a new script called ObjectCollectionManager.cs. Add the Manager to your placement GameObject. Your scene should look like this:
Open the script in Visual Studio. Add the following code to the ObjectCollectionManager.cs:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using HoloToolkit.Unity; | |
using UnityEngine; | |
public class ObjectCollectionManager : Singleton<ObjectCollectionManager> | |
{ | |
[Tooltip("A collection of Wide building prefabs to generate in the world.")] | |
public List<GameObject> WideBuildingPrefabs; | |
[Tooltip("The desired size of wide buildings in the world.")] | |
public Vector3 WideBuildingSize = new Vector3(1.0f, .5f, .5f); | |
[Tooltip("The ground to place under buildings.")] | |
public GameObject BuildingGround; | |
[Tooltip("Will be calculated at runtime if is not preset.")] | |
public float ScaleFactor; | |
public List<GameObject> ActiveHolograms = new List<GameObject>(); | |
public void CreateWideBuilding(int number, Vector3 positionCenter, Quaternion rotation) | |
{ | |
CreateBuilding(WideBuildingPrefabs[number], positionCenter, rotation, WideBuildingSize); | |
} | |
private void CreateBuilding(GameObject buildingToCreate, Vector3 positionCenter, Quaternion rotation, Vector3 desiredSize) | |
{ | |
// Stay center in the square but move down to the ground | |
var position = positionCenter – new Vector3(0, desiredSize.y * .5f, 0); | |
GameObject newObject = Instantiate(buildingToCreate, position, rotation) as GameObject; | |
if (newObject != null) | |
{ | |
// Set the parent of the new object the GameObject it was placed on | |
newObject.transform.parent = gameObject.transform; | |
newObject.transform.localScale = RescaleToSameScaleFactor(buildingToCreate); | |
ActiveHolograms.Add(newObject); | |
} | |
} | |
public void CreateGround(GameObject groundToCreate, Vector3 positionCenter, Quaternion rotation, Vector3 size) | |
{ | |
// Stay center in the square but move down to the ground | |
var position = positionCenter – new Vector3(0, size.y * .5f, 0); | |
GameObject newObject = Instantiate(groundToCreate, position, rotation) as GameObject; | |
if (newObject != null) | |
{ | |
// Set the parent of the new object the GameObject it was placed on | |
newObject.transform.parent = gameObject.transform; | |
newObject.transform.localScale = StretchToFit(groundToCreate, size); | |
ActiveHolograms.Add(newObject); | |
} | |
} | |
private Vector3 RescaleToSameScaleFactor(GameObject objectToScale) | |
{ | |
// ReSharper disable once CompareOfFloatsByEqualityOperator | |
if (ScaleFactor == 0f) | |
{ | |
CalculateScaleFactor(); | |
} | |
return objectToScale.transform.localScale * ScaleFactor; | |
} | |
private Vector3 StretchToFit(GameObject obj, Vector3 desiredSize) | |
{ | |
var curBounds = GetBoundsForAllChildren(obj).size; | |
return new Vector3(desiredSize.x / curBounds.x / 2, desiredSize.y, desiredSize.z / curBounds.z / 2); | |
} | |
private void CalculateScaleFactor() | |
{ | |
float maxScale = float.MaxValue; | |
var ratio = CalcScaleFactorHelper(WideBuildingPrefabs, WideBuildingSize); | |
if (ratio < maxScale) | |
{ | |
maxScale = ratio; | |
} | |
ScaleFactor = maxScale; | |
} | |
private float CalcScaleFactorHelper(List<GameObject> objects, Vector3 desiredSize) | |
{ | |
float maxScale = float.MaxValue; | |
foreach (var obj in objects) | |
{ | |
var curBounds = GetBoundsForAllChildren(obj).size; | |
var difference = curBounds – desiredSize; | |
float ratio; | |
if (difference.x > difference.y && difference.x > difference.z) | |
{ | |
ratio = desiredSize.x / curBounds.x; | |
} | |
else if (difference.y > difference.x && difference.y > difference.z) | |
{ | |
ratio = desiredSize.y / curBounds.y; | |
} | |
else | |
{ | |
ratio = desiredSize.z / curBounds.z; | |
} | |
if (ratio < maxScale) | |
{ | |
maxScale = ratio; | |
} | |
} | |
return maxScale; | |
} | |
private Bounds GetBoundsForAllChildren(GameObject findMyBounds) | |
{ | |
Bounds result = new Bounds(Vector3.zero, Vector3.zero); | |
foreach (var curRenderer in findMyBounds.GetComponentsInChildren<Renderer>()) | |
{ | |
if (result.extents == Vector3.zero) | |
{ | |
result = curRenderer.bounds; | |
} | |
else | |
{ | |
result.Encapsulate(curRenderer.bounds); | |
} | |
} | |
return result; | |
} | |
} |
We start by establishing a few properties that are used to manage the desired size of buildings and a place to hold all the models. The ScaleFactor is used to track (or force) the scaling that will happen to the holograms. Finally, the ActiveHolograms list is used to track all the holograms after they are instantiated, this makes it easy to add other scripts that manipulate the holograms after they are instantiated.
We add the CreateWideBuilding method which is a refactored version of the same method from the previous tutorial. This method calls CreateBuilding with parameters telling the method how to instantiate the building. The only difference from the last tutorial is that we add the instantiated gameobject to the ActiveHolograms list. We also replaced the call to RescaleDesiredSizeProportional with a call to the helper RescaleToSameScaleFactor. The reason for this change is that we need all the holograms to get the same scale factor applied to them so that they all look the same size relative to one another.
The new RescaleToSameScaleFactor is made of a series of utility functions that looks at every hologram in the manager and calculates the largest scale factor that would allow every hologram to fit within the bounding box that is established by WideBuildingSize. Once this is calculated it is reused in every future call and does not need to be recalculated.
Switch back to Unity 3D. The object collection manager now has some properties, we need to populate. Use the following settings:
The Object Collection Manager is now configured to display three different buildings, scaled to fit within a bounding box of 1 x 0.5 x 0.5 meters.
Some of the functionality of the Object Placer Script has moved into the Object Collection Manager. Next we will update Object Placer to remove that redundant functionality. Open ObjectPlacer.cs in Visual Studio. Edit the Contents to:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using HoloToolkit.Unity; | |
using UnityEngine; | |
public class ObjectPlacer : MonoBehaviour | |
{ | |
public bool DrawDebugBoxes = false; | |
public bool DrawBuildings = true; | |
public SpatialUnderstandingCustomMesh SpatialUnderstandingMesh; | |
private readonly List<BoxDrawer.Box> _lineBoxList = new List<BoxDrawer.Box>(); | |
private readonly Queue<PlacementResult> _results = new Queue<PlacementResult>(); | |
private bool _timeToHideMesh; | |
private BoxDrawer _boxDrawing; | |
// Use this for initialization | |
void Start() | |
{ | |
if (DrawDebugBoxes) | |
{ | |
_boxDrawing = new BoxDrawer(gameObject); | |
} | |
} | |
void Update() | |
{ | |
ProcessPlacementResults(); | |
if (_timeToHideMesh) | |
{ | |
SpatialUnderstandingState.Instance.HideText = true; | |
HideGridEnableOcclulsion(); | |
_timeToHideMesh = false; | |
} | |
if (DrawDebugBoxes) | |
{ | |
_boxDrawing.UpdateBoxes(_lineBoxList); | |
} | |
} | |
private void HideGridEnableOcclulsion() | |
{ | |
SpatialUnderstandingMesh.DrawProcessedMesh = false; | |
} | |
public void CreateScene() | |
{ | |
// Only if we're enabled | |
if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding) | |
{ | |
return; | |
} | |
SpatialUnderstandingDllObjectPlacement.Solver_Init(); | |
SpatialUnderstandingState.Instance.SpaceQueryDescription = "Generating World"; | |
List<PlacementQuery> queries = new List<PlacementQuery>(); | |
if (DrawBuildings) | |
{ | |
queries.AddRange(AddBuildings()); | |
} | |
GetLocationsFromSolver(queries); | |
} | |
public List<PlacementQuery> AddBuildings() | |
{ | |
var queries = CreateLocationQueriesForSolver(ObjectCollectionManager.Instance.WideBuildingPrefabs.Count, ObjectCollectionManager.Instance.WideBuildingSize, ObjectType.WideBuilding); | |
return queries; | |
} | |
private int _placedWideBuilding; | |
private void ProcessPlacementResults() | |
{ | |
if (_results.Count > 0) | |
{ | |
var toPlace = _results.Dequeue(); | |
// Output | |
if (DrawDebugBoxes) | |
{ | |
DrawBox(toPlace, Color.red); | |
} | |
var rotation = Quaternion.LookRotation(toPlace.Normal, Vector3.up); | |
switch (toPlace.ObjType) | |
{ | |
case ObjectType.WideBuilding: | |
ObjectCollectionManager.Instance.CreateGround(ObjectCollectionManager.Instance.BuildingGround, toPlace.Position, rotation, toPlace.Dimensions); | |
ObjectCollectionManager.Instance.CreateWideBuilding(_placedWideBuilding++, toPlace.Position, rotation); | |
break; | |
} | |
} | |
} | |
private void DrawBox(PlacementResult boxLocation, Color color) | |
{ | |
if (boxLocation != null) | |
{ | |
_lineBoxList.Add( | |
new BoxDrawer.Box( | |
boxLocation.Position, | |
Quaternion.LookRotation(boxLocation.Normal, Vector3.up), | |
color, | |
boxLocation.Dimensions * 0.5f) | |
); | |
} | |
} | |
private void GetLocationsFromSolver(List<PlacementQuery> placementQueries) | |
{ | |
#if UNITY_WSA && !UNITY_EDITOR | |
System.Threading.Tasks.Task.Run(() => | |
{ | |
// Go through the queries in the list | |
for (int i = 0; i < placementQueries.Count; ++i) | |
{ | |
var result = PlaceObject(placementQueries[i].ObjType.ToString() + i, | |
placementQueries[i].PlacementDefinition, | |
placementQueries[i].Dimensions, | |
placementQueries[i].ObjType, | |
placementQueries[i].PlacementRules, | |
placementQueries[i].PlacementConstraints); | |
if (result != null) | |
{ | |
_results.Enqueue(result); | |
} | |
} | |
_timeToHideMesh = true; | |
}); | |
#else | |
_timeToHideMesh = true; | |
#endif | |
} | |
private PlacementResult PlaceObject(string placementName, | |
SpatialUnderstandingDllObjectPlacement.ObjectPlacementDefinition placementDefinition, | |
Vector3 boxFullDims, | |
ObjectType objType, | |
List<SpatialUnderstandingDllObjectPlacement.ObjectPlacementRule> placementRules = null, | |
List<SpatialUnderstandingDllObjectPlacement.ObjectPlacementConstraint> placementConstraints = null) | |
{ | |
// New query | |
if (SpatialUnderstandingDllObjectPlacement.Solver_PlaceObject( | |
placementName, | |
SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(placementDefinition), | |
(placementRules != null) ? placementRules.Count : 0, | |
((placementRules != null) && (placementRules.Count > 0)) ? SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(placementRules.ToArray()) : IntPtr.Zero, | |
(placementConstraints != null) ? placementConstraints.Count : 0, | |
((placementConstraints != null) && (placementConstraints.Count > 0)) ? SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(placementConstraints.ToArray()) : IntPtr.Zero, | |
SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticObjectPlacementResultPtr()) > 0) | |
{ | |
SpatialUnderstandingDllObjectPlacement.ObjectPlacementResult placementResult = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticObjectPlacementResult(); | |
return new PlacementResult(placementResult.Clone() as SpatialUnderstandingDllObjectPlacement.ObjectPlacementResult, boxFullDims, objType); | |
} | |
return null; | |
} | |
private List<PlacementQuery> CreateLocationQueriesForSolver(int desiredLocationCount, Vector3 boxFullDims, ObjectType objType) | |
{ | |
List<PlacementQuery> placementQueries = new List<PlacementQuery>(); | |
var halfBoxDims = boxFullDims * .5f; | |
var disctanceFromOtherObjects = halfBoxDims.x > halfBoxDims.z ? halfBoxDims.x * 3f : halfBoxDims.z * 3f; | |
for (int i = 0; i < desiredLocationCount; ++i) | |
{ | |
var placementRules = new List<SpatialUnderstandingDllObjectPlacement.ObjectPlacementRule> | |
{ | |
SpatialUnderstandingDllObjectPlacement.ObjectPlacementRule.Create_AwayFromOtherObjects(disctanceFromOtherObjects) | |
}; | |
var placementConstraints = new List<SpatialUnderstandingDllObjectPlacement.ObjectPlacementConstraint>(); | |
SpatialUnderstandingDllObjectPlacement.ObjectPlacementDefinition placementDefinition = SpatialUnderstandingDllObjectPlacement.ObjectPlacementDefinition.Create_OnFloor(halfBoxDims); | |
placementQueries.Add( | |
new PlacementQuery(placementDefinition, | |
boxFullDims, | |
objType, | |
placementRules, | |
placementConstraints | |
)); | |
} | |
return placementQueries; | |
} | |
} |
These changes will remove some of the properties in Unity 3D. Switch back to Unity 3D. Verify that your Placement GameObject properties match these:
Build and Deploy to your device. Make sure you map a lot of floor space, and you should now see three buildings. Notice that if you map a small amount of floor space the ObjectPlacer handles it gracefully and only displays as many holograms that it can fit in the available floor space.
Add other Types of Holograms
Next we will extend this to generate many different types of buildings, and create an entire town. Update ObjectCollectionManager.cs with these changes:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using HoloToolkit.Unity; | |
using UnityEngine; | |
public class ObjectCollectionManager : Singleton<ObjectCollectionManager> | |
{ | |
[Tooltip("A collection of square building prefabs to generate in the world.")] | |
public List<GameObject> SquareBuildingPrefabs; | |
[Tooltip("The desired size of square buildings in the world.")] | |
public Vector3 SquareBuildingSize = new Vector3(.5f, .5f, .5f); | |
[Tooltip("A collection of Wide building prefabs to generate in the world.")] | |
public List<GameObject> WideBuildingPrefabs; | |
[Tooltip("The desired size of wide buildings in the world.")] | |
public Vector3 WideBuildingSize = new Vector3(1.0f, .5f, .5f); | |
[Tooltip("A collection of tall building prefabs to generate in the world.")] | |
public List<GameObject> TallBuildingPrefabs; | |
[Tooltip("The desired size of tall buildings in the world.")] | |
public Vector3 TallBuildingSize = new Vector3(.25f, .05f, .25f); | |
[Tooltip("A collection of tree prefabs to generate in the world.")] | |
public List<GameObject> TreePrefabs; | |
[Tooltip("The desired size of trees in the world.")] | |
public Vector3 TreeSize = new Vector3(.25f, .5f, .25f); | |
[Tooltip("Will be calculated at runtime if is not preset.")] | |
public float ScaleFactor; | |
public List<GameObject> ActiveHolograms = new List<GameObject>(); | |
public void CreateSquareBuilding(int number, Vector3 positionCenter, Quaternion rotation) | |
{ | |
CreateBuilding(SquareBuildingPrefabs[number], positionCenter, rotation, SquareBuildingSize); | |
} | |
public void CreateTallBuilding(int number, Vector3 positionCenter, Quaternion rotation) | |
{ | |
CreateBuilding(TallBuildingPrefabs[number], positionCenter, rotation, TallBuildingSize); | |
} | |
public void CreateWideBuilding(int number, Vector3 positionCenter, Quaternion rotation) | |
{ | |
CreateBuilding(WideBuildingPrefabs[number], positionCenter, rotation, WideBuildingSize); | |
} | |
private void CreateBuilding(GameObject buildingToCreate, Vector3 positionCenter, Quaternion rotation, Vector3 desiredSize) | |
{ | |
// Stay center in the square but move down to the ground | |
var position = positionCenter – new Vector3(0, desiredSize.y * .5f, 0); | |
GameObject newObject = Instantiate(buildingToCreate, position, rotation); | |
if (newObject != null) | |
{ | |
// Set the parent of the new object the GameObject it was placed on | |
newObject.transform.parent = gameObject.transform; | |
newObject.transform.localScale = RescaleToSameScaleFactor(buildingToCreate); | |
ActiveHolograms.Add(newObject); | |
} | |
} | |
public void CreateTree(int number, Vector3 positionCenter, Quaternion rotation) | |
{ | |
// Stay center in the square but move down to the ground | |
var position = positionCenter – new Vector3(0, TreeSize.y * .5f, 0); | |
GameObject newObject = Instantiate(TreePrefabs[number], position, rotation); | |
if (newObject != null) | |
{ | |
// Set the parent of the new object the GameObject it was placed on | |
newObject.transform.parent = gameObject.transform; | |
newObject.transform.localScale = RescaleToSameScaleFactor(TreePrefabs[number]); | |
ActiveHolograms.Add(newObject); | |
} | |
} | |
private Vector3 RescaleToSameScaleFactor(GameObject objectToScale) | |
{ | |
// ReSharper disable once CompareOfFloatsByEqualityOperator | |
if (ScaleFactor == 0f) | |
{ | |
CalculateScaleFactor(); | |
} | |
return objectToScale.transform.localScale * ScaleFactor; | |
} | |
private Vector3 StretchToFit(GameObject obj, Vector3 desiredSize) | |
{ | |
var curBounds = GetBoundsForAllChildren(obj).size; | |
return new Vector3(desiredSize.x / curBounds.x / 2, desiredSize.y, desiredSize.z / curBounds.z / 2); | |
} | |
private void CalculateScaleFactor() | |
{ | |
float maxScale = float.MaxValue; | |
var ratio = CalcScaleFactorHelper(WideBuildingPrefabs, WideBuildingSize); | |
if (ratio < maxScale) | |
{ | |
maxScale = ratio; | |
} | |
ScaleFactor = maxScale; | |
} | |
private float CalcScaleFactorHelper(List<GameObject> objects, Vector3 desiredSize) | |
{ | |
float maxScale = float.MaxValue; | |
foreach (var obj in objects) | |
{ | |
var curBounds = GetBoundsForAllChildren(obj).size; | |
var difference = curBounds – desiredSize; | |
float ratio; | |
if (difference.x > difference.y && difference.x > difference.z) | |
{ | |
ratio = desiredSize.x / curBounds.x; | |
} | |
else if (difference.y > difference.x && difference.y > difference.z) | |
{ | |
ratio = desiredSize.y / curBounds.y; | |
} | |
else | |
{ | |
ratio = desiredSize.z / curBounds.z; | |
} | |
if (ratio < maxScale) | |
{ | |
maxScale = ratio; | |
} | |
} | |
return maxScale; | |
} | |
private Bounds GetBoundsForAllChildren(GameObject findMyBounds) | |
{ | |
Bounds result = new Bounds(Vector3.zero, Vector3.zero); | |
foreach (var curRenderer in findMyBounds.GetComponentsInChildren<Renderer>()) | |
{ | |
if (result.extents == Vector3.zero) | |
{ | |
result = curRenderer.bounds; | |
} | |
else | |
{ | |
result.Encapsulate(curRenderer.bounds); | |
} | |
} | |
return result; | |
} | |
} |
These changes add properties to allow you to specify other types of building and trees. These work just like the methods we created earlier to create wide buildings.
Modify ObjectPlacer.cs to contain these contents:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using HoloToolkit.Unity; | |
using UnityEngine; | |
public class ObjectPlacer : MonoBehaviour | |
{ | |
public bool DrawDebugBoxes = false; | |
public bool DrawBuildings = true; | |
public bool DrawTrees = true; | |
public SpatialUnderstandingCustomMesh SpatialUnderstandingMesh; | |
private readonly List<BoxDrawer.Box> _lineBoxList = new List<BoxDrawer.Box>(); | |
private readonly Queue<PlacementResult> _results = new Queue<PlacementResult>(); | |
private bool _timeToHideMesh; | |
private BoxDrawer _boxDrawing; | |
// Use this for initialization | |
void Start() | |
{ | |
if (DrawDebugBoxes) | |
{ | |
_boxDrawing = new BoxDrawer(gameObject); | |
} | |
} | |
void Update() | |
{ | |
ProcessPlacementResults(); | |
if (_timeToHideMesh) | |
{ | |
SpatialUnderstandingState.Instance.HideText = true; | |
HideGridEnableOcclulsion(); | |
_timeToHideMesh = false; | |
} | |
if (DrawDebugBoxes) | |
{ | |
_boxDrawing.UpdateBoxes(_lineBoxList); | |
} | |
} | |
private void HideGridEnableOcclulsion() | |
{ | |
SpatialUnderstandingMesh.DrawProcessedMesh = false; | |
} | |
public void CreateScene() | |
{ | |
// Only if we're enabled | |
if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding) | |
{ | |
return; | |
} | |
SpatialUnderstandingDllObjectPlacement.Solver_Init(); | |
SpatialUnderstandingState.Instance.SpaceQueryDescription = "Generating World"; | |
List<PlacementQuery> queries = new List<PlacementQuery>(); | |
if (DrawBuildings) | |
{ | |
queries.AddRange(AddBuildings()); | |
} | |
if (DrawTrees) | |
{ | |
queries.AddRange(AddTrees()); | |
} | |
GetLocationsFromSolver(queries); | |
} | |
public List<PlacementQuery> AddBuildings() | |
{ | |
var queries = CreateLocationQueriesForSolver(ObjectCollectionManager.Instance.WideBuildingPrefabs.Count, ObjectCollectionManager.Instance.WideBuildingSize, ObjectType.WideBuilding); | |
queries.AddRange(CreateLocationQueriesForSolver(ObjectCollectionManager.Instance.SquareBuildingPrefabs.Count, ObjectCollectionManager.Instance.SquareBuildingSize, ObjectType.SquareBuilding)); | |
queries.AddRange(CreateLocationQueriesForSolver(ObjectCollectionManager.Instance.TallBuildingPrefabs.Count, ObjectCollectionManager.Instance.TallBuildingSize, ObjectType.TallBuilding)); | |
return queries; | |
} | |
public List<PlacementQuery> AddTrees() | |
{ | |
var queries = CreateLocationQueriesForSolver(ObjectCollectionManager.Instance.TreePrefabs.Count, ObjectCollectionManager.Instance.TreeSize, ObjectType.Tree); | |
return queries; | |
} | |
private int _placedSquareBuilding; | |
private int _placedTallBuilding; | |
private int _placedWideBuilding; | |
private int _placedTree; | |
private void ProcessPlacementResults() | |
{ | |
if (_results.Count > 0) | |
{ | |
var toPlace = _results.Dequeue(); | |
// Output | |
if (DrawDebugBoxes) | |
{ | |
DrawBox(toPlace, Color.red); | |
} | |
var rotation = Quaternion.LookRotation(toPlace.Normal, Vector3.up); | |
switch (toPlace.ObjType) | |
{ | |
case ObjectType.SquareBuilding: | |
ObjectCollectionManager.Instance.CreateSquareBuilding(_placedSquareBuilding++, toPlace.Position, rotation); | |
break; | |
case ObjectType.TallBuilding: | |
ObjectCollectionManager.Instance.CreateTallBuilding(_placedTallBuilding++, toPlace.Position, rotation); | |
break; | |
case ObjectType.WideBuilding: | |
ObjectCollectionManager.Instance.CreateWideBuilding(_placedWideBuilding++, toPlace.Position, rotation); | |
break; | |
case ObjectType.Tree: | |
ObjectCollectionManager.Instance.CreateTree(_placedTree++, toPlace.Position, rotation); | |
break; | |
} | |
} | |
} | |
private void DrawBox(PlacementResult boxLocation, Color color) | |
{ | |
if (boxLocation != null) | |
{ | |
_lineBoxList.Add( | |
new BoxDrawer.Box( | |
boxLocation.Position, | |
Quaternion.LookRotation(boxLocation.Normal, Vector3.up), | |
color, | |
boxLocation.Dimensions * 0.5f) | |
); | |
} | |
} | |
private void GetLocationsFromSolver(List<PlacementQuery> placementQueries) | |
{ | |
#if UNITY_WSA && !UNITY_EDITOR | |
System.Threading.Tasks.Task.Run(() => | |
{ | |
// Go through the queries in the list | |
for (int i = 0; i < placementQueries.Count; ++i) | |
{ | |
var result = PlaceObject(placementQueries[i].ObjType.ToString() + i, | |
placementQueries[i].PlacementDefinition, | |
placementQueries[i].Dimensions, | |
placementQueries[i].ObjType, | |
placementQueries[i].PlacementRules, | |
placementQueries[i].PlacementConstraints); | |
if (result != null) | |
{ | |
_results.Enqueue(result); | |
} | |
} | |
_timeToHideMesh = true; | |
}); | |
#else | |
_timeToHideMesh = true; | |
#endif | |
} | |
private PlacementResult PlaceObject(string placementName, | |
SpatialUnderstandingDllObjectPlacement.ObjectPlacementDefinition placementDefinition, | |
Vector3 boxFullDims, | |
ObjectType objType, | |
List<SpatialUnderstandingDllObjectPlacement.ObjectPlacementRule> placementRules = null, | |
List<SpatialUnderstandingDllObjectPlacement.ObjectPlacementConstraint> placementConstraints = null) | |
{ | |
// New query | |
if (SpatialUnderstandingDllObjectPlacement.Solver_PlaceObject( | |
placementName, | |
SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(placementDefinition), | |
(placementRules != null) ? placementRules.Count : 0, | |
((placementRules != null) && (placementRules.Count > 0)) ? SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(placementRules.ToArray()) : IntPtr.Zero, | |
(placementConstraints != null) ? placementConstraints.Count : 0, | |
((placementConstraints != null) && (placementConstraints.Count > 0)) ? SpatialUnderstanding.Instance.UnderstandingDLL.PinObject(placementConstraints.ToArray()) : IntPtr.Zero, | |
SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticObjectPlacementResultPtr()) > 0) | |
{ | |
SpatialUnderstandingDllObjectPlacement.ObjectPlacementResult placementResult = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticObjectPlacementResult(); | |
return new PlacementResult(placementResult.Clone() as SpatialUnderstandingDllObjectPlacement.ObjectPlacementResult, boxFullDims, objType); | |
} | |
return null; | |
} | |
private List<PlacementQuery> CreateLocationQueriesForSolver(int desiredLocationCount, Vector3 boxFullDims, ObjectType objType) | |
{ | |
List<PlacementQuery> placementQueries = new List<PlacementQuery>(); | |
var halfBoxDims = boxFullDims * .5f; | |
var disctanceFromOtherObjects = halfBoxDims.x > halfBoxDims.z ? halfBoxDims.x * 3f : halfBoxDims.z * 3f; | |
for (int i = 0; i < desiredLocationCount; ++i) | |
{ | |
var placementRules = new List<SpatialUnderstandingDllObjectPlacement.ObjectPlacementRule> | |
{ | |
SpatialUnderstandingDllObjectPlacement.ObjectPlacementRule.Create_AwayFromOtherObjects(disctanceFromOtherObjects) | |
}; | |
var placementConstraints = new List<SpatialUnderstandingDllObjectPlacement.ObjectPlacementConstraint>(); | |
SpatialUnderstandingDllObjectPlacement.ObjectPlacementDefinition placementDefinition = SpatialUnderstandingDllObjectPlacement.ObjectPlacementDefinition.Create_OnFloor(halfBoxDims); | |
placementQueries.Add( | |
new PlacementQuery(placementDefinition, | |
boxFullDims, | |
objType, | |
placementRules, | |
placementConstraints | |
)); | |
} | |
return placementQueries; | |
} | |
} |
These changes add an extra property that is used to tell the ObjectPlacer whether or not to draw the trees. We also added some logic to find locations for these trees. Notice that in the CreateScene method we are creating a work list of all the queries for every object we hope to place in the scene and sending all that work to the solver at once. Because the Object Placement Solver is computationally intensive it is better to do all of this work at once while the scene is loading. For this example I place the result of the query for each object in a work queue called _results which is serviced every frame. This causes the objects to pop up as each object is found, this probably is not desirable for a real application, but helps demonstrate the speed of the solver. When the solver is unable to place an item we continue without any errors to the user. The goal for this demo is to fill all the available floor space with holograms, not to display every hologram.
Switch back to Unity 3D. The Placement GameObject now has additional properties to configure. Set them with the following settings:
Build and Deploy to your device. You should now see a complete town generated. Because the spatial map is slightly different each time, the solver will place items differently in the same room. ObjectPlacer and ObjectCollectionManager can easily be customized to manage holograms for a different application, or even generalized as a component to use across different applications.
Tutorial Index
Versions: Unity 2017.1.0p5 | MixedRealityToolkit-Unity v1.2017.1.0 | Visual Studio 2017 15.3.2
Unity 3D Project Creation | How to create a HoloLens project in Unity 3D |
Source Control | Configure git for HoloLens / Unity work |
Spatial Mapping | How to spatial map a Room |
Object Surface Observer | Set up fake spatial map data for the Unity editor |
TagAlongs and Billboarding | Tag along instructions to the user to force text on screen |
Spatial Understanding | Add spatial understanding to get play space detail |
Finalizing Spatial Understanding | Complete Spatial Understanding and Input Manager |
Object Placement and Scaling | Find valid locations for holograms in the play space |
Hologram Management | Manage the holograms you want to place in the world |
Cursor and Voice | Add a cursor and voice commands |
Occlusion | Add occlusion with the real world to your scene |
Colliders and Rigidbodys | Add Colliders and RigidBodys to your holograms |
- Download the completed app Western Town in the Windows Store!
- Completed Source code from the entire tutorial available on GitHub.
So, is this part really up to date? The ObjectCollectionManager.cs script don´t seem to be correct.
It is up to date as of a few weeks ago, what seems to be wrong? What versions of everything are you using?
I’m also facing problems in this section.
After scanning the room, “Generating world” text brielfy enters the screen. After that there’s nothing rendered.