Advanced Tutorial: Garden Chunks

Build a simple custom AR meshing experience that turns the real world into a garden.

../../../_images/meshing_garden.jpg

Concept

Different kinds of plants, depending on the geometry detected, will appear as you scan the environment:

  • Ground plants will grow on flat parts of the mesh.

  • Wall plants will grow on vertical parts of the mesh.

This is done by randomly sampling the mesh geometry and growing plants at suitable vertices (i.e. ones with normals either facing directly up or sideways).

This example only spawns one plant per chunk. This means that once a plant starts growing, you can stop sampling that chunk’s mesh data.

First things first, you’ll need a handful of plant assets to pick from for both ground plants and wall plants.

Set up the Scene

Follow the steps of the Meshing: Getting Started tutorial.

Check Use Invisible Material on the ARMeshManager. In this case, the mesh should not be visible so it doesn’t distract from the garden experience.

We recommend you iterate in the Unity Editor, using Mock Meshes.

GardenChunk.cs

In order to read the mesh geometry and spawn plants, duplicate the default MeshChunk prefab and add the following script to it:

using System;
using System.Collections;
using System.Collections.Generic;

using UnityEngine;

using Random = UnityEngine.Random;

public class GardenChunk: MonoBehaviour
{
  public List<GameObject> _groundPrefabs;
  public List<GameObject> _wallPrefabs;
  public float _groundNormalTolerance = 0.01f;
  public float _wallNormalTolerance = 0.001f;
  public int _spawnMinVertexCount = 100;
  public int _despawnMaxVertexCount = 50;
  public float _growthDuration = 4.0f;
  private MeshFilter _filter;
  private GameObject _plant;

  private void Start()
  {
    _filter = GetComponent<MeshFilter>();

    // don't update the object if there's no mesh filter
    enabled = (bool)_filter;
  }

  private void Update()
  {
    int vertexCount = _filter.sharedMesh.vertexCount;
    if (vertexCount >= _spawnMinVertexCount && !(bool)_plant)
    {
      // plant a plant! (might not succeed)
      _plant = InstantiatePlant(_filter.sharedMesh);
    }
    else if (vertexCount <= _despawnMaxVertexCount && (bool)_plant)
    {
      // pull a plant!
      StopCoroutine(GrowPlant());
      Destroy(_plant);
      _plant = null;
    }
  }

  private GameObject InstantiatePlant(Mesh mesh)
  {
    bool wall;
    Vector3 position;
    Vector3 normal;
    // if we find a suitable vertex, plop a plant at that location
    if (FindVertex(_filter.sharedMesh, out wall, out position, out normal))
    {
      GameObject prefab = wall
        ? _wallPrefabs[Random.Range(0, _wallPrefabs.Count)]
        : _groundPrefabs[Random.Range(0, _groundPrefabs.Count)];

      Quaternion rotation = wall
        ? Quaternion.LookRotation(normal, Vector3.up)
        : Quaternion.Euler(0, Random.Range(0.0f, 360.0f), 0);

      // use local position/rotation because of the different coordinate system
      GameObject plant = Instantiate(prefab, transform, false);
      plant.transform.localPosition = position;
      plant.transform.localRotation = rotation;
      plant.transform.localScale = Vector3.zero;
      StartCoroutine(GrowPlant());
      return plant;
    }

    return null;
  }

  private bool FindVertex(Mesh mesh, out bool wall, out Vector3 position, out Vector3 normal)
  {
    int v = Random.Range(0, mesh.vertexCount);
    position = mesh.vertices[v];
    normal = mesh.normals[v];
    bool ground = normal.y > 1.0f - _groundNormalTolerance && _groundPrefabs.Count > 0;
    wall = Mathf.Abs(normal.y) < _wallNormalTolerance && _wallPrefabs.Count > 0;
    return wall || ground;
  }

  private IEnumerator GrowPlant()
  {
    yield return null;

    float progress = 0.0f;
    // end scale has Y inverted because of the transform on the mesh root
    Vector3 endScale = new Vector3(1.0f, -1.0f, 1.0f);
    while (progress < 1.0f && (bool)_plant)
    {
      progress = Mathf.Min(1.0f, progress + Time.deltaTime / _growthDuration);
      _plant.transform.localScale = Vector3.Lerp(Vector3.zero, endScale, progress);
      yield return null;
    }
  }
}

Once the script is attached to the prefab, tweak the parameters to your preference, and attach your plant assets. You may need to fiddle with asset import settings to ensure the right scales and transforms; the script assumes the asset is upright and its origin is where it connects to the ground.

../../../_images/meshing_gardenchunk.jpg