Subtle Technology

A blog about Microsoft .NET, Unity, MVC, Ext JS, Silverlight, FaceBook, Twitter, and other technologies by Michael Urvan

Pages

Recent posts

Tags

Categories

Navigation

Archive

Blogroll

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

Unity LOD by using QuickLOD with SimpleLOD for fast performance

Using custom LOD systems are not very relevant anymore since Unity 5 finally gives us an LOD system for free without having to upgrade or buy a PRO license, and many people may still have Unity 4.x Free projects that they want to add LOD to.. So hopefully some people will find this article useful. The best performance will probably come from using Unity 5's LODGroups rather than any custom LOD system, so that is what I would recommend using if you are already on Unity 5. The LOD assets sold on the store are great, but they probably won't outperform Unity's built in LOD system. That being said, if you want the easiest LOD solution you may want to try combining QuickLOD with SimpleLOD.

SimpleLOD is an awesome asset, it will automatically generate three LODs for any object.. and I have scripts that I will include here so that you can generate decimated LODs for an entire scene within minutes. SimpleLOD's decimation does not work with trees, so if you are using trees placed in your scene (rather than placed via terrain) you will need to set them to just use one Mesh and remove the other two. The downside to using SimpleLOD is that it's LODSwitcher component uses Update() to deterrmine the distance to your camera and calculate the LOD to use... once you have a scene with hundreds of these LODSwitchers you will have performance problems, so by itself SimpleLOD won't work well with large scenes where you have LODSwitchers on every object.

QuickLOD is also an awesome asset, but you need to have the LODs made and ready to be used. It can handle thousands of objects with little CPU usage, the asset description says it has been tested with over 1 million objects. But if you have thousands of assets that you need LODs for, it doesn't do you very much good.

The convenience of SimpleLOD can be combined with the performance of QuickLOD so that you can use the SimpleLOD editor window to generate LODs and then modify the LODSwitcher so that it receives its update from QuickLOD. Luckily QuickLOD was already designed to allow the ability to be used with other LOD systems just by inheriting from a base class.

The first step to using both systems would be to purchase and import both systems from the Unity asset store, and then read the QuickLOD documentation and configure a few objects as a test (even though you do not have LODs yet). You can generate the LODs using SimpleLOD and then just point the QuickLOD component to use those LODs once you locate them. SimpleLOD creates an LODMeshes folder in same folder as each FBX where the original mesh was located.

First you should get QuickLOD configured and working in your scene with a few test objects, so that you can verify the LOD is working (and how it does). The best method is to use the "Hard with Borders" and allow it to use your camera's view angle.

The first step once you have both LOD systems imported, is to make LODSwitcher inherit from LodObjectBase by modifying LODSwitcher.cs:

public class LODSwitcher : LodObjectBase {

then you will need to add these methods to LODSwitcher.cs:

    private void ComputeLODLevel(float sp) {
        int newLodLevel = 0;
        if(fixedLODLevel >= 0) {
            newLodLevel = fixedLODLevel;
        } else {
            float screenPortion = sp;
            if(screenPortion >= 0f) {
                for(int i=0;i<lodScreenSizes.Length;i++) {
                    if(screenPortion < lodScreenSizes[i]) newLodLevel++;
                }
            } else newLodLevel = -1;
        }
        if(newLodLevel != lodLevel) SetLODLevel(newLodLevel);
    }
    public override float GetLargestLodDistance()
    {
        return deactivateAtDistance;
    }

    public override int GetLodAmount()
    {
        return lodMeshes.Length;
    }

    public override void ForceUpdateAllLods()
    {
        if (!Application.isPlaying)
            return;

        SetLODLevel(CurrentLodLevel);
    }

    protected override void OnLodLevelChanged()
    {
        if (!Application.isPlaying)
            return;

        SetLODLevel(CurrentLodLevel);
    }

    public override void SetNewRelativeDistance(float distance)
    {
        if (!Application.isPlaying)
            return;

        base.SetNewRelativeDistance(distance);

        if (deactivateAtDistance > 0f && distance > deactivateAtDistance)
        {
            CurrentLodLevel = -1;
            return;
        }

        float pixelSize = objectSize * pixelsPerMeter;
        float screenPortion = pixelSize / distance / Screen.width;

        screenPortion = Mathf.RoundToInt(screenPortion * 40f) * 0.025f;
        ComputeLODLevel(screenPortion);
    }

You will also need to comment out the Update() function in LODSwitcher.cs:

    //void Update () {
    //    if((Time.frameCount + frameOffset) % frameInterval != 0) return;  // no need to do this every frame for every object in the scene
    //    ComputeLODLevel();
    //}

I also turned off the "ExecuteInEditorMode" in LodObjectBase.cs since it was causing problems in the editor:

    //[ExecuteInEditMode]
    public abstract class LodObjectBase : MonoBehaviour

 

What happens now is that the LODManager that you created in your scene (remember to add an LODSource to your camera as well) for using QuickLOD will now see all the LODSwitchers as something it can use, and it will add them to a collection and poll all the objects by asking them to compute their LODs by providing the distance in the overriden functions from LodObjectBase that we added to LODSwitcher. QuickLOD's LODManager has a sophisticated grid based system for determining which grid cells of objects to update at a time, and it will also use the camera's view angle for determining which grid cells to update. The LODManager has checkboxes that you can enable that will show the overlay of it's grid in your scene inside the editor, the grid must be big enough cover everything in your scene (x,y and z) - and if you load scenes additively it would need to be big enough to cover those as well. QuickLOD divides your scene into cells and finds objects within the cell areas, which it then uses to efficiently determine which objects to poll each Update. The grid cells should also be small, maybe the size of a small house in your scene. If the cells are too large performance will suffer.

If you have been using SimpleLOD and you look at the Unity Profiler, you will see the LODSwitcher's Update() taking up a lot of CPU time once you have several hundred objects in the scene with LODSwitchers. By combining QuickLOD with SimpleLOD you can use the SimpleLOD system for generating LODs easily, and still have a high performing LOD system in place.

 

Below is an editor script for using SimpleLOD to generate LODs for all objects with a MeshRenderer in your active scene in the editor, except for ones that already have the LODSwitcher component. It saves the scene every time it generates the LODs for 5 objects, for some reason it tends to lock up Unity 5 (it was working fine under Unity 4). If you run the script and open the Task Manager in windows, you should see Unity taking up >20% CPU time. Once its no longer taking CPU time, its done (whether locked up or not). It can take a long time to generate a full scene of LODs, so you will want to let it work as long as its taking up CPU time and just end Unity if it wont respond and stays at zero percent CPU and for a while if you are running Unity 5.

        [MenuItem("Tools/Add SimpleLOD for all meshes")]
        public static void DoAddSimpleLODs()
        {
            Debug.Log("Add SimpleLODs starting...");
            SimpleLOD_EditorPopup window = (SimpleLOD_EditorPopup)EditorWindow.GetWindow(typeof (SimpleLOD_EditorPopup));
            window.OpenWindow();

            var lst = FindObjectsOfType<MeshRenderer>();
            List<MeshRenderer> objects = new List<MeshRenderer>();
            foreach(MeshRenderer mr in lst)
            {
                if (mr.GetComponent<LODSwitcher>())
                    continue;

                objects.Add(mr);
            }

            var total = objects.Count;
            Debug.Log(string.Format("{0} total objects to check", total));
           
            for(int currentObjectIndex=0; currentObjectIndex<total; currentObjectIndex++)
            {
                MeshRenderer mr = objects[currentObjectIndex];
                GameObject go = mr.gameObject;

                Debug.Log(string.Format("Processing {0} object named {1}", currentObjectIndex, go.name), go);

                Selection.activeGameObject = go;
                window.go = go;
           
                window.CreateLODSwitcherWithMeshes();
                //if (i <= total-250)
                    //break;

                EditorUtility.SetDirty(go);

                if (currentObjectIndex % 5 == 0)
                    EditorApplication.SaveScene();
            }

            Debug.Log("Add SimpleLODs done!");

            EditorApplication.SaveScene();
        }

 

You will probably also find the code below useful, I found problems with the way that SimpleLOD generates LODs when they are named similar.. an example is the free Bootcamp assets from Unity. A lot of the bootcamp models imported from FBX have the same name "metal" or "wood", and multiple meshes from the same FBX will have the name "metal". SimpleLOD doesn't handle this very well.. another major problem is that it won't see previously generated LODs - each time you ask SimpleLOD to generate LODs it will generate new LODs assets even for the exact same models. So I modified the function shown below to remember LOD names that it previously generated (mostly for using with the editor script above that generates an entire scene) and to store them in folders that include the parent object's name. You could move the recentMeshes variable outside of the SimpleLOD editorpopup window class, so that it stays around for the entire time that your Unity editor is open (make it static) so that each SimpleLOD window remember the previously generated lods also. My code only handles generating LODs with meshes, and not the other methods (generating child object LODs) but you could copy the code to work with those also.

The code below replaces CreateLODSwitcherWithMeshes() in the SimpleLOD_Editor.cs file:

    Dictionary<string, Mesh> recentMeshes = new Dictionary<string,Mesh>();

    public string CreateLODSwitcherWithMeshes () {
        char[] trimChars = {' ','1','2','3','4','5','6','7','8','9','0'};
        string path = string.Empty;
        string subFolder = "LODMeshes";
        string trimName = go.name.TrimEnd(trimChars);
        var firstMesh = go.Get1stSharedMesh();
        if (firstMesh != null)
        {
            switch(firstMesh.name)
            {
                case "concret":
                case "concrete":
                case "metal":
                case "wood":
                case "dirt":
                case "alphas":
                    if (go.transform.parent)
                    {
                        switch(trimName)
                        {
                            case "concret":
                            case "concrete":
                            case "metal":
                            case "wood":
                            case "dirt":
                            case "alphas":
                                trimName = go.transform.parent.name.TrimEnd(trimChars) + "_" + trimName;
                                break;
                        }
                    }
                    subFolder += "_" + trimName;
                    break;
            }
        }
        path = StoragePathUsing1stMeshAndSubPath(subFolder);

        if(path != null) {
            Mesh[] meshes = null;
            //try {
            if (firstMesh && firstMesh.vertexCount >= 200)
                meshes = go.SetUpLODLevelsWithLODSwitcher(new float[3] {.2f, 0.1f, 0.05f}, compression, recalcNormals, smallObjectsValue);
            else
            {
                meshes = new Mesh[1];
                meshes[0] = firstMesh;

                var sw = go.AddComponent<LODSwitcher>();
                sw.ComputeDimensions();

                sw.lodMeshes = new Mesh[1];
                sw.lodMeshes[0] = firstMesh;

                sw.lodScreenSizes = new float[1];
                sw.lodScreenSizes[0] = .2f;
            }
            //} catch(Exception e) {
//                Debug.Log("SetUpLODLevelsWithLODSwitcher exception:" + e.Message);
                //return e.Message;
            //}

            if(meshes != null) {
                //try {
                    var sw = go.GetComponent<LODSwitcher>();

                    string sizeStr = "";
                    sizeStr = "LOD 0: " + meshes[0].vertexCount + " vertices, " + (meshes[0].triangles.Length / 3) + " triangles";
                    path = path + "/" + meshes[0].name + "_LOD";

                    if (firstMesh.vertexCount < 200)
                    {
                        // already done
                    }
                    else
                    if (meshes[1].vertexCount == meshes[2].vertexCount && meshes[2].vertexCount == meshes[3].vertexCount) // no improvement by decimation
                    {
                        sw.lodMeshes = new Mesh[1];
                        sw.lodMeshes[0] = firstMesh;

                        sw.lodScreenSizes = new float[1];
                        sw.lodScreenSizes[0] = .2f;
                    }
                    else
                    if (recentMeshes.ContainsKey(path+".asset") && recentMeshes.ContainsKey(path+"1.asset") && recentMeshes.ContainsKey(path+"2.asset") && recentMeshes.ContainsKey(path+"3.asset"))
                    {
                        sw.lodMeshes = new Mesh[4];
                        sw.lodMeshes[0] = recentMeshes[path+".asset"];
                        sw.lodMeshes[1] = recentMeshes[path+"1.asset"];
                        sw.lodMeshes[2] = recentMeshes[path+"2.asset"];
                        sw.lodMeshes[3] = recentMeshes[path+"3.asset"];
                    }
                    else
                    {
                        Mesh mesh1 = AssetDatabase.LoadAssetAtPath(path+"1.asset", typeof(Mesh)) as Mesh;
                        Mesh mesh2 = AssetDatabase.LoadAssetAtPath(path+"2.asset", typeof(Mesh)) as Mesh;
                        Mesh mesh3 = AssetDatabase.LoadAssetAtPath(path+"3.asset", typeof(Mesh)) as Mesh;
                        if (mesh1 && mesh2 && mesh3)
                        {
                            sw.lodMeshes = new Mesh[4];
                            sw.lodMeshes[0] = firstMesh;
                            sw.lodMeshes[1] = mesh1;
                            sw.lodMeshes[2] = mesh2;
                            sw.lodMeshes[3] = mesh3;
                        }
                        else
                        {
                            recentMeshes.Add(path+".asset", meshes[0]);
                            for(int i=1;i<meshes.Length;i++) {
                                if (meshes[i])
                                {
                                    sizeStr = sizeStr + "\nLOD " + i +": " + meshes[i].vertexCount + " vertices, " + (meshes[i].triangles.Length / 3) + " triangles";
                                    string meshPath = AssetDatabase.GenerateUniqueAssetPath(path + i + ".asset");
                                    AssetDatabase.CreateAsset(meshes[i], meshPath);
                                    AssetDatabase.SaveAssets();

                                    recentMeshes.Add(path+i+".asset", meshes[i]);
                                }
                            }
                        }
                    }
                   
                    //Resources.UnloadUnusedAssets();
                    return "Finished! LOD meshes were saved under "+path+"[1..."+meshes.Length+"].\n" + sizeStr;
                //} catch(Exception e) {
                //    Debug.Log("Save SimpleLOD assets exception: "+e.Message);
                //    return e.Message;
                //}
            }
            else
            {
                Debug.Log("meshes is null");
            }
        }
        return "No mesh found in gameobject";
    }

 

Posted: May 03 2015, 05:06 by Michael Urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: LOD | Unity Bookmark and Share
Unity Find materials by shader name tool and LOD

Here is a tool that will search all material assets in the database and current scene by shader name. It's useful for finding materials that are using a shader. In my case I had a shader that was not working with DirectX 11 turned on and so I needed to switch to a different shader. Just modify the code to change the .Contains("_dust") to be something else.

The other tool is for finding objects in the current scene that are missing meshes. I found this useful because QuickLOD would change all the meshs to be null even while in the editor sometimes (it should only do this at runtime, part of the reason that I stopped using it) and after I stopped using that component some of my meshes were still null. The author needs to update the code and make more Application.isRunning checks. I found SimpleLOD to be a better asset but the way it handles LOD is not nearly as fast as QuickLOD because every LODSwitcher component does it's own processing whereas QuickLOD has a manager that does batches of objects and also ignores components outside of the view angle and distance. I'm going to see if I can rip the LODManager code from QuickLOD and make it poll the LODSwitchers from SimpleLOD Cool to create a better LOD hybrid.

 


namespace SubtleTechnology
{
    public class UnityTools
    {
        [MenuItem("Tools/Find materials using a shader")]
        public static void Do6()
        {
            Debug.Log("Finding materials with a shader in database...");
            var lst = Resources.FindObjectsOfTypeAll(typeof(Material)) as Material[];
            foreach(var mat in lst)
            {
                if (mat.shader.name.Contains("_dust"))
                    Debug.Log("Found material using shader: "+mat.name);
            }
            Debug.Log("Finding materials with shader done!");
        }

        [MenuItem("Tools/Find mesh filter with null mesh")]
        public static void Do7()
        {
            Debug.Log("Finding null mesh filters...");
            var lst = GameObject.FindObjectsOfType(typeof(MeshFilter)) as MeshFilter[];
            foreach(var mf in lst)
            {
                if (mf.sharedMesh == null)
                    Debug.Log("Found null mesh shader: "+mf.name);
            }
            Debug.Log("Finding null meshes done!");
        }
    }
}

Posted: Mar 06 2015, 10:45 by Michael Urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: C# | Unity | LOD Bookmark and Share
Unity questions and answers and a quick start to building a game

I spend quite a bit of time answering questions at both:

http://forum.plyoung.com/

If you have Unity questions come by and ask us.

If anyone needs help with Unity games this framework will help you build your game, although it does not really support first person types of games right now.

 

I also added some youtube videos that gives you a quick start into build a game with PlyGame:

http://www.youtube.com/playlist?list=PLRcetVlsroIgPOFzBsYCKca6GrAs2UUSK

 

 

 

Posted: Feb 22 2015, 23:44 by Michael Urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Unity | PlyGame Bookmark and Share
How to stop Skype Click 2 Call phone number converting highlighting via javascript detection for html elements

The Skype Click 2 Call plugins for IE, FireFox, and Chrome examines the html elements for phone numbers and adds the ability to click on the phone numbers to dial. Unfortunately it applies it's own styles to the phone number elements and injects html. Several ways to disable the Skype injection have been offered by the development community. One of the most quoted method is to add a Meta tag to your pages, but this disables the Skype plugin for the entire page content. Ideally, you would want to work around their styling or disable it only for certain elements. Below I document the methods suggested by myself as well as the other methods documented by the development community.

The first and possibly ideal method is to examine the Skype Click 2 Call plugin source code and find out what they are looking for in a page. As it turns out, they avoid certain elements when parsing a web page. I have taken the avoidance code from the Skype Click 2 Call plugin for FireFox and displayed it below.

Taken from Skype click to call ff extension – contentscript.js as of May 6th, 2014:


/**
* @brief Object that stores html tags that we want to ignore.
*        NOTE: Items in this list should be sorted array because we use binary search.
*/

var ElementsFilter = {
    // DOM elements to ignore
    elementsToIgnore : [   "a", "acronym", "applet", "area", "audio",
                           "button", "canvas", "code",
                           "col", "colgroup", "command",
                           "datalist", "del", "dir",
                           "embed", "frame", "frameset",
                           "iframe", "img", "input",
                           "kbd", "keygen", "label", "link",
                           "map", "menu", "meta", "meter",
                           "nav", "noframes", "noscript",
                           "object", "optgroup", "option", "output",
                           "param", "progress", "s", "samp",
                           "script", "select", "source", "strike",
                           "style", "textarea", "time",
                           "track", "var", "video"],

The simplest and most obvious method when examining this list of elements shows that the anchor tag <a> is ignored by the plugin. Many web developers use a <span> to display the phone number, and Skype find those to turn into links. However, you should try to output them as <a href="tel:1404-555-1212">1 (404) 555-12121 (404) 555-1212</a> tags instead. This gives the desktop and mobile devices the ability to use the link to dial the phone number directly, without the need for Skype's markup. The href="tel:" works similar to a href="mailto", it allows whatever default dialing method or application to be used for dialing the phone number.

Another possibly valid alternate is to use <label> elements for phone numbers, but I recommend the more natural <a href="tel:xxx"> method over it.

 

The second method several posts documented was to disable the Skype plugin for an entire web page by adding the following Meta tag:


<meta name="SKYPE_TOOLBAR" content="SKYPE_TOOLBAR_PARSER_COMPATIBLE" />

Some complained that this tag was no longer working, others say that it works just fine. It may depend on the web browser and its variant of the plugin.

 

A third method was to insert a soft hyphen by adding "&shy" into the phone number. An example would be <span>1&shy;800&shy;555&shy;1212</span> for a number like 1-800-555-12121-800-555-1212. However, this method may not continue to work long term if Skype at some point decides to take into account the soft hyphens or other markup added to the text when looking for phone number.

Yet another suggested method was to override a setting element that Skype injects into the web page by placing your own copy early in the page. By adding the following html early (presumably right after the body begin tag):
<!-- <span id="skype_highlighting_settings" display="none" autoextractnumbers="0"></span> -->

Another method someone felt worked was to add more spaces into the area code/county code portion of a phone number like "( 800 ) 555-1212( 800 ) 555-1212".

Yet another method, using CSS to disable skype by adding the following rule in your stylesheets. It hides the skype elements.

span[class^='skype_pnh_container'] { display:none !important; }
span[class^='skype_pnh_print_container'] { display:inline !important; }

Credit goes to stackoverflow for the list of possible solutions beyond my own suggestion:
http://stackoverflow.com/questions/3032427/how-to-prevent-phone-numbers-to-be-converted-into-skype-links

Hopefully this post will save you some time in deciding on a method to disable Skype Click to Call for your web pages.

Posted: May 06 2014, 20:25 by Michael Urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: skype | Web Development Bookmark and Share
Web Development Portfolio Pages

I have added a Portfolio Page to show some of my recent web application work. The first is on TripFiles.com:

Click here to view the portfolio for TripFiles

Posted: Mar 11 2014, 08:00 by Michael Urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Bookmark and Share
A jQuery dataTable plugin for sorting currency or formatted numbers with html

The dataTables.net website lists a number of sorting plugins for the jQuery dataTable, one of them being the formatted-num plugin for sorting currency and the other is num-html that strips html you have inside your table (but it doesn't handle commas or dollar signs).. so I combined the two to create a pseudo-number sort. It will sort any number as long as the numbers are displayed in the datatable with consistent decimal precision. If you display two decimal places on numbers then you must always display two ("1.50" and "1.00"). You can't have other numbers in the datatable with varying decimal places or the sorting will not work correctly("1.2" or whole numbers like "1"). It will sort ip addresses, tags like NNN-NNN, currency, numbers with comas, numbers with currency symbols, and other types of mixed numerics. It works similar to the formatted-num plugin except that it strips all characters out, including periods or commas (for internationalization). The resulting number (if you are maintaining the same decimal places) will sort.

I can understand why they list the datatable sort plugins separately, but it is better if the sorting does not break just because someone needed to add html into a datatable at a later time.

First, the code strips off currency symbols and commas, leaving the digits and minuses. Later, it checks for a beginning minus then strips all but digits and inserts the minus back on.

jQuery.extend( jQuery.fn.dataTableExt.oSort, {
    "pseudo-num-pre": function ( a ) {
        a = String(a).replace( /<[\s\S]*?>/g, "" );
        a = a.replace( /[^\d\-]/g, "" );

        var negative = false;
        if (a.length > 1 && a.slice(0, 1) == "-")
            negative = true;

        a = a.replace( /[^\d]/g, "" );
        if (negative)
            a = "-" + a;

        if (a === "-" || a === "")
            return 0;

        return parseFloat( a );
    },
 
    "pseudo-num-asc": function ( a, b ) {
        return a - b;
    },
 
    "pseudo-num-desc": function ( a, b ) {
        return b - a;
    }
} );

 

here is a more efficient (twice as fast) version, it still strips html but leaves the period(s) so the flexability drops considerably:

jQuery.extend( jQuery.fn.dataTableExt.oSort, {
    "pseudo-num-pre": function ( a ) {
        a = String(a).replace( /<[\s\S]*?>/g, "" );
        a = a.replace( /[^\d\-\.]/g, "" );
        if (a === "-" || a === "")
            return 0;
        return parseFloat( a );
    },
 
    "pseudo-num-asc": function ( a, b ) {
        return a - b;
    },
 
    "pseudo-num-desc": function ( a, b ) {
        return b - a;
    }
} );

Not my best work (probably the multiple regex could be combined) but I thought someone might find it useful.

Posted: Feb 20 2014, 15:27 by Michael Urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: jQuery Bookmark and Share
A technique for ASP.NET MVC 5 themes via css optimization bundling

In this article I will demonstrate a technique to dynamically apply the concept of individual themes to an ASP.NET MVC website. They could be custom themes, bootstrap themes, or any kind of extra CSS that you want to include in a dynamic way. Each time the web application starts up it will enumerate the directories in ~/App_Themes and add the CSS files from the chosen theme subdirectory to the .net bundling and minification process.

Once you have the following code in place, you just create subdirectories in /App_Themes in your MVC web site, like /App_Themes/DarkBlue. Inside the sub folder you can place as many css files as you need for your theme. The folder name can be anything, I just use App_Themes as an example and its familiar to people who come from ASP.NET Web Forms. Later in the example I use AppUser.CurrentUser.Theme.Name to get the "DarkBlue" string from the currently user based on their saved preference. AppUser being a static thread safe helper class or an injected dependency for all views.

For instance you can have:

/App_Themes/DarkBlue/firstpartoftheme.css
/App_Themes/DarkBlue/secondpartoftheme.css
/App_Themes/DarkBlue/images/darkblueicon.png

and in your css (or less files if you want to compile from it) you can use relative paths like background-image:url(images/darkblueicon.png) to reference them if you like.

    public class BundleConfig
    {
        public static void AddThemesFolderBundles(BundleCollection bundles)
        {
            string themesFolderName = AppSettings.ThemesFolderName;

            string themesPath = HttpContext.Current.Server.MapPath("~").AppendBackSlash() + themesFolderName;
            if (System.IO.Directory.Exists(themesPath))
            {
                IEnumerable<string> themeFolders = System.IO.Directory.GetDirectories(themesPath);
                foreach (string path in themeFolders)
                {
                    string folderName = new System.IO.DirectoryInfo(path).Name;
                    string bundleName = String.Format("~/{0}/{1}/CssBundle", themesFolderName, folderName);
                    string bundleVirtualPath = String.Format("~/{0}/{1}/{2}", themesFolderName, folderName, AppSettings.ThemesIncludeFileName);

                    bundles.Add(new StyleBundle(bundleName).Include(bundleVirtualPath));
                }
            }
        }

        public static void RegisterBundles(BundleCollection bundles)
        {
            AddThemesFolderBundles(bundles);

and a typical global.asax.cs might look like:

        protected void Application_Start()
        {
            try
            {
                AreaRegistration.RegisterAllAreas();
                GlobalConfiguration.Configure(WebApiConfig.Register);
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);

then in your cshtml page for instance _Layout.cshtml you would add a line after the <title> section:


    <title>My Web Page Title</title>
    @Styles.Render("~/css/othercssbundles")   
    @Styles.Render("~/" + AppSettings.ThemesFolderName + "/" + AppUser.CurrentUser.Theme.Name + "/CssBundle")
    @RenderSection("Styles", required: false)

The AppUser.CurrentUser.Theme.Name would resolve to "DarkBlue" for this example.

I usually make a static AppSettings class in my web projects for type-safe, easy access to web.config-possible settings:

    public static class AppSettings
    {
        public static string ThemesFolderName = ConfigurationManager.AppSettings["ThemesFolderName"] ?? "App_Themes";
        public static string ThemesIncludeFileName = ConfigurationManager.AppSettings["ThemesIncludeFileName"] ?? "*.css";

 

because we are dynamically including the theme you can use this example to set the theme based on the logged in user or via a web.config setting.

 

AppendBackSlash is part of a simple String extension:

    public static class StringExtensions
    {
        public static string AppendBackSlash(this String s)
        {
            if (s.EndsWith(@"\"))
                return s;

            return s + @"
\";
        }
        public static string AppendForwardSlash(this String s)
        {
            if (s.EndsWith(@"
/"))
                return s;

            return s + @"
/";
        }
    }

Bundling and minification are two techniques you can use in ASP.NET 4.5 to improve request load time.  Bundling and minification improves load time by reducing the number of requests to the server and reducing the size of requested assets (such as CSS and JavaScript) - quote as borrowed from the bundling and minification tutorial http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification )

I hope this article saves you some time.

Posted: Dec 18 2013, 07:54 by Michael Urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: ASP.NET | C# | MVC Bookmark and Share
Sencha ExtJS and jQuery, Intellisense and getting started

It's probably been quite a while since I blogged anything really useful. So I have been working for the past few months at a company called AgilityNOW, making some modifications, adding new features, and learning the framework architected by the two business entrepreneurs. Their framework is designed around ExtJS, Microsoft ASP.NET with ASP.NET WebMethods for web services. The mainly JavaScript based ExtJS front end consumes said web services and the ASPX pages/server side is mostly for serving up the data and CRUD operations. I have been learning about ExtJS, which has a pretty steep learning curve and I wanted to begin sharing some of my experiences about getting started with ExtJS and their controls in this type of environment. Since I have been doing server side ASP.NET using the ASP.NET UI controls for such a long time, it is especially hard for me to step away from the ASP.NET framework and begin using 100% JavaScript based UI that consumes web services. There are some benefits to doing it this way, and elimination of the postbacks and viewstate is nice but I still was very reluctant to go down this path if it weren't required by the company. My experience for the past few months have been exclusive to ExtJS, since jQuery is not allowed (from my understanding of the reasons why, it is that mixing the frameworks will cause confusion without a standard framework and there is a valid point there). I have a lot of positive experience with jQuery and I still think it is easier to use, but I will write about my experience with ExtJS to help others who are having the same difficult time learning how to use it. I've also been trying to decide on what kind of Testing framework to suggest to be used here, I've used a few that could be helpful but ExtJS adds its own complexity to the testing situation.

First off, ExtJS and jQuery were from my limited understanding, originally frameworks used for difference purposes. Originally jQuery started out as a DOM manipulation framework and not really trying to provide heavy functional controls. The jQuery UI was released a while after jQuery started, sometime in 2007. ExtJS from my understanding was really built from the ground up to be UI centric and provide controls that are used to build full featured UI from. ExtJS has a good object oriented design / architecture and their UI controls are meant to be used together to create great looking, theme-able UIs. Every UI framework, including ASP.NET, has their own ways of doing things and once you understand them the pieces start to fall together and you can progress more easily building great UI. The difficult part is getting started and making sure that you have a solid foundation to begin with.

And so my first helpful tip on working with ExtJS will be adding Intellisense support to your web pages if you are working with Visual Studio 2010. There are patches and updates that get the Intellisense to work under VS 2008, and I will post that in a later article but for now I show you the simplest method for enabling it.

Adding JavaScript Intellisense with ExtJS revolves around making sure that your ExtJS code is separated out into ".js" files. This is just good programming practice but a lot of developers may get used to entering their JavaScript inline to the web pages. To use the Intellisense hints below, they must be at the top of a ".js" file for Visual Studio to recognize them. I know that they do not work on ASPX pages for obvious reasons, but I haven't tried using them on ".html" or other file types yet.

/// <reference path="~/scripts/ext/adapter/ext/ext-base.js" />
/// <reference path="~/scripts/ext/ext-all.js" />

You can also add more of your own .js references, and have one .js file reference another for Intellisense purposes, for instance:

/// <reference path="~/scripts/ext/adapter/ext/ext-base.js" />
/// <reference path="~/scripts/ext/ext-all.js" />
/// <reference path="~/scripts/HomePage.js" />
/// <reference path="~/scripts/AboutUsPage.js" />

Ext.onReady(function() {
        … initialize your controls here …
    });

For simplicity I used web page naming conventions for our JavaScript includes and assuming we have a HomePage.aspx or HomePage.html we would have a corresponding HomePage.js include file. When working with ExtJS, you will want to use fully qualified namespace naming conventions or class naming conventions for the files names. If you are designing UI components with ExtJS, many times you will have classes that are reusable and do not necessarily have to be tied to a page.

The last tip you will need to know is that you must press CTRL-SHIFT-J to manually tell Visual Studio to reload your JS references. If there are javascript warnings or errors (quite possible) in your javascript references, they will be shown on the Output window in Visual Studio.

Unfortunately, the Intellisense does not give you insight into the many class properties that you have to initialize when you are building the controls, but it does help for some of the simpler dom manipulations. Sencha has updated the ExtJS documentation site recently, and it has a great autocomplete/suggestion search box format that really helps find things quickly (the old version of the doc site was not so good).

Here is a link to the Sencha Ext JS docs for any versions of their framework:
http://docs.sencha.com/

Posted: Nov 16 2011, 01:49 by michael urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Bookmark and Share
FaceBook’s Like / Share feature and your website thumbnail image

An awesome discussion on the local web design group email list reminded me about the FaceBook Developer Tools page:

http://developers.facebook.com/tools/

At the bottom of this page is a tool called the URL Linter. It will check your website for FaceBook properties and meta data that FB expects, and it shows you what thumbnail image you may have entered in your HTML metadata. One of the users in the discussion group (thanks Dede!) mentioned that it also has the side effect of refreshing FaceBook’s cache of your thumbnail.

Sites wanting to display a thumbnail image when a user enters their URL for the Share or Like feature on FaceBook have to put specific HTML markup on the home page. The Linter tool mentions FaceBook’s Open Graph protocol properties a lot, as this is the new system that handles rendering the thumbnail image for your website.

One of the easiest ways to learn something is by example, just remember that my examples may not be perfect or will become outdated. Here is a sample of Subtle Technology’s home page HTML:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server" profile="http://gmpg.org/xfn/11">
    <link rel="stylesheet" href="style.css" type="text/css" />
    <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
    <link rel="image_src" href="http://www.subtletechnology.com/SubtleTechnologyFBThumbnail.png" />
    <meta property="og:title" content="Subtle Technology Inc - Technical blurbs by Michael Urvan"/>
    <meta property="og:type" content="company"/>
    <meta property="og:url" content="http://www.subtletechnology.com/"/>
    <meta property="og:site_name" content="SubtleTechnology"/>
    <meta property="fb:admins" content="1321525614"/>
    <meta property="og:description"
          content="A blog about Microsoft .NET, Silverlight, FaceBook, Twitter, and other technologies for Subtle Technology Inc by Michael Urvan"/>
    <meta property="og:image" content="http://www.subtletechnology.com/SubtleTechnologyFBThumbnail.png"/>
</head>

The image_src is the old method of specifying the thumbnail image for your web site, and FaceBook will probably continue to support that method. Remember that the thumbnail image is grabbed and cached by FaceBook systems, and using the URL Linter tool will help refresh that cache if you are seeing an old Thumbnail Image when you try to test the Share or Like FaceBook feature. Remember that FaceBook servers may abide by cache period metadata returned in the HTTP headers by your webserver or caching pragmas in your HTML markup.

Posted: Jul 23 2011, 14:30 by michael urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Facebook Bookmark and Share
Tumblr Yoda Bot

Yoda has decided to add his daily words of wisdom to Tumblr.

http://yodabot.tumblr.com

 

Posted: Jun 05 2011, 21:29 by Michael Urvan | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Star Wars | Tumblr | Yoda Bot Bookmark and Share