From d1838234a2c27e84b1687f1ea95b333bec8b62fe Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 08:30:02 +0000
Subject: [PATCH 1/7] Initial plan
From bfd839d372451fe95479ed090a049048e5649a49 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 08:34:22 +0000
Subject: [PATCH 2/7] Replace tree primitives with procedural 3D model
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
---
src/c#/main/entity/TreeModel.cs | 210 +++++++++++++++++++++++
src/c#/main/entity/TreeModel.cs.meta | 11 ++
src/c#/main/entity/entities/AppleTree.cs | 22 +--
src/c#/tests/entity/TestAppleTree.cs | 15 +-
4 files changed, 231 insertions(+), 27 deletions(-)
create mode 100644 src/c#/main/entity/TreeModel.cs
create mode 100644 src/c#/main/entity/TreeModel.cs.meta
diff --git a/src/c#/main/entity/TreeModel.cs b/src/c#/main/entity/TreeModel.cs
new file mode 100644
index 00000000..230a7b53
--- /dev/null
+++ b/src/c#/main/entity/TreeModel.cs
@@ -0,0 +1,210 @@
+using UnityEngine;
+
+namespace beyondnations {
+
+ ///
+ /// Creates a procedural 3D tree model.
+ /// This replaces the simple primitive shapes (Cylinder + Cube) with a proper mesh-based tree.
+ /// Can be easily replaced with an imported 3D model file when available.
+ ///
+ public class TreeModel {
+
+ public static GameObject CreateTree(Vector3 position, int height) {
+ GameObject treeRoot = new GameObject("Tree");
+ treeRoot.transform.position = position;
+
+ // Create trunk
+ GameObject trunk = CreateTrunk(height);
+ trunk.transform.parent = treeRoot.transform;
+ trunk.transform.localPosition = Vector3.zero;
+
+ // Create leaves/canopy
+ GameObject leaves = CreateLeaves(height);
+ leaves.transform.parent = treeRoot.transform;
+ leaves.transform.localPosition = new Vector3(0, height - 1, 0);
+
+ return treeRoot;
+ }
+
+ private static GameObject CreateTrunk(int height) {
+ GameObject trunk = new GameObject("Trunk");
+
+ MeshFilter meshFilter = trunk.AddComponent();
+ MeshRenderer meshRenderer = trunk.AddComponent();
+
+ // Create a cylindrical trunk mesh
+ Mesh trunkMesh = CreateCylinderMesh(0.5f, height, 8);
+ meshFilter.mesh = trunkMesh;
+
+ // Set brown bark material
+ Material trunkMaterial = new Material(Shader.Find("Standard"));
+ trunkMaterial.color = new Color(0.4f, 0.2f, 0.1f);
+ meshRenderer.material = trunkMaterial;
+
+ return trunk;
+ }
+
+ private static GameObject CreateLeaves(int height) {
+ GameObject leaves = new GameObject("Leaves");
+
+ MeshFilter meshFilter = leaves.AddComponent();
+ MeshRenderer meshRenderer = leaves.AddComponent();
+
+ // Create a spherical canopy mesh
+ Mesh leavesMesh = CreateSphereMesh(2.5f, 10, 10);
+ meshFilter.mesh = leavesMesh;
+
+ // Set green foliage material
+ Material leavesMaterial = new Material(Shader.Find("Standard"));
+ leavesMaterial.color = new Color(0.1f, 0.6f, 0.1f);
+ meshRenderer.material = leavesMaterial;
+
+ return leaves;
+ }
+
+ ///
+ /// Creates a cylindrical mesh for the trunk
+ ///
+ private static Mesh CreateCylinderMesh(float radius, float height, int segments) {
+ Mesh mesh = new Mesh();
+ mesh.name = "CylinderMesh";
+
+ int vertexCount = segments * 2 + 2; // Top and bottom circles plus centers
+ Vector3[] vertices = new Vector3[vertexCount];
+ Vector3[] normals = new Vector3[vertexCount];
+ Vector2[] uvs = new Vector2[vertexCount];
+
+ // Create vertices
+ float angleStep = 360f / segments * Mathf.Deg2Rad;
+
+ // Bottom circle
+ for (int i = 0; i < segments; i++) {
+ float angle = i * angleStep;
+ float x = Mathf.Cos(angle) * radius;
+ float z = Mathf.Sin(angle) * radius;
+ vertices[i] = new Vector3(x, 0, z);
+ normals[i] = new Vector3(x, 0, z).normalized;
+ uvs[i] = new Vector2((float)i / segments, 0);
+ }
+
+ // Top circle
+ for (int i = 0; i < segments; i++) {
+ float angle = i * angleStep;
+ float x = Mathf.Cos(angle) * radius * 0.8f; // Slightly tapered
+ float z = Mathf.Sin(angle) * radius * 0.8f;
+ vertices[segments + i] = new Vector3(x, height, z);
+ normals[segments + i] = new Vector3(x, 0, z).normalized;
+ uvs[segments + i] = new Vector2((float)i / segments, 1);
+ }
+
+ // Center points for caps
+ vertices[segments * 2] = new Vector3(0, 0, 0); // Bottom center
+ vertices[segments * 2 + 1] = new Vector3(0, height, 0); // Top center
+ normals[segments * 2] = Vector3.down;
+ normals[segments * 2 + 1] = Vector3.up;
+ uvs[segments * 2] = new Vector2(0.5f, 0.5f);
+ uvs[segments * 2 + 1] = new Vector2(0.5f, 0.5f);
+
+ // Create triangles
+ int[] triangles = new int[segments * 12];
+ int triIndex = 0;
+
+ // Side triangles
+ for (int i = 0; i < segments; i++) {
+ int next = (i + 1) % segments;
+
+ // First triangle
+ triangles[triIndex++] = i;
+ triangles[triIndex++] = segments + i;
+ triangles[triIndex++] = next;
+
+ // Second triangle
+ triangles[triIndex++] = next;
+ triangles[triIndex++] = segments + i;
+ triangles[triIndex++] = segments + next;
+ }
+
+ // Bottom cap
+ for (int i = 0; i < segments; i++) {
+ int next = (i + 1) % segments;
+ triangles[triIndex++] = segments * 2;
+ triangles[triIndex++] = next;
+ triangles[triIndex++] = i;
+ }
+
+ // Top cap
+ for (int i = 0; i < segments; i++) {
+ int next = (i + 1) % segments;
+ triangles[triIndex++] = segments * 2 + 1;
+ triangles[triIndex++] = segments + i;
+ triangles[triIndex++] = segments + next;
+ }
+
+ mesh.vertices = vertices;
+ mesh.normals = normals;
+ mesh.uv = uvs;
+ mesh.triangles = triangles;
+
+ mesh.RecalculateBounds();
+ return mesh;
+ }
+
+ ///
+ /// Creates a spherical mesh for the leaves
+ ///
+ private static Mesh CreateSphereMesh(float radius, int latitudeSegments, int longitudeSegments) {
+ Mesh mesh = new Mesh();
+ mesh.name = "SphereMesh";
+
+ int vertexCount = (latitudeSegments + 1) * (longitudeSegments + 1);
+ Vector3[] vertices = new Vector3[vertexCount];
+ Vector3[] normals = new Vector3[vertexCount];
+ Vector2[] uvs = new Vector2[vertexCount];
+
+ int vertIndex = 0;
+ for (int lat = 0; lat <= latitudeSegments; lat++) {
+ float theta = lat * Mathf.PI / latitudeSegments;
+ float sinTheta = Mathf.Sin(theta);
+ float cosTheta = Mathf.Cos(theta);
+
+ for (int lon = 0; lon <= longitudeSegments; lon++) {
+ float phi = lon * 2 * Mathf.PI / longitudeSegments;
+ float sinPhi = Mathf.Sin(phi);
+ float cosPhi = Mathf.Cos(phi);
+
+ Vector3 normal = new Vector3(cosPhi * sinTheta, cosTheta, sinPhi * sinTheta);
+ vertices[vertIndex] = normal * radius;
+ normals[vertIndex] = normal;
+ uvs[vertIndex] = new Vector2((float)lon / longitudeSegments, (float)lat / latitudeSegments);
+ vertIndex++;
+ }
+ }
+
+ int[] triangles = new int[latitudeSegments * longitudeSegments * 6];
+ int triIndex = 0;
+
+ for (int lat = 0; lat < latitudeSegments; lat++) {
+ for (int lon = 0; lon < longitudeSegments; lon++) {
+ int current = lat * (longitudeSegments + 1) + lon;
+ int next = current + longitudeSegments + 1;
+
+ triangles[triIndex++] = current;
+ triangles[triIndex++] = next;
+ triangles[triIndex++] = current + 1;
+
+ triangles[triIndex++] = current + 1;
+ triangles[triIndex++] = next;
+ triangles[triIndex++] = next + 1;
+ }
+ }
+
+ mesh.vertices = vertices;
+ mesh.normals = normals;
+ mesh.uv = uvs;
+ mesh.triangles = triangles;
+
+ mesh.RecalculateBounds();
+ return mesh;
+ }
+ }
+}
diff --git a/src/c#/main/entity/TreeModel.cs.meta b/src/c#/main/entity/TreeModel.cs.meta
new file mode 100644
index 00000000..93d0a8d4
--- /dev/null
+++ b/src/c#/main/entity/TreeModel.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a68d5ee19da7445a9ca1cc8d424b2795
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/c#/main/entity/entities/AppleTree.cs b/src/c#/main/entity/entities/AppleTree.cs
index 6c505ab7..71fd93a2 100644
--- a/src/c#/main/entity/entities/AppleTree.cs
+++ b/src/c#/main/entity/entities/AppleTree.cs
@@ -4,8 +4,6 @@
namespace beyondnations {
public class AppleTree : Entity {
- private GameObject trunk;
- private GameObject leaves;
private int height;
public AppleTree(Vector3 position, int height) : base(EntityType.TREE, "Tree") {
@@ -14,25 +12,9 @@ public AppleTree(Vector3 position, int height) : base(EntityType.TREE, "Tree") {
}
public override void createGameObject(Vector3 position) {
- GameObject gameObject = new GameObject();
- gameObject.transform.position = position;
+ // Use the new 3D tree model instead of primitives
+ GameObject gameObject = TreeModel.CreateTree(position, height);
gameObject.name = "AppleTree";
-
- trunk = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
- trunk.transform.localScale = new Vector3(1, height, 1);
- trunk.GetComponent().material.color = new Color(0.5f, 0.25f, 0);
- trunk.transform.position = position;
- trunk.transform.parent = gameObject.transform;
- trunk.name = "Trunk";
- UnityEngine.Object.Destroy(trunk.GetComponent());
-
- leaves = GameObject.CreatePrimitive(PrimitiveType.Cube);
- leaves.transform.localScale = new Vector3(3, 3, 3);
- leaves.GetComponent().material.color = Color.green;
- leaves.transform.position = position + new Vector3(0, height - 1, 0);
- leaves.transform.parent = gameObject.transform;
- leaves.name = "Leaves";
- UnityEngine.Object.Destroy(leaves.GetComponent());
setGameObject(gameObject);
diff --git a/src/c#/tests/entity/TestAppleTree.cs b/src/c#/tests/entity/TestAppleTree.cs
index c414d187..7cdd8e28 100644
--- a/src/c#/tests/entity/TestAppleTree.cs
+++ b/src/c#/tests/entity/TestAppleTree.cs
@@ -15,18 +15,19 @@ public static void testInstantiation() {
int height = 5;
AppleTree tree = new AppleTree(new Vector3(0, 0, 0), height);
- // check
+ // check - verify tree is created with proper structure
UnityEngine.Debug.Assert(tree.getType() == EntityType.TREE);
UnityEngine.Debug.Assert(tree.getGameObject().name == "AppleTree");
UnityEngine.Debug.Assert(tree.getGameObject().transform.position == new Vector3(0, 0, 0));
- UnityEngine.Debug.Assert(tree.getGameObject().transform.localScale == new Vector3(1, 1, 1));
- UnityEngine.Debug.Assert(tree.getGameObject().transform.childCount == 2);
+ UnityEngine.Debug.Assert(tree.getGameObject().transform.childCount == 2); // Trunk and Leaves
UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(0).name == "Trunk");
- UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(0).transform.localScale == new Vector3(1, height, 1));
- UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(0).GetComponent().material.color == new Color(0.5f, 0.25f, 0));
UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(1).name == "Leaves");
- UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(1).transform.localScale == new Vector3(3, 3, 3));
- UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(1).GetComponent().material.color == Color.green);
+
+ // Verify trunk and leaves have MeshFilter and MeshRenderer components (3D model)
+ UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(0).GetComponent() != null);
+ UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(0).GetComponent() != null);
+ UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(1).GetComponent() != null);
+ UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(1).GetComponent() != null);
// clean up
GameObject.Destroy(tree.getGameObject());
From 411b401720790977b7b6be1f06ab68b55e0fb0f5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 08:36:00 +0000
Subject: [PATCH 3/7] Fix tree colors to match original implementation
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
---
src/c#/main/entity/TreeModel.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/c#/main/entity/TreeModel.cs b/src/c#/main/entity/TreeModel.cs
index 230a7b53..0a73a377 100644
--- a/src/c#/main/entity/TreeModel.cs
+++ b/src/c#/main/entity/TreeModel.cs
@@ -36,9 +36,9 @@ private static GameObject CreateTrunk(int height) {
Mesh trunkMesh = CreateCylinderMesh(0.5f, height, 8);
meshFilter.mesh = trunkMesh;
- // Set brown bark material
+ // Set brown bark material (matching original color)
Material trunkMaterial = new Material(Shader.Find("Standard"));
- trunkMaterial.color = new Color(0.4f, 0.2f, 0.1f);
+ trunkMaterial.color = new Color(0.5f, 0.25f, 0);
meshRenderer.material = trunkMaterial;
return trunk;
@@ -54,9 +54,9 @@ private static GameObject CreateLeaves(int height) {
Mesh leavesMesh = CreateSphereMesh(2.5f, 10, 10);
meshFilter.mesh = leavesMesh;
- // Set green foliage material
+ // Set green foliage material (matching original color)
Material leavesMaterial = new Material(Shader.Find("Standard"));
- leavesMaterial.color = new Color(0.1f, 0.6f, 0.1f);
+ leavesMaterial.color = Color.green;
meshRenderer.material = leavesMaterial;
return leaves;
From 1dcbc619b85eccfb0d88ffa7501c785ab116e31f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 08:36:43 +0000
Subject: [PATCH 4/7] Improve TreeModel API and remove redundant code
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
---
src/c#/main/entity/TreeModel.cs | 4 ++--
src/c#/main/entity/entities/AppleTree.cs | 3 +--
src/c#/tests/entity/TestAppleTree.cs | 2 +-
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/c#/main/entity/TreeModel.cs b/src/c#/main/entity/TreeModel.cs
index 0a73a377..1e988121 100644
--- a/src/c#/main/entity/TreeModel.cs
+++ b/src/c#/main/entity/TreeModel.cs
@@ -9,8 +9,8 @@ namespace beyondnations {
///
public class TreeModel {
- public static GameObject CreateTree(Vector3 position, int height) {
- GameObject treeRoot = new GameObject("Tree");
+ public static GameObject CreateTree(Vector3 position, int height, string name = "Tree") {
+ GameObject treeRoot = new GameObject(name);
treeRoot.transform.position = position;
// Create trunk
diff --git a/src/c#/main/entity/entities/AppleTree.cs b/src/c#/main/entity/entities/AppleTree.cs
index 71fd93a2..f96606a9 100644
--- a/src/c#/main/entity/entities/AppleTree.cs
+++ b/src/c#/main/entity/entities/AppleTree.cs
@@ -13,8 +13,7 @@ public AppleTree(Vector3 position, int height) : base(EntityType.TREE, "Tree") {
public override void createGameObject(Vector3 position) {
// Use the new 3D tree model instead of primitives
- GameObject gameObject = TreeModel.CreateTree(position, height);
- gameObject.name = "AppleTree";
+ GameObject gameObject = TreeModel.CreateTree(position, height, "AppleTree");
setGameObject(gameObject);
diff --git a/src/c#/tests/entity/TestAppleTree.cs b/src/c#/tests/entity/TestAppleTree.cs
index 7cdd8e28..1281cc85 100644
--- a/src/c#/tests/entity/TestAppleTree.cs
+++ b/src/c#/tests/entity/TestAppleTree.cs
@@ -19,7 +19,7 @@ public static void testInstantiation() {
UnityEngine.Debug.Assert(tree.getType() == EntityType.TREE);
UnityEngine.Debug.Assert(tree.getGameObject().name == "AppleTree");
UnityEngine.Debug.Assert(tree.getGameObject().transform.position == new Vector3(0, 0, 0));
- UnityEngine.Debug.Assert(tree.getGameObject().transform.childCount == 2); // Trunk and Leaves
+ UnityEngine.Debug.Assert(tree.getGameObject().transform.childCount == 2);
UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(0).name == "Trunk");
UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(1).name == "Leaves");
From ee07bfb2e4a6c8f3b8766edeaf9df512b47eb162 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 11:50:58 +0000
Subject: [PATCH 5/7] Address code review feedback: Add mesh cleanup, fix
positioning, validate height, add shader fallback, improve tests
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
---
src/c#/main/entity/TreeModel.cs | 65 ++++++++++++++++++++----
src/c#/main/entity/entities/AppleTree.cs | 2 +-
src/c#/tests/entity/TestAppleTree.cs | 27 ++++++++--
3 files changed, 78 insertions(+), 16 deletions(-)
diff --git a/src/c#/main/entity/TreeModel.cs b/src/c#/main/entity/TreeModel.cs
index 1e988121..f304efb0 100644
--- a/src/c#/main/entity/TreeModel.cs
+++ b/src/c#/main/entity/TreeModel.cs
@@ -6,17 +6,24 @@ namespace beyondnations {
/// Creates a procedural 3D tree model.
/// This replaces the simple primitive shapes (Cylinder + Cube) with a proper mesh-based tree.
/// Can be easily replaced with an imported 3D model file when available.
+ /// Note: The trunk has a slight taper (top is 0.8x the bottom radius) for more realistic appearance.
///
public class TreeModel {
public static GameObject CreateTree(Vector3 position, int height, string name = "Tree") {
+ // Validate height parameter
+ if (height <= 0) {
+ height = 1;
+ }
+
GameObject treeRoot = new GameObject(name);
treeRoot.transform.position = position;
- // Create trunk
+ // Create trunk - position it to match original primitive cylinder behavior
+ // Unity's primitive cylinder is centered, so we offset by height/2 to match
GameObject trunk = CreateTrunk(height);
trunk.transform.parent = treeRoot.transform;
- trunk.transform.localPosition = Vector3.zero;
+ trunk.transform.localPosition = new Vector3(0, height / 2.0f, 0);
// Create leaves/canopy
GameObject leaves = CreateLeaves(height);
@@ -26,6 +33,34 @@ public static GameObject CreateTree(Vector3 position, int height, string name =
return treeRoot;
}
+ ///
+ /// Properly destroys a tree and its associated mesh resources.
+ /// Call this instead of Object.Destroy to prevent memory leaks.
+ ///
+ public static void DestroyTree(GameObject treeRoot) {
+ if (treeRoot == null) return;
+
+ // Explicitly destroy the meshes to prevent memory leaks
+ Transform trunk = treeRoot.transform.Find("Trunk");
+ if (trunk != null) {
+ MeshFilter meshFilter = trunk.GetComponent();
+ if (meshFilter != null && meshFilter.mesh != null) {
+ UnityEngine.Object.Destroy(meshFilter.mesh);
+ }
+ }
+
+ Transform leaves = treeRoot.transform.Find("Leaves");
+ if (leaves != null) {
+ MeshFilter meshFilter = leaves.GetComponent();
+ if (meshFilter != null && meshFilter.mesh != null) {
+ UnityEngine.Object.Destroy(meshFilter.mesh);
+ }
+ }
+
+ // Destroy the tree GameObject
+ UnityEngine.Object.Destroy(treeRoot);
+ }
+
private static GameObject CreateTrunk(int height) {
GameObject trunk = new GameObject("Trunk");
@@ -37,7 +72,11 @@ private static GameObject CreateTrunk(int height) {
meshFilter.mesh = trunkMesh;
// Set brown bark material (matching original color)
- Material trunkMaterial = new Material(Shader.Find("Standard"));
+ Shader shader = Shader.Find("Standard");
+ if (shader == null) {
+ shader = Shader.Find("Unlit/Color");
+ }
+ Material trunkMaterial = new Material(shader);
trunkMaterial.color = new Color(0.5f, 0.25f, 0);
meshRenderer.material = trunkMaterial;
@@ -55,7 +94,11 @@ private static GameObject CreateLeaves(int height) {
meshFilter.mesh = leavesMesh;
// Set green foliage material (matching original color)
- Material leavesMaterial = new Material(Shader.Find("Standard"));
+ Shader shader = Shader.Find("Standard");
+ if (shader == null) {
+ shader = Shader.Find("Unlit/Color");
+ }
+ Material leavesMaterial = new Material(shader);
leavesMaterial.color = Color.green;
meshRenderer.material = leavesMaterial;
@@ -77,29 +120,29 @@ private static Mesh CreateCylinderMesh(float radius, float height, int segments)
// Create vertices
float angleStep = 360f / segments * Mathf.Deg2Rad;
- // Bottom circle
+ // Bottom circle - centered at -height/2 to match Unity primitive cylinder
for (int i = 0; i < segments; i++) {
float angle = i * angleStep;
float x = Mathf.Cos(angle) * radius;
float z = Mathf.Sin(angle) * radius;
- vertices[i] = new Vector3(x, 0, z);
+ vertices[i] = new Vector3(x, -height / 2.0f, z);
normals[i] = new Vector3(x, 0, z).normalized;
uvs[i] = new Vector2((float)i / segments, 0);
}
- // Top circle
+ // Top circle - at +height/2 to match Unity primitive cylinder
for (int i = 0; i < segments; i++) {
float angle = i * angleStep;
- float x = Mathf.Cos(angle) * radius * 0.8f; // Slightly tapered
+ float x = Mathf.Cos(angle) * radius * 0.8f; // Slightly tapered for realism
float z = Mathf.Sin(angle) * radius * 0.8f;
- vertices[segments + i] = new Vector3(x, height, z);
+ vertices[segments + i] = new Vector3(x, height / 2.0f, z);
normals[segments + i] = new Vector3(x, 0, z).normalized;
uvs[segments + i] = new Vector2((float)i / segments, 1);
}
// Center points for caps
- vertices[segments * 2] = new Vector3(0, 0, 0); // Bottom center
- vertices[segments * 2 + 1] = new Vector3(0, height, 0); // Top center
+ vertices[segments * 2] = new Vector3(0, -height / 2.0f, 0); // Bottom center
+ vertices[segments * 2 + 1] = new Vector3(0, height / 2.0f, 0); // Top center
normals[segments * 2] = Vector3.down;
normals[segments * 2 + 1] = Vector3.up;
uvs[segments * 2] = new Vector2(0.5f, 0.5f);
diff --git a/src/c#/main/entity/entities/AppleTree.cs b/src/c#/main/entity/entities/AppleTree.cs
index f96606a9..c3d627a5 100644
--- a/src/c#/main/entity/entities/AppleTree.cs
+++ b/src/c#/main/entity/entities/AppleTree.cs
@@ -23,7 +23,7 @@ public override void createGameObject(Vector3 position) {
}
public override void destroyGameObject() {
- UnityEngine.Object.Destroy(getGameObject());
+ TreeModel.DestroyTree(getGameObject());
}
}
}
\ No newline at end of file
diff --git a/src/c#/tests/entity/TestAppleTree.cs b/src/c#/tests/entity/TestAppleTree.cs
index 1281cc85..14dd7aeb 100644
--- a/src/c#/tests/entity/TestAppleTree.cs
+++ b/src/c#/tests/entity/TestAppleTree.cs
@@ -24,10 +24,29 @@ public static void testInstantiation() {
UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(1).name == "Leaves");
// Verify trunk and leaves have MeshFilter and MeshRenderer components (3D model)
- UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(0).GetComponent() != null);
- UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(0).GetComponent() != null);
- UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(1).GetComponent() != null);
- UnityEngine.Debug.Assert(tree.getGameObject().transform.GetChild(1).GetComponent() != null);
+ Transform trunkTransform = tree.getGameObject().transform.GetChild(0);
+ Transform leavesTransform = tree.getGameObject().transform.GetChild(1);
+
+ MeshFilter trunkMeshFilter = trunkTransform.GetComponent();
+ MeshRenderer trunkMeshRenderer = trunkTransform.GetComponent();
+ MeshFilter leavesMeshFilter = leavesTransform.GetComponent();
+ MeshRenderer leavesMeshRenderer = leavesTransform.GetComponent();
+
+ // Components should exist
+ UnityEngine.Debug.Assert(trunkMeshFilter != null);
+ UnityEngine.Debug.Assert(trunkMeshRenderer != null);
+ UnityEngine.Debug.Assert(leavesMeshFilter != null);
+ UnityEngine.Debug.Assert(leavesMeshRenderer != null);
+
+ // Meshes should be assigned
+ UnityEngine.Debug.Assert(trunkMeshFilter.mesh != null);
+ UnityEngine.Debug.Assert(leavesMeshFilter.mesh != null);
+
+ // Materials should be assigned and have expected colors
+ UnityEngine.Debug.Assert(trunkMeshRenderer.material != null);
+ UnityEngine.Debug.Assert(leavesMeshRenderer.material != null);
+ UnityEngine.Debug.Assert(trunkMeshRenderer.material.color == new Color(0.5f, 0.25f, 0));
+ UnityEngine.Debug.Assert(leavesMeshRenderer.material.color == Color.green);
// clean up
GameObject.Destroy(tree.getGameObject());
From 34672b285b86f691f1bed51dc234aefe2b265c4e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 11:51:48 +0000
Subject: [PATCH 6/7] Extract shader fallback logic to helper method and add
final fallback
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
---
src/c#/main/entity/TreeModel.cs | 28 ++++++++++++++++++----------
1 file changed, 18 insertions(+), 10 deletions(-)
diff --git a/src/c#/main/entity/TreeModel.cs b/src/c#/main/entity/TreeModel.cs
index f304efb0..fe8bcc4b 100644
--- a/src/c#/main/entity/TreeModel.cs
+++ b/src/c#/main/entity/TreeModel.cs
@@ -61,6 +61,22 @@ public static void DestroyTree(GameObject treeRoot) {
UnityEngine.Object.Destroy(treeRoot);
}
+ ///
+ /// Gets a default shader with fallback options.
+ /// Tries Standard shader first, then Unlit/Color, and finally uses a built-in fallback.
+ ///
+ private static Shader GetDefaultShader() {
+ Shader shader = Shader.Find("Standard");
+ if (shader == null) {
+ shader = Shader.Find("Unlit/Color");
+ }
+ if (shader == null) {
+ // Final fallback - use the built-in default diffuse shader
+ shader = Shader.Find("Diffuse");
+ }
+ return shader;
+ }
+
private static GameObject CreateTrunk(int height) {
GameObject trunk = new GameObject("Trunk");
@@ -72,11 +88,7 @@ private static GameObject CreateTrunk(int height) {
meshFilter.mesh = trunkMesh;
// Set brown bark material (matching original color)
- Shader shader = Shader.Find("Standard");
- if (shader == null) {
- shader = Shader.Find("Unlit/Color");
- }
- Material trunkMaterial = new Material(shader);
+ Material trunkMaterial = new Material(GetDefaultShader());
trunkMaterial.color = new Color(0.5f, 0.25f, 0);
meshRenderer.material = trunkMaterial;
@@ -94,11 +106,7 @@ private static GameObject CreateLeaves(int height) {
meshFilter.mesh = leavesMesh;
// Set green foliage material (matching original color)
- Shader shader = Shader.Find("Standard");
- if (shader == null) {
- shader = Shader.Find("Unlit/Color");
- }
- Material leavesMaterial = new Material(shader);
+ Material leavesMaterial = new Material(GetDefaultShader());
leavesMaterial.color = Color.green;
meshRenderer.material = leavesMaterial;
From 40efa9dee2bfd72cadd93b68b58ca83c1079e4dd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 11:53:46 +0000
Subject: [PATCH 7/7] Add debug warning when invalid tree height is clamped
Co-authored-by: dmccoystephenson <21204351+dmccoystephenson@users.noreply.github.com>
---
src/c#/main/entity/TreeModel.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/c#/main/entity/TreeModel.cs b/src/c#/main/entity/TreeModel.cs
index fe8bcc4b..6febe28b 100644
--- a/src/c#/main/entity/TreeModel.cs
+++ b/src/c#/main/entity/TreeModel.cs
@@ -13,6 +13,7 @@ public class TreeModel {
public static GameObject CreateTree(Vector3 position, int height, string name = "Tree") {
// Validate height parameter
if (height <= 0) {
+ UnityEngine.Debug.LogWarning($"Invalid tree height {height}, clamping to minimum of 1");
height = 1;
}