Bladeren bron

Examples restructuring. The old Test is now remade in script and called GraphicsTest. The ScriptTest has been renamed to NSWRemake. The executable for running the script examples is renamed to Urho3D.exe.
Various script API fixes. CScriptArray is again garbage collected.
More file & ProcessUtils functions exposed to script.
More random functions exposed to script.
VolumeNode & GeometryNode exposed to script.

Lasse Öörni 15 jaren geleden
bovenliggende
commit
e7a3473066

BIN
Bin/Data/Models/Box.mdl


BIN
Bin/Data/Models/CloudPlane.mdl


+ 576 - 0
Bin/Data/Scripts/GraphicsTest.as

@@ -0,0 +1,576 @@
+// GraphicsTest example
+
+const uint NUM_OBJECTS = 250;
+const uint NUM_LIGHTS = 20;
+const uint NUM_INSTANCENODES = 20;
+const uint NUM_INSTANCES = 50;
+const uint NUM_BILLBOARDNODES = 20;
+const uint NUM_BILLBOARDS = 15;
+
+Scene@ testScene;
+Camera@ camera;
+Light@ cameraLight;
+float yaw = 0.0;
+float pitch = 0.0;
+float objectangle = 0.0;
+bool paused = true;
+
+int texturequality = 2;
+int materialquality = 2;
+bool usespecular = true;
+bool drawshadows = true;
+int shadowmapsize = 1024;
+bool hiresshadowmap = false;
+bool useocclusion = true;
+bool attach = true;
+int drawdebug = 0;
+
+array<Entity@> animatingObjects;
+array<Entity@> billboards;
+array<Entity@> lights;
+
+void start()
+{
+    if (engine.isHeadless())
+    {
+        errorDialog("GraphicsTest", "Headless mode is not supported. The program will now exit.");
+        engine.exit();
+        return;
+    }
+
+    initConsole();
+    initScene();
+    initUI();
+    createCamera();
+
+    subscribeToEvent("Update", "handleUpdate");
+    subscribeToEvent("KeyDown", "handleKeyDown");
+    subscribeToEvent("MouseMove", "handleMouseMove");
+    subscribeToEvent("MouseButtonDown", "handleMouseButtonDown");
+    subscribeToEvent("MouseButtonUp", "handleMouseButtonUp");
+    subscribeToEvent("PostRenderUpdate", "handlePostRenderUpdate");
+}
+
+void runFrame()
+{
+    engine.runFrame(testScene, camera, true);
+
+    if (input.getKeyPress(KEY_ESCAPE))
+        engine.exit();
+}
+
+void initScene()
+{
+    @testScene = engine.createScene("GraphicsTest", BoundingBox(-1000.0, 1000.0), 8, true);
+    
+    PhysicsWorld@ world = testScene.getPhysicsWorld();
+    
+    // Set the physics world parameters
+    world.setGravity(Vector3(0.0, -9.81, 0.0));
+    world.setFps(100);
+    world.setLinearRestThreshold(0.1);
+    world.setAngularRestThreshold(0.1);
+    world.setContactSurfaceLayer(0.001);
+
+    // Create the directional light
+    Entity@ sun = testScene.createEntity();
+    Light@ sunLight = sun.createComponent("Light");
+    sunLight.setLightType(LIGHT_DIRECTIONAL);
+    sunLight.setDirection(Vector3(0.5, -1.0, 0.5));
+    sunLight.setColor(Color(0.2, 0.2, 0.2));
+    sunLight.setSpecularIntensity(1.0);
+    //sunLight.setCastShadows(true);
+    //sunLight.setShadowCascade(CascadeParameters(3, 0.95, 0.2, 500.0));
+    
+    // Create a zone to control the ambient lighting
+    Entity@ zoneEntity = testScene.createEntity();
+    Zone@ zone = zoneEntity.createComponent("Zone");
+    zone.setBoundingBox(BoundingBox(-1000.0, 1000.0));
+    zone.setAmbientColor(Color(0.1, 0.1, 0.1));
+
+    // Create the "floor"
+    for (int y = -5; y <= 5; y++)
+    {
+        for (int x = -5; x <= 5; x++)
+        {
+            Entity@ newEntity = testScene.createEntity();
+            RigidBody@ body = newEntity.createComponent("RigidBody");
+            body.setPosition(Vector3(x * 20.5, -0.5, y * 20.5));
+            body.setScale(Vector3(10.0, 0.5, 10.0));
+            body.setCollisionShape(cache.getResource("CollisionShape", "Physics/Box.xml"));
+            body.setCollisionGroup(2);
+            body.setCollisionMask(1);
+            
+            StaticModel@ object = newEntity.createComponent("StaticModel");
+            object.setModel(cache.getResource("Model", "Models/Box.mdl"));
+            object.setMaterial(cache.getResource("Material", "Materials/Test.xml"));
+            body.addChild(object);
+        }
+    }
+    
+    // Create 2 occluder walls
+    for (int x = 0; x < 2; x++)
+    {
+        Entity@ newEntity = testScene.createEntity();
+        RigidBody@ body = newEntity.createComponent("RigidBody");
+        body.setPosition(Vector3(0.0, 5.0, 0.0));
+        body.setRotation(Quaternion(x * 90.0f, Vector3(0, 1, 0)));
+        body.setScale(Vector3(112.0, 5.0, 0.5));
+        body.setCollisionShape(cache.getResource("CollisionShape", "Physics/Box.xml"));
+        body.setCollisionGroup(2);
+        body.setCollisionMask(1);
+        
+        StaticModel@ object = newEntity.createComponent("StaticModel");
+        object.setModel(cache.getResource("Model", "Models/Box.mdl"));
+        object.setMaterial(cache.getResource("Material", "Materials/Test.xml"));
+        object.setCastShadows(true);
+        object.setOccluder(true);
+        body.addChild(object);
+    }
+    
+    // Create static mushroom with physics
+    {
+        Entity@ newEntity = testScene.createEntity();
+        RigidBody@ body = newEntity.createComponent("RigidBody");
+        body.setPosition(Vector3(50.0, 0.0, 50.0));
+        body.setScale(10.0);
+        body.setCollisionShape(cache.getResource("CollisionShape", "Physics/Mushroom.xml"));
+        body.setCollisionGroup(2);
+        body.setCollisionMask(1);
+        
+        StaticModel@ object = newEntity.createComponent("StaticModel");
+        object.setModel(cache.getResource("Model", "Models/Mushroom.mdl"));
+        object.setMaterial(cache.getResource("Material", "Materials/Mushroom.xml"));
+        object.setCastShadows(true);
+        object.setOccluder(true);
+        body.addChild(object);
+    }
+    
+    // Create instanced mushrooms
+    for (uint i = 0; i < NUM_INSTANCENODES; ++i)
+    {
+        Entity@ newEntity = testScene.createEntity();
+        InstancedModel@ instanced = newEntity.createComponent("InstancedModel");
+
+        instanced.setModel(cache.getResource("Model", "Models/Mushroom.mdl"));
+        instanced.setMaterial(cache.getResource("Material", "Materials/Mushroom.xml"));
+        instanced.setPosition(Vector3(random() * 160.0 - 80.0, 0.0, random() * 160.0 - 80.0));
+        instanced.setCastShadows(true);
+        instanced.setNumInstances(NUM_INSTANCES);
+
+        for (uint j = 0; j < NUM_INSTANCES; ++j)
+        {
+            Vector3 position(random() * 20.0f - 10.0f, 0.0f, random() * 20.0f - 10.0f);
+            float angle = random() * 360.0f;
+            float size = 1.0f + random() * 2.0f;
+            
+            Instance@ instance = instanced.getInstance(j);
+            instance.position = position;
+            instance.rotation = Quaternion(angle, Vector3(0, 1, 0));
+            instance.scale = Vector3(size, size, size);
+        }
+        
+        instanced.updated();
+    }
+    
+    // Create animated models
+    for (uint i = 0; i < NUM_OBJECTS; ++i)
+    {
+        Entity@ newEntity = testScene.createEntity();
+        AnimatedModel@ object = newEntity.createComponent("AnimatedModel");
+        
+        Vector3 position(random() * 180.0 - 90.0, 0.0, random() * 180.0 - 90.0);
+        float angle = random() * 360.0f;
+        
+        object.setPosition(position);
+        object.setRotation(Quaternion(angle, Vector3(0, 1, 0)));
+        object.setCastShadows(true);
+        object.setScale(1.0 + random() * 0.25);
+        object.setModel(cache.getResource("Model", "Models/Jack.mdl"));
+        object.setMaterial(cache.getResource("Material", "Materials/Jack.xml"));
+        object.setShadowDistance(200.0);
+        object.setDrawDistance(300.0);
+        
+        AnimationState@ anim = object.addAnimationState(cache.getResource("Animation", "Models/Jack_Walk.ani"));
+        anim.setUseNlerp(true);
+        anim.setLooped(true);
+        anim.setWeight(1.0f);
+
+        animatingObjects.insertLast(newEntity);
+    }
+    
+    // Create floating smoke clouds
+    for (uint i = 0; i < NUM_BILLBOARDNODES; ++i)
+    {
+        Entity@ newEntity = testScene.createEntity();
+        BillboardSet@ billboard = newEntity.createComponent("BillboardSet");
+
+        billboard.setNumBillboards(NUM_BILLBOARDS);
+        billboard.setPosition(Vector3(random() * 200.0 - 100.0, random() * 15.0 + 5.0, random() * 200.0 - 100.0));
+        billboard.setMaterial(cache.getResource("Material", "Materials/LitSmoke.xml"));
+        billboard.setBillboardsSorted(true);
+        
+        for (uint j = 0; j < NUM_BILLBOARDS; ++j)
+        {
+            Billboard@ bb = billboard.getBillboard(j);
+            bb.position = Vector3(random() * 15.0 - 7.5, random() * 8.0 - 4.0, random() * 15.0 - 7.5);
+            bb.size = Vector2(random() * 2.0 + 3.0, random() * 2.0 + 3.0);
+            bb.rotation = random() * 360.0;
+            bb.enabled = true;
+        }
+        
+        billboard.updated();
+        
+        billboards.insertLast(newEntity);
+    }
+    
+    // Create lights
+    for (uint i = 0; i < NUM_LIGHTS; ++i)
+    {
+        Entity@ newEntity = testScene.createEntity();
+        Light@ light = newEntity.createComponent("Light");
+
+        Vector3 position(
+            random() * 150.0 - 75.0,
+            random() * 30.0 + 30.0,
+            random() * 150.0 - 75.0
+        );
+        
+        Color color(
+            (randomInt() & 1) * 0.5 + 0.5,
+            (randomInt() & 1) * 0.5 + 0.5,
+            (randomInt() & 1) * 0.5 + 0.5
+        );
+        
+        if ((color.r == 0.5) && (color.g == 0.5) && (color.b == 0.5))
+            color = Color(1.0, 1.0, 1.0);
+        
+        float angle = random() * 360.0;
+        
+        light.setPosition(position);
+        light.setDirection(Vector3(sin(angle), -1.0, cos(angle)));
+        light.setLightType(LIGHT_SPOT);
+        light.setRange(75.0);
+        light.setRampTexture(cache.getResource("Texture2D", "Textures/RampExtreme.png"));
+        light.setFov(15.0);
+        light.setColor(color);
+        light.setSpecularIntensity(1.0f);
+        light.setCastShadows(true);
+        light.setShadowBias(BiasParameters(0.00002, 0.0));
+        light.setShadowResolution(0.5);
+        // The spot lights will not have anything near them, so move the near plane of the shadow camera farther
+        // for better shadow depth resolution
+        light.setShadowNearFarRatio(0.01);
+        
+        lights.insertLast(newEntity);
+    }
+}
+
+void animateScene(float timeStep)
+{
+    objectangle += 10.0f * timeStep;
+    
+    for (uint i = 0; i < lights.length(); ++i)
+    {
+        Light@ light = lights[i].getComponent("Light");
+        light.setRotation(Quaternion(0, objectangle * 2, 0));
+    }
+    
+    for (uint i = 0; i < animatingObjects.length(); ++i)
+    {
+        AnimatedModel@ model = animatingObjects[i].getComponent("AnimatedModel");
+        uint numAnims = model.getNumAnimationStates();
+        for (uint j = 0; j < numAnims; ++j)
+        {
+            AnimationState@ state = model.getAnimationState(j);
+            state.addTime(timeStep);
+        }
+    }
+    
+    for (uint i = 0; i < billboards.length(); ++i)
+    {
+        BillboardSet@ billboard = billboards[i].getComponent("BillboardSet");
+        uint numBB = billboard.getNumBillboards();
+        for (uint j = 0; j < numBB; ++j)
+        {
+            Billboard@ bb = billboard.getBillboard(j);
+            bb.rotation += 50.0 * timeStep;
+        }
+
+        billboard.updated();
+    }
+}
+
+void initConsole()
+{
+    Console@ console = engine.createConsole();
+    console.setNumRows(16);
+    console.setFont(cache.getResource("Font", "cour.ttf"), 12);
+    BorderImage@ cursor = console.getLineEditElement().getCursorElement();
+    cursor.setWidth(4);
+    cursor.setTexture(cache.getResource("Texture2D", "Textures/UI.png"));
+    cursor.setImageRect(112, 0, 116, 16);
+
+    engine.createDebugHud();
+    debugHud.setFont(cache.getResource("Font", "cour.ttf"), 12);
+    debugHud.setMode(DEBUGHUD_SHOW_STATS | DEBUGHUD_SHOW_MODE);
+}
+
+void initUI()
+{
+    XMLFile@ uiStyle = cache.getResource("XMLFile", "UI/DefaultStyle.xml");
+    
+    Cursor@ cursor = Cursor("Cursor");
+    cursor.setStyleAuto(uiStyle);
+    cursor.setPosition(renderer.getWidth() / 2, renderer.getHeight() / 2);
+    ui.setCursor(cursor);
+    
+    //XMLFile@ uiLayout = cache.getResource("XMLFile", "UI/TestLayout.xml");
+    //UIElement@ layoutRoot = ui.loadLayout(uiLayout, uiStyle);
+    //uiRoot.addChild(layoutRoot);
+}
+
+void createCamera()
+{
+    Entity@ cameraEntity = testScene.createEntity("Camera");
+    @camera = cameraEntity.createComponent("Camera");
+    camera.setAspectRatio(float(renderer.getWidth()) / float(renderer.getHeight()));
+    camera.setPosition(Vector3(-50.0, 2.0, -50.0));
+    
+    @cameraLight = cameraEntity.createComponent("Light");
+    cameraLight.setLightType(LIGHT_SPOT);
+    cameraLight.setDirection(Vector3(0.0, 0.0, 1.0));
+    cameraLight.setRange(50.0);
+    cameraLight.setColor(Color(2.0, 2.0, 2.0));
+    cameraLight.setSpecularIntensity(2.0);
+    cameraLight.setCastShadows(true);
+    cameraLight.setShadowResolution(0.5);
+    cameraLight.setShadowFocus(FocusParameters(false, false, false, 0.5, 3.0));
+    cameraLight.setRampTexture(cache.getResource("Texture2D", "Textures/RampWide.png"));
+    cameraLight.setSpotTexture(cache.getResource("Texture2D", "Textures/SpotWide.png"));
+    camera.addChild(cameraLight);
+}
+
+void handleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    float timeStep = eventData["TimeStep"].getFloat();
+
+    if (!paused)
+        animateScene(timeStep);
+
+    if (@ui.getFocusElement() == null)
+    {
+        float speedMultiplier = 1.0f;
+        if (input.getKeyDown(KEY_SHIFT))
+            speedMultiplier = 5.0f;
+        if (input.getKeyDown(KEY_CONTROL))
+            speedMultiplier = 0.1f;
+
+        if (input.getKeyDown('W'))
+            camera.translateRelative(Vector3(0, 0, 10) * timeStep * speedMultiplier);
+        if (input.getKeyDown('S'))
+            camera.translateRelative(Vector3(0, 0, -10) * timeStep * speedMultiplier);
+        if (input.getKeyDown('A'))
+            camera.translateRelative(Vector3(-10, 0, 0) * timeStep * speedMultiplier);
+        if (input.getKeyDown('D'))
+            camera.translateRelative(Vector3(10, 0, 0) * timeStep * speedMultiplier);
+        
+        if (input.getKeyPress('1'))
+        {
+            int nextRenderMode = renderer.getRenderMode();
+            if (input.getKeyDown(KEY_SHIFT))
+            {
+                --nextRenderMode;
+                if (nextRenderMode < 0)
+                    nextRenderMode = 2;
+            }
+            else
+            {
+                ++nextRenderMode;
+                if (nextRenderMode > 2)
+                    nextRenderMode = 0;
+            }
+            
+            renderer.setMode(RenderMode(nextRenderMode), renderer.getWidth(), renderer.getHeight(), renderer.getFullscreen(),
+                renderer.getVsync(), renderer.getMultiSample());
+        }
+        
+        if (input.getKeyPress('2'))
+        {
+            texturequality++;
+            if (texturequality > 2)
+                texturequality = 0;
+            pipeline.setTextureQuality(texturequality);
+        }
+
+        if (input.getKeyPress('3'))
+        {
+            materialquality++;
+            if (materialquality > 2)
+                materialquality = 0;
+            pipeline.setMaterialQuality(materialquality);
+        }
+        
+        if (input.getKeyPress('4'))
+        {
+            usespecular = !usespecular;
+            pipeline.setSpecularLighting(usespecular);
+        }
+        
+        if (input.getKeyPress('5'))
+        {
+            drawshadows = !drawshadows;
+            pipeline.setDrawShadows(drawshadows);
+        }
+        
+        if (input.getKeyPress('6'))
+        {
+            shadowmapsize *= 2;
+            if (shadowmapsize > 2048)
+                shadowmapsize = 512;
+            
+            pipeline.setShadowMapSize(shadowmapsize);
+        }
+        
+        if (input.getKeyPress('7'))
+        {
+            hiresshadowmap = !hiresshadowmap;
+            pipeline.setShadowMapHiresDepth(hiresshadowmap);
+        }
+        
+        if (input.getKeyPress('8'))
+        {
+            useocclusion = !useocclusion;
+            pipeline.setMaxOccluderTriangles(useocclusion ? 5000 : 0);
+        }
+
+        if (input.getKeyPress('L'))
+        {
+            attach = !attach;
+            if (attach)
+            {
+                cameraLight.setPosition(Vector3(0, 0, 0));
+                cameraLight.setRotation(Quaternion());
+                camera.addChild(cameraLight);
+            }
+            else
+            {
+                // Detach child and set world transform to match what it was before detach
+                camera.removeChild(cameraLight, true);
+            }
+        }
+        
+        if (input.getKeyPress(' '))
+        {
+            drawdebug++;
+            if (drawdebug > 2) drawdebug = 0;
+            engine.setDebugDrawMode(drawdebug);
+        }
+        
+        if (input.getKeyPress('P'))
+        {
+            paused = !paused;
+        }
+
+        if (input.getKeyPress('C'))
+            camera.setOrthographic(!camera.isOrthographic());
+
+        if (input.getKeyPress('T'))
+            debugHud.toggle(DEBUGHUD_SHOW_PROFILER);
+
+        if (input.getKeyPress('F'))
+        {
+            EdgeFilterParameters params = pipeline.getEdgeFilter();
+            if (params.maxFilter > 0.0f)
+                params.maxFilter = 0.0f;
+            else
+                params.maxFilter = 1.0f;
+            pipeline.setEdgeFilter(params);
+        }
+        
+        if (input.getKeyPress(KEY_ESCAPE))
+            engine.exit();
+    }
+}
+
+void handleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+    // Check for toggling the console
+    if (eventData["Key"].getInt() == 220)
+    {
+        console.toggle();
+        input.suppressNextChar();
+    }
+}
+
+void handleMouseMove(StringHash eventType, VariantMap& eventData)
+{
+    if (eventData["Buttons"].getInt() & MOUSEB_RIGHT != 0)
+    {
+        int mousedx = eventData["X"].getInt();
+        int mousedy = eventData["Y"].getInt();
+        yaw += mousedx / 10.0f;
+        pitch += mousedy / 10.0f;
+        if (pitch < -90.0f)
+            pitch = -90.0f;
+        if (pitch > 90.0f)
+            pitch = 90.0f;
+
+        camera.setRotation(Quaternion(yaw, Vector3(0, 1, 0)) * Quaternion(pitch, Vector3(1, 0, 0)));
+    }
+}
+
+void handleMouseButtonDown(StringHash eventType, VariantMap& eventData)
+{
+    int button = eventData["Button"].getInt();
+    if (button == MOUSEB_RIGHT)
+        uiCursor.setVisible(false);
+
+    // Test creating a new physics object
+    if ((button == MOUSEB_LEFT) && (@ui.getElementAt(ui.getCursorPosition(), true) == null) && (@ui.getFocusElement() == null))
+    {
+        Entity@ newEntity = testScene.createEntity();
+        
+        RigidBody@ body = newEntity.createComponent("RigidBody");
+        body.setMode(PHYS_DYNAMIC);
+        body.setPosition(camera.getPosition());
+        body.setRotation(camera.getRotation());
+        body.setScale(0.1);
+        body.setFriction(1.0);
+        body.setAngularMaxVelocity(500.0);
+        body.setCollisionShape(cache.getResource("CollisionShape", "Physics/Box.xml"));
+        body.setCollisionGroup(1);
+        body.setCollisionMask(3);
+        body.setLinearVelocity(camera.getUpVector() + camera.getForwardVector() * 10.0);
+        
+        StaticModel@ object = newEntity.createComponent("StaticModel");
+        object.setModel(cache.getResource("Model", "Models/Box.mdl"));
+        object.setMaterial(cache.getResource("Material", "Materials/Test.xml"));
+        object.setCastShadows(true);
+        object.setShadowDistance(75.0f);
+        object.setDrawDistance(100.0f);
+        body.addChild(object);
+    }
+}
+
+void handleMouseButtonUp(StringHash eventType, VariantMap& eventData)
+{
+    if (eventData["Button"].getInt() == MOUSEB_RIGHT)
+        uiCursor.setVisible(true);
+}
+
+void handlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+    IntVector2 pos = ui.getCursorPosition();
+    if (@ui.getElementAt(pos, true) == null)
+    {
+        Ray cameraRay = camera.getScreenRay(float(pos.x) / renderer.getWidth(), float(pos.y) / renderer.getHeight());
+        array<RayQueryResult> result = testScene.getOctree().raycast(cameraRay, NODE_GEOMETRY, NODE_BILLBOARDSET, 250.0f, RAY_TRIANGLE);
+        if (result.length() > 0)
+        {
+            VolumeNode@ node = result[0].node;
+            Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result[0].distance;
+            debugRenderer.addBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos + Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true);
+        }
+    }
+}

+ 26 - 8
Bin/Data/Scripts/NinjaSnowWar.as → Bin/Data/Scripts/NSWScript.as

@@ -1,3 +1,5 @@
+// Partial remake of NinjaSnowWar in script
+
 #include "Scripts/Controls.as"
 #include "Scripts/GameObject.as"
 
@@ -15,7 +17,7 @@ float cameraMaxDist = 500;
 float cameraSafetyDist = 30;
 bool paused = false;
 
-void init()
+void start()
 {
     initAudio();
     initConsole();
@@ -24,11 +26,9 @@ void init()
     createOverlays();
     spawnPlayer();
 
-    engine.createDebugHud();
-    debugHud.setFont(cache.getResource("Font", "cour.ttf"), 12);
-
     subscribeToEvent("Update", "handleUpdate");
     subscribeToEvent("PostUpdate", "handlePostUpdate");
+    subscribeToEvent("KeyDown", "handleKeyDown");
 }
 
 void runFrame()
@@ -52,6 +52,9 @@ void initAudio()
 
 void initConsole()
 {
+    if (engine.isHeadless())
+        return;
+
     Console@ console = engine.createConsole();
     console.setNumRows(16);
     console.setFont(cache.getResource("Font", "cour.ttf"), 12);
@@ -59,11 +62,14 @@ void initConsole()
     cursor.setWidth(4);
     cursor.setTexture(cache.getResource("Texture2D", "Textures/UI.png"));
     cursor.setImageRect(112, 0, 116, 16);
+
+    engine.createDebugHud();
+    debugHud.setFont(cache.getResource("Font", "cour.ttf"), 12);
 }
 
 void initScene()
 {
-    @gameScene = engine.createScene("ScriptTest", BoundingBox(-100000.0, 100000.0), 8, true);
+    @gameScene = engine.createScene("NSWScript", BoundingBox(-100000.0, 100000.0), 8, true);
 
     File@ levelFile = cache.getFile("TestLevel.xml");
     gameScene.loadXML(levelFile);
@@ -73,7 +79,8 @@ void createCamera()
 {
     Entity@ cameraEntity = gameScene.createEntity("Camera");
     @gameCamera = cameraEntity.createComponent("Camera");
-    gameCamera.setAspectRatio(float(renderer.getWidth()) / float(renderer.getHeight()));
+    if (!engine.isHeadless())
+        gameCamera.setAspectRatio(float(renderer.getWidth()) / float(renderer.getHeight()));
     gameCamera.setNearClip(10.0);
     gameCamera.setFarClip(16000.0);
     gameCamera.setPosition(Vector3(0, 200, -1000));
@@ -81,6 +88,9 @@ void createCamera()
 
 void createOverlays()
 {
+    if (engine.isHeadless())
+        return;
+
     BorderImage@ sight = BorderImage();
     sight.setTexture(cache.getResource("Texture2D", "Textures/Sight.png"));
     sight.setAlignment(HA_CENTER, VA_CENTER);
@@ -141,8 +151,6 @@ void spawnPlayer()
 
 void handleUpdate(StringHash eventType, VariantMap& eventData)
 {
-    if (input.getKeyPress(220))
-        console.toggle();
     if (input.getKeyPress(KEY_F1))
         debugHud.toggleAll();
     if (input.getKeyPress(KEY_F2))
@@ -233,3 +241,13 @@ void updateCamera()
     audio.setListenerPosition(pos);
     audio.setListenerRotation(dir);
 }
+
+void handleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+    // Check for toggling the console
+    if (eventData["Key"].getInt() == 220)
+    {
+        console.toggle();
+        input.suppressNextChar();
+    }
+}

+ 1 - 0
Bin/GraphicsTest.bat

@@ -0,0 +1 @@
+Urho3D Scripts/GraphicsTest.as

+ 1 - 0
Bin/NSWScript.bat

@@ -0,0 +1 @@
+Urho3D Scripts/NSWScript.as

+ 1 - 2
CMakeLists.txt

@@ -119,8 +119,7 @@ add_subdirectory (Engine/Script)
 add_subdirectory (Engine/UI)
 add_subdirectory (Examples/NetworkTest)
 add_subdirectory (Examples/NinjaSnowWar)
-add_subdirectory (Examples/ScriptTest)
-add_subdirectory (Examples/Test)
+add_subdirectory (Examples/Urho3D)
 add_subdirectory (SourceAssets/Models)
 add_subdirectory (SourceAssets/Shaders)
 add_subdirectory (ThirdParty/AngelScript)

+ 8 - 1
Engine/Common/ProcessUtils.cpp

@@ -48,6 +48,7 @@ bool miniDumpWritten = false;
 #include "DebugNew.h"
 
 std::string currentLine;
+std::vector<std::string> arguments;
 
 void setExecutableWorkingDirectory()
 {
@@ -91,7 +92,7 @@ void errorDialog(const std::string& title, const std::string& message)
     MessageBox(0, message.c_str(), title.c_str(), 0);
 }
 
-void getArguments(const char* cmdLine, std::vector<std::string>& arguments)
+void parseArguments(const char* cmdLine)
 {
     if (!cmdLine)
         return;
@@ -100,6 +101,7 @@ void getArguments(const char* cmdLine, std::vector<std::string>& arguments)
     unsigned cmdStart, cmdEnd;
     bool inCmd = false;
     bool inQuote = false;
+    arguments.clear();
     
     for (unsigned i = 0; i < cmdStr.length(); ++i)
     {
@@ -130,6 +132,11 @@ void getArguments(const char* cmdLine, std::vector<std::string>& arguments)
     }
 }
 
+const std::vector<std::string>& getArguments()
+{
+    return arguments;
+}
+
 bool getConsoleInput(std::string& line)
 {
     HANDLE input = GetStdHandle(STD_INPUT_HANDLE);

+ 3 - 1
Engine/Common/ProcessUtils.h

@@ -34,7 +34,9 @@ void openConsoleWindow();
 //! Display an error dialog with the specified title and message
 void errorDialog(const std::string& title, const std::string& message);
 //! Parse arguments from a command line
-void getArguments(const char* cmdLine, std::vector<std::string>& arguments);
+void parseArguments(const char* cmdLine);
+//! Return previously parsed arguments
+const std::vector<std::string>& getArguments();
 //! Read input from the console window. Return false if there was no input
 bool getConsoleInput(std::string& line);
 //! Return the user's document directory which should have read/write access for writing logs, savegames etc.

+ 19 - 14
Engine/Engine/Console.cpp

@@ -25,10 +25,10 @@
 #include "BorderImage.h"
 #include "Console.h"
 #include "Engine.h"
-#include "EngineEvents.h"
 #include "Font.h"
-#include "InputEvents.h"
 #include "LineEdit.h"
+#include "Renderer.h"
+#include "RendererEvents.h"
 #include "ScriptEngine.h"
 #include "StringUtils.h"
 #include "Text.h"
@@ -54,17 +54,17 @@ Console::Console(Engine* engine) :
     
     mBackground = new BorderImage();
     mBackground->setWidth(uiRoot->getWidth());
-    mBackground->setColor(C_TOPLEFT, Color(0.25f, 0.25f, 0.5f, 0.5f));
-    mBackground->setColor(C_TOPRIGHT, Color(0.25f, 0.25f, 0.5f, 0.5f));
-    mBackground->setColor(C_BOTTOMLEFT, Color(0.5f, 0.5f, 1.0f, 0.5f));
-    mBackground->setColor(C_BOTTOMRIGHT, Color(0.5f, 0.5f, 1.0f, 0.5f));
+    mBackground->setColor(C_TOPLEFT, Color(0.0f, 0.25f, 0.0f, 0.75f));
+    mBackground->setColor(C_TOPRIGHT, Color(0.0f, 0.25f, 0.0f, 0.75f));
+    mBackground->setColor(C_BOTTOMLEFT, Color(0.25f, 0.75f, 0.25f, 0.75f));
+    mBackground->setColor(C_BOTTOMRIGHT, Color(0.25f, 0.75f, 0.25f, 0.75f));
+    mBackground->setBringToBack(false);
     mBackground->setEnabled(true);
     mBackground->setVisible(false);
     mBackground->setPriority(200); // Show on top of the debug HUD
     
     mLineEdit = new LineEdit();
-    mLineEdit->setWidth(uiRoot->getWidth() - 8);
-    mLineEdit->setColor(Color(0.0f, 0.0f, 0.25f, 0.5f));
+    mLineEdit->setColor(Color(0.0f, 0.0f, 0.0f, 0.5f));
     mLineEdit->setDefocusable(false);
     mBackground->addChild(mLineEdit);
     
@@ -73,6 +73,7 @@ Console::Console(Engine* engine) :
     updateElements();
     
     subscribeToEvent(EVENT_TEXTFINISHED, EVENT_HANDLER(Console, handleTextFinished));
+    subscribeToEvent(EVENT_WINDOWRESIZED, EVENT_HANDLER(Console, handleWindowResized));
 }
 
 Console::~Console()
@@ -117,10 +118,7 @@ void Console::setVisible(bool enable)
     if (enable)
         mEngine->getUI()->setFocusElement(mLineEdit);
     else
-    {
         mLineEdit->setFocus(false);
-        mLineEdit->setText(std::string());
-    }
 }
 
 void Console::toggle()
@@ -169,6 +167,8 @@ bool Console::isVisible() const
 
 void Console::updateElements()
 {
+    int width = mEngine->getRenderer()->getWidth();
+    
     if (mFont)
     {
         try
@@ -182,12 +182,12 @@ void Console::updateElements()
         }
     }
     
+    mLineEdit->setWidth(width - 8);
     mLineEdit->setHeight(mLineEdit->getTextElement()->getRowHeight());
     mBackground->layoutVertical(0, IntRect(4, 4, 4, 4), true, true);
     
-    int maxWidth = mEngine->getUIRoot()->getWidth();
-    if (mBackground->getWidth() > maxWidth)
-        mBackground->setWidth(maxWidth);
+    if (mBackground->getWidth() > width)
+        mBackground->setWidth(width);
 }
 
 void Console::handleTextFinished(StringHash eventType, VariantMap& eventData)
@@ -203,3 +203,8 @@ void Console::handleTextFinished(StringHash eventType, VariantMap& eventData)
         mLineEdit->setText(std::string());
     }
 }
+
+void Console::handleWindowResized(StringHash eventType, VariantMap& eventData)
+{
+    updateElements();
+}

+ 2 - 0
Engine/Engine/Console.h

@@ -69,6 +69,8 @@ private:
     void updateElements();
     //! Handle enter pressed on the line edit
     void handleTextFinished(StringHash eventType, VariantMap& eventData);
+    //! Handle rendering window resize
+    void handleWindowResized(StringHash eventType, VariantMap& eventData);
     
     //! Engine
     WeakPtr<Engine> mEngine;

+ 19 - 10
Engine/Engine/Engine.cpp

@@ -44,6 +44,7 @@
 #include "PhysicsResourceFactory.h"
 #include "PhysicsWorld.h"
 #include "Pipeline.h"
+#include "ProcessUtils.h"
 #include "Profiler.h"
 #include "RegisterLibraries.h"
 #include "Renderer.h"
@@ -65,7 +66,8 @@
 
 Engine* Engine::sInstance = 0;
 
-Engine::Engine(const std::string& logFileName, bool headless) :
+Engine::Engine(const std::string& windowTitle, const std::string& logFileName, bool headless) :
+    mWindowTitle(windowTitle),
     mMinFps(10),
     mMaxFps(200),
     mMaxInactiveFps(50),
@@ -118,13 +120,13 @@ Engine::~Engine()
         sInstance = 0;
 }
 
-void Engine::init(const std::string& windowTitle, const std::vector<std::string>& arguments)
+void Engine::init(const std::vector<std::string>& arguments)
 {
-    PROFILE(Engine_Init);
-    
     if (mInitialized)
         return;
     
+    PROFILE(Engine_Init);
+    
     RenderMode mode = RENDER_DEFERRED;
     int width = 0;
     int height = 0;
@@ -132,6 +134,7 @@ void Engine::init(const std::string& windowTitle, const std::vector<std::string>
     bool fullscreen = true;
     bool vsync = false;
     bool forceSM2 = false;
+    bool shadows = true;
     
     int mixRate = 44100;
     int buffer = 100;
@@ -146,7 +149,11 @@ void Engine::init(const std::string& windowTitle, const std::vector<std::string>
         {
             std::string argument = toLower(arguments[i].substr(1));
             
-            if (argument == "nosound")
+            if (argument == "headless")
+                mHeadless = true;
+            else if (argument == "nolimit")
+                setMaxFps(0);
+            else if (argument == "nosound")
                 sound = false;
             else if (argument == "noip")
                 interpolate = false;
@@ -160,6 +167,8 @@ void Engine::init(const std::string& windowTitle, const std::vector<std::string>
                 mode = RENDER_PREPASS;
             else if (argument == "forward")
                 mode = RENDER_FORWARD;
+            else if (argument == "noshadows")
+                shadows = false;
             else if (argument == "sm2")
                 forceSM2 = true;
             else
@@ -209,7 +218,7 @@ void Engine::init(const std::string& windowTitle, const std::vector<std::string>
     
     if (!mHeadless)
     {
-        mRenderer = new Renderer(windowTitle);
+        mRenderer = new Renderer(mWindowTitle);
         mRenderer->setForceSM2(forceSM2);
         mRenderer->setMode(mode, width, height, fullscreen, vsync, multiSample);
         
@@ -219,6 +228,8 @@ void Engine::init(const std::string& windowTitle, const std::vector<std::string>
     }
     else
     {
+        // Open console window for the log
+        openConsoleWindow();
         // Create the audio system also in headless mode so that length of sound effects can be "simulated"
         mAudio = new Audio((int)INVALID_HANDLE_VALUE);
     }
@@ -242,6 +253,8 @@ void Engine::init(const std::string& windowTitle, const std::vector<std::string>
         mDebugRenderer = new DebugRenderer(mRenderer, mCache);
         mPipeline = new Pipeline(mRenderer, mCache);
         mUI = new UI(mRenderer, mCache);
+        if (!shadows)
+            mPipeline->setDrawShadows(false);
     }
     
     mInitialized = true;
@@ -565,10 +578,6 @@ void Engine::update(float timeStep, Scene* scene, Camera* camera, bool updateSce
     
     // Audio update
     mAudio->update(timeStep);
-    
-    // Script garbage collection
-    if (mScriptEngine)
-        mScriptEngine->garbageCollect();
 }
 
 void Engine::render()

+ 5 - 3
Engine/Engine/Engine.h

@@ -62,13 +62,13 @@ class Engine : public RefCounted, public EventListener
     friend Engine* getEngine();
     
 public:
-    //! Construct with log file name and whether to start in headless mode
-    Engine(const std::string& logFileName = "Urho3D.log", bool headless = false);
+    //! Construct with window title, log file name and whether to start in headless mode
+    Engine(const std::string& windowTitle = "Urho3D", const std::string& logFileName = "Urho3D.log", bool headless = false);
     //! Destruct. Free all subsystems
     virtual ~Engine();
     
     //! Initialize and show the application window
-    void init(const std::string& windowTitle, const std::vector<std::string>& arguments = std::vector<std::string>());
+    void init(const std::vector<std::string>& arguments = std::vector<std::string>());
     //! Run one frame with the scene and camera given
     void runFrame(Scene* scene, Camera* camera, bool updateScene = true);
     //! Create a scene
@@ -199,6 +199,8 @@ private:
     SharedPtr<ScriptEngine> mScriptEngine;
     //! UI subsystem
     SharedPtr<UI> mUI;
+    //! Window title
+    std::string mWindowTitle;
     //! Frame update timer
     Timer mFrameTimer;
     //! Minimum frames per second

+ 23 - 0
Engine/Engine/RegisterCommon.cpp

@@ -24,6 +24,7 @@
 #include "Precompiled.h"
 #include "Log.h"
 #include "PackageFile.h"
+#include "ProcessUtils.h"
 #include "RegisterTemplates.h"
 #include "StringUtils.h"
 #include "VectorBuffer.h"
@@ -518,6 +519,12 @@ static bool VariantEqualsBuffer(const VectorBuffer& buffer, Variant* ptr)
     return (*ptr) == buffer.getBuffer();
 }
 
+static CScriptArray* ScanDirectory(const std::string& pathName, const std::string& filter, bool recursive)
+{
+    std::vector<std::string> result = scanDirectory(pathName, filter, recursive);
+    return vectorToArray<std::string>(result, "array<string>");
+}
+
 static void registerSerialization(asIScriptEngine* engine)
 {
     engine->RegisterObjectType("Serializer", 0, asOBJ_REF);
@@ -546,6 +553,8 @@ static void registerSerialization(asIScriptEngine* engine)
     engine->RegisterObjectMethod("File", "uint getChecksum()", asMETHOD(File, getChecksum), asCALL_THISCALL);
     
     engine->RegisterGlobalFunction("bool fileExists(const string& in)", asFUNCTION(fileExists), asCALL_CDECL);
+    engine->RegisterGlobalFunction("void createDirectory(const string& in)", asFUNCTION(createDirectory), asCALL_CDECL);
+    engine->RegisterGlobalFunction("array<string>@ scanDirectory(const string& in, const string& in, bool)", asFUNCTION(ScanDirectory), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getPath(const string& in)", asFUNCTION(getPath), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getFileName(const string& in)", asFUNCTION(getFileName), asCALL_CDECL);
     engine->RegisterGlobalFunction("string getExtension(const string& in, bool)", asFUNCTION(getExtension), asCALL_CDECL);
@@ -611,6 +620,19 @@ static void registerTimer(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Timer", "void reset()", asMETHOD(Timer, reset), asCALL_THISCALL);
 }
 
+static CScriptArray* GetArguments()
+{
+    return vectorToArray<std::string>(getArguments(), "array<string>");
+}
+
+static void registerProcessUtils(asIScriptEngine* engine)
+{
+    engine->RegisterGlobalFunction("array<string>@ getArguments()", asFUNCTION(GetArguments), asCALL_CDECL);
+    engine->RegisterGlobalFunction("void errorDialog(const string& in, const string& in)", asFUNCTION(errorDialog), asCALL_CDECL);
+    engine->RegisterGlobalFunction("string getUserDocumentsDirectory()", asFUNCTION(getUserDocumentsDirectory), asCALL_CDECL);
+    engine->RegisterGlobalFunction("uint getNumLogicalProcessors()", asFUNCTION(getNumLogicalProcessors), asCALL_CDECL);
+}
+
 void registerCommonLibrary(asIScriptEngine* engine)
 {
     registerColor(engine);
@@ -621,4 +643,5 @@ void registerCommonLibrary(asIScriptEngine* engine)
     registerSerialization(engine);
     registerPackageFile(engine);
     registerTimer(engine);
+    registerProcessUtils(engine);
 }

+ 1 - 0
Engine/Engine/RegisterInput.cpp

@@ -194,6 +194,7 @@ static void registerInput(asIScriptEngine* engine)
     engine->RegisterObjectBehaviour("Input", asBEHAVE_ADDREF, "void f()", asMETHOD(Input, addRef), asCALL_THISCALL);
     engine->RegisterObjectBehaviour("Input", asBEHAVE_RELEASE, "void f()", asMETHOD(Input, releaseRef), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "void setToggleFullscreen(bool)", asMETHOD(Input, setToggleFullscreen), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "void suppressNextChar()", asMETHOD(Input, suppressNextChar), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool getKeyDown(int) const", asMETHOD(Input, getKeyDown), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool getKeyPress(int) const", asMETHOD(Input, getKeyPress), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool getMouseButtonDown(int) const", asMETHOD(Input, getMouseButtonDown), asCALL_THISCALL);

+ 3 - 0
Engine/Engine/RegisterMath.cpp

@@ -76,6 +76,9 @@ static void registerMathFunctions(asIScriptEngine* engine)
     engine->RegisterGlobalFunction("float pow(float)", asFUNCTION(powf), asCALL_CDECL);
     engine->RegisterGlobalFunction("float random()", asFUNCTIONPR(random, (), float), asCALL_CDECL);
     engine->RegisterGlobalFunction("float random(float)", asFUNCTIONPR(random, (float), float), asCALL_CDECL);
+    engine->RegisterGlobalFunction("int randomInt()", asFUNCTION(rand), asCALL_CDECL);
+    engine->RegisterGlobalFunction("int randomInt(int)", asFUNCTIONPR(random, (int), int), asCALL_CDECL);
+    engine->RegisterGlobalFunction("void setRandomSeed(int)", asFUNCTION(srand), asCALL_CDECL);
     engine->RegisterGlobalFunction("float min(float, float)", asFUNCTIONPR(min, (float, float), float), asCALL_CDECL);
     engine->RegisterGlobalFunction("float max(float, float)", asFUNCTIONPR(min, (float, float), float), asCALL_CDECL);
     engine->RegisterGlobalFunction("float clamp(float, float, float)", asFUNCTIONPR(clamp, (float, float, float), float), asCALL_CDECL);

+ 19 - 1
Engine/Engine/RegisterRenderer.cpp

@@ -435,6 +435,20 @@ static void registerAnimation(asIScriptEngine* engine)
     registerRefCasts<Resource, Animation>(engine, "Resource", "Animation");
 }
 
+static void registerVolumeNode(asIScriptEngine* engine)
+{
+    registerVolumeNode<VolumeNode>(engine, "VolumeNode");
+    registerRefCasts<Component, VolumeNode>(engine, "Component", "VolumeNode");
+    registerRefCasts<Node, VolumeNode>(engine, "Node", "VolumeNode");
+}
+
+static void registerGeometryNode(asIScriptEngine* engine)
+{
+    registerGeometryNode<GeometryNode>(engine, "GeometryNode");
+    registerRefCasts<Component, GeometryNode>(engine, "Component", "GeometryNode");
+    registerRefCasts<Node, GeometryNode>(engine, "Node", "GeometryNode");
+}
+
 static void ConstructBiasParameters(BiasParameters* ptr)
 {
     new(ptr) BiasParameters(0.0f, 0.0f);
@@ -668,8 +682,10 @@ static void registerAnimatedModel(asIScriptEngine* engine)
     engine->RegisterObjectMethod("AnimatedModel", "void syncMorphs(AnimatedModel@+)", asMETHOD(AnimatedModel, syncMorphs), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "void setLocalAnimation(bool)", asMETHOD(AnimatedModel, setLocalAnimation), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "Skeleton@+ getSkeleton() const", asMETHOD(AnimatedModel, getSkeleton), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimatedModel", "uint getNumAnimationStates() const", asMETHOD(AnimatedModel, getNumAnimationStates), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "AnimationState@+ getAnimationState(Animation@+) const", asMETHODPR(AnimatedModel, getAnimationState, (Animation*) const, AnimationState*), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "AnimationState@+ getAnimationState(const string& in) const", asMETHODPR(AnimatedModel, getAnimationState, (const std::string&) const, AnimationState*), asCALL_THISCALL);
+    engine->RegisterObjectMethod("AnimatedModel", "AnimationState@+ getAnimationState(uint) const", asMETHODPR(AnimatedModel, getAnimationState, (unsigned) const, AnimationState*), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "float getAnimationLodBias() const", asMETHOD(AnimatedModel, getAnimationLodBias), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "uint getNumMorphs() const", asMETHOD(AnimatedModel, getNumMorphs), asCALL_THISCALL);
     engine->RegisterObjectMethod("AnimatedModel", "float getMorphWeight(uint) const", asMETHODPR(AnimatedModel, getMorphWeight, (unsigned) const, float), asCALL_THISCALL);
@@ -697,7 +713,7 @@ static void registerBillboardSet(asIScriptEngine* engine)
     engine->RegisterObjectMethod("BillboardSet", "void setBillboardsSorted(bool)", asMETHOD(BillboardSet, setBillboardsSorted), asCALL_THISCALL);
     engine->RegisterObjectMethod("BillboardSet", "void setScaleBillboards(bool)", asMETHOD(BillboardSet, setScaleBillboards), asCALL_THISCALL);
     engine->RegisterObjectMethod("BillboardSet", "void setAnimationLodBias(float)", asMETHOD(BillboardSet, setAnimationLodBias), asCALL_THISCALL);
-    engine->RegisterObjectMethod("BillboardSet", "void updated(bool)", asMETHOD(BillboardSet, updated), asCALL_THISCALL);
+    engine->RegisterObjectMethod("BillboardSet", "void updated()", asMETHOD(BillboardSet, updated), asCALL_THISCALL);
     engine->RegisterObjectMethod("BillboardSet", "Material@+ getMaterial() const", asMETHOD(BillboardSet, getMaterial), asCALL_THISCALL);
     engine->RegisterObjectMethod("BillboardSet", "uint getNumBillboards() const", asMETHOD(BillboardSet, getNumBillboards), asCALL_THISCALL);
     engine->RegisterObjectMethod("BillboardSet", "Billboard@+ getBillboard(uint)", asMETHOD(BillboardSet, getBillboard), asCALL_THISCALL);
@@ -979,6 +995,8 @@ void registerRendererLibrary(asIScriptEngine* engine)
     registerMaterial(engine);
     registerModel(engine);
     registerAnimation(engine);
+    registerVolumeNode(engine);
+    registerGeometryNode(engine);
     registerLight(engine);
     registerZone(engine);
     registerStaticModel(engine);

+ 9 - 1
Engine/Input/Input.cpp

@@ -37,7 +37,8 @@ Input::Input(Renderer* renderer) :
     mToggleFullscreen(true),
     mActive(false),
     mMinimized(false),
-    mActivated(false)
+    mActivated(false),
+    mSuppressNextChar(false)
 {
     LOGINFO("Input created");
     
@@ -124,6 +125,11 @@ void Input::setToggleFullscreen(bool enable)
     mToggleFullscreen = enable;
 }
 
+void Input::suppressNextChar()
+{
+    mSuppressNextChar = true;
+}
+
 bool Input::getKeyDown(int key) const
 {
     if ((key < 0) || (key >= MAX_KEYS))
@@ -250,6 +256,7 @@ void Input::handleWindowMessage(StringHash eventType, VariantMap& eventData)
         break;
 
     case WM_CHAR:
+        if (!mSuppressNextChar)
         {
             using namespace Char;
             
@@ -257,6 +264,7 @@ void Input::handleWindowMessage(StringHash eventType, VariantMap& eventData)
             keyEventData[P_CHAR] = wParam;
             sendEvent(EVENT_CHAR, keyEventData);
         }
+        mSuppressNextChar = false;
         eventData[P_HANDLED] = true;
         break;
     }

+ 4 - 0
Engine/Input/Input.h

@@ -45,6 +45,8 @@ public:
     void update();
     //! Set whether ALT-ENTER fullscreen toggle is enabled
     void setToggleFullscreen(bool enable);
+    //! Suppress the next char message
+    void suppressNextChar();
     
     //! Check if a key is held down
     bool getKeyDown(int key) const;
@@ -105,6 +107,8 @@ private:
     bool mMinimized;
     //! Activated on this frame flag
     bool mActivated;
+    //! Next char message suppress flag
+    bool mSuppressNextChar;
 };
 
 #endif // INPUT_INPUT_H

+ 7 - 0
Engine/Renderer/AnimatedModel.cpp

@@ -930,6 +930,13 @@ AnimationState* AnimatedModel::getAnimationState(StringHash animationNameHash) c
     return 0;
 }
 
+AnimationState* AnimatedModel::getAnimationState(unsigned index) const
+{
+    if (index >= mAnimationStates.size())
+        return 0;
+    return mAnimationStates[index];
+}
+
 void AnimatedModel::setSkeleton(const Skeleton& skeleton)
 {
     PROFILE(AnimatedModel_SetSkeleton);

+ 4 - 0
Engine/Renderer/AnimatedModel.h

@@ -110,12 +110,16 @@ public:
     const Skeleton& getSkeleton() const { return mSkeleton; }
     //! Return all animation states
     const std::vector<AnimationState*>& getAnimationStates() const { return mAnimationStates; }
+    //! Return number of animation states
+    unsigned getNumAnimationStates() const { return mAnimationStates.size(); }
     //! Return animation state by animation pointer
     AnimationState* getAnimationState(Animation* animation) const;
     //! Return animation state by animation name
     AnimationState* getAnimationState(const std::string& animationName) const;
     //! Return animation state by animation name hash
     AnimationState* getAnimationState(const StringHash animationNameHash) const;
+    //! Return animation state by index
+    AnimationState* getAnimationState(unsigned index) const;
     //! Return animation LOD bias
     float getAnimationLodBias() const { return mAnimationLodBias; }
     //! Return all vertex morphs

+ 1 - 3
Engine/Renderer/BillboardSet.cpp

@@ -357,11 +357,9 @@ void BillboardSet::setAnimationLodBias(float bias)
     mAnimationLodBias = max(bias, 0.0f);
 }
 
-void BillboardSet::updated(bool force)
+void BillboardSet::updated()
 {
     markPositionsDirty();
-    if (force)
-        mForceUpdate = true;
 }
 
 Billboard* BillboardSet::getBillboard(unsigned index)

+ 2 - 2
Engine/Renderer/BillboardSet.h

@@ -93,8 +93,8 @@ public:
     void setScaleBillboards(bool enable);
     //! Set animation LOD bias
     void setAnimationLodBias(float bias);
-    //! Call after changing the billboards. Optionally force update against animation LOD
-    void updated(bool force = false);
+    //! Call after changing the billboards
+    void updated();
     
     //! Return material
     Material* getMaterial() const { return mMaterial; }

+ 6 - 6
Engine/Script/RegisterArray.cpp

@@ -577,7 +577,7 @@ bool CScriptArray::GetFlag()
 // Registers the template array type
 void registerArray(asIScriptEngine* engine)
 {
-    engine->RegisterObjectType("array<class T>", 0, asOBJ_REF | asOBJ_TEMPLATE);
+    engine->RegisterObjectType("array<class T>", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE);
     engine->RegisterObjectBehaviour("array<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL);
     engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int& in)", asFUNCTIONPR(ScriptArrayFactory, (asIObjectType*), CScriptArray*), asCALL_CDECL);
     engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int& in, uint)", asFUNCTIONPR(ScriptArrayFactory2, (asIObjectType*, asUINT), CScriptArray*), asCALL_CDECL);
@@ -594,10 +594,10 @@ void registerArray(asIScriptEngine* engine)
     engine->RegisterObjectMethod("array<T>", "void removeLast()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "void resize(uint)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL);
-    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL);
-    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL);
-    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL);
-    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ENUMREFS, "void f(int& in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL);
-    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASEREFS, "void f(int& in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL);
+    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL);
+    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL);
+    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL);
+    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ENUMREFS, "void f(int& in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL);
+    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASEREFS, "void f(int& in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL);
     engine->RegisterDefaultArrayType("array<T>");
 }

+ 1 - 1
Engine/Script/ScriptEngine.cpp

@@ -128,7 +128,7 @@ void ScriptEngine::garbageCollect()
 {
     PROFILE(Script_GarbageCollect);
     
-    mAngelScriptEngine->GarbageCollect(asGC_ONE_STEP);
+    mAngelScriptEngine->GarbageCollect(asGC_ONE_STEP | asGC_DESTROY_GARBAGE | asGC_DETECT_GARBAGE);
 }
 
 void ScriptEngine::setLogMode(ScriptLogMode mode)

+ 1 - 4
Engine/UI/UIElement.cpp

@@ -34,7 +34,7 @@ UIElement::UIElement(const std::string& name) :
     mParent(0),
     mOrigin(0),
     mClipBorder(IntRect::sZero),
-    mHoverColor(Color(0.0f, 0.0f, 0.0f)),
+    mHoverColor(Color(0.0f, 0.0f, 0.0f, 0.0f)),
     mPriority(0),
     mOpacity(1.0f),
     mBringToFront(false),
@@ -712,9 +712,6 @@ XMLElement UIElement::getStyleElement(XMLFile* file, const std::string& typeName
 
 void UIElement::markDirty()
 {
-    if ((mScreenPositionDirty) && (mDerivedOpacityDirty))
-        return;
-    
     mScreenPositionDirty = true;
     mDerivedOpacityDirty = true;
     

+ 14 - 71
Examples/NinjaSnowWar/Game.cpp

@@ -39,15 +39,12 @@
 #include "GameConfig.h"
 #include "GameEvents.h"
 #include "GameObjectFactory.h"
-#include "Geometry.h"
-#include "IndexBuffer.h"
 #include "Input.h"
 #include "InputEvents.h"
 #include "Light.h"
 #include "Log.h"
 #include "Material.h"
 #include "MemoryBuffer.h"
-#include "Model.h"
 #include "Ninja.h"
 #include "PackageFile.h"
 #include "PositionalChannel.h"
@@ -75,7 +72,6 @@
 #include "Texture2D.h"
 #include "UI.h"
 #include "UIEvents.h"
-#include "VertexBuffer.h"
 #include "XM.h"
 #include "XMLFile.h"
 #include "Zone.h"
@@ -89,8 +85,7 @@ int simulateLatency = 0;
 std::string applicationDir;
 std::string downloadDir;
 
-Game::Game(const std::vector<std::string>& arguments) :
-    mArguments(arguments),
+Game::Game() :
     mTimeScale(1.0f),
     mCameraMinDist(0.0f),
     mCameraMaxDist(0.0f),
@@ -171,11 +166,12 @@ void Game::init()
     std::string logName = "NinjaSnowWar.log";
     
     // Force forward rendering
-    mArguments.insert(mArguments.begin(), "-forward");
+    std::vector<std::string> arguments = getArguments();
+    arguments.insert(arguments.begin(), "-forward");
     
-    for (unsigned i = 0; i < mArguments.size(); ++i)
+    for (unsigned i = 0; i < arguments.size(); ++i)
     {
-        if (toLower(mArguments[i]) == "server")
+        if (toLower(arguments[i]) == "server")
         {
             logName = "Server.log";
             runServer = true;
@@ -183,49 +179,35 @@ void Game::init()
         }
         else
         {
-            if ((mArguments[i][0] != '-') && (!runClient))
+            if ((arguments[i][0] != '-') && (!runClient))
             {
                 logName = "Client.log";
                 runServer = false;
                 runClient = true;
-                address = mArguments[i];
-                if ((mArguments.size() > i + 1) && (mArguments[i + 1][0] != '-'))
-                    mUserName = mArguments[i + 1];
+                address = arguments[i];
+                if ((arguments.size() > i + 1) && (arguments[i + 1][0] != '-'))
+                    mUserName = arguments[i + 1];
             }
         }
     }
     
-    // Run server in headless mode
-    mEngine = new Engine(logName, runServer);
-    if (runServer)
-        openConsoleWindow();
+    // Initialize the engine. If running the server, use headless mode
+    mEngine = new Engine("NinjaSnowWar", logName, runServer);
     
     // Add the resources as a package if available
     mCache = mEngine->getResourceCache();
     if (fileExists("Data.pak"))
         mCache->addPackageFile(new PackageFile("Data.pak"));
+    else
+        mCache->addResourcePath("Data");
     
-    mEngine->init("NinjaSnowWar", mArguments);
+    mEngine->init(arguments);
     
     mCache->addResourcePath(getSystemFontDirectory());
     
-    Pipeline* pipeline = mEngine->getPipeline();
-    if (pipeline)
-    {
-        for (unsigned i = 0; i < mArguments.size(); ++i)
-        {
-            if (toLower(mArguments[i]) == "-noshadows")
-                pipeline->setDrawShadows(false);
-            if (toLower(mArguments[i]) == "-nolimit")
-                mEngine->setMaxFps(0);
-        }
-    }
-    
     DebugHud* debugHud = mEngine->createDebugHud();
     debugHud->setFont(mCache->getResource<Font>("cour.ttf"), 12);
     
-    createSkyPlaneModel();
-    
     if (runServer)
     {
         mServer = mEngine->createServer();
@@ -1181,42 +1163,3 @@ int Game::getObjectCount(ShortStringHash type, int side)
     
     return count;
 }
-
-void Game::createSkyPlaneModel()
-{
-    const float skyplanevertices[] = 
-    {
-        -1, 0, -1, 0, 0,
-        1, 0, -1, 2, 0,
-        1, 0, 1, 2, 2,
-        -1, 0, 1, 0, 2
-    };
-    const unsigned short skyplaneindices[] =
-    {
-        0, 1, 3,
-        1, 2, 3
-    };
-    
-    Renderer* renderer = mEngine->getRenderer();
-    
-    SharedPtr<VertexBuffer> vb(new VertexBuffer(renderer));
-    vb->setSize(4, MASK_POSITION | MASK_TEXCOORD1);
-    vb->setData(skyplanevertices);
-    
-    SharedPtr<IndexBuffer> ib(new IndexBuffer(renderer));
-    ib->setSize(6, false);
-    ib->setData(skyplaneindices);
-    
-    SharedPtr<Geometry> geom(new Geometry());
-    geom->setVertexBuffer(0, vb);
-    geom->setIndexBuffer(ib);
-    geom->setDrawRange(TRIANGLE_LIST, 0, ib->getIndexCount());
-    
-    SharedPtr<Model> model(new Model(renderer, "Models/CloudPlane.mdl"));
-    model->setNumGeometries(1);
-    model->setNumGeometryLodLevels(0, 1);
-    model->setGeometry(0, 0, geom);
-    model->setBoundingBox(BoundingBox(-1, 1));
-    // Add the cloudplane model as a manual resource so it can be retrieved during savegame load
-    mCache->addManualResource(model);
-}

+ 1 - 4
Examples/NinjaSnowWar/Game.h

@@ -69,7 +69,7 @@ static bool compareHiScores(const HiScore& lhs, const HiScore& rhs)
 class Game : public EventListener
 {
 public:
-    Game(const std::vector<std::string>& arguments);
+    Game();
     ~Game();
     
     void run();
@@ -111,9 +111,6 @@ private:
     void updateCamera();
     void updateStatus(float timeStep);
     int getObjectCount(ShortStringHash type, int side = SIDE_UNDEFINED);
-    void createSkyPlaneModel();
-    
-    std::vector<std::string> mArguments;
     
     SharedPtr<Engine> mEngine;
     SharedPtr<ResourceCache> mCache;

+ 8 - 5
Examples/NinjaSnowWar/Main.cpp

@@ -53,22 +53,25 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, in
 
 void run(const char* cmdLine)
 {
-    std::vector<std::string> arguments;
-    getArguments(cmdLine, arguments);
+    parseArguments(cmdLine);
     setExecutableWorkingDirectory();
     
     #if !defined(_MSC_VER) || !defined(_DEBUG)
+    std::string error;
     try
     {
-        Game game(arguments);
+        Game game;
         game.run();
     }
     catch (Exception& e)
     {
-        errorDialog("NinjaSnowWar", e.whatStr());
+        error = e.whatStr();
     }
+    // Let the rendering window close first before showing the error dialog
+    if (!error.empty())
+        errorDialog("NinjaSnowWar", error);
     #else
-    Game game(arguments);
+    Game game;
     game.run();
     #endif
 }

+ 0 - 950
Examples/Test/Application.cpp

@@ -1,950 +0,0 @@
-//
-// Urho3D Engine
-// Copyright (c) 2008-2011 Lasse Öörni
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-
-#include "Precompiled.h"
-#include "AnimatedModel.h"
-#include "Animation.h"
-#include "AnimationState.h"
-#include "Audio.h"
-#include "Application.h"
-#include "BillboardSet.h"
-#include "CollisionShape.h"
-#include "Cursor.h"
-#include "CustomObject.h"
-#include "DebugHud.h"
-#include "DebugRenderer.h"
-#include "Engine.h"
-#include "EngineEvents.h"
-#include "Exception.h"
-#include "File.h"
-#include "Font.h"
-#include "Geometry.h"
-#include "IndexBuffer.h"
-#include "Input.h"
-#include "InstancedModel.h"
-#include "Joint.h"
-#include "Light.h"
-#include "Log.h"
-#include "Material.h"
-#include "Mod.h"
-#include "OcclusionBuffer.h"
-#include "Octree.h"
-#include "OctreeQuery.h"
-#include "PhysicsEvents.h"
-#include "PhysicsWorld.h"
-#include "Pipeline.h"
-#include "ParticleEmitter.h"
-#include "Profiler.h"
-#include "Renderer.h"
-#include "RendererImpl.h"
-#include "ResourceCache.h"
-#include "RigidBody.h"
-#include "Scene.h"
-#include "Skybox.h"
-#include "StaticModel.h"
-#include "Text.h"
-#include "StringUtils.h"
-#include "Texture2D.h"
-#include "UI.h"
-#include "UIEvents.h"
-#include "VertexBuffer.h"
-#include "Window.h"
-#include "XM.h"
-#include "XMLFile.h"
-#include "Zone.h"
-
-#include "DebugNew.h"
-
-void calculateTangents(float* vertexData, unsigned vertexCount, const unsigned short* indexData, unsigned indexCount);
-
-float vertexData[] =
-{
-    // Side 1
-    -1, 1, -1,   0, 0, -1,  0, 0,  0, 0, 0, 0,
-    1, 1, -1,    0, 0, -1,  1, 0,  0, 0, 0, 0,
-    1, -1, -1,   0, 0, -1,  1, 1,  0, 0, 0, 0,
-    -1, -1, -1,  0, 0, -1,  0, 1,  0, 0, 0, 0,
-
-    // Side 2
-    1, 1, -1,    1, 0, 0,   0, 0,  0, 0, 0, 0,
-    1, 1, 1,     1, 0, 0,   1, 0,  0, 0, 0, 0,
-    1, -1, 1,    1, 0, 0,   1, 1,  0, 0, 0, 0,
-    1, -1, -1,   1, 0, 0,   0, 1,  0, 0, 0, 0,
-
-    // Side 3
-    1, 1, 1,     0, 0, 1,   0, 0,  0, 0, 0, 0,
-    -1, 1, 1,    0, 0, 1,   1, 0,  0, 0, 0, 0,
-    -1, -1, 1,   0, 0, 1,   1, 1,  0, 0, 0, 0,
-    1, -1, 1,    0, 0, 1,   0, 1,  0, 0, 0, 0,
-    
-    // Side 4
-    -1, 1, 1,    -1, 0, 0,  0, 0,  0, 0, 0, 0,
-    -1, 1, -1,   -1, 0, 0,  1, 0,  0, 0, 0, 0,
-    -1, -1, -1,  -1, 0, 0,  1, 1,  0, 0, 0, 0,
-    -1, -1, 1,   -1, 0, 0,  0, 1,  0, 0, 0, 0,
-    
-    // Side 5
-    -1, 1, 1,    0, 1, 0,   0, 0,  0, 0, 0, 0,
-    1, 1, 1,     0, 1, 0,   1, 0,  0, 0, 0, 0,
-    1, 1, -1,    0, 1, 0,   1, 1,  0, 0, 0, 0,
-    -1, 1, -1,   0, 1, 0,   0, 1,  0, 0, 0, 0,
-    
-    // Side 6
-    -1, -1, -1,  0, -1, 0,  0, 0,  0, 0, 0, 0,
-    1, -1, -1,   0, -1, 0,  1, 0,  0, 0, 0, 0,
-    1, -1, 1,    0, -1, 0,  1, 1,  0, 0, 0, 0,
-    -1, -1, 1,   0, -1, 0,  0, 1,  0, 0, 0, 0
-};
-
-const unsigned short indexData[] =
-{
-    0, 1, 2,
-    2, 3, 0,
-    4, 5, 6,
-    6, 7, 4,
-    8, 9, 10,
-    10, 11, 8,
-    12, 13, 14,
-    14, 15, 12,
-    16, 17, 18,
-    18, 19, 16,
-    20, 21, 22,
-    22, 23, 20
-};
-
-float objectangle = 0;
-float yaw = 0;
-float pitch = 0;
-bool paused = true;
-int texturequality = 2;
-int materialquality = 2;
-int fallbacklevel = 0;
-int shadowmapsize = 1024;
-bool hiresshadowmap = false;
-int texturefilter = 2;
-bool usespecular = true;
-int drawdebug = 0;
-bool drawshadows = true;
-bool attach = true;
-bool useocclusion = true;
-bool shadowfocus = true;
-
-static const int NUM_OBJECTS = 250;
-static const int NUM_LIGHTS = 20;
-static const int NUM_INSTANCENODES = 20;
-static const int NUM_INSTANCES = 50;
-static const int NUM_BILLBOARDNODES = 20;
-static const int NUM_BILLBOARDS = 15;
-
-Application::Application(const std::vector<std::string>& arguments) :
-    mArguments(arguments)
-{
-    subscribeToEvent(EVENT_UPDATE, EVENT_HANDLER(Application, handleUpdate));
-    subscribeToEvent(EVENT_POSTRENDERUPDATE, EVENT_HANDLER(Application, handlePostRenderUpdate));
-    subscribeToEvent(EVENT_MOUSEMOVE, EVENT_HANDLER(Application, handleMouseMove));
-    subscribeToEvent(EVENT_MOUSEBUTTONDOWN, EVENT_HANDLER(Application, handleMouseButtonDown));
-    subscribeToEvent(EVENT_MOUSEBUTTONUP, EVENT_HANDLER(Application, handleMouseButtonUp));
-}
-
-Application::~Application()
-{
-    if (mEngine)
-    {
-        mEngine->dumpResources();
-        mEngine->dumpProfilingData();
-    }
-}
-
-void Application::run()
-{
-    init();
-    
-    while (!mEngine->isExiting())
-        mEngine->runFrame(mScene, mCameraEntity->getComponent<Camera>());
-}
-
-void Application::init()
-{
-    mEngine = new Engine("Test.log");
-    mEngine->init("Urho3D Test", mArguments);
-    
-    PROFILE(App_Init);
-    
-    mCache = mEngine->getResourceCache();
-    mCache->addResourcePath(getSystemFontDirectory());
-    
-    Renderer* renderer = mEngine->getRenderer();
-    UI* ui = mEngine->getUI();
-    UIElement* uiRoot = ui->getRootElement();
-    
-    XMLFile* uiStyle = mCache->getResource<XMLFile>("UI/DefaultStyle.xml");
-    
-    Cursor* cursor = new Cursor("Cursor");
-    cursor->setStyleAuto(uiStyle, mCache);
-    cursor->setPosition(renderer->getWidth() / 2, renderer->getHeight() / 2);
-    ui->setCursor(cursor);
-    
-    //XMLFile* uiLayout = mCache->getResource<XMLFile>("UI/TestLayout.xml");
-    //SharedPtr<UIElement> layoutElement = ui->loadLayout(uiLayout, uiStyle);
-    //uiRoot->addChild(layoutElement);
-    
-    mScene = mEngine->createScene();
-    PhysicsWorld* world = mScene->getExtension<PhysicsWorld>();
-    world->setGravity(Vector3(0.0f, -9.81f, 0.0f));
-    world->setFps(100);
-    world->setLinearRestThreshold(0.1f);
-    world->setAngularRestThreshold(0.1f);
-    world->setContactSurfaceLayer(0.001f);
-    
-    DebugHud* debugHud = mEngine->createDebugHud();
-    debugHud->setFont(mCache->getResource<Font>("cour.ttf"), 12);
-    debugHud->setMode(DEBUGHUD_SHOW_STATS | DEBUGHUD_SHOW_MODE);
-    
-    mCameraEntity = mScene->createEntity();
-    Camera* camera = mCameraEntity->createComponent<Camera>();
-    camera->setPosition(Vector3(-50.0f, 2.0f, -50.0f));
-    camera->setAspectRatio((float)renderer->getWidth() / (float)renderer->getHeight());
-    
-    calculateTangents(vertexData, 24, indexData, 36);
-    
-    SharedPtr<VertexBuffer> vb(new VertexBuffer(renderer, false));
-    vb->setSize(24, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
-    vb->setData(vertexData);
-    
-    SharedPtr<IndexBuffer> ib(new IndexBuffer(renderer, false));
-    ib->setSize(36, false);
-    ib->setData(indexData);
-    
-    SharedPtr<Geometry> g(new Geometry());
-    g->setVertexBuffer(0, vb);
-    g->setIndexBuffer(ib);
-    g->setDrawRange(TRIANGLE_LIST, 0, ib->getIndexCount());
-    
-    SharedPtr<Model> box(new Model(renderer, "Models/Box.mdl"));
-    box->setNumGeometries(1);
-    box->setNumGeometryLodLevels(0, 1);
-    box->setGeometry(0, 0, g);
-    box->setBoundingBox(BoundingBox(-1.0f, 1.0f));
-    mCache->addManualResource(box);
-    
-    createScene();
-    
-    Entity* sun = mScene->createEntity();
-    Light* sunLight = sun->createComponent<Light>();
-    sunLight->setLightType(LIGHT_DIRECTIONAL);
-    sunLight->setDirection(Vector3(0.5f, -1.0f, 0.5f));
-    sunLight->setColor(Color(0.2f, 0.2f, 0.2f));
-    sunLight->setSpecularIntensity(1.0);
-    //sunLight->setCastShadows(true);
-    //sunLight->setShadowCascade(CascadeParameters(3, 0.95f, 0.2f, 500.0f));
-    
-    Light* cameraLight = mCameraEntity->createComponent<Light>();
-    cameraLight->setLightType(LIGHT_SPOT);
-    cameraLight->setDirection(Vector3(0.0f, 0.0f, 1.0f));
-    cameraLight->setRange(50.0f);
-    cameraLight->setColor(Color(2.0f, 2.0f, 2.0f));
-    cameraLight->setSpecularIntensity(2.0f);
-    cameraLight->setCastShadows(true);
-    cameraLight->setShadowResolution(0.5f);
-    cameraLight->setShadowFocus(FocusParameters(false, false, false, 0.5f, 3.0f));
-    cameraLight->setRampTexture(mCache->getResource<Texture2D>("Textures/RampWide.png"));
-    cameraLight->setSpotTexture(mCache->getResource<Texture2D>("Textures/SpotWide.png"));
-    camera->addChild(cameraLight);
-}
-
-void Application::createScene()
-{
-    PROFILE(App_CreateScene);
-    
-    Octree* octree = mScene->getExtension<Octree>();
-    PhysicsWorld* world = mScene->getExtension<PhysicsWorld>();
-    
-    // Create a zone to control the ambient lighting
-    Zone* zone = new Zone(octree);
-    zone->setBoundingBox(BoundingBox(-1000.0f, 1000.0f));
-    zone->setAmbientColor(Color(0.1f, 0.1f, 0.1f));
-    Entity* newEntity = mScene->createEntity();
-    newEntity->addComponent(zone);
-    
-    // Create the "floor"
-    for (int y = -5; y <= 5; y++)
-    {
-        for (int x = -5; x <= 5; x++)
-        {
-            RigidBody* body = new RigidBody(world);
-            body->setPosition(Vector3(x * 20.5f, -0.5f, y * 20.5f));
-            body->setScale(Vector3(10.0f, 0.5f, 10.0f));
-            body->setCollisionShape(mCache->getResource<CollisionShape>("Physics/Box.xml"));
-            body->setCollisionGroup(2);
-            body->setCollisionMask(1);
-            
-            StaticModel* object = new StaticModel(octree);
-            object->setModel(mCache->getResource<Model>("Models/Box.mdl"));
-            object->setMaterial(mCache->getResource<Material>("Materials/Test.xml"));
-            body->addChild(object);
-            
-            Entity* newEntity = mScene->createEntity();
-            newEntity->addComponent(body);
-            newEntity->addComponent(object);
-        }
-    }
-    
-    // Create 2 occluder walls
-    for (int x = 0; x < 2; x++)
-    {
-        RigidBody* body = new RigidBody(world);
-        body->setPosition(Vector3(0.0f, 5.0f, 0.0f));
-        body->setRotation(Quaternion(x * 90.0f, Vector3::sUp));
-        body->setScale(Vector3(112.0f, 5.0f, 0.5f));
-        body->setCollisionShape(mCache->getResource<CollisionShape>("Physics/Box.xml"));
-        body->setCollisionGroup(2);
-        body->setCollisionMask(1);
-        
-        StaticModel* object = new StaticModel(octree);
-        object->setModel(mCache->getResource<Model>("Models/Box.mdl"));
-        object->setMaterial(mCache->getResource<Material>("Materials/Test.xml"));
-        object->setCastShadows(true);
-        object->setOccluder(true);
-        body->addChild(object);
-        
-        Entity* newEntity = mScene->createEntity();
-        newEntity->addComponent(body);
-        newEntity->addComponent(object);
-    }
-    
-    // Create static mushroom with physics
-    {
-        RigidBody* body = new RigidBody(world);
-        body->setPosition(Vector3(50.0f, 0.0f, 50.0f));
-        body->setScale(10.0f);
-        body->setCollisionShape(mCache->getResource<CollisionShape>("Physics/Mushroom.xml"));
-        body->setCollisionGroup(2);
-        body->setCollisionMask(1);
-        
-        StaticModel* object = new StaticModel(octree);
-        object->setModel(mCache->getResource<Model>("Models/Mushroom.mdl"));
-        object->setMaterial(mCache->getResource<Material>("Materials/Mushroom.xml"));
-        object->setCastShadows(true);
-        object->setOccluder(true);
-        body->addChild(object);
-        
-        Entity* newEntity = mScene->createEntity();
-        newEntity->addComponent(body);
-        newEntity->addComponent(object);
-    }
-    
-    // Create instanced mushrooms
-    for (unsigned j = 0; j < NUM_INSTANCENODES; ++j)
-    {
-        InstancedModel* instanced = new InstancedModel(octree);
-        instanced->setModel(mCache->getResource<Model>("Models/Mushroom.mdl"));
-        instanced->setMaterial(mCache->getResource<Material>("Materials/Mushroom.xml"));
-        instanced->setPosition(Vector3(random() * 160.0f - 80.0f, 0.0f, random() * 160.0f - 80.0f));
-        instanced->setCastShadows(true);
-        instanced->setNumInstances(50);
-        
-        std::vector<Instance>& instances = instanced->getInstances();
-        
-        for (unsigned i = 0; i < NUM_INSTANCES; ++i)
-        {
-            Vector3 position(random() * 20.0f - 10.0f, 0.0f, random() * 20.0f - 10.0f);
-            float angle = random() * 360.0f;
-            float size = 1.0f + random() * 2.0f;
-            
-            instances[i].mPosition = position;
-            instances[i].mRotation = Quaternion(angle, Vector3::sUp);
-            instances[i].mScale = Vector3(size, size, size);
-        }
-        
-        instanced->updated();
-        
-        Entity* newEntity = mScene->createEntity();
-        newEntity->addComponent(instanced);
-    }
-    
-    // Create animated models
-    for (unsigned i = 0; i < NUM_OBJECTS; ++i)
-    {
-        AnimatedModel* object = new AnimatedModel(octree);
-        
-        Vector3 position(random() * 180.0f - 90.0f, 0.0f, random() * 180.0f - 90.0f);
-        float angle = random() * 360.0f;
-        
-        object->setPosition(position);
-        object->setRotation(Quaternion(angle, Vector3::sUp));
-        object->setCastShadows(true);
-        object->setScale(1.0f + random() * 0.25f);
-        object->setModel(mCache->getResource<Model>("Models/Jack.mdl"));
-        object->setMaterial(mCache->getResource<Material>("Materials/Jack.xml"));
-        object->setShadowDistance(200.0f);
-        object->setDrawDistance(300.0f);
-        
-        AnimationState* anim = object->addAnimationState(mCache->getResource<Animation>("Models/Jack_Walk.ani"));
-        if (anim)
-        {
-            anim->setUseNlerp(true);
-            anim->setLooped(true);
-            anim->setWeight(1.0f);
-        }
-        
-        Entity* newEntity = mScene->createEntity();
-        newEntity->addComponent(object);
-        mAnimatingObjects.push_back(newEntity);
-    }
-    
-    // Create floating smoke clouds
-    for (unsigned b = 0; b < NUM_BILLBOARDNODES; ++b)
-    {
-        BillboardSet* billboard = new BillboardSet(octree);
-        billboard->setNumBillboards(NUM_BILLBOARDS);
-        billboard->setPosition(Vector3(random() * 200.0f - 100.0f, random() * 15.0f + 5.0f, random() * 200.0f - 100.0f));
-        billboard->setMaterial(mCache->getResource<Material>("Materials/LitSmoke.xml"));
-        billboard->setBillboardsSorted(true);
-        
-        std::vector<Billboard>& bb = billboard->getBillboards();
-        
-        for (unsigned i = 0; i < NUM_BILLBOARDS; ++i)
-        {
-            bb[i].mPosition = Vector3(random() * 15.0f - 7.5f, random() * 8.0f - 4.0f, random() * 15.0f - 7.5f);
-            bb[i].mSize = Vector2(random() * 2.0f + 3.0f, random() * 2.0f + 3.0f);
-            bb[i].mRotation = random() * 360.0f;
-            bb[i].mEnabled = true;
-        }
-        
-        billboard->updated();
-        
-        Entity* newEntity = mScene->createEntity();
-        newEntity->addComponent(billboard);
-        mBillboards.push_back(newEntity);
-    }
-    
-    // Create lights
-    for (unsigned i = 0; i < NUM_LIGHTS; ++i)
-    {
-        Light* light = new Light(octree);
-        
-        Vector3 position(
-            random() * 150.0f - 75.0f,
-            random() * 30.0f + 30.0f,
-            random() * 150.0f - 75.0f
-        );
-        
-        Color color(
-            (rand() & 1) * 0.5f + 0.5f,
-            (rand() & 1) * 0.5f + 0.5f,
-            (rand() & 1) * 0.5f + 0.5f
-        );
-        
-        if ((color.mR == 0.5f) && (color.mG == 0.5f) && (color.mB == 0.5f))
-            color = Color(1.0f, 1.0f, 1.0f);
-        
-        float angle = random() * 360.0f;
-        
-        light->setPosition(position);
-        light->setDirection(Vector3(sinf(angle * M_DEGTORAD), -1.0f, cosf(angle * M_DEGTORAD)));
-        light->setLightType(LIGHT_SPOT);
-        light->setRange(75.0f);
-        light->setRampTexture(mCache->getResource<Texture2D>("Textures/RampExtreme.png"));
-        light->setFov(15.0f);
-        light->setColor(color);
-        light->setSpecularIntensity(1.0f);
-        light->setCastShadows(true);
-        light->setShadowBias(BiasParameters(0.00002f, 0.0f));
-        light->setShadowResolution(0.5f);
-        // The spot lights will not have anything near them, so move the near plane of the shadow camera farther
-        // for better shadow depth resolution
-        light->setShadowNearFarRatio(0.01f);
-        
-        Entity* newEntity = mScene->createEntity();
-        newEntity->addComponent(light);
-        mLights.push_back(newEntity);
-    }
-}
-
-void Application::handleUpdate(StringHash eventType, VariantMap& eventData)
-{
-    using namespace Update;
-    
-    float timeStep = eventData[P_TIMESTEP].getFloat();
-    
-    if (!paused)
-    {
-        objectangle += 10.0f * timeStep;
-        
-        for (unsigned i = 0; i < mLights.size(); ++i)
-        {
-            Light* light = mLights[i]->getComponent<Light>();
-            light->setRotation(Quaternion(0, objectangle * 2, 0));
-        }
-        
-        for (unsigned i = 0; i < mAnimatingObjects.size(); ++i)
-        {
-            AnimatedModel* model = mAnimatingObjects[i]->getComponent<AnimatedModel>();
-            const std::vector<AnimationState*>& anims = model->getAnimationStates();
-            for (unsigned j = 0; j < anims.size(); ++j)
-                anims[j]->addTime(timeStep);
-        }
-        
-        for (unsigned i = 0; i < mBillboards.size(); ++i)
-        {
-            BillboardSet* billboard = mBillboards[i]->getComponent<BillboardSet>();
-            std::vector<Billboard>& bb = billboard->getBillboards();
-            for (unsigned j = 0; j < bb.size(); ++j)
-                bb[j].mRotation += 50.0f * timeStep;
-            billboard->updated();
-        }
-    }
-    
-    UI* ui = mEngine->getUI();
-    if (!ui->getFocusElement())
-    {
-        Input* input = mEngine->getInput();
-        
-        float speedMultiplier = 1.0f;
-        if (input->getKeyDown(KEY_SHIFT))
-            speedMultiplier = 5.0f;
-        if (input->getKeyDown(KEY_CONTROL))
-            speedMultiplier = 0.1f;
-        
-        if (input->getKeyDown('W'))
-            mCameraEntity->getComponent<Camera>()->translateRelative(Vector3(0.0f,0.0f,10.0f) * timeStep * speedMultiplier);
-        if (input->getKeyDown('S'))
-            mCameraEntity->getComponent<Camera>()->translateRelative(Vector3(0.0f,0.0f,-10.0f) * timeStep * speedMultiplier);
-        if (input->getKeyDown('A'))
-            mCameraEntity->getComponent<Camera>()->translateRelative(Vector3(-10.0f,0.0f,0.0f) * timeStep * speedMultiplier);
-        if (input->getKeyDown('D'))
-            mCameraEntity->getComponent<Camera>()->translateRelative(Vector3(10.0f,0.0f,0.0f) * timeStep * speedMultiplier);
-        
-        if (input->getKeyPress('1'))
-        {
-            Renderer* renderer = mEngine->getRenderer();
-            
-            int nextRenderMode = renderer->getRenderMode();
-            if (input->getKeyDown(KEY_SHIFT))
-            {
-                --nextRenderMode;
-                if (nextRenderMode < 0)
-                    nextRenderMode = 2;
-            }
-            else
-            {
-                ++nextRenderMode;
-                if (nextRenderMode > 2)
-                    nextRenderMode = 0;
-            }
-            
-            renderer->setMode((RenderMode)nextRenderMode, renderer->getWidth(), renderer->getHeight(), renderer->getFullscreen(),
-                renderer->getVsync(), renderer->getMultiSample());
-        }
-        
-        if (input->getKeyPress('2'))
-        {
-            texturequality++;
-            if (texturequality > 2)
-                texturequality = 0;
-            
-            mEngine->getPipeline()->setTextureQuality(texturequality);
-        }
-        
-        if (input->getKeyPress('3'))
-        {
-            materialquality++;
-            if (materialquality > 2)
-                materialquality = 0;
-            mEngine->getPipeline()->setMaterialQuality(materialquality);
-        }
-        
-        if (input->getKeyPress('4'))
-        {
-            usespecular = !usespecular;
-            mEngine->getPipeline()->setSpecularLighting(usespecular);
-        }
-        
-        if (input->getKeyPress('5'))
-        {
-            drawshadows = !drawshadows;
-            mEngine->getPipeline()->setDrawShadows(drawshadows);
-        }
-        
-        if (input->getKeyPress('6'))
-        {
-            shadowmapsize *= 2;
-            if (shadowmapsize > 2048)
-                shadowmapsize = 512;
-            
-            mEngine->getPipeline()->setShadowMapSize(shadowmapsize);
-        }
-        
-        if (input->getKeyPress('7'))
-        {
-            hiresshadowmap = !hiresshadowmap;
-            mEngine->getPipeline()->setShadowMapHiresDepth(hiresshadowmap);
-        }
-        
-        if (input->getKeyPress('8'))
-        {
-            useocclusion = !useocclusion;
-            mEngine->getPipeline()->setMaxOccluderTriangles(useocclusion ? 5000 : 0);
-        }
-        
-        if (input->getKeyPress('L'))
-        {
-            Light* cameraLight = mCameraEntity->getComponent<Light>();
-            attach = !attach;
-            if (attach)
-            {
-                cameraLight->setPosition(Vector3::sZero);
-                cameraLight->setRotation(Quaternion::sIdentity);
-                mCameraEntity->getComponent<Camera>()->addChild(cameraLight);
-            }
-            else
-            {
-                // Detach child and set world transform to match what it was before detach
-                mCameraEntity->getComponent<Camera>()->removeChild(cameraLight, true);
-            }
-        }
-        
-        if (input->getKeyPress(' '))
-        {
-            drawdebug++;
-            if (drawdebug > 2) drawdebug = 0;
-            mEngine->setDebugDrawMode(drawdebug);
-        }
-        
-        if (input->getKeyPress('P'))
-        {
-            paused = !paused;
-        }
-        
-        if (input->getKeyPress('C'))
-        {
-            Camera* camera = mCameraEntity->getComponent<Camera>();
-            camera->setOrthographic(!camera->isOrthographic());
-        }
-        
-        if (input->getKeyPress('O'))
-        {
-            if (!mOcclusionDebugImage)
-            {
-                try
-                {
-                    Renderer* renderer = mEngine->getRenderer();
-                    UIElement* uiRoot = mEngine->getUIRoot();
-                    
-                    mOcclusionDebugTexture = new Texture2D(renderer, TEXTURE_DYNAMIC);
-                    mOcclusionDebugTexture->setNumLevels(1);
-                    mOcclusionDebugTexture->setSize(256, 256, D3DFMT_R32F);
-                    mOcclusionDebugImage = new BorderImage();
-                    mOcclusionDebugImage->setSize(256, 256);
-                    mOcclusionDebugImage->setTexture(mOcclusionDebugTexture);
-                    mOcclusionDebugImage->setAlignment(HA_RIGHT, VA_BOTTOM);
-                    uiRoot->addChild(mOcclusionDebugImage);
-                }
-                catch (...)
-                {
-                }
-            }
-            else
-            {
-                mOcclusionDebugImage->setVisible(!mOcclusionDebugImage->isVisible());
-            }
-        }
-        
-        if (input->getKeyPress('T'))
-            mEngine->getDebugHud()->toggle(DEBUGHUD_SHOW_PROFILER);
-        
-        if (input->getKeyPress('F'))
-        {
-            Pipeline* pipeline = mEngine->getPipeline();
-            EdgeFilterParameters params = pipeline->getEdgeFilter();
-            if (params.mMaxFilter > 0.0f)
-                params.mMaxFilter = 0.0f;
-            else
-                params.mMaxFilter = 1.0f;
-            pipeline->setEdgeFilter(params);
-        }
-        
-        if (input->getKeyPress(KEY_ESCAPE))
-            mEngine->exit();
-    }
-}
-
-void Application::handlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
-{
-    using namespace PostRenderUpdate;
-    
-    float timeStep = eventData[P_TIMESTEP].getFloat();
-    
-    // Test world raycast
-    Camera* camera = mCameraEntity->getComponent<Camera>();
-    UI* ui = mEngine->getUI();
-    Renderer* renderer = mEngine->getRenderer();
-    DebugRenderer* debug = mEngine->getDebugRenderer();
-    
-    if (camera)
-    {
-        IntVector2 pos = ui->getCursorPosition();
-        if (!ui->getElementAt(pos))
-        {
-            Ray cameraRay = camera->getScreenRay(((float)pos.mX) / renderer->getWidth(), ((float)pos.mY) / renderer->getHeight());
-            std::vector<RayQueryResult> result;
-            RayOctreeQuery query(cameraRay, result, NODE_GEOMETRY, NODE_BILLBOARDSET, false, false, 250.0f, RAY_TRIANGLE);
-            
-            mScene->getExtension<Octree>()->getNodes(query);
-            if (result.size())
-            {
-                VolumeNode* node = result[0].mNode;
-                Vector3 rayHitPos = cameraRay.mOrigin + query.mResult[0].mDistance * cameraRay.mDirection;
-                debug->addBoundingBox(BoundingBox(-0.01f, 0.01f), Matrix4x3(rayHitPos, Quaternion::sIdentity, Vector3::sUnity), Color(1.0f, 1.0f, 1.0f));
-                
-                // Check for sub-object results (node-specific)
-                if (result[0].mSubObject < M_MAX_UNSIGNED)
-                {
-                    // Bone
-                    if (node->getNodeFlags() & NODE_ANIMATEDMODEL)
-                    {
-                        AnimatedModel* anim = static_cast<AnimatedModel*>(node);
-                        Bone* bone = anim->getSkeleton().getBone(result[0].mSubObject);
-                        debug->addBoundingBox(bone->getBoundingBox(), bone->getWorldTransform(), Color(1.0f, 1.0f, 1.0f));
-                    }
-                    // Instance
-                    else if (node->getNodeFlags() & NODE_INSTANCEDMODEL)
-                    {
-                        InstancedModel* instanced = static_cast<InstancedModel*>(node);
-                        Instance* instance = instanced->getInstance(result[0].mSubObject);
-                        Matrix4x3 transform(instance->mPosition, instance->mRotation, instance->mScale);
-                        if (instanced->getInstancesRelative())
-                            transform = instanced->getWorldTransform() * transform;
-                        debug->addBoundingBox(instanced->getBoundingBox().getTransformed(transform), Color(1.0f, 1.0f, 1.0f));
-                    }
-                    // Custom object subgeometry
-                    else if (node->getNodeFlags() & NODE_CUSTOMOBJECT)
-                    {
-                        CustomObject* custom = static_cast<CustomObject*>(node);
-                        const CustomGeometry* geom = custom->getGeometry(result[0].mSubObject);
-                        debug->addBoundingBox(geom->mBoundingBox.getTransformed(custom->getWorldTransform()), Color(1.0f, 1.0f, 1.0f));
-                    }
-                }
-                else
-                    debug->addBoundingBox(node->getWorldBoundingBox(), Color(1.0f, 1.0f, 1.0f));
-            }
-        }
-    }
-    
-    // Update occlusion debug texture if visible
-    if ((mOcclusionDebugImage) && (mOcclusionDebugImage->isVisible()))
-    {
-        PROFILE(App_UpdateOcclusionDebugTextures);
-        
-        static const float INV_Z_SCALE = 1.0f / (OCCLUSION_Z_SCALE);
-        
-        // Dump occlusion depth buffer to debug texture
-        // Get an occlusion buffer matching the aspect ratio, it should be the main view occlusion buffer
-        const OcclusionBuffer* buffer = mEngine->getPipeline()->getOcclusionBuffer(camera->getAspectRatio());
-        if (buffer)
-        {
-            int width = buffer->getWidth();
-            int height = buffer->getHeight();
-            if (buffer->getBuffer())
-            {
-                mOcclusionDebugTexture->setSize(width, height, D3DFMT_R32F);
-                mOcclusionDebugImage->setSize(width, height);
-                mOcclusionDebugImage->setFullImageRect();
-                D3DLOCKED_RECT hwRect;
-                mOcclusionDebugTexture->lock(0, 0, &hwRect);
-                for (int y = 0; y < height; ++y)
-                {
-                    float* dest = (float*)(((unsigned char*)hwRect.pBits) + y * hwRect.Pitch);
-                    int* src = buffer->getBuffer() + y * width;
-                    for (int x = 0; x < width; ++x)
-                    {
-                        float depth = src[x] * INV_Z_SCALE;
-                        dest[x] = depth * 0.5f;
-                    }
-                }
-                mOcclusionDebugTexture->unlock();
-            }
-        }
-    }
-}
-
-void Application::handleMouseButtonDown(StringHash eventType, VariantMap& eventData)
-{
-    using namespace MouseButtonDown;
-    
-    int button = eventData[P_BUTTON].getInt();
-    if (button == MOUSEB_RIGHT)
-    {
-        UIElement* cursor = mEngine->getUICursor();
-        if (cursor)
-            cursor->setVisible(false);
-    }
-    
-    UI* ui = mEngine->getUI();
-    
-    if ((button == MOUSEB_LEFT) && (!ui->getElementAt(ui->getCursorPosition())) && (!ui->getFocusElement()))
-    {
-        // Test creating a new physics object
-        if (mCameraEntity)
-        {
-            Camera* camera = mCameraEntity->getComponent<Camera>();
-            Octree* octree = mScene->getExtension<Octree>();
-            PhysicsWorld* world = mScene->getExtension<PhysicsWorld>();
-            
-            Entity* newEntity = mScene->createEntity();
-            
-            RigidBody* body = new RigidBody(world);
-            body->setMode(PHYS_DYNAMIC);
-            body->setPosition(camera->getPosition());
-            body->setRotation(camera->getRotation());
-            body->setScale(0.1f);
-            body->setFriction(1.0f);
-            body->setAngularMaxVelocity(500.0f);
-            body->setCollisionShape(mCache->getResource<CollisionShape>("Physics/Box.xml"));
-            body->setCollisionGroup(1);
-            body->setCollisionMask(3);
-            body->setLinearVelocity(camera->getUpVector() + camera->getForwardVector() * 10.0f);
-            
-            StaticModel* object = new StaticModel(octree);
-            object->setModel(mCache->getResource<Model>("Models/Box.mdl"));
-            object->setMaterial(mCache->getResource<Material>("Materials/Test.xml"));
-            object->setCastShadows(true);
-            object->setShadowDistance(75.0f);
-            object->setDrawDistance(100.0f);
-            body->addChild(object);
-            
-            newEntity->addComponent(body);
-            newEntity->addComponent(object);
-        }
-    }
-}
-
-void Application::handleMouseButtonUp(StringHash eventType, VariantMap& eventData)
-{
-    using namespace MouseButtonDown;
-    
-    if (eventData[P_BUTTON].getInt() == MOUSEB_RIGHT)
-    {
-        UIElement* cursor = mEngine->getUICursor();
-        if (cursor)
-            cursor->setVisible(true);
-    }
-}
-
-void Application::handleMouseMove(StringHash eventType, VariantMap& eventData)
-{
-    using namespace MouseMove;
-    
-    if (eventData[P_BUTTONS].getInt() & MOUSEB_RIGHT)
-    {
-        int mousedx = eventData[P_X].getInt();
-        int mousedy = eventData[P_Y].getInt();
-        yaw += mousedx / 10.0f;
-        pitch += mousedy / 10.0f;
-        if (pitch < -90.0f)
-            pitch = -90.0f;
-        if (pitch > 90.0f)
-            pitch = 90.0f;
-        
-        if (mCameraEntity)
-            mCameraEntity->getComponent<Camera>()->setRotation(Quaternion(yaw, Vector3::sUp) * Quaternion(pitch, Vector3::sRight));
-    }
-}
-
-void calculateTangents(float* vertexData, unsigned vertexCount, const unsigned short* indexData, unsigned indexCount)
-{
-    // Tangent generation from
-    // http://www.terathon.com/code/tangent.html
-    
-    static const int V_OFS = 0;
-    static const int N_OFS = 3;
-    static const int UV_OFS = 6;
-    static const int T_OFS = 8;
-    static const int V_SIZE = 12;
-    
-    Vector3 *tan1 = new Vector3[vertexCount * 2];
-    Vector3 *tan2 = tan1 + vertexCount;
-    memset(tan1, 0, sizeof(Vector3) * vertexCount * 2);
-    
-    for (unsigned a = 0; a < indexCount; a += 3)
-    {
-        unsigned short i1 = indexData[a+0];
-        unsigned short i2 = indexData[a+1];
-        unsigned short i3 = indexData[a+2];
-        
-        const Vector3 v1 = Vector3(&vertexData[i1 * V_SIZE + V_OFS]);
-        const Vector3 v2 = Vector3(&vertexData[i2 * V_SIZE + V_OFS]);
-        const Vector3 v3 = Vector3 (&vertexData[i3 * V_SIZE + V_OFS]);
-        
-        const Vector2 w1 = Vector2(&vertexData[i1 * V_SIZE + UV_OFS]);
-        const Vector2 w2 = Vector2(&vertexData[i2 * V_SIZE + UV_OFS]);
-        const Vector2 w3 = Vector2(&vertexData[i3 * V_SIZE + UV_OFS]);
-        
-        float x1 = v2.mX - v1.mX;
-        float x2 = v3.mX - v1.mX;
-        float y1 = v2.mY - v1.mY;
-        float y2 = v3.mY - v1.mY;
-        float z1 = v2.mZ - v1.mZ;
-        float z2 = v3.mZ - v1.mZ;
-        
-        float s1 = w2.mX - w1.mX;
-        float s2 = w3.mX - w1.mX;
-        float t1 = w2.mY - w1.mY;
-        float t2 = w3.mY - w1.mY;
-        
-        float r = 1.0f / (s1 * t2 - s2 * t1);
-        Vector3 sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
-                (t2 * z1 - t1 * z2) * r);
-        Vector3 tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
-                (s1 * z2 - s2 * z1) * r);
-        
-        tan1[i1] += sdir;
-        tan1[i2] += sdir;
-        tan1[i3] += sdir;
-        
-        tan2[i1] += tdir;
-        tan2[i2] += tdir;
-        tan2[i3] += tdir;
-    }
-    
-    for (unsigned a = 0; a < vertexCount; a++)
-    {
-        const Vector3 n = Vector3(&vertexData[a * V_SIZE + N_OFS]);
-        const Vector3 t = Vector3(tan1[a]);
-        Vector3 xyz;
-        float w;
-        
-        // Gram-Schmidt orthogonalize
-        xyz = (t - n * n.dotProduct(t)).getNormalized();
-        
-        // Calculate handedness
-        w = n.crossProduct(t).dotProduct(tan2[a]) < 0.0f ? -1.0f : 1.0f;
-        
-        vertexData[a * V_SIZE + T_OFS] = xyz.mX;
-        vertexData[a * V_SIZE + T_OFS + 1] = xyz.mY;
-        vertexData[a * V_SIZE + T_OFS + 2] = xyz.mZ;
-        vertexData[a * V_SIZE + T_OFS + 3] = w;
-    }
-    
-    delete[] tan1;
-}

+ 0 - 91
Examples/Test/Application.h

@@ -1,91 +0,0 @@
-//
-// Urho3D Engine
-// Copyright (c) 2008-2011 Lasse Öörni
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-
-#ifndef TEST_APPLICATION_H
-#define TEST_APPLICATION_H
-
-#include "EventListener.h"
-#include "SharedPtr.h"
-
-#include <string>
-#include <vector>
-
-class AnimatedModel;
-class BillboardSet;
-class BorderImage;
-class Camera;
-class Channel;
-class DebugHud;
-class Engine;
-class Entity;
-class Geometry;
-class Light;
-class Material;
-class Model;
-class Node;
-class Octree;
-class ResourceCache;
-class RigidBody;
-class Scene;
-class Song;
-class Sound;
-class Text;
-class Texture2D;
-class TiXmlElement;
-class VolumeNode;
-class XMLFile;
-class Zone;
-
-class Application : public EventListener
-{
-public:
-    Application(const std::vector<std::string>& arguments);
-    ~Application();
-    
-    void run();
-
-private:
-    void init();
-    void createScene();
-    void handleUpdate(StringHash eventType, VariantMap& eventData);
-    void handlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
-    void handleMouseMove(StringHash eventType, VariantMap& eventData);
-    void handleMouseButtonDown(StringHash eventType, VariantMap& eventData);
-    void handleMouseButtonUp(StringHash eventType, VariantMap& eventData);
-    
-    std::vector<std::string> mArguments;
-    
-    SharedPtr<Engine> mEngine;
-    SharedPtr<ResourceCache> mCache;
-    SharedPtr<Scene> mScene;
-    
-    std::vector<Entity*> mAnimatingObjects;
-    std::vector<Entity*> mLights;
-    std::vector<Entity*> mBillboards;
-    Entity* mCameraEntity;
-    
-    SharedPtr<Texture2D> mOcclusionDebugTexture;
-    SharedPtr<BorderImage> mOcclusionDebugImage;
-};
-
-#endif // TEST_APPLICATION_H

+ 0 - 24
Examples/Test/Precompiled.cpp

@@ -1,24 +0,0 @@
-//
-// Urho3D Engine
-// Copyright (c) 2008-2011 Lasse Öörni
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-
-#include "Precompiled.h"

+ 0 - 39
Examples/Test/Readme.txt

@@ -1,39 +0,0 @@
-This is a simple application with a fairly static world and camera control,
-world raycasting to the cursor position, and controls to toggle the rendering
-options during runtime.
-
-
-Controls:
-
-W A S D     - Move
-Left MB     - Shoot physics object
-Right MB    - Camera rotation (hold down)
-1           - Toggle deferred, light prepass & forward rendering
-2           - Cycle texture quality
-3           - Cycle material quality
-4           - Toggle specular lighting
-5           - Toggle shadows
-6           - Cycle shadow map resolution
-7           - Toggle shadow map depth resolution
-8           - Toggle software occlusion
-F           - Toggle edge filtering (deferred rendering only)
-L           - Detach/attach camera spotlight
-O           - Toggle occlusion buffer debug display
-P           - Toggle scene animation
-T           - Toggle profiler
-Space       - Cycle debug geometry mode (none, renderer, physics)
-Alt-Enter   - Toggle fullscreen/windowed
-
-Commandline options:
-
--x<res>     - Horizontal resolution
--y<res>     - Vertical resolution
--m<level>   - Multisampling level
--w          - Start windowed
--v          - Enable vsync
--forward    - Start with forward rendering
--prepass    - Start with light prepass rendering
--sm2        - Force Shader Model 2.0 rendering
-
-Note: when multisampling is specified with deferred rendering, it enables an
-edge filtering postprocess instead of true hardware multisampling.

+ 98 - 0
Examples/Urho3D/Application.cpp

@@ -0,0 +1,98 @@
+//
+// Urho3D Engine
+// Copyright (c) 2008-2011 Lasse Öörni
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Application.h"
+#include "Engine.h"
+#include "Exception.h"
+#include "Font.h"
+#include "Input.h"
+#include "Log.h"
+#include "PackageFile.h"
+#include "ProcessUtils.h"
+#include "ResourceCache.h"
+#include "ScriptEngine.h"
+#include "ScriptFile.h"
+
+#include "DebugNew.h"
+
+Application::Application()
+{
+    std::string userDir = getUserDocumentsDirectory();
+    std::string applicationDir = userDir + "Urho3D";
+    
+    createDirectory(applicationDir);
+}
+
+Application::~Application()
+{
+    // The scripts hold references to engine subsystems, not releasing them shows up as numerous memory leaks
+    mScriptFile.reset();
+    if (mCache)
+        mCache->releaseResources(ShortStringHash("ScriptFile"), true);
+}
+
+void Application::run()
+{
+    init();
+    
+    while (!mEngine->isExiting())
+    {
+        if (!mScriptFile->execute("void runFrame()"))
+            EXCEPTION("Failed to execute the runFrame() function");
+        
+        // Script garbage collection
+        mEngine->getScriptEngine()->garbageCollect();
+    }
+}
+
+void Application::init()
+{
+    const std::vector<std::string>& arguments = getArguments();
+    std::string scriptFileName;
+    if ((arguments.size()) && (arguments[0][0] != '-'))
+        scriptFileName = arguments[0];
+    if (scriptFileName.empty())
+        EXCEPTION("Usage: Urho3D <scriptfile> [options]\n\n"
+            "The script file should implement the functions void start() and void runFrame(). See the readme for options.\n");
+    
+    mEngine = new Engine();
+    
+    // Add resources
+    mCache = mEngine->getResourceCache();
+    if (fileExists("CoreData.pak"))
+        mCache->addPackageFile(new PackageFile("CoreData.pak"));
+    if (fileExists("Data.pak"))
+        mCache->addPackageFile(new PackageFile("Data.pak"));
+    else
+        mCache->addResourcePath("Data");
+    
+    mCache->addResourcePath(getSystemFontDirectory());
+    
+    mEngine->init(arguments);
+    mEngine->createScriptEngine();
+    
+    // Execute the rest of initialization, including scene creation, in script
+    mScriptFile = mCache->getResource<ScriptFile>(scriptFileName);
+    if (!mScriptFile->execute("void start()"))
+        EXCEPTION("Failed to execute the start() function");
+}

+ 25 - 10
Examples/Test/Precompiled.h → Examples/Urho3D/Application.h

@@ -21,15 +21,30 @@
 // THE SOFTWARE.
 //
 
-#ifndef TEST_PRECOMPILED_H
-#define TEST_PRECOMPILED_H
+#ifndef URHO3D_APPLICATION_H
+#define URHO3D_APPLICATION_H
 
-#include <cstdio>
-#include <cstdlib>
-#include <map>
-#include <set>
-#include <stdexcept>
-#include <string>
-#include <vector>
+#include "EventListener.h"
+#include "SharedPtr.h"
 
-#endif // TEST_PRECOMPILED_H
+class Engine;
+class ResourceCache;
+class ScriptFile;
+
+class Application
+{
+public:
+    Application();
+    ~Application();
+    
+    void run();
+    
+private:
+    void init();
+    
+    SharedPtr<Engine> mEngine;
+    SharedPtr<ResourceCache> mCache;
+    SharedPtr<ScriptFile> mScriptFile;
+};
+
+#endif // URHO3D_APPLICATION_H

+ 3 - 4
Examples/Test/CMakeLists.txt → Examples/Urho3D/CMakeLists.txt

@@ -1,5 +1,5 @@
 # Define target name
-set (TARGET_NAME Test)
+set (TARGET_NAME Urho3D)
 
 # Define source files
 file (GLOB CPP_FILES *.cpp)
@@ -10,13 +10,12 @@ set (SOURCE_FILES ${CPP_FILES} ${H_FILES})
 include_directories (
     ../../Engine/Audio ../../Engine/Common ../../Engine/Engine ../../Engine/Event ../../Engine/Input
     ../../Engine/Math ../../Engine/Network ../../Engine/Physics ../../Engine/Renderer ../../Engine/Resource
-    ../../Engine/Scene ../../Engine/UI ../../ThirdParty/STB
+    ../../Engine/Scene ../../Engine/Script ../../Engine/UI ../../ThirdParty/AngelScript/include
 )
 
 # Define target & libraries to link
 add_executable (${TARGET_NAME} WIN32 ${SOURCE_FILES})
 set_target_properties (${TARGET_NAME} PROPERTIES DEBUG_POSTFIX _d)
 target_link_libraries (${TARGET_NAME} Audio Common Engine Event Input Math Network Physics Renderer
-    Resource Scene UI)
-enable_pch ()
+    Resource Scene Script UI)
 finalize_exe ()

+ 12 - 10
Examples/Test/Main.cpp → Examples/Urho3D/Main.cpp

@@ -21,9 +21,8 @@
 // THE SOFTWARE.
 //
 
-#include "Precompiled.h"
-#include "Application.h"
 #include "Exception.h"
+#include "Application.h"
 #include "ProcessUtils.h"
 
 #include <windows.h>
@@ -43,7 +42,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, in
     {
         run(cmdLine);
     }
-    __except(writeMiniDump("Test", GetExceptionInformation())) {}
+    __except(writeMiniDump("Urho3D", GetExceptionInformation())) {}
     #else
     run(cmdLine);
     #endif
@@ -53,22 +52,25 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, in
 
 void run(const char* cmdLine)
 {
-    std::vector<std::string> arguments;
-    getArguments(cmdLine, arguments);
+    parseArguments(cmdLine);
     setExecutableWorkingDirectory();
     
     #if !defined(_MSC_VER) || !defined(_DEBUG)
+    std::string error;
     try
     {
-        Application app(arguments);
-        app.run();
+        Application application;
+        application.run();
     }
     catch (Exception& e)
     {
-        errorDialog("Test", e.whatStr());
+        error = e.whatStr();
     }
+    // Let the rendering window close first before showing the error dialog
+    if (!error.empty())
+        errorDialog("Urho3D", error);
     #else
-    Application app(arguments);
-    app.run();
+    Application application;
+    application.run();
     #endif
 }

+ 24 - 0
Examples/Urho3D/Readme.txt

@@ -0,0 +1,24 @@
+This is a shell for running script-based Urho3D examples. Usage:
+Urho3D <scriptfile> [options]
+
+The script file should implement the functions void start() and void runFrame().
+
+
+Commandline options:
+
+-x<res>     - Horizontal resolution
+-y<res>     - Vertical resolution
+-m<level>   - Multisampling level
+-w          - Start windowed
+-v          - Enable vsync
+-r<mixrate> - Sound mixrate in Hz, default 44100
+-forward    - Start with forward rendering
+-prepass    - Start with light prepass rendering
+-mono       - Force mono sound output
+-8bit       - Force 8-bit sound output
+-noip       - Force non-interpolated sound mixing
+-nosound    - Disable sound
+-nolimit    - Disable FPS limiter
+-noshadows  - Disable shadows
+-headless   - Disable the rendering window
+-sm2        - Force Shader Model 2.0 rendering