Browse Source

Added terrain blend shader and terrain example.
Added function to get terrain normal at point.
Always update terrain patch stitching.
Fixed material texture indexing & setting.

Lasse Öörni 13 years ago
parent
commit
30c21a3789

+ 144 - 0
Bin/CoreData/Shaders/GLSL/TerrainBlend.frag

@@ -0,0 +1,144 @@
+#include "Uniforms.frag"
+#include "Samplers.frag"
+#include "Lighting.frag"
+#include "Fog.frag"
+
+varying vec2 vTexCoord;
+#ifdef PERPIXEL
+    varying vec4 vLightVec;
+    #ifdef SPECULAR
+        varying vec3 vEyeVec;
+    #endif
+    varying vec3 vNormal;
+    #ifdef SHADOW
+        #if defined(DIRLIGHT) && !defined(GL_ES)
+	    varying vec4 vShadowPos[4];
+        #elif defined(DIRLIGHT) && defined(GL_ES)
+	    varying vec4 vShadowPos[2];
+        #elif defined(SPOTLIGHT)
+            varying vec4 vShadowPos;
+        #else
+            varying vec3 vShadowPos;
+        #endif
+    #endif
+    #ifdef SPOTLIGHT
+        varying vec4 vSpotPos;
+    #endif
+    #ifdef POINTLIGHT
+        varying vec3 vCubeMaskVec;
+    #endif
+#else
+    varying vec4 vVertexLight;
+    varying vec3 vNormal;
+    varying vec4 vScreenPos;
+#endif
+
+uniform sampler2D sWeightMap0;
+uniform sampler2D sDetailMap1;
+uniform sampler2D sDetailMap2;
+uniform sampler2D sDetailMap3;
+
+uniform vec2 cDetailTiling;
+
+void main()
+{
+    // Get material diffuse albedo
+    vec3 weights = texture2D(sWeightMap0, vTexCoord).rgb;
+    float sumWeights = weights.r + weights.g + weights.b;
+    weights /= sumWeights;
+    vec2 detailTexCoord = cDetailTiling * vTexCoord;
+    vec4 diffColor = cMatDiffColor * (weights.r * texture2D(sDetailMap1, detailTexCoord) +
+        weights.g * texture2D(sDetailMap2, detailTexCoord) + weights.b * texture2D(sDetailMap3, detailTexCoord));
+
+    // Get material specular albedo
+    vec3 specColor = cMatSpecColor.rgb;
+
+    #if defined(PERPIXEL)
+        // Per-pixel forward lighting
+        vec3 lightColor;
+        vec3 lightDir;
+        vec3 finalColor;
+        float diff;
+
+        vec3 normal = normalize(vNormal);
+
+        #ifdef DIRLIGHT
+            lightDir = vLightVec.xyz;
+            diff = GetDiffuseDir(normal, lightDir);
+        #else
+            diff = GetDiffusePointOrSpot(normal, vLightVec.xyz, lightDir);
+        #endif
+
+        #ifdef SHADOW
+            #if defined(DIRLIGHT)
+                vec4 shadowPos = GetDirShadowPos(vShadowPos, vLightVec.w);
+                diff *= GetDirShadow(shadowPos, vLightVec.w);
+            #elif defined(SPOTLIGHT)
+                diff *= GetShadow(vShadowPos);
+            #else
+                diff *= GetPointShadow(vShadowPos);
+            #endif
+        #endif
+
+        #if defined(SPOTLIGHT)
+            lightColor = vSpotPos.w > 0.0 ? texture2DProj(sLightSpotMap, vSpotPos).rgb * cLightColor.rgb : vec3(0.0, 0.0, 0.0);
+        #elif defined(CUBEMASK)
+            lightColor = textureCube(sLightCubeMap, vCubeMaskVec).rgb * cLightColor.rgb;
+        #else
+            lightColor = cLightColor.rgb;
+        #endif
+
+        #ifdef SPECULAR
+            float spec = GetSpecular(normal, vEyeVec, lightDir, cMatSpecColor.a);
+            finalColor = diff * lightColor * (diffColor.rgb + spec * specColor * cLightColor.a);
+        #else
+            finalColor = diff * lightColor * diffColor.rgb;
+        #endif
+
+        #ifdef AMBIENT
+            finalColor += cAmbientColor * diffColor.rgb;
+            gl_FragColor = vec4(GetFog(finalColor, vLightVec.w), diffColor.a);
+        #else
+            gl_FragColor = vec4(GetLitFog(finalColor, vLightVec.w), diffColor.a);
+        #endif
+    #elif defined(PREPASS)
+        // Fill light pre-pass G-Buffer
+        vec3 normal = vNormal;
+
+        float specPower = cMatSpecColor.a / 255.0;
+
+        #ifdef HWDEPTH
+            gl_FragColor = vec4(normal * 0.5 + 0.5, specPower);
+        #else
+            gl_FragData[0] = vec4(normal * 0.5 + 0.5, specPower);
+            gl_FragData[1] = vec4(EncodeDepth(vVertexLight.a), 0.0);
+        #endif
+    #elif defined(DEFERRED)
+        // Fill deferred G-buffer
+        vec3 normal = vNormal;
+
+        float specIntensity = specColor.g;
+        float specPower = cMatSpecColor.a / 255.0;
+
+        gl_FragData[0] = vec4(GetFog(vVertexLight.rgb * diffColor.rgb, vVertexLight.a), 1.0);
+        gl_FragData[1] = GetFogFactor(vVertexLight.a) * vec4(diffColor.rgb, specIntensity);
+        gl_FragData[2] = vec4(normal * 0.5 + 0.5, specPower);
+        #ifndef HWDEPTH
+            gl_FragData[3] = vec4(EncodeDepth(vVertexLight.a), 0.0);
+        #endif
+    #else
+        // Ambient & per-vertex lighting
+        vec3 finalColor = vVertexLight.rgb * diffColor.rgb;
+
+        #ifdef MATERIAL
+            // Add light pre-pass accumulation result
+            // Lights are accumulated at half intensity. Bring back to full intensity now
+            vec4 lightInput = 2.0 * texture2DProj(sLightBuffer, vScreenPos);
+            vec3 lightSpecColor = lightInput.a * lightInput.rgb / max(GetIntensity(lightInput.rgb), 0.001);
+
+            finalColor += lightInput.rgb * diffColor.rgb + lightSpecColor * specColor;
+        #endif
+
+        gl_FragColor = vec4(GetFog(finalColor, vVertexLight.a), diffColor.a);
+    #endif
+}

+ 92 - 0
Bin/CoreData/Shaders/GLSL/TerrainBlend.vert

@@ -0,0 +1,92 @@
+#include "Uniforms.vert"
+#include "Transform.vert"
+#include "ScreenPos.vert"
+#include "Lighting.vert"
+
+varying vec2 vTexCoord;
+#ifdef PERPIXEL
+    varying vec4 vLightVec;
+    #ifdef SPECULAR
+        varying vec3 vEyeVec;
+    #endif
+    varying vec3 vNormal;
+    #ifdef SHADOW
+        #if defined(DIRLIGHT) && !defined(GL_ES)
+	    varying vec4 vShadowPos[4];
+        #elif defined(DIRLIGHT) && defined(GL_ES)
+	    varying vec4 vShadowPos[2];
+        #elif defined(SPOTLIGHT)
+            varying vec4 vShadowPos;
+        #else
+            varying vec3 vShadowPos;
+        #endif
+    #endif
+    #ifdef SPOTLIGHT
+        varying vec4 vSpotPos;
+    #endif
+    #ifdef POINTLIGHT
+        varying vec3 vCubeMaskVec;
+    #endif
+#else
+    varying vec4 vVertexLight;
+    varying vec3 vNormal;
+    varying vec4 vScreenPos;
+#endif
+
+void main()
+{
+    mat4 modelMatrix = iModelMatrix;
+    vec3 worldPos = GetWorldPos(modelMatrix);
+    gl_Position = GetClipPos(worldPos);
+    vTexCoord = GetTexCoord(iTexCoord);
+    vNormal = GetWorldNormal(modelMatrix);
+
+    #ifdef PERPIXEL
+        // Per-pixel forward lighting
+        vec4 projWorldPos = vec4(worldPos, 1.0);
+
+        #ifdef SHADOW
+            // Shadow projection: transform from world space to shadow space
+            #if defined(DIRLIGHT)
+                vShadowPos[0] = cLightMatrices[0] * projWorldPos;
+                vShadowPos[1] = cLightMatrices[1] * projWorldPos;
+                #ifndef GL_ES
+                    vShadowPos[2] = cLightMatrices[2] * projWorldPos;
+                    vShadowPos[3] = cLightMatrices[3] * projWorldPos;
+                #endif
+            #elif defined(SPOTLIGHT)
+                vShadowPos = cLightMatrices[1] * projWorldPos;
+            #else
+                vShadowPos = worldPos - cLightPos.xyz;
+            #endif
+        #endif
+
+        #ifdef SPOTLIGHT
+            // Spotlight projection: transform from world space to projector texture coordinates
+            vSpotPos = cLightMatrices[0] * projWorldPos;
+        #endif
+
+        #ifdef POINTLIGHT
+            vCubeMaskVec = mat3(cLightMatrices[0][0].xyz, cLightMatrices[0][1].xyz, cLightMatrices[0][2].xyz) * (cLightPos.xyz - worldPos);
+        #endif
+
+        #ifdef DIRLIGHT
+            vLightVec = vec4(cLightDir, GetDepth(gl_Position));
+        #else
+            vLightVec = vec4((cLightPos.xyz - worldPos) * cLightPos.w, GetDepth(gl_Position));
+        #endif
+        #ifdef SPECULAR
+            vEyeVec = cCameraPos - worldPos;
+        #endif
+    #else
+        // Ambient & per-vertex lighting
+        vVertexLight = vec4(GetAmbient(GetZonePos(worldPos)), GetDepth(gl_Position));
+
+        #ifdef NUMVERTEXLIGHTS
+            for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+                vVertexLight.rgb += GetVertexLight(i, worldPos, vNormal) * cVertexLights[i * 3].rgb;
+        #endif
+        
+        vScreenPos = GetScreenPos(gl_Position);
+    #endif
+}

+ 51 - 0
Bin/CoreData/Shaders/GLSL/TerrainBlend.xml

@@ -0,0 +1,51 @@
+<shaders>
+    <shader type="vs">
+        <variation name="" define="AMBIENT" />
+        <variation name="1VL" define="NUMVERTEXLIGHTS=1" />
+        <variation name="2VL" define="NUMVERTEXLIGHTS=2" />
+        <variation name="3VL" define="NUMVERTEXLIGHTS=3" />
+        <variation name="4VL" define="NUMVERTEXLIGHTS=4" />
+        <variation name="Dir">
+            <define name="DIRLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Spot">
+            <define name="SPOTLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Point">
+            <define name="POINTLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <option name="Spec" define="SPECULAR" require="PERPIXEL" />
+        <option name="Shadow" define="SHADOW" require="PERPIXEL" />
+        <variation name="" />
+        <variation name="Skinned" define="SKINNED" />
+        <variation name="Billboard" define="BILLBOARD" />
+    </shader>
+    <shader type="ps">
+        <option name="Ambient" define="AMBIENT" require="PERPIXEL" />
+        <variation name="" define="AMBIENT" />
+        <variation name="Dir">
+            <define name="DIRLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Spot">
+            <define name="SPOTLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Point">
+            <define name="POINTLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Prepass" define="PREPASS" />
+        <variation name="Material" define="MATERIAL" exclude="Normal" />
+        <variation name="Deferred" define="DEFERRED" />
+        <option name="HW" define="HWDEPTH" require="PREPASS" />
+        <option name="HW" define="HWDEPTH" require="DEFERRED" />
+        <option name="Mask" define="CUBEMASK" require="POINTLIGHT" />
+        <option name="Spec" define="SPECULAR" require="PERPIXEL" />
+        <option name="Shadow" define="SHADOW" require="PERPIXEL" />
+        <option name="LQ" define="LQSHADOW" require="SHADOW" />
+    </shader>
+</shaders>

+ 0 - 3
Bin/CoreData/Shaders/HLSL/LitSolid.hlsl

@@ -55,9 +55,6 @@ void VS(float4 iPos : POSITION,
             out float4 oScreenPos : TEXCOORD3,
         #endif
     #endif
-    #ifdef VERTEXCOLOR
-        out float4 oColor : COLOR0,
-    #endif
     out float4 oPos : POSITION)
 {
     float4x3 modelMatrix = iModelMatrix;

+ 255 - 0
Bin/CoreData/Shaders/HLSL/TerrainBlend.hlsl

@@ -0,0 +1,255 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Lighting.hlsl"
+#include "Fog.hlsl"
+
+sampler2D sWeightMap0 : register(S0);
+sampler2D sDetailMap1 : register(S1);
+sampler2D sDetailMap2 : register(S2);
+sampler2D sDetailMap3 : register(S3);
+
+uniform float2 cDetailTiling;
+
+void VS(float4 iPos : POSITION,
+    float3 iNormal : NORMAL,
+    float2 iTexCoord : TEXCOORD0,
+    #ifdef SKINNED
+        float4 iBlendWeights : BLENDWEIGHT,
+        int4 iBlendIndices : BLENDINDICES,
+    #endif
+    #ifdef INSTANCED
+        float4x3 iModelInstance : TEXCOORD2,
+    #endif
+    #ifdef BILLBOARD
+        float2 iSize : TEXCOORD1,
+    #endif
+    out float2 oTexCoord : TEXCOORD0,
+    out float3 oNormal : TEXCOORD2,
+    #ifdef PERPIXEL
+        out float4 oLightVec : TEXCOORD1,
+        #ifdef SPECULAR
+            out float3 oEyeVec : TEXCOORD3,
+        #endif
+        #ifdef SHADOW
+            #if defined(DIRLIGHT)
+                out float4 oShadowPos[4] : TEXCOORD4,
+            #elif defined(SPOTLIGHT)
+                out float4 oShadowPos : TEXCOORD4,
+            #else
+                out float3 oShadowPos : TEXCOORD4,
+            #endif
+        #endif
+        #ifdef SPOTLIGHT
+            out float4 oSpotPos : TEXCOORD5,
+        #endif
+        #ifdef POINTLIGHT
+            out float3 oCubeMaskVec : TEXCOORD5,
+        #endif
+    #else
+        out float4 oVertexLight : TEXCOORD1,
+        out float4 oScreenPos : TEXCOORD3,
+    #endif
+    #ifdef VERTEXCOLOR
+        out float4 oColor : COLOR0,
+    #endif
+    out float4 oPos : POSITION)
+{
+    float4x3 modelMatrix = iModelMatrix;
+    float3 worldPos = GetWorldPos(modelMatrix);
+    oPos = GetClipPos(worldPos);
+    oTexCoord = GetTexCoord(iTexCoord);
+    oNormal = GetWorldNormal(modelMatrix);
+
+    #ifdef PERPIXEL
+        // Per-pixel forward lighting
+        float4 projWorldPos = float4(worldPos, 1.0);
+
+        #ifdef DIRLIGHT
+            oLightVec = float4(cLightDir, GetDepth(oPos));
+        #else
+            oLightVec = float4((cLightPos.xyz - worldPos) * cLightPos.w, GetDepth(oPos));
+        #endif
+
+        #ifdef SHADOW
+            // Shadow projection: transform from world space to shadow space
+            #if defined(DIRLIGHT)
+                oShadowPos[0] = mul(projWorldPos, cLightMatrices[0]);
+                oShadowPos[1] = mul(projWorldPos, cLightMatrices[1]);
+                oShadowPos[2] = mul(projWorldPos, cLightMatrices[2]);
+                oShadowPos[3] = mul(projWorldPos, cLightMatrices[3]);
+            #elif defined(SPOTLIGHT)
+                oShadowPos = mul(projWorldPos, cLightMatrices[1]);
+            #else
+                oShadowPos = worldPos - cLightPos.xyz;
+            #endif
+        #endif
+
+        #ifdef SPOTLIGHT
+            // Spotlight projection: transform from world space to projector texture coordinates
+            oSpotPos = mul(projWorldPos, cLightMatrices[0]);
+        #endif
+
+        #ifdef POINTLIGHT
+            oCubeMaskVec = mul(oLightVec.xyz, (float3x3)cLightMatrices[0]);
+        #endif
+
+        #ifdef SPECULAR
+            oEyeVec = cCameraPos - worldPos;
+        #endif
+    #else
+        // Ambient & per-vertex lighting
+        oVertexLight = float4(GetAmbient(GetZonePos(worldPos)), GetDepth(oPos));
+
+        #ifdef NUMVERTEXLIGHTS
+            for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+                oVertexLight.rgb += GetVertexLight(i, worldPos, oNormal) * cVertexLights[i * 3].rgb;
+        #endif
+
+        oScreenPos = GetScreenPos(oPos);
+    #endif
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+    #ifdef PERPIXEL
+        float4 iLightVec : TEXCOORD1,
+        float3 iNormal : TEXCOORD2,
+        #ifdef SPECULAR
+            float3 iEyeVec : TEXCOORD3,
+        #endif
+        #ifdef SHADOW
+            #if defined(DIRLIGHT)
+                float4 iShadowPos[4] : TEXCOORD4,
+            #elif defined(SPOTLIGHT)
+                float4 iShadowPos : TEXCOORD4,
+            #else
+                float3 iShadowPos : TEXCOORD4,
+            #endif
+        #endif
+        #ifdef SPOTLIGHT
+            float4 iSpotPos : TEXCOORD5,
+        #endif
+        #ifdef CUBEMASK
+            float3 iCubeMaskVec : TEXCOORD5,
+        #endif
+    #else
+        float4 iVertexLight : TEXCOORD1,
+        float3 iNormal : TEXCOORD2,
+        float4 iScreenPos : TEXCOORD3,
+    #endif
+    #if defined(PREPASS) && !defined(HWDEPTH)
+        out float4 oDepth : COLOR1,
+    #endif
+    #ifdef DEFERRED
+        out float4 oAlbedo : COLOR1,
+        out float4 oNormal : COLOR2,
+        #ifndef HWDEPTH
+            out float4 oDepth : COLOR3,
+        #endif
+    #endif
+    out float4 oColor : COLOR0)
+{
+    // Get material diffuse albedo
+    float3 weights = tex2D(sWeightMap0, iTexCoord).rgb;
+    float sumWeights = weights.r + weights.g + weights.b;
+    weights /= sumWeights;
+    float2 detailTexCoord = cDetailTiling * iTexCoord;
+    float4 diffColor = cMatDiffColor * (weights.r * tex2D(sDetailMap1, detailTexCoord) +
+        weights.g * tex2D(sDetailMap2, detailTexCoord) + weights.b * tex2D(sDetailMap3, detailTexCoord));
+
+    // Get material specular albedo
+    float3 specColor = cMatSpecColor.rgb;
+
+    #if defined(PERPIXEL)
+        // Per-pixel forward lighting
+        float3 lightDir;
+        float3 lightColor;
+        float3 finalColor;
+        float diff;
+
+        float3 normal = normalize(iNormal);
+
+        #ifdef DIRLIGHT
+            lightDir = iLightVec.xyz;
+            diff = GetDiffuseDir(normal, lightDir);
+        #else
+            diff = GetDiffusePointOrSpot(normal, iLightVec.xyz, lightDir);
+        #endif
+    
+        #ifdef SHADOW
+            #if defined(DIRLIGHT)
+                float4 shadowPos = GetDirShadowPos(iShadowPos, iLightVec.w);
+                diff *= GetDirShadow(shadowPos, iLightVec.w);
+            #elif defined(SPOTLIGHT)
+                diff *= GetShadow(iShadowPos);
+            #else
+                diff *= GetPointShadow(iShadowPos);
+            #endif
+        #endif
+    
+        #if defined(SPOTLIGHT)
+            lightColor = iSpotPos.w > 0.0 ? tex2Dproj(sLightSpotMap, iSpotPos).rgb * cLightColor.rgb : 0.0;
+        #elif defined(CUBEMASK)
+            lightColor = texCUBE(sLightCubeMap, iCubeMaskVec).rgb * cLightColor.rgb;
+        #else
+            lightColor = cLightColor.rgb;
+        #endif
+    
+        #ifdef SPECULAR
+            float spec = GetSpecular(normal, iEyeVec, lightDir, cMatSpecColor.a);
+            finalColor = diff * lightColor * (diffColor.rgb + spec * specColor * cLightColor.a);
+        #else
+            finalColor = diff * lightColor * diffColor.rgb;
+        #endif
+    
+        #ifdef AMBIENT
+            finalColor += cAmbientColor * diffColor.rgb;
+            oColor = float4(GetFog(finalColor, iLightVec.w), diffColor.a);
+        #else
+            oColor = float4(GetLitFog(finalColor, iLightVec.w), diffColor.a);
+        #endif
+    #elif defined(PREPASS)
+        // Fill light pre-pass G-Buffer
+        float3 normal = iNormal;
+
+        float specPower = cMatSpecColor.a / 255.0;
+
+        oColor = float4(normal * 0.5 + 0.5, specPower);
+        #ifndef HWDEPTH
+            oDepth = iVertexLight.a;
+        #endif
+    #elif defined(DEFERRED)
+        // Fill deferred G-buffer
+        float3 normal = iNormal;
+
+        // If using SM2, light volume shader may not have instructions left to normalize the normal. Therefore do it here
+        #if !defined(SM3)
+            normal = normalize(normal);
+        #endif
+
+        float specIntensity = specColor.g;
+        float specPower = cMatSpecColor.a / 255.0;
+
+        oColor = float4(GetFog(iVertexLight.rgb * diffColor.rgb, iVertexLight.a), 1.0);
+        oAlbedo = GetFogFactor(iVertexLight.a) * float4(diffColor.rgb, specIntensity);
+        oNormal = float4(normal * 0.5 + 0.5, specPower);
+        #ifndef HWDEPTH
+            oDepth = iVertexLight.a;
+        #endif
+    #else
+        // Ambient & per-vertex lighting
+        float3 finalColor = iVertexLight.rgb * diffColor.rgb;
+
+        #ifdef MATERIAL
+            // Add light pre-pass accumulation result
+            // Lights are accumulated at half intensity. Bring back to full intensity now
+            float4 lightInput = 2.0 * tex2Dproj(sLightBuffer, iScreenPos);
+            float3 lightSpecColor = lightInput.a * (lightInput.rgb / GetIntensity(lightInput.rgb));
+
+            finalColor += lightInput.rgb * diffColor.rgb + lightSpecColor * specColor;
+        #endif
+
+        oColor = float4(GetFog(finalColor, iVertexLight.a), diffColor.a);
+    #endif
+}

+ 53 - 0
Bin/CoreData/Shaders/HLSL/TerrainBlend.xml

@@ -0,0 +1,53 @@
+<shaders>
+    <shader type="vs">
+        <variation name="" define="AMBIENT" />
+        <variation name="1VL" define="NUMVERTEXLIGHTS=1" />
+        <variation name="2VL" define="NUMVERTEXLIGHTS=2" />
+        <variation name="3VL" define="NUMVERTEXLIGHTS=3" />
+        <variation name="4VL" define="NUMVERTEXLIGHTS=4" />
+        <variation name="Dir">
+            <define name="DIRLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Spot">
+            <define name="SPOTLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Point">
+            <define name="POINTLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <option name="Spec" define="SPECULAR" require="PERPIXEL" />
+        <option name="Shadow" define="SHADOW" require="PERPIXEL" />
+        <variation name="" />
+        <variation name="Skinned" define="SKINNED" />
+        <variation name="Instanced" define="INSTANCED" require="SM3" />
+        <variation name="Billboard" define="BILLBOARD" />
+    </shader>
+    <shader type="ps">
+        <option name="Ambient" define="AMBIENT" require="PERPIXEL" />
+        <variation name="" define="AMBIENT" />
+        <variation name="Dir">
+            <define name="DIRLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Spot">
+            <define name="SPOTLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Point">
+            <define name="POINTLIGHT" />
+            <define name="PERPIXEL" />
+        </variation>
+        <variation name="Prepass" define="PREPASS" />
+        <variation name="Material" define="MATERIAL" />
+        <variation name="Deferred" define="DEFERRED" />
+        <option name="HW" define="HWDEPTH" require="PREPASS" />
+        <option name="HW" define="HWDEPTH" require="DEFERRED" />
+        <option name="Mask" define="CUBEMASK" require="POINTLIGHT" />
+        <option name="Spec" define="SPECULAR" require="PERPIXEL" />
+        <option name="Shadow" define="SHADOW" require="PERPIXEL" />
+        <option name="LQ" define="LQSHADOW" require="HWSHADOW" />
+        <option name="HW" define="HWSHADOW" require="SHADOW" />
+    </shader>
+</shaders>

+ 9 - 0
Bin/CoreData/Techniques/TerrainBlend.xml

@@ -0,0 +1,9 @@
+<technique>
+    <pass name="base" vs="TerrainBlend" ps="TerrainBlend" />
+    <pass name="litbase" vs="TerrainBlend" ps="TerrainBlend_Ambient" />
+    <pass name="light" vs="TerrainBlend" ps="TerrainBlend" depthtest="equal" depthwrite="false" blend="add" />
+    <pass name="prepass" vs="TerrainBlend" ps="TerrainBlend_Prepass" />
+    <pass name="material" vs="TerrainBlend" ps="TerrainBlend_Material" depthtest="equal" depthwrite="false" />
+    <pass name="deferred" vs="TerrainBlend" ps="TerrainBlend_Deferred" />
+    <pass name="shadow" vs="Shadow" ps="Shadow" />
+</technique>

+ 9 - 0
Bin/Data/Materials/Terrain.xml

@@ -0,0 +1,9 @@
+<material>
+    <technique name="Techniques/TerrainBlend.xml" />
+    <texture unit="0" name="Textures/TerrainWeights.dds" />
+    <texture unit="1" name="Textures/TerrainDetail1.dds" />
+    <texture unit="2" name="Textures/TerrainDetail2.dds" />
+    <texture unit="3" name="Textures/TerrainDetail3.dds" />
+    <parameter name="MatSpecColor" value="0.5 0.5 0.5 16" />
+    <parameter name="DetailTiling" value="32 32" />
+</material>

+ 550 - 0
Bin/Data/Scripts/Terrain.as

@@ -0,0 +1,550 @@
+#include "Scripts/Utilities/Network.as"
+
+Scene@ testScene;
+Camera@ camera;
+Node@ cameraNode;
+PostProcess@ edgeFilter;
+PostProcess@ bloom;
+
+float yaw = 0.0;
+float pitch = 0.0;
+int drawDebug = 0;
+
+Text@ downloadsText;
+
+void Start()
+{
+    if (!engine.headless)
+    {
+        InitConsole();
+        InitUI();
+    }
+    else
+        OpenConsoleWindow();
+
+    ParseNetworkArguments();
+
+    InitScene();
+
+    SubscribeToEvent("Update", "HandleUpdate");
+    SubscribeToEvent("KeyDown", "HandleKeyDown");
+    SubscribeToEvent("MouseMove", "HandleMouseMove");
+    SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");
+    SubscribeToEvent("MouseButtonUp", "HandleMouseButtonUp");
+    SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+    SubscribeToEvent("SpawnBox", "HandleSpawnBox");
+    SubscribeToEvent("PhysicsCollision", "HandlePhysicsCollision");
+
+    network.RegisterRemoteEvent("SpawnBox");
+
+    if (runServer)
+    {
+        network.StartServer(serverPort);
+        SubscribeToEvent("ClientConnected", "HandleClientConnected");
+
+        // Disable physics interpolation to ensure clients get sent physically correct transforms
+        testScene.physicsWorld.interpolation = false;
+
+        // Test package download by adding all package files in the cache as requirements for the scene
+        Array<PackageFile@> packages = cache.packageFiles;
+        for (uint i = 0; i < packages.length; ++i)
+            testScene.AddRequiredPackageFile(packages[i]);
+    }
+    if (runClient)
+    {
+        // Test package download. Remove existing Data.pak from resource cache so that it will be downloaded
+        // However, be sure to add the Data directory so that resource requests do not fail in the meanwhile
+        String packageName = fileSystem.programDir + "Data.pak";
+        cache.RemovePackageFile(packageName, false);
+        cache.AddResourceDir(fileSystem.programDir + "Data");
+
+        network.packageCacheDir = fileSystem.programDir;
+        network.Connect(serverAddress, serverPort, testScene);
+    }
+}
+
+void InitConsole()
+{
+    XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+
+    engine.CreateDebugHud();
+    debugHud.style = uiStyle;
+    debugHud.mode = DEBUGHUD_SHOW_ALL;
+
+    engine.CreateConsole();
+    console.style = uiStyle;
+}
+
+void InitUI()
+{
+    XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+
+    Cursor@ newCursor = Cursor("Cursor");
+    newCursor.style = uiStyle;
+    newCursor.position = IntVector2(graphics.width / 2, graphics.height / 2);
+    ui.cursor = newCursor;
+    if (GetPlatform() == "Android")
+        ui.cursor.visible = false;
+
+    downloadsText = Text();
+    downloadsText.SetAlignment(HA_CENTER, VA_CENTER);
+    downloadsText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 20);
+    ui.root.AddChild(downloadsText);
+}
+
+void InitScene()
+{
+    testScene = Scene("TestScene");
+
+    // Enable access to this script file & scene from the console
+    script.defaultScene = testScene;
+    script.defaultScriptFile = scriptFile;
+
+    // Create the camera outside the scene so it is unaffected by scene load/save
+    cameraNode = Node();
+    camera = cameraNode.CreateComponent("Camera");
+    camera.farClip = 1000;
+    cameraNode.position = Vector3(0, 20, 0);
+
+    if (!engine.headless)
+    {
+        edgeFilter = PostProcess();
+        edgeFilter.parameters = cache.GetResource("XMLFile", "PostProcess/EdgeFilter.xml");
+        edgeFilter.active = false; // Start out disabled
+
+        bloom = PostProcess();
+        bloom.parameters = cache.GetResource("XMLFile", "PostProcess/Bloom.xml");
+        bloom.active = false;
+
+        renderer.viewports[0] = Viewport(testScene, camera);
+        renderer.viewports[0].AddPostProcess(edgeFilter);
+        renderer.viewports[0].AddPostProcess(bloom);
+    }
+
+    if (runClient)
+        return;
+
+    PhysicsWorld@ world = testScene.CreateComponent("PhysicsWorld");
+    testScene.CreateComponent("Octree");
+    testScene.CreateComponent("DebugRenderer");
+
+    Node@ zoneNode = testScene.CreateChild("Zone");
+    Zone@ zone = zoneNode.CreateComponent("Zone");
+    zone.ambientColor = Color(0.1, 0.1, 0.1);
+    zone.fogColor = Color(0.5, 0.5, 0.7);
+    zone.fogStart = 500.0;
+    zone.fogEnd = 1000.0;
+    zone.boundingBox = BoundingBox(-2000, 2000);
+
+    {
+        Node@ lightNode = testScene.CreateChild("GlobalLight");
+        lightNode.direction = Vector3(0.5, -0.5, 0.5);
+
+        Light@ light = lightNode.CreateComponent("Light");
+        light.lightType = LIGHT_DIRECTIONAL;
+        light.castShadows = true;
+        light.shadowBias = BiasParameters(0.0001, 0.5);
+        light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8);
+        light.specularIntensity = 1.0;
+    }
+
+    Terrain@ terrain;
+
+    {
+        Node@ terrainNode = testScene.CreateChild("Terrain");
+        terrainNode.position = Vector3(0, 0, 0);
+        terrain = terrainNode.CreateComponent("Terrain");
+        terrain.patchSize = 64;
+        terrain.spacing = Vector3(2, 0.5, 2);
+        terrain.heightMap = cache.GetResource("Image", "Textures/HeightMap.png");
+        terrain.material = cache.GetResource("Material", "Materials/Terrain.xml");
+
+        RigidBody@ body = terrainNode.CreateComponent("RigidBody");
+        CollisionShape@ shape = terrainNode.CreateComponent("CollisionShape");
+        shape.SetTerrain();
+    }
+
+    for (uint i = 0; i < 1000; ++i)
+    {
+        Node@ objectNode = testScene.CreateChild("Mushroom");
+        Vector3 position(Random() * 2000 - 1000, 0, Random() * 2000 - 1000);
+        position.y = terrain.GetHeight(position) - 0.1;
+
+        objectNode.position = position;
+        objectNode.rotation = Quaternion(Vector3(0, 1, 0), terrain.GetNormal(position));
+        objectNode.SetScale(3);
+
+        StaticModel@ object = objectNode.CreateComponent("StaticModel");
+        object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+        object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+        object.castShadows = true;
+
+        RigidBody@ body = objectNode.CreateComponent("RigidBody");
+        CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
+        shape.SetTriangleMesh(cache.GetResource("Model", "Models/Mushroom.mdl"), 0);
+    }
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    float timeStep = eventData["TimeStep"].GetFloat();
+
+    if (ui.focusElement is null)
+    {
+        float speedMultiplier = 1.0;
+        if (input.keyDown[KEY_LSHIFT])
+            speedMultiplier = 5.0;
+        if (input.keyDown[KEY_LCTRL])
+            speedMultiplier = 0.1;
+
+        if (input.keyDown['W'])
+            cameraNode.TranslateRelative(Vector3(0, 0, 10) * timeStep * speedMultiplier);
+        if (input.keyDown['S'])
+            cameraNode.TranslateRelative(Vector3(0, 0, -10) * timeStep * speedMultiplier);
+        if (input.keyDown['A'])
+            cameraNode.TranslateRelative(Vector3(-10, 0, 0) * timeStep * speedMultiplier);
+        if (input.keyDown['D'])
+            cameraNode.TranslateRelative(Vector3(10, 0, 0) * timeStep * speedMultiplier);
+    }
+
+    // Update package download status
+    if (network.serverConnection !is null)
+    {
+        Connection@ connection = network.serverConnection;
+        if (connection.numDownloads > 0)
+        {
+            downloadsText.text = "Downloads: " + connection.numDownloads + " Current download: " +
+                connection.downloadName + " (" + int(connection.downloadProgress * 100.0) + "%)";
+        }
+        else if (!downloadsText.text.empty)
+            downloadsText.text = "";
+    }
+}
+
+void HandleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+    int key = eventData["Key"].GetInt();
+
+    if (key == KEY_ESC)
+    {
+        if (ui.focusElement is null)
+            engine.Exit();
+        else
+            console.visible = false;
+    }
+
+    if (key == KEY_F1)
+        console.Toggle();
+
+    if (ui.focusElement is null)
+    {
+        if (key == '1')
+            renderer.renderMode = RenderMode((renderer.renderMode + 1) % 3);
+
+        if (key == '2')
+        {
+            int quality = renderer.textureQuality;
+            ++quality;
+            if (quality > 2)
+                quality = 0;
+            renderer.textureQuality = quality;
+        }
+
+        if (key == '3')
+        {
+            int quality = renderer.materialQuality;
+            ++quality;
+            if (quality > 2)
+                quality = 0;
+            renderer.materialQuality = quality;
+        }
+
+        if (key == '4')
+            renderer.specularLighting = !renderer.specularLighting;
+
+        if (key == '5')
+            renderer.drawShadows = !renderer.drawShadows;
+
+        if (key == '6')
+        {
+            int size = renderer.shadowMapSize;
+            size *= 2;
+            if (size > 2048)
+                size = 512;
+            renderer.shadowMapSize = size;
+        }
+
+        if (key == '7')
+            renderer.shadowQuality = renderer.shadowQuality + 1;
+
+        if (key == '8')
+        {
+            bool occlusion = renderer.maxOccluderTriangles > 0;
+            occlusion = !occlusion;
+            renderer.maxOccluderTriangles = occlusion ? 5000 : 0;
+        }
+
+        if (key == '9')
+            renderer.dynamicInstancing = !renderer.dynamicInstancing;
+
+        if (key == ' ')
+        {
+            drawDebug++;
+            if (drawDebug > 1)
+                drawDebug = 0;
+        }
+
+        if (key == 'O')
+            camera.orthographic = !camera.orthographic;
+
+        if (key == 'B')
+            bloom.active = !bloom.active;
+
+        if (key == 'F')
+            edgeFilter.active = !edgeFilter.active;
+
+        if (key == 'T')
+            debugHud.Toggle(DEBUGHUD_SHOW_PROFILER);
+
+        if (key == KEY_F5)
+        {
+            File@ xmlFile = File(fileSystem.programDir + "Data/Scenes/Scene.xml", FILE_WRITE);
+            testScene.SaveXML(xmlFile);
+        }
+
+        if (key == KEY_F7)
+        {
+            File@ xmlFile = File(fileSystem.programDir + "Data/Scenes/Scene.xml", FILE_READ);
+            if (xmlFile.open)
+                testScene.LoadXML(xmlFile);
+        }
+    }
+}
+
+void HandleMouseMove(StringHash eventType, VariantMap& eventData)
+{
+    if (eventData["Buttons"].GetInt() & MOUSEB_RIGHT != 0)
+    {
+        int mousedx = eventData["DX"].GetInt();
+        int mousedy = eventData["DY"].GetInt();
+        yaw += mousedx / 10.0;
+        pitch += mousedy / 10.0;
+        if (pitch < -90.0)
+            pitch = -90.0;
+        if (pitch > 90.0)
+            pitch = 90.0;
+
+        cameraNode.rotation = Quaternion(pitch, yaw, 0);
+    }
+}
+
+void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
+{
+    int button = eventData["Button"].GetInt();
+    if (button == MOUSEB_RIGHT)
+        ui.cursor.visible = false;
+
+    // Test either creating a new physics object or painting a decal (SHIFT down)
+    if (button == MOUSEB_LEFT && ui.GetElementAt(ui.cursorPosition, true) is null && ui.focusElement is null)
+    {
+        if (!input.qualifierDown[QUAL_SHIFT])
+        {
+            VariantMap eventData;
+            eventData["Pos"] = cameraNode.position;
+            eventData["Rot"] = cameraNode.rotation;
+
+            // If we are the client, send the spawn command as a remote event, else send locally
+            if (runClient)
+            {
+                if (network.serverConnection !is null)
+                    network.serverConnection.SendRemoteEvent("SpawnBox", true, eventData);
+            }
+            else
+                SendEvent("SpawnBox", eventData);
+        }
+        else
+        {
+            IntVector2 pos = ui.cursorPosition;
+            if (ui.GetElementAt(pos, true) is null && testScene.octree !is null)
+            {
+                Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
+                RayQueryResult result = testScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, 250.0, DRAWABLE_GEOMETRY);
+                if (result.drawable !is null)
+                {
+                    Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result.distance;
+                    DecalSet@ decal = result.drawable.node.GetComponent("DecalSet");
+                    if (decal is null)
+                    {
+                        decal = result.drawable.node.CreateComponent("DecalSet");
+                        decal.material = cache.GetResource("Material", "Materials/UrhoDecal.xml");
+                        decal.maxVertices = 2048;
+                        decal.maxIndices = 4096;
+                    }
+                    decal.AddDecal(result.drawable, rayHitPos, cameraNode.worldRotation, 0.5, 1.0, 1.0, Vector2(0, 0),
+                        Vector2(1, 1));
+                }
+            }
+        }
+    }
+}
+
+void HandleSpawnBox(StringHash eventType, VariantMap& eventData)
+{
+    Vector3 position = eventData["Pos"].GetVector3();
+    Quaternion rotation = eventData["Rot"].GetQuaternion();
+
+    Node@ newNode = testScene.CreateChild();
+    newNode.position = position;
+    newNode.rotation = rotation;
+    newNode.SetScale(0.2);
+
+    RigidBody@ body = newNode.CreateComponent("RigidBody");
+    body.mass = 1.0;
+    body.friction = 1.0;
+    body.linearVelocity = rotation * Vector3(0.0, 1.0, 10.0);
+    body.ccdRadius = 0.25;
+    body.ccdMotionThreshold = 0.5;
+
+    CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
+    shape.SetBox(Vector3(1, 1, 1));
+
+    StaticModel@ object = newNode.CreateComponent("StaticModel");
+    object.model = cache.GetResource("Model", "Models/Box.mdl");
+    object.material = cache.GetResource("Material", "Materials/StoneSmall.xml");
+    object.castShadows = true;
+    object.shadowDistance = 150.0;
+    object.drawDistance = 200.0;
+}
+
+void HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
+{
+    if (eventData["Button"].GetInt() == MOUSEB_RIGHT)
+        ui.cursor.visible = true;
+}
+
+void HandlePostRenderUpdate()
+{
+    if (engine.headless)
+        return;
+
+    // Draw rendering debug geometry without depth test to see the effect of occlusion
+    if (drawDebug == 1)
+        renderer.DrawDebugGeometry(false);
+
+    IntVector2 pos = ui.cursorPosition;
+    if (ui.GetElementAt(pos, true) is null && testScene.octree !is null)
+    {
+        Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
+        RayQueryResult result = testScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, 250.0, DRAWABLE_GEOMETRY);
+        if (result.drawable !is null)
+        {
+            Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result.distance;
+            testScene.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);
+        }
+    }
+}
+
+void HandleClientConnected(StringHash eventType, VariantMap& eventData)
+{
+    Connection@ connection = eventData["Connection"].GetConnection();
+    connection.scene = testScene; // Begin scene replication to the client
+    connection.logStatistics = true;
+}
+
+void HandlePhysicsCollision(StringHash eventType, VariantMap& eventData)
+{
+    // Check if either of the nodes has an AnimatedModel component
+    Node@ nodeA = eventData["NodeA"].GetNode();
+    Node@ nodeB = eventData["NodeB"].GetNode();
+    if (nodeA.HasComponent("AnimatedModel"))
+    {
+        // Remove the trigger physics shape, and create the ragdoll
+        nodeA.RemoveComponent("RigidBody");
+        nodeA.RemoveComponent("CollisionShape");
+        CreateRagdoll(nodeA.GetComponent("AnimatedModel"));
+    }
+    else if (nodeB.HasComponent("AnimatedModel"))
+    {
+        nodeB.RemoveComponent("RigidBody");
+        nodeB.RemoveComponent("CollisionShape");
+        CreateRagdoll(nodeB.GetComponent("AnimatedModel"));
+    }
+}
+
+void CreateRagdoll(AnimatedModel@ model)
+{
+    Node@ root = model.node;
+
+    CreateRagdollBone(root, "Bip01_Pelvis", SHAPE_CAPSULE, Vector3(0.3, 0.3, 0.3), Vector3(0.0, 0, 0), Quaternion(0, 0, 0));
+    CreateRagdollBone(root, "Bip01_Spine1", SHAPE_CAPSULE, Vector3(0.3, 0.4, 0.3), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_L_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_L_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_Head", SHAPE_SPHERE, Vector3(0.25, 0.25, 0.25), Vector3(0.1, 0, 0), Quaternion(0, 0, 0));
+    CreateRagdollBone(root, "Bip01_L_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_L_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
+
+    CreateRagdollConstraint(root, "Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_L_Calf", "Bip01_L_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_R_Calf", "Bip01_R_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_HINGE, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(90, 0), Vector2(-25, 0));
+    CreateRagdollConstraint(root, "Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_L_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_R_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_L_Forearm", "Bip01_L_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_R_Forearm", "Bip01_R_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
+
+    // Disable animation from all bones (both physical and non-physical) to not interfere
+    Skeleton@ skel = model.skeleton;
+    for (uint i = 0; i < skel.numBones; ++i)
+        skel.bones[i].animated = false;
+}
+
+void CreateRagdollBone(Node@ root, const String&in boneName, ShapeType type, const Vector3&in size, const Vector3&in position,
+    const Quaternion&in rotation)
+{
+    Node@ boneNode = root.GetChild(boneName, true);
+    if (boneNode is null || boneNode.HasComponent("RigidBody"))
+        return;
+
+    // In networked operation both client and server detect collisions separately, and create ragdolls on their own
+    // (bones are not synced over network.) To prevent replicated component ID range clashes when the client creates
+    // any components, it is important that the LOCAL creation mode is specified.
+    RigidBody@ body = boneNode.CreateComponent("RigidBody", LOCAL);
+    body.mass = 1.0;
+    body.linearDamping = 0.05;
+    body.angularDamping = 0.85;
+    body.linearRestThreshold = 1.5;
+    body.angularRestThreshold = 2.5;
+
+    CollisionShape@ shape = boneNode.CreateComponent("CollisionShape", LOCAL);
+    shape.shapeType = type;
+    shape.size = size;
+    shape.position = position;
+    shape.rotation = rotation;
+}
+
+void CreateRagdollConstraint(Node@ root, const String&in boneName, const String&in parentName, ConstraintType type,
+    const Vector3&in axis, const Vector3&in parentAxis, const Vector2&in highLimit, const Vector2&in lowLimit)
+{
+    Node@ boneNode = root.GetChild(boneName, true);
+    Node@ parentNode = root.GetChild(parentName, true);
+    if (boneNode is null || parentNode is null || boneNode.HasComponent("Constraint"))
+        return;
+
+    Constraint@ constraint = boneNode.CreateComponent("Constraint", LOCAL);
+    constraint.constraintType = type;
+    constraint.disableCollision = true;
+    // The connected body must be specified before setting the world position
+    constraint.otherBody = parentNode.GetComponent("RigidBody");
+    constraint.worldPosition = boneNode.worldPosition;
+    constraint.axis = axis;
+    constraint.otherAxis = parentAxis;
+    constraint.highLimit = highLimit;
+    constraint.lowLimit = lowLimit;
+}

BIN
Bin/Data/Textures/HeightMap.png


BIN
Bin/Data/Textures/TerrainDetail1.dds


BIN
Bin/Data/Textures/TerrainDetail2.dds


BIN
Bin/Data/Textures/TerrainDetail3.dds


BIN
Bin/Data/Textures/TerrainWeights.dds


+ 1 - 0
Bin/Terrain.bat

@@ -0,0 +1 @@
+Urho3D.exe Scripts/Terrain.as %1 %2 %3 %4 %5 %6 %7 %8

+ 2 - 0
Docs/GettingStarted.dox

@@ -113,6 +113,8 @@ TestScene also includes a network replication test, where clients can connect, m
 
 There is also a variation of TestScene ported from Urho3D 1.0, TestSceneOld. It lacks networking features, but is provided for examining backward compatibility and performance. It can be run with TestSceneOld.bat or by using the command Urho3D.exe Scripts/TestSceneOld.as
 
+Yet another variation of TestScene demonstrates terrain rendering. To start, run Terrain.bat, or use the command Urho3D.exe Scripts/Terrain.as
+
 \section Running_NinjaSnowWar NinjaSnowWar
 
 A third-person action game. To start, run NinjaSnowWar.bat in the Bin directory, or use the command Urho3D.exe Scripts/NinjaSnowWar.as

+ 1 - 0
Docs/ScriptAPI.dox

@@ -2379,6 +2379,7 @@ Methods:<br>
 - void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
 - float GetHeight(const Vector3&) const
+- Vector3 GetNormal(const Vector3&) const
 - TerrainPatch@ GetPatch(int, int) const
 
 Properties:<br>

+ 1 - 1
Docs/Urho3D.dox

@@ -78,7 +78,7 @@ DXT / ETC1 / PVRTC decompression code based on the Squish library and the Oolong
 
 Jack and mushroom models from the realXtend project. (http://www.realxtend.org)
 
-Ninja model and smoke/flare/status bar textures from OGRE.
+Ninja model and terrain/smoke/flare/status bar textures from OGRE.
 
 BlueHighway font from Larabie Fonts.
 

+ 1 - 0
Engine/Engine/GraphicsAPI.cpp

@@ -778,6 +778,7 @@ static void RegisterTerrain(asIScriptEngine* engine)
     RegisterDrawable<TerrainPatch>(engine, "TerrainPatch");
     RegisterComponent<Terrain>(engine, "Terrain");
     engine->RegisterObjectMethod("Terrain", "float GetHeight(const Vector3&in) const", asMETHOD(Terrain, GetHeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "Vector3 GetNormal(const Vector3&in) const", asMETHOD(Terrain, GetNormal), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "TerrainPatch@+ GetPatch(int, int) const", asMETHODPR(Terrain, GetPatch, (int, int) const, TerrainPatch*), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "void set_material(Material@+)", asMETHOD(Terrain, SetMaterial), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "Material@+ get_material() const", asMETHOD(Terrain, GetMaterial), asCALL_THISCALL);

+ 3 - 1
Engine/Graphics/Batch.cpp

@@ -602,7 +602,9 @@ void Batch::Prepare(Graphics* graphics, Renderer* renderer, bool setModelTransfo
         if (graphics->HasTextureUnit(TU_NORMAL))
             graphics->SetTexture(TU_NORMAL, textures[TU_NORMAL]);
         if (graphics->HasTextureUnit(TU_SPECULAR))
-            graphics->SetTexture(TU_NORMAL, textures[TU_SPECULAR]);
+            graphics->SetTexture(TU_SPECULAR, textures[TU_SPECULAR]);
+        if (graphics->HasTextureUnit(TU_EMISSIVE))
+            graphics->SetTexture(TU_EMISSIVE, textures[TU_EMISSIVE]);
         if (graphics->HasTextureUnit(TU_ENVIRONMENT))
             graphics->SetTexture(TU_ENVIRONMENT, textures[TU_ENVIRONMENT]);
     }

+ 2 - 2
Engine/Graphics/GraphicsDefs.h

@@ -244,9 +244,9 @@ enum TextureUnit
     TU_ALBEDOBUFFER = 0,
     TU_NORMAL = 1,
     TU_NORMALBUFFER = 1,
-    TU_EMISSIVE = 2,
+    TU_SPECULAR = 2,
     TU_DEPTHBUFFER = 2,
-    TU_SPECULAR = 3,
+    TU_EMISSIVE = 3,
     TU_ENVIRONMENT = 4,
     MAX_MATERIAL_TEXTURE_UNITS = 5,
     TU_LIGHTRAMP = 5,

+ 1 - 0
Engine/Graphics/OpenGL/OGLGraphics.cpp

@@ -2279,6 +2279,7 @@ void Graphics::SetTextureUnitMappings()
     textureUnits_["DiffMap"] = TU_DIFFUSE;
     textureUnits_["DiffCubeMap"] = TU_DIFFUSE;
     textureUnits_["NormalMap"] = TU_NORMAL;
+    textureUnits_["SpecMap"] = TU_SPECULAR;
     textureUnits_["EmissiveMap"] = TU_EMISSIVE;
     textureUnits_["EnvironmentMap"] = TU_ENVIRONMENT;
     textureUnits_["EnvironmentCubeMap"] = TU_ENVIRONMENT;

+ 35 - 2
Engine/Graphics/Terrain.cpp

@@ -371,6 +371,39 @@ float Terrain::GetHeight(const Vector3& worldPosition) const
         return 0.0f;
 }
 
+Vector3 Terrain::GetNormal(const Vector3& worldPosition) const
+{
+    if (node_)
+    {
+        Vector3 position = node_->GetWorldTransform().Inverse() * worldPosition;
+        float xPos = (position.x_ - patchWorldOrigin_.x_) / spacing_.x_;
+        float zPos = (position.z_ - patchWorldOrigin_.y_) / spacing_.z_;
+        float xFrac = xPos - floorf(xPos);
+        float zFrac = zPos - floorf(zPos);
+        Vector3 n1, n2, n3;
+        
+        if (xFrac + zFrac >= 1.0f)
+        {
+            n1 = GetRawNormal((unsigned)xPos + 1, (unsigned)zPos + 1);
+            n2 = GetRawNormal((unsigned)xPos, (unsigned)zPos + 1);
+            n3 = GetRawNormal((unsigned)xPos + 1, (unsigned)zPos);
+            xFrac = 1.0f - xFrac;
+            zFrac = 1.0f - zFrac;
+        }
+        else
+        {
+            n1 = GetRawNormal((unsigned)xPos, (unsigned)zPos);
+            n2 = GetRawNormal((unsigned)xPos + 1, (unsigned)zPos);
+            n3 = GetRawNormal((unsigned)xPos, (unsigned)zPos + 1);
+        }
+        
+        Vector3 n = (n1 * (1.0f - xFrac - zFrac) + n2 * xFrac + n3 * zFrac).Normalized();
+        return node_->GetWorldRotation() * n;
+    }
+    else
+        return Vector3::UP;
+}
+
 void Terrain::CreatePatchGeometry(TerrainPatch* patch)
 {
     PROFILE(CreatePatchGeometry);
@@ -412,7 +445,7 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
                 box.Merge(position);
                 
                 // Normal
-                Vector3 normal = GetNormal(xPos, zPos);
+                Vector3 normal = GetRawNormal(xPos, zPos);
                 *vertexData++ = normal.x_;
                 *vertexData++ = normal.y_;
                 *vertexData++ = normal.z_;
@@ -842,7 +875,7 @@ float Terrain::GetLodHeight(int x, int z, unsigned lodLevel) const
     return h1 * (1.0f - xFrac - zFrac) + h2 * xFrac + h3 * zFrac;
 }
 
-Vector3 Terrain::GetNormal(int x, int z) const
+Vector3 Terrain::GetRawNormal(int x, int z) const
 {
     float baseHeight = GetRawHeight(x, z);
     float nSlope = GetRawHeight(x, z - 1) - baseHeight;

+ 4 - 2
Engine/Graphics/Terrain.h

@@ -99,6 +99,8 @@ public:
     TerrainPatch* GetPatch(int x, int z) const;
     /// Return height at world coordinates.
     float GetHeight(const Vector3& worldPosition) const;
+    /// Return normal at world coordinates.
+    Vector3 GetNormal(const Vector3& worldPosition) const;
     /// Return raw height data.
     SharedArrayPtr<float> GetHeightData() const { return heightData_; }
     /// Return draw distance.
@@ -150,8 +152,8 @@ private:
     float GetRawHeight(int x, int z) const;
     /// Return interpolated height for a specific LOD level.
     float GetLodHeight(int x, int z, unsigned lodLevel) const;
-    /// Get terrain normal at position.
-    Vector3 GetNormal(int x, int z) const;
+    /// Get slope-based terrain normal at position.
+    Vector3 GetRawNormal(int x, int z) const;
     /// Calculate LOD errors for a patch.
     void CalculateLodErrors(TerrainPatch* patch);
     /// Set neighbors for a patch.

+ 3 - 50
Engine/Graphics/TerrainPatch.cpp

@@ -48,8 +48,7 @@ TerrainPatch::TerrainPatch(Context* context) :
     maxLodGeometry_(new Geometry(context)),
     vertexBuffer_(new VertexBuffer(context)),
     coordinates_(IntVector2::ZERO),
-    lodLevel_(0),
-    lodDirty_(false)
+    lodLevel_(0)
 {
     drawableFlags_ = DRAWABLE_GEOMETRY;
     
@@ -144,26 +143,7 @@ void TerrainPatch::UpdateBatches(const FrameInfo& frame)
             newLodLevel = i;
     }
     
-    unsigned correctedLodLevel = GetCorrectedLodLevel(newLodLevel);
-    
-    if (correctedLodLevel != lodLevel_)
-    {
-        lodLevel_ = correctedLodLevel;
-        lodDirty_ = true;
-        
-        // If correction took place, recursively check also neighbor patches for correct LOD for stitching
-        if (newLodLevel != correctedLodLevel)
-        {
-            if (north_)
-                north_->CheckLodConstraints();
-            if (south_)
-                south_->CheckLodConstraints();
-            if (west_)
-                west_->CheckLodConstraints();
-            if (east_)
-                east_->CheckLodConstraints();
-        }
-    }
+    lodLevel_ = GetCorrectedLodLevel(newLodLevel);
 }
 
 void TerrainPatch::UpdateGeometry(const FrameInfo& frame)
@@ -178,20 +158,14 @@ void TerrainPatch::UpdateGeometry(const FrameInfo& frame)
     
     if (owner_)
         owner_->UpdatePatchLod(this);
-    
-    lodDirty_ = false;
 }
 
 UpdateGeometryType TerrainPatch::GetUpdateGeometryType()
 {
-    // If any of the neighbor patches have changed LOD, must also update own LOD because of stitching
     if (vertexBuffer_->IsDataLost())
         return UPDATE_MAIN_THREAD;
-    else if (lodDirty_ || (north_ && north_->lodDirty_) || (south_ && south_->lodDirty_) || (west_ && west_->lodDirty_) ||
-        (east_ && east_->lodDirty_))
+    else 
         return UPDATE_WORKER_THREAD;
-    else
-        return UPDATE_NONE;
 }
 
 Geometry* TerrainPatch::GetLodGeometry(unsigned batchIndex, unsigned level)
@@ -278,7 +252,6 @@ void TerrainPatch::SetCoordinates(const IntVector2& coordinates)
 void TerrainPatch::ResetLod()
 {
     lodLevel_ = 0;
-    lodDirty_ = false;
 }
 
 Geometry* TerrainPatch::GetGeometry() const
@@ -319,23 +292,3 @@ unsigned TerrainPatch::GetCorrectedLodLevel(unsigned lodLevel)
     
     return lodLevel;
 }
-
-void TerrainPatch::CheckLodConstraints()
-{
-    unsigned correctedLodLevel = GetCorrectedLodLevel(lodLevel_);
-    
-    if (correctedLodLevel != lodLevel_)
-    {
-        lodLevel_ = correctedLodLevel;
-        lodDirty_ = true;
-        
-        if (north_)
-            north_->CheckLodConstraints();
-        if (south_)
-            south_->CheckLodConstraints();
-        if (west_)
-            west_->CheckLodConstraints();
-        if (east_)
-            east_->CheckLodConstraints();
-    }
-}

+ 0 - 4
Engine/Graphics/TerrainPatch.h

@@ -102,8 +102,6 @@ protected:
 private:
     /// Return a corrected LOD level to ensure stitching can work correctly.
     unsigned GetCorrectedLodLevel(unsigned lodLevel);
-    /// Recursively ensure own and neighbor patch LOD level to correctly support pstitching.
-    void CheckLodConstraints();
     
     /// Geometry.
     SharedPtr<Geometry> geometry_;
@@ -129,6 +127,4 @@ private:
     IntVector2 coordinates_;
     /// Current LOD level.
     unsigned lodLevel_;
-    /// LOD level dirty flag.
-    bool lodDirty_;
 };

+ 1 - 1
Readme.txt

@@ -54,7 +54,7 @@ DXT / ETC1 / PVRTC decompression code based on the Squish library and the Oolong
 
 Jack and mushroom models from the realXtend project. (http://www.realxtend.org)
 
-Ninja model and smoke/flare/status bar textures from OGRE.
+Ninja model and terrain/smoke/flare/status bar textures from OGRE.
 
 BlueHighway font from Larabie Fonts.