├── Scripts
├── ColorHeight.cs
├── PlanetData.cs
├── PopulateData.cs
├── GenerationData.cs
├── Planet.cs
└── Polygon.cs
├── README.md
├── PopulateData.cs
└── LICENSE
/Scripts/ColorHeight.cs:
--------------------------------------------------------------------------------
1 | /**
2 | ColorHeight.cs
3 | Purpose: Contains ColorHeight class and
4 | the class required to have a custom layout on the inspector.
5 | Require: No other files required.
6 |
7 | @author Mikel Jauregui
8 | @version 1.1.0 14/07/2018
9 | */
10 |
11 | using UnityEngine;
12 | using UnityEditor;
13 | using System;
14 |
15 | [Serializable]
16 | public class ColorHeight
17 | {
18 | [Tooltip("Minimun height to set the color.")]
19 | public int layer;
20 | [Tooltip("Color of the mesh.")]
21 | public Color color;
22 | }
23 |
24 | [CustomPropertyDrawer(typeof(ColorHeight), true)]
25 | public class ColorHeightDrawer : PropertyDrawer
26 | {
27 | ///
28 | /// Custom GUI view of the ColorHeight class.
29 | ///
30 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
31 | {
32 | label = EditorGUI.BeginProperty(position, label, property);
33 | Rect contentPosition = EditorGUI.PrefixLabel(position, label);
34 | contentPosition.width *= 0.75f;
35 | EditorGUI.indentLevel = 0;
36 | contentPosition.width /= 2f;
37 | EditorGUIUtility.labelWidth = 45f;
38 | EditorGUI.PropertyField(contentPosition, property.FindPropertyRelative("layer"), new GUIContent("Layer"));
39 | contentPosition.x += contentPosition.width + 10;
40 | EditorGUI.PropertyField(contentPosition, property.FindPropertyRelative("color"), new GUIContent("Color"));
41 | EditorGUI.EndProperty();
42 | }
43 | }
--------------------------------------------------------------------------------
/Scripts/PlanetData.cs:
--------------------------------------------------------------------------------
1 | /**
2 | PlanetData.cs
3 | Purpose: Scriptable object that have all the data needed
4 | to crate a planet.
5 | Require: GenerationData.cs, ColorHeight.cs, PopulateData.cs.
6 |
7 | @author Mikel Jauregui
8 | @version 1.1.0 14/07/2018
9 | */
10 |
11 | using UnityEngine;
12 |
13 | namespace Generation
14 | {
15 | [CreateAssetMenu()]
16 | public class PlanetData : ScriptableObject
17 | {
18 | [Tooltip("Name for the Empty Object that will hold the terrains.")]
19 | public string newName;
20 | [Tooltip("Seed for the planet.")]
21 | public int seed;
22 | [Tooltip("If true it will ignore the seed and generate a new random one.")]
23 | public bool generateRandomSeed;
24 | [Tooltip("Types of terrain generation.")]
25 | public Style terrainStyle;
26 | [Tooltip("Material for the terrain(to be able to apply the color per layer the material need to be vertex friendly).")]
27 | public Material material;
28 | [Tooltip("Material for the sea.")]
29 | public Material seaMaterial;
30 | [Space(5)]
31 | [Tooltip("Position of the center of the planet.")]
32 | public Vector3 position;
33 | [Tooltip("Radius of the planet.")]
34 | public float radius;
35 | [Tooltip("Level of the sea.")]
36 | public float seaLevel;
37 | [Tooltip("Amount of times the icosphere is divided.")]
38 | public int subdivisions;
39 | [Tooltip("Amount of times each icosphere division is divided.")]
40 | public int chunckSubdivisions;
41 | [Space(5)]
42 | [Tooltip("Noise to modify the terrain shape.")]
43 | public GenerationData[] generationData;
44 | [Tooltip("Colors for the terrain.")]
45 | public ColorHeight[] ColorPerLayer;
46 | [Tooltip("Data to populate the terrain.")]
47 | public PopulateData[] population;
48 | }
49 | ///
50 | /// Types of terrains.
51 | ///
52 | public enum Style { LowPoly, Terrace };
53 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # New feature added:
4 | Now the planet have water and can be populate:
5 | 
6 |
7 | # Procedural-planet-generation (Unity Project)
8 | Generate planets proceduraly.
9 | [(Generate)](https://thehunterjp.itch.io/planetary-generation-prototype)
10 | # Low Poly style:
11 |
12 |
13 | # Terrace style:
14 |
15 | 
16 |
17 |
18 | # How to use?
19 | #### We only need to **import 5 files**:
20 | 'Planet.cs' , 'PlanetData.cs' , 'Polygon.cs' , 'ColorHeight.cs' , 'GenerationData.cs'.
21 |
22 | #### GenerationData:
23 | This scriptable object stores the information that will then be used to get the height of the map vertices (it uses perlin noise).
24 |
25 | 
26 |
27 | #### PlanetData:
28 | This scriptable object stores the information that will then be used to create a planet.
29 |
30 | 
31 |
32 | #### Generate the planets:
33 | To add a planet to the list of planets that will be created, use the functtion:
34 | > Planet.AddPlanetToQueu();
35 |
36 | To start the thread that will calculate the data for the mesh sphere generation and modification of the landscape (will only be executed after StartDataQueu has ended):
37 | > Planet.StartDataQueu();
38 |
39 | Instantiate the planet into the scene:
40 | >Planet.InstantiateIntoWorld();
41 |
42 | Wait until the planet data have been compute. Then load the chunks in range to the position.
43 | >Planet.HideAndShow(Vector3 position);
44 |
45 | Both InstantiateIntoWorld and HideAndShow can be used at the same time. In that case
46 | InstantiateIntoWorld will generate the planet until it's fully generated and the will hide all the
47 | terrain that is not in range of the position.
48 |
49 | The end result should look like this:
50 | 
51 |
--------------------------------------------------------------------------------
/PopulateData.cs:
--------------------------------------------------------------------------------
1 | /**
2 | PopulateData.cs
3 | Purpose: Scriptable object that have all the data needed
4 | to crate a planet.
5 | Require: GenerationData.cs, ColorHeight.cs, PopulateData.cs.
6 |
7 | @author Mikel Jauregui
8 | @version 1.1.0 14/07/2018
9 | */
10 |
11 | using UnityEngine;
12 |
13 | namespace Generation
14 | {
15 | [CreateAssetMenu]
16 | public class PopulateData : ScriptableObject
17 | {
18 | // To generate all random numbers.
19 | private System.Random rng;
20 |
21 | [Tooltip("List of models.")]
22 | public GameObject[] models;
23 | [Tooltip("Noise.")]
24 | public GenerationData noise;
25 | [Tooltip("Minimum height to get the height.")]
26 | public int minHeight;
27 | [Tooltip("Maximum height to get the height.")]
28 | public int maxHeight;
29 | [Tooltip("Numbers of divisions.")]
30 | public int divisions;
31 | [Range(0,1)]
32 | [Tooltip("Probability to generatethe model.")]
33 | public float probability;
34 |
35 | // Offset for the noise.
36 | private Vector3 offset;
37 |
38 | ///
39 | /// Set the seed random number generator and the offset.
40 | ///
41 | /// Seed of the planet.
42 | /// List of population that want to add the seed.
43 | static public void SetSeed(int seed, PopulateData[] population)
44 | {
45 | foreach (var item in population)
46 | {
47 | item.rng = new System.Random(123);
48 | Vector3 offset = new Vector3(item.rng.Next(-10000, 10000), item.rng.Next(-10000, 10000), item.rng.Next(-10000, 10000));
49 | }
50 | }
51 | ///
52 | /// Check if it can be instantiate.
53 | ///
54 | /// Position to check.
55 | /// Return true if it can be instantiate.
56 | public bool InstantiateAtPosition(Vector3 position)
57 | {
58 | // Get the height at the position.
59 | float height = noise.CalculateNoise(position + offset);
60 | // Check if in range.
61 | if(height > minHeight && height < maxHeight)
62 | return true;
63 | return false;
64 | }
65 | ///
66 | /// Get a random model from the list.
67 | ///
68 | /// Return a model.
69 | public GameObject GetRandomModel()
70 | {
71 | // Get random model.
72 | return models[rng.Next(0, models.Length)];
73 | }
74 | ///
75 | /// Get the height at that position.
76 | ///
77 | /// Position to check.
78 | /// Return the height.
79 | public float GetHeightAtPosition(Vector3 position)
80 | {
81 | // Get noise at position.
82 | return noise.CalculateNoise(position + offset);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Scripts/PopulateData.cs:
--------------------------------------------------------------------------------
1 | /**
2 | PopulateData.cs
3 | Purpose: Scriptable object that have all the data needed
4 | to crate a planet.
5 | Require: GenerationData.cs, ColorHeight.cs, PopulateData.cs.
6 |
7 | @author Mikel Jauregui
8 | @version 1.1.0 14/07/2018
9 | */
10 |
11 | using UnityEngine;
12 |
13 | namespace Generation
14 | {
15 | [CreateAssetMenu]
16 | public class PopulateData : ScriptableObject
17 | {
18 | // To generate all random numbers.
19 | private System.Random rng;
20 |
21 | [Tooltip("List of models.")]
22 | public GameObject[] models;
23 | [Tooltip("Noise.")]
24 | public GenerationData noise;
25 | [Tooltip("Minimum height to get the height.")]
26 | public int minHeight;
27 | [Tooltip("Maximum height to get the height.")]
28 | public int maxHeight;
29 | [Tooltip("Numbers of divisions.")]
30 | public int divisions;
31 | [Range(0,1)]
32 | [Tooltip("Probability to generatethe model.")]
33 | public float probability;
34 |
35 | // Offset for the noise.
36 | private Vector3 offset;
37 |
38 | ///
39 | /// Set the seed random number generator and the offset.
40 | ///
41 | /// Seed of the planet.
42 | /// List of population that want to add the seed.
43 | static public void SetSeed(int seed, PopulateData[] population)
44 | {
45 | foreach (var item in population)
46 | {
47 | item.rng = new System.Random(123);
48 | Vector3 offset = new Vector3(item.rng.Next(-10000, 10000), item.rng.Next(-10000, 10000), item.rng.Next(-10000, 10000));
49 | }
50 | }
51 | ///
52 | /// Check if it can be instantiate.
53 | ///
54 | /// Position to check.
55 | /// Return true if it can be instantiate.
56 | public bool InstantiateAtPosition(Vector3 position)
57 | {
58 | // Get the height at the position.
59 | float height = noise.CalculateNoise(position + offset);
60 | // Check if in range.
61 | if(height > minHeight && height < maxHeight)
62 | return true;
63 | return false;
64 | }
65 | ///
66 | /// Get a random model from the list.
67 | ///
68 | /// Return a model.
69 | public GameObject GetRandomModel()
70 | {
71 | // Get random model.
72 | return models[rng.Next(0, models.Length)];
73 | }
74 | ///
75 | /// Get the height at that position.
76 | ///
77 | /// Position to check.
78 | /// Return the height.
79 | public float GetHeightAtPosition(Vector3 position)
80 | {
81 | // Get noise at position.
82 | return noise.CalculateNoise(position + offset);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Scripts/GenerationData.cs:
--------------------------------------------------------------------------------
1 | /**
2 | GenerationData.cs
3 | Purpose: Scriptable object that stores the
4 | data and some functions for the terrain generation.
5 | Require: No other files required.
6 |
7 | @author Mikel Jauregui
8 | @version 1.1.0 14/07/2018
9 | */
10 |
11 | using UnityEngine;
12 |
13 | namespace Generation
14 | {
15 | [CreateAssetMenu()]
16 | public class GenerationData : ScriptableObject
17 | {
18 | [Range(1, 10)]
19 | [Tooltip("Amounts of calls.")]
20 | public int octaves = 8;
21 | [Range(0.1f, 1.0f)]
22 | [Tooltip("Amplitud variation through octaves.")]
23 | public float persistance = 0.5f;
24 | [Range(1.0f, 2f)]
25 | [Tooltip("Frequency variation through octaves.")]
26 | public float lacunarity = 2f;
27 | [Range(0.1f, 500)]
28 | [Tooltip("General frequency division.")]
29 | public float scale = 1;
30 | [Range(0f,1f)]
31 | [Tooltip("Minimum perlin value that return a value.")]
32 | public float minimum_height = 0.5f;
33 | [Range(1f,500f)]
34 | [Tooltip("Height that will be multiply to the 0 to 1 value.")]
35 | public int height_multiplier = 1;
36 | [Tooltip("True to invert the values sign.")]
37 | public bool invert = false;
38 |
39 | ///
40 | /// Genrate 3D perlin noise value.
41 | ///
42 | /// x axis.
43 | /// y axis.
44 | /// z axis.
45 | /// Return a float between 0 and 1.
46 | static private float PerlinNoise3D(float x, float y, float z)
47 | {
48 |
49 | float ab = Mathf.PerlinNoise(x, y);
50 | float bc = Mathf.PerlinNoise(y, z);
51 | float ac = Mathf.PerlinNoise(x, z);
52 |
53 | float ba = Mathf.PerlinNoise(y, x);
54 | float cb = Mathf.PerlinNoise(z, y);
55 | float ca = Mathf.PerlinNoise(z, x);
56 |
57 | float abc = ab + bc + ac + ba + cb + ca;
58 | return abc / 6f;
59 | }
60 |
61 | ///
62 | /// Calculate the noise at a specific position.
63 | ///
64 | /// Position.
65 | /// Returns the height at that position.
66 | public float CalculateNoise(Vector3 position)
67 | {
68 | float x = position.x;
69 | float y = position.y;
70 | float z = position.z;
71 |
72 | float output = 0;
73 | float frequency = 1;
74 | float amplitude = 1;
75 |
76 | for (int i = 0; i < octaves; i++)
77 | {
78 | output += PerlinNoise3D(x * frequency / scale, y * frequency / scale, z * frequency / scale) * amplitude;
79 | amplitude *= persistance;
80 | frequency *= lacunarity;
81 | }
82 | // Make output to stay between 0 and 1.
83 | output /= octaves;
84 |
85 | // if output is less than minimum_height then set to 0.
86 | output = output > minimum_height ? Mathf.InverseLerp(0, 1f - minimum_height, output - minimum_height) : 0;
87 | output *= height_multiplier;
88 | if (invert)
89 | output *= -1;
90 | return output;
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Scripts/Planet.cs:
--------------------------------------------------------------------------------
1 | /**
2 | Planet.cs
3 | Purpose: Generate Planets into the scene.
4 | Require: Polygon.cs, GenerationData.cs,
5 | ColorHeight.cs, PopulationData.cs.
6 |
7 | @author Mikel Jauregui
8 | @version 1.1.0 14/07/18
9 | */
10 |
11 | using System.Collections.Generic;
12 | using UnityEngine;
13 | using System.Threading;
14 |
15 | namespace Generation
16 | {
17 | public class Planet : MonoBehaviour
18 | {
19 | // List where we store all the planets.
20 | static private List planetList = new List();
21 | // Queue where we add the polygons that need to be instantiate.
22 | static private Queue polygonsToInstantiate = new Queue();
23 | // List of polygons instantiated.
24 | static List distanceList = new List();
25 | // True if the thread that generate
26 | // the data for the planet has started.
27 | static private bool isThreadBeenStarted = false;
28 | // True if the thread that generate the
29 | // data for the planet has ended.
30 | static private bool isThreadDone = false;
31 | // True if the game want to quit.
32 | static private bool gameQuiting = false;
33 | // The terrain has been instantiate.
34 | static private bool terrainInstantiated = false;
35 |
36 | // Stores a reference to its own
37 | // to pass it to the polygons.
38 | private Planet planet;
39 | // Terrain style for the planet.
40 | private Style terrainStyle;
41 | // Array of noises that will
42 | // then be used to generate the terrain.
43 | private GenerationData[] generationData;
44 | // Array of colors that will then be used
45 | // to color the terrain.
46 | private ColorHeight[] colorHeight;
47 | // Array of offsets that will be apply
48 | // to the terrain generation noise.
49 | private Vector3[] offset;
50 | // Array that stores the data to
51 | // generate the population for the planet.
52 | private PopulateData[] population;
53 | // Material for the terrain.
54 | private Material material;
55 | // Material for the sea.
56 | private Material seaMaterial;
57 | // List of polygons that compose the planet.
58 | private List polygons;
59 | // List of vertices of the icosphere.
60 | private List vertices;
61 | // Center of the planet.
62 | private Vector3 position;
63 | // Planet radius.
64 | private float radius;
65 | // Sea Level.
66 | private float seaLevel;
67 | // Planet seed.
68 | private int seed;
69 | // Icosphere subdivisions.
70 | private int subdivisions;
71 | // Chunck subdivisions.
72 | private int chunckSubdivisions;
73 |
74 | ///
75 | /// Thread that generate all the data.
76 | ///
77 | static private void DataThreadLoop()
78 | {
79 | //Prevent list modification during
80 | // thread loop.
81 | lock (planetList)
82 | {
83 | foreach (var planet in planetList)
84 | {
85 | if (gameQuiting)
86 | return;
87 | // Create icosahedron.
88 | planet.InitAsIcosahedron();
89 | // Subdivide it.
90 | planet.Subdivide();
91 | // Create noise offset.
92 | planet.CalculateOffset();
93 |
94 | // Generate mesh data.
95 | foreach (var poly in planet.polygons)
96 | {
97 | if (gameQuiting)
98 | return;
99 |
100 | poly.GenerateMesh();
101 | }
102 | }
103 | }
104 | isThreadDone = true;
105 | }
106 | ///
107 | /// Check is a polygon is in range of the viewer.
108 | ///
109 | ///
110 | ///
111 | ///
112 | static private bool PolyInRange(Polygon poly, Vector3 position)
113 | {
114 | // Normalize to check on the circle unit.
115 | position = position.normalized;
116 |
117 | // Get the planet data.
118 | Planet p = poly.GetPlanet();
119 | int[] i = poly.GetVertices().ToArray();
120 | Vector3[] v = new Vector3[3] { p.vertices[i[0]], p.vertices[i[1]], p.vertices[i[2]] };
121 | float d0, d1, d2;
122 | d0 = Vector3.Distance(position, v[0].normalized);
123 | d1 = Vector3.Distance(position, v[1].normalized);
124 | d2 = Vector3.Distance(position, v[2].normalized);
125 | // True if in range.
126 | return d0 < Main.G_viewDistance || d1 < Main.G_viewDistance || d2 < Main.G_viewDistance;
127 | }
128 | ///
129 | /// Add a polygon to the Instantiate queue.
130 | ///
131 | /// The polygon that need to be added.
132 | static public void AddPolygonToQueue(Polygon poly)
133 | {
134 | lock (polygonsToInstantiate)
135 | {
136 | // Add to end of the queue.
137 | polygonsToInstantiate.Enqueue(poly);
138 | }
139 | }
140 | ///
141 | /// Check if the main thread want to quit.
142 | ///
143 | /// True if the game is quitting.
144 | static public bool ThreadNeedToQuit()
145 | {
146 | return gameQuiting;
147 | }
148 | ///
149 | /// Add a planet to the queue of the ones that need to be generated.
150 | ///
151 | /// Name of the Empty object that will hold the terrain objects
152 | /// Center of the planet.
153 | /// Seed for the planet.
154 | /// True if the seed need to be change by a random new one.
155 | /// Style of the terrain.
156 | /// Level of the sea.
157 | /// Noise to generate the terrain.
158 | /// Color to add to the layers.
159 | /// Data to populate the planet.
160 | /// Material for the terrain.
161 | /// Material for the sea.
162 | /// Radius of the planet.
163 | /// Icosphere subdivisions.
164 | /// Chunck subdivisions.
165 | static public void AddPlanetToQueue(string name, Vector3 position, int seed, bool randomSeed, Style terrainStyle, float seaLevel, GenerationData[] generationData, ColorHeight[] colorHeight, PopulateData[] population, Material material, Material seaMaterial, float radius, int subdivisions, int chunckSubdivisions)
166 | {
167 | // Instantiate a gameobject that hold planet.
168 | GameObject obj = new GameObject();
169 | obj.name = name;
170 | obj.transform.position = position;
171 | Planet planet = obj.AddComponent();
172 | planet.planet = planet;
173 | int new_seed = randomSeed == true ? UnityEngine.Random.Range(0, int.MaxValue) : seed;
174 | planet.InstantiatePlanetVariables(new_seed, terrainStyle, seaLevel, generationData, colorHeight, population, material, seaMaterial, radius, subdivisions, chunckSubdivisions);
175 | planet.position = position;
176 |
177 | // Add the planet to the list of planets.
178 | planetList.Add(planet);
179 | }
180 | ///
181 | /// Start the Data computations.
182 | ///
183 | static public void StartDataQueue()
184 | {
185 | // Prevent to overload of threads. Only
186 | // one will be running at a time.
187 | if (isThreadBeenStarted)
188 | return;
189 |
190 | // Start the thread.
191 | Thread thread = new Thread(DataThreadLoop);
192 | thread.Start();
193 | isThreadBeenStarted = true;
194 | }
195 | ///
196 | /// Generate the terrain while added to the queue.
197 | ///
198 | static public void InstantiateIntoWorld()
199 | {
200 | terrainInstantiated = true;
201 | // Instantiate the polygons into the scene while been added to the queue.
202 | while (polygonsToInstantiate.Count != 0)
203 | {
204 | Polygon poly = polygonsToInstantiate.Dequeue();
205 | poly.Instantiate();
206 | // Add it to the list for later modification.
207 | distanceList.Add(poly);
208 | }
209 | }
210 | ///
211 | /// Hide or show the polygons objects
212 | /// if they are in range of the viewer.
213 | ///
214 | /// Position tto check if the polygon is in range.
215 | static public void HideAndShow(Vector3 viewer)
216 | {
217 | // Wait to the secondary thread to end for avoid errors.
218 | if (!isThreadDone)
219 | return;
220 |
221 | // if distanceList is empty fill it.
222 | if(!terrainInstantiated)
223 | while (polygonsToInstantiate.Count != 0)
224 | distanceList.Add(polygonsToInstantiate.Dequeue());
225 |
226 | foreach (var item in distanceList)
227 | {
228 | // if polygon is in range to the viewer instantiate.
229 | if(PolyInRange(item, viewer))
230 | item.Show();
231 | // else hide it.
232 | else
233 | item.Hide();
234 | }
235 | }
236 | ///
237 | /// Set the vairables to the values.
238 | ///
239 | /// Seed for the planet.
240 | /// True if the seed need to be change by a random new one.
241 | /// Style of the terrain.
242 | /// Level of the sea.
243 | /// Noise to generate the terrain.
244 | /// Color to add to the layers.
245 | /// Data to populate the planet.
246 | /// Material for the terrain.
247 | /// Material for the sea.
248 | /// Radius of the planet.
249 | /// Icosphere subdivisions.
250 | /// Chunck subdivisions.
251 | private void InstantiatePlanetVariables(int seed, Style terrainStyle, float seaLevel, GenerationData[] generationData, ColorHeight[] colorHeight, PopulateData[] population, Material material, Material seaMaterial, float radius, int subdivisions, int chunckSubdivisions)
252 | {
253 | this.seed = seed;
254 | this.terrainStyle = terrainStyle;
255 | this.radius = radius;
256 | this.generationData = generationData;
257 | this.colorHeight = colorHeight;
258 | this.population = population;
259 | PopulateData.SetSeed(seed, population);
260 | this.subdivisions = subdivisions;
261 | this.seaLevel = seaLevel;
262 | this.material = material;
263 | this.seaMaterial = seaMaterial;
264 | this.chunckSubdivisions = chunckSubdivisions;
265 | }
266 | ///
267 | /// Generate the basic Icosphere structure.
268 | ///
269 | private void InitAsIcosahedron()
270 | {
271 | polygons = new List();
272 | vertices = new List();
273 |
274 | // An icosahedron has 12 vertices, and
275 | // since it's completely symmetrical the
276 | // formula for calculating them is kind of
277 | // symmetrical too:
278 |
279 | float t = (1.0f + Mathf.Sqrt(5.0f)) / 2.0f;
280 |
281 | vertices.Add(position + new Vector3(-1, t, 0).normalized * this.radius);
282 | vertices.Add(position + new Vector3(1, t, 0).normalized * this.radius);
283 | vertices.Add(position + new Vector3(-1, -t, 0).normalized * this.radius);
284 | vertices.Add(position + new Vector3(1, -t, 0).normalized * this.radius);
285 | vertices.Add(position + new Vector3(0, -1, t).normalized * this.radius);
286 | vertices.Add(position + new Vector3(0, 1, t).normalized * this.radius);
287 | vertices.Add(position + new Vector3(0, -1, -t).normalized * this.radius);
288 | vertices.Add(position + new Vector3(0, 1, -t).normalized * this.radius);
289 | vertices.Add(position + new Vector3(t, 0, -1).normalized * this.radius);
290 | vertices.Add(position + new Vector3(t, 0, 1).normalized * this.radius);
291 | vertices.Add(position + new Vector3(-t, 0, -1).normalized * this.radius);
292 | vertices.Add(position + new Vector3(-t, 0, 1).normalized * this.radius);
293 |
294 | // And here's the formula for the 20 sides,
295 | // referencing the 12 vertices we just created.
296 | polygons.Add(new Polygon(0, 11, 5, ref planet));
297 | polygons.Add(new Polygon(0, 5, 1, ref planet));
298 | polygons.Add(new Polygon(0, 1, 7, ref planet));
299 | polygons.Add(new Polygon(0, 7, 10, ref planet));
300 | polygons.Add(new Polygon(0, 10, 11, ref planet));
301 | polygons.Add(new Polygon(1, 5, 9, ref planet));
302 | polygons.Add(new Polygon(5, 11, 4, ref planet));
303 | polygons.Add(new Polygon(11, 10, 2, ref planet));
304 | polygons.Add(new Polygon(10, 7, 6, ref planet));
305 | polygons.Add(new Polygon(7, 1, 8, ref planet));
306 | polygons.Add(new Polygon(3, 9, 4, ref planet));
307 | polygons.Add(new Polygon(3, 4, 2, ref planet));
308 | polygons.Add(new Polygon(3, 2, 6, ref planet));
309 | polygons.Add(new Polygon(3, 6, 8, ref planet));
310 | polygons.Add(new Polygon(3, 8, 9, ref planet));
311 | polygons.Add(new Polygon(4, 9, 5, ref planet));
312 | polygons.Add(new Polygon(2, 4, 11, ref planet));
313 | polygons.Add(new Polygon(6, 2, 10, ref planet));
314 | polygons.Add(new Polygon(8, 6, 7, ref planet));
315 | polygons.Add(new Polygon(9, 8, 1, ref planet));
316 | }
317 | ///
318 | /// Subdivide the Icosphere by 'subdivisions' times.
319 | ///
320 | private void Subdivide()
321 | {
322 | var mid_point_cache = new Dictionary();
323 |
324 | for (int i = 0; i < subdivisions; i++)
325 | {
326 | var new_polys = new List();
327 | foreach (var poly in polygons)
328 | {
329 | List t_vertices = poly.GetVertices();
330 | int a = t_vertices[0];
331 | int b = t_vertices[1];
332 | int c = t_vertices[2];
333 | // Use GetMidPointIndex to either create a
334 | // new vertex between two old vertices, or
335 | // find the one that was already created.
336 | int ab = GetMidPointIndex(mid_point_cache, a, b);
337 | int bc = GetMidPointIndex(mid_point_cache, b, c);
338 | int ca = GetMidPointIndex(mid_point_cache, c, a);
339 | // Create the four new polygons using our original
340 | // three vertices, and the three new midpoints.
341 | new_polys.Add(new Polygon(a, ab, ca, ref planet));
342 | new_polys.Add(new Polygon(b, bc, ab, ref planet));
343 | new_polys.Add(new Polygon(c, ca, bc, ref planet));
344 | new_polys.Add(new Polygon(ab, bc, ca, ref planet));
345 | }
346 | // Replace all our old polygons with the new set of
347 | // subdivided ones.
348 | polygons = new_polys;
349 | }
350 | }
351 | ///
352 | /// Get the Middle point.
353 | ///
354 | private int GetMidPointIndex(Dictionary cache, int indexA, int indexB)
355 | {
356 | // We create a key out of the two original indices
357 | // by storing the smaller index in the upper two bytes
358 | // of an integer, and the larger index in the lower two
359 | // bytes. By sorting them according to whichever is smaller
360 | // we ensure that this function returns the same result
361 | // whether you call
362 | // GetMidPointIndex(cache, 5, 9)
363 | // or...
364 | // GetMidPointIndex(cache, 9, 5)
365 | int smaller_index = Mathf.Min(indexA, indexB);
366 | int greater_index = Mathf.Max(indexA, indexB);
367 | int key = (smaller_index << 16) + greater_index;
368 | // If a midpoint is already defined, just return it.
369 | int ret;
370 | if (cache.TryGetValue(key, out ret))
371 | return ret;
372 | // If we're here, it's because a midpoint for these two
373 | // vertices hasn't been created yet. Let's do that now!
374 | Vector3 p1 = vertices[indexA];
375 | Vector3 p2 = vertices[indexB];
376 | Vector3 middle = (Vector3.Lerp(p1, p2, 0.5f) - position).normalized * radius + position;
377 |
378 | ret = vertices.Count;
379 | vertices.Add(middle);
380 |
381 | cache.Add(key, ret);
382 | return ret;
383 | }
384 | ///
385 | /// Calculate the offset for the noise using the seed.
386 | ///
387 | private void CalculateOffset()
388 | {
389 | // Generate a random number generator to
390 | // always have the same random for the same seed.
391 | System.Random rng = new System.Random(seed);
392 |
393 | offset = new Vector3[generationData.Length];
394 |
395 | // Calculate the offset of the generation noise.
396 | for (int i = 0; i < offset.Length; i++)
397 | {
398 | offset[i].x = rng.Next(-10000, 10000);
399 | offset[i].y = rng.Next(-10000, 10000);
400 | offset[i].z = rng.Next(-10000, 10000);
401 | }
402 | }
403 | ///
404 | /// If the game try to quit.
405 | ///
406 | private void OnApplicationQuit()
407 | {
408 | // If the background thread is still running.
409 | // quit will make it stop.
410 | gameQuiting = true;
411 | }
412 |
413 | ///
414 | /// Get the style.
415 | ///
416 | /// Return the style.
417 | public Style GetStyle()
418 | {
419 | return terrainStyle;
420 | }
421 | ///
422 | /// Get the polygons list.
423 | ///
424 | /// Return the polygon list.
425 | public List GetPolygons()
426 | {
427 | return polygons;
428 | }
429 | ///
430 | /// Get the Color height array.
431 | ///
432 | /// Return the colorHeight array.
433 | public ColorHeight[] GetColorHeight()
434 | {
435 | return colorHeight;
436 | }
437 | ///
438 | /// Get the vertex list.
439 | ///
440 | /// Return the vertex list.
441 | public List GetVertices()
442 | {
443 | return vertices;
444 | }
445 | ///
446 | /// Get the sea level.
447 | ///
448 | /// Return the sea level.
449 | public float GetSeaLevel()
450 | {
451 | return seaLevel;
452 | }
453 | ///
454 | /// Get the populationData array.
455 | ///
456 | /// Return the populationData array.
457 | public PopulateData[] GetPopulation()
458 | {
459 | return population;
460 | }
461 | ///
462 | /// Get the planet center.
463 | ///
464 | /// Return the planet center.
465 | public Vector3 GetPosition()
466 | {
467 | return position;
468 | }
469 | ///
470 | /// Get material.
471 | ///
472 | /// Return the terrain material.
473 | public Material GetMaterial()
474 | {
475 | return material;
476 | }
477 | ///
478 | /// Get the sea material.
479 | ///
480 | /// Return the material for the sea.
481 | public Material GetSeaMaterial()
482 | {
483 | return seaMaterial;
484 | }
485 | ///
486 | /// Get seed.
487 | ///
488 | /// Return the seed.
489 | public int GetSeed()
490 | {
491 | return seed;
492 | }
493 | ///
494 | /// Get the chunck subdivisions.
495 | ///
496 | /// Return the chunck subdivision number.
497 | public int GetChunkSubdivisions()
498 | {
499 | return chunckSubdivisions;
500 | }
501 | ///
502 | /// Calculate the height at a specific position.
503 | ///
504 | /// Position where we want tto get the height.
505 | /// Return the height.
506 | public float CalculateHeightAtPosition(Vector3 position)
507 | {
508 | float height = 0;
509 |
510 | for (int i = 0; i < generationData.Length; i++)
511 | height += generationData[i].CalculateNoise(position + offset[i]);
512 |
513 | return height;
514 | }
515 | }
516 | }
517 |
--------------------------------------------------------------------------------
/Scripts/Polygon.cs:
--------------------------------------------------------------------------------
1 | /**
2 | Polygon.cs
3 | Purpose: Generate Polygons into the scene.
4 | Require: Planet.cs.
5 |
6 | @author Mikel Jauregui
7 | @version 1.1.0 14/07/2018
8 | */
9 |
10 | using System.Collections.Generic;
11 | using UnityEngine;
12 |
13 | namespace Generation
14 | {
15 | public class Polygon
16 | {
17 | // Random number generator.
18 | static private System.Random rng = new System.Random(0);
19 |
20 | // Planet data.
21 | private Planet planet;
22 | // Object that hold the terrain.
23 | private GameObject face;
24 | // Object that hold the sea.
25 | private GameObject seaFace;
26 | // Data for generate the mesh.
27 | private MeshData meshData;
28 | // Data for generate the sea mesh.
29 | private MeshData seaMeshData;
30 | // Index of the vertices of the chunk.
31 | private List vertices;
32 | // Positions of the populations that need to be instantiated.
33 | private List[] pop;
34 | // Terrain has been instantiated.
35 | private bool instantiate = false;
36 | // Face is active.
37 | private bool showing = false;
38 |
39 | ///
40 | /// Get the middle points and add them to the list.
41 | ///
42 | /// List where we add the points.
43 | /// Point a.
44 | /// Point b.
45 | /// Amount of subdivisions.
46 | static private void ListMiddlePoints(List list, Vector3 a, Vector3 b, int sub)
47 | {
48 | Vector3 new_vector = new Vector3();
49 |
50 | new_vector.x = (b.x - a.x) / sub;
51 | new_vector.y = (b.y - a.y) / sub;
52 | new_vector.z = (b.z - a.z) / sub;
53 |
54 | for (int i = 1; i < sub; i++)
55 | list.Add(a + new_vector * i);
56 | }
57 | ///
58 | /// Organize the three vectors by distance to
59 | ///
60 | private void Organize(ref Vector3 a, ref Vector3 b, ref Vector3 c)
61 | {
62 | Vector3 v1, v2, v3;
63 | float h1, h2, h3;
64 |
65 | // Get distance from center of the planet.
66 | h1 = Vector3.Magnitude(a);
67 | h2 = Vector3.Magnitude(b);
68 | h3 = Vector3.Magnitude(c);
69 |
70 | // Sort.
71 | if (h1 > h2)
72 | {
73 | if (h1 > h3)
74 | {
75 | v1 = a;
76 | if (h2 > h3)
77 | {
78 | v2 = b;
79 | v3 = c;
80 | }
81 | else
82 | {
83 | v2 = c;
84 | v3 = b;
85 | }
86 | }
87 | else
88 | {
89 | v1 = c;
90 | v2 = a;
91 | v3 = b;
92 | }
93 | }
94 | else
95 | {
96 | if (h2 > h3)
97 | {
98 | v1 = b;
99 | if (h1 > h3)
100 | {
101 | v2 = a;
102 | v3 = c;
103 | }
104 | else
105 | {
106 | v2 = c;
107 | v3 = a;
108 | }
109 | }
110 | else
111 | {
112 | v1 = c;
113 | v2 = b;
114 | v3 = a;
115 | }
116 | }
117 | // Set the result.
118 | a = v1;
119 | b = v2;
120 | c = v3;
121 | }
122 | ///
123 | /// From a triangle generate a stair.
124 | ///
125 | /// Triangle to use.
126 | /// Return the mesh of the stair.
127 | private MeshData TriangleToStair(Vector3[] triangle)
128 | {
129 | Vector3 middle = planet.GetPosition();
130 | MeshData mesh_data = new MeshData();
131 | Vector3 v1, v2, v3;
132 |
133 | v1 = triangle[0];
134 | v2 = triangle[1];
135 | v3 = triangle[2];
136 | Organize(ref v1, ref v2, ref v3);
137 |
138 | float h1, h2, h3;
139 | h1 = Vector3.Magnitude(v1);
140 | h2 = Vector3.Magnitude(v2);
141 | h3 = Vector3.Magnitude(v3);
142 |
143 | int h_min, h_max;
144 | h_min = Mathf.FloorToInt(Mathf.Min(h1, h2, h3));
145 | h_max = Mathf.FloorToInt(Mathf.Max(h1, h2, h3));
146 | int iv = 0;
147 | for (int h = h_min; h <= h_max; h++)
148 | {
149 | int points_above = 0;
150 | if (h3 == h2 && h3 == h1)
151 | points_above = 3;
152 | else if (h3 > h)
153 | points_above = 3;
154 | else if (h2 > h)
155 | points_above = 2;
156 | else
157 | points_above = 1;
158 |
159 | // Current Plane.
160 | Vector3 v1_c, v2_c, v3_c;
161 | v1_c = v1.normalized * h;
162 | v2_c = v2.normalized * h;
163 | v3_c = v3.normalized * h;
164 |
165 | // Previous Plane.
166 | Vector3 v1_p, v2_p, v3_p;
167 | v1_p = v1.normalized * (h - 1);
168 | v2_p = v2.normalized * (h - 1);
169 | v3_p = v3.normalized * (h - 1);
170 |
171 | Vector3 normal = Vector3.Cross(v1_p - v2_p, v3_p - v2_p).normalized;
172 | float d = -Vector3.Dot(normal, v1_p);
173 | float sign1 = d;
174 | float distance = Mathf.Abs(normal.x * v1_c.x + normal.y * v1_c.y + normal.z * v1_c.z + d);
175 |
176 | Color color = Color.white;
177 | ColorHeight[] colorHeight = planet.GetColorHeight();
178 | foreach (var item in colorHeight)
179 | {
180 | if (h > item.layer)
181 | {
182 | color = item.color;
183 | break;
184 | }
185 | }
186 |
187 | if (points_above == 3)
188 | {
189 | // Floor.
190 | mesh_data.vertices.Add(v1_c + middle);
191 | mesh_data.vertices.Add(v2_c + middle);
192 | mesh_data.vertices.Add(v3_c + middle);
193 |
194 | mesh_data.colors.Add(color);
195 | mesh_data.colors.Add(color);
196 | mesh_data.colors.Add(color);
197 |
198 | if (sign1 < 0)
199 | {
200 | // Floor.
201 | mesh_data.triangles.Add(iv);
202 | mesh_data.triangles.Add(iv + 2);
203 | mesh_data.triangles.Add(iv + 1);
204 | }
205 | else
206 | {
207 | mesh_data.triangles.Add(iv);
208 | mesh_data.triangles.Add(iv + 1);
209 | mesh_data.triangles.Add(iv + 2);
210 | }
211 | iv += 3;
212 | }
213 | else if (points_above == 2)
214 | {
215 | float t1 = (h1 - h) / (h1 - h3);
216 | Vector3 v1_v3_c = Vector3.Lerp(v1, v3, t1);
217 | Vector3 v1_v3_p = v1_v3_c + Vector3.Cross(v1_c - v2_c, v3_c - v2_c).normalized * distance * Mathf.Sign(sign1);
218 |
219 | float t2 = (h2 - h) / (h2 - h3);
220 | Vector3 v2_v3_c = Vector3.Lerp(v2, v3, t2);
221 | Vector3 v2_v3_p = v2_v3_c + Vector3.Cross(v1_c - v2_c, v3_c - v2_c).normalized * distance * Mathf.Sign(sign1);
222 |
223 | if (sign1 < 0)
224 | {
225 | // Floor.
226 | mesh_data.vertices.Add(v1_v3_c + middle);
227 | mesh_data.vertices.Add(v2_v3_c + middle);
228 | mesh_data.vertices.Add(v1_c + middle);
229 | mesh_data.vertices.Add(v2_c + middle);
230 |
231 | mesh_data.colors.Add(color);
232 | mesh_data.colors.Add(color);
233 | mesh_data.colors.Add(color);
234 | mesh_data.colors.Add(color);
235 |
236 | mesh_data.triangles.Add(iv);
237 | mesh_data.triangles.Add(iv + 1);
238 | mesh_data.triangles.Add(iv + 2);
239 |
240 | mesh_data.triangles.Add(iv + 1);
241 | mesh_data.triangles.Add(iv + 3);
242 | mesh_data.triangles.Add(iv + 2);
243 | iv += 4;
244 |
245 | // Wall.
246 | mesh_data.vertices.Add(v1_v3_c + middle);
247 | mesh_data.vertices.Add(v1_v3_p + middle);
248 | mesh_data.vertices.Add(v2_v3_c + middle);
249 | mesh_data.vertices.Add(v2_v3_p + middle);
250 |
251 | mesh_data.colors.Add(color);
252 | mesh_data.colors.Add(color);
253 | mesh_data.colors.Add(color);
254 | mesh_data.colors.Add(color);
255 |
256 | mesh_data.triangles.Add(iv);
257 | mesh_data.triangles.Add(iv + 1);
258 | mesh_data.triangles.Add(iv + 2);
259 |
260 | mesh_data.triangles.Add(iv + 1);
261 | mesh_data.triangles.Add(iv + 3);
262 | mesh_data.triangles.Add(iv + 2);
263 | iv += 4;
264 | }
265 | else
266 | {
267 | // Floor.
268 | mesh_data.vertices.Add(v1_v3_c + middle);
269 | mesh_data.vertices.Add(v2_v3_c + middle);
270 | mesh_data.vertices.Add(v1_c + middle);
271 | mesh_data.vertices.Add(v2_c + middle);
272 |
273 | mesh_data.colors.Add(color);
274 | mesh_data.colors.Add(color);
275 | mesh_data.colors.Add(color);
276 | mesh_data.colors.Add(color);
277 |
278 | mesh_data.triangles.Add(iv);
279 | mesh_data.triangles.Add(iv + 2);
280 | mesh_data.triangles.Add(iv + 1);
281 |
282 | mesh_data.triangles.Add(iv + 1);
283 | mesh_data.triangles.Add(iv + 2);
284 | mesh_data.triangles.Add(iv + 3);
285 | iv += 4;
286 |
287 | // Wall.
288 | mesh_data.vertices.Add(v1_v3_c + middle);
289 | mesh_data.vertices.Add(v1_v3_p + middle);
290 | mesh_data.vertices.Add(v2_v3_c + middle);
291 | mesh_data.vertices.Add(v2_v3_p + middle);
292 |
293 | mesh_data.colors.Add(color);
294 | mesh_data.colors.Add(color);
295 | mesh_data.colors.Add(color);
296 | mesh_data.colors.Add(color);
297 |
298 | mesh_data.triangles.Add(iv);
299 | mesh_data.triangles.Add(iv + 2);
300 | mesh_data.triangles.Add(iv + 1);
301 |
302 | mesh_data.triangles.Add(iv + 1);
303 | mesh_data.triangles.Add(iv + 2);
304 | mesh_data.triangles.Add(iv + 3);
305 | iv += 4;
306 | }
307 | }
308 | else if (points_above == 1)
309 | {
310 | float t1 = (h1 - h) / (h1 - h3);
311 | Vector3 v1_v3_c = Vector3.Lerp(v1, v3, t1);
312 | Vector3 v1_v3_p = v1_v3_c + Vector3.Cross(v1_c - v2_c, v3_c - v2_c).normalized * distance * Mathf.Sign(sign1);
313 |
314 | float t2 = (h1 - h) / (h1 - h2);
315 | Vector3 v2_v3_c = Vector3.Lerp(v1, v2, t2);
316 | Vector3 v2_v3_p = v2_v3_c + Vector3.Cross(v1_c - v2_c, v3_c - v2_c).normalized * distance * Mathf.Sign(sign1);
317 |
318 | if (sign1 < 0)
319 | {
320 | // Floor.
321 | mesh_data.vertices.Add(v1_v3_c + middle);
322 | mesh_data.vertices.Add(v2_v3_c + middle);
323 | mesh_data.vertices.Add(v1_c + middle);
324 |
325 | mesh_data.colors.Add(color);
326 | mesh_data.colors.Add(color);
327 | mesh_data.colors.Add(color);
328 |
329 | mesh_data.triangles.Add(iv);
330 | mesh_data.triangles.Add(iv + 1);
331 | mesh_data.triangles.Add(iv + 2);
332 |
333 | iv += 3;
334 |
335 | // Wall.
336 | mesh_data.vertices.Add(v1_v3_c + middle);
337 | mesh_data.vertices.Add(v1_v3_p + middle);
338 | mesh_data.vertices.Add(v2_v3_c + middle);
339 | mesh_data.vertices.Add(v2_v3_p + middle);
340 |
341 | mesh_data.colors.Add(color);
342 | mesh_data.colors.Add(color);
343 | mesh_data.colors.Add(color);
344 | mesh_data.colors.Add(color);
345 |
346 | mesh_data.triangles.Add(iv);
347 | mesh_data.triangles.Add(iv + 1);
348 | mesh_data.triangles.Add(iv + 2);
349 |
350 | mesh_data.triangles.Add(iv + 1);
351 | mesh_data.triangles.Add(iv + 3);
352 | mesh_data.triangles.Add(iv + 2);
353 | iv += 4;
354 | }
355 | else
356 | {
357 | // Floor.
358 | mesh_data.vertices.Add(v1_v3_c + middle);
359 | mesh_data.vertices.Add(v2_v3_c + middle);
360 | mesh_data.vertices.Add(v1_c + middle);
361 |
362 | mesh_data.colors.Add(color);
363 | mesh_data.colors.Add(color);
364 | mesh_data.colors.Add(color);
365 |
366 | mesh_data.triangles.Add(iv);
367 | mesh_data.triangles.Add(iv + 2);
368 | mesh_data.triangles.Add(iv + 1);
369 |
370 | iv += 3;
371 |
372 | // Wall.
373 | mesh_data.vertices.Add(v1_v3_c + middle);
374 | mesh_data.vertices.Add(v1_v3_p + middle);
375 | mesh_data.vertices.Add(v2_v3_c + middle);
376 | mesh_data.vertices.Add(v2_v3_p + middle);
377 |
378 | mesh_data.colors.Add(color);
379 | mesh_data.colors.Add(color);
380 | mesh_data.colors.Add(color);
381 | mesh_data.colors.Add(color);
382 |
383 | mesh_data.triangles.Add(iv);
384 | mesh_data.triangles.Add(iv + 2);
385 | mesh_data.triangles.Add(iv + 1);
386 |
387 | mesh_data.triangles.Add(iv + 1);
388 | mesh_data.triangles.Add(iv + 2);
389 | mesh_data.triangles.Add(iv + 3);
390 | iv += 4;
391 | }
392 | }
393 | }
394 | MeshData data = new MeshData();
395 | data.vertices = new List(triangle);
396 | int[] i = new int[3] { 0, 1, 2 };
397 | data.triangles = new List(i);
398 |
399 | return mesh_data;
400 | }
401 | ///
402 | /// Subdivide a triangle.
403 | ///
404 | /// Index of the vertices of the triangle.
405 | /// Return aa list with the points of the triangle divided.
406 | private List> Subdivide(List m_Vertices)
407 | {
408 | List> vertices = new List>();
409 | List p_Vertices = planet.GetVertices();
410 | List left = new List();
411 | List right = new List();
412 | vertices = new List>();
413 |
414 | Vector3 a = p_Vertices[m_Vertices[0]] - planet.GetPosition();
415 | Vector3 b = p_Vertices[m_Vertices[1]] - planet.GetPosition();
416 | Vector3 c = p_Vertices[m_Vertices[2]] - planet.GetPosition();
417 |
418 | // Amount of sections we need to create.
419 | int size = (int)Mathf.Pow(2, planet.GetChunkSubdivisions());
420 |
421 | // Create the sections of the left side of the triangle.
422 | left.Add(a);
423 | ListMiddlePoints(left, a, c, size);
424 | left.Add(c);
425 |
426 | // Create the sections of the right side of the triangle.
427 | right.Add(b);
428 | ListMiddlePoints(right, b, c, size);
429 | right.Add(c);
430 |
431 | // Calculate each slice points.
432 | for (int i = 0; i < size; i++)
433 | {
434 | vertices.Add(new List());
435 | vertices[i].Add(left[i]);
436 | ListMiddlePoints(vertices[i], left[i], right[i], size - i);
437 | vertices[i].Add(right[i]);
438 | }
439 |
440 | // Add the last slice.
441 | vertices.Add(new List());
442 | vertices[vertices.Count - 1].Add(c);
443 |
444 | return vertices;
445 | }
446 | ///
447 | /// If a value is in range of two others.
448 | ///
449 | /// Minimum.
450 | /// Maximum.
451 | /// Value to check.
452 | /// True if the value is in range.
453 | private bool InRange(float min, float max, float value)
454 | {
455 | return min < value && max > value;
456 | }
457 | ///
458 | /// Calculate the height at a point.
459 | ///
460 | /// Position.
461 | /// Return the height.
462 | private float getHeight(Vector3 p)
463 | {
464 | return planet.CalculateHeightAtPosition(p);
465 | }
466 |
467 | ///
468 | /// Constructor
469 | ///
470 | /// Index of a vertex.
471 | /// Index of a vertex.
472 | /// Index of a vertex.
473 | /// Parent planet.
474 | public Polygon(int a, int b, int c, ref Planet planet)
475 | {
476 | vertices = new List() { a, b, c };
477 | this.planet = planet;
478 | }
479 | ///
480 | /// Generate the data for the meshes and the population locations.
481 | ///
482 | public void GenerateMesh()
483 | {
484 | List t_vertices = new List();
485 | List t_triangles = new List();
486 | List t_colors = new List();
487 |
488 | // If the chunck don't need to be divided.
489 | if(planet.GetChunkSubdivisions() == 0)
490 | {
491 | // Only asssign the original triangle.
492 | List v = planet.GetVertices();
493 | t_vertices = new List() { v[vertices[0]], v[vertices[1]], v[vertices[2]] };
494 | t_triangles = new List() { 0,1,2 };
495 | meshData = new MeshData(t_vertices, t_triangles, t_colors);
496 | Planet.AddPolygonToQueue(this);
497 | return;
498 | }
499 |
500 | List> sub_vertices = Subdivide(vertices);
501 | List meshes = new List();
502 | int size = (int)Mathf.Pow(2, planet.GetChunkSubdivisions());
503 | int iv = 0;
504 | PopulateData[] population = planet.GetPopulation();
505 | pop = new List[population.Length];
506 | for (int i = 0; i < pop.Length; i++)
507 | pop[i] = new List();
508 |
509 | // Create the mesh data.
510 | for (int x = 0; x < size; x++)
511 | {
512 | for (int y = 0; y < size - x; y++)
513 | {
514 | if (Planet.ThreadNeedToQuit())
515 | return;
516 |
517 | Vector3 v1, v2, v3, v4 = Vector3.zero;
518 |
519 | v1 = sub_vertices[x][y];
520 | v2 = sub_vertices[x][y + 1];
521 | v3 = sub_vertices[x + 1][y];
522 |
523 | // Variables for the height of each vertex of the triangle.
524 | float h1 = 0.0f, h2 = 0.0f, h3 = 0.0f, h4 = 0.0f;
525 |
526 | h1 = getHeight(v1);
527 | h2 = getHeight(v2);
528 | h3 = getHeight(v3);
529 |
530 | v1 = v1 + (v1).normalized * h1;
531 | v2 = v2 + (v2).normalized * h2;
532 | v3 = v3 + (v3).normalized * h3;
533 |
534 | if(planet.GetStyle() == Style.LowPoly)
535 | {
536 | t_vertices.Add(v1 + planet.GetPosition());
537 | t_vertices.Add(v2 + planet.GetPosition());
538 | t_vertices.Add(v3 + planet.GetPosition());
539 |
540 | t_triangles.Add(iv + 0);
541 | t_triangles.Add(iv + 1);
542 | t_triangles.Add(iv + 2);
543 |
544 | if (h1 > 19 && h2 > 19 && h3 > 19)
545 | {
546 | t_colors.Add(new Color(0.6415094f, 0.5063629f, 0.1845852f, 1));
547 | t_colors.Add(new Color(0.6415094f, 0.5063629f, 0.1845852f, 1));
548 | t_colors.Add(new Color(0.6415094f, 0.5063629f, 0.1845852f, 1));
549 | }
550 | else if (h1 > 16 && h2 > 16 && h3 > 16)
551 | {
552 | t_colors.Add(new Color(0.4470589f, 0.6392157f, 0.145098f, 1));
553 | t_colors.Add(new Color(0.4470589f, 0.6392157f, 0.145098f, 1));
554 | t_colors.Add(new Color(0.4470589f, 0.6392157f, 0.145098f, 1));
555 | }
556 | else if (h1 > 14 && h2 > 14 && h3 > 14)
557 | {
558 | t_colors.Add(new Color(0.6196079f, 0.6431373f, 0.1568628f, 1));
559 | t_colors.Add(new Color(0.6196079f, 0.6431373f, 0.1568628f, 1));
560 | t_colors.Add(new Color(0.6196079f, 0.6431373f, 0.1568628f, 1));
561 | }
562 | else
563 | {
564 | t_colors.Add(new Color(0.9450981f, 0.8392158f, 0.4980392f, 1));
565 | t_colors.Add(new Color(0.9450981f, 0.8392158f, 0.4980392f, 1));
566 | t_colors.Add(new Color(0.9450981f, 0.8392158f, 0.4980392f, 1));
567 | }
568 |
569 | iv += 3;
570 | }
571 | else
572 | {
573 | Vector3[] triangle = new Vector3[3] { v1, v2, v3 };
574 | MeshData mesh_data = TriangleToStair(triangle);
575 |
576 | foreach (var item in mesh_data.vertices)
577 | {
578 | t_vertices.Add(item);
579 | }
580 | foreach (var item in mesh_data.triangles)
581 | {
582 | t_triangles.Add(iv + item);
583 | }
584 | foreach (var item in mesh_data.colors)
585 | {
586 | t_colors.Add(item);
587 | }
588 |
589 | iv += mesh_data.vertices.Count;
590 | }
591 |
592 | if (x != 0)
593 | {
594 | v4 = sub_vertices[x - 1][y + 1];
595 |
596 | h4 = getHeight(v4);
597 |
598 | v4 = v4 + v4.normalized * h4;
599 |
600 | if(planet.GetStyle() == Style.LowPoly)
601 | {
602 | t_vertices.Add(v1 + planet.GetPosition());
603 | t_vertices.Add(v2 + planet.GetPosition());
604 | t_vertices.Add(v4 + planet.GetPosition());
605 |
606 | t_triangles.Add(iv + 0);
607 | t_triangles.Add(iv + 2);
608 | t_triangles.Add(iv + 1);
609 |
610 | if (h1 > 19 && h2 > 19 && h4 > 19)
611 | {
612 | t_colors.Add(new Color(0.6415094f, 0.5063629f, 0.1845852f, 1));
613 | t_colors.Add(new Color(0.6415094f, 0.5063629f, 0.1845852f, 1));
614 | t_colors.Add(new Color(0.6415094f, 0.5063629f, 0.1845852f, 1));
615 | }
616 | else if (h1 > 16 && h2 > 16 && h4 > 16)
617 | {
618 | t_colors.Add(new Color(0.4470589f, 0.6392157f, 0.145098f, 1));
619 | t_colors.Add(new Color(0.4470589f, 0.6392157f, 0.145098f, 1));
620 | t_colors.Add(new Color(0.4470589f, 0.6392157f, 0.145098f, 1));
621 | }
622 | else if (h1 > 14 && h2 > 14 && h4 > 14)
623 | {
624 | t_colors.Add(new Color(0.6196079f, 0.6431373f, 0.1568628f, 1));
625 | t_colors.Add(new Color(0.6196079f, 0.6431373f, 0.1568628f, 1));
626 | t_colors.Add(new Color(0.6196079f, 0.6431373f, 0.1568628f, 1));
627 | }
628 | else
629 | {
630 | t_colors.Add(new Color(0.9450981f, 0.8392158f, 0.4980392f, 1));
631 | t_colors.Add(new Color(0.9450981f, 0.8392158f, 0.4980392f, 1));
632 | t_colors.Add(new Color(0.9450981f, 0.8392158f, 0.4980392f, 1));
633 | }
634 |
635 | iv += 3;
636 | }
637 | else
638 | {
639 | Vector3[] triangle2 = new Vector3[3] { v1, v2, v4 };
640 |
641 | MeshData mesh_data2 = TriangleToStair(triangle2);
642 |
643 | foreach (var item in mesh_data2.vertices)
644 | {
645 | t_vertices.Add(item);
646 | }
647 | foreach (var item in mesh_data2.triangles)
648 | {
649 | t_triangles.Add(iv + item);
650 | }
651 | foreach (var item in mesh_data2.colors)
652 | {
653 | t_colors.Add(item);
654 | }
655 | iv += mesh_data2.vertices.Count;
656 | }
657 | }
658 |
659 | if (Vector3.Magnitude(v1) < planet.GetSeaLevel()
660 | || Vector3.Magnitude(v2) < planet.GetSeaLevel()
661 | || Vector3.Magnitude(v3) < planet.GetSeaLevel())
662 |
663 | {
664 | // Only asssign the original triangle.
665 | List v = planet.GetVertices();
666 | List sea_t_vertices = new List() {(v[vertices[0]] - planet.GetPosition()).normalized * planet.GetSeaLevel() + planet.GetPosition(),
667 | (v[vertices[1]] - planet.GetPosition()).normalized * planet.GetSeaLevel() + planet.GetPosition(),
668 | (v[vertices[2]] - planet.GetPosition()).normalized * planet.GetSeaLevel() + planet.GetPosition()};
669 | List sea_t_triangles = new List() { 0, 1, 2 };
670 | List sea_t_colors = new List();
671 | seaMeshData = new MeshData(sea_t_vertices, sea_t_triangles, sea_t_colors);
672 | }
673 |
674 | Vector3 a = v1;
675 | Vector3 b = v2;
676 | Vector3 c = v3;
677 | Vector3 d = v4;
678 |
679 | for (int i = 0; i < pop.Length; i++)
680 | {
681 | float rand1 = (float)rng.NextDouble();
682 | float rand2 = (float)rng.NextDouble();
683 | Vector3 ac = Vector3.Lerp(a, c, rand1);
684 | Vector3 bc = Vector3.Lerp(b, c, rand1);
685 |
686 | Vector3 point = Vector3.Lerp(ac, bc, rand2);
687 | float height = Vector3.Magnitude(point);
688 | if (Style.Terrace == planet.GetStyle())
689 | {
690 | height = Mathf.Floor(height);
691 | point = point.normalized * height;
692 | }
693 | if (height >= population[i].minHeight && height <= population[i].maxHeight
694 | && population[i].GetHeightAtPosition(point) > 0
695 | && population[i].probability > (float)rng.NextDouble())
696 | {
697 | pop[i].Add(point + planet.GetPosition());
698 | }
699 |
700 | if (x != 0)
701 | {
702 | rand1 = (float)rng.NextDouble();
703 | rand2 = (float)rng.NextDouble();
704 | Vector3 ab = Vector3.Lerp(a, b, rand1);
705 | Vector3 db = Vector3.Lerp(d, b, rand1);
706 |
707 | point = Vector3.Lerp(ab, db, rand2);
708 | height = Vector3.Distance(planet.GetPosition(), point);
709 | if (Style.Terrace == planet.GetStyle())
710 | {
711 | height = Mathf.Floor(height);
712 | point = point.normalized * height;
713 | }
714 | if (height >= population[i].minHeight && height <= population[i].maxHeight
715 | && population[i].GetHeightAtPosition(point) > 0
716 | && population[i].probability > (float)rng.NextDouble())
717 | {
718 | pop[i].Add(point + planet.GetPosition());
719 | }
720 | }
721 | }
722 | }
723 | }
724 | // Assign the mesh data.
725 | meshData = new MeshData(t_vertices, t_triangles, t_colors);
726 | Planet.AddPolygonToQueue(this);
727 | }
728 | ///
729 | /// Instantiate terrain and populate it.
730 | ///
731 | public void Instantiate()
732 | {
733 | if (instantiate)
734 | return;
735 | // Instantiate the gameobject.
736 | face = new GameObject();
737 | face.name = "Terrain Chunck";
738 | face.transform.parent = planet.transform;
739 | MeshFilter mesh_filter = face.AddComponent();
740 | MeshCollider collider = face.AddComponent();
741 | MeshRenderer renderer = face.AddComponent();
742 | renderer.material = planet.GetMaterial();
743 |
744 | // Apply the mesh to the object.
745 | Mesh mesh = new Mesh();
746 | mesh.vertices = meshData.vertices.ToArray();
747 | mesh.triangles = meshData.triangles.ToArray();
748 | mesh.colors = meshData.colors.ToArray();
749 | mesh.RecalculateBounds();
750 | mesh.RecalculateNormals();
751 | mesh.RecalculateTangents();
752 | mesh_filter.mesh = mesh;
753 | collider.sharedMesh = mesh;
754 |
755 | if(seaMeshData != null)
756 | {
757 | // Instantiate the gameobject.
758 | seaFace = new GameObject();
759 | seaFace.name = "Sea";
760 | seaFace.transform.parent = face.transform;
761 | MeshFilter sea_mesh_filter = seaFace.AddComponent();
762 | MeshCollider sea_collider = seaFace.AddComponent();
763 | MeshRenderer sea_renderer = seaFace.AddComponent();
764 | sea_renderer.material = planet.GetSeaMaterial();
765 |
766 | // Apply the mesh to the object.
767 | Mesh sea_mesh = new Mesh();
768 | sea_mesh.vertices = seaMeshData.vertices.ToArray();
769 | sea_mesh.triangles = seaMeshData.triangles.ToArray();
770 | sea_mesh.colors = seaMeshData.colors.ToArray();
771 | sea_mesh.RecalculateBounds();
772 | sea_mesh.RecalculateNormals();
773 | sea_mesh.RecalculateTangents();
774 | sea_mesh_filter.mesh = sea_mesh;
775 | sea_collider.sharedMesh = sea_mesh;
776 | }
777 |
778 | PopulateData[] population = planet.GetPopulation();
779 | for (int i = 0; i < pop.Length; i++)
780 | {
781 | foreach (var pos in pop[i])
782 | {
783 | GameObject cube = GameObject.Instantiate(population[i].GetRandomModel());
784 | cube.transform.parent = face.transform;
785 | Quaternion rotation = Quaternion.FromToRotation(Vector3.up, (pos - planet.GetPosition()).normalized);
786 | cube.transform.SetPositionAndRotation(pos, rotation);
787 | }
788 | }
789 | instantiate = true;
790 | showing = true;
791 | }
792 | ///
793 | /// Show the terrain on the scene.
794 | ///
795 | public void Show()
796 | {
797 | Instantiate();
798 | face.SetActive(true);
799 | }
800 | ///
801 | /// Hide the terrain on the scene.
802 | ///
803 | public void Hide()
804 | {
805 | if (!instantiate)
806 | return;
807 |
808 | face.SetActive(false);
809 | showing = false;
810 | }
811 | public List GetVertices()
812 | {
813 | return vertices;
814 | }
815 | public Planet GetPlanet()
816 | {
817 | return planet;
818 | }
819 | public bool GetShowing()
820 | {
821 | return showing;
822 | }
823 | }
824 | public class MeshData
825 | {
826 | public List vertices;
827 | public List triangles;
828 | public List colors;
829 |
830 | public MeshData()
831 | {
832 | this.vertices = new List();
833 | this.triangles = new List();
834 | this.colors = new List();
835 | }
836 | public MeshData(List vertices, List triangles, List colors)
837 | {
838 | this.vertices = vertices;
839 | this.triangles = triangles;
840 | this.colors = colors;
841 | }
842 | }
843 | }
--------------------------------------------------------------------------------