mapbox / mapbox-unity-sdk

Mapbox Unity SDK - https://www.mapbox.com/unity/
Other
725 stars 210 forks source link

Prefabs along road spawn at random locations when loading new tiles #1672

Open henning101 opened 4 years ago

henning101 commented 4 years ago

Hi everyone! I am trying to instantiate prefabs at road joints (not at road centers or first vertex - which is possible with the prefab modifier). I copied the prefab modifier code and added the following:

// ...
GameObject go = null;
Vector3[] vertices = ve.Mesh.vertices;

if (_objects.ContainsKey(ve.GameObject))
{
    go = _objects[ve.GameObject];
}
else
{
    go = new GameObject("RoadJoints");
    _prefabList.Add(go);
    _objects.Add(ve.GameObject, go);
    go.transform.SetParent(ve.GameObject.transform, false);

    // My code:
    foreach (Vector3 vertex in vertices)
    {
        GameObject instance = Instantiate(_options.prefab);
        instance.transform.SetParent(ve.GameObject.transform, false);
        // Works on fresh reload, and then it seems like the vertex position become random:
        instance.transform.localPosition = vertex; 
    }
}
// ...

this works fine after a fresh map reload (after clearing the Mapbox file cache in the editor) but as soon as a I move the center transform (map set to "range around transform") the vertex positions seem random. It looks like some of them are not cleared from the buffer and others are randomly generated.

I don't quite understand why this happens because I thought the modifier is called for every road segment each of which has a defined number of points.

I'd be super glad if someone can point me in the right direction!

henning101 commented 4 years ago

I made some minimal progress. When I don't parent the instantiated prefabs to the ve.GameObject.transform the instances stay at their locations. Unfortunately they are not cleared anymore. I checked the name of the vector entities and for some reason there are vector entities with the same name at multiple locations?

This is the code I use as mesh modifier (primitive type: Point):

public class RoadJointPointModifier : MeshModifier
{

    public override ModifierType Type
    {
        get { return ModifierType.Preprocess; }
    }

    public override void SetProperties(ModifierProperties properties)
    {

    }

    public override void Run(VectorFeatureUnity feature, MeshData md, float scale)
    {
        AddVertices(feature, md);
    }

    public override void Run(VectorFeatureUnity feature, MeshData md, UnityTile tile = null)
    {
        AddVertices(feature, md);
    }

    public void AddVertices(VectorFeatureUnity feature, MeshData md)
    {
        foreach (List<Vector3> segment in feature.Points)
        {
            foreach (Vector3 point in segment)
            {
                md.Vertices.Add(point);
            }
        }
    }
}

and this is the code I use for the game object modifier:

public class RoadJointPrefabModifier : GameObjectModifier
{
    private Dictionary<string, GameObject> _objects;
    [SerializeField]
    private SpawnPrefabOptions _options;
    //private List<GameObject> _prefabList = new List<GameObject>();

    public override void Initialize()
    {
        if (_objects == null)
        {
            _objects = new Dictionary<string, GameObject>();
        }
    }

    public override void SetProperties(ModifierProperties properties)
    {
        _options = (SpawnPrefabOptions)properties;
        _options.PropertyHasChanged += UpdateModifier;
    }

    public override void Run(VectorEntity ve, UnityTile tile)
    {
        if (_options.prefab == null)
        {
            return;
        }

        for (int i = 0; i < ve.Mesh.vertices.Length; i++)
        {
            GameObject instance = null;
            string id = ve.GameObject.name + "_" + i;
            if (_objects.ContainsKey(id))
            {
                // instance = _objects[id];
            }
            else
            {
                instance = Instantiate(_options.prefab);
                //_prefabList.Add(instance);
                _objects.Add(id, instance);
                instance.transform.position = new Vector3(
                    ve.Mesh.vertices[i].x + ve.GameObject.transform.position.x,
                    ve.Mesh.vertices[i].y + ve.GameObject.transform.position.y,
                    ve.Mesh.vertices[i].z + ve.GameObject.transform.position.z
                );
            }
        }
        Debug.Log(ve.GameObject + " position: " + ve.GameObject.transform.position);
    }

    public override void Clear()
    {
        base.Clear();
        foreach (var gameObject in _objects.Values)
        {
            if (Application.isEditor && !Application.isPlaying)
            {
                DestroyImmediate(gameObject);
            } else
            {
                Destroy(gameObject);
            }
        }
        _objects.Clear();
    }
}

I still can't figure out how exactly the buffering works. Is there maybe an overview of the complete pipeline?

henning101 commented 4 years ago

Another bit of progress. So the problem occurs if I try to create multiple objects at mesh vertex positions inside a GameObjectModifier instead of spawning a single object and parenting it to ve.GameObject. I guess it has to do with how buffered tiles are activated and deactivated? My solution for now is a slight modification of the original Mapbox prefab generator. Instead of calculating the centroid by averaging over all vertex positions I simple select a single vertex position randomly. This way I can make sure I have a single prefab instance somewhere along the street for each vector entity. Also "Combine Meshes" must be unchecked.

If anyone could help me make this work for object spawning per mesh vertex I'm still highly interested in a solution!

using UnityEngine;
using Mapbox.Unity.MeshGeneration.Data;
using Mapbox.Unity.MeshGeneration.Components;
using Mapbox.Unity.MeshGeneration.Interfaces;
using System.Collections.Generic;
using Mapbox.Unity.Map;
using System;
using Mapbox.Unity.MeshGeneration.Modifiers;

public class RoadJointPrefabModifier : GameObjectModifier
{
    private Dictionary<GameObject, GameObject> _objects;
    [SerializeField]
    private SpawnPrefabOptions _options;
    private List<GameObject> _prefabList = new List<GameObject>();

    public override void Initialize()
    {
        if (_objects == null)
        {
            _objects = new Dictionary<GameObject, GameObject>();
        }
    }

    public override void SetProperties(ModifierProperties properties)
    {
        _options = (SpawnPrefabOptions)properties;
        _options.PropertyHasChanged += UpdateModifier;
    }

    public override void Run(VectorEntity ve, UnityTile tile)
    {
        if (_options.prefab == null)
        {
            return;
        }

        GameObject go = null;

        if (_objects.ContainsKey(ve.GameObject))
        {
            go = _objects[ve.GameObject];
        } else
        {
            go = Instantiate(_options.prefab);
            _prefabList.Add(go);
            _objects.Add(ve.GameObject, go);
            go.transform.SetParent(ve.GameObject.transform, false);
        }

        PositionScaleRectTransform(ve, tile, go);

        if (_options.AllPrefabsInstatiated != null)
        {
            _options.AllPrefabsInstatiated(_prefabList);
        }
    }

    public void PositionScaleRectTransform(VectorEntity ve, UnityTile tile, GameObject go)
    {
        RectTransform goRectTransform;
        IFeaturePropertySettable settable = null;
        var centroidVector = new Vector3();
        /*
        foreach (var point in ve.Feature.Points[0])
        {
            centroidVector += point;
        }
        centroidVector = centroidVector / ve.Feature.Points[0].Count;
        */

        centroidVector = ve.Feature.Points[0][UnityEngine.Random.Range(0, ve.Feature.Points[0].Count)];

        go.name = ve.Feature.Data.Id.ToString();

        goRectTransform = go.GetComponent<RectTransform>();
        if (goRectTransform == null)
        {
            go.transform.localPosition = centroidVector;
            if (_options.scaleDownWithWorld)
            {
                go.transform.localScale = _options.prefab.transform.localScale * (tile.TileScale);
            }
        } else
        {
            goRectTransform.anchoredPosition3D = centroidVector;
            if (_options.scaleDownWithWorld)
            {
                goRectTransform.localScale = _options.prefab.transform.localScale * (tile.TileScale);
            }
        }

        settable = go.GetComponent<IFeaturePropertySettable>();
        if (settable != null)
        {
            settable.Set(ve.Feature.Properties);
        }
    }

    public override void Clear()
    {
        base.Clear();
        foreach (var gameObject in _objects.Values)
        {
            gameObject.Destroy();
        }

        foreach (var gameObject in _prefabList)
        {
            gameObject.Destroy();
        }
    }
}

Prefab Modifier 👆Here is a video of the effect when I use the following code:

// ...
if (_objects.ContainsKey(ve.GameObject))
{
    go = _objects[ve.GameObject];
} else
{
    go = Instantiate(_options.prefab);
    _prefabList.Add(go);
    _objects.Add(ve.GameObject, go);
    go.transform.SetParent(ve.GameObject.transform, false);

    foreach (var segment in ve.Feature.Points)
    {
        foreach (var point in segment)
        {
            GameObject instance = Instantiate(_options.prefab);
            instance.transform.SetParent(go.transform, true);
            instance.transform.position = new Vector3(
                go.transform.position.x + point.x,
                go.transform.position.y + point.y,
                go.transform.position.z + point.z
            );
        }
    }
}
// ...
brnkhy commented 4 years ago

Hey @henning101, Object positioning and hierarchy can be really difficult, I have been through that a lot of times. I don't have a quick solution on top of my head for this case but