using CodeWalker.GameFiles;
using CodeWalker.Properties;
using CodeWalker.Rendering;
using CodeWalker.World;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.XInput;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CodeWalker.Forms
{
    public partial class ModelForm : Form, DXForm
    {
        public Form Form { get { return this; } } //for DXForm/DXManager use
        DXManager dxman = new DXManager();
        public DXManager DXMan { get { return dxman; } }
        Device currentdevice;
        public Device Device { get { return currentdevice; } }
        object rendersyncroot = new object();
        public object RenderSyncRoot { get { return rendersyncroot; } }

        ShaderManager shaders;

        volatile bool formopen = false;
        //volatile bool running = false;
        volatile bool pauserendering = false;
        //volatile bool initialised = false;

        Stopwatch frametimer = new Stopwatch();
        Camera camera = new Camera();
        Timecycle timecycle = new Timecycle();
        Weather weather = new Weather();
        Clouds clouds = new Clouds();

        bool MouseLButtonDown = false;
        bool MouseRButtonDown = false;
        int MouseX;
        int MouseY;
        System.Drawing.Point MouseDownPoint;
        System.Drawing.Point MouseLastPoint;



        //public GameFileCache GameFileCache { get { return gameFileCache; } }
        //GameFileCache gameFileCache = new GameFileCache();
        RenderableCache renderableCache = new RenderableCache();


        Vector3 prevworldpos = new Vector3(0, 0, 0); //also the start pos

        Entity camEntity = new Entity();
        volatile bool kbmovefwd = false;
        volatile bool kbmovebck = false;
        volatile bool kbmovelft = false;
        volatile bool kbmovergt = false;
        volatile bool kbmoveup = false;
        volatile bool kbmovedn = false;
        volatile bool kbjump = false;

        KeyBindings keyBindings = new KeyBindings(Settings.Default.KeyBindings);
        //bool iseditmode = false;


        float timeofday = 12.0f;
        bool controltimeofday = true;
        bool timerunning = false;
        float timespeed = 0.5f;//min/sec
        //string weathertype = "";
        string individualcloudfrag = "contrails";
        Vector4 currentWindVec = Vector4.Zero;
        float currentWindTime = 0.0f;

        bool controllightdir = !Settings.Default.Skydome;//true; //if not, use timecycle
        float lightdirx = 2.25f;//radians // approx. light dir on map satellite view
        float lightdiry = 0.65f;//radians  - used for manual light placement
        bool renderskydome = Settings.Default.Skydome;

        bool rendernaturalambientlight = true;
        bool renderartificialambientlight = true;
        ShaderGlobalLights globalLights = new ShaderGlobalLights();

        double currentRealTime = 0;
        int framecount = 0;
        float fcelapsed = 0.0f;
        int fps = 0;

        bool initedOk = false;


        private string fileName;
        public string FileName
        {
            get { return fileName; }
            set
            {
                fileName = value;
                UpdateFormTitle();
            }
        }
        public string FilePath { get; set; }

        YdrFile Ydr = null;
        YddFile Ydd = null;
        YftFile Yft = null;
        YbnFile Ybn = null;
        YptFile Ypt = null;
        YnvFile Ynv = null;

        bool waitforchildrentoload = true;
        bool rendercollisionmeshes = false;// Settings.Default.ShowCollisionMeshes;

        bool renderskeletons = true;
        List<Renderable> renderskeletonlist = new List<Renderable>();
        List<VertexTypePC> skeletonLineVerts = new List<VertexTypePC>();


        bool CtrlPressed = false;
        bool ShiftPressed = false;

        Controller xbcontroller = null;
        State xbcontrollerstate;
        State xbcontrollerstateprev;
        Vector4 xbmainaxes = Vector4.Zero;
        Vector4 xbmainaxesprev = Vector4.Zero;
        Vector2 xbtrigs = Vector2.Zero;
        Vector2 xbtrigsprev = Vector2.Zero;
        float xbcontrolvelocity = 0.0f;

        bool toolsPanelResizing = false;
        int toolsPanelResizeStartX = 0;
        int toolsPanelResizeStartLeft = 0;
        int toolsPanelResizeStartRight = 0;

        Dictionary<DrawableBase, bool> DrawableDrawFlags = new Dictionary<DrawableBase, bool>();
        Dictionary<DrawableModel, bool> ModelDrawFlags = new Dictionary<DrawableModel, bool>();
        Dictionary<DrawableGeometry, bool> GeometryDrawFlags = new Dictionary<DrawableGeometry, bool>();


        bool enableGrid = true;
        float gridSize = 1.0f;
        int gridCount = 40;
        List<VertexTypePC> gridVerts = new List<VertexTypePC>();
        object gridSyncRoot = new object();

        GameFileCache gameFileCache = null;
        Archetype currentArchetype = null;
        bool updateArchetypeStatus = true;


        public ModelForm(ExploreForm ExpForm = null)
        {
            InitializeComponent();


            gameFileCache = ExpForm?.GetFileCache(); 

            initedOk = dxman.Init(this, false);
        }

        private void Init()
        {
            //called from WorldForm_Load

            if (!initedOk)
            {
                Close();
                return;
            }


            MouseWheel += ModelForm_MouseWheel;

            string fldr = Settings.Default.GTAFolder;
            if (string.IsNullOrEmpty(fldr) || !Directory.Exists(fldr))
            {
                SelectFolderForm f = new SelectFolderForm();
                f.ShowDialog();
                if (f.Result == DialogResult.OK)
                {
                    fldr = f.SelectedFolder;
                }
                else
                {
                    Close();
                    return;
                }
            }
            if (!Directory.Exists(fldr))
            {
                MessageBox.Show("The specified folder does not exist:\n" + fldr);
                Close();
                return;
            }
            if (!File.Exists(fldr + "\\gta5.exe"))
            {
                MessageBox.Show("GTA5.exe not found in folder:\n" + fldr);
                Close();
                return;
            }

            Settings.Default.GTAFolder = fldr; //seems ok, save it for later




            MetaName[] texsamplers = RenderableGeometry.GetTextureSamplerList();
            foreach (var texsampler in texsamplers)
            {
                TextureSamplerComboBox.Items.Add(texsampler);
            }
            //TextureSamplerComboBox.SelectedIndex = 0;//LoadSettings will do this..


            UpdateGridVerts();
            GridSizeComboBox.SelectedIndex = 1;
            GridCountComboBox.SelectedIndex = 1;



            InitController();


            dxman.Start();
        }

        private void InitController()
        {
            xbcontroller = new Controller(UserIndex.One);
            if (!xbcontroller.IsConnected)
            {
                var controllers = new[] { new Controller(UserIndex.Two), new Controller(UserIndex.Three), new Controller(UserIndex.Four) };
                foreach (var selectControler in controllers)
                {
                    if (selectControler.IsConnected)
                    {
                        xbcontroller = selectControler;
                        xbcontrollerstate = xbcontroller.GetState();
                        xbcontrollerstateprev = xbcontrollerstate;
                        break;
                    }
                }
            }
            else
            {
                xbcontrollerstate = xbcontroller.GetState();
                xbcontrollerstateprev = xbcontrollerstate;
            }

        }


        public void InitScene(Device device)
        {
            currentdevice = device;

            int width = ClientSize.Width;
            int height = ClientSize.Height;

            try
            {
                shaders = new ShaderManager(device, dxman);
                shaders.OnWindowResize(width, height); //init the buffers
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error loading shaders!\n" + ex.ToString());
                return;
            }

            //shaders.hdrLumBlendSpeed = 1000.0f;

            renderableCache.OnDeviceCreated(device);

            camera.OnWindowResize(width, height); //init the projection stuff
            camera.FollowEntity = camEntity;
            camera.FollowEntity.Position = prevworldpos;
            camera.FollowEntity.Orientation = Quaternion.LookAtLH(Vector3.Zero, Vector3.Up, Vector3.ForwardLH);
            camera.TargetDistance = 2.0f;
            camera.CurrentDistance = 2.0f;
            camera.TargetRotation.Y = 0.2f;
            camera.CurrentRotation.Y = 0.2f;

            LoadSettings();


            formopen = true;
            new Thread(new ThreadStart(ContentThread)).Start();

            frametimer.Start();
        }
        public void CleanupScene()
        {
            formopen = false;

            renderableCache.OnDeviceDestroyed();

            shaders.Dispose();


            //int count = 0;
            //while (running && (count < 5000)) //wait for the content thread to exit gracefully
            //{
            //    Thread.Sleep(1);
            //    count++;
            //}

            currentdevice = null;
        }
        public void BuffersResized(int w, int h)
        {
            lock (rendersyncroot)
            {
                camera.OnWindowResize(w, h);
                shaders.OnWindowResize(w, h);
            }
        }
        public void RenderScene(DeviceContext context)
        {
            float elapsed = (float)frametimer.Elapsed.TotalSeconds;
            framecount++;
            fcelapsed += elapsed;
            if (fcelapsed >= 0.5f)
            {
                fps = framecount * 2;
                framecount = 0;
                fcelapsed -= 0.5f;
            }
            if (elapsed > 0.1f) elapsed = 0.1f;
            frametimer.Restart();

            currentRealTime += elapsed;

            if (pauserendering) return;

            if (!Monitor.TryEnter(rendersyncroot, 50))
            { return; } //couldn't get a lock, try again next time
            //Monitor.Enter(rendersyncroot);

            UpdateControlInputs(elapsed);

            UpdateTimeOfDay(elapsed);

            weather.Update(elapsed);

            //clouds.Update(elapsed);

            UpdateWindVector(elapsed);

            UpdateGlobalLights();

            camera.SetMousePosition(MouseLastPoint.X, MouseLastPoint.Y);

            camera.Update(elapsed);

            //UpdateWidgets();

            //HilightBoxes.Clear();
            //BoundingBoxes.Clear();
            //BoundingSpheres.Clear();
            //BeginMouseHitTest();
            BeginFrame();


            dxman.ClearRenderTarget(context);

            shaders.BeginFrame(context, currentRealTime, elapsed);

            shaders.EnsureShaderTextures(gameFileCache, renderableCache);

            RenderSky(context);

            //RenderClouds(context);

            shaders.ClearDepth(context);

            //if (renderworld || rendermaps)
            //{
            //    RenderWorld();
            //    if (rendermaps)
            //    {
            //        RenderYmaps();
            //    }
            //}
            //else
            //{
            RenderSingleItem();
            //}

            RenderGrid(context);

            shaders.RenderQueued(context, camera, currentWindVec);

            RenderSkeletons(context);

            //RenderBounds(context);

            //RenderSelection(context);

            //RenderMoused(context);

            //RenderSelectionGeometry(context);

            shaders.RenderFinalPass(context);

            //RenderMarkers(context);

            //RenderWidgets(context);

            renderableCache.RenderThreadSync();

            Monitor.Exit(rendersyncroot);

            //UpdateMarkerSelectionPanelInvoke();
        }


        private void ContentThread()
        {
            //main content loading thread.
            //running = true;

            //UpdateStatus("Scanning...");

            //try
            //{
            //    GTA5Keys.LoadFromPath(Settings.Default.GTAFolder); //now loads from magic
            //}
            //catch
            //{
            //    MessageBox.Show("Keys not found! This shouldn't happen.");
            //    Close();
            //    return;
            //}

            //gameFileCache.Init(UpdateStatus, LogError);

            ////UpdateDlcListComboBox(gameFileCache.DlcNameList);
            ////EnableCacheDependentUI();
            ////LoadWorld();


            //initialised = true;

            ////EnableDLCModsUI();

            UpdateStatus("Ready");


            while (formopen && !IsDisposed) //main asset loop
            {

                if ((gameFileCache != null) && (gameFileCache.IsInited))
                {
                    if (!timecycle.Inited)
                    {
                        //UpdateStatus("Loading timecycles...");
                        timecycle.Init(gameFileCache, UpdateStatus);
                        timecycle.SetTime(timeofday);
                        //UpdateStatus("Timecycles loaded.");
                    }
                    if (renderskydome)
                    {
                        if (!weather.Inited)
                        {
                            //UpdateStatus("Loading weather...");
                            weather.Init(gameFileCache, UpdateStatus, timecycle);
                            //UpdateStatus("Weather loaded.");
                        }
                        //if (!clouds.Inited)
                        //{
                        //    UpdateStatus("Loading clouds...");
                        //    clouds.Init(gameFileCache, UpdateStatus, weather);
                        //    UpdateStatus("Clouds loaded.");
                        //}
                    }
                }



                //if ((gameFileCache != null) && (gameFileCache.IsInited))
                //{
                //    gameFileCache.ContentThreadProc();
                //}

                renderableCache.ContentThreadProc();

                if (!(renderableCache.ItemsStillPending)) //gameFileCache.ItemsStillPending || 
                {
                    Thread.Sleep(1); //sleep if there's nothing to do
                }
            }

            //gameFileCache.Clear();

            //running = false;
        }






        private void LoadSettings()
        {
            var s = Settings.Default;
            //WindowState = s.WindowMaximized ? FormWindowState.Maximized : WindowState;
            //FullScreenCheckBox.Checked = s.FullScreen;
            WireframeCheckBox.Checked = s.Wireframe;
            HDRRenderingCheckBox.Checked = s.HDR;
            ShadowsCheckBox.Checked = s.Shadows;
            SkydomeCheckBox.Checked = s.Skydome;
            RenderModeComboBox.SelectedIndex = Math.Max(RenderModeComboBox.FindString(s.RenderMode), 0);
            TextureSamplerComboBox.SelectedIndex = Math.Max(TextureSamplerComboBox.FindString(s.RenderTextureSampler), 0);
            TextureCoordsComboBox.SelectedIndex = Math.Max(TextureCoordsComboBox.FindString(s.RenderTextureSamplerCoord), 0);
            AnisotropicFilteringCheckBox.Checked = s.AnisotropicFiltering;
            //ErrorConsoleCheckBox.Checked = s.ShowErrorConsole;
            //StatusBarCheckBox.Checked = s.ShowStatusBar;
        }






        private Archetype TryGetArchetype(uint hash)
        {
            if ((gameFileCache == null) || (!gameFileCache.IsInited)) return null;

            var arch = gameFileCache.GetArchetype(hash);

            if ((arch != null) && (arch != currentArchetype) && (updateArchetypeStatus))
            {
                UpdateStatus("Archetype: " + arch.Name.ToString());
                currentArchetype = arch;
                updateArchetypeStatus = false;
            }

            return arch;
        }

        private DrawableBase TryGetDrawable(Archetype arche)
        {
            if (arche == null) return null;
            if ((gameFileCache == null) || (!gameFileCache.IsInited)) return null;

            uint drawhash = arche.Hash;
            DrawableBase drawable = null;
            if ((arche.DrawableDict != 0))// && (arche.DrawableDict != arche.Hash))
            {
                //try get drawable from ydd...
                YddFile ydd = gameFileCache.GetYdd(arche.DrawableDict);
                if (ydd != null)
                {
                    if (ydd.Loaded && (ydd.Dict != null))
                    {
                        Drawable d;
                        ydd.Dict.TryGetValue(drawhash, out d); //can't out to base class?
                        drawable = d;
                        if (drawable == null)
                        {
                            return null; //drawable wasn't in dict!!
                        }
                    }
                    else
                    {
                        return null; //ydd not loaded yet, or has no dict
                    }
                }
                else
                {
                    //return null; //couldn't find drawable dict... quit now?
                }
            }
            if (drawable == null)
            {
                //try get drawable from ydr.
                YdrFile ydr = gameFileCache.GetYdr(drawhash);
                if (ydr != null)
                {
                    if (ydr.Loaded)
                    {
                        drawable = ydr.Drawable;
                    }
                }
                else
                {
                    YftFile yft = gameFileCache.GetYft(drawhash);
                    if (yft != null)
                    {
                        if (yft.Loaded)
                        {
                            if (yft.Fragment != null)
                            {
                                drawable = yft.Fragment.Drawable;
                            }
                        }
                    }
                }
            }

            return drawable;
        }

        private Renderable TryGetRenderable(Archetype arche, DrawableBase drawable, uint txdHash = 0)
        {
            if (drawable == null) return null;
            //BUG: only last texdict used!! needs to cache textures per archetype........
            //(but is it possible to have the same drawable with different archetypes?)
            uint texDict = (arche != null) ? arche.TextureDict.Hash : txdHash;
            uint clipDict = (arche != null) ? arche.ClipDict.Hash : 0;
            var yptTextDict = Ypt?.PtfxList?.TextureDictionary;

            Renderable rndbl = renderableCache.GetRenderable(drawable);
            if (rndbl == null) return null;


            var gfc = gameFileCache;
            if ((gfc != null) && (!gfc.IsInited))
            {
                gfc = null;
            }


            //if (clipDict != 0)
            //{
            //    YcdFile ycd = gameFileCache.GetYcd(clipDict);
            //    if ((ycd != null) && (ycd.Loaded))
            //    {
            //        ClipMapEntry cme;
            //        if (ycd.ClipMap.TryGetValue(arche.Hash, out cme))
            //        {
            //        }
            //        else
            //        { }
            //    }
            //}


            bool alltexsloaded = true;
            int missingtexcount = 0;
            for (int mi = 0; mi < rndbl.HDModels.Length; mi++)
            {
                var model = rndbl.HDModels[mi];

                //if (!RenderIsModelFinalRender(model) && !renderproxies)
                //{
                //    continue; //filter out reflection proxy models...
                //}


                foreach (var geom in model.Geometries)
                {
                    if (geom.Textures != null)
                    {
                        for (int i = 0; i < geom.Textures.Length; i++)
                        {
                            var tex = geom.Textures[i];
                            var ttex = tex as Texture;
                            RenderableTexture rdtex = null;
                            if ((ttex == null) && (tex != null))
                            {
                                //TextureRef means this RenderableTexture needs to be loaded from texture dict...
                                if (yptTextDict != null) //for ypt files, first try the embedded tex dict..
                                {
                                    var dtex = yptTextDict.Lookup(tex.NameHash);
                                    rdtex = renderableCache.GetRenderableTexture(dtex);
                                }
                                else if (texDict != 0)
                                {
                                    YtdFile ytd = gfc?.GetYtd(texDict);
                                    if ((ytd != null) && (ytd.Loaded) && (ytd.TextureDict != null))
                                    {
                                        var dtex = ytd.TextureDict.Lookup(tex.NameHash);
                                        if (dtex == null)
                                        {
                                            //not present in dictionary... check already loaded texture dicts...
                                            YtdFile ytd2 = gfc?.TryGetTextureDictForTexture(tex.NameHash);
                                            if ((ytd2 != null) && (ytd2.Loaded) && (ytd2.TextureDict != null))
                                            {
                                                dtex = ytd2.TextureDict.Lookup(tex.NameHash);
                                            }
                                            else
                                            {
                                                //couldn't find texture dict?
                                                //first try going through ytd hierarchy...
                                                dtex = gfc?.TryFindTextureInParent(tex.NameHash, texDict);


                                                //if (dtex == null)
                                                //{ //try for a texture dict with the same hash as the archetype?
                                                //    dtex = gameFileCache.TryFindTextureInParent(tex.TextureRef.NameHash, arche.Hash);
                                                //    if (dtex != null)
                                                //    { }
                                                //}
                                            }
                                        }
                                        if (dtex != null)
                                        {
                                            geom.Textures[i] = dtex; //cache it for next time to avoid the lookup...
                                            rdtex = renderableCache.GetRenderableTexture(dtex);
                                        }
                                        if (rdtex == null)
                                        { } //nothing to see here :(
                                    }
                                    else if ((ytd == null))
                                    {
                                        Texture dtex = null;
                                        if (drawable.ShaderGroup.TextureDictionary != null)
                                        {
                                            dtex = drawable.ShaderGroup.TextureDictionary.Lookup(tex.NameHash);
                                            if (dtex == null)
                                            {
                                                //dtex = drawable.ShaderGroup.TextureDictionary.Textures.data_items[0];
                                            }
                                        }
                                        if (dtex == null)
                                        {
                                            YtdFile ytd2 = gfc?.TryGetTextureDictForTexture(tex.NameHash);
                                            if ((ytd2 != null) && (ytd2.Loaded) && (ytd2.TextureDict != null))
                                            {
                                                dtex = ytd2.TextureDict.Lookup(tex.NameHash);
                                            }
                                            if (dtex == null)
                                            {
                                                dtex = gfc?.TryFindTextureInParent(tex.NameHash, texDict);
                                            }
                                        }
                                        rdtex = renderableCache.GetRenderableTexture(dtex);
                                        if (rdtex == null)
                                        { missingtexcount -= 2; } //(give extra chance..)  couldn't find the texture! :(
                                    }
                                    else if (ytd != null)
                                    {
                                        alltexsloaded = false;//ytd not loaded yet
                                        //missingtexcount++;
                                    }
                                }
                                else //no texdict specified, nothing to see here..
                                {
                                    YtdFile ytd2 = gfc?.TryGetTextureDictForTexture(tex.NameHash);
                                    if ((ytd2 != null) && (ytd2.Loaded) && (ytd2.TextureDict != null))
                                    {
                                        var dtex = ytd2.TextureDict.Lookup(tex.NameHash);
                                        rdtex = renderableCache.GetRenderableTexture(dtex);
                                    }
                                }
                            }
                            else if (ttex != null) //ensure embedded renderable texture
                            {
                                rdtex = renderableCache.GetRenderableTexture(ttex);
                            }
                            else if (tex == null)
                            { } //tex wasn't loaded? shouldn't happen..


                            geom.RenderableTextures[i] = rdtex;
                            if (rdtex != null)
                            {
                                if (!rdtex.IsLoaded)
                                {
                                    alltexsloaded = false;
                                    missingtexcount++;
                                }
                            }
                            else
                            {
                                //alltexsloaded = false;
                                missingtexcount++;
                            }


                        }
                    }
                }
            }

            rndbl.AllTexturesLoaded = alltexsloaded || (missingtexcount < 2);

            return rndbl;
        }





        private void BeginFrame()
        {
            renderskeletonlist.Clear();
        }





        private void RenderSky(DeviceContext context)
        {
            if (!renderskydome) return;
            if (!weather.Inited) return;

            var shader = shaders.Skydome;
            shader.UpdateSkyLocals(weather, globalLights);




            DrawableBase skydomeydr = null;
            YddFile skydomeydd = gameFileCache.GetYdd(2640562617); //skydome hash
            if ((skydomeydd != null) && (skydomeydd.Loaded) && (skydomeydd.Dict != null))
            {
                skydomeydr = skydomeydd.Dict.Values.FirstOrDefault();
            }

            Texture starfield = null;
            //Texture moon = null;
            YtdFile skydomeytd = gameFileCache.GetYtd(2640562617); //skydome hash
            if ((skydomeytd != null) && (skydomeytd.Loaded) && (skydomeytd.TextureDict != null) && (skydomeytd.TextureDict.Dict != null))
            {
                skydomeytd.TextureDict.Dict.TryGetValue(1064311147, out starfield); //starfield hash
                //skydomeytd.TextureDict.Dict.TryGetValue(234339206, out moon); //moon-new hash
            }

            Renderable sdrnd = null;
            if (skydomeydr != null)
            {
                sdrnd = renderableCache.GetRenderable(skydomeydr);
            }

            RenderableTexture sftex = null;
            if (starfield != null)
            {
                sftex = renderableCache.GetRenderableTexture(starfield);
            }

            //RenderableTexture moontex = null;
            //if (moon != null)
            //{
            //    moontex = renderableCache.GetRenderableTexture(moon);
            //}

            if ((sdrnd != null) && (sdrnd.IsLoaded) && (sftex != null) && (sftex.IsLoaded))
            {
                shaders.SetDepthStencilMode(context, DepthStencilMode.DisableAll);
                shaders.SetRasterizerMode(context, RasterizerMode.Solid);

                RenderableInst rinst = new RenderableInst();
                rinst.Position = Vector3.Zero;
                rinst.CamRel = Vector3.Zero;
                rinst.Distance = 0.0f;
                rinst.BBMin = skydomeydr.BoundingBoxMin.XYZ();
                rinst.BBMax = skydomeydr.BoundingBoxMax.XYZ();
                rinst.BSCenter = Vector3.Zero;
                rinst.Radius = skydomeydr.BoundingSphereRadius;
                rinst.Orientation = Quaternion.Identity;
                rinst.Scale = Vector3.One;
                rinst.TintPaletteIndex = 0;
                rinst.Renderable = sdrnd;
                shader.SetShader(context);
                shader.SetInputLayout(context, VertexType.PTT);
                shader.SetSceneVars(context, camera, null, globalLights);
                shader.SetEntityVars(context, ref rinst);

                RenderableModel rmod = ((sdrnd.HDModels != null) && (sdrnd.HDModels.Length > 0)) ? sdrnd.HDModels[0] : null;
                RenderableGeometry rgeom = ((rmod != null) && (rmod.Geometries != null) && (rmod.Geometries.Length > 0)) ? rmod.Geometries[0] : null;

                if ((rgeom != null) && (rgeom.VertexType == VertexType.PTT))
                {
                    shader.SetModelVars(context, rmod);
                    shader.SetTextures(context, sftex);

                    rgeom.Render(context);
                }

                //shaders.SetRasterizerMode(context, RasterizerMode.SolidDblSided);
                //shaders.SetDepthStencilMode(context, DepthStencilMode.Enabled);
                shader.RenderSun(context, camera, weather, globalLights);


                //if ((moontex != null) && (moontex.IsLoaded))
                //{
                //    shader.RenderMoon(context, camera, weather, globalLights, moontex);
                //}



                shader.UnbindResources(context);
            }

        }

        private void RenderClouds(DeviceContext context)
        {
            if (!renderskydome) return;
            if (!weather.Inited) return;
            if (!clouds.Inited) return;


            var shader = shaders.Clouds;

            shaders.SetDepthStencilMode(context, DepthStencilMode.DisableAll);
            shaders.SetRasterizerMode(context, RasterizerMode.Solid);
            shaders.SetDefaultBlendState(context);
            //shaders.SetAlphaBlendState(context);

            shader.SetShader(context);
            shader.UpdateCloudsLocals(clouds, globalLights);
            shader.SetSceneVars(context, camera, null, globalLights);

            var vtype = (VertexType)0;

            if (!string.IsNullOrEmpty(individualcloudfrag))
            {
                //render one cloud fragment.

                CloudHatFrag frag = clouds.HatManager.FindFrag(individualcloudfrag);
                if (frag == null) return;

                for (int i = 0; i < frag.Layers.Length; i++)
                {
                    CloudHatFragLayer layer = frag.Layers[i];
                    uint dhash = JenkHash.GenHash(layer.Filename.ToLower());
                    Archetype arch = gameFileCache.GetArchetype(dhash);
                    if (arch == null)
                    { continue; }

                    if (Math.Max(camera.Position.Z, 0.0f) < layer.HeightTigger) continue;

                    var drw = TryGetDrawable(arch);
                    var rnd = TryGetRenderable(arch, drw);

                    if ((rnd == null) || (rnd.IsLoaded == false) || (rnd.AllTexturesLoaded == false))
                    { continue; }


                    RenderableInst rinst = new RenderableInst();
                    rinst.Position = frag.Position;// Vector3.Zero;
                    rinst.CamRel = Vector3.Zero;// - camera.Position;
                    rinst.Distance = rinst.CamRel.Length();
                    rinst.BBMin = arch.BBMin;
                    rinst.BBMax = arch.BBMax;
                    rinst.BSCenter = frag.Position;
                    rinst.Radius = arch.BSRadius;
                    rinst.Orientation = Quaternion.Identity;
                    rinst.Scale = frag.Scale;// Vector3.One;
                    rinst.TintPaletteIndex = 0;
                    rinst.Renderable = rnd;

                    shader.SetEntityVars(context, ref rinst);


                    for (int mi = 0; mi < rnd.HDModels.Length; mi++)
                    {
                        var model = rnd.HDModels[mi];

                        for (int gi = 0; gi < model.Geometries.Length; gi++)
                        {
                            var geom = model.Geometries[gi];

                            if (geom.VertexType != vtype)
                            {
                                vtype = geom.VertexType;
                                shader.SetInputLayout(context, vtype);
                            }

                            shader.SetGeomVars(context, geom);

                            geom.Render(context);
                        }
                    }

                }


            }



        }







        private void UpdateGridVerts()
        {
            lock (gridSyncRoot)
            {
                gridVerts.Clear();

                float s = gridSize * gridCount * 0.5f;
                uint cblack = (uint)SharpDX.Color.Black.ToRgba();
                uint cgray = (uint)SharpDX.Color.DimGray.ToRgba();
                uint cred = (uint)SharpDX.Color.DarkRed.ToRgba();
                uint cgrn = (uint)SharpDX.Color.DarkGreen.ToRgba();
                int interval = 10;

                for (int i = 0; i <= gridCount; i++)
                {
                    float o = (gridSize * i) - s;
                    if ((i % interval) != 0)
                    {
                        gridVerts.Add(new VertexTypePC() { Position = new Vector3(o, -s, 0), Colour = cgray });
                        gridVerts.Add(new VertexTypePC() { Position = new Vector3(o, s, 0), Colour = cgray });
                        gridVerts.Add(new VertexTypePC() { Position = new Vector3(-s, o, 0), Colour = cgray });
                        gridVerts.Add(new VertexTypePC() { Position = new Vector3(s, o, 0), Colour = cgray });
                    }
                }
                for (int i = 0; i <= gridCount; i++) //draw main lines last, so they are on top
                {
                    float o = (gridSize * i) - s;
                    if ((i % interval) == 0)
                    {
                        var cx = (o == 0) ? cred : cblack;
                        var cy = (o == 0) ? cgrn : cblack;
                        gridVerts.Add(new VertexTypePC() { Position = new Vector3(o, -s, 0), Colour = cy });
                        gridVerts.Add(new VertexTypePC() { Position = new Vector3(o, s, 0), Colour = cy });
                        gridVerts.Add(new VertexTypePC() { Position = new Vector3(-s, o, 0), Colour = cx });
                        gridVerts.Add(new VertexTypePC() { Position = new Vector3(s, o, 0), Colour = cx });
                    }
                }

            }
        }

        private void RenderGrid(DeviceContext context)
        {
            if (!enableGrid) return;

            lock (gridSyncRoot)
            {
                if (gridVerts.Count > 0)
                {
                    shaders.SetDepthStencilMode(context, DepthStencilMode.Enabled);
                    shaders.Paths.RenderLines(context, gridVerts, camera, shaders.GlobalLights);
                }
            }
        }







        private void RenderSingleItem()
        {

            uint hash = 0;
            Archetype arch = null;

            if (Ydr != null)
            {
                if (Ydr.Loaded)
                {
                    hash = Ydr?.RpfFileEntry?.ShortNameHash ?? 0;
                    arch = TryGetArchetype(hash);

                    RenderDrawable(Ydr.Drawable, arch, null, -camera.Position, hash);
                }
            }
            else if (Ydd != null)
            {
                //render selected drawable(s)...
                if (Ydd.Loaded)
                {
                    foreach (var kvp in Ydd.Dict)
                    {
                        if (!DrawableDrawFlags.ContainsKey(kvp.Value))//only render if it's checked...
                        {
                            arch = TryGetArchetype(kvp.Key);

                            RenderDrawable(kvp.Value, arch, null, -camera.Position, Ydd.RpfFileEntry.ShortNameHash);
                        }
                    }
                }
            }
            else if (Ypt != null)
            {
                if ((Ypt.Loaded) && (Ypt.DrawableDict != null))
                {
                    foreach (var kvp in Ypt.DrawableDict)
                    {
                        if (!DrawableDrawFlags.ContainsKey(kvp.Value))//only render if it's checked...
                        {
                            arch = TryGetArchetype(kvp.Key);

                            RenderDrawable(kvp.Value, arch, null, -camera.Position, kvp.Key);
                        }
                    }
                }
            }
            else if (Yft != null)
            {
                if (Yft.Loaded)
                {
                    if (Yft.Fragment != null)
                    {
                        var f = Yft.Fragment;

                        hash = Yft?.RpfFileEntry?.ShortNameHash ?? 0;
                        arch = TryGetArchetype(hash);

                        RenderFragment(arch, null, f, hash);
                    }
                }
            }
            else if (Ybn != null)
            {
                if (Ybn.Loaded)
                {
                    RenderCollisionMesh(Ybn.Bounds, null);
                }
            }
            else if (Ypt != null)
            {
                if (Ypt.Loaded)
                {
                }
            }
            else if (Ynv != null)
            {
                if (Ynv.Loaded)
                {
                    RenderNavmesh(Ynv);
                }
            }


        }













        private bool RenderFragment(Archetype arch, YmapEntityDef ent, FragType f, uint txdhash = 0)
        {
            var pos = ent?.Position ?? Vector3.Zero;

            RenderDrawable(f.Drawable, arch, ent, pos - camera.Position, txdhash);

            if (f.Unknown_F8h_Data != null) //cloth
            {
                RenderDrawable(f.Unknown_F8h_Data, arch, ent, pos - camera.Position, txdhash);
            }

            //vehicle wheels...
            if ((f.PhysicsLODGroup != null) && (f.PhysicsLODGroup.PhysicsLOD1 != null))
            {
                var pl1 = f.PhysicsLODGroup.PhysicsLOD1;
                if ((pl1.Children != null) && (pl1.Children.data_items != null))
                {
                    for (int i = 0; i < pl1.Children.data_items.Length; i++)
                    {
                        var pch = pl1.Children.data_items[i];
                        if ((pch.Drawable1 != null) && (pch.Drawable1.AllModels.Length != 0))
                        {
                            //RenderDrawable(pch.Drawable1, arch, ent, -camera.Position, hash);
                        }
                    }
                }
            }

            return true;
        }

        private bool RenderDrawable(DrawableBase drawable, Archetype arche, YmapEntityDef entity, Vector3 camrel, uint txdHash = 0)
        {
            //enqueue a single drawable for rendering.

            if (drawable == null)
                return false;

            Renderable rndbl = TryGetRenderable(arche, drawable, txdHash);
            if (rndbl == null)
                return false;

            return RenderRenderable(rndbl, arche, entity, camrel);
        }

        private bool RenderRenderable(Renderable rndbl, Archetype arche, YmapEntityDef entity, Vector3 camrel)
        {
            //enqueue a single renderable for rendering.

            if (!rndbl.IsLoaded) return false;


            //if (((SelectionMode == MapSelectionMode.Entity) || (SelectionMode == MapSelectionMode.EntityExtension) || (SelectionMode == MapSelectionMode.ArchetypeExtension)))
            //{
            //    UpdateMouseHit(rndbl, arche, entity, camrel);
            //}

            bool isselected = true;// (rndbl.Key == SelectedItem.Drawable);

            Vector3 position = Vector3.Zero;
            Vector3 scale = Vector3.One;
            Quaternion orientation = Quaternion.Identity;
            uint tintPaletteIndex = 0;
            Vector3 bbmin = (arche != null) ? arche.BBMin : rndbl.Key.BoundingBoxMin.XYZ();
            Vector3 bbmax = (arche != null) ? arche.BBMax : rndbl.Key.BoundingBoxMax.XYZ();
            Vector3 bscen = (arche != null) ? arche.BSCenter : rndbl.Key.BoundingCenter;
            float radius = (arche != null) ? arche.BSRadius : rndbl.Key.BoundingSphereRadius;
            float distance = (camrel + bscen).Length();
            if (entity != null)
            {
                position = entity.Position;
                scale = entity.Scale;
                orientation = entity.Orientation;
                tintPaletteIndex = entity.CEntityDef.tintValue;
                bbmin = entity.BBMin;
                bbmax = entity.BBMax;
                bscen = entity.BSCenter;
            }


            if (rendercollisionmeshes)// && collisionmeshlayerdrawable)
            {
                Drawable sdrawable = rndbl.Key as Drawable;
                if ((sdrawable != null) && (sdrawable.Bound != null))
                {
                    RenderCollisionMesh(sdrawable.Bound, entity);
                }
            }
            if (renderskeletons)
            {
                if (rndbl.HasSkeleton)
                {
                    RenderSkeleton(rndbl);
                }
            }



            bool retval = true;// false;
            if (rndbl.IsLoaded && (rndbl.AllTexturesLoaded || !waitforchildrentoload))
            {
                RenderableGeometryInst rginst = new RenderableGeometryInst();
                rginst.Inst.Renderable = rndbl;
                rginst.Inst.CamRel = camrel;
                rginst.Inst.Position = position;
                rginst.Inst.Scale = scale;
                rginst.Inst.Orientation = orientation;
                rginst.Inst.TintPaletteIndex = tintPaletteIndex;
                rginst.Inst.BBMin = bbmin;
                rginst.Inst.BBMax = bbmax;
                rginst.Inst.BSCenter = bscen;
                rginst.Inst.Radius = radius;
                rginst.Inst.Distance = distance;


                RenderableModel[] models = isselected ? rndbl.AllModels : rndbl.HDModels;

                for (int mi = 0; mi < models.Length; mi++)
                {
                    var model = models[mi];

                    if (isselected)
                    {
                        if (ModelDrawFlags.ContainsKey(model.DrawableModel))
                        { continue; } //filter out models in selected item that aren't flagged for drawing.
                    }

                    if (!RenderIsModelFinalRender(model))// && !renderproxies)
                    { continue; } //filter out reflection proxy models...

                    for (int gi = 0; gi < model.Geometries.Length; gi++)
                    {
                        var geom = model.Geometries[gi];

                        if (isselected)
                        {
                            if (GeometryDrawFlags.ContainsKey(geom.DrawableGeom))
                            { continue; } //filter out geometries in selected item that aren't flagged for drawing.
                        }

                        rginst.Geom = geom;

                        shaders.Enqueue(rginst);
                    }
                }
            }
            else
            {
                retval = false;
            }
            return retval;
        }


        private void RenderCollisionMesh(Bounds bounds, YmapEntityDef entity)
        {
            //enqueue a single collision mesh for rendering.

            Vector3 position;
            Vector3 scale;
            Quaternion orientation;
            if (entity != null)
            {
                position = entity.Position;
                scale = entity.Scale;
                orientation = entity.Orientation;
            }
            else
            {
                position = Vector3.Zero;
                scale = Vector3.One;
                orientation = Quaternion.Identity;
            }

            switch (bounds.Type)
            {
                case 10: //BoundComposite
                    BoundComposite boundcomp = bounds as BoundComposite;
                    if (boundcomp != null)
                    {
                        RenderableBoundComposite rndbc = renderableCache.GetRenderableBoundComp(boundcomp);
                        if (rndbc.IsLoaded)
                        {
                            RenderableBoundGeometryInst rbginst = new RenderableBoundGeometryInst();
                            rbginst.Inst.Renderable = rndbc;
                            rbginst.Inst.Orientation = orientation;
                            rbginst.Inst.Scale = scale;
                            foreach (var geom in rndbc.Geometries)
                            {
                                if (geom == null) continue;
                                rbginst.Geom = geom;
                                rbginst.Inst.Position = position + orientation.Multiply(geom.BoundGeom.CenterGeom * scale);
                                rbginst.Inst.CamRel = rbginst.Inst.Position - camera.Position;
                                shaders.Enqueue(rbginst);
                            }

                            //UpdateMouseHits(rndbc, entity);
                        }
                    }
                    else
                    { }
                    break;
                case 3: //BoundBox - found in drawables - TODO
                    BoundBox boundbox = bounds as BoundBox;
                    if (boundbox == null)
                    { }
                    break;
                case 0: //BoundSphere - found in drawables - TODO
                    BoundSphere boundsphere = bounds as BoundSphere;
                    if (boundsphere == null)
                    { }
                    break;
                default:
                    break;
            }
        }


        private void RenderNavmesh(YnvFile ynv)
        {
            RenderablePathBatch rnd = renderableCache.GetRenderablePathBatch(ynv);
            if ((rnd != null) && (rnd.IsLoaded))
            {
                shaders.Enqueue(rnd);
            }
        }



        private bool RenderIsModelFinalRender(RenderableModel model)
        {

            if ((model.Unk2Ch & 1) == 0) //smallest bit is proxy/"final render" bit? seems to work...
            {
                return false;// renderproxies;
            }
            return true;

            //switch (model.Unk2Ch)
            //{
            //    case 65784:  //0000010000000011111000  //reflection proxy?
            //    case 65788:  //0000010000000011111100
            //    case 131312: //0000100000000011110000  //reflection proxy?
            //    case 131320: //0000100000000011111000  //reflection proxy?
            //    case 131324: //0000100000000011111100  //shadow/reflection proxy?
            //    case 196834: //0000110000000011100010 //shadow proxy? (tree branches)
            //    case 196848: //0000110000000011110000  //reflection proxy?
            //    case 196856: //0000110000000011111000 //reflection proxy? hotel nr golf course
            //    case 262392: //0001000000000011111000  //reflection proxy?
            //    case 327932: //0001010000000011111100  //reflection proxy? (alamo/sandy shores)
            //    case 983268: //0011110000000011100100  //big reflection proxy?
            //    case 2293988://1000110000000011100100  //big reflection proxy?
            //                 //case 1442047://golf course water proxy, but other things also
            //                 //case 1114367://mike house water proxy, but other things also
            //        return renderproxies;
            //}
            //return true;
        }







        private void RenderSkeleton(Renderable renderable)
        {
            renderskeletonlist.Add(renderable);
        }

        private void RenderSkeletons(DeviceContext context)
        {

            skeletonLineVerts.Clear();

            const uint cgrn = 4278255360;// (uint)new Color4(0.0f, 1.0f, 0.0f, 1.0f).ToRgba();
            const uint cblu = 4294901760;// (uint)new Color4(0.0f, 0.0f, 1.0f, 1.0f).ToRgba();
            VertexTypePC v1 = new VertexTypePC();
            VertexTypePC v2 = new VertexTypePC();
            v1.Colour = cgrn;
            v2.Colour = cblu;

            foreach (var renderable in renderskeletonlist)
            {
                DrawableBase drawable = renderable.Key;
                Skeleton skeleton = drawable?.Skeleton;
                if (skeleton == null) continue;

                var pinds = skeleton.ParentIndices;
                var bones = skeleton.Bones;
                if ((pinds == null) || (bones == null)) continue;
                var xforms = skeleton.Transformations;

                int cnt = Math.Min(pinds.Length, bones.Count);
                for (int i = 0; i < cnt; i++)
                {
                    var pind = pinds[i];
                    var bone = bones[i];
                    var pbone = bone.Parent;
                    if (pbone == null) continue; //nothing to draw for the root bone

                    if (xforms != null)//how to use xforms? bind pose?
                    {
                        var xform = (i < xforms.Length) ? xforms[i] : Matrix.Identity;
                        var pxform = (pind < xforms.Length) ? xforms[pind] : Matrix.Identity;
                    }
                    else
                    {
                    }


                    //draw line from bone's position to parent position...
                    Vector3 lbeg = Vector3.Zero;
                    Vector3 lend = bone.Translation;// bone.Rotation.Multiply();
                    while (pbone != null)
                    {
                        lbeg = pbone.Rotation.Multiply(lbeg) + pbone.Translation;
                        lend = pbone.Rotation.Multiply(lend) + pbone.Translation;
                        pbone = pbone.Parent;
                    }


                    v1.Position = lbeg;
                    v2.Position = lend;
                    skeletonLineVerts.Add(v1);
                    skeletonLineVerts.Add(v2);

                }


            }





            if (skeletonLineVerts.Count > 0)
            {
                shaders.SetDepthStencilMode(context, DepthStencilMode.DisableAll);
                shaders.Paths.RenderLines(context, skeletonLineVerts, camera, shaders.GlobalLights);
            }



        }










        public void LoadModel(YdrFile ydr)
        {
            if (ydr == null) return;

            FileName = ydr.Name;
            Ydr = ydr;

            UpdateModelsUI(ydr.Drawable);
        }
        public void LoadModels(YddFile ydd)
        {
            if (ydd == null) return;

            FileName = ydd.Name;
            Ydd = ydd;

            UpdateModelsUI(ydd.Dict);

            DetailsPropertyGrid.SelectedObject = ydd;
        }
        public void LoadModel(YftFile yft)
        {
            if (yft == null) return;

            FileName = yft.Name;
            Yft = yft;

            UpdateModelsUI(yft.Fragment.Drawable);
        }
        public void LoadModel(YbnFile ybn)
        {
            if (ybn == null) return;

            FileName = ybn.Name;
            Ybn = ybn;

            UpdateBoundsUI(ybn.Bounds);
        }
        public void LoadParticles(YptFile ypt)
        {
            if (ypt == null) return;

            FileName = ypt.Name;
            Ypt = ypt;

            UpdateModelsUI(ypt.DrawableDict);

            DetailsPropertyGrid.SelectedObject = ypt;//.PtfxList;
        }
        public void LoadNavmesh(YnvFile ynv)
        {
            if (ynv == null) return;

            FileName = ynv.Name;
            Ynv = ynv;

            //UpdateModelsUI(ypt.Particles.Drawable);
        }








        private void UpdateFormTitle()
        {
            Text = fileName + " - CodeWalker by dexyfex";
        }



        private void UpdateStatus(string text)
        {
            try
            {
                if (InvokeRequired)
                {
                    BeginInvoke(new Action(() => { UpdateStatus(text); }));
                }
                else
                {
                    StatusLabel.Text = text;
                }
            }
            catch { }
        }

        private void LogError(string text)
        {
            try
            {
                if (InvokeRequired)
                {
                    Invoke(new Action(() => { LogError(text); }));
                }
                else
                {
                    ConsoleTextBox.AppendText(text + "\r\n");
                }
            }
            catch { }
        }




        private void UpdateTimeOfDayLabel()
        {
            int v = TimeOfDayTrackBar.Value;
            float fh = v / 60.0f;
            int ih = (int)fh;
            int im = v - (ih * 60);
            if (ih == 24) ih = 0;
            TimeOfDayLabel.Text = string.Format("{0:00}:{1:00}", ih, im);
        }

        private void UpdateTimeOfDay(float elapsed)
        {
            if (timerunning)
            {
                float helapsed = elapsed * timespeed / 60.0f;
                timeofday += helapsed;
                while (timeofday >= 24.0f) timeofday -= 24.0f;
                while (timeofday < 0.0f) timeofday += 24.0f;
                timecycle.SetTime(timeofday);
            }

        }

        private void UpdateGlobalLights()
        {
            Vector3 lightdir = Vector3.Zero;//will be updated before each frame from X and Y vars
            Color4 lightdircolour = Color4.White;
            Color4 lightdirambcolour = new Color4(0.5f, 0.5f, 0.5f, 1.0f);
            Color4 lightnaturalupcolour = new Color4(0.0f);
            Color4 lightnaturaldowncolour = new Color4(0.0f);
            Color4 lightartificialupcolour = new Color4(0.0f);
            Color4 lightartificialdowncolour = new Color4(0.0f);
            bool hdr = shaders.hdr;
            float hdrint = 1.0f;
            Vector3 sundir = Vector3.Up;
            Vector3 moondir = Vector3.Down;
            Vector3 moonax = Vector3.UnitZ;

            if (controllightdir || !timecycle.Inited)
            {
                float cryd = (float)Math.Cos(lightdiry);
                lightdir.X = -(float)Math.Sin(-lightdirx) * cryd;
                lightdir.Y = -(float)Math.Cos(-lightdirx) * cryd;
                lightdir.Z = (float)Math.Sin(lightdiry);
                lightdircolour = Color4.White;
                lightdirambcolour = new Color4(0.5f, 0.5f, 0.5f, 1.0f);
                if (hdr && weather.Inited)
                {
                    lightdircolour *= weather.CurrentValues.skyHdr;
                    lightdircolour.Alpha = 1.0f;
                    lightdirambcolour *= weather.CurrentValues.skyHdr;
                    lightdirambcolour.Alpha = 1.0f;
                    hdrint = weather.CurrentValues.skyHdr;
                }
                sundir = lightdir;
                moondir = -lightdir;
                moonax = Vector3.Normalize(Vector3.UnitY);
            }
            else
            {
                float sunroll = timecycle.sun_roll * (float)Math.PI / 180.0f;  //122
                float moonroll = timecycle.moon_roll * (float)Math.PI / 180.0f;  //-122
                float moonwobamp = timecycle.moon_wobble_amp; //0.2
                float moonwobfreq = timecycle.moon_wobble_freq; //2
                float moonwoboffs = timecycle.moon_wobble_offset; //0.375
                float dayval = (0.5f + (timeofday - 6.0f) / 14.0f);
                float nightval = (((timeofday > 12.0f) ? (timeofday - 7.0f) : (timeofday + 17.0f)) / 9.0f);
                float daycyc = (float)Math.PI * dayval;
                float nightcyc = (float)Math.PI * nightval;
                Vector3 sdir = new Vector3((float)Math.Sin(daycyc), -(float)Math.Cos(daycyc), 0.0f);
                Vector3 mdir = new Vector3(-(float)Math.Sin(nightcyc), 0.0f, -(float)Math.Cos(nightcyc));
                Quaternion saxis = Quaternion.RotationYawPitchRoll(0.0f, sunroll, 0.0f);
                Quaternion maxis = Quaternion.RotationYawPitchRoll(0.0f, -moonroll, 0.0f);
                sundir = Vector3.Normalize(saxis.Multiply(sdir));
                moondir = Vector3.Normalize(maxis.Multiply(mdir));
                moonax = Vector3.Normalize(maxis.Multiply(Vector3.UnitY));
                //bool usemoon = false;

                lightdir = sundir;

                //if (lightdir.Z < -0.5f) lightdir.Z = -lightdir.Z; //make sure the lightsource is always above the horizon...

                if ((timeofday < 5.0f) || (timeofday > 21.0f))
                {
                    lightdir = moondir;
                    //usemoon = true;
                }

                if (lightdir.Z < 0)
                {
                    lightdir.Z = 0; //don't let the light source go below the horizon...
                }

                //lightdir = Vector3.Normalize(weather.CurrentValues.sunDirection);

                if (weather != null && weather.Inited)
                {
                    lightdircolour = (Color4)weather.CurrentValues.lightDirCol;
                    lightdirambcolour = (Color4)weather.CurrentValues.lightDirAmbCol;
                    lightnaturalupcolour = (Color4)weather.CurrentValues.lightNaturalAmbUp;
                    lightnaturaldowncolour = (Color4)weather.CurrentValues.lightNaturalAmbDown;
                    lightartificialupcolour = (Color4)weather.CurrentValues.lightArtificialExtUp;
                    lightartificialdowncolour = (Color4)weather.CurrentValues.lightArtificialExtDown;
                    float lamult = weather.CurrentValues.lightDirAmbIntensityMult;
                    float abounce = weather.CurrentValues.lightDirAmbBounce;
                    float minmult = hdr ? 0.1f : 0.5f;
                    lightdircolour *= Math.Max(lightdircolour.Alpha, minmult);
                    lightdirambcolour *= lightdirambcolour.Alpha * lamult; // 0.1f * lamult;

                    //if (usemoon)
                    //{
                    //    lightdircolour *= weather.CurrentValues.skyMoonIten;
                    //}


                    lightnaturalupcolour *= lightnaturalupcolour.Alpha * weather.CurrentValues.lightNaturalAmbUpIntensityMult;
                    lightnaturaldowncolour *= lightnaturaldowncolour.Alpha;
                    lightartificialupcolour *= lightartificialupcolour.Alpha;
                    lightartificialdowncolour *= lightartificialdowncolour.Alpha;

                    if (!hdr)
                    {
                        Color4 maxdirc = new Color4(1.0f);
                        Color4 maxambc = new Color4(0.5f);
                        lightdircolour = Color4.Min(lightdircolour, maxdirc);
                        lightdirambcolour = Color4.Min(lightdirambcolour, maxambc);
                        lightnaturalupcolour = Color4.Min(lightnaturalupcolour, maxambc);
                        lightnaturaldowncolour = Color4.Min(lightnaturaldowncolour, maxambc);
                        lightartificialupcolour = Color4.Min(lightartificialupcolour, maxambc);
                        lightartificialdowncolour = Color4.Min(lightartificialdowncolour, maxambc);
                    }
                    else
                    {
                        hdrint = weather.CurrentValues.skyHdr;//.lightDirCol.W;
                    }
                }


            }

            globalLights.Weather = weather;
            globalLights.HdrEnabled = hdr;
            globalLights.SpecularEnabled = true;// !MapViewEnabled;//disable specular for map view.
            globalLights.HdrIntensity = Math.Max(hdrint, 1.0f);
            globalLights.CurrentSunDir = sundir;
            globalLights.CurrentMoonDir = moondir;
            globalLights.MoonAxis = moonax;
            globalLights.Params.LightDir = lightdir;
            globalLights.Params.LightDirColour = lightdircolour;
            globalLights.Params.LightDirAmbColour = lightdirambcolour;
            globalLights.Params.LightNaturalAmbUp = rendernaturalambientlight ? lightnaturalupcolour : Color4.Black;
            globalLights.Params.LightNaturalAmbDown = rendernaturalambientlight ? lightnaturaldowncolour : Color4.Black;
            globalLights.Params.LightArtificialAmbUp = renderartificialambientlight ? lightartificialupcolour : Color4.Black;
            globalLights.Params.LightArtificialAmbDown = renderartificialambientlight ? lightartificialdowncolour : Color4.Black;


            if (shaders != null)
            {
                shaders.SetGlobalLightParams(globalLights);
            }

        }

        private void UpdateWindVector(float elapsed)
        {
            //wind still needs a lot of work.
            //currently just feed the wind vector with small oscillations...
            currentWindTime += elapsed;
            if (currentWindTime >= 200.0f) currentWindTime -= 200.0f;

            float dirval = (float)(currentWindTime * 0.01 * Math.PI);
            float dirval1 = (float)Math.Sin(currentWindTime * 0.100 * Math.PI) * 0.3f;
            float dirval2 = (float)(currentWindTime * 0.333 * Math.PI);
            float dirval3 = (float)(currentWindTime * 0.5 * Math.PI);
            float dirval4 = (float)Math.Sin(currentWindTime * 0.223 * Math.PI) * 0.4f;
            float dirval5 = (float)Math.Sin(currentWindTime * 0.4 * Math.PI) * 5.5f;

            currentWindVec.Z = (float)Math.Sin(dirval) * dirval1 + (float)Math.Cos(dirval2) * dirval4;
            currentWindVec.W = (float)Math.Cos(dirval) * dirval5 + (float)Math.Sin(dirval3) * dirval4;

            float strval = (float)(currentWindTime * 0.1 * Math.PI);
            float strval2 = (float)(currentWindTime * 0.825 * Math.PI);
            float strval3 = (float)(currentWindTime * 0.333 * Math.PI);
            float strval4 = (float)(currentWindTime * 0.666 * Math.PI);
            float strbase = 0.1f * ((float)Math.Sin(strval * 0.5));
            float strbase2 = 0.02f * ((float)Math.Sin(strval2 * 0.1));

            currentWindVec.X = (float)Math.Sin(strval) * strbase + ((float)Math.Cos(strval3) * strbase2);
            currentWindVec.Y = (float)Math.Cos(strval2) * strbase + ((float)Math.Sin(strval4 - strval3) * strbase2);
        }



        private void UpdateControlInputs(float elapsed)
        {
            var s = Settings.Default;

            float moveSpeed = 10.0f;

            bool xbenable = (xbcontroller != null) && (xbcontroller.IsConnected);
            float lx = 0, ly = 0, rx = 0, ry = 0, lt = 0, rt = 0; //input axes
            if (xbenable)
            {
                xbcontrollerstateprev = xbcontrollerstate;
                xbcontrollerstate = xbcontroller.GetState();
                xbmainaxesprev = xbmainaxes;
                xbtrigsprev = xbtrigs;
                xbmainaxes = ControllerMainAxes();
                xbtrigs = ControllerTriggers();
                lx = xbmainaxes.X;
                ly = xbmainaxes.Y;
                rx = xbmainaxes.Z;
                ry = xbmainaxes.W;
                lt = xbtrigs.X;
                rt = xbtrigs.Y;
                float lamt = s.XInputLThumbSensitivity * elapsed;
                float ramt = s.XInputRThumbSensitivity * elapsed;
                ly = s.XInputLThumbInvert ? ly : -ly;
                ry = s.XInputRThumbInvert ? ry : -ry;
                lx *= lamt;
                ly *= lamt;
                rx *= ramt;
                ry *= ramt;

                //if (ControllerButtonJustPressed(GamepadButtonFlags.Start))
                //{
                //    SetControlMode(ControlMode == WorldControlMode.Free ? WorldControlMode.Ped : WorldControlMode.Free);
                //}
            }



                Vector3 movevec = Vector3.Zero;

                //if (MapViewEnabled == true)
                //{
                //    if (kbmovefwd) movevec.Y += 1.0f;
                //    if (kbmovebck) movevec.Y -= 1.0f;
                //    if (kbmovelft) movevec.X -= 1.0f;
                //    if (kbmovergt) movevec.X += 1.0f;
                //    if (kbmoveup) movevec.Y += 1.0f;
                //    if (kbmovedn) movevec.Y -= 1.0f;
                //    movevec *= elapsed * 100.0f * Math.Min(camera.OrthographicTargetSize * 0.01f, 30.0f);
                //    float mapviewscale = 1.0f / camera.Height;
                //    float fdx = MapViewDragX * mapviewscale;
                //    float fdy = MapViewDragY * mapviewscale;
                //    movevec.X -= fdx * camera.OrthographicSize;
                //    movevec.Y += fdy * camera.OrthographicSize;
                //}
                //else
                {
                    //normal movement
                    if (kbmovefwd) movevec.Z -= 1.0f;
                    if (kbmovebck) movevec.Z += 1.0f;
                    if (kbmovelft) movevec.X -= 1.0f;
                    if (kbmovergt) movevec.X += 1.0f;
                    if (kbmoveup) movevec.Y += 1.0f;
                    if (kbmovedn) movevec.Y -= 1.0f;
                    movevec *= elapsed * moveSpeed * Math.Min(camera.TargetDistance, 50.0f);
                }


                Vector3 movewvec = camera.ViewInvQuaternion.Multiply(movevec);
                camEntity.Position += movewvec;

                //MapViewDragX = 0;
                //MapViewDragY = 0;




                if (xbenable)
                {
                    camera.ControllerRotate(lx + rx, ly + ry);

                    float zoom = 0.0f;
                    float zoomspd = s.XInputZoomSpeed;
                    float zoomamt = zoomspd * elapsed;
                    if (ControllerButtonPressed(GamepadButtonFlags.DPadUp)) zoom += zoomamt;
                    if (ControllerButtonPressed(GamepadButtonFlags.DPadDown)) zoom -= zoomamt;

                    camera.ControllerZoom(zoom);

                    float acc = 0.0f;
                    float accspd = s.XInputMoveSpeed;//actually accel speed...
                    acc += rt * accspd;
                    acc -= lt * accspd;

                    Vector3 newdir = camera.ViewDirection; //maybe use the "vehicle" direction...?
                    xbcontrolvelocity += (acc * elapsed);

                    if (ControllerButtonPressed(GamepadButtonFlags.A | GamepadButtonFlags.RightShoulder)) //handbrake...
                    {
                        xbcontrolvelocity *= Math.Max(0.75f - elapsed, 0);//not ideal for low fps...
                                                                          //xbcontrolvelocity = 0.0f;
                        if (Math.Abs(xbcontrolvelocity) < 0.001f) xbcontrolvelocity = 0.0f;
                    }

                    camEntity.Velocity = newdir * xbcontrolvelocity;
                    camEntity.Position += camEntity.Velocity * elapsed;


                    //fire!
                    //if (ControllerButtonJustPressed(GamepadButtonFlags.LeftShoulder))
                    //{
                    //    SpawnTestEntity(true);
                    //}

                }



        }

        private Vector4 ControllerMainAxes()
        {
            var gp = xbcontrollerstate.Gamepad;
            var ldz = Gamepad.LeftThumbDeadZone;
            var rdz = Gamepad.RightThumbDeadZone;
            float ltnrng = -(short.MinValue + ldz);
            float ltprng = (short.MaxValue - ldz);
            float rtnrng = -(short.MinValue + rdz);
            float rtprng = (short.MaxValue - rdz);

            float lx = (gp.LeftThumbX < 0) ? Math.Min((gp.LeftThumbX + ldz) / ltnrng, 0) :
                       (gp.LeftThumbX > 0) ? Math.Max((gp.LeftThumbX - ldz) / ltprng, 0) : 0;
            float ly = (gp.LeftThumbY < 0) ? Math.Min((gp.LeftThumbY + ldz) / ltnrng, 0) :
                       (gp.LeftThumbY > 0) ? Math.Max((gp.LeftThumbY - ldz) / ltprng, 0) : 0;
            float rx = (gp.RightThumbX < 0) ? Math.Min((gp.RightThumbX + rdz) / rtnrng, 0) :
                       (gp.RightThumbX > 0) ? Math.Max((gp.RightThumbX - rdz) / rtprng, 0) : 0;
            float ry = (gp.RightThumbY < 0) ? Math.Min((gp.RightThumbY + rdz) / rtnrng, 0) :
                       (gp.RightThumbY > 0) ? Math.Max((gp.RightThumbY - rdz) / rtprng, 0) : 0;

            return new Vector4(lx, ly, rx, ry);
        }
        private Vector2 ControllerTriggers()
        {
            var gp = xbcontrollerstate.Gamepad;
            var tt = Gamepad.TriggerThreshold;
            float trng = byte.MaxValue - tt;
            float lt = Math.Max((gp.LeftTrigger - tt) / trng, 0);
            float rt = Math.Max((gp.RightTrigger - tt) / trng, 0);
            return new Vector2(lt, rt);
        }
        private bool ControllerButtonPressed(GamepadButtonFlags b)
        {
            return ((xbcontrollerstate.Gamepad.Buttons & b) != 0);
        }
        private bool ControllerButtonJustPressed(GamepadButtonFlags b)
        {
            return (((xbcontrollerstate.Gamepad.Buttons & b) != 0) && ((xbcontrollerstateprev.Gamepad.Buttons & b) == 0));
        }




        private void UpdateModelsUI(DrawableBase drawable)
        {
            DetailsPropertyGrid.SelectedObject = drawable;

            DrawableDrawFlags.Clear();
            ModelDrawFlags.Clear();
            GeometryDrawFlags.Clear();
            ModelsTreeView.Nodes.Clear();
            ModelsTreeView.ShowRootLines = false;
            TexturesTreeView.Nodes.Clear();
            if (drawable != null)
            {
                AddDrawableModelsTreeNodes(drawable.DrawableModelsHigh, "High Detail", true);
                AddDrawableModelsTreeNodes(drawable.DrawableModelsMedium, "Medium Detail", false);
                AddDrawableModelsTreeNodes(drawable.DrawableModelsLow, "Low Detail", false);
                AddDrawableModelsTreeNodes(drawable.DrawableModelsVeryLow, "Very Low Detail", false);
                //AddSelectionDrawableModelsTreeNodes(item.Drawable.DrawableModelsX, "X Detail", false);
            }
        }
        private void UpdateModelsUI(FragType frag)
        {
            DetailsPropertyGrid.SelectedObject = frag;

            var drawable = frag.Drawable;

            DrawableDrawFlags.Clear();
            ModelDrawFlags.Clear();
            GeometryDrawFlags.Clear();
            ModelsTreeView.Nodes.Clear();
            ModelsTreeView.ShowRootLines = false;
            TexturesTreeView.Nodes.Clear();
            if (drawable != null)
            {
                AddDrawableModelsTreeNodes(drawable.DrawableModelsHigh, "High Detail", true);
                AddDrawableModelsTreeNodes(drawable.DrawableModelsMedium, "Medium Detail", false);
                AddDrawableModelsTreeNodes(drawable.DrawableModelsLow, "Low Detail", false);
                AddDrawableModelsTreeNodes(drawable.DrawableModelsVeryLow, "Very Low Detail", false);
                //AddSelectionDrawableModelsTreeNodes(item.Drawable.DrawableModelsX, "X Detail", false);
            }
        }
        private void UpdateModelsUI(Dictionary<uint, Drawable> dict)
        {
            //DetailsPropertyGrid.SelectedObject = dict; //this won't look good...

            DrawableDrawFlags.Clear();
            ModelDrawFlags.Clear();
            GeometryDrawFlags.Clear();
            ModelsTreeView.Nodes.Clear();
            ModelsTreeView.ShowRootLines = true;
            TexturesTreeView.Nodes.Clear();

            bool check = true;
            if (dict != null)
            {
                foreach (var kvp in dict)
                {
                    AddDrawableTreeNode(kvp.Value, kvp.Key, check);
                    check = false;
                }
            }

            ToolsPanel.Visible = true; //show the panel by default for dictionaries...
        }
        private void UpdateBoundsUI(Bounds bounds)
        {
            DetailsPropertyGrid.SelectedObject = bounds;
        }
        private void UpdateNavmeshUI(YnvFile ynv)
        {
            DetailsPropertyGrid.SelectedObject = ynv.Nav;
        }


        private void AddDrawableTreeNode(DrawableBase drawable, uint hash, bool check)
        {
            MetaHash mhash = new MetaHash(hash);
            
            var dnode = ModelsTreeView.Nodes.Add(mhash.ToString());
            dnode.Tag = drawable;
            dnode.Checked = check;

            AddDrawableModelsTreeNodes(drawable.DrawableModelsHigh, "High Detail", true, dnode);
            AddDrawableModelsTreeNodes(drawable.DrawableModelsMedium, "Medium Detail", false, dnode);
            AddDrawableModelsTreeNodes(drawable.DrawableModelsLow, "Low Detail", false, dnode);
            AddDrawableModelsTreeNodes(drawable.DrawableModelsVeryLow, "Very Low Detail", false, dnode);
            //AddSelectionDrawableModelsTreeNodes(item.Drawable.DrawableModelsX, "X Detail", false, dnode);

        }
        private void AddDrawableModelsTreeNodes(ResourcePointerList64<DrawableModel> models, string prefix, bool check, TreeNode parentDrawableNode = null)
        {
            if (models == null) return;
            if (models.data_items == null) return;

            for (int mi = 0; mi < models.data_items.Length; mi++)
            {
                var tnc = (parentDrawableNode != null) ? parentDrawableNode.Nodes : ModelsTreeView.Nodes;

                var model = models.data_items[mi];
                string mprefix = prefix + " " + (mi + 1).ToString();
                var mnode = tnc.Add(mprefix + " " + model.ToString());
                mnode.Tag = model;
                mnode.Checked = check;

                var tmnode = TexturesTreeView.Nodes.Add(mprefix + " " + model.ToString());
                tmnode.Tag = model;

                if (!check)
                {
                    ModelDrawFlags[model] = false;
                }

                if ((model.Geometries == null) || (model.Geometries.data_items == null)) continue;

                foreach (var geom in model.Geometries.data_items)
                {
                    var gname = geom.ToString();
                    var gnode = mnode.Nodes.Add(gname);
                    gnode.Tag = geom;
                    gnode.Checked = true;// check;

                    var tgnode = tmnode.Nodes.Add(gname);
                    tgnode.Tag = geom;

                    if ((geom.Shader != null) && (geom.Shader.ParametersList != null) && (geom.Shader.ParametersList.Hashes != null))
                    {
                        var pl = geom.Shader.ParametersList;
                        var h = pl.Hashes;
                        var p = pl.Parameters;
                        for (int ip = 0; ip < h.Length; ip++)
                        {
                            var hash = pl.Hashes[ip];
                            var parm = pl.Parameters[ip];
                            var tex = parm.Data as TextureBase;
                            if (tex != null)
                            {
                                var t = tex as Texture;
                                var tstr = tex.Name.Trim();
                                if (t != null)
                                {
                                    tstr = string.Format("{0} ({1}x{2}, embedded)", tex.Name, t.Width, t.Height);
                                }
                                var tnode = tgnode.Nodes.Add(hash.ToString().Trim() + ": " + tstr);
                                tnode.Tag = tex;
                            }
                        }
                        tgnode.Expand();
                    }

                }

                mnode.Expand();
                tmnode.Expand();
            }
        }
        private void UpdateSelectionDrawFlags(TreeNode node)
        {
            //update the selection draw flags depending on tag and checked/unchecked
            var drwbl = node.Tag as DrawableBase;
            var model = node.Tag as DrawableModel;
            var geom = node.Tag as DrawableGeometry;
            bool rem = node.Checked;
            lock (rendersyncroot)
            {
                if (drwbl != null)
                {
                    if (rem)
                    {
                        if (DrawableDrawFlags.ContainsKey(drwbl))
                        {
                            DrawableDrawFlags.Remove(drwbl);
                        }
                    }
                    else
                    {
                        DrawableDrawFlags[drwbl] = false;
                    }
                }
                if (model != null)
                {
                    if (rem)
                    {
                        if (ModelDrawFlags.ContainsKey(model))
                        {
                            ModelDrawFlags.Remove(model);
                        }
                    }
                    else
                    {
                        ModelDrawFlags[model] = false;
                    }
                }
                if (geom != null)
                {
                    if (rem)
                    {
                        if (GeometryDrawFlags.ContainsKey(geom))
                        {
                            GeometryDrawFlags.Remove(geom);
                        }
                    }
                    else
                    {
                        GeometryDrawFlags[geom] = false;
                    }
                }
                updateArchetypeStatus = true;
            }
        }








        private void ModelForm_Load(object sender, EventArgs e)
        {
            Init();
        }

        private void ModelForm_MouseDown(object sender, MouseEventArgs e)
        {
            switch (e.Button)
            {
                case MouseButtons.Left: MouseLButtonDown = true; break;
                case MouseButtons.Right: MouseRButtonDown = true; break;
            }

            MouseDownPoint = e.Location;
            MouseLastPoint = MouseDownPoint;


            if (MouseRButtonDown)
            {
                //SelectMousedItem();
            }

            MouseX = e.X; //to stop jumps happening on mousedown, sometimes the last MouseMove event was somewhere else... (eg after clicked a menu)
            MouseY = e.Y;
        }

        private void ModelForm_MouseUp(object sender, MouseEventArgs e)
        {
            switch (e.Button)
            {
                case MouseButtons.Left: MouseLButtonDown = false; break;
                case MouseButtons.Right: MouseRButtonDown = false; break;
            }

            //lock (MouseControlSyncRoot)
            //{
            //    MouseControlButtons &= ~e.Button;
            //}

        }

        private void ModelForm_MouseMove(object sender, MouseEventArgs e)
        {
            int dx = e.X - MouseX;
            int dy = e.Y - MouseY;

            if (MouseLButtonDown)
            {
                camera.MouseRotate(dx, dy);
            }
            if (MouseRButtonDown)
            {
                if (controllightdir)
                {
                    lightdirx += (dx * camera.Sensitivity);
                    lightdiry += (dy * camera.Sensitivity);
                }
                else if (controltimeofday)
                {
                    timeofday += (dx - dy) / 30.0f;
                    while (timeofday >= 24.0f) timeofday -= 24.0f;
                    while (timeofday < 0.0f) timeofday += 24.0f;
                    timecycle.SetTime(timeofday);

                    float fv = timeofday * 60.0f;
                    TimeOfDayTrackBar.Value = (int)fv;
                    UpdateTimeOfDayLabel();
                }
            }

            MouseX = e.X;
            MouseY = e.Y;
            MouseLastPoint = e.Location;



        }

        private void ModelForm_MouseWheel(object sender, MouseEventArgs e)
        {
            if (e.Delta != 0)
            {
                //if (ControlMode == WorldControlMode.Free)
                //{
                    camera.MouseZoom(e.Delta);
                //}
                //else
                //{
                //    lock (MouseControlSyncRoot)
                //    {
                //        MouseControlWheel += e.Delta;
                //    }
                //}
            }

        }

        private void ModelForm_KeyDown(object sender, KeyEventArgs e)
        {
            if (ActiveControl is TextBox)
            {
                var tb = ActiveControl as TextBox;
                if (!tb.ReadOnly) return; //don't move the camera when typing!
            }
            if (ActiveControl is ComboBox)
            {
                var cb = ActiveControl as ComboBox;
                if (cb.DropDownStyle != ComboBoxStyle.DropDownList) return; //nontypable combobox
            }

            var k = e.KeyCode;

            bool ctrl = (e.Modifiers & Keys.Control) > 0;
            bool shift = (e.Modifiers & Keys.Shift) > 0;
            CtrlPressed = ctrl;
            ShiftPressed = shift;

            bool enablemove = true;// (!iseditmode) || (MouseLButtonDown && (GrabbedMarker == null) && (GrabbedWidget == null));

            enablemove = enablemove && (!ctrl);

            //WASD move the camera entity...
            if (enablemove)
            {
                if (k == keyBindings.MoveForward) kbmovefwd = true;
                if (k == keyBindings.MoveBackward) kbmovebck = true;
                if (k == keyBindings.MoveLeft) kbmovelft = true;
                if (k == keyBindings.MoveRight) kbmovergt = true;
                if (k == keyBindings.MoveUp) kbmoveup = true;
                if (k == keyBindings.MoveDown) kbmovedn = true;
                if (k == keyBindings.Jump) kbjump = true;
            }

            bool moving = kbmovefwd || kbmovebck || kbmovelft || kbmovergt || kbmoveup || kbmovedn || kbjump;


            if (!ctrl)
            {
                if (k == keyBindings.MoveSlowerZoomIn)
                {
                    camera.MouseZoom(1);
                }
                if (k == keyBindings.MoveFasterZoomOut)
                {
                    camera.MouseZoom(-1);
                }
            }



            if (!moving) //don't trigger further actions if moving.
            {

                if (!ctrl)
                {
                    //switch widget modes and spaces.
                    //if ((k == keyBindings.ExitEditMode))
                    //{
                    //    if (Widget.Mode == WidgetMode.Default) ToggleWidgetSpace();
                    //    else SetWidgetMode("Default");
                    //}
                    //if ((k == keyBindings.EditPosition))// && !enablemove)
                    //{
                    //    if (Widget.Mode == WidgetMode.Position) ToggleWidgetSpace();
                    //    else SetWidgetMode("Position");
                    //}
                    //if ((k == keyBindings.EditRotation))// && !enablemove)
                    //{
                    //    if (Widget.Mode == WidgetMode.Rotation) ToggleWidgetSpace();
                    //    else SetWidgetMode("Rotation");
                    //}
                    //if ((k == keyBindings.EditScale))// && !enablemove)
                    //{
                    //    if (Widget.Mode == WidgetMode.Scale) ToggleWidgetSpace();
                    //    else SetWidgetMode("Scale");
                    //}
                    //if (k == keyBindings.ToggleMouseSelect)
                    //{
                    //    SetMouseSelect(!MouseSelectEnabled);
                    //}
                    //if (k == keyBindings.ToggleToolbar)
                    //{
                    //    ToggleToolbar();
                    //}
                    //if (k == Keys.P)
                    //{
                    //    //TEMPORARY!
                    //    SetControlMode((ControlMode == WorldControlMode.Free) ? WorldControlMode.Ped : WorldControlMode.Free);
                    //}
                }
                else
                {
                    //switch (k)
                    //{
                    //    case Keys.N:
                    //        New();
                    //        break;
                    //    case Keys.O:
                    //        Open();
                    //        break;
                    //    case Keys.S:
                    //        if (shift) SaveAll();
                    //        else Save();
                    //        break;
                    //    case Keys.Z:
                    //        Undo();
                    //        break;
                    //    case Keys.Y:
                    //        Redo();
                    //        break;
                    //    case Keys.C:
                    //        CopyItem();
                    //        break;
                    //    case Keys.V:
                    //        PasteItem();
                    //        break;
                    //    case Keys.U:
                    //        ToolsPanelShowButton.Visible = !ToolsPanelShowButton.Visible;
                    //        break;
                    //}
                }
                //if (k == Keys.Escape) //temporary? panic get cursor back
                //{
                //    if (ControlMode != WorldControlMode.Free) SetControlMode(WorldControlMode.Free);
                //}
            }

            //if (ControlMode != WorldControlMode.Free)
            //{
            //    e.Handled = true;
            //}
        }

        private void ModelForm_KeyUp(object sender, KeyEventArgs e)
        {
            bool ctrl = (e.Modifiers & Keys.Control) > 0;
            bool shift = (e.Modifiers & Keys.Shift) > 0;
            CtrlPressed = ctrl;
            ShiftPressed = shift;

            if (ActiveControl is TextBox)
            {
                var tb = ActiveControl as TextBox;
                if (!tb.ReadOnly) return; //don't move the camera when typing!
            }
            if (ActiveControl is ComboBox)
            {
                var cb = ActiveControl as ComboBox;
                if (cb.DropDownStyle != ComboBoxStyle.DropDownList) return; //non-typable combobox
            }


            var k = e.KeyCode;
            if (k == keyBindings.MoveForward) kbmovefwd = false;
            if (k == keyBindings.MoveBackward) kbmovebck = false;
            if (k == keyBindings.MoveLeft) kbmovelft = false;
            if (k == keyBindings.MoveRight) kbmovergt = false;
            if (k == keyBindings.MoveUp) kbmoveup = false;
            if (k == keyBindings.MoveDown) kbmovedn = false;
            if (k == keyBindings.Jump) kbjump = false;


            //if (ControlMode != WorldControlMode.Free)
            //{
            //    e.Handled = true;
            //}
        }

        private void ModelForm_Deactivate(object sender, EventArgs e)
        {
            //try not to lock keyboard movement if the form loses focus.
            kbmovefwd = false;
            kbmovebck = false;
            kbmovelft = false;
            kbmovergt = false;
            kbmoveup = false;
            kbmovedn = false;
            kbjump = false;
        }

        private void StatsUpdateTimer_Tick(object sender, EventArgs e)
        {
            int rgc = (shaders != null) ? shaders.RenderedGeometries : 0;
            int crc = renderableCache.LoadedRenderableCount;
            int ctc = renderableCache.LoadedTextureCount;
            int tcrc = renderableCache.MemCachedRenderableCount;
            int tctc = renderableCache.MemCachedTextureCount;
            long vr = renderableCache.TotalGraphicsMemoryUse + (shaders != null ? shaders.TotalGraphicsMemoryUse : 0);
            string vram = TextUtil.GetBytesReadable(vr);
            //StatsLabel.Text = string.Format("Drawn: {0} geom, Loaded: {1}/{5} dr, {2}/{6} tx, Vram: {3}, Fps: {4}", rgc, crc, ctc, vram, fps, tcrc, tctc);
            StatsLabel.Text = string.Format("Drawn: {0} geom, Loaded: {1} dr, {2} tx, Vram: {3}, Fps: {4}", rgc, crc, ctc, vram, fps);

            if (timerunning)
            {
                float fv = timeofday * 60.0f;
                //TimeOfDayTrackBar.Value = (int)fv;
                UpdateTimeOfDayLabel();
            }

            //CameraPositionTextBox.Text = FloatUtil.GetVector3String(camera.Position, "0.##");
        }

        private void ToolsPanelShowButton_Click(object sender, EventArgs e)
        {
            ToolsPanel.Visible = true;
        }

        private void ToolsPanelHideButton_Click(object sender, EventArgs e)
        {
            ToolsPanel.Visible = false;
        }

        private void ToolsDragPanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                toolsPanelResizing = true;
                toolsPanelResizeStartX = e.X + ToolsPanel.Left + ToolsDragPanel.Left;
                toolsPanelResizeStartLeft = ToolsPanel.Left;
                toolsPanelResizeStartRight = ToolsPanel.Right;
            }
        }

        private void ToolsDragPanel_MouseUp(object sender, MouseEventArgs e)
        {
            toolsPanelResizing = false;
        }

        private void ToolsDragPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (toolsPanelResizing)
            {
                int rx = e.X + ToolsPanel.Left + ToolsDragPanel.Left;
                int dx = rx - toolsPanelResizeStartX;
                ToolsPanel.Width = toolsPanelResizeStartRight - toolsPanelResizeStartLeft + dx;
            }
        }

        private void ModelsTreeView_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (e.Node != null)
            {
                UpdateSelectionDrawFlags(e.Node);
            }
        }

        private void ModelsTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Node != null)
            {
                e.Node.Checked = !e.Node.Checked;
                //UpdateSelectionDrawFlags(e.Node);
            }
        }

        private void ModelsTreeView_KeyPress(object sender, KeyPressEventArgs e)
        {
            e.Handled = true; //stops annoying ding sound...
        }

        private void HDRRenderingCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            lock (rendersyncroot)
            {
                shaders.hdr = HDRRenderingCheckBox.Checked;
            }
        }

        private void ShadowsCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            lock (rendersyncroot)
            {
                shaders.shadows = ShadowsCheckBox.Checked;
            }
        }

        private void SkydomeCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            renderskydome = SkydomeCheckBox.Checked;
            //controllightdir = !renderskydome;
        }

        private void ControlLightDirCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            controllightdir = ControlLightDirCheckBox.Checked;
        }

        private void TimeOfDayTrackBar_Scroll(object sender, EventArgs e)
        {
            int v = TimeOfDayTrackBar.Value;
            float fh = v / 60.0f;
            UpdateTimeOfDayLabel();
            lock (rendersyncroot)
            {
                timeofday = fh;
                timecycle.SetTime(timeofday);
            }
        }

        private void ShowBoundsCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            rendercollisionmeshes = ShowBoundsCheckBox.Checked;
        }

        private void WireframeCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            shaders.wireframe = WireframeCheckBox.Checked;
        }

        private void AnisotropicFilteringCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            shaders.AnisotropicFiltering = AnisotropicFilteringCheckBox.Checked;
        }

        private void RenderModeComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            TextureSamplerComboBox.Enabled = false;
            TextureCoordsComboBox.Enabled = false;
            switch (RenderModeComboBox.Text)
            {
                default:
                case "Default":
                    shaders.RenderMode = WorldRenderMode.Default;
                    break;
                case "Single texture":
                    shaders.RenderMode = WorldRenderMode.SingleTexture;
                    TextureSamplerComboBox.Enabled = true;
                    TextureCoordsComboBox.Enabled = true;
                    break;
                case "Vertex normals":
                    shaders.RenderMode = WorldRenderMode.VertexNormals;
                    break;
                case "Vertex tangents":
                    shaders.RenderMode = WorldRenderMode.VertexTangents;
                    break;
                case "Vertex colour 1":
                    shaders.RenderMode = WorldRenderMode.VertexColour;
                    shaders.RenderVertexColourIndex = 1;
                    break;
                case "Vertex colour 2":
                    shaders.RenderMode = WorldRenderMode.VertexColour;
                    shaders.RenderVertexColourIndex = 2;
                    break;
                case "Vertex colour 3":
                    shaders.RenderMode = WorldRenderMode.VertexColour;
                    shaders.RenderVertexColourIndex = 3;
                    break;
                case "Texture coord 1":
                    shaders.RenderMode = WorldRenderMode.TextureCoord;
                    shaders.RenderTextureCoordIndex = 1;
                    break;
                case "Texture coord 2":
                    shaders.RenderMode = WorldRenderMode.TextureCoord;
                    shaders.RenderTextureCoordIndex = 2;
                    break;
                case "Texture coord 3":
                    shaders.RenderMode = WorldRenderMode.TextureCoord;
                    shaders.RenderTextureCoordIndex = 3;
                    break;
            }
        }

        private void TextureSamplerComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (TextureSamplerComboBox.SelectedItem is MetaName)
            {
                shaders.RenderTextureSampler = (MetaName)TextureSamplerComboBox.SelectedItem;
            }
        }

        private void TextureCoordsComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            switch (TextureCoordsComboBox.Text)
            {
                default:
                case "Texture coord 1":
                    shaders.RenderTextureSamplerCoord = 1;
                    break;
                case "Texture coord 2":
                    shaders.RenderTextureSamplerCoord = 2;
                    break;
                case "Texture coord 3":
                    shaders.RenderTextureSamplerCoord = 3;
                    break;
            }
        }

        private void GridCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            enableGrid = GridCheckBox.Checked;
        }

        private void GridSizeComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            float newgs;
            float.TryParse(GridSizeComboBox.Text, out newgs);
            if (newgs != gridSize)
            {
                gridSize = newgs;
                UpdateGridVerts();
            }
        }

        private void GridCountComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            int newgc;
            int.TryParse(GridCountComboBox.Text, out newgc);
            if (newgc != gridCount)
            {
                gridCount = newgc;
                UpdateGridVerts();
            }
        }

        private void SkeletonsCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            renderskeletons = SkeletonsCheckBox.Checked;
        }

        private void ErrorConsoleCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            ConsolePanel.Visible = ErrorConsoleCheckBox.Checked;
        }

        private void StatusBarCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            StatusStrip.Visible = StatusBarCheckBox.Checked;
        }
    }
}
