Making a hollow voxel cone - c#

I'm trying to create a voxel-style cone shape in C#. I've got the cone building using cubes, but can't work out how to only build the outer layer (make it hollow) rather than build a solid cone as shown in the pic.
The code so far (Edited from someone else's script)
// Create a cone made of cubes. Can be called at runtime
public void MakeVoxelCone()
{
for (int currentLength = 0; currentLength < maxLength; currentLength++)
MakeNewLayer(currentLength);
}
// Make a new layer of cubes on the cone
private void MakeNewLayer(int currentLength)
{
center = new Vector3(0, currentLength, 0);
for (int x = -currentLength; x < currentLength; x++)
{
for (int z = -currentLength; z < currentLength; z++)
{
// Set position to spawn cube
Vector3 pos = new Vector3(x, currentLength, z);
// The distance to the center of the cone at currentLength point
float distanceToMiddle = Vector3.Distance(pos, center);
// Create another layer of the hollow cone
if (distanceToMiddle < currentLength)
{
// Add all cubes to a List array
Cubes.Add(MakeCube(pos));
}
}
}
}
// Create the cube and set its properties
private GameObject MakeCube(Vector3 position)
{
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.GetComponent<Renderer>().material.color = Color.blue;
if (!AddCollider)
Destroy(cube.GetComponent<BoxCollider>());
cube.transform.position = position;
cube.transform.parent = transform;
cube.name = "cube [" + position.y + "-" + position.x + "," + position.z + "]";
return cube;
}
I think its probably simple, but cant figure it out.
Maybe something about the if (distanceToMiddle < currentLength) part, but swapping < for == breaks the whole shape.
#Jax297 >= (currentLength-1) Closer, but not correct just yet. Now its a pyramid with a cone cut out.

assuming your currentlength is the outer diameter you have to introduce a thickness vareable and compare to currentleght - thickness, so there is a inner diameter to be kept free
(currentLength - thickness) < distanceToMiddle && distanceToMiddle < currentLength

If you want to create a "empty" cone. Try to describe that shape in English first.
Create a circle at height going from 0...h with increasing radius for each step from 0..r
So now you need to find equation of a circle (should take you back to HS trig): sin(theta)^2 + cos(theta)^2 = radius:
Now you want to create this circle while increasing the height.
This will create only the empty cone you wish.
Here's an quick implementation (adjust as needed):
public List<GameObject> instantiatedObjects = new List<GameObject>();
public GameObject rootParent;
public int numOfRows = 10;
public int incrementPerRow = 4;
public float radiusStep = 0.5f;
public float height = 5;
[Button]
public void CreateVoxelCone()
{
// first one. root object.
rootParent = GameObject.CreatePrimitive(PrimitiveType.Cube);
rootParent.name = "Pivot";
rootParent.transform.position = Vector3.zero;
var itemsForThisRow = incrementPerRow;
var heightStep = height / numOfRows;
// for each row...
for (int i = 0; i < numOfRows; i++)
{
// create items in a circle
var radianStep = Mathf.PI * 2 / itemsForThisRow;
for (float j = 0; j < Mathf.PI*2; j=j+radianStep)
{
var newPosition = new Vector3(Mathf.Cos(j) * radiusStep * i, heightStep * i, Mathf.Sin(j) * radiusStep*i);
var point = GameObject.CreatePrimitive(PrimitiveType.Cube);
point.name = ($"Row: {i}, {j}");
point.transform.SetParent(rootParent.transform);
point.transform.localPosition = newPosition;
instantiatedObjects.Add(point);
}
itemsForThisRow += incrementPerRow;
}
}
[Button]
public void CleanUp()
{
DestroyImmediate(rootParent);
}

Related

Normal issues in Unity

We are trying, with a friend, to modelise a tree on unity. To do so, we want to modelise a tree trunk. We are doing it with the following code :
`
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class Frustum : MonoBehaviour
{
/*====== PUBLIC ======*/
public float height = 1;
public float top_radius = 0.9F;
public float base_radius = 1F;
public int circle_subdivisions = 30;
/*====== PRIVATE ======*/
private Mesh mesh;
private Vector3[] vao;
private int[] ibo;
// Start is called before the first frame update
void Start()
{
mesh = new Mesh();
mesh.name = "Cone";
this.GetComponent<MeshFilter>().mesh = mesh;
draw();
}
// OnValidate is called at each public input change
private void OnValidate() {
draw();
}
private void draw() {
ComputeFrustumVao();
ComputeFrustumIbo();
mesh.Clear();
mesh.vertices = vao;
mesh.triangles = ibo;
mesh.RecalculateBounds();
mesh.RecalculateTangents();
mesh.RecalculateNormals();
// mesh.Optimize();
}
private void ComputeFrustumVao()
{
// COMPUTE BASE VERTICES
List<Vector3> vertices = ComputeCircleVertices(transform.position, base_radius, circle_subdivisions);
// ADD TOP VERTICES
Vector3 top_origin = transform.position + new Vector3(0, height, 0);
vertices.AddRange(ComputeCircleVertices(top_origin, top_radius, circle_subdivisions));
vao = vertices.ToArray();
}
private List<Vector3> ComputeCircleVertices(Vector3 origin, float radius, int nb_subdivisions) {
List<Vector3> vao = new List<Vector3>();
float step_angle = (2*Mathf.PI)/nb_subdivisions;
for(int i = 0; i < nb_subdivisions; ++i){
float current_angle = step_angle * i;
Vector3 translate_vector = new Vector3(
Mathf.Cos(current_angle) * radius, // x coordinate
0, // y coordinate
Mathf.Sin(current_angle) * radius // z coordinate
);
Vector3 new_vertex = origin + translate_vector;
vao.Add(new_vertex);
}
return vao;
}
private void ComputeFrustumIbo() {
List<int> indices = new List<int>();
int nb_vertices = vao.Length;
int nb_vertices_per_circle = nb_vertices/2;
int nb_triangles = nb_vertices_per_circle - 2;
// DRAW BASE
for(int i = 0; i < nb_triangles; ++i){
indices.Add(0);
indices.Add(i + 1);
indices.Add(i + 2);
}
// DRAW TOP
for(int i = nb_vertices_per_circle; i < nb_vertices_per_circle + nb_triangles; ++i){
indices.Add(nb_vertices_per_circle);
indices.Add(i + 2);
indices.Add(i + 1);
}
// DRAW SIDES
for(int i = 0; i < nb_vertices_per_circle; ++i){
int next_index = (i + 1) % nb_vertices_per_circle;
indices.Add(i); // BOTTOM LEFT
indices.Add( nb_vertices_per_circle + next_index); // TOP RIGHT
indices.Add(next_index); // BOTTOM RIGHT
indices.Add(i); // BOTTOM LEFT
indices.Add(i + nb_vertices_per_circle); // TOP LEFT
indices.Add(nb_vertices_per_circle + next_index); // TOP RIGHT
}
ibo = indices.ToArray();
}
void OnDrawGizmosSelected() //Draw the markers, if the object is selected
{
Gizmos.color = Color.red;
for (int i=0; i< mesh.vertices.Length; i++){
Gizmos.color= new Color(((float)i)/mesh.vertices.Length,0, 0);
Vector3 worldPos = transform.TransformPoint(mesh.vertices[i]);
Gizmos.DrawCube(worldPos, Vector3.one * 0.1f);
}
}
}
`
With this code we can create a 3D shape like a cube or a cylinder but when we create our top and our base ( in DRAW TOP and DRAW BASE) it makes the normal looks weird and I dont understand why. Can you help me to understand it please ?
our Normal problem
We know that ussually people create their models in 3D software like blender but we really need to do it in unity for our project.
I've seen that if my normals are pointing to the top the face are too illuminated but when they are facing down it's too shady. To create our base and top shape, we have followed this tutorial

Newbie cant generate a chessboard in Godot 3

I'm new to godot 3.4! My first project is a orthographic chess make. I cant generate the chess board.
I've wrote the following script trying to generate the chessboard:
using Godot;
using System;
public class TileSpawner : Spatial {
[Export] private readonly Material lightSquare;
[Export] private readonly Material darkSquare;
private bool isLight = false;
public override void _Ready() {
Vector2 coordinates = new Vector2(0, 0);
for (int y = 0; y < 8; y++)
for (int x = 0; x < 8; x++) {
coordinates.x = x; coordinates.y = y;
AddChild(MakeTile(coordinates));
}
}
private MeshInstance MakeTile(Vector2 coordinates) {
MeshInstance mesh = new MeshInstance() {
Mesh = new CubeMesh() {
Material = isLight ? lightSquare : darkSquare
}
};
mesh.Translate(new Vector3(-coordinates.x, 0, coordinates.y) + new Vector3(coordinates.x/2, 0, -coordinates.y/2));
mesh.Scale = new Vector3(1.0f, 0.2f, 1.0f);
isLight = !isLight;
return mesh;
}
}
and the result its flickery...
one object overlapping other
so I added this line on the MakeTile function to see the problem clearly:
mesh.Translate(isLight ? Vector3.Up : Vector3.Down);
and got something like this.there's two big block in place small 64 tiles
not sure what's going on. I've been trying to solve this for 4 hours.
#Theraot answer was promising but didn't changed any thing.
My Solution:
Step 1: Create a scene containing the dark and light square mesh.
Step 2: Go Scene(On the top left corner of the screen) > Convert To... > MeshLibrary.
Step 3: Tick the "Merge With Existing" Checkbox and save the file as name.meshlib
Step 4: Create a GridMap on the scene you want your chessboard to be in.
Step 5: Load the name.meshlib on the GridMap Node and attach the following script:
using Godot;
using System;
public class FillWithTile : GridMap {
public override void _Ready() {
Vector2 coordinates = new Vector2(0, 0);
for (int y = -4; y < 4; y++)
for (int x = -4; x < 4; x++) {
coordinates.x = x; coordinates.y = y;
MakeTile(coordinates);
}
}
private void MakeTile(Vector2 coordinates) {
SetCellItem((int)coordinates.x, 0, (int)coordinates.y, (coordinates.x % 2 == 0) != (coordinates.y % 2 == 0)?1:0);
}
}
We are going to make a Transform:
var t = new Transform(
x_axis,
y_axis,
z_axis,
origin
);
So, let us define the components we used there. First for origin I'm going to guess that leaving it zero is fine. You will be able to change it to move the board.
var origin = Vector3.Zero;
And I'm also assuming the axis can be in the usual directions, just scaled:
var x_axis = Vector3.Right;
var y_axis = Vector3.Up * 2.0f;
var z_axis = Vector3.Back;
Alternatively, do you want them rotated? You could do something like this:
var x_axis = (Vector3.Right + Vector3.Back).Normalized();
var y_axis = Vector3.Up * 2.0f;
var z_axis = (Vector3.Back + Vector3.Left).Normalized();
Addendum: The scale is too big. The tiles are taking up the area of four cells. So shrink on the x and z axis by half:
var x_axis = Vector3.Right * 0.5f;
var y_axis = Vector3.Up * 2.0f;
var z_axis = Vector3.Back * 0.5f;
Or for the other case:
var x_axis = (Vector3.Right + Vector3.Back).Normalized() * 0.5f;
var y_axis = Vector3.Up * 2.0f;
var z_axis = (Vector3.Back + Vector3.Left).Normalized() * 0.5f;
Now, do you remember I said this is the Transform?
var t = new Transform(
x_axis,
y_axis,
z_axis,
origin
);
Well, not quite. We are going to displace it according to the coordinates we got, like this:
var t = new Transform(
x_axis,
y_axis,
z_axis,
origin + x_axis * coordinates.x + z_axis * coordinates.y
);
Addendum: I think you need to compensate for halving the scale by doubling the offset here: origin + x_axis * coordinates.x * 2.0f + z_axis * coordinates.y * 2.0f
And set it to the mesh:
mesh.Transform = t;
Addendum: the decision of light vs dark si wrong. It goes by rows and switching, like this:
10101010
10101010
…
Here is an alternative algorithm:
isLight = (((int)coordinates.x) % 2 == 0) != (((int)coordinates.y) % 2 == 0)

Create a hexagonal grid where I can get the position of the corners

I find a lot of examples of creating a hex grid like the following. But I'm having a hard time understanding how I might have a list of the corners in a hex grid. Basically I'd want a character to move along the line of the grid instead of the center. so I want to grab the position of the next corner in the hex grid and move them there.
I was thinking of using a basic hex grid code to create the prefabs in the right place and then just add empty game objects on each corner of the prefab, but then I have a bunch of overlapping positions that are shared corners for each hexagon. I thought I could delete them if they are overlapping but it just all seems too brute force and hard to keep track of. I'd love to hear some ideas on approaching something like this. By the way I'd also want to know the center of the hex besides knowing the corners.
this code successfully creates a hex grid pattern where I can add a hex shaped game object to instance.
using UnityEngine;
public class Grid : MonoBehaviour
{
public Transform hexPrefab;
public int gridWidth = 11;
public int gridHeight = 11;
float hexWidth = 1.732f;
float hexHeight = 2.0f;
public float gap = 0.0f;
Vector3 startPos;
void Start()
{
AddGap();
CalcStartPos();
CreateGrid();
}
void AddGap()
{
hexWidth += hexWidth * gap;
hexHeight += hexHeight * gap;
}
void CalcStartPos()
{
float offset = 0;
if (gridHeight / 2 % 2 != 0)
offset = hexWidth / 2;
float x = -hexWidth * (gridWidth / 2) - offset;
float z = hexHeight * 0.75f * (gridHeight / 2);
startPos = new Vector3(x, 0, z);
}
Vector3 CalcWorldPos(Vector2 gridPos)
{
float offset = 0;
if (gridPos.y % 2 != 0)
offset = hexWidth / 2;
float x = startPos.x + gridPos.x * hexWidth + offset;
float z = startPos.z - gridPos.y * hexHeight * 0.75f;
return new Vector3(x, 0, z);
}
void CreateGrid()
{
for (int y = 0; y < gridHeight; y++)
{
for (int x = 0; x < gridWidth; x++)
{
Transform hex = Instantiate(hexPrefab) as Transform;
Vector2 gridPos = new Vector2(x, y);
hex.position = CalcWorldPos(gridPos);
hex.parent = this.transform;
hex.name = "Hexagon" + x + "|" + y;
}
}
}
}

Can I have some help optimizing this script

I wrote a infinite terrain script which works! Saddly everytime the player moves a chunk it lags for a moment. I know my code isn't great but I'm here to learn why :D
I'm unsure of what else to do. I've looked online and found no simple or understandable solution to me because I just don't know enough so I tried to write it on my own and it works but barley.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GEN_InfiniteTerrain : MonoBehaviour
{
public GameObject targetObject;
public GameObject chunkObject;
public int chunkSize;
public float unitSize;
public int renderDistance;
Dictionary<Vector2, GameObject> gridOfChunks = new Dictionary<Vector2, GameObject>();
List<Vector2> expectedChunkGridPositions = new List<Vector2>();
public float noiseScale;
// Infinite terrain values
float absoluteChunkSize;
private void Start()
{
// Calculate absolute chunk size
GetAbsoluteChunkSize();
// Generate base world
GenerateBase();
}
Vector2 lastTargetGridPosition = Vector2.zero;
private void LateUpdate()
{
// Get the targets position in world space
Vector3 targetAbsolutePosition = targetObject.transform.position;
// Convert the targets world position to grid position (/ 10 * 10 is just rounding to 10)
Vector2 targetGridPosition = new Vector2();
targetGridPosition.x = Mathf.RoundToInt(targetAbsolutePosition.x / 10) * 10 / absoluteChunkSize;
targetGridPosition.y = Mathf.RoundToInt(targetAbsolutePosition.z / 10) * 10 / absoluteChunkSize;
if (targetGridPosition - lastTargetGridPosition != Vector2.zero)
{
GenerateExpectedChunkAreas(targetGridPosition);
UpdateChunkPositions(targetGridPosition);
}
lastTargetGridPosition = targetGridPosition;
}
void GenerateBase()
{
for (int x = -renderDistance / 2; x < renderDistance / 2; x++)
{
for (int z = -renderDistance / 2; z < renderDistance / 2; z++)
{
Vector2 gridPosition = new Vector2(x, z);
Vector3 worldPosition = new Vector3(x * (unitSize * chunkSize), 0, z * (unitSize * chunkSize));
GameObject chunk = Instantiate(chunkObject, worldPosition, Quaternion.identity);
chunk.GetComponent<GEN_Chunk>().gridPosition = gridPosition;
gridOfChunks.Add(gridPosition, chunk);
}
}
GenerateExpectedChunkAreas(Vector2.zero);
}
void GenerateExpectedChunkAreas(Vector2 targetGridPosition)
{
expectedChunkGridPositions.Clear();
for (int x = -renderDistance / 2; x < renderDistance / 2; x++)
{
for (int z = -renderDistance / 2; z < renderDistance / 2; z++)
{
Vector2 gridPosition = new Vector2(x, z) + targetGridPosition;
expectedChunkGridPositions.Add(gridPosition);
}
}
}
void UpdateChunkPositions(Vector2 targetGridPosition)
{
List<Vector2> positionsWithoutChunks = new List<Vector2>();
List<Vector2> positionsWithOldChunks = new List<Vector2>();
for (int chunkCount = 0, x = -renderDistance / 2; x < renderDistance / 2; x++)
{
for (int z = -renderDistance / 2; z < renderDistance / 2; z++)
{
Vector2 gridPosition = new Vector2(x, z) + targetGridPosition;
if(!gridOfChunks.ContainsKey(gridPosition))
{
positionsWithoutChunks.Add(gridPosition);
}
chunkCount++;
}
}
foreach (GameObject chunk in gridOfChunks.Values)
{
if(!expectedChunkGridPositions.Contains(chunk.GetComponent<GEN_Chunk>().gridPosition))
{
positionsWithOldChunks.Add(chunk.GetComponent<GEN_Chunk>().gridPosition);
}
}
for (int i = 0; i < positionsWithOldChunks.Count; i++)
{
Vector3 worldPosition = new Vector3(positionsWithoutChunks[i].x * absoluteChunkSize, 0, positionsWithoutChunks[i].y * absoluteChunkSize);
gridOfChunks[positionsWithOldChunks[i]].transform.position = worldPosition;
// Recalculating noise for chunk based on its new position does lag more but even WITHOUT this it still stutters when player moves around. ( plan to learn threading just to calculate noise on seperate threads )
// gridOfChunks[positionsWithOldChunks[i]].GetComponent<GEN_Chunk>().ApplyNoise();
}
}
void GetAbsoluteChunkSize()
{
absoluteChunkSize = unitSize * chunkSize;
}
}
I need some smooth working infinite terrain (in quotes 'infinite')
And I'd like to learn too!

Instantiate as many gameObjects as possible from one position and end at another

How do I instantiate as many gameObjects as possible that start at one position and end at another position. For example, instantiate gameObject at x=0 and end at x=5 axis. Between these two values, there should be as many gameObjects as possible, preferably 10-12 small scaled ones.
public GameObject prefab;
void Awake()
{
GameObject ref = (GameObject)Instantiate(prefab, Vector3.zero, Quaternion.identity);
}
when you say as many GameObjects as possible I guess you mean without overlapping?
This solution works assuming the prefab uses Colliders.
I would instantiate the first object allways and simply get it's bounding box so we know how big it is
var first = Instantiate(prefab, Vector3.zero + Vector3.right * MinX, Quaternion.identity);
var bounds = new Bounds(Vector3.zero, Vector3.zero);
foreach (var col in first.GetComponentsInChildren<Collider>(true))
{
bounds.Encapsulate(col.bounds);
}
// now you can get the size in X direction
var width = bounds.size.x;
I suspect the pivot point of your prefab would probably be in the center so first move it to the right by half of its width
first.transform.position += Vector3.right * width / 2f;
Now you can check how many objects will fit in your given range. Lets say e.g. the width was 1 then in a range from 0 to 5 there would fit in total 4 objects. There will be some redundancies in the calculation (adding 1 then decreasing 1 etc ) but I'll leave it sfor better understanding
var minPosition = MinX;
var maxPosition = MaxX;
var actualMinPosition = minPosition + width / 2;
var actualMaxPosition = maxPosition - width / 2;
// +1 here since before we reduced actualMinPosition and actualMaxPosition by
// exactly 1 * width
var possibleAmount = (int)Mathf.Floor((actualMaxPosition - actualMinPosition) / width) + 1;
So now instantiate the missing objects
// since I guess you also want them evenly spread between the start and end position
var distanceBetween = (actualMaxPosition - actualMinPosition) / (possibleAmount - 1);
// since we already instantiated the first one
// we spawn only possibleAmount - 1 more
for (var i = 0; i < possibleAmount - 1; i++)
{
// +1 here since we started the loop with i=0 but the first
// object here is actually the second to be spawned in total
// so we want it to be moved already
var x = actualMinPosition + distanceBetween * (i + 1);
var obj = Instantiate(prefab, Vector3.zero + Vector3.right * x, Quaternion.identity);
}
So all together
public void Spawn()
{
var first = Instantiate(prefab, Vector3.zero, Quaternion.identity);
var bounds = new Bounds(Vector3.zero, Vector3.zero);
foreach (var col in first.GetComponentsInChildren<Collider>(true))
{
bounds.Encapsulate(col.bounds);
}
// now you can get the size in X direction
var width = bounds.size.x;
first.transform.position += Vector3.right * width / 2f;
var minPosition = MinX;
var maxPosition = MaxX;
var actualMinPosition = minPosition + width / 2;
var actualMaxPosition = maxPosition - width / 2;
// +1 here since before we reduced actualMinPosition and actualMaxPosition by
// exactly 1 * width
var possibleAmount = (int)Mathf.Floor((actualMaxPosition - actualMinPosition) / width) + 1;
// since I guess you also want them evenly spread between the start and end position
var distanceBetween = (actualMaxPosition - actualMinPosition) / (possibleAmount - 1);
// since we already instantiated the first one
// we spawn only possibleAmount - 1 more
for (var i = 0; i < possibleAmount - 1; i++)
{
// +1 here since we started the loop with i=0 but the first
// object here is actually the second to be spawned in total
// so we want it to be moved already
var x = actualMinPosition + distanceBetween * (i + 1);
var obj = Instantiate(prefab, Vector3.zero + Vector3.right * x, Quaternion.identity);
}
}
I simply destroyed and respawned everything in Update for this demo
You could loop the amount of enemys that you want to be spawned(12 for example) and increase the position at every loop iteration.
public GameObject prefab;
public Vector3 pos;
void Awake()
{
for (int i = 0; i < 12; i++)
{
Instantiate(prefab, pos, Quaternion.identity);
pos.x += 0.5f;
}
}
This should create 10 GameObject between 0 and 5.
void Awake() {
for(float x = 0; x < 5; x+=0.5f){
Vector3 loc = new Vector3(x, 0, 0);
GameObject gameObject = (GameObject)Instantiate(prefab, loc, Quaternion.identity);
}
}
Thank you for all your answers, I did something like this:
(a) Create two gameObjects in the scene separated by some distance
(b) In the script, give reference to these two gameObjects
(c) Give number of segments (spheres) that should be generated between these two points
public Transform PointA; public Transform PointB; public float NumberOfSegments = 3; public float AlongThePath = .25f;
// Update is called once per frame
void Start()
{
Create();
}
void Create()
{
StartCoroutine(StartSpheringOut());
}
IEnumerator StartSpheringOut()
{
NumberOfSegments += 1;// since we are skipping 1st placement since its the same as starting point we increase the number by 1
AlongThePath = 1 / (NumberOfSegments);//% along the path
for (int i = 1; i < NumberOfSegments; i++)
{
yield return new WaitForSeconds(0.05f);
Vector3 CreatPosition = PointA.position + (PointB.position - PointA.position) * (AlongThePath * i);
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = CreatPosition;
sphere.transform.localScale = new Vector3(0.25f, 0.25f, 0.25f);
}

Categories