<?xml version="1.0" encoding="windows-1251"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="https://neoaxis.bbon.ru/export.php?type=rss" rel="self" type="application/rss+xml" />
		<title>NeoAxis Engine version:3.x.x</title>
		<link>http://neoaxis.bbon.ru/</link>
		<description>NeoAxis Engine version:3.x.x</description>
		<language>ru-ru</language>
		<lastBuildDate>Sat, 04 Mar 2023 09:39:38 +0300</lastBuildDate>
		<generator>MyBB/mybb.ru</generator>
		<item>
			<title>ОЗВУЧКА</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=180#p180</link>
			<description>&lt;p&gt;А возможно ли в принципе, добавлять в игровой проект озвучку персонажей? Звуки у многих моделек сами по себе есть...Но даже в демо-картах, где есть худо-бедные, но катсцены, нет никакой озвучки.&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Рекс)</author>
			<pubDate>Sat, 04 Mar 2023 09:39:38 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=180#p180</guid>
		</item>
		<item>
			<title>Персонаж Actor</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=179#p179</link>
			<description>&lt;p&gt;Можно перезалив? :)&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Рекс)</author>
			<pubDate>Sat, 04 Mar 2023 09:29:15 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=179#p179</guid>
		</item>
		<item>
			<title>Как сделать смену музыки в игровом мире?</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=178#p178</link>
			<description>&lt;p&gt;&lt;a href=&quot;https://upforme.ru/uploads/0019/e8/4e/32/41370.png&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;postimg&quot; loading=&quot;lazy&quot; src=&quot;https://upforme.ru/uploads/0019/e8/4e/32/t41370.png&quot; alt=&quot;https://upforme.ru/uploads/0019/e8/4e/32/t41370.png&quot; /&gt;&lt;/a&gt;&lt;br /&gt;Вот уже около года, я умею добавлять музыку на карту. Нужную мелодию можно сконвертировать в .ogg и закинуть в папку, и уже на карте в настройках, выбрать нужное.&lt;br /&gt;Однако!!!!!!!!&lt;br /&gt;До сих пор, не могу разобраться как добавить на карту...Сразу несколько мелодий. И остановить зацикливание - после того как музыка проигрывается, она начинает играть снова (а я бы хотел намашанить так, чтобы после остановления одной мелодии игралась другая - ну, их было несколько)...&lt;br /&gt;Неужели выход только в том, чтобы расставлять по карте спецобъекты (забыл как они точно называются) и прикреплять к ним звук (мелодии, в моем случае)?&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Рекс)</author>
			<pubDate>Sat, 04 Mar 2023 09:25:45 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=178#p178</guid>
		</item>
		<item>
			<title>Стандартная музыка движка</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=177#p177</link>
			<description>&lt;p&gt;Всех приветствую.&lt;br /&gt;Если нужно, выкладываю отдельно стандартную музыку, которая уже есть в недрах движка.&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=j_PASnp7KRY&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;https://www.youtube.com/watch?v=j_PASnp7KRY&lt;/a&gt; -&amp;#160; Game&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Рекс)</author>
			<pubDate>Sat, 04 Mar 2023 09:10:49 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=177#p177</guid>
		</item>
		<item>
			<title>Мой форум на Unity</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=176#p176</link>
			<description>&lt;p&gt;&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Thu, 27 May 2021 19:29:33 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=176#p176</guid>
		</item>
		<item>
			<title>Нужна помощь ! помогите начать !!!</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=175#p175</link>
			<description>&lt;p&gt;Я сделаю уроки.&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Wed, 19 May 2021 17:10:16 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=175#p175</guid>
		</item>
		<item>
			<title>Спавнер + улучшенный GameCharacterAI</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=173#p173</link>
			<description>&lt;p&gt;Спавнер создает юниты на карте. &lt;br /&gt;Может быть постройкой, которую можно уничтожить. Может привязывать к себе юниты, чтобы они возвращались на точку спавна. Может тратить ресурсы на юниты, в зависимости от их стоимости, если на карте присутствует RTSFactionManager. Можно указать конкретное число юнитов, которые он должен заспавнить. Если 0, то будет создавать бесконечно.&lt;/p&gt;
						&lt;p&gt;Новый ИИ может патрулировать, используя класс UnitPoint. В радиусе 1000 обнаруживает все сущности UnitPoint и добавляет их себе в очередь задач. Новый ИИ имеет ограниченный угол обзора (не видит спиной), а также не видит через препядствия, но может принимать сообщения о противнике от союзных юнитов и также реагирует на атаки по нему. Если видит противника, то не строит маршрут, а идет напролом (есть нерешенная проблема с движением к цели, когда он ее видит, но она на возвышенности). Также идет напролом, если по какой-то причине нет маршрута. Если цель потеряна из виду, идет в координаты, где видел цель в последний раз. Если ИИ отстал от цели, перестает ее преследовать.&lt;/p&gt;
						&lt;p&gt;&lt;a href=&quot;https://yadi.sk/d/5tDDhfkCaxHpnQ&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Скачать с Яндекс Диска файлы классов&lt;/a&gt; и закинуть в проект. Замену подтверждать.&lt;/p&gt;
						&lt;p&gt;Компилируем, когда выдаст ошибку класс PathfindingDemoGameWindow, просто удалите его.&lt;br /&gt;&lt;a href=&quot;http://upforme.ru/uploads/0019/e8/4e/26/63969.png&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;postimg&quot; loading=&quot;lazy&quot; src=&quot;http://upforme.ru/uploads/0019/e8/4e/26/t63969.png&quot; alt=&quot;http://upforme.ru/uploads/0019/e8/4e/26/t63969.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
						&lt;p&gt;Снова запустите компиляцию, ошибку, выданную классом GameEngineApp устраните, удалением строк об отсутствующем классе PathfindingDemoGameWindow&lt;br /&gt;&lt;a href=&quot;http://upforme.ru/uploads/0019/e8/4e/26/474007.png&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;postimg&quot; loading=&quot;lazy&quot; src=&quot;http://upforme.ru/uploads/0019/e8/4e/26/t474007.png&quot; alt=&quot;http://upforme.ru/uploads/0019/e8/4e/26/t474007.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
						&lt;p&gt;Теперь уже компилируем окончательно.&lt;/p&gt;
						&lt;p&gt;В редакторе ресурсов создайте новые типы: Spawner и UnitPoint. Далее их можно размещать на карте.&lt;br /&gt;Напишите, если не работает, мог что-то забыть&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Gedifilyte)</author>
			<pubDate>Sun, 30 Aug 2020 18:05:42 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=173#p173</guid>
		</item>
		<item>
			<title>Код вертолета</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=172#p172</link>
			<description>&lt;p&gt;Хоть этот код не совершенен, вертолет неплохо летает, наклоняется и даже стреляет.&lt;br /&gt;Важные ньюансы:&lt;br /&gt;Для вертолета нужно создать физическую модель в которой будут четыре тела с названиями: helicopter (основное тело в которое пойдут меши корпуса вертолета), stabilizator (невидимое тело, которое должно располагаться над вертолетом, под ним будет висеть вертолет на специальном сочленении, у самого тела следует выключить параметр &amp;quot;Контактировать&amp;quot;), mainPropeller (тело с мешем основного винта), backPropeller (тело с мешем заднего винта). Также нужно три сочленения: два HingeJoint с именами suspensionJoint, соединяющий тело helicopter с телом mainPropeller и rotationJoint, соединяющий тело backPropeller с телом helicopter. Третье сочленение - UniversalJoint с именем stabilizeJoint соединяет тело stabilizator с телом helicopter. У этого сочленения должны быть включены ограничители на обеих осях. Направление одной оси - X, другой - Y. А также значения ограничителей должны стоять на 0. Еще нужны два мотора GearedMotor. Один с именем suspensionMotor должен крепиться на сочленении suspensionJoint, второй мотор с именем rotationMotor должен крепиться на сочленении rotationJoint.&lt;/p&gt;
						&lt;p&gt;Образец вертолета &lt;a href=&quot;https://yadi.sk/d/75JpL1m0I_Q88g&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;прикрепляю&lt;/a&gt;&lt;/p&gt;
						&lt;p&gt;А теперь по коду. Это код класса Helicopter. Создать новый файл Helicopter.cs нужно в папке ProjectEntities&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 35em&quot;&gt;&lt;pre&gt;using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Drawing.Design;
using Engine;
using Engine.EntitySystem;
using Engine.MapSystem;
using Engine.MathEx;
using Engine.PhysicsSystem;
using Engine.Renderer;
using Engine.SoundSystem;
using Engine.Utils;
using ProjectCommon;
using System.IO;
using Engine.FileSystem;

namespace ProjectEntities
{
    public class HelicopterType : UnitType
    {
        [FieldSerialize]
        private float maxSpeedUp = 40f;

        [FieldSerialize]
        private float speedRotations = 1f;

        [FieldSerialize]
        private float maxSpeedMove = 80f;

        [FieldSerialize]
        private float maxForce = 85000f;

        [FieldSerialize]
        private float force = 50000f;

        [FieldSerialize]
        Range optimalAttackDistanceRange;

        [FieldSerialize]
        Degree towerTurnSpeed = 90;

        [FieldSerialize]
        string soundOn;

        [FieldSerialize]
        string soundOff;

        [FieldSerialize]
        string soundGearUp;

        [FieldSerialize]
        string soundGearDown;

        [FieldSerialize]
        Range gunRotationAngleRange = new Range(-80, 20);

        [FieldSerialize]
        List&amp;lt;Gear&amp;gt; gears = new List&amp;lt;Gear&amp;gt;();

        ///////////////////////////////////////////

        public class Gear
        {
            [FieldSerialize]
            int number;

            [FieldSerialize]
            Range speedRange;

            [FieldSerialize]
            string soundMotor;

            [FieldSerialize]
            [DefaultValue(typeof(Range), &amp;quot;1 1.2&amp;quot;)]
            Range soundMotorPitchRange = new Range(1, 1.2f);

            //

            [DefaultValue(0)]
            public int Number
            {
                get { return number; }
                set { number = value; }
            }

            [DefaultValue(typeof(Range), &amp;quot;0 0&amp;quot;)]
            public Range SpeedRange
            {
                get { return speedRange; }
                set { speedRange = value; }
            }

            [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
            public string SoundMotor
            {
                get { return soundMotor; }
                set { soundMotor = value; }
            }

            [DefaultValue(typeof(Range), &amp;quot;1 1.2&amp;quot;)]
            public Range SoundMotorPitchRange
            {
                get { return soundMotorPitchRange; }
                set { soundMotorPitchRange = value; }
            }

            public override string ToString()
            {
                return string.Format(&amp;quot;Gear {0}&amp;quot;, number);
            }
        }

        ///////////////////////////////////////////

        public float MaxSpeedUp
        {
            get { return maxSpeedUp; }
            set { maxSpeedUp = value; }
        }

        public float SpeedRotations
        {
            get { return speedRotations; }
            set { speedRotations = value; }
        }

        public float MaxSpeedMove
        {
            get { return maxSpeedMove; }
            set { maxSpeedMove = value; }
        }

        public float MaxForce
        {
            get { return maxForce; }
            set { maxForce = value; }
        }

        public float Force
        {
            get { return force; }
            set { force = value; }
        }

        [Description(&amp;quot;In degrees.&amp;quot;)]
        [DefaultValue(typeof(Range), &amp;quot;-80 20&amp;quot;)]
        public Range GunRotationAngleRange
        {
            get { return gunRotationAngleRange; }
            set { gunRotationAngleRange = value; }
        }

        [DefaultValue(typeof(Range), &amp;quot;0 0&amp;quot;)]
        public Range OptimalAttackDistanceRange
        {
            get { return optimalAttackDistanceRange; }
            set { optimalAttackDistanceRange = value; }
        }

        [Description(&amp;quot;Degrees per second.&amp;quot;)]
        [DefaultValue(typeof(Degree), &amp;quot;90&amp;quot;)]
        public Degree TowerTurnSpeed
        {
            get { return towerTurnSpeed; }
            set { towerTurnSpeed = value; }
        }

        [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
        public string SoundOn
        {
            get { return soundOn; }
            set { soundOn = value; }
        }

        [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
        public string SoundOff
        {
            get { return soundOff; }
            set { soundOff = value; }
        }

        public List&amp;lt;Gear&amp;gt; Gears
        {
            get { return gears; }
        }

        [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
        public string SoundGearUp
        {
            get { return soundGearUp; }
            set { soundGearUp = value; }
        }

        [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
        public string SoundGearDown
        {
            get { return soundGearDown; }
            set { soundGearDown = value; }
        }
    }

    public class Helicopter : Unit
    {
        private HelicopterType _type = null;
        public new HelicopterType Type
        {
            get { return _type; }
        }

        ///////////////////////////////////////////
        class Track
        {
            public float speed;
            public float server_sentSpeed;
        }
        ///////////////////////////////////////////

        Body helicopterBody; //Это тело самого вертолета
        Body stabilizatorBody; //Это тело стабилизатора. Небольшой куб, который надо расположить в редакторе физики над        
        //вертолетом, затем соединить его с телом вертолета при помощи сочленения UniversalJoint. Стабилизатор будет выравнивать
        //тело вертолета, когда оно будет наклоняться. Тело стабилизатора должно быть над центром вертолета, чтобы тело вертолета
        //висело ровно и не наклонялось само.
        float Mass, suspensionForce;
        GearedMotor suspensionMotor, rotationMotor;
        HingeJoint suspensionJoint, rotationJoint;
        UniversalJoint stabilizeJoint;
        bool isWorking = false;
        bool firstTick = true;

        float RL = 0;
        float FB1 = 0;
        float FB2 = 0;
        float RwLw1 = 0;
        float RwLw2 = 0;

        Body towerBody;
        Vec3 towerBodyLocalPosition;

        MapObjectAttachedMapObject mainGunAttachedObject;
        Gun mainGun;
        Vec3 mainGunOffsetPosition;

        SphereDir towerLocalDirection;
        SphereDir needTowerLocalDirection;
        SphereDir server_sentTowerLocalDirection;

        bool motorOn;
        string currentMotorSoundName;
        VirtualChannel motorSoundChannel;

        HelicopterType.Gear currentGear;
        Track leftTrack = new Track();
        Track rightTrack = new Track();

        protected override void OnPostCreate(bool loaded)
        {
            base.OnPostCreate(loaded);
            if (EngineApp.Instance.ApplicationType == EngineApp.ApplicationTypes.Simulation)
                GetPhysics();
            {

                if (EngineApp.Instance.ApplicationType != EngineApp.ApplicationTypes.ResourceEditor)
                {
                    if (PhysicsModel == null)
                    {
                        Log.Error(&amp;quot;Не добавлена физика вертолету. Класс Helicopter, экземпляр: &amp;quot; + Name);
                        return;
                    }

                    helicopterBody = PhysicsModel.GetBody(&amp;quot;helicopter&amp;quot;);
                    if (helicopterBody == null)
                    {
                        Log.Error(&amp;quot;Тело &#039;helicopter&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя. Класс Helicopter, экземпляр: &amp;quot; + Name);
                        return;
                    }
                    towerBody = PhysicsModel.GetBody(&amp;quot;tower&amp;quot;);
                    stabilizatorBody = PhysicsModel.GetBody(&amp;quot;stabilizator&amp;quot;);
                    if (stabilizatorBody == null)
                    {
                        Log.Error(&amp;quot;Тело &#039;stabilizator&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя. Класс Helicopter, экземпляр: &amp;quot; + Name);
                        return;
                    }
                    stabilizeJoint = PhysicsModel.GetJoint(&amp;quot;stabilizeJoint&amp;quot;) as UniversalJoint;
                    if (stabilizeJoint == null)
                    {
                        Log.Error(&amp;quot;Сочленение &#039;stabilizeJoint&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя, либо сочленение не является UniversalJoint. Класс Helicopter, экземпляр: &amp;quot; + Name);
                        return;
                    }
                }
                //mainGun
                foreach (MapObjectAttachedObject attachedObject in AttachedObjects)
                {
                    MapObjectAttachedMapObject attachedMapObject = attachedObject as MapObjectAttachedMapObject;
                    if (attachedMapObject == null)
                        continue;

                    mainGun = attachedMapObject.MapObject as Gun;
                    if (mainGun != null)
                    {
                        mainGunAttachedObject = attachedMapObject;
                        mainGunOffsetPosition = attachedMapObject.PositionOffset;
                        break;
                    }
                }
                //towerBodyLocalPosition
                if (towerBody != null)
                    towerBodyLocalPosition = PhysicsModel.ModelDeclaration.GetBody(towerBody.Name).Position;

                //initialize currentGear
                currentGear = Type.Gears.Find(delegate(HelicopterType.Gear gear)
                {
                    return gear.Number == 0;
                });

                //disable contacts between chassisBody and towerBody
                if (helicopterBody != null &amp;amp;&amp;amp; towerBody != null)
                {
                    foreach (Shape shape1 in helicopterBody.Shapes)
                    {
                        foreach (Shape shape2 in towerBody.Shapes)
                        {
                            PhysicsWorld.Instance.SetShapePairFlags(shape1, shape2,
                                ShapePairFlags.DisableContacts);
                        }
                    }
                }
            }
        }

        protected override void OnDestroy()
        {
            if (motorSoundChannel != null)
            {
                motorSoundChannel.Stop();
                motorSoundChannel = null;
            }
            base.OnDestroy();
        }

        protected override void OnRender(Camera camera)
        {
            //not very true update in the OnRender.
            //it is here because need update after all Ticks and before update attached objects.
            UpdateTowerTransform();
            base.OnRender(camera);
        }

        void TickTowerTurn()
        {
            //update direction
            if (towerLocalDirection != needTowerLocalDirection)
            {
                Radian turnSpeed = Type.TowerTurnSpeed;

                SphereDir needDirection = needTowerLocalDirection;
                SphereDir direction = towerLocalDirection;

                //update horizontal direction
                float diffHorizontalAngle = needDirection.Horizontal - direction.Horizontal;
                while (diffHorizontalAngle &amp;lt; -MathFunctions.PI)
                    diffHorizontalAngle += MathFunctions.PI * 2;
                while (diffHorizontalAngle &amp;gt; MathFunctions.PI)
                    diffHorizontalAngle -= MathFunctions.PI * 2;

                if (diffHorizontalAngle &amp;gt; 0)
                {
                    if (direction.Horizontal &amp;gt; needDirection.Horizontal)
                        direction.Horizontal -= MathFunctions.PI * 2;
                    direction.Horizontal += turnSpeed * TickDelta;
                    if (direction.Horizontal &amp;gt; needDirection.Horizontal)
                        direction.Horizontal = needDirection.Horizontal;
                }
                else
                {
                    if (direction.Horizontal &amp;lt; needDirection.Horizontal)
                        direction.Horizontal += MathFunctions.PI * 2;
                    direction.Horizontal -= turnSpeed * TickDelta;
                    if (direction.Horizontal &amp;lt; needDirection.Horizontal)
                        direction.Horizontal = needDirection.Horizontal;
                }

                //update vertical direction
                if (direction.Vertical &amp;lt; needDirection.Vertical)
                {
                    direction.Vertical += turnSpeed * TickDelta;
                    if (direction.Vertical &amp;gt; needDirection.Vertical)
                        direction.Vertical = needDirection.Vertical;
                }
                else
                {
                    direction.Vertical -= turnSpeed * TickDelta;
                    if (direction.Vertical &amp;lt; needDirection.Vertical)
                        direction.Vertical = needDirection.Vertical;
                }

                if (direction.Equals(needTowerLocalDirection, .001f))
                    towerLocalDirection = direction;

                towerLocalDirection = direction;
            }
        }

        private void GetPhysics()
        {
            //Будьте внимательны при вводе имен тел, сочленений и моторов в редакторе ресурсов при создании физики.
            //Обязательно соблюдайте регистр. По умолчанию имена принято писать с маленькой буквы
            helicopterBody = PhysicsModel.GetBody(&amp;quot;helicopter&amp;quot;);
            if (helicopterBody == null) Log.Error(&amp;quot;{0} - Тело &#039;helicopter&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя. Класс Helicopter, экземпляр: &amp;quot; + Name, this);
            stabilizatorBody = PhysicsModel.GetBody(&amp;quot;stabilizator&amp;quot;);
            if (stabilizatorBody == null) Log.Error(&amp;quot;{0} - Тело &#039;stabilizator&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя. Класс Helicopter, экземпляр: &amp;quot; + Name, this);

            rotationJoint = PhysicsModel.GetJoint(&amp;quot;rotationJoint&amp;quot;) as HingeJoint;
            rotationMotor = PhysicsModel.GetMotor(&amp;quot;rotationMotor&amp;quot;) as GearedMotor;
            suspensionJoint = PhysicsModel.GetJoint(&amp;quot;suspensionJoint&amp;quot;) as HingeJoint;
            suspensionMotor = PhysicsModel.GetMotor(&amp;quot;suspensionMotor&amp;quot;) as GearedMotor;

            if (suspensionMotor == null) Log.Error(&amp;quot;{0} - Мотор &#039;stabilizationMotor&#039; не найден. Либо он отсутствует, либо задано неправильное имя, либо мотор не является GearedMotor. Класс Helicopter, экземпляр: &amp;quot; + Name, this);
            if (suspensionJoint == null) Log.Error(&amp;quot;{0} - Сочлинение &#039;suspensionJoint&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя, либо сочленение не является HingeJoint. Класс Helicopter, экземпляр: &amp;quot; + Name, this);
            if (rotationJoint == null) Log.Error(&amp;quot;{0} - Сочлинение &#039;rotationJoint&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя, либо сочленение не является HingeJoint. Класс Helicopter, экземпляр: &amp;quot; + Name, this);
            if (rotationMotor == null) Log.Error(&amp;quot;{0} - Мотор &#039;rotationMotor&#039; не найден. Либо он отсутствует, либо задано неправильное имя, либо мотор не является GearedMotor. Класс Helicopter, экземпляр: &amp;quot; + Name, this);

            foreach (Body b in PhysicsModel.Bodies) Mass += b.Mass;
            suspensionForce = (-PhysicsWorld.Instance.MainScene.Gravity.Z * Mass) + Type.Force;
        }

        private void Starting()
        {
            if (suspensionJoint.Broken) return;
            if (Intellect == null)
            {
                isWorking = false;
                suspensionMotor.Throttle = 0;
                rotationMotor.Throttle = 0;
            }
            else if (!isWorking &amp;amp;&amp;amp; suspensionMotor.Throttle &amp;lt; 1)
            {
                suspensionMotor.Throttle += (TickDelta / 7);
                rotationMotor.Throttle += (TickDelta / 7);
            }
            else
                isWorking = true;
        }

        private void MoveUpDown()
        {
            Vec3 ForceVec = new Vec3(0, 0, suspensionForce);

            if (Intellect.IsControlKeyPressed(GameControlKeys.Jump) &amp;amp;&amp;amp; GetSpeed().Z &amp;lt; Type.MaxSpeedUp)
                stabilizatorBody.AddForce(ForceType.GlobalAtLocalPos, TickDelta, ForceVec, Vec3.Zero);
            else if (Intellect.IsControlKeyPressed(GameControlKeys.Run) &amp;amp;&amp;amp; GetSpeed().Z &amp;gt; -Type.MaxSpeedUp)
                stabilizatorBody.AddForce(ForceType.GlobalAtLocalPos, TickDelta, -ForceVec, Vec3.Zero);
        }

        private void MoveForwardBackward()
        {
            if (Intellect.IsControlKeyPressed(GameControlKeys.Forward))
            {
                if (GetSpeed().X &amp;lt; Type.MaxSpeedMove)
                    stabilizatorBody.AddForce(ForceType.LocalAtLocalPos, TickDelta, new Vec3(Type.Force, 0, 0), Vec3.Zero);
            }
            else if (Intellect.IsControlKeyPressed(GameControlKeys.Backward))
            {
                if (GetSpeed().X &amp;lt; Type.MaxSpeedMove)
                    stabilizatorBody.AddForce(ForceType.LocalAtLocalPos, TickDelta, new Vec3(-Type.Force, 0, 0), Vec3.Zero);
            }
        }

        private void MoveLeftRight()
        {
            if (Intellect.IsControlKeyPressed(GameControlKeys.Rightward))
            {
                if (GetSpeed().X &amp;lt; Type.MaxSpeedMove)
                    stabilizatorBody.AddForce(ForceType.LocalAtLocalPos, TickDelta, new Vec3(0, -Type.Force, 0), Vec3.Zero);
            }
            else if (Intellect.IsControlKeyPressed(GameControlKeys.Leftward))
                if (GetSpeed().X &amp;lt; Type.MaxSpeedMove)
                    stabilizatorBody.AddForce(ForceType.LocalAtLocalPos, TickDelta, new Vec3(0, Type.Force, 0), Vec3.Zero);
        }

        private void Rotate()
        {
            //Наклоны тела работают не совсем и не всегда корректно, нуждаются в доработке
            //Тело наклоняется посредством изменения значения ограничителя в UniversalJoint, которое по умолчанию 0
            //и не дает вертолету наклоняться
            if (rotationJoint.Broken) return;

            if (RL &amp;lt; 20)
                RL += Intellect.GetControlKeyStrength(GameControlKeys.Left); //сила, с которой тело вертолета разворачивается
            if (RL &amp;gt; -20)
                RL -= Intellect.GetControlKeyStrength(GameControlKeys.Right);

            if (FB1 &amp;lt; 30)
                FB1 += Intellect.GetControlKeyStrength(GameControlKeys.Backward) * 2;
            if (FB1 &amp;gt; 0 &amp;amp;&amp;amp; FB1 &amp;lt; 30)
                stabilizeJoint.Axis2.LimitHigh = FB1; //Наклоняет тело назад до 30 градусов
            if (FB2 &amp;gt; -30)
                FB2 -= Intellect.GetControlKeyStrength(GameControlKeys.Forward) * 2;
            if (FB2 &amp;lt; 0 &amp;amp;&amp;amp; FB2 &amp;gt; -30)
                stabilizeJoint.Axis2.LimitLow = FB2; //Наклоняет тело вперед до 30 градусов

            if (RwLw1 &amp;lt; 30)
                RwLw1 += Intellect.GetControlKeyStrength(GameControlKeys.Leftward) * 2;
            if (RwLw1 &amp;gt; 0 &amp;amp;&amp;amp; RwLw1 &amp;lt; 30)
                stabilizeJoint.Axis1.LimitHigh = RwLw1; //Наклоняет тело влево до 30 градусов
            if (RwLw2 &amp;gt; -30)
                RwLw2 -= Intellect.GetControlKeyStrength(GameControlKeys.Rightward) * 2;
            if (RwLw2 &amp;lt; 0 &amp;amp;&amp;amp; RwLw2 &amp;gt; -30)
                stabilizeJoint.Axis1.LimitLow = RwLw2; //Наклоняет тело вправо до 30 градусов

            stabilizatorBody.AngularVelocity = new Vec3(0, 0, RL * Type.SpeedRotations); //применение силы к телу для поворота
            //по оси Z. Обратите внимание, что поворачиваться по оси Z должно не тело вертолета, а тело стабилизатора, которое
            //плавно тянет за собой тело вертолета
        }

        private void Stabilization()
        {
            if (rotationJoint.Broken)
            {
                if (!suspensionJoint.Broken &amp;amp;&amp;amp; suspensionMotor.Throttle != 0)
                    stabilizatorBody.AngularVelocity = new Vec3(0, 0, 3);
            }
            else stabilizatorBody.AddForce(ForceType.GlobalAtLocalPos, TickDelta,
                                         -PhysicsWorld.Instance.MainScene.Gravity * Mass, Vec3.Zero);
            //Автоматическое выравнивание вертолета
            if (RL &amp;gt; 0) RL -= 0.1f;
            if (RL &amp;lt; 0) RL += 0.1f;
            if (FB1 &amp;gt; 0) FB1 -= 1f;
            if (FB2 &amp;lt; 0) FB2 += 1f;
            if (RwLw1 &amp;gt; 0) RwLw1 -= 1f;
            if (RwLw2 &amp;lt; 0) RwLw2 += 1f;
        }

        //not ideal true
        private Vec3 GetSpeed()
        {
            Vec3 v = Vec3.Zero;
            Vec3 linearVelocity = stabilizatorBody.LinearVelocity;
            Vec3 angularVelocity = stabilizatorBody.AngularVelocity;
            //optimization
            if (linearVelocity.Equals(Vec3.Zero, .2f) &amp;amp;&amp;amp; angularVelocity.Equals(Vec3.Zero, .2f)) return v;
            Vec3 localLinearVelocity = linearVelocity * stabilizatorBody.Rotation.GetInverse();

            v.X = localLinearVelocity.X + Math.Abs(angularVelocity.X) * 2;
            v.Y = localLinearVelocity.Y + Math.Abs(angularVelocity.Y) * 2;
            v.Z = localLinearVelocity.Z + Math.Abs(angularVelocity.Z) * 2;

            return v;
        }

        protected override void OnTick()
        {
            base.OnTick();

            bool lastMotorOn = motorOn;
            motorOn = Intellect != null &amp;amp;&amp;amp; Intellect.IsActive();

            if (motorOn != lastMotorOn)
            {
                if (motorOn)
                    SoundPlay3D(Type.SoundOn, .7f, true);
            }
            if (motorSoundChannel != null &amp;amp;&amp;amp; !motorOn)
            {
                motorSoundChannel.Pause = true;
                if (motorOn != lastMotorOn)
                {
                    if (!motorOn)
                        SoundPlay3D(Type.SoundOff, .7f, true);
                }
            }
            else if (motorSoundChannel != null &amp;amp;&amp;amp; Intellect != null &amp;amp;&amp;amp; isWorking == true)
                motorSoundChannel.Pause = false;

            TickTowerTurn();
            firstTick = false;
            Starting();

            if (!isWorking)
                return;

            Stabilization();

            if (!suspensionJoint.Broken)
            {
                MoveUpDown();
                MoveForwardBackward();
                MoveLeftRight();
                Rotate();
            }
            else
                isWorking = false;

            TickMotorSound();
            TickCurrentGear();

            if (Intellect != null)
            {
                if (Intellect.IsControlKeyPressed(GameControlKeys.Fire1))
                    if (GunsTryFire(false))
                        if (Intellect.IsControlKeyPressed(GameControlKeys.Fire2))
                            GunsTryFire(true);
            }
            {
                //send tower local direction to clients
                if (EntitySystemWorld.Instance.IsServer())
                    Server_TickSendTowerLocalDirection();

                //!!!!!should use for disabled renderer
                if (EntitySystemWorld.Instance.IsDedicatedServer())
                    UpdateTowerTransform();
            }
        }

        void Print(string Text)
        {
            //Метод для проверки работы кода.
            //Чтобы его задействовать, впишите в нужный вам метод: Print(&amp;quot;какой-то текст&amp;quot; + переменная, например Health);
            //В игре в вызванной командной строке при помощи тильды &amp;quot;~&amp;quot; будет отображаться ваш текст
            if (EngineConsole.Instance == null) return;
            EngineConsole.Instance.Print(Text);
        }

        void TickMotorSound()
        {
            string needSoundName = null;
            if (currentGear != null)
                needSoundName = currentGear.SoundMotor;

            if (needSoundName != currentMotorSoundName)
            {
                //change motor sound

                currentMotorSoundName = needSoundName;

                if (!string.IsNullOrEmpty(needSoundName))
                {
                    Sound sound = SoundWorld.Instance.SoundCreate(
                        RelativePathUtils.ConvertToFullPath(Path.GetDirectoryName(Type.FilePath), needSoundName),
                        SoundMode.Mode3D | SoundMode.Loop);

                    if (sound != null)
                    {
                        motorSoundChannel = SoundWorld.Instance.SoundPlay(
                            sound, EngineApp.Instance.DefaultSoundChannelGroup, .3f, true);
                        motorSoundChannel.Position = Position;
                        switch (Type.SoundRolloffMode)
                        {
                            case DynamicType.SoundRolloffModes.Logarithmic:
                                motorSoundChannel.SetLogarithmicRolloff(Type.SoundMinDistance, Type.SoundMaxDistance,
                                    Type.SoundRolloffLogarithmicFactor);
                                break;
                            case DynamicType.SoundRolloffModes.Linear:
                                motorSoundChannel.SetLinearRolloff(Type.SoundMinDistance, Type.SoundMaxDistance);
                                break;
                        }
                        motorSoundChannel.Pause = false;
                    }
                }
            }

            //update motor channel position and pitch
            if (motorSoundChannel != null)
            {
                Range speedRangeAbs = currentGear.SpeedRange;
                if (speedRangeAbs.Minimum &amp;lt; 0 &amp;amp;&amp;amp; speedRangeAbs.Maximum &amp;lt; 0)
                    speedRangeAbs = new Range(-speedRangeAbs.Maximum, -speedRangeAbs.Minimum);
                Range pitchRange = currentGear.SoundMotorPitchRange;

                float speedCoef = 0;
                MathFunctions.Clamp(ref speedCoef, 0, 1);

                //update channel
                motorSoundChannel.Pitch = pitchRange.Minimum + speedCoef * pitchRange.Size();
                motorSoundChannel.Position = Position;
            }
        }

        protected override void Client_OnTick()
        {
            base.Client_OnTick();

            TickCurrentGear();
            TickMotorSound();
            firstTick = false;
        }

        void TickCurrentGear()
        {
            //currently gears used only for sounds

            if (currentGear == null)
                return;

            if (motorOn)
            {
                float speed = Math.Max(leftTrack.speed, rightTrack.speed);

                HelicopterType.Gear newGear = null;

                if (speed &amp;lt; currentGear.SpeedRange.Minimum || speed &amp;gt; currentGear.SpeedRange.Maximum)
                {
                    //find new gear
                    newGear = Type.Gears.Find(delegate(HelicopterType.Gear gear)
                    {
                        return speed &amp;gt;= gear.SpeedRange.Minimum &amp;amp;&amp;amp; speed &amp;lt;= gear.SpeedRange.Maximum;
                    });
                }

                if (newGear != null &amp;amp;&amp;amp; currentGear != newGear)
                {
                    //change gear
                    HelicopterType.Gear oldGear = currentGear;
                    OnGearChange(oldGear, newGear);
                    currentGear = newGear;
                }
            }
            else
            {
                if (currentGear.Number != 0)
                {
                    currentGear = Type.Gears.Find(delegate(HelicopterType.Gear gear)
                    {
                        return gear.Number == 0;
                    });
                }
            }
        }

        void OnGearChange(HelicopterType.Gear oldGear, HelicopterType.Gear newGear)
        {
            if (!firstTick &amp;amp;&amp;amp; Health != 0)
            {
                bool up = Math.Abs(newGear.Number) &amp;gt; Math.Abs(oldGear.Number);
                string soundName = up ? Type.SoundGearUp : Type.SoundGearDown;
                SoundPlay3D(soundName, .7f, true);
            }
        }

        [Browsable(false)]
        public Gun MainGun
        {
            get { return mainGun; }
        }

        void UpdateTowerTransform()
        {
            if (towerBody == null || helicopterBody == null || mainGunAttachedObject == null)
                return;

            Radian horizontalAngle = towerLocalDirection.Horizontal;
            Radian verticalAngle = towerLocalDirection.Vertical;

            Range gunRotationRange = Type.GunRotationAngleRange * MathFunctions.PI / 180.0f;
            if (verticalAngle &amp;lt; gunRotationRange.Minimum)
                verticalAngle = gunRotationRange.Minimum;
            if (verticalAngle &amp;gt; gunRotationRange.Maximum)
                verticalAngle = gunRotationRange.Maximum;

            //update tower body
            towerBody.Position = GetInterpolatedPosition() +
                GetInterpolatedRotation() * towerBodyLocalPosition;
            towerBody.Rotation = GetInterpolatedRotation() *
                new Angles(0, 0, -horizontalAngle.InDegrees()).ToQuat();
            towerBody.Sleeping = true;

            //update gun vertical rotation
            Quat verticalRotation = new Angles(0, verticalAngle.InDegrees(), 0).ToQuat();
            mainGunAttachedObject.RotationOffset = verticalRotation;
        }

        bool GunsTryFire(bool alternative)
        {
            bool fire = false;

            foreach (MapObjectAttachedObject attachedObject in AttachedObjects)
            {
                MapObjectAttachedMapObject attachedMapObject = attachedObject as MapObjectAttachedMapObject;
                if (attachedMapObject == null)
                    continue;

                Gun gun = attachedMapObject.MapObject as Gun;

                if (gun != null)
                {
                    if (gun.TryFire(alternative))
                        fire = true;
                }
            }

            return fire;
        }

        public void SetMomentaryTurnToPosition(Vec3 pos)
        {
            if (towerBody == null)
                return;

            Vec3 direction = pos - towerBody.Position;
            towerLocalDirection = SphereDir.FromVector(Rotation.GetInverse() * direction);
            needTowerLocalDirection = towerLocalDirection;
        }

        public void SetNeedTurnToPosition(Vec3 pos)
        {
            if (towerBody == null)
                return;

            if (Type.TowerTurnSpeed != 0)
            {
                Vec3 direction = pos - towerBody.Position;
                needTowerLocalDirection = SphereDir.FromVector(Rotation.GetInverse() * direction);
            }
            else
                SetMomentaryTurnToPosition(pos);
        }

        protected override void Server_OnClientConnectedAfterPostCreate(
            RemoteEntityWorld remoteEntityWorld)
        {
            base.Server_OnClientConnectedAfterPostCreate(remoteEntityWorld);

            RemoteEntityWorld[] worlds = new RemoteEntityWorld[] { remoteEntityWorld };
            Server_SendTowerLocalDirectionToClients(worlds);
        }

        void Server_TickSendTowerLocalDirection()
        {
            float epsilon = new Degree(.5f).InRadians();
            if (!towerLocalDirection.Equals(server_sentTowerLocalDirection, epsilon))
            {
                Server_SendTowerLocalDirectionToClients(EntitySystemWorld.Instance.RemoteEntityWorlds);
                server_sentTowerLocalDirection = towerLocalDirection;
            }
        }

        void Server_SendTowerLocalDirectionToClients(IList&amp;lt;RemoteEntityWorld&amp;gt; remoteEntityWorlds)
        {
            SendDataWriter writer = BeginNetworkMessage(remoteEntityWorlds, typeof(Helicopter),
                (ushort)NetworkMessages.TowerLocalDirectionToClient);
            writer.Write(towerLocalDirection);
            EndNetworkMessage();
        }

        [NetworkReceive(NetworkDirections.ToClient, (ushort)NetworkMessages.TowerLocalDirectionToClient)]
        void Client_ReceiveTowerLocalDirection(RemoteEntityWorld sender, ReceiveDataReader reader)
        {
            SphereDir value = reader.ReadSphereDir();
            if (!reader.Complete())
                return;
            towerLocalDirection = value;
        }

        ///////////////////////////////////////////

        enum NetworkMessages
        {
            TowerLocalDirectionToClient,
            TracksSpeedToClient,
        }

        ///////////////////////////////////////////

    }
}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;Теперь нужно подправить несколько других классов&lt;br /&gt;Класс GameControlKeys. Добавить две новые клавиши. Движение в правый бок и в левый бок. Назначение клавиши Use поменять&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 15em&quot;&gt;&lt;pre&gt;[DefaultKeyboardMouseValue( EKeys.F )]
 Use,

[DefaultKeyboardMouseValue(EKeys.E)]
 Rightward,

[DefaultKeyboardMouseValue(EKeys.Q)]
 Leftward,&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;Класс PlayerIntellect. Найдите метод void ServerOrSingle_RestoreMainControlledUnit() и в нем Tank specific&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 9em&quot;&gt;&lt;pre&gt;if( ControlledObject != null )
{
       //Tank specific
       if( ControlledObject is Tank || ControlledObject is Car )&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;Добавьте в условие этого метода ControlledObject is Helicopter&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 9em&quot;&gt;&lt;pre&gt;if( ControlledObject != null )
{
 //Tank specific
 if( ControlledObject is Tank || ControlledObject is Car || ControlledObject is Helicopter )&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;В классе ActionGameWindow, найдите метод void UpdateHUD(). В нем под Tank specific добавьте строки с Helicopter&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 16.5em&quot;&gt;&lt;pre&gt;//Tank specific
Tank tank = playerUnit as Tank;
if( tank != null )
    weapon = tank.MainGun;

//Helicopter specific
Helicopter heli = playerUnit as Helicopter;
if (heli != null)
    weapon = heli.MainGun;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;То же самое напишите в этом же классе, в методе void DrawTarget( GuiRenderer renderer )&lt;/p&gt;
						&lt;p&gt;А ниже в этом методе, под строкой Tank specific&amp;#160; &amp;#160;DrawTankGunTarget(renderer) добавьте аналогичные строки про Helicopter&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 10.5em&quot;&gt;&lt;pre&gt;//Tank specific
DrawTankGunTarget( renderer );

//Helicopter specific
DrawHeliGunTarget(renderer);&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;Вроде ничего не забыл. Спрашивайте, если что-то не понятно&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Gedifilyte)</author>
			<pubDate>Tue, 12 May 2020 15:01:35 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=172#p172</guid>
		</item>
		<item>
			<title>Код вертолета</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=171#p171</link>
			<description>&lt;p&gt;Хоть этот код не совершенен, вертолет неплохо летает, наклоняется и даже стреляет.&lt;br /&gt;Важные ньюансы:&lt;br /&gt;Для вертолета нужно создать физическую модель в которой будут четыре тела с названиями: helicopter (основное тело в которое пойдут меши корпуса вертолета), stabilizator (невидимое тело, которое должно располагаться над вертолетом, под ним будет висеть вертолет на специальном сочленении, у самого тела следует выключить параметр &amp;quot;Контактировать&amp;quot;), mainPropeller (тело с мешем основного винта), backPropeller (тело с мешем заднего винта). Также нужно три сочленения: два HingeJoint с именами suspensionJoint, соединяющий тело helicopter с телом mainPropeller и rotationJoint, соединяющий тело backPropeller с телом helicopter. Третье сочленение - UniversalJoint с именем stabilizeJoint соединяет тело stabilizator с телом helicopter. У этого сочленения должны быть включены ограничители на обеих осях. Направление одной оси - X, другой - Y. А также значения ограничителей должны стоять на 0. Еще нужны два мотора GearedMotor. Один с именем suspensionMotor должен крепиться на сочленении suspensionJoint, второй мотор с именем rotationMotor должен крепиться на сочленении rotationJoint.&lt;/p&gt;
						&lt;p&gt;Образец вертолета &lt;a href=&quot;https://yadi.sk/d/75JpL1m0I_Q88g&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;прикрепляю&lt;/a&gt;&lt;/p&gt;
						&lt;p&gt;А теперь по коду. Это код класса Helicopter. Создать новый файл Helicopter.cs нужно в папке ProjectEntities&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 35em&quot;&gt;&lt;pre&gt;using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Drawing.Design;
using Engine;
using Engine.EntitySystem;
using Engine.MapSystem;
using Engine.MathEx;
using Engine.PhysicsSystem;
using Engine.Renderer;
using Engine.SoundSystem;
using Engine.Utils;
using ProjectCommon;
using System.IO;
using Engine.FileSystem;

namespace ProjectEntities
{
    public class HelicopterType : UnitType
    {
        [FieldSerialize]
        private float maxSpeedUp = 40f;

        [FieldSerialize]
        private float speedRotations = 1f;

        [FieldSerialize]
        private float maxSpeedMove = 80f;

        [FieldSerialize]
        private float maxForce = 85000f;

        [FieldSerialize]
        private float force = 50000f;

        [FieldSerialize]
        Range optimalAttackDistanceRange;

        [FieldSerialize]
        Degree towerTurnSpeed = 90;

        [FieldSerialize]
        string soundOn;

        [FieldSerialize]
        string soundOff;

        [FieldSerialize]
        string soundGearUp;

        [FieldSerialize]
        string soundGearDown;

        [FieldSerialize]
        Range gunRotationAngleRange = new Range(-80, 20);

        [FieldSerialize]
        List&amp;lt;Gear&amp;gt; gears = new List&amp;lt;Gear&amp;gt;();

        ///////////////////////////////////////////

        public class Gear
        {
            [FieldSerialize]
            int number;

            [FieldSerialize]
            Range speedRange;

            [FieldSerialize]
            string soundMotor;

            [FieldSerialize]
            [DefaultValue(typeof(Range), &amp;quot;1 1.2&amp;quot;)]
            Range soundMotorPitchRange = new Range(1, 1.2f);

            //

            [DefaultValue(0)]
            public int Number
            {
                get { return number; }
                set { number = value; }
            }

            [DefaultValue(typeof(Range), &amp;quot;0 0&amp;quot;)]
            public Range SpeedRange
            {
                get { return speedRange; }
                set { speedRange = value; }
            }

            [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
            public string SoundMotor
            {
                get { return soundMotor; }
                set { soundMotor = value; }
            }

            [DefaultValue(typeof(Range), &amp;quot;1 1.2&amp;quot;)]
            public Range SoundMotorPitchRange
            {
                get { return soundMotorPitchRange; }
                set { soundMotorPitchRange = value; }
            }

            public override string ToString()
            {
                return string.Format(&amp;quot;Gear {0}&amp;quot;, number);
            }
        }

        ///////////////////////////////////////////

        public float MaxSpeedUp
        {
            get { return maxSpeedUp; }
            set { maxSpeedUp = value; }
        }

        public float SpeedRotations
        {
            get { return speedRotations; }
            set { speedRotations = value; }
        }

        public float MaxSpeedMove
        {
            get { return maxSpeedMove; }
            set { maxSpeedMove = value; }
        }

        public float MaxForce
        {
            get { return maxForce; }
            set { maxForce = value; }
        }

        public float Force
        {
            get { return force; }
            set { force = value; }
        }

        [Description(&amp;quot;In degrees.&amp;quot;)]
        [DefaultValue(typeof(Range), &amp;quot;-80 20&amp;quot;)]
        public Range GunRotationAngleRange
        {
            get { return gunRotationAngleRange; }
            set { gunRotationAngleRange = value; }
        }

        [DefaultValue(typeof(Range), &amp;quot;0 0&amp;quot;)]
        public Range OptimalAttackDistanceRange
        {
            get { return optimalAttackDistanceRange; }
            set { optimalAttackDistanceRange = value; }
        }

        [Description(&amp;quot;Degrees per second.&amp;quot;)]
        [DefaultValue(typeof(Degree), &amp;quot;90&amp;quot;)]
        public Degree TowerTurnSpeed
        {
            get { return towerTurnSpeed; }
            set { towerTurnSpeed = value; }
        }

        [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
        public string SoundOn
        {
            get { return soundOn; }
            set { soundOn = value; }
        }

        [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
        public string SoundOff
        {
            get { return soundOff; }
            set { soundOff = value; }
        }

        public List&amp;lt;Gear&amp;gt; Gears
        {
            get { return gears; }
        }

        [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
        public string SoundGearUp
        {
            get { return soundGearUp; }
            set { soundGearUp = value; }
        }

        [Editor(typeof(EditorSoundUITypeEditor), typeof(UITypeEditor))]
        public string SoundGearDown
        {
            get { return soundGearDown; }
            set { soundGearDown = value; }
        }
    }

    public class Helicopter : Unit
    {
        private HelicopterType _type = null;
        public new HelicopterType Type
        {
            get { return _type; }
        }

        ///////////////////////////////////////////
        class Track
        {
            public float speed;
            public float server_sentSpeed;
        }
        ///////////////////////////////////////////

        Body helicopterBody; //Это тело самого вертолета
        Body stabilizatorBody; //Это тело стабилизатора. Небольшой куб, который надо расположить в редакторе физики над        
        //вертолетом, затем соединить его с телом вертолета при помощи сочленения UniversalJoint. Стабилизатор будет выравнивать
        //тело вертолета, когда оно будет наклоняться. Тело стабилизатора должно быть над центром вертолета, чтобы тело вертолета
        //висело ровно и не наклонялось само.
        float Mass, suspensionForce;
        GearedMotor suspensionMotor, rotationMotor;
        HingeJoint suspensionJoint, rotationJoint;
        UniversalJoint stabilizeJoint;
        bool isWorking = false;
        bool firstTick = true;

        float RL = 0;
        float FB1 = 0;
        float FB2 = 0;
        float RwLw1 = 0;
        float RwLw2 = 0;

        Body towerBody;
        Vec3 towerBodyLocalPosition;

        MapObjectAttachedMapObject mainGunAttachedObject;
        Gun mainGun;
        Vec3 mainGunOffsetPosition;

        SphereDir towerLocalDirection;
        SphereDir needTowerLocalDirection;
        SphereDir server_sentTowerLocalDirection;

        bool motorOn;
        string currentMotorSoundName;
        VirtualChannel motorSoundChannel;

        HelicopterType.Gear currentGear;
        Track leftTrack = new Track();
        Track rightTrack = new Track();

        protected override void OnPostCreate(bool loaded)
        {
            base.OnPostCreate(loaded);
            if (EngineApp.Instance.ApplicationType == EngineApp.ApplicationTypes.Simulation)
                GetPhysics();
            {

                if (EngineApp.Instance.ApplicationType != EngineApp.ApplicationTypes.ResourceEditor)
                {
                    if (PhysicsModel == null)
                    {
                        Log.Error(&amp;quot;Не добавлена физика вертолету. Класс Helicopter, экземпляр: &amp;quot; + Name);
                        return;
                    }

                    helicopterBody = PhysicsModel.GetBody(&amp;quot;helicopter&amp;quot;);
                    if (helicopterBody == null)
                    {
                        Log.Error(&amp;quot;Тело &#039;helicopter&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя. Класс Helicopter, экземпляр: &amp;quot; + Name);
                        return;
                    }
                    towerBody = PhysicsModel.GetBody(&amp;quot;tower&amp;quot;);
                    stabilizatorBody = PhysicsModel.GetBody(&amp;quot;stabilizator&amp;quot;);
                    if (stabilizatorBody == null)
                    {
                        Log.Error(&amp;quot;Тело &#039;stabilizator&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя. Класс Helicopter, экземпляр: &amp;quot; + Name);
                        return;
                    }
                    stabilizeJoint = PhysicsModel.GetJoint(&amp;quot;stabilizeJoint&amp;quot;) as UniversalJoint;
                    if (stabilizeJoint == null)
                    {
                        Log.Error(&amp;quot;Сочленение &#039;stabilizeJoint&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя, либо сочленение не является UniversalJoint. Класс Helicopter, экземпляр: &amp;quot; + Name);
                        return;
                    }
                }
                //mainGun
                foreach (MapObjectAttachedObject attachedObject in AttachedObjects)
                {
                    MapObjectAttachedMapObject attachedMapObject = attachedObject as MapObjectAttachedMapObject;
                    if (attachedMapObject == null)
                        continue;

                    mainGun = attachedMapObject.MapObject as Gun;
                    if (mainGun != null)
                    {
                        mainGunAttachedObject = attachedMapObject;
                        mainGunOffsetPosition = attachedMapObject.PositionOffset;
                        break;
                    }
                }
                //towerBodyLocalPosition
                if (towerBody != null)
                    towerBodyLocalPosition = PhysicsModel.ModelDeclaration.GetBody(towerBody.Name).Position;

                //initialize currentGear
                currentGear = Type.Gears.Find(delegate(HelicopterType.Gear gear)
                {
                    return gear.Number == 0;
                });

                //disable contacts between chassisBody and towerBody
                if (helicopterBody != null &amp;amp;&amp;amp; towerBody != null)
                {
                    foreach (Shape shape1 in helicopterBody.Shapes)
                    {
                        foreach (Shape shape2 in towerBody.Shapes)
                        {
                            PhysicsWorld.Instance.SetShapePairFlags(shape1, shape2,
                                ShapePairFlags.DisableContacts);
                        }
                    }
                }
            }
        }

        protected override void OnDestroy()
        {
            if (motorSoundChannel != null)
            {
                motorSoundChannel.Stop();
                motorSoundChannel = null;
            }
            base.OnDestroy();
        }

        protected override void OnRender(Camera camera)
        {
            //not very true update in the OnRender.
            //it is here because need update after all Ticks and before update attached objects.
            UpdateTowerTransform();
            base.OnRender(camera);
        }

        void TickTowerTurn()
        {
            //update direction
            if (towerLocalDirection != needTowerLocalDirection)
            {
                Radian turnSpeed = Type.TowerTurnSpeed;

                SphereDir needDirection = needTowerLocalDirection;
                SphereDir direction = towerLocalDirection;

                //update horizontal direction
                float diffHorizontalAngle = needDirection.Horizontal - direction.Horizontal;
                while (diffHorizontalAngle &amp;lt; -MathFunctions.PI)
                    diffHorizontalAngle += MathFunctions.PI * 2;
                while (diffHorizontalAngle &amp;gt; MathFunctions.PI)
                    diffHorizontalAngle -= MathFunctions.PI * 2;

                if (diffHorizontalAngle &amp;gt; 0)
                {
                    if (direction.Horizontal &amp;gt; needDirection.Horizontal)
                        direction.Horizontal -= MathFunctions.PI * 2;
                    direction.Horizontal += turnSpeed * TickDelta;
                    if (direction.Horizontal &amp;gt; needDirection.Horizontal)
                        direction.Horizontal = needDirection.Horizontal;
                }
                else
                {
                    if (direction.Horizontal &amp;lt; needDirection.Horizontal)
                        direction.Horizontal += MathFunctions.PI * 2;
                    direction.Horizontal -= turnSpeed * TickDelta;
                    if (direction.Horizontal &amp;lt; needDirection.Horizontal)
                        direction.Horizontal = needDirection.Horizontal;
                }

                //update vertical direction
                if (direction.Vertical &amp;lt; needDirection.Vertical)
                {
                    direction.Vertical += turnSpeed * TickDelta;
                    if (direction.Vertical &amp;gt; needDirection.Vertical)
                        direction.Vertical = needDirection.Vertical;
                }
                else
                {
                    direction.Vertical -= turnSpeed * TickDelta;
                    if (direction.Vertical &amp;lt; needDirection.Vertical)
                        direction.Vertical = needDirection.Vertical;
                }

                if (direction.Equals(needTowerLocalDirection, .001f))
                    towerLocalDirection = direction;

                towerLocalDirection = direction;
            }
        }

        private void GetPhysics()
        {
            //Будьте внимательны при вводе имен тел, сочленений и моторов в редакторе ресурсов при создании физики.
            //Обязательно соблюдайте регистр. По умолчанию имена принято писать с маленькой буквы
            helicopterBody = PhysicsModel.GetBody(&amp;quot;helicopter&amp;quot;);
            if (helicopterBody == null) Log.Error(&amp;quot;{0} - Тело &#039;helicopter&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя. Класс Helicopter, экземпляр: &amp;quot; + Name, this);
            stabilizatorBody = PhysicsModel.GetBody(&amp;quot;stabilizator&amp;quot;);
            if (stabilizatorBody == null) Log.Error(&amp;quot;{0} - Тело &#039;stabilizator&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя. Класс Helicopter, экземпляр: &amp;quot; + Name, this);

            rotationJoint = PhysicsModel.GetJoint(&amp;quot;rotationJoint&amp;quot;) as HingeJoint;
            rotationMotor = PhysicsModel.GetMotor(&amp;quot;rotationMotor&amp;quot;) as GearedMotor;
            suspensionJoint = PhysicsModel.GetJoint(&amp;quot;suspensionJoint&amp;quot;) as HingeJoint;
            suspensionMotor = PhysicsModel.GetMotor(&amp;quot;suspensionMotor&amp;quot;) as GearedMotor;

            if (suspensionMotor == null) Log.Error(&amp;quot;{0} - Мотор &#039;stabilizationMotor&#039; не найден. Либо он отсутствует, либо задано неправильное имя, либо мотор не является GearedMotor. Класс Helicopter, экземпляр: &amp;quot; + Name, this);
            if (suspensionJoint == null) Log.Error(&amp;quot;{0} - Сочлинение &#039;suspensionJoint&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя, либо сочленение не является HingeJoint. Класс Helicopter, экземпляр: &amp;quot; + Name, this);
            if (rotationJoint == null) Log.Error(&amp;quot;{0} - Сочлинение &#039;rotationJoint&#039; не найдено. Либо оно отсутствует, либо задано неправильное имя, либо сочленение не является HingeJoint. Класс Helicopter, экземпляр: &amp;quot; + Name, this);
            if (rotationMotor == null) Log.Error(&amp;quot;{0} - Мотор &#039;rotationMotor&#039; не найден. Либо он отсутствует, либо задано неправильное имя, либо мотор не является GearedMotor. Класс Helicopter, экземпляр: &amp;quot; + Name, this);

            foreach (Body b in PhysicsModel.Bodies) Mass += b.Mass;
            suspensionForce = (-PhysicsWorld.Instance.MainScene.Gravity.Z * Mass) + Type.Force;
        }

        private void Starting()
        {
            if (suspensionJoint.Broken) return;
            if (Intellect == null)
            {
                isWorking = false;
                suspensionMotor.Throttle = 0;
                rotationMotor.Throttle = 0;
            }
            else if (!isWorking &amp;amp;&amp;amp; suspensionMotor.Throttle &amp;lt; 1)
            {
                suspensionMotor.Throttle += (TickDelta / 7);
                rotationMotor.Throttle += (TickDelta / 7);
            }
            else
                isWorking = true;
        }

        private void MoveUpDown()
        {
            Vec3 ForceVec = new Vec3(0, 0, suspensionForce);

            if (Intellect.IsControlKeyPressed(GameControlKeys.Jump) &amp;amp;&amp;amp; GetSpeed().Z &amp;lt; Type.MaxSpeedUp)
                stabilizatorBody.AddForce(ForceType.GlobalAtLocalPos, TickDelta, ForceVec, Vec3.Zero);
            else if (Intellect.IsControlKeyPressed(GameControlKeys.Run) &amp;amp;&amp;amp; GetSpeed().Z &amp;gt; -Type.MaxSpeedUp)
                stabilizatorBody.AddForce(ForceType.GlobalAtLocalPos, TickDelta, -ForceVec, Vec3.Zero);
        }

        private void MoveForwardBackward()
        {
            if (Intellect.IsControlKeyPressed(GameControlKeys.Forward))
            {
                if (GetSpeed().X &amp;lt; Type.MaxSpeedMove)
                    stabilizatorBody.AddForce(ForceType.LocalAtLocalPos, TickDelta, new Vec3(Type.Force, 0, 0), Vec3.Zero);
            }
            else if (Intellect.IsControlKeyPressed(GameControlKeys.Backward))
            {
                if (GetSpeed().X &amp;lt; Type.MaxSpeedMove)
                    stabilizatorBody.AddForce(ForceType.LocalAtLocalPos, TickDelta, new Vec3(-Type.Force, 0, 0), Vec3.Zero);
            }
        }

        private void MoveLeftRight()
        {
            if (Intellect.IsControlKeyPressed(GameControlKeys.Rightward))
            {
                if (GetSpeed().X &amp;lt; Type.MaxSpeedMove)
                    stabilizatorBody.AddForce(ForceType.LocalAtLocalPos, TickDelta, new Vec3(0, -Type.Force, 0), Vec3.Zero);
            }
            else if (Intellect.IsControlKeyPressed(GameControlKeys.Leftward))
                if (GetSpeed().X &amp;lt; Type.MaxSpeedMove)
                    stabilizatorBody.AddForce(ForceType.LocalAtLocalPos, TickDelta, new Vec3(0, Type.Force, 0), Vec3.Zero);
        }

        private void Rotate()
        {
            //Наклоны тела работают не совсем и не всегда корректно, нуждаются в доработке
            //Тело наклоняется посредством изменения значения ограничителя в UniversalJoint, которое по умолчанию 0
            //и не дает вертолету наклоняться
            if (rotationJoint.Broken) return;

            if (RL &amp;lt; 20)
                RL += Intellect.GetControlKeyStrength(GameControlKeys.Left); //сила, с которой тело вертолета разворачивается
            if (RL &amp;gt; -20)
                RL -= Intellect.GetControlKeyStrength(GameControlKeys.Right);

            if (FB1 &amp;lt; 30)
                FB1 += Intellect.GetControlKeyStrength(GameControlKeys.Backward) * 2;
            if (FB1 &amp;gt; 0 &amp;amp;&amp;amp; FB1 &amp;lt; 30)
                stabilizeJoint.Axis2.LimitHigh = FB1; //Наклоняет тело назад до 30 градусов
            if (FB2 &amp;gt; -30)
                FB2 -= Intellect.GetControlKeyStrength(GameControlKeys.Forward) * 2;
            if (FB2 &amp;lt; 0 &amp;amp;&amp;amp; FB2 &amp;gt; -30)
                stabilizeJoint.Axis2.LimitLow = FB2; //Наклоняет тело вперед до 30 градусов

            if (RwLw1 &amp;lt; 30)
                RwLw1 += Intellect.GetControlKeyStrength(GameControlKeys.Leftward) * 2;
            if (RwLw1 &amp;gt; 0 &amp;amp;&amp;amp; RwLw1 &amp;lt; 30)
                stabilizeJoint.Axis1.LimitHigh = RwLw1; //Наклоняет тело влево до 30 градусов
            if (RwLw2 &amp;gt; -30)
                RwLw2 -= Intellect.GetControlKeyStrength(GameControlKeys.Rightward) * 2;
            if (RwLw2 &amp;lt; 0 &amp;amp;&amp;amp; RwLw2 &amp;gt; -30)
                stabilizeJoint.Axis1.LimitLow = RwLw2; //Наклоняет тело вправо до 30 градусов

            stabilizatorBody.AngularVelocity = new Vec3(0, 0, RL * Type.SpeedRotations); //применение силы к телу для поворота
            //по оси Z. Обратите внимание, что поворачиваться по оси Z должно не тело вертолета, а тело стабилизатора, которое
            //плавно тянет за собой тело вертолета
        }

        private void Stabilization()
        {
            if (rotationJoint.Broken)
            {
                if (!suspensionJoint.Broken &amp;amp;&amp;amp; suspensionMotor.Throttle != 0)
                    stabilizatorBody.AngularVelocity = new Vec3(0, 0, 3);
            }
            else stabilizatorBody.AddForce(ForceType.GlobalAtLocalPos, TickDelta,
                                         -PhysicsWorld.Instance.MainScene.Gravity * Mass, Vec3.Zero);
            //Автоматическое выравнивание вертолета
            if (RL &amp;gt; 0) RL -= 0.1f;
            if (RL &amp;lt; 0) RL += 0.1f;
            if (FB1 &amp;gt; 0) FB1 -= 1f;
            if (FB2 &amp;lt; 0) FB2 += 1f;
            if (RwLw1 &amp;gt; 0) RwLw1 -= 1f;
            if (RwLw2 &amp;lt; 0) RwLw2 += 1f;
        }

        //not ideal true
        private Vec3 GetSpeed()
        {
            Vec3 v = Vec3.Zero;
            Vec3 linearVelocity = stabilizatorBody.LinearVelocity;
            Vec3 angularVelocity = stabilizatorBody.AngularVelocity;
            //optimization
            if (linearVelocity.Equals(Vec3.Zero, .2f) &amp;amp;&amp;amp; angularVelocity.Equals(Vec3.Zero, .2f)) return v;
            Vec3 localLinearVelocity = linearVelocity * stabilizatorBody.Rotation.GetInverse();

            v.X = localLinearVelocity.X + Math.Abs(angularVelocity.X) * 2;
            v.Y = localLinearVelocity.Y + Math.Abs(angularVelocity.Y) * 2;
            v.Z = localLinearVelocity.Z + Math.Abs(angularVelocity.Z) * 2;

            return v;
        }

        protected override void OnTick()
        {
            base.OnTick();

            bool lastMotorOn = motorOn;
            motorOn = Intellect != null &amp;amp;&amp;amp; Intellect.IsActive();

            if (motorOn != lastMotorOn)
            {
                if (motorOn)
                    SoundPlay3D(Type.SoundOn, .7f, true);
            }
            if (motorSoundChannel != null &amp;amp;&amp;amp; !motorOn)
            {
                motorSoundChannel.Pause = true;
                if (motorOn != lastMotorOn)
                {
                    if (!motorOn)
                        SoundPlay3D(Type.SoundOff, .7f, true);
                }
            }
            else if (motorSoundChannel != null &amp;amp;&amp;amp; Intellect != null &amp;amp;&amp;amp; isWorking == true)
                motorSoundChannel.Pause = false;

            TickTowerTurn();
            firstTick = false;
            Starting();

            if (!isWorking)
                return;

            Stabilization();

            if (!suspensionJoint.Broken)
            {
                MoveUpDown();
                MoveForwardBackward();
                MoveLeftRight();
                Rotate();
            }
            else
                isWorking = false;

            TickMotorSound();
            TickCurrentGear();

            if (Intellect != null)
            {
                if (Intellect.IsControlKeyPressed(GameControlKeys.Fire1))
                    if (GunsTryFire(false))
                        if (Intellect.IsControlKeyPressed(GameControlKeys.Fire2))
                            GunsTryFire(true);
            }
            {
                //send tower local direction to clients
                if (EntitySystemWorld.Instance.IsServer())
                    Server_TickSendTowerLocalDirection();

                //!!!!!should use for disabled renderer
                if (EntitySystemWorld.Instance.IsDedicatedServer())
                    UpdateTowerTransform();
            }
        }

        void Print(string Text)
        {
            //Метод для проверки работы кода.
            //Чтобы его задействовать, впишите в нужный вам метод: Print(&amp;quot;какой-то текст&amp;quot; + переменная, например Health);
            //В игре в вызванной командной строке при помощи тильды &amp;quot;~&amp;quot; будет отображаться ваш текст
            if (EngineConsole.Instance == null) return;
            EngineConsole.Instance.Print(Text);
        }

        void TickMotorSound()
        {
            string needSoundName = null;
            if (currentGear != null)
                needSoundName = currentGear.SoundMotor;

            if (needSoundName != currentMotorSoundName)
            {
                //change motor sound

                currentMotorSoundName = needSoundName;

                if (!string.IsNullOrEmpty(needSoundName))
                {
                    Sound sound = SoundWorld.Instance.SoundCreate(
                        RelativePathUtils.ConvertToFullPath(Path.GetDirectoryName(Type.FilePath), needSoundName),
                        SoundMode.Mode3D | SoundMode.Loop);

                    if (sound != null)
                    {
                        motorSoundChannel = SoundWorld.Instance.SoundPlay(
                            sound, EngineApp.Instance.DefaultSoundChannelGroup, .3f, true);
                        motorSoundChannel.Position = Position;
                        switch (Type.SoundRolloffMode)
                        {
                            case DynamicType.SoundRolloffModes.Logarithmic:
                                motorSoundChannel.SetLogarithmicRolloff(Type.SoundMinDistance, Type.SoundMaxDistance,
                                    Type.SoundRolloffLogarithmicFactor);
                                break;
                            case DynamicType.SoundRolloffModes.Linear:
                                motorSoundChannel.SetLinearRolloff(Type.SoundMinDistance, Type.SoundMaxDistance);
                                break;
                        }
                        motorSoundChannel.Pause = false;
                    }
                }
            }

            //update motor channel position and pitch
            if (motorSoundChannel != null)
            {
                Range speedRangeAbs = currentGear.SpeedRange;
                if (speedRangeAbs.Minimum &amp;lt; 0 &amp;amp;&amp;amp; speedRangeAbs.Maximum &amp;lt; 0)
                    speedRangeAbs = new Range(-speedRangeAbs.Maximum, -speedRangeAbs.Minimum);
                Range pitchRange = currentGear.SoundMotorPitchRange;

                float speedCoef = 0;
                MathFunctions.Clamp(ref speedCoef, 0, 1);

                //update channel
                motorSoundChannel.Pitch = pitchRange.Minimum + speedCoef * pitchRange.Size();
                motorSoundChannel.Position = Position;
            }
        }

        protected override void Client_OnTick()
        {
            base.Client_OnTick();

            TickCurrentGear();
            TickMotorSound();
            firstTick = false;
        }

        void TickCurrentGear()
        {
            //currently gears used only for sounds

            if (currentGear == null)
                return;

            if (motorOn)
            {
                float speed = Math.Max(leftTrack.speed, rightTrack.speed);

                HelicopterType.Gear newGear = null;

                if (speed &amp;lt; currentGear.SpeedRange.Minimum || speed &amp;gt; currentGear.SpeedRange.Maximum)
                {
                    //find new gear
                    newGear = Type.Gears.Find(delegate(HelicopterType.Gear gear)
                    {
                        return speed &amp;gt;= gear.SpeedRange.Minimum &amp;amp;&amp;amp; speed &amp;lt;= gear.SpeedRange.Maximum;
                    });
                }

                if (newGear != null &amp;amp;&amp;amp; currentGear != newGear)
                {
                    //change gear
                    HelicopterType.Gear oldGear = currentGear;
                    OnGearChange(oldGear, newGear);
                    currentGear = newGear;
                }
            }
            else
            {
                if (currentGear.Number != 0)
                {
                    currentGear = Type.Gears.Find(delegate(HelicopterType.Gear gear)
                    {
                        return gear.Number == 0;
                    });
                }
            }
        }

        void OnGearChange(HelicopterType.Gear oldGear, HelicopterType.Gear newGear)
        {
            if (!firstTick &amp;amp;&amp;amp; Health != 0)
            {
                bool up = Math.Abs(newGear.Number) &amp;gt; Math.Abs(oldGear.Number);
                string soundName = up ? Type.SoundGearUp : Type.SoundGearDown;
                SoundPlay3D(soundName, .7f, true);
            }
        }

        [Browsable(false)]
        public Gun MainGun
        {
            get { return mainGun; }
        }

        void UpdateTowerTransform()
        {
            if (towerBody == null || helicopterBody == null || mainGunAttachedObject == null)
                return;

            Radian horizontalAngle = towerLocalDirection.Horizontal;
            Radian verticalAngle = towerLocalDirection.Vertical;

            Range gunRotationRange = Type.GunRotationAngleRange * MathFunctions.PI / 180.0f;
            if (verticalAngle &amp;lt; gunRotationRange.Minimum)
                verticalAngle = gunRotationRange.Minimum;
            if (verticalAngle &amp;gt; gunRotationRange.Maximum)
                verticalAngle = gunRotationRange.Maximum;

            //update tower body
            towerBody.Position = GetInterpolatedPosition() +
                GetInterpolatedRotation() * towerBodyLocalPosition;
            towerBody.Rotation = GetInterpolatedRotation() *
                new Angles(0, 0, -horizontalAngle.InDegrees()).ToQuat();
            towerBody.Sleeping = true;

            //update gun vertical rotation
            Quat verticalRotation = new Angles(0, verticalAngle.InDegrees(), 0).ToQuat();
            mainGunAttachedObject.RotationOffset = verticalRotation;
        }

        bool GunsTryFire(bool alternative)
        {
            bool fire = false;

            foreach (MapObjectAttachedObject attachedObject in AttachedObjects)
            {
                MapObjectAttachedMapObject attachedMapObject = attachedObject as MapObjectAttachedMapObject;
                if (attachedMapObject == null)
                    continue;

                Gun gun = attachedMapObject.MapObject as Gun;

                if (gun != null)
                {
                    if (gun.TryFire(alternative))
                        fire = true;
                }
            }

            return fire;
        }

        public void SetMomentaryTurnToPosition(Vec3 pos)
        {
            if (towerBody == null)
                return;

            Vec3 direction = pos - towerBody.Position;
            towerLocalDirection = SphereDir.FromVector(Rotation.GetInverse() * direction);
            needTowerLocalDirection = towerLocalDirection;
        }

        public void SetNeedTurnToPosition(Vec3 pos)
        {
            if (towerBody == null)
                return;

            if (Type.TowerTurnSpeed != 0)
            {
                Vec3 direction = pos - towerBody.Position;
                needTowerLocalDirection = SphereDir.FromVector(Rotation.GetInverse() * direction);
            }
            else
                SetMomentaryTurnToPosition(pos);
        }

        protected override void Server_OnClientConnectedAfterPostCreate(
            RemoteEntityWorld remoteEntityWorld)
        {
            base.Server_OnClientConnectedAfterPostCreate(remoteEntityWorld);

            RemoteEntityWorld[] worlds = new RemoteEntityWorld[] { remoteEntityWorld };
            Server_SendTowerLocalDirectionToClients(worlds);
        }

        void Server_TickSendTowerLocalDirection()
        {
            float epsilon = new Degree(.5f).InRadians();
            if (!towerLocalDirection.Equals(server_sentTowerLocalDirection, epsilon))
            {
                Server_SendTowerLocalDirectionToClients(EntitySystemWorld.Instance.RemoteEntityWorlds);
                server_sentTowerLocalDirection = towerLocalDirection;
            }
        }

        void Server_SendTowerLocalDirectionToClients(IList&amp;lt;RemoteEntityWorld&amp;gt; remoteEntityWorlds)
        {
            SendDataWriter writer = BeginNetworkMessage(remoteEntityWorlds, typeof(Helicopter),
                (ushort)NetworkMessages.TowerLocalDirectionToClient);
            writer.Write(towerLocalDirection);
            EndNetworkMessage();
        }

        [NetworkReceive(NetworkDirections.ToClient, (ushort)NetworkMessages.TowerLocalDirectionToClient)]
        void Client_ReceiveTowerLocalDirection(RemoteEntityWorld sender, ReceiveDataReader reader)
        {
            SphereDir value = reader.ReadSphereDir();
            if (!reader.Complete())
                return;
            towerLocalDirection = value;
        }

        ///////////////////////////////////////////

        enum NetworkMessages
        {
            TowerLocalDirectionToClient,
            TracksSpeedToClient,
        }

        ///////////////////////////////////////////

    }
}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;Теперь нужно подправить несколько других классов&lt;br /&gt;Класс GameControlKeys. Добавить две новые клавиши. Движение в правый бок и в левый бок. Назначение клавиши Use поменять&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 15em&quot;&gt;&lt;pre&gt;[DefaultKeyboardMouseValue( EKeys.F )]
 Use,

        [DefaultKeyboardMouseValue(EKeys.E)]
        Rightward,

        [DefaultKeyboardMouseValue(EKeys.Q)]
        Leftward,&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;Класс PlayerIntellect. Найдите метод void ServerOrSingle_RestoreMainControlledUnit() и в нем Tank specific&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 9em&quot;&gt;&lt;pre&gt;if( ControlledObject != null )
{
       //Tank specific
       if( ControlledObject is Tank || ControlledObject is Car )&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;Добавьте в условие этого метода ControlledObject is Helicopter&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 9em&quot;&gt;&lt;pre&gt;if( ControlledObject != null )
{
 //Tank specific
 if( ControlledObject is Tank || ControlledObject is Car || ControlledObject is Helicopter )&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;В классе ActionGameWindow, найдите метод void UpdateHUD(). В нем под Tank specific добавьте строки с Helicopter&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 16.5em&quot;&gt;&lt;pre&gt;//Tank specific
Tank tank = playerUnit as Tank;
if( tank != null )
    weapon = tank.MainGun;

//Helicopter specific
Helicopter heli = playerUnit as Helicopter;
if (heli != null)
    weapon = heli.MainGun;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;То же самое напишите в этом же классе, в методе void DrawTarget( GuiRenderer renderer )&lt;/p&gt;
						&lt;p&gt;А ниже в этом методе, под строкой Tank specific&amp;#160; &amp;#160;DrawTankGunTarget(renderer) добавьте аналогичные строки про Helicopter&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 10.5em&quot;&gt;&lt;pre&gt;//Tank specific
DrawTankGunTarget( renderer );

//Helicopter specific
DrawHeliGunTarget(renderer);&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;Вроде ничего не забыл. Спрашивайте, если что-то не понятно&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Gedifilyte)</author>
			<pubDate>Tue, 12 May 2020 14:55:01 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=171#p171</guid>
		</item>
		<item>
			<title>Импорт 3d моделей и анимации (3ds max)</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=170#p170</link>
			<description>&lt;p&gt;Спасибо. Я сам понимаю не все так гладко как хотелось бы и знаний не хватает.&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Fri, 10 Apr 2020 16:40:53 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=170#p170</guid>
		</item>
		<item>
			<title>3 вопроса про деревья и облака и воду</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=169#p169</link>
			<description>&lt;p&gt;1. В редакторе карт есть инструмент ДекоратиеОбъектМенеджер&lt;br /&gt;2. У меня есть урок по спрайтам&amp;#160; ну а если рендомно то системой частиц можно реализовать.&lt;br /&gt;3. Есть система кубемап зоне для отражений в версии 3.2 есть примеры можете посмотреть.&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Sat, 14 Mar 2020 15:21:18 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=169#p169</guid>
		</item>
		<item>
			<title>Можно ли выбрать путь установки движка?</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=167#p167</link>
			<description>&lt;p&gt;Я про последнюю.. какая там уже NeoAxis Engine 2019.4.. Щас попробую 3.5 качну. Ога, спасибо выбирается путь установки&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Mobilemedved)</author>
			<pubDate>Wed, 26 Feb 2020 12:36:29 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=167#p167</guid>
		</item>
		<item>
			<title>Бесконечное возрождение NPC</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=166#p166</link>
			<description>&lt;p&gt;Бесконечное возрождение NPC &lt;br /&gt;Класс динамический ему можно назначить время жизни и жизни то есть он может быть уничтожен.&lt;br /&gt;Можно добавить для возраждения любой Character класс и назначить время через которое появляются новые боты.&lt;/p&gt;&lt;div class=&quot;quote-box spoiler-box&quot;&gt;&lt;div onclick=&quot;$(this).toggleClass(&#039;visible&#039;); $(this).next().toggleClass(&#039;visible&#039;);&quot;&gt;MapObjectResNPC.cs&lt;/div&gt;&lt;blockquote&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 35em&quot;&gt;&lt;pre&gt;using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using Engine;
using Engine.EntitySystem;
using Engine.MapSystem;
using Engine.MathEx;
using Engine.Renderer;
using Engine.PhysicsSystem;
using Engine.Utils;
using ProjectCommon;
using Engine.SoundSystem;

namespace ProjectEntities
{
	/// &amp;lt;summary&amp;gt;
	/// Defines the &amp;lt;see cref=&amp;quot;InfluenceRegion&amp;quot;/&amp;gt; entity type.
	/// &amp;lt;/summary&amp;gt;
	public class MapObjectResNPCType : DynamicType
	{
	[FieldSerialize]
    CharacterType characterType;
    
    [FieldSerialize]
    float respTimeType;
    
    [Description( &amp;quot;Character который будет возрождаться&amp;quot; )]
    public CharacterType CharacterType
    {
    	get { return characterType; }
    	set { characterType = value; }
    }
    
	[Description( &amp;quot;Время между пораждениями&amp;quot; )]
    public float RespTimeType
    {
    	get { return respTimeType; }
    	set { respTimeType = value; }
    }
    
	}

	public class MapObjectResNPC : Dynamic
	{
    
    
    float spawntime;

    MapObjectResNPCType _type = null; public new MapObjectResNPCType Type { get { return _type; } }
    
    
        
        
        protected override void OnPostCreate(bool loaded)
        {
            base.OnPostCreate(loaded);//при старте грузит
            spawntime = 0.0f;//переменная куда насчитвываються тики
            SubscribeToTickEvent();//подпись на событие таймера.
        }
        
        
        protected override void OnTick()
        {
            base.OnTick();
            
        
            if (Type.Name != &amp;quot;Box&amp;quot;) {
 
                spawntime += TickDelta;//на переменную насчитываються тики
 
                if(spawntime &amp;gt;= Type.RespTimeType) {
                    CreateNPC();//создается объект
                        spawntime = 0.0f;//обнуляется переменная для тиков 
                }   
            }
            return;
        }
            

    
    void CreateNPC()
    {
    	//Создаем новый игровой объект
        MapObject RES = (MapObject)Entities.Instance.Create(Type.CharacterType, Map.Instance);
 
        //Задаем позицию
        RES.Position = GetInterpolatedPosition() + new Vec3( 0, 0, .1f );
 
        //Завершаем инициализацию
        RES.PostCreate();
    }
    
	}
}
    &lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;/div&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Tue, 18 Feb 2020 09:05:06 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=166#p166</guid>
		</item>
		<item>
			<title>Шаблон класса для GUI</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=165#p165</link>
			<description>&lt;p&gt;Шаблон заготовка для создания собственного GUI &lt;br /&gt;Подходит для версии движка: 3.0 и выше&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 35em&quot;&gt;&lt;pre&gt;using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Engine;
using Engine.FileSystem;
using Engine.UISystem;
using Engine.EntitySystem;
using Engine.MapSystem;
using Engine.MathEx;
using Engine.Renderer;
using Engine.SoundSystem;
using ProjectCommon;
using ProjectEntities;

namespace Game
{
    public class StartWindows : Control
    {
    	
    Control window;
   
    	enum DrawAreaModes
        {
            Triangles,
            Quads,
            Lines,
            Text,
        }

	    protected override void OnAttach()
	    {
	        //Вызов родительского метода
	        base.OnAttach();
	        window = ControlDeclarationManager.Instance.CreateControl(&amp;quot;GUI\\StartWindows.gui&amp;quot;);
	        //фоновая музыка
	        GameMusic.MusicPlay( &amp;quot;GUI\\StartWindows.ogg&amp;quot;, true );
	        //Добавление GUI к окну
	        Controls.Add(window);
	    }

    }               
}&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Wed, 12 Feb 2020 16:20:55 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=165#p165</guid>
		</item>
		<item>
			<title>Перевод класса RecastNavigationSystem.cs</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=164#p164</link>
			<description>&lt;p&gt;Класс для навигации ботов способность обходить препятствия.&lt;/p&gt;&lt;div class=&quot;quote-box spoiler-box&quot;&gt;&lt;div onclick=&quot;$(this).toggleClass(&#039;visible&#039;); $(this).next().toggleClass(&#039;visible&#039;);&quot;&gt;RecastNavigationSystem.cs&lt;/div&gt;&lt;blockquote&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 35em&quot;&gt;&lt;pre&gt;// Copyright (C) NeoAxis Group Ltd. This is part of NeoAxis 3D Engine SDK.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.IO;
using System.Drawing.Design;
using Engine.FileSystem;
using Engine.Utils;
using Engine.EntitySystem;
using Engine.MapSystem;
using Engine.MathEx;
using Engine.Renderer;

namespace Engine
{
	public class RecastNavigationSystemType : MapGeneralObjectType
	{
    public RecastNavigationSystemType()
    {
    	AllowEmptyName = true;
    }
	}

	//////////////////////////////////////////////////////////////////////////////////////////////////////////

	struct Wrapper
	{
    public const string library = &amp;quot;Recast&amp;quot;;
    public const CallingConvention convention = CallingConvention.Cdecl;

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_Initialize&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern IntPtr Initialize(
    	 ref Vec3 bmin, ref Vec3 bmax,
    	 float tileSize, float cellSize, float cellHeight,
    	 int minRegionSize, int mergeRegionSize, [MarshalAs( UnmanagedType.U1 )] bool monotonePartitioning,
    	 float maxEgdeLength, float maxEdgeError,
    	 int vertsPerPoly, float detailSampleDistance, float detailMaxSampleError,
    	 float agentHeight, float agentRadius, float agentMaxClimb, float agentMaxSlope );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_NavQueryInit&amp;quot;, CallingConvention = Wrapper.convention )]
    [return: MarshalAs( UnmanagedType.U1 )]
    public unsafe static extern bool NavQueryInit( IntPtr world, int maxNodes );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_GetSizes&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern void GetSizes( IntPtr world, out int maxTiles, out int maxPolysPerTile );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_BuildAllTiles&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern void BuildAllTiles( IntPtr world );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_DestroyAllTiles&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern void DestroyAllTiles( IntPtr world );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_SetGeometry&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern void SetGeometry( IntPtr world, IntPtr vertices, int vertexCount,
    	IntPtr indices, int indexCount, int trianglesPerChunk );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_Destroy&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern void Destroy( IntPtr world );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_GetNavigationMesh&amp;quot;, CallingConvention = Wrapper.convention )]
    [return: MarshalAs( UnmanagedType.U1 )]
    public unsafe static extern bool GetNavigationMesh( IntPtr world, out Vec3* vertices,
    	out int vertexCount );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_FindPath&amp;quot;, CallingConvention = Wrapper.convention )]
    [return: MarshalAs( UnmanagedType.U1 )]
    public unsafe static extern bool FindPath( IntPtr world, ref Vec3 start, ref Vec3 end, float stepSize,
    	ref Vec3 polygonPickExtents, int maxPolygonPath, int maxSmoothPath, int maxSteerPoints,
    	out Vec3* outPath, out int outPathCount );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_FreeMemory&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern void FreeMemory( IntPtr pointer );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_LoadNavMesh&amp;quot;, CallingConvention = Wrapper.convention )]
    [return: MarshalAs( UnmanagedType.U1 )]
    public unsafe static extern bool LoadNavMesh( IntPtr world, IntPtr data, int dataSize );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_SaveNavMesh&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern void SaveNavMesh( IntPtr world, out IntPtr data, out int dataSize );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_BuildTile&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern void BuildTile( IntPtr world, ref Vec3 position );

    [DllImport( Wrapper.library, EntryPoint = &amp;quot;Recast_RemoveTile&amp;quot;, CallingConvention = Wrapper.convention )]
    public unsafe static extern void RemoveTile( IntPtr world, ref Vec3 position );
	}

	//////////////////////////////////////////////////////////////////////////////////////////////////////////

	[ExtendedFunctionalityDescriptor( &amp;quot;Engine.Editor.RecastNavigationSystemExtendedFunctionalityDescriptor, RecastNavigationSystem.Editor&amp;quot; )]
	public class RecastNavigationSystem : MapGeneralObject
	{
    static List&amp;lt;RecastNavigationSystem&amp;gt; instances = new List&amp;lt;RecastNavigationSystem&amp;gt;();
    static ReadOnlyCollection&amp;lt;RecastNavigationSystem&amp;gt; instancesReadOnly;

    [FieldSerialize( &amp;quot;boundsMin&amp;quot; )]
    Vec3 boundsMin = new Vec3( -100, -100, -100 );

    [FieldSerialize( &amp;quot;boundsMax&amp;quot; )]
    Vec3 boundsMax = new Vec3( 100, 100, 100 );

    [FieldSerialize( &amp;quot;tileSize&amp;quot; )]
    int tileSize = 32;

    [FieldSerialize( &amp;quot;cellSize&amp;quot; )]
    float cellSize = .3f;

    [FieldSerialize( &amp;quot;cellHeight&amp;quot; )]
    float cellHeight = .3f;

    [FieldSerialize( &amp;quot;trianglesPerChunk&amp;quot; )]
    int trianglesPerChunk = 512;

    [FieldSerialize( &amp;quot;minRegionSize&amp;quot; )]
    int minRegionSize = 8;

    [FieldSerialize( &amp;quot;mergeRegionSize&amp;quot; )]
    int mergeRegionSize = 20;

    [FieldSerialize( &amp;quot;monotonePartitioning&amp;quot; )]
    bool monotonePartitioning = false;

    [FieldSerialize( &amp;quot;maxEdgeLength&amp;quot; )]
    float maxEdgeLength = 12;

    [FieldSerialize( &amp;quot;maxEdgeError&amp;quot; )]
    float maxEdgeError = 1.3f;

    [FieldSerialize( &amp;quot;maxVerticesPerPolygon&amp;quot; )]
    int maxVerticesPerPolygon = 6;

    [FieldSerialize( &amp;quot;detailSampleDistance&amp;quot; )]
    float detailSampleDistance = 6;

    [FieldSerialize( &amp;quot;detailMaxSampleError&amp;quot; )]
    float detailMaxSampleError = 1;

    [FieldSerialize( &amp;quot;agentHeight&amp;quot; )]
    float agentHeight = 2.0f;

    [FieldSerialize( &amp;quot;agentRadius&amp;quot; )]
    float agentRadius = .6f;

    [FieldSerialize( &amp;quot;agentMaxClimb&amp;quot; )]
    float agentMaxClimb = .9f;

    [FieldSerialize( &amp;quot;agentMaxSlope&amp;quot; )]
    Degree agentMaxSlope = 45;

    [FieldSerialize( &amp;quot;geometries&amp;quot; )]
    List&amp;lt;Entity&amp;gt; geometries = new List&amp;lt;Entity&amp;gt;();
    ReadOnlyCollection&amp;lt;Entity&amp;gt; geometriesReadOnly;

    [FieldSerialize( &amp;quot;alwaysDrawNavMesh&amp;quot; )]
    bool alwaysDrawNavMesh;

    [FieldSerialize( &amp;quot;pathfindingMaxNodes&amp;quot; )]
    int pathfindingMaxNodes = 8192;

    [FieldSerialize( &amp;quot;dataDirectory&amp;quot; )]
    string dataDirectory = &amp;quot;RecastNavigationSystem&amp;quot;;

    //[FieldSerialize( &amp;quot;gridHeight&amp;quot; )]
    //float gridHeight = .5f;

    IntPtr recastWorld;

    Vec3[] debugNavigationMeshVertices;
    int[] debugNavigationMeshIndices;

    //Vec3[] tileGridMeshVertices;
    //int[] tileGridMeshIndices;
    //Vec3[] cellGridMeshVertices;
    //int[] cellGridMeshIndices;

    //bool drawTileGrid;

    ///////////////////////////////////////////

    [TypeField]
    RecastNavigationSystemType __type = null;
    /// &amp;lt;summary&amp;gt;
    /// Gets the entity type.
    /// &amp;lt;/summary&amp;gt;
    public new RecastNavigationSystemType Type { get { return __type; } }

    ///////////////////////////////////////////

    class IndexVertexBufferCollector
    {
    	//!!!!!!need instancing
    	public Vec3[] resultVertices = new Vec3[ 4096 ];
    	public int[] resultIndices = new int[ 4096 ];
    	public int resultVertexCount;
    	public int resultIndexCount;

    	public void Add( Vec3[] vertices, int vertexCount, int[] indices, int indexCount )
    	{
        int newVertexCount = resultVertexCount + vertexCount;
        int newIndexCount = resultIndexCount + indexCount;

        if( newVertexCount &amp;gt; resultVertices.Length )
        {
        	int s = resultVertices.Length;
        	while( newVertexCount &amp;gt; s )
            s *= 2;
        	Vec3[] old = resultVertices;
        	resultVertices = new Vec3[ s ];
        	Array.Copy( old, resultVertices, old.Length );
        }

        if( newIndexCount &amp;gt; resultIndices.Length )
        {
        	int s = resultIndices.Length;
        	while( newIndexCount &amp;gt; s )
            s *= 2;
        	int[] old = resultIndices;
        	resultIndices = new int[ s ];
        	Array.Copy( old, resultIndices, old.Length );
        }

        Array.Copy( vertices, 0, resultVertices, resultVertexCount, vertexCount );
        for( int n = 0; n &amp;lt; indexCount; n++ )
        	resultIndices[ resultIndexCount + n ] = resultVertexCount + indices[ n ];
        resultVertexCount = newVertexCount;
        resultIndexCount = newIndexCount;
    	}

    }

    ///////////////////////////////////////////

    public RecastNavigationSystem()
    {
    	instances.Add( this );

    	geometriesReadOnly = new ReadOnlyCollection&amp;lt;Entity&amp;gt;( geometries );

    	NativeLibraryManager.PreLoadLibrary( &amp;quot;Recast&amp;quot; );
    }

    public static IList&amp;lt;RecastNavigationSystem&amp;gt; Instances
    {
    	get
    	{
        if( instancesReadOnly == null )
        	instancesReadOnly = new ReadOnlyCollection&amp;lt;RecastNavigationSystem&amp;gt;( instances );
        return instancesReadOnly;
    	}
    }

    [Category( &amp;quot;Debug&amp;quot; )]
    [DefaultValue( false )]
    public bool AlwaysDrawNavMesh
    {
    	get { return alwaysDrawNavMesh; }
    	set { alwaysDrawNavMesh = value; }
    }

    //[Browsable( false )] //SodanKerjuu: controlled by the initialize toolbox form
    //public bool DrawTileGrid
    //{
    //   get { return drawTileGrid; }
    //   set { drawTileGrid = value; }
    //}

    //[DefaultValue( .5f )]
    //public float GridHeight
    //{
    //   get { return gridHeight; }
    //   set
    //   {
    //      gridHeight = value;
    //      MathFunctions.Saturate( ref gridHeight );
    //      ClearDebugGrids();
    //   }
    //}

    [Category( &amp;quot;Grid&amp;quot; )]
    public Vec3 BoundsMin
    {
    	get { return boundsMin; }
    	set
    	{
        if( boundsMin == value )
        	return;
        boundsMin = value;

        //ClearDebugGrids();
    	}
    }

    [Category( &amp;quot;Grid&amp;quot; )]
    public Vec3 BoundsMax
    {
    	get { return boundsMax; }
    	set
    	{
        if( boundsMax == value )
        	return;
        boundsMax = value;

        //ClearDebugGrids();
    	}
    }

    [Category( &amp;quot;Grid&amp;quot; )]
    [DefaultValue( 32 )]
    [LocalizedDescription( &amp;quot;Размер плитки.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public int TileSize
    {
    	get { return tileSize; }
    	set
    	{
        if( value &amp;lt; 16 )
        	value = 16;
        tileSize = value;
    	}
    }

    [Category( &amp;quot;Grid&amp;quot; )]
    [DefaultValue( .3f )]
    [LocalizedDescription( &amp;quot;Разрешение по ширине и глубине, используемое при выборке исходной геометрии. Ширина и глубина вокселей в полях вокселей. Ширина и глубина столбцов ячеек, составляющих воксельные поля. Более низкое значение позволяет сгенерированным сеткам более точно соответствовать исходной геометрии, но при более высокой стоимости обработки и памяти.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public float CellSize
    {
    	get { return cellSize; }
    	set
    	{
        if( value &amp;lt; .01f )
        	value = .01f;
        cellSize = value;
    	}
    }

    [Category( &amp;quot;Grid&amp;quot; )]
    [DefaultValue( .3f )]
    [LocalizedDescription( &amp;quot;Разрешение по высоте, используемое при выборке исходной геометрии. Высота вокселей в полях вокселей.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public float CellHeight
    {
    	get { return cellHeight; }
    	set
    	{
        if( value &amp;lt; .01f )
        	value = .01f;
        cellHeight = value;
    	}
    }

    [Category( &amp;quot;Grid&amp;quot; )]
    [DefaultValue( 512 )]
    [LocalizedDescription( &amp;quot;Максимальное количество треугольников для каждого чанка во внутреннем дереве AABB.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public int TrianglesPerChunk
    {
    	get { return trianglesPerChunk; }
    	set
    	{
        if( value &amp;lt; 128 )
        	value = 128;
        trianglesPerChunk = value;
    	}
    }

    [Category( &amp;quot;Regions&amp;quot; )]
    [DefaultValue( 8 )]
    [LocalizedDescription( &amp;quot;Минимальный размер региона для не связанных (островных) регионов. Значение в вокселях. Области, которые не связаны ни с одним другим регионом и имеют размер меньше этого размера, будут отбракованы до создания сетки. То есть Они больше не будут считаться проходимыми.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public int MinRegionSize
    {
    	get { return minRegionSize; }
    	set
    	{
        if( value &amp;lt; 1 )
        	value = 1;
        minRegionSize = value;
    	}
    }

    [Category( &amp;quot;Regions&amp;quot; )]
    [DefaultValue( 20 )]
    [LocalizedDescription( &amp;quot;Любые регионы меньше этого размера будут, если возможно, объединены с более крупными регионами. Значение в вокселях. Помогает уменьшить количество небольших регионов. Это особенно проблема в областях диагонального пути, где присущие ошибки в алгоритме генерации области могут привести к излишне маленьким областям.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public int MergeRegionSize
    {
    	get { return mergeRegionSize; }
    	set
    	{
        if( value &amp;lt; 0 )
        	value = 0;
        mergeRegionSize = value;
    	}
    }

    [Category( &amp;quot;Regions&amp;quot; )]
    [DefaultValue( false )]
    [LocalizedDescription( &amp;quot;Разделите пройденную поверхность на простые области без отверстий.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public bool MonotonePartitioning
    {
    	get { return monotonePartitioning; }
    	set { monotonePartitioning = value; }
    }

    [Category( &amp;quot;Polygonization&amp;quot; )]
    [DefaultValue( 12.0f )]
    [LocalizedDescription( &amp;quot;Максимальная длина ребер многоугольника, представляющих границу ячеек. Больше вершин будет добавлено к граничным ребрам, если это значение будет превышено для определенного ребра. Нулевое значение отключит эту функцию.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public float MaxEdgeLength
    {
    	get { return maxEdgeLength; }
    	set
    	{
        if( value &amp;lt; 0 )
        	value = 0;
        maxEdgeLength = value;
    	}
    }

    [Category( &amp;quot;Polygonization&amp;quot; )]
    [DefaultValue( 1.3f )]
    [LocalizedDescription( &amp;quot;Максимальное расстояние, на которое края сетки могут отклоняться от исходной геометрии. Более низкое значение приведет к тому, что ребра сетки будут более точно следовать контуру геометрии плоскости xz за счет увеличения числа треугольников.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public float MaxEdgeError
    {
    	get { return maxEdgeError; }
    	set
    	{
        if( value &amp;lt; .1f )
        	value = .1f;
        maxEdgeError = value;
    	}
    }

    [Category( &amp;quot;Polygonization&amp;quot; )]
    [DefaultValue( 6 )]
    public int MaxVerticesPerPolygon
    {
    	get { return maxVerticesPerPolygon; }
    	set
    	{
        if( value &amp;lt; 3 )
        	value = 3;
        maxVerticesPerPolygon = value;

        //!!!!!!
        //need change debug drawing NavMesh. at this time navmesh draws as triangle list.
    	}
    }

    [Category( &amp;quot;Detail Mesh&amp;quot; )]
    [DefaultValue( 6.0f )]
    [LocalizedDescription( &amp;quot;Устанавливает расстояние выборки, используемое при сопоставлении сетки детали с поверхностью исходной геометрии. Влияет на то, насколько хорошо конечная сетка детали соответствует контуру поверхности исходной геометрии. Более высокие значения приводят к созданию сетки деталей, которая более точно соответствует поверхности исходной геометрии за счет более высокого конечного числа треугольников и более высоких затрат на обработку.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public float DetailSampleDistance
    {
    	get { return detailSampleDistance; }
    	set
    	{
        if( value &amp;lt; 0 )
        	value = 0;
        detailSampleDistance = value;
    	}
    }

    [Category( &amp;quot;Detail Mesh&amp;quot; )]
    [DefaultValue( 1.0f )]
    [LocalizedDescription( &amp;quot;Максимальное расстояние, на котором поверхность сетки деталей может отклоняться от поверхности исходной геометрии.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public float DetailMaxSampleError
    {
    	get { return detailMaxSampleError; }
    	set
    	{
        if( value &amp;lt; 0 )
        	value = 0;
        detailMaxSampleError = value;
    	}
    }

    [Category( &amp;quot;Agent&amp;quot; )]
    [DefaultValue( 2.0f )]
    [LocalizedDescription( &amp;quot;Минимальная высота, на которой агент еще может ходить.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public float AgentHeight
    {
    	get { return agentHeight; }
    	set
    	{
        if( value &amp;lt; .1f )
        	value = .1f;
        agentHeight = value;
    	}
    }

    [Category( &amp;quot;Agent&amp;quot; )]
    [DefaultValue( .6f )]
    [LocalizedDescription( &amp;quot;Радиус агента.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public float AgentRadius
    {
    	get { return agentRadius; }
    	set
    	{
        if( value &amp;lt; 0 )
        	value = 0;
        agentRadius = value;
    	}
    }

    [Category( &amp;quot;Agent&amp;quot; )]
    [DefaultValue( .9f )]
    [LocalizedDescription( &amp;quot;Максимальная высота между ячейками сетки, на которую может подняться агент.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public float AgentMaxClimb
    {
    	get { return agentMaxClimb; }
    	set
    	{
        if( value &amp;lt; .001f )
        	value = .001f;
        agentMaxClimb = value;
    	}
    }

    [Category( &amp;quot;Agent&amp;quot; )]
    [DefaultValue( typeof( Degree ), &amp;quot;45&amp;quot; )]
    [Editor( typeof( SingleValueEditor ), typeof( UITypeEditor ) )]
    [EditorLimitsRange( 1, 89 )]
    [LocalizedDescription( &amp;quot;Максимальный ходовой угол наклона в градусах.&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public Degree AgentMaxSlope
    {
    	get { return agentMaxSlope; }
    	set
    	{
        if( value &amp;lt; 1 )
        	value = 1;
        if( value &amp;gt; 89 )
        	value = 89;
        agentMaxSlope = value;
    	}
    }

    [Browsable( false )]
    public IList&amp;lt;Entity&amp;gt; Geometries
    {
    	get { return geometriesReadOnly; }
    }

    [Category( &amp;quot;Pathfinding&amp;quot; )]
    [DefaultValue( 8192 )]
    [LocalizedDescription( &amp;quot;Максимальное количество используемых поисковых узлов (не более 65536).&amp;quot;, &amp;quot;RecastNavigationSystem&amp;quot; )]
    public int PathfindingMaxNodes
    {
    	get { return pathfindingMaxNodes; }
    	set
    	{
        if( value &amp;lt; 4 )
        	value = 4;
        if( value &amp;gt; 65536 )
        	value = 65536;
        pathfindingMaxNodes = value;

        if( recastWorld != IntPtr.Zero )
        	Wrapper.NavQueryInit( recastWorld, pathfindingMaxNodes );
    	}
    }

    //void ClearDebugGrids()
    //{
    //   tileGridMeshVertices = null;
    //   tileGridMeshIndices = null;
    //   cellGridMeshVertices = null;
    //   cellGridMeshIndices = null;
    //}

    static Vec3 ToRecastVec3( Vec3 v )
    {
    	return new Vec3( v.X, v.Z, -v.Y );
    }

    static Vec3 ToEngineVec3( Vec3 v )
    {
    	return new Vec3( v.X, -v.Z, v.Y );
    }

    protected override bool OnLoad( TextBlock block )
    {
    	if( !base.OnLoad( block ) )
        return false;

    	InitRecastWorld();

    	string virtualDataDirectory = Path.Combine( Map.Instance.GetVirtualFileDirectory(), dataDirectory );
    	string virtualFilePath = Path.Combine( virtualDataDirectory, &amp;quot;NavMesh.dat&amp;quot; );

    	if( VirtualFile.Exists( virtualFilePath ) )
    	{
        byte[] data = VirtualFile.ReadAllBytes( virtualFilePath );
        unsafe
        {
        	fixed( byte* pData = data )
        	{
            if( Wrapper.LoadNavMesh( recastWorld, (IntPtr)pData, data.Length ) )
            {
            	Wrapper.NavQueryInit( recastWorld, pathfindingMaxNodes );
            }
        	}
        }
    	}

    	return true;
    }

    protected override void OnSave( TextBlock block )
    {
    	base.OnSave( block );

    	if( EntitySystemWorld.Instance.SerializationMode == SerializationModes.MapSceneFile )
    	{
        Log.Warning( &amp;quot;Scene export: RecastNavigationSystem is not supported.&amp;quot; );
        return;
    	}

    	string mapRealFileDirectory = VirtualFileSystem.GetRealPathByVirtual(
        Map.Instance.GetVirtualFileDirectory() );
    	string realFullDataDirectory = Path.Combine( mapRealFileDirectory, dataDirectory );

    	string realFilePath = Path.Combine( realFullDataDirectory, &amp;quot;NavMesh.dat&amp;quot; );

    	if( recastWorld != IntPtr.Zero )
    	{
        if( !Directory.Exists( realFullDataDirectory ) )
        	Directory.CreateDirectory( realFullDataDirectory );

        IntPtr data;
        int size;
        Wrapper.SaveNavMesh( recastWorld, out data, out size );

        if( data != IntPtr.Zero )
        {
        	byte[] buffer = new byte[ size ];
        	Marshal.Copy( data, buffer, 0, size );

        	Wrapper.FreeMemory( data );

        	File.WriteAllBytes( realFilePath, buffer );
        }
    	}
    	else
    	{
        if( File.Exists( realFilePath ) )
        	File.Delete( realFilePath );
    	}
    }

    protected override void OnCreate()
    {
    	base.OnCreate();

    	string mapRealFileDirectory = VirtualFileSystem.GetRealPathByVirtual(
        Map.Instance.GetVirtualFileDirectory() );

    	//dataDirectory
    	for( int counter = 1; ; counter++ )
    	{
        dataDirectory = &amp;quot;RecastNavigationSystem&amp;quot;;
        if( counter != 1 )
        	dataDirectory += counter.ToString();

        bool busy = false;
        foreach( RecastNavigationSystem system in Instances )
        {
        	if( system != this &amp;amp;&amp;amp; dataDirectory == system.dataDirectory )
            busy = true;
        }
        if( !busy )
        {
        	string realPath = Path.Combine( mapRealFileDirectory, dataDirectory );
        	if( !Directory.Exists( realPath ) )
            break;
        }
    	}
    }

    /// &amp;lt;summary&amp;gt;Overridden from &amp;lt;see cref=&amp;quot;Engine.EntitySystem.Entity&amp;quot;/&amp;gt;.&amp;lt;/summary&amp;gt;
    protected override void OnPostCreate( bool loaded )
    {
    	if( !instances.Contains( this ) )
        instances.Add( this );

    	base.OnPostCreate( loaded );

    	//remove null geometry entries
    	if( loaded )
    	{
        again:
        for( int n = 0; n &amp;lt; geometries.Count; n++ )
        {
        	if( geometries[ n ] == null )
        	{
            geometries.RemoveAt( n );
            goto again;
        	}
        }
    	}
    }

    /// &amp;lt;summary&amp;gt;Overridden from &amp;lt;see cref=&amp;quot;Engine.EntitySystem.Entity&amp;quot;/&amp;gt;.&amp;lt;/summary&amp;gt;
    protected override void OnDestroy()
    {
    	DestroyRecastWorld();

    	base.OnDestroy();

    	instances.Remove( this );
    }

    public void InitRecastWorld()
    {
    	unsafe
    	{
        DestroyRecastWorld();

        Vec3 min = boundsMin;
        Vec3 max = boundsMax;
        for( int n = 0; n &amp;lt; 3; n++ )
        	if( max[ n ] - min[ n ] &amp;lt; 1 )
            max[ n ] = min[ n ] + 1;

        Vec3 recastBoundsMin = ToRecastVec3( min );
        Vec3 recastBoundsMax = ToRecastVec3( max );

        recastWorld = Wrapper.Initialize(
        	ref recastBoundsMin, ref recastBoundsMax,
        	tileSize, cellSize, cellHeight,
        	minRegionSize, mergeRegionSize, monotonePartitioning,
        	maxEdgeLength, maxEdgeError,
        	maxVerticesPerPolygon, detailSampleDistance, detailMaxSampleError,
        	agentHeight, agentRadius, agentMaxClimb, (float)agentMaxSlope );

        if( recastWorld != IntPtr.Zero )
        {
        	Wrapper.NavQueryInit( recastWorld, pathfindingMaxNodes );
        }

        //if( recastWorld != IntPtr.Zero )
        //{
        //Log.Info( &amp;quot;RecastWorld Initialized.&amp;quot; );

        //int mT = 0, mPPT = 0;
        //Wrapper.GetSizes( recastWorld, out mT, out mPPT );
        //Log.Info( &amp;quot;MaxTiles: &amp;quot; + mT.ToString() + &amp;quot;  MaxPolygonsPerTile: &amp;quot; + mPPT.ToString() );
        //}
    	}
    }

    [Browsable( false )]
    public bool IsInitialized
    {
    	get { return recastWorld != IntPtr.Zero; }
    }

    public void DestroyRecastWorld()
    {
    	debugNavigationMeshVertices = null;
    	debugNavigationMeshIndices = null;

    	if( recastWorld != IntPtr.Zero )
    	{
        Wrapper.Destroy( recastWorld );
        recastWorld = IntPtr.Zero;
    	}
    }

    public void DestroyAllTiles()
    {
    	if( recastWorld == IntPtr.Zero )
        return;

    	Wrapper.DestroyAllTiles( recastWorld );

    	//refresh debug mesh
    	debugNavigationMeshVertices = null;
    	debugNavigationMeshIndices = null;
    }

    public bool BuildAllTiles( out string error )
    {
    	error = null;

    	if( recastWorld == IntPtr.Zero )
    	{
        error = &amp;quot;Need to initialize the Recast World.&amp;quot;;
        return false;
    	}

    	if( geometries.Count == 0 )
    	{
        error = &amp;quot;No collision objects are selected.&amp;quot;;
        return false;
    	}

    	IndexVertexBufferCollector collector = GetAllGeometriesForNavigationMesh();
    	{
        Vec3[] vertices = collector.resultVertices;
        int[] indices = collector.resultIndices;
        int vertexCount = collector.resultVertexCount;
        int indexCount = collector.resultIndexCount;

        if( vertexCount == 0 )
        {
        	error = &amp;quot;No vertices were gathered from collision objects.&amp;quot;;
        	return false;
        }

        //convert to Recast space
        for( int n = 0; n &amp;lt; vertexCount; n++ )
        	vertices[ n ] = ToRecastVec3( vertices[ n ] );

        unsafe
        {
        	fixed( Vec3* pVertices = vertices )
        	{
            fixed( int* pIndices = indices )
            {
            	Wrapper.SetGeometry( recastWorld, (IntPtr)pVertices, vertexCount, (IntPtr)pIndices,
                indexCount, trianglesPerChunk );

            	Wrapper.BuildAllTiles( recastWorld );
            }
        	}
        }
    	}

    	//refresh debug mesh
    	debugNavigationMeshVertices = null;
    	debugNavigationMeshIndices = null;

    	return true;
    }

    static bool IsAllowSaveRecursive( Entity entity )
    {
    	while( entity != null )
    	{
        if( !entity.AllowSave )
        	return false;
        entity = entity.Parent;
    	}
    	return true;
    }

    public bool IsSupportedGeometry( Entity entity )
    {
    	if( !IsAllowSaveRecursive( entity ) )
        return false;

    	HeightmapTerrain terrain = entity as HeightmapTerrain;
    	if( terrain != null )
        return true;

    	StaticMesh staticMesh = entity as StaticMesh;
    	if( staticMesh != null )
    	{
        if( staticMesh.Collision )
        	return true;
        return false;
    	}

    	MapObject mapObject = entity as MapObject;
    	if( mapObject != null )
    	{
        foreach( MapObjectAttachedObject attachedObject in mapObject.AttachedObjects )
        {
        	MapObjectAttachedMesh attachedMesh = attachedObject as MapObjectAttachedMesh;
        	if( attachedMesh != null &amp;amp;&amp;amp; attachedMesh.Collision )
            return true;
        }
    	}

    	return false;
    }

    //IndexVertexBufferCollector GetGeometriesForNavigationMesh( Bounds bounds )
    //{
    //   IndexVertexBufferCollector collector = new IndexVertexBufferCollector();

    //   странный способ
    //   Map.Instance.GetObjects( new Box( bounds ), delegate( MapObject obj )
    //      {
    //         if( geometries.Contains( obj ) )
    //            AddEntityToCollector( collector, obj );
    //      } );

    //   //heightmapterrain не добавляется в выборку объема, это на самом деле хорошо, потому что теперь мы можем добавить только необходимую часть
    //   {
    //      //raycast does not hit terrain?!?
    //      /*
    //      Vec3 top = bounds.GetCenter();
    //      top.Z = boundsMax.Z;

    //      Vec3 bottom = bounds.GetCenter();
    //      bottom.Z = boundsMin.Z;

    //      RayCastResult[] results = PhysicsWorld.Instance.RayCastPiercing(new Ray(top, bottom), (int)ContactGroup.CastOnlyCollision);

    //      foreach (RayCastResult result in results)
    //      {
    //          HeightmapTerrain terrain = HeightmapTerrain.GetTerrainByBody( result.Shape.Body );
    //          if( terrain != null)
    //              if( geometries.Contains(terrain) )
    //                  AddHeightmapTerrainPartToCollector(collector, terrain, bounds); 
    //      }
    //      */

    //      //SodanKerjuu: стиль Lo-Tek, лучше надеяться, что у вас не более одного ландшафта
    //      foreach( HeightmapTerrain terrain in HeightmapTerrain.Instances )
    //         if( geometries.Contains( terrain ) )
    //            AddHeightmapTerrainPartToCollector( collector, terrain, bounds );
    //   }

    //   return collector;
    //}

    //void AddHeightmapTerrainPartToCollector( IndexVertexBufferCollector collector, HeightmapTerrain terrain, Bounds bounds )
    //{
    //   int size = terrain.GetHeightmapSizeAsInteger();

    //   Vec3[] vertices = new Vec3[ ( size + 1 ) * ( size + 1 ) ];
    //   int[] indices = new int[ size * size * 6 ];

    //   int vertexPosition = 0;
    //   int indexPosition = 0;

    //   for( int y = 0; y &amp;lt; size + 1; y++ )
    //   {
    //      for( int x = 0; x &amp;lt; size + 1; x++ )
    //      {
    //         если очень большое число дыр, то будет излишне?
    //         //SodanKerjuu: no need, you will have unused vertices, but they won&#039;t matter for Recast if they are not indexed
    //         //if( !terrain.GetHoleFlag( new Vec2i( x, y ) ) )
    //         //{
    //         Vec2 pos2 = terrain.GetPositionXY( new Vec2i( x, y ) );
    //         Vec3 pos = new Vec3( pos2.X, pos2.Y, terrain.GetHeight( new Vec2i( x, y ) ) );
    //         vertices[ vertexPosition ] = pos;
    //         vertexPosition++;
    //         //}
    //      }
    //   }

    //   for( int y = 0; y &amp;lt; size; y++ )
    //   {
    //      for( int x = 0; x &amp;lt; size; x++ )
    //      {
    //         if( !terrain.GetHoleFlag( new Vec2i( x, y ) ) )
    //         {
    //            indices[ indexPosition + 0 ] = ( size + 1 ) * y + x;
    //            indices[ indexPosition + 1 ] = ( size + 1 ) * y + x + 1;
    //            indices[ indexPosition + 2 ] = ( size + 1 ) * ( y + 1 ) + x + 1;
    //            indices[ indexPosition + 3 ] = ( size + 1 ) * ( y + 1 ) + x + 1;
    //            indices[ indexPosition + 4 ] = ( size + 1 ) * ( y + 1 ) + x;
    //            indices[ indexPosition + 5 ] = ( size + 1 ) * y + x;
    //            indexPosition += 6;
    //         }
    //      }
    //   }

    //   collector.Add( vertices, vertexPosition, indices, indexPosition );

    //}

    bool AddEntityToCollector( IndexVertexBufferCollector collector, Entity entity )
    {
    	//Static meshes
    	StaticMesh staticMesh = entity as StaticMesh;
    	if( staticMesh != null )
    	{
        if( staticMesh.Collision == false )
        	return false;

        Mesh mesh;
        if( !string.IsNullOrEmpty( staticMesh.CollisionSpecialMeshName ) )
        	mesh = MeshManager.Instance.Load( staticMesh.CollisionSpecialMeshName );
        else
        	mesh = MeshManager.Instance.Load( staticMesh.MeshName );

        if( mesh == null )
        	return false;

        Mat4 transform = staticMesh.GetTransform();
        foreach( SubMesh subMesh in mesh.SubMeshes )
        {
        	if( subMesh.AllowCollision )
        	{
            Vec3[] vertices;
            int[] indices;
            subMesh.GetSomeGeometry( out vertices, out indices );
            if( vertices != null )
            {
            	for( int n = 0; n &amp;lt; vertices.Length; n++ )
                vertices[ n ] = transform * vertices[ n ];
            	collector.Add( vertices, vertices.Length, indices, indices.Length );
            }
        	}
        }
        return true;
    	}

    	//MapObjectAttachedMesh
    	MapObject mapObject = entity as MapObject;
    	if( mapObject != null &amp;amp;&amp;amp; !( entity is StaticMesh ) ) //SodanKerjuu: static meshes qualify as both
    	{
        bool added = false;

        foreach( MapObjectAttachedObject attachedObject in mapObject.AttachedObjects )
        {
        	MapObjectAttachedMesh attachedMesh = attachedObject as MapObjectAttachedMesh;
        	if( attachedMesh != null &amp;amp;&amp;amp; attachedMesh.Collision )
        	{
            Mesh mesh;
            if( !string.IsNullOrEmpty( attachedMesh.CollisionSpecialMeshName ) )
            	mesh = MeshManager.Instance.Load( attachedMesh.CollisionSpecialMeshName );
            else
            	mesh = MeshManager.Instance.Load( attachedMesh.MeshName );

            if( mesh != null )
            {
            	Vec3 pos;
            	Quat rot;
            	Vec3 scl;
            	attachedMesh.GetGlobalTransform( out pos, out rot, out scl );
            	Mat4 transform = new Mat4( rot.ToMat3() * Mat3.FromScale( scl ), pos );

            	foreach( SubMesh subMesh in mesh.SubMeshes )
            	{
                if( subMesh.AllowCollision )
                {
                	Vec3[] vertices;
                	int[] indices;
                	subMesh.GetSomeGeometry( out vertices, out indices );
                	if( vertices != null )
                	{
                    for( int n = 0; n &amp;lt; vertices.Length; n++ )
                    	vertices[ n ] = transform * vertices[ n ];
                    collector.Add( vertices, vertices.Length, indices, indices.Length );
                    added = true;
                	}
                }
            	}
            }
        	}
        }

        return added;
    	}

    	//HeightmapTerrain
    	HeightmapTerrain terrain = entity as HeightmapTerrain;
    	if( terrain != null )
    	{
        int size = terrain.GetHeightmapSizeAsInteger();

        Vec3[] vertices = new Vec3[ ( size + 1 ) * ( size + 1 ) ];
        int[] indices = new int[ size * size * 6 ];

        int vertexPosition = 0;
        int indexPosition = 0;

        for( int y = 0; y &amp;lt; size + 1; y++ )
        {
        	for( int x = 0; x &amp;lt; size + 1; x++ )
        	{
            //SodanKerjuu: no need, you will have unused vertices, but they won&#039;t matter for Recast if they are not indexed
            //if( !terrain.GetHoleFlag( new Vec2i( x, y ) ) )
            //{
            Vec2 pos2 = terrain.GetPositionXY( new Vec2I( x, y ) );
            Vec3 pos = new Vec3( pos2.X, pos2.Y, terrain.GetHeight( new Vec2I( x, y ) ) );
            vertices[ vertexPosition ] = pos;
            vertexPosition++;
            //}
        	}
        }

        for( int y = 0; y &amp;lt; size; y++ )
        {
        	for( int x = 0; x &amp;lt; size; x++ )
        	{
            if( !terrain.GetHoleFlag( new Vec2I( x, y ) ) )
            {
            	indices[ indexPosition + 0 ] = ( size + 1 ) * y + x;
            	indices[ indexPosition + 1 ] = ( size + 1 ) * y + x + 1;
            	indices[ indexPosition + 2 ] = ( size + 1 ) * ( y + 1 ) + x + 1;
            	indices[ indexPosition + 3 ] = ( size + 1 ) * ( y + 1 ) + x + 1;
            	indices[ indexPosition + 4 ] = ( size + 1 ) * ( y + 1 ) + x;
            	indices[ indexPosition + 5 ] = ( size + 1 ) * y + x;
            	indexPosition += 6;
            }
        	}
        }

        collector.Add( vertices, vertexPosition, indices, indexPosition );
        return true;
    	}

    	return false;
    }

    IndexVertexBufferCollector GetAllGeometriesForNavigationMesh()
    {
    	IndexVertexBufferCollector collector = new IndexVertexBufferCollector();

    	foreach( Entity geometry in geometries )
    	{
        if( !geometry.Editor_IsExcludedFromWorld() )
        	AddEntityToCollector( collector, geometry );
    	}

    	return collector;
    }

    public void AddAllGeometriesOnMap()
    {
    	foreach( Entity entity in Entities.Instance.EntitiesCollection )
    	{
        if( IsSupportedGeometry( entity ) )
        {
        	if( !Geometries.Contains( entity ) )
            AddGeometry( entity );
        }
    	}
    }

    Bounds GetGeometryBounds( Entity entity )
    {
    	HeightmapTerrain terrain = entity as HeightmapTerrain;
    	if( terrain != null &amp;amp;&amp;amp; terrain.Enabled )
        return terrain.CalculateBounds();

    	MapObject mapObject = entity as MapObject;
    	if( mapObject != null )
        return mapObject.MapBounds;

    	return Engine.MathEx.Bounds.Cleared;
    }

    public void RecalculateBounds()
    {
    	Bounds bounds = Bounds.Cleared;
    	foreach( Entity entity in Geometries )
    	{
        if( entity.Editor_IsExcludedFromWorld() )
        	continue;
        bounds.Add( GetGeometryBounds( entity ) );
    	}

    	if( bounds.IsCleared() )
        bounds = new Bounds( -10, -10, -10, 10, 10, 10 );

    	//needs to be whole number because decimals are rejected on the initialize toolbox
    	Vec3 padding = new Vec3( 1, 1, 1 );

    	BoundsMin = bounds.Minimum - padding;
    	BoundsMax = bounds.Maximum + padding;
    }

    public bool FindPath( Vec3 start, Vec3 end, float stepSize, Vec3 polygonPickExtents, int maxPolygonPath,
    	int maxSmoothPath, int maxSteerPoints, out Vec3[] outPath )
    {
    	unsafe
    	{
        outPath = null;

        if( recastWorld == IntPtr.Zero )
        	return false;

        //convert to Recast space
        Vec3 recastStart = ToRecastVec3( start );
        Vec3 recastEnd = ToRecastVec3( end );
        Vec3 recastPolygonPickExtents =
        	new Vec3( polygonPickExtents.X, polygonPickExtents.Z, polygonPickExtents.Y );

        Vec3* pathPointer;
        int pathCount;
        bool result = Wrapper.FindPath( recastWorld, ref recastStart, ref recastEnd, stepSize,
        	ref recastPolygonPickExtents, maxPolygonPath, maxSmoothPath, maxSteerPoints,
        	out pathPointer, out pathCount );

        if( result )
        {
        	if( pathCount &amp;gt; 0 )
        	{
            outPath = new Vec3[ pathCount ];
            for( int n = 0; n &amp;lt; pathCount; n++ )
            	outPath[ n ] = ToEngineVec3( pathPointer[ n ] );
        	}
        	else
        	{
            outPath = new Vec3[ 1 ];
            outPath[ 0 ] = start;
        	}

        	Wrapper.FreeMemory( (IntPtr)pathPointer );
        }

        return result;
    	}
    }

    //public Bounds GetCellBounds( Vec3 pos )
    //{
    //   float gridSize = tileSize * cellSize;
    //   float minX = boundsMin.X + ( (int)Math.Floor( ( pos.X - boundsMin.X ) / gridSize ) * gridSize );
    //   float minY = boundsMax.Y + ( (int)Math.Floor( ( pos.Y - boundsMax.Y ) / gridSize ) * gridSize );
    //   xx;
    //   //SodanKerjuu: mirrored Y because of Recast coordinate system

    //   return new Bounds( minX, minY, boundsMin.Z, minX + gridSize, minY + gridSize, boundsMax.Z );
    //}

    ////use this to manually set the geometry, used with BuildTileCached to speed things up
    //public void AssignGeometry( Bounds bounds )
    //{
    //   if( recastWorld == IntPtr.Zero )
    //      return;

    //   IndexVertexBufferCollector collector = GetGeometriesForNavigationMesh( bounds );

    //   if( collector.resultVertexCount &amp;gt; 0 )
    //   {
    //      Vec3[] vertices = collector.resultVertices;
    //      int[] indices = collector.resultIndices;
    //      int vertexCount = collector.resultVertexCount;
    //      int indexCount = collector.resultIndexCount;

    //      //convert to Recast space
    //      for( int n = 0; n &amp;lt; vertexCount; n++ )
    //         vertices[ n ] = ToRecastVec3( vertices[ n ] );

    //      unsafe
    //      {
    //         fixed( Vec3* pVertices = vertices )
    //         fixed( int* pIndices = indices )
    //            Wrapper.SetGeometry( recastWorld, (IntPtr)pVertices, vertexCount, (IntPtr)pIndices, indexCount );
    //      }
    //   }
    //}

    ////this will use the previously assigned geometry instead of getting new one
    //public void BuildTileCached( Vec3 position )
    //{
    //   if( recastWorld == IntPtr.Zero )
    //      return;

    //   Vec3 targetPosition = ToRecastVec3( position );
    //   Wrapper.BuildTile( recastWorld, ref targetPosition );

    //   //redraw debug mesh
    //   debugNavigationMeshVertices = null;
    //   debugNavigationMeshIndices = null;
    //}

    //public void BuildTile( Vec3 position )
    //{
    //   if( recastWorld == IntPtr.Zero )
    //      return;

    //   Bounds selectionBounds = GetCellBounds( position );

    //   IndexVertexBufferCollector collector = GetGeometriesForNavigationMesh( selectionBounds );
    //   if( collector.resultVertexCount &amp;gt; 0 )
    //   {
    //      Vec3[] vertices = collector.resultVertices;
    //      int[] indices = collector.resultIndices;
    //      int vertexCount = collector.resultVertexCount;
    //      int indexCount = collector.resultIndexCount;

    //      //convert to Recast space
    //      for( int n = 0; n &amp;lt; vertexCount; n++ )
    //         vertices[ n ] = ToRecastVec3( vertices[ n ] );

    //      unsafe
    //      {
    //         Log.Info( &amp;quot;SetGeometry: Vertices: {0}, Indices {1}&amp;quot;, vertexCount, indexCount );

    //         fixed( Vec3* pVertices = vertices )
    //         fixed( int* pIndices = indices )
    //         {
    //            Wrapper.SetGeometry( recastWorld, (IntPtr)pVertices, vertexCount, (IntPtr)pIndices, indexCount );
    //            Vec3 targetPosition = ToRecastVec3( position );
    //            Wrapper.BuildTile( recastWorld, ref targetPosition );

    //            //redraw debug mesh
    //            debugNavigationMeshVertices = null;
    //            debugNavigationMeshIndices = null;
    //         }
    //      }
    //   }
    //}

    //public void RemoveTile( Vec3 position )
    //{
    //   if( recastWorld == IntPtr.Zero )
    //      return;

    //   Vec3 targetPosition = ToRecastVec3( position );
    //   Wrapper.RemoveTile( recastWorld, ref targetPosition );

    //   debugNavigationMeshVertices = null;
    //   debugNavigationMeshIndices = null;
    //}

    protected override void OnDeleteSubscribedToDeletionEvent( Entity entity )
    {
    	base.OnDeleteSubscribedToDeletionEvent( entity );

    	if( Geometries.Contains( entity ) )
        RemoveGeometry( entity );
    }

    protected override void OnRender( Camera camera )
    {
    	base.OnRender( camera );

    	if( camera.Purpose == Camera.Purposes.MainCamera )
    	{
        //if( drawTileGrid )
        //   DebugRenderTileGrid( camera );

        bool drawMapEditor = false;
        if( MapEditorInterface.Instance != null )
        {
        	//bool allow
        	try
        	{
            bool v = (bool)MapEditorInterface.Instance.SendCustomMessage( this, &amp;quot;IsAllowToRenderNavigationMesh&amp;quot;, null );
            if( v )
            	drawMapEditor = true;
        	}
        	catch { }
        }

        if( alwaysDrawNavMesh || drawMapEditor )
        	DebugDrawNavMesh( camera );

        //draw global bounds
        bool mapEditorIsSelected = MapEditorInterface.Instance != null &amp;amp;&amp;amp;
        	MapEditorInterface.Instance.IsEntitySelected( this );
        if( mapEditorIsSelected )
        {
        	camera.DebugGeometry.Color = new ColorValue( 0, 0, 1 );
        	Bounds bounds = new Bounds( boundsMin, boundsMax );
        	camera.DebugGeometry.AddBounds( bounds );
        }



        //bool mapEditorIsSelected = false;
        //if( MapEditorInterface.Instance != null &amp;amp;&amp;amp; MapEditorInterface.Instance.IsEntitySelected( this ) )
        //   mapEditorIsSelected = true;

        //if( alwaysDrawNavMesh || mapEditorIsSelected )
        //   DebugDrawNavMesh( camera );

        ////draw global bounds
        //if( mapEditorIsSelected )
        //{
        //   camera.DebugGeometry.Color = new ColorValue( 0, 0, 1 );

        //   Bounds bounds = new Bounds( boundsMin, boundsMax );
        //   camera.DebugGeometry.AddBounds( bounds );
        //}
    	}
    }

    public void DebugDrawNavMesh( Camera camera )
    {
    	if( recastWorld == IntPtr.Zero )
        return;

    	if( debugNavigationMeshVertices == null )
    	{
        if( !GetDebugNavigationMeshGeometry( out debugNavigationMeshVertices ) )
        	return;

        debugNavigationMeshIndices = new int[ debugNavigationMeshVertices.Length ];
        for( int n = 0; n &amp;lt; debugNavigationMeshIndices.Length; n++ )
        	debugNavigationMeshIndices[ n ] = n;
    	}

    	//Render NavMesh
    	{
        Mat4 transform = new Mat4( Mat3.Identity, new Vec3( 0, 0, .1f ) );

        //draw without depth test
        {
        	camera.DebugGeometry.SetSpecialDepthSettings( false, false );

        	camera.DebugGeometry.Color = new ColorValue( 0, 1, 0, .1f );
        	camera.DebugGeometry.AddVertexIndexBuffer( debugNavigationMeshVertices,
            debugNavigationMeshIndices, transform, false, true );

        	camera.DebugGeometry.Color = new ColorValue( 1, 1, 0, .1f );
        	camera.DebugGeometry.AddVertexIndexBuffer( debugNavigationMeshVertices,
            debugNavigationMeshIndices, transform, true, true );

        	camera.DebugGeometry.RestoreDefaultDepthSettings();
        }

        //draw with depth test
        {
        	camera.DebugGeometry.Color = new ColorValue( 0, 1, 0, .3f );
        	camera.DebugGeometry.AddVertexIndexBuffer( debugNavigationMeshVertices,
            debugNavigationMeshIndices, transform, false, true );
        	camera.DebugGeometry.Color = new ColorValue( 1, 1, 0, .3f );
        	camera.DebugGeometry.AddVertexIndexBuffer( debugNavigationMeshVertices,
            debugNavigationMeshIndices, transform, true, true );
        }
    	}
    }

    //void DebugRenderTileGrid( Camera camera )
    //{
    //   //make a tile grid
    //   {
    //      if( tileGridMeshVertices == null )
    //         CreateTileGridMesh( out tileGridMeshVertices, out tileGridMeshIndices, false );

    //      camera.DebugGeometry.Color = new ColorValue( 0f, 1f, 0f, .3f );
    //      camera.DebugGeometry.AddVertexIndexBuffer( tileGridMeshVertices, tileGridMeshIndices,
    //         Mat4.Identity, true, false );
    //   }

    //   //make a cell grid
    //   {
    //      if( cellGridMeshVertices == null )
    //         CreateTileGridMesh( out cellGridMeshVertices, out cellGridMeshIndices, true );

    //      camera.DebugGeometry.Color = new ColorValue( 1f, 0f, 0f, .3f );
    //      camera.DebugGeometry.AddVertexIndexBuffer( cellGridMeshVertices, cellGridMeshIndices,
    //         Mat4.Identity, true, false );
    //   }

    //   //add the bounds
    //   {
    //      camera.DebugGeometry.Color = new ColorValue( 0f, 1f, 1f, .6f );
    //      camera.DebugGeometry.AddBounds( new Bounds( boundsMin, boundsMax ) );
    //   }
    //}

    //void CreateTileGridMesh( out Vec3[] vertices, out int[] indices, bool cellSplit )
    //{
    //   int size;
    //   if( cellSplit )
    //      size = 128;
    //   else
    //      size = 64;

    //   vertices = new Vec3[ ( size + 1 ) * ( size + 1 ) ];
    //   {
    //      int vertexPosition = 0;
    //      for( int y = 0; y &amp;lt; size + 1; y++ )
    //      {
    //         for( int x = 0; x &amp;lt; size + 1; x++ )
    //         {
    //            xx;
    //            //SodanKerjuu: mirrored Y because of Recast coordinate system
    //            if( cellSplit )
    //            {
    //               vertices[ vertexPosition ] = new Vec3(
    //                  boundsMin.X + x * tileSize * cellSize,
    //                  boundsMax.Y - y * tileSize * cellSize,
    //                  boundsMin.Z + gridHeight * ( boundsMax.Z - boundsMin.Z ) );
    //            }
    //            else
    //            {
    //               vertices[ vertexPosition ] = new Vec3(
    //                  boundsMin.X + x * tileSize,
    //                  boundsMax.Y - y * tileSize,
    //                  boundsMin.Z + gridHeight * ( boundsMax.Z - boundsMin.Z ) );
    //            }
    //            vertexPosition++;
    //         }
    //      }
    //   }

    //   indices = new int[ size * size * 6 ];
    //   {
    //      int indexPosition = 0;
    //      for( int y = 0; y &amp;lt; size; y++ )
    //      {
    //         for( int x = 0; x &amp;lt; size; x++ )
    //         {
    //            indices[ indexPosition + 0 ] = ( size + 1 ) * y + x;
    //            indices[ indexPosition + 1 ] = ( size + 1 ) * y + x + 1;
    //            indices[ indexPosition + 2 ] = ( size + 1 ) * ( y + 1 ) + x + 1;
    //            indices[ indexPosition + 3 ] = ( size + 1 ) * ( y + 1 ) + x + 1;
    //            indices[ indexPosition + 4 ] = ( size + 1 ) * ( y + 1 ) + x;
    //            indices[ indexPosition + 5 ] = ( size + 1 ) * y + x;
    //            indexPosition += 6;
    //         }
    //      }
    //   }
    //}

    public bool GetDebugNavigationMeshGeometry( out Vec3[] vertices )
    {
    	vertices = null;

    	if( recastWorld == IntPtr.Zero )
        return false;

    	unsafe
    	{
        Vec3* nativeVertices;
        int vertexCount;
        if( !Wrapper.GetNavigationMesh( recastWorld, out nativeVertices, out vertexCount ) )
        	return false;

        vertices = new Vec3[ vertexCount ];
        for( int n = 0; n &amp;lt; vertices.Length; n++ )
        	vertices[ n ] = ToEngineVec3( nativeVertices[ n ] );

        Wrapper.FreeMemory( (IntPtr)nativeVertices );
    	}

    	return true;
    }

    public void AddGeometry( Entity entity )
    {
    	if( geometries.Contains( entity ) )
        Log.Fatal( &amp;quot;RecastNavigationSystem: AddGeometry: This entity is already added.&amp;quot; );

    	geometries.Add( entity );
    	SubscribeToDeletionEvent( entity );
    }

    public void RemoveGeometry( Entity entity )
    {
    	if( !geometries.Contains( entity ) )
        return;

    	UnsubscribeToDeletionEvent( entity );
    	geometries.Remove( entity );
    }

    public void RemoveAllGeometries()
    {
    	while( geometries.Count != 0 )
        RemoveGeometry( geometries[ geometries.Count - 1 ] );
    }
	}
}
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;/div&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Fri, 31 Jan 2020 06:14:56 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=164#p164</guid>
		</item>
		<item>
			<title>Не создается проект</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=161#p161</link>
			<description>&lt;p&gt;Но тут ошибка на вашей стороне по какой то причине отказ в доступе данной программе. У программы нет прав на доступ ищите в безопасности где то там точно подсказать не могу (((&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Fri, 17 Jan 2020 16:54:12 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=161#p161</guid>
		</item>
		<item>
			<title>Будет ли развитие форума после версии движка 3.х.х?</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=157#p157</link>
			<description>&lt;p&gt;Хорошо я создал раздел для новой версии&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Sat, 04 Jan 2020 22:07:00 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=157#p157</guid>
		</item>
		<item>
			<title>NeoAxis Engine Урок : Обзор класса Turret(турель) и создание турели.</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=153#p153</link>
			<description>&lt;p&gt;А нельзя ли переснять с нормальным звуком? Или звуковую дорожку как то почистить фильтрами? Очень сильные помехи, в виде шума. Местами, просто не разобрать.&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Kamil)</author>
			<pubDate>Mon, 23 Dec 2019 18:29:03 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=153#p153</guid>
		</item>
		<item>
			<title>Инструмент создания музыки(бесплатно)</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=148#p148</link>
			<description>&lt;p&gt;LMMS &lt;br /&gt;Отличный бесплатный инструмент для создания музыки! &lt;/p&gt;
						&lt;p&gt;Поддержка: Linux, Windows, Mac. &lt;/p&gt;
						&lt;p&gt;Оф.Сайт: &lt;a href=&quot;https://lmms.io&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;https://lmms.io&lt;/a&gt;&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Fri, 29 Nov 2019 19:04:43 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=148#p148</guid>
		</item>
		<item>
			<title>Поглощение урона для динамического класса.</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=147#p147</link>
			<description>&lt;p&gt;Исправление бага что бы при вычитании урона персоонаж/предмет не получал лечение от вычитаемого урона если значение становиться отрицательным необходимо добавить в код после строки:&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 4.5em&quot;&gt;&lt;pre&gt;486 float realDamage = damage * ReceiveDamageCoefficient * _type.reductionBy - _type.minusDamage;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
						&lt;p&gt;добавить&lt;br /&gt; &lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 13.5em&quot;&gt;&lt;pre&gt;if(_type.minusDamage != 0)
    {
    	if(realDamage &amp;lt;= 0)
    	{
        realDamage = 0;
    	}
    }&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Sat, 09 Nov 2019 19:03:11 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=147#p147</guid>
		</item>
		<item>
			<title>Класс BTR</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=145#p145</link>
			<description>&lt;p&gt;Универсальный класс для создания БТР или других колесных авто с вращающейся башней.&lt;/p&gt;
						&lt;p&gt;Так же класс авто(car.cs) получает возможность стрелять из оружия(gun.cs) прикрепленных к ним&lt;/p&gt;
						&lt;p&gt;&lt;a href=&quot;https://drive.google.com/open?id=1IgjCZ_cgGnEJ6_exBHjPngOgABvQ9SUN&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Скачать&lt;/a&gt;&lt;/p&gt;
						&lt;p&gt;Автор: &lt;strong&gt;Andrey Holkin&lt;/strong&gt;&lt;/p&gt;
						&lt;p&gt;&lt;span style=&quot;color: red&quot;&gt;Бесплатная лицензия - Все расширенное использование&lt;/span&gt;&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Mon, 07 Oct 2019 06:25:28 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=145#p145</guid>
		</item>
		<item>
			<title>Шифрование папки ресурсов в готовом проекте</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=143#p143</link>
			<description>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;quote-box spoiler-box&quot;&gt;&lt;div onclick=&quot;$(this).toggleClass(&#039;visible&#039;); $(this).next().toggleClass(&#039;visible&#039;);&quot;&gt;Data.archive&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;loadingPriority = 0.5&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Wed, 14 Aug 2019 07:34:41 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=143#p143</guid>
		</item>
		<item>
			<title>NeoAxis Engine Урок :Создание NPS с нуля</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=142#p142</link>
			<description>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;quote-box spoiler-box&quot;&gt;&lt;div onclick=&quot;$(this).toggleClass(&#039;visible&#039;); $(this).next().toggleClass(&#039;visible&#039;);&quot;&gt;Bandit.animationTree&lt;/div&gt;&lt;blockquote&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 35em&quot;&gt;&lt;pre&gt;blocks
{
	block Output
	{
    uin = 1
    in = 100
	}

	/////////////////////////////////////////////
	//Attack trigger

	block Trigger
	{
    uin = 100
    triggerName = &amp;quot;fire&amp;quot;
    off = 500
    on = 101
	}

	block Animation
	{
    uin = 101
    animationName = &amp;quot;attack1&amp;quot;
    speed = 1
	}

	/////////////////////////////////////////////
	//Attack stomach gun trigger

	block Trigger
	{
    uin = 500
    triggerName = &amp;quot;fireStomachGun&amp;quot;
    off = 200
    on = 501
	}

	block Animation
	{
    uin = 501
    animationName = &amp;quot;attack2&amp;quot;
    speed = 1
	}

	/////////////////////////////////////////////
	//Select walk or idle

	block Transition
	{
    uin = 200
	
    selectedIndexSource = 201
    animation0 = 400 // go to idle animation
    animation1 = 310 // go to walk
	}

	block Parameter
	{
    uin = 201
    parameterName = &amp;quot;move&amp;quot;
    value = 0
	}

	/////////////////////////////////////////////
	//Walk

	block Direction8Sides
	{
    uin = 310

    angleSource = 311
    speedMultiplierSource = 316

    forward = 312
    backward = 315
    left = 318
    right = 319
    forwardLeft = 312
    forwardRight = 312
    backwardLeft = 315
    backwardRight = 315
	}

	block Parameter
	{
    uin = 311
    parameterName = &amp;quot;moveAngle&amp;quot;
    value = 0
	}

	block Parameter
	{
    uin = 316
    parameterName = &amp;quot;moveSpeed&amp;quot;
    value = 1
	}

	block Animation
	{
    uin = 312
    animationName = &amp;quot;run&amp;quot;
    speed = 0.45
	}

	block Animation
	{
    uin = 315
    animationName = &amp;quot;run&amp;quot;
    speed = -0.4
	}

	block Animation
	{
    uin = 318
    animationName = &amp;quot;left&amp;quot;
    speed = 1
	}

	block Animation
	{
    uin = 319
    animationName = &amp;quot;left&amp;quot;
    speed = -1
	}
	/////////////////////////////////////////////
	//Idle

	block Animation
	{
    uin = 400
    animationName = &amp;quot;idle&amp;quot;
    speed = 1
	}

	/////////////////////////////////////////////

}
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;/div&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Fri, 19 Jul 2019 06:05:31 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=142#p142</guid>
		</item>
		<item>
			<title>NeoAxis Engine Урок :Обзор на класс MeleeWeapon (Оружие ближнего боя)</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=141#p141</link>
			<description>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 6em&quot;&gt;&lt;pre&gt;[FieldSerialize]
    float ReductionBy = 1.0f;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 12em&quot;&gt;&lt;pre&gt;[DefaultValue( 1.0f )]
    public float reductionBy
    {
    	get { return ReductionBy; }
    	set { ReductionBy = value; }
    }&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;code-box&quot;&gt;&lt;strong class=&quot;legend&quot;&gt;Код:&lt;/strong&gt;&lt;div class=&quot;blockcode&quot;&gt;&lt;div class=&quot;scrollbox&quot; style=&quot;height: 4.5em&quot;&gt;&lt;pre&gt;float realDamage = damage * ReceiveDamageCoefficient * _type.reductionBy;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Wed, 17 Jul 2019 11:30:14 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=141#p141</guid>
		</item>
		<item>
			<title>Сделать DedicatedServer доступным для внешних пользователей</title>
			<link>http://neoaxis.bbon.ru/viewtopic.php?pid=140#p140</link>
			<description>&lt;p&gt;В этом уроке я вам расскажу как сделать доступный ваш сервер из интернета что бы люди могли подключатся к вам из внешнего интернета. &lt;/p&gt;
						&lt;p&gt;Для начало нам необходимо иметь статический IP адрес, если это так то вам необходимо про сканировать порты(узнать открытые)&lt;/p&gt;
						&lt;p&gt;Это можно сделать на сайте &lt;a href=&quot;https://ivit.pro/services/avail-services/&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;https://ivit.pro/services/avail-services/&lt;/a&gt;&lt;/p&gt;
						&lt;p&gt;&lt;a href=&quot;https://radikal.ru&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;postimg&quot; loading=&quot;lazy&quot; src=&quot;https://d.radikal.ru/d10/1907/2e/530244731148.jpg&quot; alt=&quot;https://d.radikal.ru/d10/1907/2e/530244731148.jpg&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
						&lt;p&gt;Теперь мы знаем какой у нас порт открытый и люди смогу через него конектится к нашему серверу.&lt;/p&gt;
						&lt;p&gt;Теперь нам необходимо изменить в коде наш порт на открытый&lt;/p&gt;
						&lt;p&gt;&lt;a href=&quot;https://radikal.ru&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;&lt;img class=&quot;postimg&quot; loading=&quot;lazy&quot; src=&quot;https://d.radikal.ru/d34/1907/98/7014f23728bb.jpg&quot; alt=&quot;https://d.radikal.ru/d34/1907/98/7014f23728bb.jpg&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
						&lt;p&gt;Всё компилируем проект и после этого ваш сервер доступен для внешних пользователей.&lt;/p&gt;</description>
			<author>mybb@mybb.ru (Wowka70)</author>
			<pubDate>Wed, 10 Jul 2019 07:13:02 +0300</pubDate>
			<guid>http://neoaxis.bbon.ru/viewtopic.php?pid=140#p140</guid>
		</item>
	</channel>
</rss>
