private StarLayer GenerateSingleStarLayer(int stars)
        {
            float     cntSq          = (int)Math.Sqrt(stars);
            float     screenBoundRad = this.TileSize * 0.5f;
            StarLayer layer          = new StarLayer(stars);

            for (int i = 0; i < layer.stars.Length; i++)
            {
                layer.stars[i].pos = new Vector2(
                    -screenBoundRad + ((i % cntSq) / cntSq) * screenBoundRad * 2.0f +
                    (MathF.Rnd.NextFloat() - 0.5f) * screenBoundRad * 1.75f / cntSq,

                    -screenBoundRad + ((i / cntSq) / cntSq) * screenBoundRad * 2.0f +
                    (MathF.Rnd.NextFloat() - 0.5f) * screenBoundRad * 1.75f / cntSq);
                layer.stars[i].brightness = 0.5f + MathF.Rnd.NextFloat();
            }
            return(layer);
        }
        private StarLayer GenerateSingleStarLayer(int stars)
        {
            float cntSq = (int)Math.Sqrt(stars);
            float screenBoundRad = this.TileSize * 0.5f;
            StarLayer layer = new StarLayer(stars);
            for (int i = 0; i < layer.stars.Length; i++)
            {
                layer.stars[i].pos = new Vector2(
                    -screenBoundRad + ((i % cntSq) / cntSq) * screenBoundRad * 2.0f +
                    (MathF.Rnd.NextFloat() - 0.5f) * screenBoundRad * 1.75f / cntSq,

                    -screenBoundRad + ((i / cntSq) / cntSq) * screenBoundRad * 2.0f +
                    (MathF.Rnd.NextFloat() - 0.5f) * screenBoundRad * 1.75f / cntSq);
                layer.stars[i].brightness = 0.5f + MathF.Rnd.NextFloat();
            }
            return layer;
        }
        public override void Draw(IDrawDevice device)
        {
            if (this.camComp.DrawDevice != device)
            {
                return;
            }

            List <VertexC1P3> vertices       = new List <VertexC1P3>((2 * this.layerCount * this.starsPerLayer) * 2);
            float             screenBoundRad = this.camComp.ViewBoundingRadius;
            float             minZDist       = this.camComp.NearZ + this.camComp.ParallaxRefDist / 50.0f;

            float   stepTemp = this.TileSize;
            Vector2 minPos   = new Vector2(-screenBoundRad, -screenBoundRad);
            Vector2 maxPos   = new Vector2(screenBoundRad, screenBoundRad);

            // Iterate over star layers
            for (int layerIndex = 0; layerIndex < this.layerCount; layerIndex++)
            {
                StarLayer layer = this.starLayers[layerIndex];

                // Determine the layers Z value & perform layer culling if too near
                float layerZ = this.layerDepth * ((float)layerIndex / (float)this.layerCount) - this.GameObj.Transform.Pos.Z;
                if (layerZ < 0.0f)
                {
                    layerZ += this.layerDepth * (float)(1 + (int)(-layerZ / this.layerDepth));
                }
                layerZ = layerZ % this.layerDepth;
                if (layerZ <= this.GameObj.Transform.Pos.Z + minZDist)
                {
                    continue;
                }

                // Calculate transform data
                Vector3 posTemp        = this.GameObj.Transform.Pos + Vector3.UnitZ * layerZ;
                Vector3 posTempTrail   = this.GameObj.Transform.Pos + Vector3.UnitZ * (layerZ + this.GameObj.Transform.Vel.Z * this.trailLength);
                float   scaleTemp      = 1.0f;
                float   scaleTempTrail = 1.0f;
                device.PreprocessCoords(this, ref posTemp, ref scaleTemp);
                device.PreprocessCoords(this, ref posTempTrail, ref scaleTempTrail);
                posTempTrail += new Vector3(this.GameObj.Transform.Vel.Xy * this.trailLength);

                // Prepare this layers transformation to calculate a stars trail position.
                Vector2 starPosTempTrailDotX;
                Vector2 starPosTempTrailDotY;
                MathF.GetTransformDotVec(this.GameObj.Transform.AngleVel * this.trailLength, scaleTempTrail, out starPosTempTrailDotX, out starPosTempTrailDotY);

                // Iterate over stars
                for (int starIndex = 0; starIndex < layer.stars.Length; starIndex++)
                {
                    // Since it's an endless starfield, each star may show up on multiple positions at once. It's tiled.
                    StarInfo star        = layer.stars[starIndex];
                    Vector2  starPosBase = star.pos - this.GameObj.Transform.Pos.Xy;

                    // Move the topleft tiling corner to somewhere near the screens top left
                    if (starPosBase.X > minPos.X)
                    {
                        starPosBase.X -= stepTemp * MathF.Ceiling((starPosBase.X - minPos.X) / stepTemp);
                    }
                    else if (starPosBase.X < minPos.X - stepTemp)
                    {
                        starPosBase.X -= stepTemp * MathF.Ceiling((starPosBase.X - minPos.X) / stepTemp);
                    }
                    if (starPosBase.Y > minPos.Y)
                    {
                        starPosBase.Y -= stepTemp * MathF.Ceiling((starPosBase.Y - minPos.Y) / stepTemp);
                    }
                    else if (starPosBase.Y < minPos.Y - stepTemp)
                    {
                        starPosBase.Y -= stepTemp * MathF.Ceiling((starPosBase.Y - minPos.Y) / stepTemp);
                    }

                    // Tiling!
                    Vector2 startPos = starPosBase;
                    while (starPosBase.X <= maxPos.X && starPosBase.Y <= maxPos.Y)
                    {
                        // Determine star pos and perform culling
                        Vector3 starPosTemp = posTemp + new Vector3(starPosBase) * scaleTemp;
                        if (starPosTemp.X > -screenBoundRad && starPosTemp.Y > -screenBoundRad &&
                            starPosTemp.X < screenBoundRad && starPosTemp.Y < screenBoundRad)
                        {
                            // Determine trail pos
                            Vector3 starPosTempTrail = posTempTrail + new Vector3(starPosBase);
                            MathF.TransformDotVec(ref starPosTempTrail, ref starPosTempTrailDotX, ref starPosTempTrailDotY);

                            // Calculate length factor for the star's alpha value. Reduce alpha if too small or too big
                            float lenTemp = (starPosTemp - starPosTempTrail).Length;
                            if (lenTemp < 1.0f)
                            {
                                lenTemp = MathF.Max(0.25f, MathF.Sqrt(lenTemp));
                            }
                            else if (lenTemp > 1.0f)
                            {
                                lenTemp = 1.0f / MathF.Pow(lenTemp, 0.25f);
                            }

                            // Determine the star's alpha value and generate its vertices (star and trail)
                            float alpha = star.brightness * this.brightness * lenTemp * (1.0f - ((layerZ - minZDist) / (this.layerDepth - minZDist)));
                            vertices.Add(new VertexC1P3(starPosTemp, ColorRgba.White.WithAlpha(alpha)));
                            vertices.Add(new VertexC1P3(starPosTempTrail, ColorRgba.White.WithAlpha(alpha * 0.5f)));
                        }

                        // Advance X / Y grid
                        starPosBase.X += stepTemp;
                        if (starPosBase.X > maxPos.X)
                        {
                            starPosBase.X  = startPos.X;
                            starPosBase.Y += stepTemp;
                        }
                    }
                }
            }

            // Draw the stars all at once. Since they're not on the same Z layer, this may lead to wronz Z sorting
            // when interacting with a complex environment that needs Z-Sorting itsself. For this application, it should be sufficient.
            device.AddVertices(new BatchInfo(DrawTechnique.Add, ColorRgba.White), BeginMode.Lines, vertices.ToArray());
            // (Can be fixed by drawing each layer in its own batch because they can be properly Z-sorted by Duality)
        }