Paint 3D Object on a 3D Mesh Mountain

erfan71
New Member
Registered: 2014-07-01
Posts: 4

Topic

Hello.
I am new to this plugin.
I have a question.
My game have a Mesh  mountain that have a lot of hills and pits . then I want to tile it with your plugin .
With for example simple cube or custom FBX Mesh.
Can I do that ?
What I want is vary similar to painting a terrain with unity Editor. but that painting is a texture !!
I don't want use texture . I want to Cover that with Object !
because I want them for clicking or Path finding .
Thank you .
If You can't catch my Question. please tell me.

Lea Hayes
Rotorz Limited
From: United Kingdom
Registered: 2014-03-04
Posts: 638

Response 1

Hello there!

Rotorz Tile System allows you to paint tiles onto a fixed tile system plane; it does not provide a feature to drop tiles onto other shaped surfaces (like terrains).

If you want to snap tiles around another surface then you could probably do something like the following:

  1. Ensure that your terrain or custom mesh has a dedicated terrain or mesh collider (for editor usage).

  2. Write a custom editor script which subscribes to PaintingUtility.TilePainted.

  3. Each time a tile is painted, perform a raycast downward to find point of intersection with your surface.

  4. Adjust position of tile via tile.gameObject.transform.

I hope that this helps, but I may very well have misunderstood your question.

erfan71
New Member
Registered: 2014-07-01
Posts: 4

Response 2

Thanks, You understand me , And with this way , does Rotation of created object , same with my Surface?
I mean that if I click On a Hillside of my hill, my object stick on it ( like a texture) and mathematically  normal vector of them are the same?  how can i do this?

Lea Hayes
Rotorz Limited
From: United Kingdom
Registered: 2014-03-04
Posts: 638

Response 3

Awesome stuff, so I have created a couple of example scripts for which you can build upon:

The main plug-in behavior subscribes to the Brush.TilePainted event exposes static options which can be configured via a custom editor window.

Tiles are dropped from far above (+500 units) onto game objects with colliders on the "Terrain" layer. If the collider is only required to assist painting in-editor then I would recommend creating a separate game object for the collider and use the "EditorOnly" tag to exclude it from builds.

Assets/Editor/DropTileToSurface.cs

using UnityEngine;
using UnityEditor;

using Rotorz.Tile;

[InitializeOnLoad]
public static class DropTileToSurface {

	// Register custom 'TilePainted' subscription!
	static DropTileToSurface() {
		Brush.TilePainted += Brush_TilePainted;
	}

	/// <summary>
	/// Name of terrain layer.
	/// </summary>
	private const string TerrainLayerName = "Terrain";

	/// <summary>
	/// Gets or sets whether tile should be dropped to surface in layer "Terrain".
	/// </summary>
	public static bool EnableDropToSurface { get; set; }
	/// <summary>
	/// Gets or sets custom offset to apply to position of dropped tile.
	/// </summary>
	public static float CustomOffset { get; set; }

	private static void Brush_TilePainted(TileSystem system, TileData tile) {
		// Bail if no game object is associated with this tile.
		if (!EnableDropToSurface || tile.gameObject == null)
			return;

		// Estimate radius of tile based upon smallest dimension of cell size.
		float radius = Mathf.Min(system.CellSize.x, system.CellSize.y, system.CellSize.z) / 2f;

		// Find position slightly above tile to avoid glitches.
		var tileTransform = tile.gameObject.transform;
		var localTilePosition = tileTransform.localPosition;
		localTilePosition.z -= 500f;

		// Fire ray downward!
		var ray = new Ray(system.transform.localToWorldMatrix.MultiplyPoint3x4(localTilePosition), tileTransform.up * -1f);
		
		// Find point of contact with terrain object.
		RaycastHit hitInfo;
		if (Physics.SphereCast(ray, radius, out hitInfo, 1000f, 1 << LayerMask.NameToLayer(TerrainLayerName))) {
			// Transform hit point into local space of tile system.
			Vector3 localHitPoint = system.transform.worldToLocalMatrix.MultiplyPoint3x4(hitInfo.point);
			
			// Found! relocate tile on Z axis!!
			localTilePosition.z = localHitPoint.z - radius - CustomOffset;
			tileTransform.localPosition = localTilePosition;
		}
	}

}

Assets/Editor/DropTileOptionsWindow.cs

using UnityEditor;

public sealed class DropTileOptionsWindow : EditorWindow {

	[MenuItem("Window/Drop Tile Options")]
	private static void ShowWindow() {
		GetWindow<DropTileOptionsWindow>("Drop Tile Options");
	}

	private void OnGUI() {
		DropTileToSurface.EnableDropToSurface = EditorGUILayout.ToggleLeft("Enable Drop Tile", DropTileToSurface.EnableDropToSurface);
		DropTileToSurface.CustomOffset = EditorGUILayout.FloatField("Custom Offset", DropTileToSurface.CustomOffset);
	}

}

I hope that this helps with your project!

p.s.
Please don't forget to use the showcase forum to show off your creations!! :D

erfan71
New Member
Registered: 2014-07-01
Posts: 4

Response 4

Assets/Editor/DropTileToSurface.cs(37,41): error CS1061: Type `Rotorz.Tile.TileSystem' does not contain a definition for `CellSize' and no extension method `CellSize' of type `Rotorz.Tile.TileSystem' could be found (are you missing a using directive or an assembly reference?)

I get this compile error !
thanks vary much

Lea Hayes
Rotorz Limited
From: United Kingdom
Registered: 2014-03-04
Posts: 638

Response 5

You need to update to the newer version of Rotorz Tile System using the asset store :)

erfan71
New Member
Registered: 2014-07-01
Posts: 4

Response 6

ok! Thanks very much. You help me a lot.

Wolfs
New Member
Registered: 2016-10-24
Posts: 8

Response 7

Please excuse the thread necromancy, but this thread really caught my eye - I was about to make a feature request for exactly this feature, but thankfully did use the search function first :D
I was looking for a way to make plop work with tiles of multiple heights - I have 7 brushes, for hills of a height of 1, 2 and 3 (...) units, since I want to use as few game objects as possible while creating varied (if blocky) terrain.
Plop only places the tiles on the "ground" of the tile system. My goal is to use something like this to simply make the plops fall on top of whatever tile height I have...   ...and work with spray!

Ie, if I spray/place on a tile that's 2 units high, it should be placed on top of that tile, if I spray on a 1 unit high tile, it should be placed on that tile. It'd be an excellent addition for a 3D tile system.

So I'd like to ask a question here:
-Would this script still work with the current rotorz? Question is two years old, after all :) I am asking because I'm not the super duper experienced Unity dev, and rather check first before I mess around with my poor project. I hope you're fine with this!
-Assuming that I know that my basic tiles will always have a specific maximum height (say 7 Units, or 10 Units, or whatever), would it improve performance to reduce the drop height, or would it be better to just use it as is?



Edit: Editor only, I have no plans of using this during actual gameplay. This is just for level creation.

Last edited by Wolfs (2016-11-08 13:08:58)

Lea Hayes
Rotorz Limited
From: United Kingdom
Registered: 2014-03-04
Posts: 638

Response 8

Wolfs wrote:

-Would this script still work with the current rotorz? Question is two years old, after all :) I am asking because I'm not the super duper experienced Unity dev, and rather check first before I mess around with my poor project. I hope you're fine with this!

As far as I can tell the only part that needs to be updated in the script is migrating from Brush.TilePainted to PaintingUtility.TilePainted; aside from that it should continue to work:

Assets/Editor/DropTileToSurface.cs

using UnityEngine;
using UnityEditor;

using Rotorz.Tile;

[InitializeOnLoad]
public static class DropTileToSurface {

	// Register custom 'TilePainted' subscription!
	static DropTileToSurface() {
		PaintingUtility.TilePainted += PaintingUtility_TilePainted;
	}

	/// <summary>
	/// Name of terrain layer.
	/// </summary>
	private const string TerrainLayerName = "Terrain";

	/// <summary>
	/// Gets or sets whether tile should be dropped to surface in layer "Terrain".
	/// </summary>
	public static bool EnableDropToSurface { get; set; }
	/// <summary>
	/// Gets or sets custom offset to apply to position of dropped tile.
	/// </summary>
	public static float CustomOffset { get; set; }

	private static void PaintingUtility_TilePainted(TilePaintedEventArgs args) {
		// Bail if no game object is associated with this tile.
		if (!EnableDropToSurface || args.GameObject == null)
			return;

		// Estimate radius of tile based upon smallest dimension of cell size.
		float radius = Mathf.Min(args.TileSystem.CellSize.x, args.TileSystem.CellSize.y, args.TileSystem.CellSize.z) / 2f;

		// Find position slightly above tile to avoid glitches.
		var tileTransform = args.GameObject.transform;
		var localTilePosition = tileTransform.localPosition;
		localTilePosition.z -= 500f;

		// Fire ray downward!
		var ray = new Ray(args.TileSystem.transform.localToWorldMatrix.MultiplyPoint3x4(localTilePosition), tileTransform.up * -1f);
		
		// Find point of contact with terrain object.
		RaycastHit hitInfo;
		if (Physics.SphereCast(ray, radius, out hitInfo, 1000f, 1 << LayerMask.NameToLayer(TerrainLayerName))) {
			// Transform hit point into local space of tile system.
			Vector3 localHitPoint = args.TileSystem.transform.worldToLocalMatrix.MultiplyPoint3x4(hitInfo.point);
			
			// Found! relocate tile on Z axis!!
			localTilePosition.z = localHitPoint.z - radius - CustomOffset;
			tileTransform.localPosition = localTilePosition;
		}
	}

}

Assets/Editor/DropTileOptionsWindow.cs

using UnityEditor;

public sealed class DropTileOptionsWindow : EditorWindow {

	[MenuItem("Window/Drop Tile Options")]
	private static void ShowWindow() {
		GetWindow<DropTileOptionsWindow>("Drop Tile Options");
	}

	private void OnGUI() {
		DropTileToSurface.EnableDropToSurface = EditorGUILayout.ToggleLeft("Enable Drop Tile", DropTileToSurface.EnableDropToSurface);
		DropTileToSurface.CustomOffset = EditorGUILayout.FloatField("Custom Offset", DropTileToSurface.CustomOffset);
	}

}

I have applied the modifications to the above script but have not had the opportunity to test for you. Please let me know if there are any errors and I will take a further look at the above for you :)

Wolfs wrote:

-Assuming that I know that my basic tiles will always have a specific maximum height (say 7 Units, or 10 Units, or whatever), would it improve performance to reduce the drop height, or would it be better to just use it as is?

I doubt that reducing the raycast distance would make any difference to performance. You will likely observe performance improvements with newer versions of Unity, in particular upcoming versions. Unity have been greatly improving the performance of object creation and manipulation.

Wolfs wrote:

Plop only places the tiles on the "ground" of the tile system. My goal is to use something like this to simply make the plops fall on top of whatever tile height I have...   ...and work with spray!

The above script should work with any tool that 'paints' tiles; so the spray tool included. I am not sure whether the above script would work with the 'plop' feature as it stands. The 'plop' tool works slightly differently... let me know how it goes and if it doesn't work I'll see how easy it would be to add!

All the best!

Last edited by Lea Hayes (2016-11-08 21:02:46)

Wolfs
New Member
Registered: 2016-10-24
Posts: 8

Response 9

Wow, thank you! That's quite amazing.

I'll try to get this tested today or tomorrow, and mention how it works - I'm mostly interested in using it for plop after all, since the normal tiles are fine, and I mostly want to plop on them.

Wolfs
New Member
Registered: 2016-10-24
Posts: 8

Response 10

I've tested your script!

I believe line 30 should be
private static void PaintingUtility_TilePainted ( TilePaintedEventArgs args) {
- it gives me runtime errors otherwise. Is this a proper fix?

That little hiccup aside, I've tried it out some on this little test map:
oFy8GHY.png  (all made with rotorz and programmer art)

-Not using plop does cause all surrounding tiles to get pushed up, too - using it on a new tile system prevents this (and is therefore mandatory)
-Using plop works too, with similar results to using a separate tile system
-There's a but here: It really only works well on the middle of a 3x3 space. Placement on other spots often results in the object floating a bit above all nearby tiles.
-Placement should be done directly from above, otherwise it lands on unexpected tiles.
-Using the script seems to have caused some damage to the tile system: Brushes used now do not connect any longer with their placements before using it, but connect to their new placement. http://i.imgur.com/JhsqZhc.png  <- seen here. The yellow line is actually the border of this brush,and all tiles should normall connect. I can restore the regular look by simply redoing the affected area. This affects all brushes, without exception.

Still, surprised it works at all! I'm wondering if it's really worth to look into further fixes, though. Is there a way to duplicate a brush and then, somehow, change the Z offset for all contained prefabs (but only when using that brush)? Prevents spraying across multiple height levels, but would avoid the finicky placement issues, I'd think.

Sorry for the wall of text, and again, thanks for the spectacular support and replying at all!

Lea Hayes
Rotorz Limited
From: United Kingdom
Registered: 2014-03-04
Posts: 638

Response 11

Ah yes, I forgot to delete that part of the line; this is the correct fix for that error :)

Awesome!! I'm glad that this helped with your use case :D

That's some nice programmer art!