sobota, 13 maja 2017

[DSP 2017] 24# BUILD 2017 odc.2 (AI/ML, Mixed Reality)

Tym razem wybrałem kolejnych 8 pozycji. Generalnie chciałem, aby było coś z uczenia i oczywiście rzeczywistość mieszana. Od razu nasunęły mi się dwie zajawki. Po pierwsze: w szybkim ogarnięciu tylu filmów przydałaby się usługa robiąca podsumowania  z Azure, która właśnie została pokazana.  Po drugie: może kiedyś takie nagrania mogłyby być udostępniane w formacie 360, a wtedy po założeniu hełmu na głowę możnaby się niemal przenieść w czasie i przestrzeni, a konkretnie np. do sali z prezenterem. Wtedy żart z dawnych czasów, o tym że ktoś wszedł do telewizora okazałby się nieaktualny.

 

Supercharge your business with the power of AI

Spodziewałem się czegoś więcej, ale znajdziemy tam trochę przydatnych scenariuszy dla bardziej zwyczajnych, codziennych aplikacji tj. CRM.

image_thumb95

 

Reimagine what is possible with your videos using state-of-the-art AI technologies

WOW!

image_thumb98

image_thumb102

Video Indexer wygląda interesująco

image_thumb106

wyszukiwarka plików wideo po ich zawartości (wpisujemy słowo kluczowe)

analiza nagrania, gdzie wypowiedź negatywna, pozytywna, automatyczny transkrypt

image_thumb109

 

Navigating the AI Revolution

Wprowadzenie do tematyki AI

image_thumb112

image_thumb115

image_thumb118

image_thumb121

co powinien znać programista zajmujący się AI…

image_thumb124

image_thumb129

 

How Brainshark is making salespeople better through artificial intelligence and Microsoft HoloLens

Coaching na wymarciu?

image_thumb132

 

Immersive technology at Merck: Using Microsoft HoloLens to reimagine the molecular R&D process

Spodziewałem się czegoś więcej, ale znajdziemy trochę przydatnych informacji

image_thumb2

 

Machine Learning for developers, how to build even more intelligent apps and services

Kolejne wprowadzenie do podstaw AI, nawet lepsze niż to wcześniejsze. Nie ma jakichś całkowicie nowych przełomowych informacji, jest to raczej podsumowanie stanu obecnego.

image_thumb5[1]

image_thumb8

image_thumb11

image_thumb14

image_thumb17

 

Unity and Windows holographic tooling and intro to mixed reality

Zarąbista prezentacja. Polecam gorąco.

Mixed Reality Portal - mapuję przestrzeń kaskiem chodząc po pokoju, a potem zakładam na głowę i mam film w wirtualnym świecie 3D, który widać też na PC. To akurat już było, pisałem nawet już kiedyś o Mixed Reality Portal w Windows 10 (tylko bez kasku).

W Unity jest wsparcie tym razem dla rzeczywistości wirtualnej i mieszanej w Windows.

image_thumb21[1]

Uruchamianie na Mixed Reality Portal prosto z Unity.

Window –> Holographic Emulation  - uruchamianie z Unity na urządzeniu i w edytorze!

image_thumb24[1]

Łączymy się z HoloLens bezpośrednio z Unity

image_thumb29

wersja alpha

image_thumb32

image_thumb35

Bezpośrednia łączność z VS 2017 we wszystkich wersjach Unity

Teraz także dla MR Web Camera Support ?

image_thumb38

Demo “Adam”.

image_thumb42

image_thumb46

image_thumb49

360 Video w Unity

image_thumb52

image_thumb55

Chodzę sobie po zwykłym świecie w 360 stopni, a tu nagle gromada sztucznych ptaków, dajmy na to 100…

image_thumb58

 

Developer's Guide to the Galaxy #WinDev, Part 1

Cha cha, przyszedłem zabrać się na Developer Galaxy –Smile

image_thumb61

image_thumb64

Klimaty ala Zacier

image_thumb72

Vuforia, też trochę się nią bawiłem.

image_thumb75

adnotacje do kontaktów

image_thumb80

rozszerzenie w manifeście dla panelu People

PinnedContactManager - przypinanie osób do task bar

Notyfikacja w postaci animowanego obrazka

image_thumb83

image_thumb86

WebVR z Babilon.js

wirtualna rzeczywistość w przeglądarce

image_thumb89

cha cha cha tym razem w rzeczywistości mieszanej i w przeglądarce

image_thumb92

[DSP2017] 23# BUILD 2017 odc.1 (Keynote 1 & Keynote 2)

Jak co roku obejrzę dużo nagrań BUILD’a, a zaczynałem oglądać konferencje jeszcze od PDC i Mix’a. Pora na jakieś dzielenie się wrażeniami. Na mojej liście ‘to see’ znalazło się póki co przeszło 90 sesji. Zobaczymy ile uda się w tym roku obejrzeć lub chociaż rzucić okiem. Zacząłem od sesji Keynote, ponieważ dają przekrojowy pogląd na tematykę całego eventu. Generalnie polecam oglądanie, nabiera się programistycznego powera od nowa i nowe inspiracje.

 

Keynote Presentation

Nowa wizja świata po wizji “Mobile-First, Cloud-First”

Key01_01

Koniec z jedną platformą, teraz systemy na wiele urządzeń

Pilnowani w firmie przez inteligentną chmurę –Smile Uwaga! Incydent bezpieczeństwa.

Key01_02

Głośniki z Cortaną

Key01_03

Opaska pozwalająca osobie chorej na Parkinsona normalnie pisać

Key01_04

Shell linuksowy dla portalu Azure w przeglądarce

Key01_05

Aplikacja mobilna dla Azure na Android i iOS, a w niej także dostęp do konsoli!

Key01_06

Debugowanie w Visual Studio aplikacji na Azure ze snap pointami, do których można wracać

Key01_07

Visual Studio for Mac

MySQL i PostgreSQL jako usługi w Azure

Azure Cosmos DB - nowa baza jako usługa

Key01_08

Coraz więcej usług w Cognitive Services, a także ich otwarcie

image

image

Translator w Power Point

 

#MSBuild Day 2 Keynote

Windows 10 Fall Creators Update

Story Remix - nowy edytor wideo, w zasadzie sam buduje klipy na podstawie prostych działań użytkownika!

Rozpoznaje osoby na zdjęciach i klipach, wybiera ujęcia do postaci, muzyki, pozwala w inteligentny sposób dodawać elementy 3D

image

Microsoft Fluent Design System

image

image

image

image

image

image

image

Windows 10 & Microsoft Graph

Wrzucanie danych do chmury i różne urządzenia, różne scenariusze, interakcje itd.

OneDrive Files On-Demand - pobieranie na żądanie plików z chmury

image

Timeline, czyli co robiłem w przeszłości?

image

Kontynuowanie czynności na innych urządzeniach

Cloud-powered Clipboard – normalny kopiuj np. w PC i wklej np. w Androidzie!

image

Trwają prace nad:

image

Joe Belfiore po powrocie z długich wakacji przefarbował włosy na blond i głosi, że desktopowy Windows kocha wszystkie nasze urządzenia niezależnie od systemu.

image

.NET Standard 2.0 for UWP

https://docs.microsoft.com/dotnet/

XAML Standard 1.0

https://aka.ms/xamlstandard

Planowane wsparcie dla UWP i Xamarin.Forms

Współdzielenie kodu .NET i XAML pomiędzy różnymi platformami będzie znacznie prostsze.

image

Półprzezroczystość

image

Animacje

image

kończymy animację na docelowej stronie

image

łączenie się z Microsoft Graph (ludzie, aktywności i urządzenia)

Project Rome SDK for iOS

https://aka.ms/projectrome

Windows 10S

iTunes w Windows Store

Ubuntu, openSuse, Fedora w Windows !

Xamarin Live Player – pozwala uruchamiać / debugować na urządzeniu z iOS naszą aplikację bezpośrednio z poziomu Visual Studio w Windows

image

Automatyczne odświeżanie aplikacji w playerze po zapisaniu zmian w Visual Studio!

image

Mixed Reality - po założeniu okularów canvasem jest cała otaczająca nas przestrzeń, bardziej naturalna interakcja, czy to nie przyszłość ?

Motion Controllers for Mixed Reality

image

Rzeczywistość mieszana z Windows 10 jest bardzo kusząca. Zastawia mnie jedynie już tylko czy wszyscy w przyszłości zaczniemy nosić ciemne okulary lub hełmy na swoich głowach…

image

Hologram tancerza i tancerki! Zwłaszcza tancerka wygląda dość realistycznie.

image

image

czwartek, 11 maja 2017

[DSP2017] 22# HoloSurvivalShooter - skanuję pokój i na podłodze ustawiam zombi

Moja wersja gry Survival Shooter na HoloLens wzbogaciła się o kolejny ważny punkt z listy to do, a mianowicie chodzi o dynamiczne dodawanie zombi na scenę. Z tym punktem wiąże się dodatkowo jedna duża rzecz, której nie umieściłem na tej liście. Do tej pory wrzuciłem na sztywno kilka zombi z podaniem im współrzędnych, by sprawiały wrażenie, że są mniej więcej na podłodze (narysowanej dzięki spatial mapping). Dziś podejdziemy do tego w prawidłowy sposób. Skorzystamy z analizy skanowanej przestrzeni i będziemy zlecać umieszczanie obiektów na podłodze w inteligentny sposób.

Dla porządku dodam, że rozwój mini gry HoloSurvivalShooter został przedstawiony w dwóch ostatnich postach o HoloLens:

Projekt z tamtego okresu przeniosłem do gałęzi HoloSurvivalShooter1, natomiast dzisiejsza bardziej zaawansowana postać trafiła do HoloSurvivalShooter.

Skąd czerpałem wiedzę o tym jak rozpoznawać logicznie skanowaną przestrzeń i umieszczać na jej powierzchniach hologramy?

Zacząłem od dokumentacji, której wcześniej nie odwiedziłem:

W dokumentacji znajdziemy odwołanie do przykładu SpatialUnderstanding w HoloToolkitOstatnia wersja HoloToolkit nie zawiera już przykładów w swoim pakiecie dla Unity, ale znajdują się one nadal w źródłach. 

Aha, dodam że od tego razu zacząłem korzystać z ostatniej nowszej wersji HoloToolkit oraz przeszedłem na Unity 5.6. Ta wersja Unity wnosi jedną bardzo ciekawą rzecz, którą już testowałem, ale o tym może innym razem. Wróćmy do analizowania przestrzeni.

Mike Taulty poruszył tę tematykę w postach:

Zwłaszcza ostatni jest interesujący, wyjaśnia trzewia API. Okazuje się, że tak naprawdę za rozmieszczanie obiektów odpowiada niezarządzana biblioteka w C++, do której napisane są wrappery. Autor zmodyfikował nawet tę bibliotekę, ale nie jest to potrzebne w moim przypadku.

Poszukiwałem opisu przykładu z HoloToolkit, aby go zrozumieć i wykorzystać jakiś jego podzbiór, by zamiast sześcianów umieszczać zombi na podłodze. Trochę szukałem i w końcu natrafiłem na genialny tutorial na stronie http://www.cameronvetter.com/tag/tutorial/. Autor opisuje w nim krok po kroku, co robi, a także tłumaczy pisany przez siebie kod, a nawet objaśnia różne funkcje z API biblioteki dostarczanej w HoloToolkit. Moim zdaniem takie coś powinno znaleźć się w Holographic Academy. Przygotowałem sobie wstępnie scenę na nowym pustym projekcie wykonując:

Teraz mogłem zająć się esencją zawartą w:

Do tak powstałego projektu przeniosłem część obiektów sceny, zasoby i skrypty z dotychczasowej wersji gry. Do szczegółów wrócę poniżej, teraz pokażę jak to działa.

Po odpaleniu w emulatorze pojawia się skrawek siatki oraz napis informujący, by sobie trochę pochodzić po pokoju w celu zeskanowania przestrzeni do gry.

HVG00

Chodzę sobie i po chwili zaczynam dostawać bieżące dane o jakości skanowania.

HVG01

Gdy pojawi się żółty napis informujący, że system jest gotowy, możemy “kliknąć” i zakończyć skanowanie otaczającej nas przestrzeni. Oznacza to, że osiągnęliśmy jakość skanu wystarczającą do rozpoczęcia gry. Można sobie więcej pochodzić, by jeszcze poprawić siatkę.

HVG1

Po “kliknięciu” dane skanowania zostały ukryte (przy korzystaniu z HoloLens w prawdziwym pokoju siatka też powinna raczej zostać ukryta). Zostały za to rozmieszczone 3 zombi na podłodze, po chwili pojawią się nastęne 3 itd. Pojawił się też licznik punktów. Jak najadę kursorem na zombi i “kliknę” to oddam celny strzał. Po kilku takich strzałach go zabiję, on upadnie i zniknie, a ja - jak ostatnio - zdobędę za niego punkty.

HVG2

Jak to wygląda od strony dewelopera? W Unity powstała następująca scena:

HVU1

Pod obiekt HoloLensCamera jak poprzednio podpięty jest skrypt  PlayerShooting, w którym dokonałem małej zmiany. Uzależniłem oddanie strzału od tego, czy zakończyło się skanowanie przestrzeni (czy dane o skanowaniu są ukryte):

public void OnInputClicked(InputClickedEventData eventData)
    {
        if(timer >= timeBetweenBullets && Time.timeScale != 0 && SpatialUnderstandingState.Instance.HideText)
        {
            Shoot ();
        }
    }

Pole ScoreText ma kanał alfa na pełną przezroczystość, by na początku w trakcie skanowania nie było widoczne. Tak jak ostatnio jest podpięty do niego skrypt ScoreManager. Oczywiście zombi też mają swój skrypt jak poprzednio.

Najbardziej kluczowe są skrypty ObjectCollectionManager i ObjectPlacer. Pierwszy jest zbiorem moetod do dynamicznego dodawanie obiektów zombi na podłodze. Drugi korzysta z tych metod.

HVU8

Skrypt ObjectCollectionManager jest wzorowany na tym z tutoriala, ale przerobiłem go tak, by rysował zombi, a nie budynki i drzewa.

public class ObjectCollectionManager : Singleton<ObjectCollectionManager>
{

    [Tooltip("Zombunny prefab.")]
    public GameObject ZombunnyPrefab;

    [Tooltip("The desired size of Zombunny.")]
    public Vector3 ZombunnySize = new Vector3(.5f, 1.0f, .5f);

    [Tooltip("ZomBear prefabs.")]
    public GameObject ZomBearPrefab;

    [Tooltip("The desired size of ZomBear.")]
    public Vector3 ZomBearSize = new Vector3(.5f, 1.0f, .5f);

    [Tooltip("Hellephant prefabs.")]
    public GameObject HellephantPrefab;

    [Tooltip("The desired size of Hellephant.")]
    public Vector3 HellephantSize = new Vector3(1.2f, 1.2f, 1.2f);   

    [Tooltip("Will be calculated at runtime if is not preset.")]
    public float ScaleFactor;
   

    public void CreateZombunny(Vector3 positionCenter, Quaternion rotation)
    {
        CreateEnemy(ZombunnyPrefab, positionCenter, rotation, ZombunnySize);
    }

    public void CreateZomBear(Vector3 positionCenter, Quaternion rotation)
    {
        CreateEnemy(ZomBearPrefab, positionCenter, rotation, ZomBearSize);
    }

    public void CreateHellephant(Vector3 positionCenter, Quaternion rotation)
    {
        CreateEnemy(HellephantPrefab, positionCenter, rotation, HellephantSize);
    }

    private void CreateEnemy(GameObject enemyToCreate, 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(enemyToCreate, 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(enemyToCreate);           
        }
    }

    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(HellephantPrefab, HellephantSize);
        if (ratio < maxScale)
        {
            maxScale = ratio;
        }

        ScaleFactor = maxScale;
    }

    private float CalcScaleFactorHelper(GameObject obj, Vector3 desiredSize)
    {
        float maxScale = float.MaxValue;
       
        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;
    }
}

W skrypcie ObjectPlacer w stosunku do tutoriala dodałem pole ScoreText oraz parametr spawnTime. Mając referencję na ScoreText ustawiam na nim biały kolor (z pełnym kanałem alfa) po tym jak zakończy się skanowanie i zostaną ukryte informacje na temat jego jakości. SpawnTime to okres na stworzenie nowego zombi i okres przerwy do następnego tworzenia. Zamiast wszystko zbudować raz za pomocą metody CreateScene, przerobiłem to tak, że wydzieliłem logikę odpowiedzialną za dodanie obiektów do metody CreateSceneObjects, po czym zlecam powtarzanie jej wykonywania za pomocą standardowego w Unity wywołania InvokeRepeating.

public class ObjectPlacer : MonoBehaviour
{
    public bool DrawDebugBoxes = false;
    public bool DrawEnemies = true;   

    public SpatialUnderstandingCustomMesh SpatialUnderstandingMesh;

    public Text ScoreText;

    public float spawnTime = 12f;

    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;
            ScoreText.color = Color.white;
            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";

        InvokeRepeating("CreateSceneObjects", spawnTime, spawnTime);
    }

    private void CreateSceneObjects()
    {
        List<PlacementQuery> queries = new List<PlacementQuery>();

        if (DrawEnemies)
        {
            queries.AddRange(AddEnemies());
        }

        GetLocationsFromSolver(queries);
    }

    public List<PlacementQuery> AddEnemies()
    {

        var queries = CreateLocationQueriesForSolver(1, ObjectCollectionManager.Instance.ZombunnySize, ObjectType.Zombunny);
        queries.AddRange(CreateLocationQueriesForSolver(1, ObjectCollectionManager.Instance.ZomBearSize, ObjectType.ZomBear));
        queries.AddRange(CreateLocationQueriesForSolver(1, ObjectCollectionManager.Instance.HellephantSize, ObjectType.Hellephant));
        return queries;
    }       

    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.Zombunny:
                    ObjectCollectionManager.Instance.CreateZombunny(toPlace.Position, rotation);
                    break;
                case ObjectType.ZomBear:
                    ObjectCollectionManager.Instance.CreateZomBear(toPlace.Position, rotation);
                    break;
                case ObjectType.Hellephant:
                    ObjectCollectionManager.Instance.CreateHellephant(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;
    }

}

Tak się rozpisałem, że zapomniałem wyłączyć emulator HoloLens i namnożyło się w nim trochę tych zombi –SmileMożna powiedzieć, że powstał tłum jak na jakimś marszu czy manifestacji, ale pamiętajmy że to tylko hologramy.

HVG3

Trzeba dodać, że teraz zombi są jeszcze niegroźne, bo nie idą do mnie po podłodze pokoju i nie gryzą przy zbliżeniu. Nie tracę więc życia nic nie robiąc w grze, a zabijać je mogę. Następnym razem już tak lightowo być nie może –Smile Stay tuned.