From 2adac241d0c647ae1caae1409ac1f455baccf456 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 12 Apr 2018 15:11:10 +0200 Subject: [PATCH 01/16] Fixed incorrect fragment to eye dir computation in haze --- .../render-utils/src/DeferredGlobalLight.slh | 6 ++-- .../render-utils/src/ForwardGlobalLight.slh | 2 +- libraries/render-utils/src/Haze.slf | 2 +- libraries/render-utils/src/Haze.slh | 33 ++++++++++--------- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index 51884ccbee..2ffdad46af 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -240,8 +240,8 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( vec4(color, 1.0), // fragment original color position, // fragment position in eye coordinates fragEyeVector, // fragment position in world coordinates - invViewMat[3].y, // eye height in world coordinates - lightDirection // keylight direction vector + invViewMat[3].xyz, // eye position in world coordinates + lightDirection // keylight direction vector in world coordinates ); color = colorV4.rgb; @@ -278,7 +278,7 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( vec4(color, 1.0), // fragment original color position, // fragment position in eye coordinates surface.eyeDir, // fragment eye vector in world coordinates - invViewMat[3].y, // eye height in world coordinates + invViewMat[3].xyz, // eye position in world coordinates lightDirection // keylight direction vector ); diff --git a/libraries/render-utils/src/ForwardGlobalLight.slh b/libraries/render-utils/src/ForwardGlobalLight.slh index a945acb8c4..2c794bb31c 100644 --- a/libraries/render-utils/src/ForwardGlobalLight.slh +++ b/libraries/render-utils/src/ForwardGlobalLight.slh @@ -234,7 +234,7 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( vec4(color, 1.0), // fragment original color position, // fragment position in eye coordinates fragEyeVector, // fragment position in world coordinates - invViewMat[3].y, // eye height in world coordinates + invViewMat[3].xyz, // eye position in world coordinates lightDirection // keylight direction vector ); diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index e254266350..d63de64b07 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -56,5 +56,5 @@ void main(void) { Light light = getKeyLight(); vec3 lightDirection = getLightDirection(light); - outFragColor = computeHazeColor(fragColor, eyeFragPos.xyz, worldFragPos.xyz, worldEyePos.y, lightDirection); + outFragColor = computeHazeColor(fragColor, eyeFragPos.xyz, worldFragPos.xyz, worldEyePos.xyz, lightDirection); } diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index 10b493da2c..25a80953b5 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -44,15 +44,15 @@ layout(std140) uniform hazeBuffer { // Input: // color - fragment original color // lightDirection - parameters of the keylight -// worldFragPos - fragment position in world coordinates +// fragWorldPos - fragment position in world coordinates // Output: // fragment colour after haze effect // // General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission // -vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 worldFragPos) { +vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 fragWorldPos) { // Directional light attenuation is simulated by assuming the light source is at a fixed height above the - // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height + // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height // // The distance is computed from the height and the directional light orientation // The distance is limited to height * 1,000, which gives an angle of ~0.057 degrees @@ -79,7 +79,7 @@ vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 w // Note that the haze base reference affects only the haze density as function of altitude float hazeDensityDistribution = hazeParams.hazeKeyLightRangeFactor * - exp(-hazeParams.hazeKeyLightAltitudeFactor * (worldFragPos.y - hazeParams.hazeBaseReference)); + exp(-hazeParams.hazeKeyLightAltitudeFactor * (fragWorldPos.y - hazeParams.hazeBaseReference)); float hazeIntegral = hazeDensityDistribution * distance; @@ -93,25 +93,26 @@ vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 w // Input: // fragColor - fragment original color -// eyeFragPos - fragment position in eye coordinates -// worldFragPos - fragment position in world coordinates -// worldEyeHeight - eye height in world coordinates +// fragEyePos - fragment position in eye coordinates +// fragWorldPos - fragment position in world coordinates +// eyeWorldPos - eye position in world coordinates // Output: // fragment colour after haze effect // // General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission // -vec4 computeHazeColor(vec4 fragColor, vec3 eyeFragPos, vec3 worldFragPos, float worldEyeHeight, vec3 lightDirection) { +vec4 computeHazeColor(vec4 fragColor, vec3 fragEyePos, vec3 fragWorldPos, vec3 eyeWorldPos, vec3 lightDirection) { // Distance to fragment - float distance = length(eyeFragPos); + float distance = length(fragEyePos); + float eyeWorldHeight = eyeWorldPos.y; // Convert haze colour from uniform into a vec4 vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); // Directional light component is a function of the angle from the eye, between the fragment and the sun - vec3 eyeFragDir = normalize(worldFragPos); + vec3 fragToEyeWorldDir = normalize(fragWorldPos-eyeWorldPos); - float glareComponent = max(0.0, dot(eyeFragDir, -lightDirection)); + float glareComponent = max(0.0, dot(fragToEyeWorldDir, -lightDirection)); float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); @@ -134,12 +135,12 @@ vec4 computeHazeColor(vec4 fragColor, vec3 eyeFragPos, vec3 worldFragPos, float // Note that the haze base reference affects only the haze density as function of altitude vec3 hazeDensityDistribution = hazeParams.colorModulationFactor * - exp(-hazeParams.hazeHeightFactor * (worldEyeHeight - hazeParams.hazeBaseReference)); + exp(-hazeParams.hazeHeightFactor * (eyeWorldHeight - hazeParams.hazeBaseReference)); - vec3 hazeIntegral = hazeDensityDistribution * distance; + vec3 hazeIntegral = hazeDensityDistribution * distance; const float slopeThreshold = 0.01; - float deltaHeight = worldFragPos.y - worldEyeHeight; + float deltaHeight = fragWorldPos.y - eyeWorldHeight; if (abs(deltaHeight) > slopeThreshold) { float t = hazeParams.hazeHeightFactor * deltaHeight; hazeIntegral *= (1.0 - exp (-t)) / t; @@ -162,12 +163,12 @@ vec4 computeHazeColor(vec4 fragColor, vec3 eyeFragPos, vec3 worldFragPos, float // Note that the haze base reference affects only the haze density as function of altitude float hazeDensityDistribution = hazeParams.hazeRangeFactor * - exp(-hazeParams.hazeHeightFactor * (worldEyeHeight - hazeParams.hazeBaseReference)); + exp(-hazeParams.hazeHeightFactor * (eyeWorldHeight - hazeParams.hazeBaseReference)); float hazeIntegral = hazeDensityDistribution * distance; const float slopeThreshold = 0.01; - float deltaHeight = worldFragPos.y - worldEyeHeight; + float deltaHeight = fragWorldPos.y - eyeWorldHeight; if (abs(deltaHeight) > slopeThreshold) { float t = hazeParams.hazeHeightFactor * deltaHeight; // Protect from wild values From 190e9313c73019ea20d93f6566500966b46e0645 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 13 Apr 2018 15:38:55 +0200 Subject: [PATCH 02/16] Sorted out the mix between eye space and world space vectors in translucent object shading --- .../render-utils/src/DeferredGlobalLight.slh | 58 ++++++++++--------- .../render-utils/src/ForwardGlobalLight.slh | 49 ++++++++-------- libraries/render-utils/src/Haze.slf | 10 ++-- libraries/render-utils/src/Haze.slh | 32 +++++----- .../render-utils/src/MaterialTextures.slh | 4 +- .../src/forward_model_normal_map.slf | 10 ++-- .../render-utils/src/model_normal_map.slf | 12 ++-- .../render-utils/src/model_normal_map.slv | 12 ++-- .../render-utils/src/model_translucent.slf | 26 +++++---- .../render-utils/src/model_translucent.slv | 12 ++-- .../src/model_translucent_fade.slf | 30 +++++----- .../src/model_translucent_normal_map.slf | 31 +++++----- .../src/model_translucent_normal_map.slv | 16 ++--- .../src/model_translucent_normal_map_fade.slf | 34 ++++++----- 14 files changed, 174 insertions(+), 162 deletions(-) diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index 2ffdad46af..c3ab17f72b 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -32,12 +32,13 @@ vec3 color = vec3(0.0); <@endfunc@> -<@func prepareGlobalLight(isScattering)@> +<@func prepareGlobalLight(positionES, normalWS)@> // prepareGlobalLight // Transform directions to worldspace - vec3 fragNormal = vec3((normal)); - vec3 fragEyeVector = vec3(invViewMat * vec4(-position, 0.0)); - vec3 fragEyeDir = normalize(fragEyeVector); + vec3 fragNormalWS = vec3(<$normalWS$>); + vec3 fragPositionWS = vec3(invViewMat * vec4(<$positionES$>, 1.0)); + vec3 fragEyeVectorWS = invViewMat[3].xyz - fragPositionWS; + vec3 fragEyeDirWS = normalize(fragEyeVectorWS); <$fetchGlobalLight()$> @@ -46,7 +47,7 @@ <@func declareEvalAmbientGlobalColor()@> vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, float roughness) { - <$prepareGlobalLight()$> + <$prepareGlobalLight(position, normal)$> color += albedo * getLightColor(light) * obscurance * getLightAmbientIntensity(lightAmbient); return color; } @@ -67,14 +68,14 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature <@endif@> ) { - <$prepareGlobalLight($supportScattering$)$> + <$prepareGlobalLight(position, normal)$> - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragEyeDirWS); // Ambient vec3 ambientDiffuse; vec3 ambientSpecular; - evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance <@if supportScattering@> ,scattering, midNormalCurvature, lowNormalCurvature <@endif@> ); @@ -85,7 +86,7 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness // Directional vec3 directionalDiffuse; vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation <@if supportScattering@> ,scattering, midNormalCurvature, lowNormalCurvature <@endif@> ); @@ -114,14 +115,14 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature <@endif@> ) { - <$prepareGlobalLight($supportScattering$)$> + <$prepareGlobalLight(position, normal)$> - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragEyeDirWS); // Ambient vec3 ambientDiffuse; vec3 ambientSpecular; - evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance <@if supportScattering@> ,scattering, midNormalCurvature, lowNormalCurvature <@endif@> @@ -131,7 +132,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu vec3 directionalDiffuse; vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation <@if supportScattering@> ,scattering, midNormalCurvature, lowNormalCurvature <@endif@> @@ -141,7 +142,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu // Attenuate the light if haze effect selected if ((hazeParams.hazeMode & HAZE_MODE_IS_KEYLIGHT_ATTENUATED) == HAZE_MODE_IS_KEYLIGHT_ATTENUATED) { - color = computeHazeColorKeyLightAttenuation(color, lightDirection, position); + color = computeHazeColorKeyLightAttenuation(color, lightDirection, fragPositionWS); } return color; @@ -180,9 +181,9 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur <$declareLightingDirectional()$> vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity, vec3 prevLighting) { - <$prepareGlobalLight()$> + <$prepareGlobalLight(position, normal)$> - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragEyeDirWS); color = prevLighting; color += emissive * isEmissiveEnabled(); @@ -190,14 +191,14 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl // Ambient vec3 ambientDiffuse; vec3 ambientSpecular; - evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance); + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance); color += ambientDiffuse; color += ambientSpecular / opacity; // Directional vec3 directionalDiffuse; vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation); + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; color += directionalSpecular / opacity; @@ -211,26 +212,27 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl <$declareLightingDirectional()$> vec3 evalGlobalLightingAlphaBlendedWithHaze( - mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, + mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 positionES, vec3 normalWS, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) { - <$prepareGlobalLight()$> - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + <$prepareGlobalLight(positionES, normalWS)$> + + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragEyeDirWS); color += emissive * isEmissiveEnabled(); // Ambient vec3 ambientDiffuse; vec3 ambientSpecular; - evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance); + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance); color += ambientDiffuse; color += ambientSpecular / opacity; // Directional vec3 directionalDiffuse; vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation); + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; color += directionalSpecular / opacity; @@ -238,8 +240,8 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { vec4 colorV4 = computeHazeColor( vec4(color, 1.0), // fragment original color - position, // fragment position in eye coordinates - fragEyeVector, // fragment position in world coordinates + positionES, // fragment position in eye coordinates + fragPositionWS, // fragment position in world coordinates invViewMat[3].xyz, // eye position in world coordinates lightDirection // keylight direction vector in world coordinates ); @@ -251,7 +253,7 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( } vec3 evalGlobalLightingAlphaBlendedWithHaze( - mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, + mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 positionES, vec3 positionWS, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, SurfaceData surface, float opacity, vec3 prevLighting) { <$fetchGlobalLight()$> @@ -276,8 +278,8 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { vec4 colorV4 = computeHazeColor( vec4(color, 1.0), // fragment original color - position, // fragment position in eye coordinates - surface.eyeDir, // fragment eye vector in world coordinates + positionES, // fragment position in eye coordinates + positionWS, // fragment position in world coordinates invViewMat[3].xyz, // eye position in world coordinates lightDirection // keylight direction vector ); diff --git a/libraries/render-utils/src/ForwardGlobalLight.slh b/libraries/render-utils/src/ForwardGlobalLight.slh index 2c794bb31c..3aadb182fa 100644 --- a/libraries/render-utils/src/ForwardGlobalLight.slh +++ b/libraries/render-utils/src/ForwardGlobalLight.slh @@ -21,12 +21,13 @@ <@include LightDirectional.slh@> -<@func prepareGlobalLight(isScattering)@> +<@func prepareGlobalLight(positionES, normalWS)@> // prepareGlobalLight // Transform directions to worldspace - vec3 fragNormal = vec3((normal)); - vec3 fragEyeVector = vec3(invViewMat * vec4(-1.0*position, 0.0)); - vec3 fragEyeDir = normalize(fragEyeVector); + vec3 fragNormalWS = vec3(<$normalWS$>); + vec3 fragPositionWS = vec3(invViewMat * vec4(<$positionES$>, 1.0)); + vec3 fragEyeVectorWS = invViewMat[3].xyz - fragPositionWS; + vec3 fragEyeDirWS = normalize(fragEyeVectorWS); // Get light Light light = getKeyLight(); @@ -42,7 +43,7 @@ <@func declareEvalAmbientGlobalColor()@> vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, float roughness) { - <$prepareGlobalLight()$> + <$prepareGlobalLight(position, normal)$> color += albedo * getLightColor(light) * obscurance * getLightAmbientIntensity(lightAmbient); return color; } @@ -63,14 +64,14 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature <@endif@> ) { - <$prepareGlobalLight($supportScattering$)$> + <$prepareGlobalLight(position, normal)$> - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragEyeDirWS); // Ambient vec3 ambientDiffuse; vec3 ambientSpecular; - evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance <@if supportScattering@> ,scattering, midNormalCurvature, lowNormalCurvature <@endif@> ); @@ -81,7 +82,7 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness // Directional vec3 directionalDiffuse; vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation <@if supportScattering@> ,scattering, midNormalCurvature, lowNormalCurvature <@endif@> ); @@ -109,14 +110,14 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu , float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature <@endif@> ) { - <$prepareGlobalLight($supportScattering$)$> + <$prepareGlobalLight(position, normal)$> - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragEyeDirWS); // Ambient vec3 ambientDiffuse; vec3 ambientSpecular; - evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance <@if supportScattering@> ,scattering, midNormalCurvature, lowNormalCurvature <@endif@> @@ -126,7 +127,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu vec3 directionalDiffuse; vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation <@if supportScattering@> ,scattering, midNormalCurvature, lowNormalCurvature <@endif@> @@ -137,7 +138,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu // FIXME - temporarily removed until we support it for forward... // Attenuate the light if haze effect selected // if ((hazeParams.hazeMode & HAZE_MODE_IS_KEYLIGHT_ATTENUATED) == HAZE_MODE_IS_KEYLIGHT_ATTENUATED) { - // color = computeHazeColorKeyLightAttenuation(color, lightDirection, position); + // color = computeHazeColorKeyLightAttenuation(color, lightDirection, fragPositionWS); // } return color; @@ -180,23 +181,23 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur <$declareLightingDirectional()$> vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) { - <$prepareGlobalLight()$> + <$prepareGlobalLight(position, normal)$> - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragEyeDirWS); color += emissive * isEmissiveEnabled(); // Ambient vec3 ambientDiffuse; vec3 ambientSpecular; - evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance); + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance); color += ambientDiffuse; color += ambientSpecular / opacity; // Directional vec3 directionalDiffuse; vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation); + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; color += directionalSpecular / opacity; @@ -207,23 +208,23 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) { - <$prepareGlobalLight()$> + <$prepareGlobalLight(position, normal)$> - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragEyeDirWS); color += emissive * isEmissiveEnabled(); // Ambient vec3 ambientDiffuse; vec3 ambientSpecular; - evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surface, metallic, fresnel, albedo, obscurance); + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance); color += ambientDiffuse; color += ambientSpecular / opacity; // Directional vec3 directionalDiffuse; vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation); + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; color += directionalSpecular / opacity; @@ -232,8 +233,8 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( /* if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { vec4 colorV4 = computeHazeColor( vec4(color, 1.0), // fragment original color - position, // fragment position in eye coordinates - fragEyeVector, // fragment position in world coordinates + positionES, // fragment position in eye coordinates + fragPositionWS, // fragment position in world coordinates invViewMat[3].xyz, // eye position in world coordinates lightDirection // keylight direction vector ); diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index d63de64b07..b9f23fd932 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -47,14 +47,14 @@ void main(void) { } vec4 fragColor = texture(colorMap, varTexCoord0); - vec4 eyeFragPos = unpackPositionFromZeye(varTexCoord0); + vec4 fragPositionES = unpackPositionFromZeye(varTexCoord0); mat4 viewInverse = getViewInverse(); - vec4 worldFragPos = viewInverse * eyeFragPos; - vec4 worldEyePos = viewInverse[3]; + vec4 fragPositionWS = viewInverse * fragPositionES; + vec4 eyePositionWS = viewInverse[3]; Light light = getKeyLight(); - vec3 lightDirection = getLightDirection(light); + vec3 lightDirectionWS = getLightDirection(light); - outFragColor = computeHazeColor(fragColor, eyeFragPos.xyz, worldFragPos.xyz, worldEyePos.xyz, lightDirection); + outFragColor = computeHazeColor(fragColor, fragPositionES.xyz, fragPositionWS.xyz, eyePositionWS.xyz, lightDirectionWS); } diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index 25a80953b5..a9c8ff829e 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -43,14 +43,14 @@ layout(std140) uniform hazeBuffer { // Input: // color - fragment original color -// lightDirection - parameters of the keylight -// fragWorldPos - fragment position in world coordinates +// lightDirectionWS - parameters of the keylight +// fragPositionWS - fragment position in world coordinates // Output: // fragment colour after haze effect // // General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission // -vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 fragWorldPos) { +vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirectionWS, vec3 fragPositionWS) { // Directional light attenuation is simulated by assuming the light source is at a fixed height above the // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height // @@ -65,7 +65,7 @@ vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 f } // Note that we need the sine to be positive - float sin_pitch = abs(lightDirection.y); + float sin_pitch = abs(lightDirectionWS.y); float distance; const float minimumSinPitch = 0.001; @@ -79,7 +79,7 @@ vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 f // Note that the haze base reference affects only the haze density as function of altitude float hazeDensityDistribution = hazeParams.hazeKeyLightRangeFactor * - exp(-hazeParams.hazeKeyLightAltitudeFactor * (fragWorldPos.y - hazeParams.hazeBaseReference)); + exp(-hazeParams.hazeKeyLightAltitudeFactor * (fragPositionWS.y - hazeParams.hazeBaseReference)); float hazeIntegral = hazeDensityDistribution * distance; @@ -92,27 +92,27 @@ vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 f } // Input: -// fragColor - fragment original color -// fragEyePos - fragment position in eye coordinates -// fragWorldPos - fragment position in world coordinates -// eyeWorldPos - eye position in world coordinates +// fragColor - fragment original color +// fragPositionES - fragment position in eye coordinates +// fragPositionWS - fragment position in world coordinates +// eyePositionWS - eye position in world coordinates // Output: // fragment colour after haze effect // // General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission // -vec4 computeHazeColor(vec4 fragColor, vec3 fragEyePos, vec3 fragWorldPos, vec3 eyeWorldPos, vec3 lightDirection) { +vec4 computeHazeColor(vec4 fragColor, vec3 fragPositionES, vec3 fragPositionWS, vec3 eyePositionWS, vec3 lightDirectionWS) { // Distance to fragment - float distance = length(fragEyePos); - float eyeWorldHeight = eyeWorldPos.y; + float distance = length(fragPositionES); + float eyeWorldHeight = eyePositionWS.y; // Convert haze colour from uniform into a vec4 vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); // Directional light component is a function of the angle from the eye, between the fragment and the sun - vec3 fragToEyeWorldDir = normalize(fragWorldPos-eyeWorldPos); + vec3 fragToEyeDirWS = normalize(fragPositionWS - eyePositionWS); - float glareComponent = max(0.0, dot(fragToEyeWorldDir, -lightDirection)); + float glareComponent = max(0.0, dot(fragToEyeDirWS, -lightDirectionWS)); float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); @@ -140,7 +140,7 @@ vec4 computeHazeColor(vec4 fragColor, vec3 fragEyePos, vec3 fragWorldPos, vec3 e vec3 hazeIntegral = hazeDensityDistribution * distance; const float slopeThreshold = 0.01; - float deltaHeight = fragWorldPos.y - eyeWorldHeight; + float deltaHeight = fragPositionWS.y - eyeWorldHeight; if (abs(deltaHeight) > slopeThreshold) { float t = hazeParams.hazeHeightFactor * deltaHeight; hazeIntegral *= (1.0 - exp (-t)) / t; @@ -168,7 +168,7 @@ vec4 computeHazeColor(vec4 fragColor, vec3 fragEyePos, vec3 fragWorldPos, vec3 e float hazeIntegral = hazeDensityDistribution * distance; const float slopeThreshold = 0.01; - float deltaHeight = fragWorldPos.y - eyeWorldHeight; + float deltaHeight = fragPositionWS.y - eyeWorldHeight; if (abs(deltaHeight) > slopeThreshold) { float t = hazeParams.hazeHeightFactor * deltaHeight; // Protect from wild values diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh index 26e809e4ba..14e8b0ef07 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -228,14 +228,14 @@ vec3 fetchLightmapMap(vec2 uv) { } <@endfunc@> -<@func evalMaterialNormalLOD(fragPos, fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@> +<@func evalMaterialNormalLOD(fragPosES, fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@> { vec3 normalizedNormal = normalize(<$interpolatedNormal$>.xyz); vec3 normalizedTangent = normalize(<$interpolatedTangent$>.xyz); vec3 normalizedBitangent = cross(normalizedNormal, normalizedTangent); // attenuate the normal map divergence from the mesh normal based on distance // The attenuation range [20,100] meters from the eye is arbitrary for now - vec3 localNormal = mix(<$fetchedNormal$>, vec3(0.0, 1.0, 0.0), smoothstep(20.0, 100.0, (-<$fragPos$>).z)); + vec3 localNormal = mix(<$fetchedNormal$>, vec3(0.0, 1.0, 0.0), smoothstep(20.0, 100.0, (-<$fragPosES$>).z)); <$normal$> = vec3(normalizedBitangent * localNormal.x + normalizedNormal * localNormal.y + normalizedTangent * localNormal.z); } <@endfunc@> diff --git a/libraries/render-utils/src/forward_model_normal_map.slf b/libraries/render-utils/src/forward_model_normal_map.slf index 0ba464d3f0..ac76e909e4 100644 --- a/libraries/render-utils/src/forward_model_normal_map.slf +++ b/libraries/render-utils/src/forward_model_normal_map.slf @@ -23,11 +23,11 @@ <@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION)$> -in vec4 _position; +in vec4 _positionES; in vec2 _texCoord0; in vec2 _texCoord1; -in vec3 _normal; -in vec3 _tangent; +in vec3 _normalWS; +in vec3 _tangentWS; in vec3 _color; layout(location = 0) out vec4 _fragColor0; @@ -56,9 +56,9 @@ void main(void) { <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; vec3 fresnel = getFresnelF0(metallic, albedo); - vec3 fragPosition = _position.xyz; + vec3 fragPosition = _positionES.xyz; vec3 fragNormal; - <$evalMaterialNormal(normalTex, _normal, _tangent, fragNormal)$> + <$evalMaterialNormal(normalTex, _normalWS, _tangentWS, fragNormal)$> TransformCamera cam = getTransformCamera(); diff --git a/libraries/render-utils/src/model_normal_map.slf b/libraries/render-utils/src/model_normal_map.slf index 49613eca6a..b13377af21 100644 --- a/libraries/render-utils/src/model_normal_map.slf +++ b/libraries/render-utils/src/model_normal_map.slf @@ -19,11 +19,11 @@ <@include MaterialTextures.slh@> <$declareMaterialTextures(ALBEDO, ROUGHNESS, NORMAL, METALLIC, EMISSIVE, OCCLUSION, SCATTERING)$> -in vec4 _position; +in vec4 _positionES; in vec2 _texCoord0; in vec2 _texCoord1; -in vec3 _normal; -in vec3 _tangent; +in vec3 _normalWS; +in vec3 _tangentWS; in vec3 _color; void main(void) { @@ -46,8 +46,8 @@ void main(void) { vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - vec3 fragNormal; - <$evalMaterialNormalLOD(_position, normalTex, _normal, _tangent, fragNormal)$> + vec3 fragNormalWS; + <$evalMaterialNormalLOD(_positionES, normalTex, _normalWS, _tangentWS, fragNormalWS)$> float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; @@ -56,7 +56,7 @@ void main(void) { <$evalMaterialScattering(scatteringTex, scattering, matKey, scattering)$>; packDeferredFragment( - normalize(fragNormal.xyz), + normalize(fragNormalWS.xyz), opacity, albedo, roughness, diff --git a/libraries/render-utils/src/model_normal_map.slv b/libraries/render-utils/src/model_normal_map.slv index a84f8c5e2a..cc99c6d22d 100644 --- a/libraries/render-utils/src/model_normal_map.slv +++ b/libraries/render-utils/src/model_normal_map.slv @@ -20,11 +20,11 @@ <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _position; +out vec4 _positionES; out vec2 _texCoord0; out vec2 _texCoord1; -out vec3 _normal; -out vec3 _tangent; +out vec3 _normalWS; +out vec3 _tangentWS; out vec3 _color; out float _alpha; @@ -40,7 +40,7 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> - <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangent)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normalWS)$> + <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangentWS)$> } diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index bac97f2546..f109170068 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -28,9 +28,9 @@ in vec2 _texCoord0; in vec2 _texCoord1; -in vec4 _position; -in vec4 _worldPosition; -in vec3 _normal; +in vec4 _positionES; +in vec4 _positionWS; +in vec3 _normalWS; in vec3 _color; in float _alpha; @@ -58,20 +58,21 @@ void main(void) { vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - vec3 fragPosition = _position.xyz; + vec3 fragPositionES = _positionES.xyz; + vec3 fragPositionWS = _positionWS.xyz; // Lighting is done in world space - vec3 fragNormal = normalize(_normal); + vec3 fragNormalWS = normalize(_normalWS); TransformCamera cam = getTransformCamera(); - vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); - vec3 fragEyeDir = normalize(fragEyeVector); - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + vec3 fragToEyeWS = fragPositionWS - cam._viewInverse[3].xyz; + vec3 fragToEyeDirWS = normalize(fragToEyeWS); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragToEyeDirWS); vec4 localLighting = vec4(0.0); - <$fetchClusterInfo(_worldPosition)$>; + <$fetchClusterInfo(_positionWS)$>; if (hasLocalLights(numLights, clusterPos, dims)) { - localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, surface, + localLighting = evalLocalLighting(cluster, numLights, fragPositionWS, surfaceWS, metallic, fresnel, albedo, 0.0, vec4(0), vec4(0), opacity); } @@ -80,11 +81,12 @@ void main(void) { cam._viewInverse, 1.0, occlusionTex, - fragPosition, + fragPositionES, + fragPositionWS, albedo, fresnel, metallic, emissive, - surface, opacity, localLighting.rgb), + surfaceWS, opacity, localLighting.rgb), opacity); } diff --git a/libraries/render-utils/src/model_translucent.slv b/libraries/render-utils/src/model_translucent.slv index 7a57a4baec..61a1c96ce8 100644 --- a/libraries/render-utils/src/model_translucent.slv +++ b/libraries/render-utils/src/model_translucent.slv @@ -22,9 +22,9 @@ out float _alpha; out vec2 _texCoord0; out vec2 _texCoord1; -out vec4 _position; -out vec4 _worldPosition; -out vec3 _normal; +out vec4 _positionES; +out vec4 _positionWS; +out vec3 _normalWS; out vec3 _color; void main(void) { @@ -38,7 +38,7 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> - <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _positionWS)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normalWS)$> } diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index b81ed06479..47349930de 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -24,11 +24,11 @@ in vec2 _texCoord0; in vec2 _texCoord1; -in vec4 _position; -in vec3 _normal; +in vec4 _positionES; +in vec4 _positionWS; +in vec3 _normalWS; in vec3 _color; in float _alpha; -in vec4 _worldPosition; out vec4 _fragColor; @@ -37,7 +37,7 @@ void main(void) { FadeObjectParams fadeParams; <$fetchFadeObjectParams(fadeParams)$> - applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + applyFade(fadeParams, _positionWS.xyz, fadeEmissive); Material mat = getMaterial(); BITFIELD matKey = getMaterialKey(mat); @@ -60,20 +60,21 @@ void main(void) { vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - vec3 fragPosition = _position.xyz; + vec3 fragPositionES = _positionES.xyz; + vec3 fragPositionWS = _positionWS.xyz; // Lighting is done in world space - vec3 fragNormal = normalize(_normal); + vec3 fragNormalWS = normalize(_normalWS); TransformCamera cam = getTransformCamera(); - vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); - vec3 fragEyeDir = normalize(fragEyeVector); - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + vec3 fragToEyeWS = fragPositionWS - cam._viewInverse[3].xyz; + vec3 fragToEyeDirWS = normalize(fragToEyeWS); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragToEyeDirWS); vec4 localLighting = vec4(0.0); - <$fetchClusterInfo(_worldPosition)$>; + <$fetchClusterInfo(_positionWS)$>; if (hasLocalLights(numLights, clusterPos, dims)) { - localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, surface, + localLighting = evalLocalLighting(cluster, numLights, fragPositionWS, surfaceWS, metallic, fresnel, albedo, 0.0, vec4(0), vec4(0), opacity); } @@ -82,11 +83,12 @@ void main(void) { cam._viewInverse, 1.0, occlusionTex, - fragPosition, + fragPositionES, + fragPositionWS, albedo, fresnel, metallic, - emissive+fadeEmissive, - surface, opacity, localLighting.rgb), + emissive + fadeEmissive, + surfaceWS, opacity, localLighting.rgb), opacity); } diff --git a/libraries/render-utils/src/model_translucent_normal_map.slf b/libraries/render-utils/src/model_translucent_normal_map.slf index 52015660c6..89f5f46f6a 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slf +++ b/libraries/render-utils/src/model_translucent_normal_map.slf @@ -28,10 +28,10 @@ in vec2 _texCoord0; in vec2 _texCoord1; -in vec4 _position; -in vec4 _worldPosition; -in vec3 _normal; -in vec3 _tangent; +in vec4 _positionES; +in vec4 _positionWS; +in vec3 _normalWS; +in vec3 _tangentWS; in vec3 _color; in float _alpha; @@ -59,20 +59,22 @@ void main(void) { vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - vec3 fragPosition = _position.xyz; - vec3 fragNormal; - <$evalMaterialNormalLOD(_position, normalTex, _normal, _tangent, fragNormal)$> + vec3 fragPositionES = _positionES.xyz; + vec3 fragPositionWS = _positionWS.xyz; + // Lighting is done in world space + vec3 fragNormalWS; + <$evalMaterialNormalLOD(_positionES, normalTex, _normalWS, _tangentWS, fragNormalWS)$> TransformCamera cam = getTransformCamera(); - vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); - vec3 fragEyeDir = normalize(fragEyeVector); - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + vec3 fragToEyeWS = fragPositionWS - cam._viewInverse[3].xyz; + vec3 fragToEyeDirWS = normalize(fragToEyeWS); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragToEyeDirWS); vec4 localLighting = vec4(0.0); - <$fetchClusterInfo(_worldPosition)$>; + <$fetchClusterInfo(_positionWS)$>; if (hasLocalLights(numLights, clusterPos, dims)) { - localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, surface, + localLighting = evalLocalLighting(cluster, numLights, fragPositionWS, surfaceWS, metallic, fresnel, albedo, 0.0, vec4(0), vec4(0), opacity); } @@ -81,11 +83,12 @@ void main(void) { cam._viewInverse, 1.0, occlusionTex, - fragPosition, + fragPositionES, + fragPositionWS, albedo, fresnel, metallic, emissive, - surface, opacity, localLighting.rgb), + surfaceWS, opacity, localLighting.rgb), opacity); } diff --git a/libraries/render-utils/src/model_translucent_normal_map.slv b/libraries/render-utils/src/model_translucent_normal_map.slv index 981a03627b..21d56418c0 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slv +++ b/libraries/render-utils/src/model_translucent_normal_map.slv @@ -22,10 +22,10 @@ out float _alpha; out vec2 _texCoord0; out vec2 _texCoord1; -out vec4 _position; -out vec4 _worldPosition; -out vec3 _normal; -out vec3 _tangent; +out vec4 _positionES; +out vec4 _positionWS; +out vec3 _normalWS; +out vec3 _tangentWS; out vec3 _color; void main(void) { @@ -39,8 +39,8 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> - <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> - <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangent)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _positionWS)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normalWS)$> + <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangentWS)$> } diff --git a/libraries/render-utils/src/model_translucent_normal_map_fade.slf b/libraries/render-utils/src/model_translucent_normal_map_fade.slf index c6c0e16812..a87167af63 100644 --- a/libraries/render-utils/src/model_translucent_normal_map_fade.slf +++ b/libraries/render-utils/src/model_translucent_normal_map_fade.slf @@ -31,12 +31,12 @@ in vec2 _texCoord0; in vec2 _texCoord1; -in vec4 _position; -in vec3 _normal; -in vec3 _tangent; +in vec4 _positionES; +in vec3 _normalWS; +in vec3 _tangentWS; in vec3 _color; in float _alpha; -in vec4 _worldPosition; +in vec4 _positionWS; out vec4 _fragColor; @@ -45,7 +45,7 @@ void main(void) { FadeObjectParams fadeParams; <$fetchFadeObjectParams(fadeParams)$> - applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + applyFade(fadeParams, _positionWS.xyz, fadeEmissive); Material mat = getMaterial(); int matKey = getMaterialKey(mat); @@ -68,21 +68,22 @@ void main(void) { vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - vec3 fragPosition = _position.xyz; + vec3 fragPositionES = _positionES.xyz; + vec3 fragPositionWS = _positionWS.xyz; // Lighting is done in world space - vec3 fragNormal; - <$evalMaterialNormalLOD(_position, normalTex, _normal, _tangent, fragNormal)$> + vec3 fragNormalWS; + <$evalMaterialNormalLOD(_positionES, normalTex, _normalWS, _tangentWS, fragNormalWS)$> TransformCamera cam = getTransformCamera(); - vec3 fragEyeVector = vec3(cam._viewInverse * vec4(-fragPosition, 0.0)); - vec3 fragEyeDir = normalize(fragEyeVector); - SurfaceData surface = initSurfaceData(roughness, fragNormal, fragEyeDir); + vec3 fragToEyeWS = fragPositionWS - cam._viewInverse[3].xyz; + vec3 fragToEyeDirWS = normalize(fragToEyeWS); + SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragToEyeDirWS); vec4 localLighting = vec4(0.0); - <$fetchClusterInfo(_worldPosition)$>; + <$fetchClusterInfo(_positionWS)$>; if (hasLocalLights(numLights, clusterPos, dims)) { - localLighting = evalLocalLighting(cluster, numLights, _worldPosition.xyz, surface, + localLighting = evalLocalLighting(cluster, numLights, fragPositionWS, surfaceWS, metallic, fresnel, albedo, 0.0, vec4(0), vec4(0), opacity); } @@ -91,11 +92,12 @@ void main(void) { cam._viewInverse, 1.0, occlusionTex, - fragPosition, + fragPositionES, + fragPositionWS, albedo, fresnel, metallic, - emissive+fadeEmissive, - surface, opacity, localLighting.rgb), + emissive + fadeEmissive, + surfaceWS, opacity, localLighting.rgb), opacity); } From 8ef25301cbc0e20419d2f9112a603e15b62f21ee Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 13 Apr 2018 17:14:19 +0200 Subject: [PATCH 03/16] Fixed some shader program link errors --- libraries/render-utils/src/model_fade.slf | 10 +++++----- libraries/render-utils/src/model_fade.slv | 12 +++++------ .../src/model_normal_map_fade.slf | 20 +++++++++---------- .../src/model_normal_map_fade.slv | 16 +++++++-------- .../src/model_translucent_unlit_fade.slf | 4 ++-- .../render-utils/src/model_unlit_fade.slf | 8 ++++---- .../render-utils/src/skin_model_fade.slv | 12 +++++------ .../render-utils/src/skin_model_fade_dq.slv | 12 +++++------ .../src/skin_model_normal_map.slv | 12 +++++------ .../src/skin_model_normal_map_dq.slv | 12 +++++------ .../src/skin_model_normal_map_fade.slv | 16 +++++++-------- .../src/skin_model_normal_map_fade_dq.slv | 16 +++++++-------- 12 files changed, 75 insertions(+), 75 deletions(-) diff --git a/libraries/render-utils/src/model_fade.slf b/libraries/render-utils/src/model_fade.slf index 577324f97a..432fc0239b 100644 --- a/libraries/render-utils/src/model_fade.slf +++ b/libraries/render-utils/src/model_fade.slf @@ -22,19 +22,19 @@ <@include Fade.slh@> <$declareFadeFragment()$> -in vec4 _position; +in vec4 _positionES; in vec2 _texCoord0; in vec2 _texCoord1; -in vec3 _normal; +in vec3 _normalWS; in vec3 _color; -in vec4 _worldPosition; +in vec4 _positionWS; void main(void) { vec3 fadeEmissive; FadeObjectParams fadeParams; <$fetchFadeObjectParams(fadeParams)$> - applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + applyFade(fadeParams, _positionWS.xyz, fadeEmissive); Material mat = getMaterial(); BITFIELD matKey = getMaterialKey(mat); @@ -61,7 +61,7 @@ void main(void) { float scattering = getMaterialScattering(mat); packDeferredFragment( - normalize(_normal), + normalize(_normalWS), opacity, albedo, roughness, diff --git a/libraries/render-utils/src/model_fade.slv b/libraries/render-utils/src/model_fade.slv index 61b8e9e1b6..6e3a8271ce 100644 --- a/libraries/render-utils/src/model_fade.slv +++ b/libraries/render-utils/src/model_fade.slv @@ -22,9 +22,9 @@ out float _alpha; out vec2 _texCoord0; out vec2 _texCoord1; -out vec4 _position; -out vec4 _worldPosition; -out vec3 _normal; +out vec4 _positionES; +out vec4 _positionWS; +out vec3 _normalWS; out vec3 _color; void main(void) { @@ -38,7 +38,7 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> - <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _positionWS)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normalWS)$> } diff --git a/libraries/render-utils/src/model_normal_map_fade.slf b/libraries/render-utils/src/model_normal_map_fade.slf index bf6222652c..7ece8fea38 100644 --- a/libraries/render-utils/src/model_normal_map_fade.slf +++ b/libraries/render-utils/src/model_normal_map_fade.slf @@ -2,7 +2,7 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// model_normal_specular_map_fade.frag +// model_normal_map_fade.frag // fragment shader // // Created by Olivier Prat on 06/05/17. @@ -22,20 +22,20 @@ <@include Fade.slh@> <$declareFadeFragment()$> -in vec4 _position; +in vec4 _positionES; in vec2 _texCoord0; in vec2 _texCoord1; -in vec3 _normal; -in vec3 _tangent; +in vec3 _normalWS; +in vec3 _tangentWS; in vec3 _color; -in vec4 _worldPosition; +in vec4 _positionWS; void main(void) { vec3 fadeEmissive; FadeObjectParams fadeParams; <$fetchFadeObjectParams(fadeParams)$> - applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + applyFade(fadeParams, _positionWS.xyz, fadeEmissive); Material mat = getMaterial(); BITFIELD matKey = getMaterialKey(mat); @@ -56,8 +56,8 @@ void main(void) { vec3 emissive = getMaterialEmissive(mat); <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - vec3 fragNormal; - <$evalMaterialNormalLOD(_position, normalTex, _normal, _tangent, fragNormal)$> + vec3 fragNormalWS; + <$evalMaterialNormalLOD(_positionES, normalTex, _normalWS, _tangentWS, fragNormalWS)$> float metallic = getMaterialMetallic(mat); <$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>; @@ -65,12 +65,12 @@ void main(void) { float scattering = getMaterialScattering(mat); packDeferredFragment( - normalize(fragNormal.xyz), + normalize(fragNormalWS.xyz), opacity, albedo, roughness, metallic, - emissive+fadeEmissive, + emissive + fadeEmissive, occlusionTex, scattering); } diff --git a/libraries/render-utils/src/model_normal_map_fade.slv b/libraries/render-utils/src/model_normal_map_fade.slv index 6a6142d317..a75087f93d 100644 --- a/libraries/render-utils/src/model_normal_map_fade.slv +++ b/libraries/render-utils/src/model_normal_map_fade.slv @@ -20,12 +20,12 @@ <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _position; -out vec4 _worldPosition; +out vec4 _positionES; +out vec4 _positionWS; out vec2 _texCoord0; out vec2 _texCoord1; -out vec3 _normal; -out vec3 _tangent; +out vec3 _normalWS; +out vec3 _tangentWS; out vec3 _color; out float _alpha; @@ -41,8 +41,8 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$> - <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> - <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normal)$> - <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangent)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> + <$transformModelToWorldPos(obj, inPosition, _positionWS)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normalWS)$> + <$transformModelToWorldDir(cam, obj, inTangent.xyz, _tangentWS)$> } diff --git a/libraries/render-utils/src/model_translucent_unlit_fade.slf b/libraries/render-utils/src/model_translucent_unlit_fade.slf index ea8a912a86..0f7c3366bb 100644 --- a/libraries/render-utils/src/model_translucent_unlit_fade.slf +++ b/libraries/render-utils/src/model_translucent_unlit_fade.slf @@ -24,7 +24,7 @@ in vec2 _texCoord0; in vec3 _color; in float _alpha; -in vec4 _worldPosition; +in vec4 _positionWS; out vec4 _fragColor; @@ -33,7 +33,7 @@ void main(void) { FadeObjectParams fadeParams; <$fetchFadeObjectParams(fadeParams)$> - applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + applyFade(fadeParams, _positionWS.xyz, fadeEmissive); Material mat = getMaterial(); BITFIELD matKey = getMaterialKey(mat); diff --git a/libraries/render-utils/src/model_unlit_fade.slf b/libraries/render-utils/src/model_unlit_fade.slf index 50c1681c0d..d8f8cfce38 100644 --- a/libraries/render-utils/src/model_unlit_fade.slf +++ b/libraries/render-utils/src/model_unlit_fade.slf @@ -23,17 +23,17 @@ <$declareMaterialTextures(ALBEDO)$> in vec2 _texCoord0; -in vec3 _normal; +in vec3 _normalWS; in vec3 _color; in float _alpha; -in vec4 _worldPosition; +in vec4 _positionWS; void main(void) { vec3 fadeEmissive; FadeObjectParams fadeParams; <$fetchFadeObjectParams(fadeParams)$> - applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); + applyFade(fadeParams, _positionWS.xyz, fadeEmissive); Material mat = getMaterial(); BITFIELD matKey = getMaterialKey(mat); @@ -48,7 +48,7 @@ void main(void) { albedo *= _color; albedo += fadeEmissive; packDeferredFragmentUnlit( - normalize(_normal), + normalize(_normalWS), opacity, albedo * isUnlitEnabled()); } diff --git a/libraries/render-utils/src/skin_model_fade.slv b/libraries/render-utils/src/skin_model_fade.slv index 4f459d75f3..6c3df7586d 100644 --- a/libraries/render-utils/src/skin_model_fade.slv +++ b/libraries/render-utils/src/skin_model_fade.slv @@ -23,13 +23,13 @@ <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _position; +out vec4 _positionES; out vec2 _texCoord0; out vec2 _texCoord1; -out vec3 _normal; +out vec3 _normalWS; out vec3 _color; out float _alpha; -out vec4 _worldPosition; +out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -48,7 +48,7 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> - <$transformModelToWorldPos(obj, position, _worldPosition)$> - <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normal.xyz)$> + <$transformModelToEyeAndClipPos(cam, obj, position, _positionES, gl_Position)$> + <$transformModelToWorldPos(obj, position, _positionWS)$> + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normalWS.xyz)$> } diff --git a/libraries/render-utils/src/skin_model_fade_dq.slv b/libraries/render-utils/src/skin_model_fade_dq.slv index 232170d714..9d9ddaeaf8 100644 --- a/libraries/render-utils/src/skin_model_fade_dq.slv +++ b/libraries/render-utils/src/skin_model_fade_dq.slv @@ -23,13 +23,13 @@ <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _position; +out vec4 _positionES; out vec2 _texCoord0; out vec2 _texCoord1; -out vec3 _normal; +out vec3 _normalWS; out vec3 _color; out float _alpha; -out vec4 _worldPosition; +out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -48,7 +48,7 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> - <$transformModelToWorldPos(obj, position, _worldPosition)$> - <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normal.xyz)$> + <$transformModelToEyeAndClipPos(cam, obj, position, _positionES, gl_Position)$> + <$transformModelToWorldPos(obj, position, _positionWS)$> + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normalWS.xyz)$> } diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index b54c84e5b3..fd3efd087e 100644 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -23,11 +23,11 @@ <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _position; +out vec4 _positionES; out vec2 _texCoord0; out vec2 _texCoord1; -out vec3 _normal; -out vec3 _tangent; +out vec3 _normalWS; +out vec3 _tangentWS; out vec3 _color; out float _alpha; @@ -52,10 +52,10 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> + <$transformModelToEyeAndClipPos(cam, obj, position, _positionES, gl_Position)$> <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> <$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> - _normal = interpolatedNormal.xyz; - _tangent = interpolatedTangent.xyz; + _normalWS = interpolatedNormal.xyz; + _tangentWS = interpolatedTangent.xyz; } diff --git a/libraries/render-utils/src/skin_model_normal_map_dq.slv b/libraries/render-utils/src/skin_model_normal_map_dq.slv index b34f68d291..4c56e0d64d 100644 --- a/libraries/render-utils/src/skin_model_normal_map_dq.slv +++ b/libraries/render-utils/src/skin_model_normal_map_dq.slv @@ -23,11 +23,11 @@ <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _position; +out vec4 _positionES; out vec2 _texCoord0; out vec2 _texCoord1; -out vec3 _normal; -out vec3 _tangent; +out vec3 _normalWS; +out vec3 _tangentWS; out vec3 _color; out float _alpha; @@ -52,10 +52,10 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> + <$transformModelToEyeAndClipPos(cam, obj, position, _positionES, gl_Position)$> <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> <$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> - _normal = interpolatedNormal.xyz; - _tangent = interpolatedTangent.xyz; + _normalWS = interpolatedNormal.xyz; + _tangentWS = interpolatedTangent.xyz; } diff --git a/libraries/render-utils/src/skin_model_normal_map_fade.slv b/libraries/render-utils/src/skin_model_normal_map_fade.slv index 0e788b81b5..47cc790f70 100644 --- a/libraries/render-utils/src/skin_model_normal_map_fade.slv +++ b/libraries/render-utils/src/skin_model_normal_map_fade.slv @@ -23,14 +23,14 @@ <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _position; +out vec4 _positionES; out vec2 _texCoord0; out vec2 _texCoord1; -out vec3 _normal; -out vec3 _tangent; +out vec3 _normalWS; +out vec3 _tangentWS; out vec3 _color; out float _alpha; -out vec4 _worldPosition; +out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -53,11 +53,11 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> - <$transformModelToWorldPos(obj, position, _worldPosition)$> + <$transformModelToEyeAndClipPos(cam, obj, position, _positionES, gl_Position)$> + <$transformModelToWorldPos(obj, position, _positionWS)$> <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> <$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> - _normal = interpolatedNormal.xyz; - _tangent = interpolatedTangent.xyz; + _normalWS = interpolatedNormal.xyz; + _tangentWS = interpolatedTangent.xyz; } diff --git a/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv index 230077ba3b..092d7b214f 100644 --- a/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv +++ b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv @@ -23,14 +23,14 @@ <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> -out vec4 _position; +out vec4 _positionES; out vec2 _texCoord0; out vec2 _texCoord1; -out vec3 _normal; -out vec3 _tangent; +out vec3 _normalWS; +out vec3 _tangentWS; out vec3 _color; out float _alpha; -out vec4 _worldPosition; +out vec4 _positionWS; void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); @@ -53,11 +53,11 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> - <$transformModelToWorldPos(obj, position, _worldPosition)$> + <$transformModelToEyeAndClipPos(cam, obj, position, _positionES, gl_Position)$> + <$transformModelToWorldPos(obj, position, _positionWS)$> <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> <$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> - _normal = interpolatedNormal.xyz; - _tangent = interpolatedTangent.xyz; + _normalWS = interpolatedNormal.xyz; + _tangentWS = interpolatedTangent.xyz; } From 9ad9c070f8e4f83791e3943afe1a3b4f08ee6bc9 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 16 Apr 2018 08:50:18 +0200 Subject: [PATCH 04/16] Some small shader optimizations --- libraries/render-utils/src/DeferredGlobalLight.slh | 7 ++----- libraries/render-utils/src/ForwardGlobalLight.slh | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index c3ab17f72b..f6c1d290a7 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -20,7 +20,6 @@ <@include LightAmbient.slh@> <@include LightDirectional.slh@> - <@func fetchGlobalLight()@> // Get light Light light = getKeyLight(); @@ -193,14 +192,13 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl vec3 ambientSpecular; evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance); color += ambientDiffuse; - color += ambientSpecular / opacity; // Directional vec3 directionalDiffuse; vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; - color += directionalSpecular / opacity; + color += (ambientSpecular + directionalSpecular) / opacity; return color; } @@ -227,14 +225,13 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( vec3 ambientSpecular; evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance); color += ambientDiffuse; - color += ambientSpecular / opacity; // Directional vec3 directionalDiffuse; vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; - color += directionalSpecular / opacity; + color += (ambientSpecular + directionalSpecular) / opacity; // Haze if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { diff --git a/libraries/render-utils/src/ForwardGlobalLight.slh b/libraries/render-utils/src/ForwardGlobalLight.slh index 3aadb182fa..84999f347b 100644 --- a/libraries/render-utils/src/ForwardGlobalLight.slh +++ b/libraries/render-utils/src/ForwardGlobalLight.slh @@ -20,7 +20,6 @@ <@include LightAmbient.slh@> <@include LightDirectional.slh@> - <@func prepareGlobalLight(positionES, normalWS)@> // prepareGlobalLight // Transform directions to worldspace @@ -192,14 +191,13 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl vec3 ambientSpecular; evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance); color += ambientDiffuse; - color += ambientSpecular / opacity; // Directional vec3 directionalDiffuse; vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; - color += directionalSpecular / opacity; + color += (ambientSpecular + directionalSpecular) / opacity; return color; } @@ -219,14 +217,13 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( vec3 ambientSpecular; evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, surfaceWS, metallic, fresnel, albedo, obscurance); color += ambientDiffuse; - color += ambientSpecular / opacity; // Directional vec3 directionalDiffuse; vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; - color += directionalSpecular / opacity; + color += (ambientSpecular + directionalSpecular) / opacity; // Haze // FIXME - temporarily removed until we support it for forward... From 5e22202355b1e386abe4972842f1fb1ae9d51c44 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Mon, 16 Apr 2018 13:11:03 +0300 Subject: [PATCH 05/16] Material resources: support relative path for emissiveMap --- .../model-networking/src/model-networking/MaterialCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index f0cbfc914a..823602d939 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -194,7 +194,7 @@ std::pair> NetworkMaterialResource } else if (key == "emissiveMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setEmissiveMap(value.toString()); + material->setEmissiveMap(baseUrl.resolved(value.toString())); } } else if (key == "albedoMap") { auto value = materialJSON.value(key); From 6a089be973b240ead39692c073896540ecc4521a Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 16 Apr 2018 12:15:08 +0200 Subject: [PATCH 06/16] Enabled haze to be vertically inverted (ceiling lower than base) --- libraries/graphics/src/graphics/Haze.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/graphics/src/graphics/Haze.h b/libraries/graphics/src/graphics/Haze.h index c38fadef94..59138319f4 100644 --- a/libraries/graphics/src/graphics/Haze.h +++ b/libraries/graphics/src/graphics/Haze.h @@ -57,7 +57,7 @@ namespace graphics { // limit range and altitude to no less than 1.0 metres static inline float convertHazeRangeToHazeRangeFactor(const float hazeRange) { return -LOG_P_005 / glm::max(hazeRange, 1.0f); } - static inline float convertHazeAltitudeToHazeAltitudeFactor(const float hazeHeight) { return -LOG_P_005 / glm::max(hazeHeight, 1.0f); } + static inline float convertHazeAltitudeToHazeAltitudeFactor(const float hazeHeight) { return -(LOG_P_005 * glm::sign(hazeHeight)) / glm::max(glm::abs(hazeHeight), 1.0f); } // Derivation (s is the proportion of sun blend, a is the angle at which the blend is 50%, solve for m = 0.5 // s = dot(lookAngle, sunAngle) = cos(a) From 93cf399fd6667ea74108af842dd3b81b75d60766 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 16 Apr 2018 15:07:45 -0700 Subject: [PATCH 07/16] Only dismiss letterbox when clicking on grey areas --- .../resources/qml/hifi/LetterboxMessage.qml | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/LetterboxMessage.qml b/interface/resources/qml/hifi/LetterboxMessage.qml index d88bf1b147..c0f3fa1006 100644 --- a/interface/resources/qml/hifi/LetterboxMessage.qml +++ b/interface/resources/qml/hifi/LetterboxMessage.qml @@ -135,9 +135,46 @@ Item { } } } + // Left gray MouseArea MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton + anchors.left: parent.left; + anchors.right: textContainer.left; + anchors.top: textContainer.top; + anchors.bottom: textContainer.bottom; + acceptedButtons: Qt.LeftButton; + onClicked: { + letterbox.visible = false; + } + } + // Right gray MouseArea + MouseArea { + anchors.left: textContainer.left; + anchors.right: parent.left; + anchors.top: textContainer.top; + anchors.bottom: textContainer.bottom; + acceptedButtons: Qt.LeftButton; + onClicked: { + letterbox.visible = false; + } + } + // Top gray MouseArea + MouseArea { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.bottom: textContainer.top; + acceptedButtons: Qt.LeftButton; + onClicked: { + letterbox.visible = false; + } + } + // Bottom gray MouseArea + MouseArea { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: textContainer.bottom; + anchors.bottom: parent.bottom; + acceptedButtons: Qt.LeftButton; onClicked: { letterbox.visible = false; } From f36a5289c9ac6a79880dc0cc10bb84daf3608929 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 17 Apr 2018 14:33:11 +1200 Subject: [PATCH 08/16] Add Entities materialData JSDoc --- libraries/entities/src/EntityItemProperties.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index f78168b05d..4638b46437 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -695,8 +695,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @typedef {object} Entities.EntityProperties-Material * @property {string} materialURL="" - URL to a {@link MaterialResource}. If you append ?name to the URL, the * material with that name in the {@link MaterialResource} will be applied to the entity.
- * Alternatively, set the property value to "userData" to use the {@link Entities.EntityProperties|userData} - * entity property to live edit the material resource values. + * Alternatively, set the property value to "materialData" to use the materialData property + * for the {@link MaterialResource} values. * @property {number} priority=0 - The priority for applying the material to its parent. Only the highest priority material is * applied, with materials of the same priority randomly assigned. Materials that come with the model have a priority of * 0. @@ -710,6 +710,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * { x: 0, y: 0 }{ x: 1, y: 1 }. * @property {Vec2} materialMappingScale=1,1 - How much to scale the material within the parent's UV-space. * @property {number} materialMappingRot=0 - How much to rotate the material within the parent's UV-space, in degrees. + * @property {string} materialData="" - Used to store {@link MaterialResource} data as a JSON string. You can use + * JSON.parse() to parse the string into a JavaScript object which you can manipulate the properties of, and + * use JSON.stringify() to convert the object into a string to put in the property. * @example Color a sphere using a Material entity. * var entityID = Entities.addEntity({ * type: "Sphere", @@ -722,13 +725,14 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * var materialID = Entities.addEntity({ * type: "Material", * parentID: entityID, - * materialURL: "userData", + * materialURL: "materialData", * priority: 1, - * userData: JSON.stringify({ + * materialData: JSON.stringify({ + * materialVersion: 1, * materials: { * // Can only set albedo on a Shape entity. * // Value overrides entity's "color" property. - * albedo: [1.0, 0, 0] + * albedo: [1.0, 1.0, 0] // Yellow * } * }), * }); From e6f0bfbf4cc7b50a94123d06c9048ea2ede5d696 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 18 Apr 2018 00:10:49 +0200 Subject: [PATCH 09/16] get proper checkbox value from the allow light selection menu option --- scripts/system/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index fd7a488eb7..88e2fd7bf0 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1293,7 +1293,7 @@ Script.scriptEnding.connect(function () { Settings.setValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); - Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); progressDialog.cleanup(); From 40e311fe1ee640c68ab87eeae71aa1ffff44e74f Mon Sep 17 00:00:00 2001 From: MiladNazeri Date: Tue, 17 Apr 2018 15:20:05 -0700 Subject: [PATCH 10/16] Fix QT typo --- BUILD_LINUX.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 64c0e9a643..0daef5ae05 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -11,11 +11,11 @@ Should you choose not to install Qt5 via a package manager that handles dependen ## Ubuntu 16.04 specific build guide ### Prepare environment - +hifiqt5.10.1 Install qt: ```bash -wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.10.1_5.10.1_amd64.deb -sudo dpkg -i hifi-qt5.10.1_5.10.1_amd64.deb +wget http://debian.highfidelity.com/pool/h/hi/hifiqt5.10.1_5.10.1_amd64.deb +sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb ``` Install build dependencies: From 2e8a6e1961eec3f2c240cd28518504a64ef9c4bd Mon Sep 17 00:00:00 2001 From: Clement Date: Tue, 10 Apr 2018 18:49:30 -0700 Subject: [PATCH 11/16] Remove more dead octree code --- .../src/entities/EntityTreeSendThread.h | 2 - .../src/octree/OctreeSendThread.cpp | 177 ++-- .../src/octree/OctreeSendThread.h | 10 +- assignment-client/src/octree/OctreeServer.cpp | 4 - assignment-client/src/octree/OctreeServer.h | 2 +- libraries/entities/src/EntityItem.h | 1 - libraries/entities/src/EntityTree.h | 5 - libraries/entities/src/EntityTreeElement.cpp | 449 --------- libraries/entities/src/EntityTreeElement.h | 11 - libraries/entities/src/LightEntityItem.cpp | 1 - libraries/entities/src/LineEntityItem.cpp | 1 - libraries/entities/src/LineEntityItem.h | 1 - libraries/entities/src/MaterialEntityItem.cpp | 2 - libraries/entities/src/MaterialEntityItem.h | 1 - libraries/entities/src/ModelEntityItem.cpp | 3 +- libraries/entities/src/ModelEntityItem.h | 1 - .../entities/src/ParticleEffectEntityItem.cpp | 2 - libraries/entities/src/PolyLineEntityItem.cpp | 2 - libraries/entities/src/PolyLineEntityItem.h | 1 - libraries/entities/src/PolyVoxEntityItem.cpp | 2 - libraries/entities/src/PolyVoxEntityItem.h | 1 - libraries/entities/src/ShapeEntityItem.cpp | 2 - libraries/entities/src/TextEntityItem.cpp | 2 - libraries/entities/src/TextEntityItem.h | 1 - libraries/entities/src/WebEntityItem.cpp | 2 - libraries/entities/src/WebEntityItem.h | 1 - libraries/entities/src/ZoneEntityItem.cpp | 2 - libraries/entities/src/ZoneEntityItem.h | 1 - libraries/octree/src/Octree.cpp | 849 ------------------ libraries/octree/src/Octree.h | 103 +-- libraries/octree/src/OctreeElement.cpp | 27 - libraries/octree/src/OctreeElement.h | 13 - libraries/octree/src/OctreeElementBag.cpp | 40 - libraries/octree/src/OctreeElementBag.h | 22 - libraries/octree/src/OctreeProcessor.cpp | 2 +- libraries/octree/src/OctreeQueryNode.cpp | 69 -- libraries/octree/src/OctreeQueryNode.h | 31 - libraries/octree/src/OctreeSceneStats.cpp | 4 - libraries/octree/src/OctreeSceneStats.h | 3 - libraries/shared/src/OctalCode.cpp | 16 - libraries/shared/src/OctalCode.h | 2 - tests/entities/src/main.cpp | 1 - 42 files changed, 66 insertions(+), 1806 deletions(-) delete mode 100644 libraries/octree/src/OctreeElementBag.cpp diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 594f423838..1e2bd15429 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -48,8 +48,6 @@ private: void preDistributionProcessing() override; bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); } bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); } - void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {}; - bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; } DiffTraversal _traversal; EntityPriorityQueue _sendQueue; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 715e83f403..f8d566862e 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -304,23 +304,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* return numPackets; } -void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) { - // If we're starting a full scene, then definitely we want to empty the elementBag - if (isFullScene) { - nodeData->elementBag.deleteAll(); - } - - // This is the start of "resending" the scene. - bool dontRestartSceneOnMove = false; // this is experimental - if (dontRestartSceneOnMove) { - if (nodeData->elementBag.isEmpty()) { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } - } else { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } -} - /// Version of octree element distributor that sends the deepest LOD level at once int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { OctreeServer::didPacketDistributor(this); @@ -366,16 +349,8 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // the current view frustum for things to send. if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) { - // if our view has changed, we need to reset these things... - if (viewFrustumChanged) { - if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) { - nodeData->dumpOutOfView(); - } - } - // track completed scenes and send out the stats packet accordingly nodeData->stats.sceneCompleted(); - nodeData->setLastRootTimestamp(_myServer->getOctree()->getRoot()->getLastChanged()); _myServer->getOctree()->releaseSceneEncodeData(&nodeData->extraEncodeData); // TODO: add these to stats page @@ -389,111 +364,74 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // TODO: add these to stats page //::startSceneSleepTime = _usleepTime; - nodeData->sceneStart(usecTimestampNow() - CHANGE_FUDGE); // start tracking our stats nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot()); - - preStartNewScene(nodeData, isFullScene); } - // If we have something in our elementBag, then turn them into packets and send them out... - if (shouldTraverseAndSend(nodeData)) { - quint64 start = usecTimestampNow(); + quint64 start = usecTimestampNow(); - _myServer->getOctree()->withReadLock([&]{ - traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); - }); + _myServer->getOctree()->withReadLock([&]{ + traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); + }); - // Here's where we can/should allow the server to send other data... - // send the environment packet - // TODO: should we turn this into a while loop to better handle sending multiple special packets - if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) { - int specialPacketsSent = 0; - int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); - nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed - _truePacketsSent += specialPacketsSent; - _trueBytesSent += specialBytesSent; - _packetsSentThisInterval += specialPacketsSent; + // Here's where we can/should allow the server to send other data... + // send the environment packet + // TODO: should we turn this into a while loop to better handle sending multiple special packets + if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) { + int specialPacketsSent = 0; + int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); + nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed + _truePacketsSent += specialPacketsSent; + _trueBytesSent += specialBytesSent; + _packetsSentThisInterval += specialPacketsSent; - _totalPackets += specialPacketsSent; - _totalBytes += specialBytesSent; + _totalPackets += specialPacketsSent; + _totalBytes += specialBytesSent; - _totalSpecialPackets += specialPacketsSent; - _totalSpecialBytes += specialBytesSent; + _totalSpecialPackets += specialPacketsSent; + _totalSpecialBytes += specialBytesSent; + } + + // calculate max number of packets that can be sent during this interval + int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); + int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + + // Re-send packets that were nacked by the client + while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) { + const NLPacket* packet = nodeData->getNextNackedPacket(); + if (packet) { + DependencyManager::get()->sendUnreliablePacket(*packet, *node); + int numBytes = packet->getDataSize(); + _truePacketsSent++; + _trueBytesSent += numBytes; + _packetsSentThisInterval++; + + _totalPackets++; + _totalBytes += numBytes; + _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize(); } + } - // calculate max number of packets that can be sent during this interval - int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); - int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + quint64 end = usecTimestampNow(); + int elapsedmsec = (end - start) / USECS_PER_MSEC; + OctreeServer::trackLoopTime(elapsedmsec); - // Re-send packets that were nacked by the client - while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) { - const NLPacket* packet = nodeData->getNextNackedPacket(); - if (packet) { - DependencyManager::get()->sendUnreliablePacket(*packet, *node); - int numBytes = packet->getDataSize(); - _truePacketsSent++; - _trueBytesSent += numBytes; - _packetsSentThisInterval++; + // if after sending packets we've emptied our bag, then we want to remember that we've sent all + // the octree elements from the current view frustum + if (!hasSomethingToSend(nodeData)) { + nodeData->setViewSent(true); - _totalPackets++; - _totalBytes += numBytes; - _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize(); - } + // If this was a full scene then make sure we really send out a stats packet at this point so that + // the clients will know the scene is stable + if (isFullScene) { + nodeData->stats.sceneCompleted(); + handlePacketSend(node, nodeData, true); } - - quint64 end = usecTimestampNow(); - int elapsedmsec = (end - start) / USECS_PER_MSEC; - OctreeServer::trackLoopTime(elapsedmsec); - - // if after sending packets we've emptied our bag, then we want to remember that we've sent all - // the octree elements from the current view frustum - if (!hasSomethingToSend(nodeData)) { - nodeData->updateLastKnownViewFrustum(); - nodeData->setViewSent(true); - - // If this was a full scene then make sure we really send out a stats packet at this point so that - // the clients will know the scene is stable - if (isFullScene) { - nodeData->stats.sceneCompleted(); - handlePacketSend(node, nodeData, true); - } - } - - } // end if bag wasn't empty, and so we sent stuff... + } return _truePacketsSent; } -bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) { - bool somethingToSend = false; - OctreeQueryNode* nodeData = static_cast(params.nodeData); - if (!nodeData->elementBag.isEmpty()) { - quint64 encodeStart = usecTimestampNow(); - quint64 lockWaitStart = encodeStart; - - _myServer->getOctree()->withReadLock([&]{ - OctreeServer::trackTreeWaitTime((float)(usecTimestampNow() - lockWaitStart)); - - OctreeElementPointer subTree = nodeData->elementBag.extract(); - if (subTree) { - // NOTE: this is where the tree "contents" are actually packed - nodeData->stats.encodeStarted(); - _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params); - nodeData->stats.encodeStopped(); - - somethingToSend = true; - } - }); - - OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart)); - } else { - OctreeServer::trackTreeWaitTime(OctreeServer::SKIP_TIME); - OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME); - } - return somethingToSend; -} - void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { // calculate max number of packets that can be sent during this interval int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); @@ -502,21 +440,12 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre int extraPackingAttempts = 0; // init params once outside the while loop - int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); - int boundaryLevelAdjust = boundaryLevelAdjustClient + - (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - float octreeSizeScale = nodeData->getOctreeSizeScale(); - EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, - viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, - isFullScene, nodeData); + EncodeBitstreamParams params(WANT_EXISTS_BITS, nodeData); // Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { _myServer->trackSend(dataID, dataEdited, _nodeUuid); }; nodeData->copyCurrentViewFrustum(params.viewFrustum); - if (viewFrustumChanged) { - nodeData->copyLastKnownViewFrustum(params.lastViewFrustum); - } bool somethingToSend = true; // assume we have something bool hadSomething = hasSomethingToSend(nodeData); @@ -537,7 +466,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre } // If the bag had contents but is now empty then we know we've sent the entire scene. - bool completedScene = hadSomething && nodeData->elementBag.isEmpty(); + bool completedScene = hadSomething; if (completedScene || lastNodeDidntFit) { // we probably want to flush what has accumulated in nodeData but: // do we have more data to send? and is there room? diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 220952e209..91c0ec7adc 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -54,7 +54,7 @@ protected: virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene); - virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters); + virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) = 0; OctreePacketData _packetData; QWeakPointer _node; @@ -63,14 +63,12 @@ protected: private: /// Called before a packetDistributor pass to allow for pre-distribution processing - virtual void preDistributionProcessing() {}; + virtual void preDistributionProcessing() = 0; int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false); int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged); - virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); } - virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); } - virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene); - virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); } + virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) = 0; + virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) = 0; int _truePacketsSent { 0 }; // available for debug stats int _trueBytesSent { 0 }; // available for debug stats diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 0dbd24fe9c..fad2c1f026 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -876,10 +876,6 @@ void OctreeServer::parsePayload() { } } -OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePointer& node) { - return std::unique_ptr(new OctreeSendThread(this, node)); -} - OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) { auto sendThread = newSendThread(node); diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index e7efc731f2..b25e537d70 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -174,7 +174,7 @@ protected: void beginRunning(QByteArray replaceData); UniqueSendThread createSendThread(const SharedNodePointer& node); - virtual UniqueSendThread newSendThread(const SharedNodePointer& node); + virtual UniqueSendThread newSendThread(const SharedNodePointer& node) = 0; int _argc; const char** _argv; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index de98c1a47a..0557bbe5ad 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -120,7 +120,6 @@ public: void markAsChangedOnServer(); quint64 getLastChangedOnServer() const; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 791c030fc8..a080801a0e 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -88,7 +88,6 @@ public: // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing - virtual bool getWantSVOfileVersions() const override { return true; } virtual PacketType expectedDataPacketType() const override { return PacketType::EntityData; } virtual bool handlesEditPacketType(PacketType packetType) const override; void fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties); @@ -107,11 +106,7 @@ public: virtual bool rootElementHasData() const override { return true; } - // the root at least needs to store the number of entities in the packet/buffer - virtual int minimumRequiredRootDataBytes() const override { return sizeof(uint16_t); } - virtual bool suppressEmptySubtrees() const override { return false; } virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const override; - virtual bool mustIncludeAllChildData() const override { return false; } virtual void update() override { update(true); } diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 1ae55bc333..cbcddfc57b 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -67,455 +67,6 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons } } -void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) { - - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - // Check to see if this element yet has encode data... if it doesn't create it - if (!extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData { new EntityTreeElementExtraEncodeData() }; - entityTreeElementExtraEncodeData->elementCompleted = (_entityItems.size() == 0); - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - EntityTreeElementPointer child = getChildAtIndex(i); - if (!child) { - entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed - } else { - if (child->hasEntities()) { - entityTreeElementExtraEncodeData->childCompleted[i] = false; // HAS ENTITIES NEEDS ENCODING - } else { - entityTreeElementExtraEncodeData->childCompleted[i] = true; // child doesn't have enities, it is completed - } - } - } - forEachEntity([&](EntityItemPointer entity) { - entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); - }); - - // TODO: some of these inserts might be redundant!!! - extraEncodeData->insert(this, entityTreeElementExtraEncodeData); - } -} - -bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { - - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - - if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex]; - - // If we haven't completely sent the child yet, then we should include it - return !childCompleted; - } - - // I'm not sure this should ever happen, since we should have the extra encode data if we're considering - // the child data for this element - assert(false); - return false; -} - -bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { - EntityTreeElementPointer childElement = getChildAtIndex(childIndex); - if (childElement->alreadyFullyEncoded(params)) { - return false; - } - - return true; // if we don't know otherwise than recurse! -} - -bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const { - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - - if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - // If we know that ALL subtrees below us have already been recursed, then we don't - // need to recurse this child. - return entityTreeElementExtraEncodeData->subtreeCompleted; - } - return false; -} - -void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - - if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - if (childAppendState == OctreeElement::COMPLETED) { - entityTreeElementExtraEncodeData->childCompleted[childIndex] = true; - } - } else { - assert(false); // this shouldn't happen! - } -} - - - - -void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const { - const bool wantDebug = false; - - if (wantDebug) { - qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube; - } - - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - assert(extraEncodeData->contains(this)); - - EntityTreeElementExtraEncodeDataPointer thisExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - // Note: this will be called when OUR element has finished running through encodeTreeBitstreamRecursion() - // which means, it's possible that our parent element hasn't finished encoding OUR data... so - // in this case, our children may be complete, and we should clean up their encode data... - // but not necessarily cleanup our own encode data... - // - // If we're really complete here's what must be true... - // 1) our own data must be complete - // 2) the data for all our immediate children must be complete. - // However, the following might also be the case... - // 1) it's ok for our child trees to not yet be fully encoded/complete... - // SO LONG AS... the our child's node is in the bag ready for encoding - - bool someChildTreeNotComplete = false; - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - EntityTreeElementPointer childElement = getChildAtIndex(i); - if (childElement) { - - // why would this ever fail??? - // If we've encoding this element before... but we're coming back a second time in an attempt to - // encode our parent... this might happen. - if (extraEncodeData->contains(childElement.get())) { - EntityTreeElementExtraEncodeDataPointer childExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[childElement.get()]); - - if (wantDebug) { - qCDebug(entities) << "checking child: " << childElement->_cube; - qCDebug(entities) << " childElement->isLeaf():" << childElement->isLeaf(); - qCDebug(entities) << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted; - qCDebug(entities) << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted; - } - - if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) { - if (wantDebug) { - qCDebug(entities) << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!"; - } - childExtraEncodeData->subtreeCompleted = true; - } - - if (!childExtraEncodeData->elementCompleted || !childExtraEncodeData->subtreeCompleted) { - someChildTreeNotComplete = true; - } - } - } - } - - if (wantDebug) { - qCDebug(entities) << "for this element: " << _cube; - qCDebug(entities) << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted; - qCDebug(entities) << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - } - - thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete; - - if (wantDebug) { - qCDebug(entities) << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted; - qCDebug(entities) << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - - if (thisExtraEncodeData->subtreeCompleted) { - qCDebug(entities) << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - } - } -} - -OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData, - EncodeBitstreamParams& params) const { - - OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best... - - auto entityNodeData = static_cast(params.nodeData); - Q_ASSERT_X(entityNodeData, "EntityTreeElement::appendElementData", "expected params.nodeData not to be null"); - - // first, check the params.extraEncodeData to see if there's any partial re-encode data for this element - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData = NULL; - bool hadElementExtraData = false; - if (extraEncodeData && extraEncodeData->contains(this)) { - entityTreeElementExtraEncodeData = - std::static_pointer_cast((*extraEncodeData)[this]); - hadElementExtraData = true; - } else { - // if there wasn't one already, then create one - entityTreeElementExtraEncodeData.reset(new EntityTreeElementExtraEncodeData()); - entityTreeElementExtraEncodeData->elementCompleted = !hasContent(); - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - EntityTreeElementPointer child = getChildAtIndex(i); - if (!child) { - entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed - } else { - if (child->hasEntities()) { - entityTreeElementExtraEncodeData->childCompleted[i] = false; - } else { - // if the child doesn't have enities, it is completed - entityTreeElementExtraEncodeData->childCompleted[i] = true; - } - } - } - forEachEntity([&](EntityItemPointer entity) { - entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); - }); - } - - //assert(extraEncodeData); - //assert(extraEncodeData->contains(this)); - //entityTreeElementExtraEncodeData = std::static_pointer_cast((*extraEncodeData)[this]); - - LevelDetails elementLevel = packetData->startLevel(); - - // write our entities out... first determine which of the entities are in view based on our params - uint16_t numberOfEntities = 0; - uint16_t actualNumberOfEntities = 0; - int numberOfEntitiesOffset = 0; - withReadLock([&] { - QVector indexesOfEntitiesToInclude; - - // It's possible that our element has been previous completed. In this case we'll simply not include any of our - // entities for encoding. This is needed because we encode the element data at the "parent" level, and so we - // need to handle the case where our sibling elements need encoding but we don't. - if (!entityTreeElementExtraEncodeData->elementCompleted) { - - - // we have an EntityNodeData instance - // so we should assume that means we might have JSON filters to check - auto jsonFilters = entityNodeData->getJSONParameters(); - - - for (uint16_t i = 0; i < _entityItems.size(); i++) { - EntityItemPointer entity = _entityItems[i]; - bool includeThisEntity = true; - - if (!params.forceSendScene && entity->getLastChangedOnServer() < entityNodeData->getLastTimeBagEmpty()) { - includeThisEntity = false; - } - - // if this entity has been updated since our last full send and there are json filters, check them - if (includeThisEntity && !jsonFilters.isEmpty()) { - - // if params include JSON filters, check if this entity matches - bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters); - - if (entityMatchesFilters) { - // make sure this entity is in the set of entities sent last frame - entityNodeData->insertSentFilteredEntity(entity->getID()); - } else if (entityNodeData->sentFilteredEntity(entity->getID())) { - // this entity matched in the previous frame - we send it still so the client realizes it just - // fell outside of their filter - entityNodeData->removeSentFilteredEntity(entity->getID()); - } else if (!entityNodeData->isEntityFlaggedAsExtra(entity->getID())) { - // we don't send this entity because - // (1) it didn't match our filter - // (2) it didn't match our filter last frame - // (3) it isn't one the JSON query flags told us we should still include - includeThisEntity = false; - } - } - - if (includeThisEntity && hadElementExtraData) { - includeThisEntity = entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID()); - } - - // we only check the bounds against our frustum and LOD if the query has asked us to check against the frustum - // which can sometimes not be the case when JSON filters are sent - if (entityNodeData->getUsesFrustum() && (includeThisEntity || params.recurseEverything)) { - - // we want to use the maximum possible box for this, so that we don't have to worry about the nuance of - // simulation changing what's visible. consider the case where the entity contains an angular velocity - // the entity may not be in view and then in view a frame later, let the client side handle it's view - // frustum culling on rendering. - bool success; - AACube entityCube = entity->getQueryAACube(success); - if (!success || !params.viewFrustum.cubeIntersectsKeyhole(entityCube)) { - includeThisEntity = false; // out of view, don't include it - } else { - // Check the size of the entity, it's possible that a "too small to see" entity is included in a - // larger octree cell because of its position (for example if it crosses the boundary of a cell it - // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen - // before we consider including it. - success = true; - // we can't cull a parent-entity by its dimensions because the child may be larger. we need to - // avoid sending details about a child but not the parent. the parent's queryAACube should have - // been adjusted to encompass the queryAACube of the child. - AABox entityBounds = entity->hasChildren() ? AABox(entityCube) : entity->getAABox(success); - if (!success) { - // if this entity is a child of an avatar, the entity-server wont be able to determine its - // AABox. If this happens, fall back to the queryAACube. - entityBounds = AABox(entityCube); - } - auto renderAccuracy = calculateRenderAccuracy(params.viewFrustum.getPosition(), - entityBounds, - params.octreeElementSizeScale, - params.boundaryLevelAdjust); - if (renderAccuracy <= 0.0f) { - includeThisEntity = false; // too small, don't include it - - #ifdef WANT_LOD_DEBUGGING - qCDebug(entities) << "skipping entity - TOO SMALL - \n" - << "......id:" << entity->getID() << "\n" - << "....name:" << entity->getName() << "\n" - << "..bounds:" << entityBounds << "\n" - << "....cell:" << getAACube(); - #endif - } - } - } - - if (includeThisEntity) { - #ifdef WANT_LOD_DEBUGGING - qCDebug(entities) << "including entity - \n" - << "......id:" << entity->getID() << "\n" - << "....name:" << entity->getName() << "\n" - << "....cell:" << getAACube(); - #endif - indexesOfEntitiesToInclude << i; - numberOfEntities++; - } else { - // if the extra data included this entity, and we've decided to not include the entity, then - // we can treat it as if it was completed. - entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); - } - } - } - - numberOfEntitiesOffset = packetData->getUncompressedByteOffset(); - bool successAppendEntityCount = packetData->appendValue(numberOfEntities); - - if (successAppendEntityCount) { - foreach(uint16_t i, indexesOfEntitiesToInclude) { - EntityItemPointer entity = _entityItems[i]; - LevelDetails entityLevel = packetData->startLevel(); - OctreeElement::AppendState appendEntityState = entity->appendEntityData(packetData, - params, entityTreeElementExtraEncodeData); - - // If none of this entity data was able to be appended, then discard it - // and don't include it in our entity count - if (appendEntityState == OctreeElement::NONE) { - packetData->discardLevel(entityLevel); - } else { - // If either ALL or some of it got appended, then end the level (commit it) - // and include the entity in our final count of entities - packetData->endLevel(entityLevel); - actualNumberOfEntities++; - - // If the entity item got completely appended, then we can remove it from the extra encode data - if (appendEntityState == OctreeElement::COMPLETED) { - entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); - } - } - - // If any part of the entity items didn't fit, then the element is considered partial - // NOTE: if the entity item didn't fit or only partially fit, then the entity item should have - // added itself to the extra encode data. - if (appendEntityState != OctreeElement::COMPLETED) { - appendElementState = OctreeElement::PARTIAL; - } - } - } else { - // we we couldn't add the entity count, then we couldn't add anything for this element and we're in a NONE state - appendElementState = OctreeElement::NONE; - } - }); - - // If we were provided with extraEncodeData, and we allocated and/or got entityTreeElementExtraEncodeData - // then we need to do some additional processing, namely make sure our extraEncodeData is up to date for - // this octree element. - if (extraEncodeData && entityTreeElementExtraEncodeData) { - - // After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data. - // Only our parent can remove our extra data in these cases and only after it knows that all of its - // children have been encoded. - // - // FIXME -- this comment seems wrong.... - // - // If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data - // since that will signal that the entire element needs to be encoded on the next attempt - if (appendElementState == OctreeElement::NONE) { - - if (!entityTreeElementExtraEncodeData->elementCompleted && entityTreeElementExtraEncodeData->entities.size() == 0) { - // TODO: we used to delete the extra encode data here. But changing the logic around - // this is now a dead code branch. Clean this up! - } else { - // TODO: some of these inserts might be redundant!!! - extraEncodeData->insert(this, entityTreeElementExtraEncodeData); - } - } else { - - // If we weren't previously completed, check to see if we are - if (!entityTreeElementExtraEncodeData->elementCompleted) { - // If all of our items have been encoded, then we are complete as an element. - if (entityTreeElementExtraEncodeData->entities.size() == 0) { - entityTreeElementExtraEncodeData->elementCompleted = true; - } - } - - // TODO: some of these inserts might be redundant!!! - extraEncodeData->insert(this, entityTreeElementExtraEncodeData); - } - } - - // Determine if no entities at all were able to fit - bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0); - - // If we wrote fewer entities than we expected, update the number of entities in our packet - bool successUpdateEntityCount = true; - if (numberOfEntities != actualNumberOfEntities) { - successUpdateEntityCount = packetData->updatePriorBytes(numberOfEntitiesOffset, - (const unsigned char*)&actualNumberOfEntities, sizeof(actualNumberOfEntities)); - } - - // If we weren't able to update our entity count, or we couldn't fit any entities, then - // we should discard our element and return a result of NONE - if (!successUpdateEntityCount) { - packetData->discardLevel(elementLevel); - appendElementState = OctreeElement::NONE; - } else { - if (noEntitiesFit) { - //appendElementState = OctreeElement::PARTIAL; - packetData->discardLevel(elementLevel); - appendElementState = OctreeElement::NONE; - } else { - packetData->endLevel(elementLevel); - } - } - return appendElementState; -} - bool EntityTreeElement::containsEntityBounds(EntityItemPointer entity) const { bool success; auto queryCube = entity->getQueryAACube(success); diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index a56af5d03f..76e1e40812 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -121,17 +121,6 @@ public: virtual bool requiresSplit() const override { return false; } virtual void debugExtraEncodeData(EncodeBitstreamParams& params) const override; - virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) override; - virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const override; - virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const override; - virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const override; - virtual void elementEncodeComplete(EncodeBitstreamParams& params) const override; - - bool alreadyFullyEncoded(EncodeBitstreamParams& params) const; - - /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual OctreeElement::AppendState appendElementData(OctreePacketData* packetData, - EncodeBitstreamParams& params) const override; /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 3f7fc5f799..f0fbb20f98 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -186,7 +186,6 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_IS_SPOTLIGHT; diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 00196d7b23..92a1c25970 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -128,7 +128,6 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 375453e0e9..84f9acf5f5 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -26,7 +26,6 @@ class LineEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index 6c040296a3..489ba5772c 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -89,8 +89,6 @@ int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_MATERIAL_URL; diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index 969eb577ff..30743850dd 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -32,7 +32,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index bec7bc1906..be62664ff9 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -144,7 +144,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); @@ -721,4 +720,4 @@ bool ModelEntityItem::isAnimatingSomething() const { _animationProperties.getRunning() && (_animationProperties.getFPS() != 0.0f); }); -} \ No newline at end of file +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index c2109ba51f..327606ae2f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -30,7 +30,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 7d27011c56..d9ef5e2178 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -503,8 +503,6 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index 498d13058e..420c570e8d 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -216,8 +216,6 @@ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 2dc8befe97..871c451c50 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -26,7 +26,6 @@ class PolyLineEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 84ce83d3a1..ed3372818a 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -183,8 +183,6 @@ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* dat return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_VOXEL_VOLUME_SIZE; diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 0ddfe3e8cc..4dfe7b9535 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -26,7 +26,6 @@ class PolyVoxEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 99f18d2dac..520d892682 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -188,8 +188,6 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SHAPE; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 7030a95562..97080d3ca2 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -98,8 +98,6 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_TEXT; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 06b377ee14..efdc84bcd8 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -30,7 +30,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 548bca3225..f3159ba3f8 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -83,8 +83,6 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SOURCE_URL; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index dab7cd5e22..1179f22ded 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -29,7 +29,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 4ae020f966..b07d0597bc 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -191,8 +191,6 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 2c6b01fc69..3a9c7cb1e6 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -33,7 +33,6 @@ public: virtual bool setProperties(const EntityItemProperties& properties) override; virtual bool setSubClassProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index dafdfd5bf4..43883f68f3 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -45,7 +45,6 @@ #include "Octree.h" #include "OctreeConstants.h" -#include "OctreeElementBag.h" #include "OctreeLogging.h" #include "OctreeQueryNode.h" #include "OctreeUtils.h" @@ -57,7 +56,6 @@ Octree::Octree(bool shouldReaverage) : _rootElement(NULL), _isDirty(true), _shouldReaverage(shouldReaverage), - _stopImport(false), _isViewing(false), _isServer(false) { @@ -490,131 +488,6 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, uint64_t buffe // skip bitstream to new startPoint bitstreamAt += theseBytesRead; bytesRead += theseBytesRead; - - if (args.wantImportProgress) { - emit importProgress((100 * (bitstreamAt - bitstream)) / bufferSizeBytes); - } - } -} - -void Octree::deleteOctreeElementAt(float x, float y, float z, float s) { - unsigned char* octalCode = pointToOctalCode(x,y,z,s); - deleteOctalCodeFromTree(octalCode); - delete[] octalCode; // cleanup memory -} - -class DeleteOctalCodeFromTreeArgs { -public: - bool collapseEmptyTrees; - const unsigned char* codeBuffer; - int lengthOfCode; - bool deleteLastChild; - bool pathChanged; -}; - -// Note: uses the codeColorBuffer format, but the color's are ignored, because -// this only finds and deletes the element from the tree. -void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees) { - // recurse the tree while decoding the codeBuffer, once you find the element in question, recurse - // back and implement color reaveraging, and marking of lastChanged - DeleteOctalCodeFromTreeArgs args; - args.collapseEmptyTrees = collapseEmptyTrees; - args.codeBuffer = codeBuffer; - args.lengthOfCode = numberOfThreeBitSectionsInCode(codeBuffer); - args.deleteLastChild = false; - args.pathChanged = false; - - withWriteLock([&] { - deleteOctalCodeFromTreeRecursion(_rootElement, &args); - }); -} - -void Octree::deleteOctalCodeFromTreeRecursion(const OctreeElementPointer& element, void* extraData) { - DeleteOctalCodeFromTreeArgs* args = (DeleteOctalCodeFromTreeArgs*)extraData; - - int lengthOfElementCode = numberOfThreeBitSectionsInCode(element->getOctalCode()); - - // Since we traverse the tree in code order, we know that if our code - // matches, then we've reached our target element. - if (lengthOfElementCode == args->lengthOfCode) { - // we've reached our target, depending on how we're called we may be able to operate on it - // it here, we need to recurse up, and delete it there. So we handle these cases the same to keep - // the logic consistent. - args->deleteLastChild = true; - return; - } - - // Ok, we know we haven't reached our target element yet, so keep looking - int childIndex = branchIndexWithDescendant(element->getOctalCode(), args->codeBuffer); - OctreeElementPointer childElement = element->getChildAtIndex(childIndex); - - // If there is no child at the target location, and the current parent element is a colored leaf, - // then it means we were asked to delete a child out of a larger leaf voxel. - // We support this by breaking up the parent voxel into smaller pieces. - if (!childElement && element->requiresSplit()) { - // we need to break up ancestors until we get to the right level - OctreeElementPointer ancestorElement = element; - while (true) { - int index = branchIndexWithDescendant(ancestorElement->getOctalCode(), args->codeBuffer); - - // we end up with all the children, even the one we want to delete - ancestorElement->splitChildren(); - - int lengthOfAncestorElement = numberOfThreeBitSectionsInCode(ancestorElement->getOctalCode()); - - // If we've reached the parent of the target, then stop breaking up children - if (lengthOfAncestorElement == (args->lengthOfCode - 1)) { - - // since we created all the children when we split, we need to delete this target one - ancestorElement->deleteChildAtIndex(index); - break; - } - ancestorElement = ancestorElement->getChildAtIndex(index); - } - _isDirty = true; - args->pathChanged = true; - - // ends recursion, unwinds up stack - return; - } - - // if we don't have a child and we reach this point, then we actually know that the parent - // isn't a colored leaf, and the child branch doesn't exist, so there's nothing to do below and - // we can safely return, ending the recursion and unwinding - if (!childElement) { - return; - } - - // If we got this far then we have a child for the branch we're looking for, but we're not there yet - // recurse till we get there - deleteOctalCodeFromTreeRecursion(childElement, args); - - // If the lower level determined it needs to be deleted, then we should delete now. - if (args->deleteLastChild) { - element->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this element - - // track our tree dirtiness - _isDirty = true; - - // track that path has changed - args->pathChanged = true; - - // If we're in collapseEmptyTrees mode, and this was the last child of this element, then we also want - // to delete this element. This will collapse the empty tree above us. - if (args->collapseEmptyTrees && element->getChildCount() == 0) { - // Can't delete the root this way. - if (element == _rootElement) { - args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything - } - } else { - args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything - } - } - - // If the lower level did some work, then we need to let this element know, so it can - // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc - if (args->pathChanged) { - element->handleSubtreeChanged(shared_from_this()); } } @@ -883,720 +756,6 @@ OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Oc return args.element; } - - -int Octree::encodeTreeBitstream(const OctreeElementPointer& element, - OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params) { - - // How many bytes have we written so far at this level; - int bytesWritten = 0; - - // you can't call this without a valid element - if (!element) { - qCDebug(octree, "WARNING! encodeTreeBitstream() called with element=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE; - return bytesWritten; - } - - // you can't call this without a valid nodeData - auto octreeQueryNode = static_cast(params.nodeData); - if (!octreeQueryNode) { - qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA; - return bytesWritten; - } - - // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! - if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything && !element->isInView(params.viewFrustum)) { - params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; - return bytesWritten; - } - - // write the octal code - bool roomForOctalCode = false; // assume the worst - int codeLength = 1; // assume root - if (params.chopLevels) { - unsigned char* newCode = chopOctalCode(element->getOctalCode(), params.chopLevels); - roomForOctalCode = packetData->startSubTree(newCode); - - if (newCode) { - codeLength = numberOfThreeBitSectionsInCode(newCode); - delete[] newCode; - } else { - codeLength = 1; - } - } else { - roomForOctalCode = packetData->startSubTree(element->getOctalCode()); - codeLength = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(element->getOctalCode())); - } - - // If the octalcode couldn't fit, then we can return, because no nodes below us will fit... - if (!roomForOctalCode) { - bag.insert(element); - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - return bytesWritten; - } - - bytesWritten += codeLength; // keep track of byte count - - int currentEncodeLevel = 0; - - // record some stats, this is the one element that we won't record below in the recursion function, so we need to - // track it here - octreeQueryNode->stats.traversed(element); - - ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully - - int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params, - currentEncodeLevel, parentLocationThisView); - - // if childBytesWritten == 1 then something went wrong... that's not possible - assert(childBytesWritten != 1); - - // if childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some - // reason couldn't be written... so reset them here... This isn't true for the non-color included case - if (suppressEmptySubtrees() && childBytesWritten == 2) { - childBytesWritten = 0; - //params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT... - } - - // if we wrote child bytes, then return our result of all bytes written - if (childBytesWritten) { - bytesWritten += childBytesWritten; - } else { - // otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code - bytesWritten = 0; - //params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - if (bytesWritten == 0) { - packetData->discardSubTree(); - } else { - packetData->endSubTree(); - } - - return bytesWritten; -} - -int Octree::encodeTreeBitstreamRecursion(const OctreeElementPointer& element, - OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params, int& currentEncodeLevel, - const ViewFrustum::intersection& parentLocationThisView) const { - - - const bool wantDebug = false; - - // The append state of this level/element. - OctreeElement::AppendState elementAppendState = OctreeElement::COMPLETED; // assume the best - - // How many bytes have we written so far at this level; - int bytesAtThisLevel = 0; - - // you can't call this without a valid element - if (!element) { - qCDebug(octree, "WARNING! encodeTreeBitstreamRecursion() called with element=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE; - return bytesAtThisLevel; - } - - // you can't call this without a valid nodeData - auto octreeQueryNode = static_cast(params.nodeData); - if (!octreeQueryNode) { - qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA; - return bytesAtThisLevel; - } - - - // Keep track of how deep we've encoded. - currentEncodeLevel++; - - params.maxLevelReached = std::max(currentEncodeLevel, params.maxLevelReached); - - // If we've reached our max Search Level, then stop searching. - if (currentEncodeLevel >= params.maxEncodeLevel) { - params.stopReason = EncodeBitstreamParams::TOO_DEEP; - return bytesAtThisLevel; - } - - ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside - if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything) { - float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); - - // If we're too far away for our render level, then just return - if (element->distanceToCamera(params.viewFrustum) >= boundaryDistance) { - octreeQueryNode->stats.skippedDistance(element); - params.stopReason = EncodeBitstreamParams::LOD_SKIP; - return bytesAtThisLevel; - } - - // if the parent isn't known to be INSIDE, then it must be INTERSECT, and we should double check to see - // if we are INSIDE, INTERSECT, or OUTSIDE - if (parentLocationThisView != ViewFrustum::INSIDE) { - assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE! - nodeLocationThisView = element->computeViewIntersection(params.viewFrustum); - } - - // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! - // although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if - // we're out of view - if (nodeLocationThisView == ViewFrustum::OUTSIDE) { - octreeQueryNode->stats.skippedOutOfView(element); - params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; - return bytesAtThisLevel; - } - - // Ok, we are in view, but if we're in delta mode, then we also want to make sure we weren't already in view - // because we don't send nodes from the previously know in view frustum. - bool wasInView = false; - - if (params.deltaView) { - ViewFrustum::intersection location = element->computeViewIntersection(params.lastViewFrustum); - - // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (element->isLeaf()) { - wasInView = location != ViewFrustum::OUTSIDE; - } else { - wasInView = location == ViewFrustum::INSIDE; - } - - // If we were in view, double check that we didn't switch LOD visibility... namely, the was in view doesn't - // tell us if it was so small we wouldn't have rendered it. Which may be the case. And we may have moved closer - // to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it - // as "was in view"... - if (wasInView) { - float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); - if (element->distanceToCamera(params.lastViewFrustum) >= boundaryDistance) { - // This would have been invisible... but now should be visible (we wouldn't be here otherwise)... - wasInView = false; - } - } - } - - // If we were previously in the view, then we normally will return out of here and stop recursing. But - // if we're in deltaView mode, and this element has changed since it was last sent, then we do - // need to send it. - if (wasInView && !(params.deltaView && element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))) { - octreeQueryNode->stats.skippedWasInView(element); - params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW; - return bytesAtThisLevel; - } - } - - // If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed, - // then we can also bail early and save bits - if (!params.forceSendScene && !params.deltaView && - !element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE)) { - - octreeQueryNode->stats.skippedNoChange(element); - - params.stopReason = EncodeBitstreamParams::NO_CHANGE; - return bytesAtThisLevel; - } - - bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more! - - // At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level - // is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees. - // There could be sub trees below this point, which might take many more bytes, but that's ok, because we can - // always mark our subtrees as not existing and stop the packet at this point, then start up with a new packet - // for the remaining sub trees. - unsigned char childrenExistInTreeBits = 0; - unsigned char childrenExistInPacketBits = 0; - unsigned char childrenDataBits = 0; - - // Make our local buffer large enough to handle writing at this level in case we need to. - LevelDetails thisLevelKey = packetData->startLevel(); - int requiredBytes = sizeof(childrenDataBits) + sizeof(childrenExistInPacketBits); - if (params.includeExistsBits) { - requiredBytes += sizeof(childrenExistInTreeBits); - } - - // If this datatype allows root elements to include data, and this is the root, then ask the tree for the - // minimum bytes needed for root data and reserve those also - if (element == _rootElement && rootElementHasData()) { - requiredBytes += minimumRequiredRootDataBytes(); - } - - bool continueThisLevel = packetData->reserveBytes(requiredBytes); - - // If we can't reserve our minimum bytes then we can discard this level and return as if none of this level fits - if (!continueThisLevel) { - packetData->discardLevel(thisLevelKey); - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - bag.insert(element); - return bytesAtThisLevel; - } - - int inViewCount = 0; - int inViewNotLeafCount = 0; - int inViewWithColorCount = 0; - - OctreeElementPointer sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; - float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElementPointer childElement = element->getChildAtIndex(i); - - // if the caller wants to include childExistsBits, then include them - if (params.includeExistsBits && childElement) { - childrenExistInTreeBits += (1 << (7 - i)); - } - - sortedChildren[i] = childElement; - indexOfChildren[i] = i; - distancesToChildren[i] = 0.0f; - - // track stats - // must check childElement here, because it could be we got here with no childElement - if (childElement) { - octreeQueryNode->stats.traversed(childElement); - } - } - - // for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so - // add them to our distance ordered array of children - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElementPointer childElement = sortedChildren[i]; - int originalIndex = indexOfChildren[i]; - - bool childIsInView = (childElement && - (params.recurseEverything || !octreeQueryNode->getUsesFrustum() || - (nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are - (nodeLocationThisView == ViewFrustum::INTERSECT && - childElement->isInView(params.viewFrustum)) // the parent intersects and the child is in view - )); - - if (!childIsInView) { - // must check childElement here, because it could be we got here because there was no childElement - if (childElement) { - octreeQueryNode->stats.skippedOutOfView(childElement); - } - } else { - // Before we consider this further, let's see if it's in our LOD scope... - float boundaryDistance = params.recurseEverything || !octreeQueryNode->getUsesFrustum() ? 1 : - boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); - - if (!(distancesToChildren[i] < boundaryDistance)) { - // don't need to check childElement here, because we can't get here with no childElement - octreeQueryNode->stats.skippedDistance(childElement); - } else { - inViewCount++; - - // track children in view as existing and not a leaf, if they're a leaf, - // we don't care about recursing deeper on them, and we don't consider their - // subtree to exist - if (!(childElement && childElement->isLeaf())) { - childrenExistInPacketBits += (1 << (7 - originalIndex)); - inViewNotLeafCount++; - } - - bool childIsOccluded = false; // assume it's not occluded - - bool shouldRender = params.recurseEverything || !octreeQueryNode->getUsesFrustum() || - childElement->calculateShouldRender(params.viewFrustum, - params.octreeElementSizeScale, params.boundaryLevelAdjust); - - // track some stats - // don't need to check childElement here, because we can't get here with no childElement - if (!shouldRender && childElement->isLeaf()) { - octreeQueryNode->stats.skippedDistance(childElement); - } - // don't need to check childElement here, because we can't get here with no childElement - if (childIsOccluded) { - octreeQueryNode->stats.skippedOccluded(childElement); - } - - // track children with actual color, only if the child wasn't previously in view! - if (shouldRender && !childIsOccluded) { - bool childWasInView = false; - - if (childElement && params.deltaView) { - ViewFrustum::intersection location = childElement->computeViewIntersection(params.lastViewFrustum); - - // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (childElement->isLeaf()) { - childWasInView = location != ViewFrustum::OUTSIDE; - } else { - childWasInView = location == ViewFrustum::INSIDE; - } - } - - // If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items. - // Or if we were previously in the view, but this element has changed since it was last sent, then we do - // need to send it. - if (!childWasInView || - (params.deltaView && - childElement->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))){ - - childrenDataBits += (1 << (7 - originalIndex)); - inViewWithColorCount++; - } else { - // otherwise just track stats of the items we discarded - // don't need to check childElement here, because we can't get here with no childElement - if (childWasInView) { - octreeQueryNode->stats.skippedWasInView(childElement); - } else { - octreeQueryNode->stats.skippedNoChange(childElement); - } - } - } - } - } - } - - // NOTE: the childrenDataBits indicates that there is an array of child element data included in this packet. - // We will write this bit mask but we may come back later and update the bits that are actually included - packetData->releaseReservedBytes(sizeof(childrenDataBits)); - continueThisLevel = packetData->appendBitMask(childrenDataBits); - - int childDataBitsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenDataBits)); - unsigned char actualChildrenDataBits = 0; - - assert(continueThisLevel); // since we used reserved bits, this really shouldn't fail - bytesAtThisLevel += sizeof(childrenDataBits); // keep track of byte count - - octreeQueryNode->stats.colorBitsWritten(); // really data bits not just color bits - - // NOW might be a good time to give our tree subclass and this element a chance to set up and check any extra encode data - element->initializeExtraEncodeData(params); - - // write the child element data... - // NOTE: the format of the bitstream is generally this: - // [octalcode] - // [bitmask for existence of child data] - // N x [child data] - // [bitmask for existence of child elements in tree] - // [bitmask for existence of child elements in buffer] - // N x [ ... tree for children ...] - // - // This section of the code, is writing the "N x [child data]" portion of this bitstream - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (oneAtBit(childrenDataBits, i)) { - OctreeElementPointer childElement = element->getChildAtIndex(i); - - // the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already - // processed and sent the data bits for. Let our tree subclass determine if it really wants to send the - // data for this child at this point - if (childElement && element->shouldIncludeChildData(i, params)) { - - int bytesBeforeChild = packetData->getUncompressedSize(); - - // a childElement may "partially" write it's data. for example, the model server where the entire - // contents of the element may be larger than can fit in a single MTU/packetData. In this case, - // we want to allow the appendElementData() to respond that it produced partial data, which should be - // written, but that the childElement needs to be reprocessed in an additional pass or passes - // to be completed. - LevelDetails childDataLevelKey = packetData->startLevel(); - - OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params); - - // allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state - element->updateEncodedData(i, childAppendState, params); - - // Continue this level so long as some part of this child element was appended. - bool childFit = (childAppendState != OctreeElement::NONE); - - // some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit - // the data type wants to bail on this element level completely - if (!childFit && mustIncludeAllChildData()) { - continueThisLevel = false; - break; - } - - // If the child was partially or fully appended, then mark the actualChildrenDataBits as including - // this child data - if (childFit) { - actualChildrenDataBits += (1 << (7 - i)); - continueThisLevel = packetData->endLevel(childDataLevelKey); - } else { - packetData->discardLevel(childDataLevelKey); - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - // If this child was partially appended, then consider this element to be partially appended - if (childAppendState == OctreeElement::PARTIAL) { - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - int bytesAfterChild = packetData->getUncompressedSize(); - - bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child - - // don't need to check childElement here, because we can't get here with no childElement - if (childAppendState != OctreeElement::NONE) { - octreeQueryNode->stats.colorSent(childElement); - } - } - } - } - - if (!mustIncludeAllChildData() && !continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: reached end of child element data loop with continueThisLevel=FALSE"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - - if (continueThisLevel && actualChildrenDataBits != childrenDataBits) { - // repair the child data mask - continueThisLevel = packetData->updatePriorBitMask(childDataBitsPlaceHolder, actualChildrenDataBits); - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childDataBitsPlaceHolder"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // if the caller wants to include childExistsBits, then include them even if not in view, put them before the - // childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits - if (continueThisLevel && params.includeExistsBits) { - packetData->releaseReservedBytes(sizeof(childrenExistInTreeBits)); - continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits); - if (continueThisLevel) { - bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count - - octreeQueryNode->stats.existsBitsWritten(); - } else { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInTreeBits"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // write the child exist bits - if (continueThisLevel) { - packetData->releaseReservedBytes(sizeof(childrenExistInPacketBits)); - continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits); - if (continueThisLevel) { - bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count - - octreeQueryNode->stats.existsInPacketBitsWritten(); - } else { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInPacketBits"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // We only need to keep digging, if there is at least one child that is inView, and not a leaf. - keepDiggingDeeper = (inViewNotLeafCount > 0); - - // - // NOTE: the format of the bitstream is generally this: - // [octalcode] - // [bitmask for existence of child data] - // N x [child data] - // [bitmask for existence of child elements in tree] - // [bitmask for existence of child elements in buffer] - // N x [ ... tree for children ...] - // - // This section of the code, is writing the "N x [ ... tree for children ...]" portion of this bitstream - // - if (continueThisLevel && keepDiggingDeeper) { - - // at this point, we need to iterate the children who are in view, even if not colored - // and we need to determine if there's a deeper tree below them that we care about. - // - // Since this recursive function assumes we're already writing, we know we've already written our - // childrenExistInPacketBits. But... we don't really know how big the child tree will be. And we don't know if - // we'll have room in our buffer to actually write all these child trees. What we kinda would like to do is - // write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they - // write something, we keep them in the bits, if they don't, we take them out. - // - // we know the last thing we wrote to the packet was our childrenExistInPacketBits. Let's remember where that was! - int childExistsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenExistInPacketBits)); - - // we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the - // final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes, - // and then later reshuffle these sections of our output buffer back into normal order. This allows us to make - // a single recursive pass in distance sorted order, but retain standard order in our encoded packet - - // for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so - // add them to our distance ordered array of children - for (int indexByDistance = 0; indexByDistance < NUMBER_OF_CHILDREN; indexByDistance++) { - OctreeElementPointer childElement = sortedChildren[indexByDistance]; - int originalIndex = indexOfChildren[indexByDistance]; - - if (oneAtBit(childrenExistInPacketBits, originalIndex)) { - - int thisLevel = currentEncodeLevel; - - int childTreeBytesOut = 0; - - // NOTE: some octree styles (like models and particles) will store content in parent elements, and child - // elements. In this case, if we stop recursion when we include any data (the colorbits should really be - // called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep - // recursing, by returning TRUE in recurseChildrenWithData(). - - if (params.recurseEverything || !octreeQueryNode->getUsesFrustum() - || recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) { - - // Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this - // will be true. But if the tree has already been encoded, we will skip this. - if (element->shouldRecurseChildTree(originalIndex, params)) { - childTreeBytesOut = encodeTreeBitstreamRecursion(childElement, packetData, bag, params, - thisLevel, nodeLocationThisView); - } else { - childTreeBytesOut = 0; - } - } - - // if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space, - // basically, the children below don't contain any info. - - // if the child tree wrote 1 byte??? something must have gone wrong... because it must have at least the color - // byte and the child exist byte. - // - assert(childTreeBytesOut != 1); - - // if the child tree wrote just 2 bytes, then it means: it had no colors and no child nodes, because... - // if it had colors it would write 1 byte for the color mask, - // and at least a color's worth of bytes for the element of colors. - // if it had child trees (with something in them) then it would have the 1 byte for child mask - // and some number of bytes of lower children... - // so, if the child returns 2 bytes out, we can actually consider that an empty tree also!! - // - // we can make this act like no bytes out, by just resetting the bytes out in this case - if (suppressEmptySubtrees() && !params.includeExistsBits && childTreeBytesOut == 2) { - childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees - - } - - bytesAtThisLevel += childTreeBytesOut; - - // If we had previously started writing, and if the child DIDN'T write any bytes, - // then we want to remove their bit from the childExistsPlaceHolder bitmask - if (childTreeBytesOut == 0) { - - // remove this child's bit... - childrenExistInPacketBits -= (1 << (7 - originalIndex)); - - // repair the child exists mask - continueThisLevel = packetData->updatePriorBitMask(childExistsPlaceHolder, childrenExistInPacketBits); - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childExistsPlaceHolder"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - - // If this is the last of the child exists bits, then we're actually be rolling out the entire tree - if (childrenExistInPacketBits == 0) { - octreeQueryNode->stats.childBitsRemoved(params.includeExistsBits); - } - - if (!continueThisLevel) { - if (wantDebug) { - qCDebug(octree) << " WARNING line:" << __LINE__; - qCDebug(octree) << " breaking the child recursion loop with continueThisLevel=false!!!"; - qCDebug(octree) << " AFTER attempting to updatePriorBitMask() for empty sub tree...."; - qCDebug(octree) << " IS THIS ACCEPTABLE!!!!"; - } - break; // can't continue... - } - - // Note: no need to move the pointer, cause we already stored this - } // end if (childTreeBytesOut == 0) - } // end if (oneAtBit(childrenExistInPacketBits, originalIndex)) - } // end for - } // end keepDiggingDeeper - - // If we made it this far, then we've written all of our child data... if this element is the root - // element, then we also allow the root element to write out it's data... - if (continueThisLevel && element == _rootElement && rootElementHasData()) { - int bytesBeforeChild = packetData->getUncompressedSize(); - - // release the bytes we reserved... - packetData->releaseReservedBytes(minimumRequiredRootDataBytes()); - - LevelDetails rootDataLevelKey = packetData->startLevel(); - OctreeElement::AppendState rootAppendState = element->appendElementData(packetData, params); - - bool partOfRootFit = (rootAppendState != OctreeElement::NONE); - bool allOfRootFit = (rootAppendState == OctreeElement::COMPLETED); - - if (partOfRootFit) { - continueThisLevel = packetData->endLevel(rootDataLevelKey); - if (!continueThisLevel) { - qCDebug(octree) << " UNEXPECTED ROOT ELEMENT -- could not packetData->endLevel(rootDataLevelKey) -- line:" << __LINE__; - } - } else { - packetData->discardLevel(rootDataLevelKey); - } - - if (!allOfRootFit) { - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - // do we really ever NOT want to continue this level??? - //continueThisLevel = (rootAppendState == OctreeElement::COMPLETED); - - int bytesAfterChild = packetData->getUncompressedSize(); - - if (continueThisLevel) { - bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child - - octreeQueryNode->stats.colorSent(element); - } - - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in packing ROOT data"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // if we were unable to fit this level in our packet, then rewind and add it to the element bag for - // sending later... - if (continueThisLevel) { - continueThisLevel = packetData->endLevel(thisLevelKey); - } else { - packetData->discardLevel(thisLevelKey); - - if (!mustIncludeAllChildData()) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in attempting to pack this element"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // This happens if the element could not be written at all. In the case of Octree's that support partial - // element data, continueThisLevel will be true. So this only happens if the full element needs to be - // added back to the element bag. - if (!continueThisLevel) { - if (!mustIncludeAllChildData()) { - qCDebug(octree) << "WARNING UNEXPECTED CASE - Something failed in attempting to pack this element."; - qCDebug(octree) << " If the datatype requires all child data, then this might happen. Otherwise" ; - qCDebug(octree) << " this is an unexpected case and we should research a potential logic error." ; - } - - bag.insert(element); - - // don't need to check element here, because we can't get here with no element - octreeQueryNode->stats.didntFit(element); - - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - bytesAtThisLevel = 0; // didn't fit - } else { - - // assuming we made it here with continueThisLevel == true, we STILL might want - // to add our element back to the bag for additional encoding, specifically if - // the appendState is PARTIAL, in this case, we re-add our element to the bag - // and assume that the appendElementData() has stored any required state data - // in the params extraEncodeData - if (elementAppendState == OctreeElement::PARTIAL) { - bag.insert(element); - } - } - - // If our element is completed let the element know so it can do any cleanup it of extra wants - if (elementAppendState == OctreeElement::COMPLETED) { - element->elementEncodeComplete(params); - } - - return bytesAtThisLevel; -} - bool Octree::readFromFile(const char* fileName) { QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS); @@ -1615,14 +774,10 @@ bool Octree::readFromFile(const char* fileName) { QFileInfo fileInfo(qFileName); uint64_t fileLength = fileInfo.size(); - emit importSize(1.0f, 1.0f, 1.0f); - emit importProgress(0); - qCDebug(octree) << "Loading file" << qFileName << "..."; bool success = readFromStream(fileLength, fileInputStream); - emit importProgress(100); file.close(); return success; @@ -1863,7 +1018,3 @@ bool Octree::countOctreeElementsOperation(const OctreeElementPointer& element, v (*(uint64_t*)extraData)++; return true; // keep going } - -void Octree::cancelImport() { - _stopImport = true; -} diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index c4c4508138..a2ad834e18 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -49,126 +49,62 @@ public: // Callback function, for recuseTreeWithOperation using RecurseOctreeOperation = std::function; -typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; typedef QHash CubeList; const bool NO_EXISTS_BITS = false; const bool WANT_EXISTS_BITS = true; -const bool COLLAPSE_EMPTY_TREE = true; -const bool DONT_COLLAPSE = false; -const int DONT_CHOP = 0; const int NO_BOUNDARY_ADJUST = 0; const int LOW_RES_MOVING_ADJUST = 1; -#define IGNORE_COVERAGE_MAP NULL - class EncodeBitstreamParams { public: ViewFrustum viewFrustum; - ViewFrustum lastViewFrustum; - int maxEncodeLevel; - int maxLevelReached; bool includeExistsBits; - int chopLevels; - bool deltaView; - bool recurseEverything { false }; - int boundaryLevelAdjust; - float octreeElementSizeScale; - bool forceSendScene; NodeData* nodeData; // output hints from the encode process typedef enum { UNKNOWN, DIDNT_FIT, - NULL_NODE, - NULL_NODE_DATA, - TOO_DEEP, - LOD_SKIP, - OUT_OF_VIEW, - WAS_IN_VIEW, - NO_CHANGE, - OCCLUDED, FINISHED } reason; reason stopReason; - EncodeBitstreamParams( - int maxEncodeLevel = INT_MAX, - bool includeExistsBits = WANT_EXISTS_BITS, - int chopLevels = 0, - bool useDeltaView = false, - int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, - float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE, - bool forceSendScene = true, - NodeData* nodeData = nullptr) : - maxEncodeLevel(maxEncodeLevel), - maxLevelReached(0), + EncodeBitstreamParams(bool includeExistsBits = WANT_EXISTS_BITS, + NodeData* nodeData = nullptr) : includeExistsBits(includeExistsBits), - chopLevels(chopLevels), - deltaView(useDeltaView), - boundaryLevelAdjust(boundaryLevelAdjust), - octreeElementSizeScale(octreeElementSizeScale), - forceSendScene(forceSendScene), nodeData(nodeData), stopReason(UNKNOWN) { - lastViewFrustum.invalidate(); } void displayStopReason() { printf("StopReason: "); switch (stopReason) { - default: case UNKNOWN: qDebug("UNKNOWN"); break; - case DIDNT_FIT: qDebug("DIDNT_FIT"); break; - case NULL_NODE: qDebug("NULL_NODE"); break; - case TOO_DEEP: qDebug("TOO_DEEP"); break; - case LOD_SKIP: qDebug("LOD_SKIP"); break; - case OUT_OF_VIEW: qDebug("OUT_OF_VIEW"); break; - case WAS_IN_VIEW: qDebug("WAS_IN_VIEW"); break; - case NO_CHANGE: qDebug("NO_CHANGE"); break; - case OCCLUDED: qDebug("OCCLUDED"); break; + case FINISHED: qDebug("FINISHED"); break; } } QString getStopReason() { switch (stopReason) { - default: case UNKNOWN: return QString("UNKNOWN"); break; - case DIDNT_FIT: return QString("DIDNT_FIT"); break; - case NULL_NODE: return QString("NULL_NODE"); break; - case TOO_DEEP: return QString("TOO_DEEP"); break; - case LOD_SKIP: return QString("LOD_SKIP"); break; - case OUT_OF_VIEW: return QString("OUT_OF_VIEW"); break; - case WAS_IN_VIEW: return QString("WAS_IN_VIEW"); break; - case NO_CHANGE: return QString("NO_CHANGE"); break; - case OCCLUDED: return QString("OCCLUDED"); break; + case FINISHED: return QString("FINISHED"); break; } } std::function trackSend { [](const QUuid&, quint64){} }; }; -class ReadElementBufferToTreeArgs { -public: - const unsigned char* buffer; - int length; - bool destructive; - bool pathChanged; -}; - class ReadBitstreamToTreeParams { public: bool includeExistsBits; OctreeElementPointer destinationElement; QUuid sourceUUID; SharedNodePointer sourceNode; - bool wantImportProgress; - PacketVersion bitstreamVersion; int elementsPerPacket = 0; int entitiesPerPacket = 0; @@ -176,15 +112,11 @@ public: bool includeExistsBits = WANT_EXISTS_BITS, OctreeElementPointer destinationElement = NULL, QUuid sourceUUID = QUuid(), - SharedNodePointer sourceNode = SharedNodePointer(), - bool wantImportProgress = false, - PacketVersion bitstreamVersion = 0) : + SharedNodePointer sourceNode = SharedNodePointer()) : includeExistsBits(includeExistsBits), destinationElement(destinationElement), sourceUUID(sourceUUID), - sourceNode(sourceNode), - wantImportProgress(wantImportProgress), - bitstreamVersion(bitstreamVersion) + sourceNode(sourceNode) {} }; @@ -199,7 +131,6 @@ public: // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing - virtual bool getWantSVOfileVersions() const { return false; } virtual PacketType expectedDataPacketType() const { return PacketType::Unknown; } virtual PacketVersion expectedVersion() const { return versionForPacketType(expectedDataPacketType()); } virtual bool handlesEditPacketType(PacketType packetType) const { return false; } @@ -209,12 +140,8 @@ public: virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } - virtual bool recurseChildrenWithData() const { return true; } virtual bool rootElementHasData() const { return false; } - virtual int minimumRequiredRootDataBytes() const { return 0; } - virtual bool suppressEmptySubtrees() const { return true; } virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const { } - virtual bool mustIncludeAllChildData() const { return true; } virtual void update() { } // nothing to do by default @@ -223,11 +150,8 @@ public: virtual void eraseAllOctreeElements(bool createNewRoot = true); virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); - void deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE); void reaverageOctreeElements(OctreeElementPointer startElement = OctreeElementPointer()); - void deleteOctreeElementAt(float x, float y, float z, float s); - /// Find the voxel at position x,y,z,s /// \return pointer to the OctreeElement or NULL if none at x,y,z,s. OctreeElementPointer getOctreeElementAt(float x, float y, float z, float s) const; @@ -250,8 +174,6 @@ public: void recurseTreeWithOperator(RecurseOctreeOperator* operatorObject); - int encodeTreeBitstream(const OctreeElementPointer& element, OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params) ; bool isDirty() const { return _isDirty; } void clearDirtyBit() { _isDirty = false; } @@ -344,22 +266,10 @@ public: void incrementPersistDataVersion() { _persistDataVersion++; } -signals: - void importSize(float x, float y, float z); - void importProgress(int progress); - -public slots: - void cancelImport(); - protected: void deleteOctalCodeFromTreeRecursion(const OctreeElementPointer& element, void* extraData); - int encodeTreeBitstreamRecursion(const OctreeElementPointer& element, - OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params, int& currentEncodeLevel, - const ViewFrustum::intersection& parentLocationThisView) const; - static bool countOctreeElementsOperation(const OctreeElementPointer& element, void* extraData); OctreeElementPointer nodeForOctalCode(const OctreeElementPointer& ancestorElement, const unsigned char* needleCode, OctreeElementPointer* parentOfFoundElement) const; @@ -374,7 +284,6 @@ protected: bool _isDirty; bool _shouldReaverage; - bool _stopImport; bool _isViewing; bool _isServer; diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 989951b661..6446e3b460 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -461,33 +461,6 @@ ViewFrustum::intersection OctreeElement::computeViewIntersection(const ViewFrust return viewFrustum.calculateCubeKeyholeIntersection(_cube); } -// There are two types of nodes for which we want to "render" -// 1) Leaves that are in the LOD -// 2) Non-leaves are more complicated though... usually you don't want to render them, but if their children -// wouldn't be rendered, then you do want to render them. But sometimes they have some children that ARE -// in the LOD, and others that are not. In this case we want to render the parent, and none of the children. -// -// Since, if we know the camera position and orientation, we can know which of the corners is the "furthest" -// corner. We can use we can use this corner as our "voxel position" to do our distance calculations off of. -// By doing this, we don't need to test each child voxel's position vs the LOD boundary -bool OctreeElement::calculateShouldRender(const ViewFrustum& viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const { - bool shouldRender = false; - - if (hasContent()) { - float furthestDistance = furthestDistanceToCamera(viewFrustum); - float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize); - bool inChildBoundary = (furthestDistance <= childBoundary); - if (hasDetailedContent() && inChildBoundary) { - shouldRender = true; - } else { - float boundary = childBoundary * 2.0f; // the boundary is always twice the distance of the child boundary - bool inBoundary = (furthestDistance <= boundary); - shouldRender = inBoundary && !inChildBoundary; - } - } - return shouldRender; -} - // Calculates the distance to the furthest point of the voxel to the camera // does as much math as possible in voxel scale and then scales up to TREE_SCALE at end float OctreeElement::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const { diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 514039713b..b7857c3e6c 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -85,16 +85,6 @@ public: typedef enum { COMPLETED, PARTIAL, NONE } AppendState; virtual void debugExtraEncodeData(EncodeBitstreamParams& params) const { } - virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) { } - virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { return true; } - virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; } - - virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { } - virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { } - - /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const - { return COMPLETED; } /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. @@ -139,9 +129,6 @@ public: float distanceToCamera(const ViewFrustum& viewFrustum) const; float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const; - bool calculateShouldRender(const ViewFrustum& viewFrustum, - float voxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const; - // points are assumed to be in Voxel Coordinates (not TREE_SCALE'd) float distanceSquareToPoint(const glm::vec3& point) const; // when you don't need the actual distance, use this. float distanceToPoint(const glm::vec3& point) const; diff --git a/libraries/octree/src/OctreeElementBag.cpp b/libraries/octree/src/OctreeElementBag.cpp deleted file mode 100644 index afd2d5cdc3..0000000000 --- a/libraries/octree/src/OctreeElementBag.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// OctreeElementBag.cpp -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 4/25/2013. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "OctreeElementBag.h" -#include - -void OctreeElementBag::deleteAll() { - _bagElements = Bag(); -} - -/// does the bag contain elements? -/// if all of the contained elements are expired, they will not report as empty, and -/// a single last item will be returned by extract as a null pointer -bool OctreeElementBag::isEmpty() { - return _bagElements.empty(); -} - -void OctreeElementBag::insert(const OctreeElementPointer& element) { - _bagElements[element.get()] = element; -} - -OctreeElementPointer OctreeElementBag::extract() { - OctreeElementPointer result; - - // Find the first element still alive - Bag::iterator it = _bagElements.begin(); - while (it != _bagElements.end() && !result) { - result = it->second.lock(); - it = _bagElements.erase(it); - } - return result; -} diff --git a/libraries/octree/src/OctreeElementBag.h b/libraries/octree/src/OctreeElementBag.h index 34c49f1e60..a79648f596 100644 --- a/libraries/octree/src/OctreeElementBag.h +++ b/libraries/octree/src/OctreeElementBag.h @@ -16,30 +16,8 @@ #ifndef hifi_OctreeElementBag_h #define hifi_OctreeElementBag_h -#include - #include "OctreeElement.h" -class OctreeElementBag { - using Bag = std::unordered_map; - -public: - void insert(const OctreeElementPointer& element); // put a element into the bag - - OctreeElementPointer extract(); /// pull a element out of the bag (could come in any order) and if all of the - /// elements have expired, a single null pointer will be returned - - bool isEmpty(); /// does the bag contain elements, - /// if all of the contained elements are expired, they will not report as empty, and - /// a single last item will be returned by extract as a null pointer - - void deleteAll(); - size_t size() const { return _bagElements.size(); } - -private: - Bag _bagElements; -}; - class OctreeElementExtraEncodeDataBase { public: OctreeElementExtraEncodeDataBase() {} diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index 65b30dd197..0808e817ed 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -117,7 +117,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL, - sourceUUID, sourceNode, false, message.getVersion()); + sourceUUID, sourceNode); quint64 startUncompress, startLock = usecTimestampNow(); quint64 startReadBitsteam, endReadBitsteam; // FIXME STUTTER - there may be an opportunity to bump this lock outside of the diff --git a/libraries/octree/src/OctreeQueryNode.cpp b/libraries/octree/src/OctreeQueryNode.cpp index f0c9027493..16542b697e 100644 --- a/libraries/octree/src/OctreeQueryNode.cpp +++ b/libraries/octree/src/OctreeQueryNode.cpp @@ -144,11 +144,6 @@ void OctreeQueryNode::copyCurrentViewFrustum(ViewFrustum& viewOut) const { viewOut = _currentViewFrustum; } -void OctreeQueryNode::copyLastKnownViewFrustum(ViewFrustum& viewOut) const { - QMutexLocker viewLocker(&_viewMutex); - viewOut = _lastKnownViewFrustum; -} - bool OctreeQueryNode::updateCurrentViewFrustum() { // if shutting down, return immediately if (_isShuttingDown) { @@ -229,70 +224,6 @@ void OctreeQueryNode::setViewSent(bool viewSent) { } } -void OctreeQueryNode::updateLastKnownViewFrustum() { - // if shutting down, return immediately - if (_isShuttingDown) { - return; - } - - { - QMutexLocker viewLocker(&_viewMutex); - bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum); - - if (frustumChanges) { - // save our currentViewFrustum into our lastKnownViewFrustum - _lastKnownViewFrustum = _currentViewFrustum; - } - } - - // save that we know the view has been sent. - setLastTimeBagEmpty(); -} - - -bool OctreeQueryNode::moveShouldDump() const { - // if shutting down, return immediately - if (_isShuttingDown) { - return false; - } - - QMutexLocker viewLocker(&_viewMutex); - glm::vec3 oldPosition = _lastKnownViewFrustum.getPosition(); - glm::vec3 newPosition = _currentViewFrustum.getPosition(); - - // theoretically we could make this slightly larger but relative to avatar scale. - const float MAXIMUM_MOVE_WITHOUT_DUMP = 0.0f; - return glm::distance(newPosition, oldPosition) > MAXIMUM_MOVE_WITHOUT_DUMP; -} - -void OctreeQueryNode::dumpOutOfView() { - // if shutting down, return immediately - if (_isShuttingDown) { - return; - } - - int stillInView = 0; - int outOfView = 0; - OctreeElementBag tempBag; - ViewFrustum viewCopy; - copyCurrentViewFrustum(viewCopy); - while (OctreeElementPointer elementToCheck = elementBag.extract()) { - if (elementToCheck->isInView(viewCopy)) { - tempBag.insert(elementToCheck); - stillInView++; - } else { - outOfView++; - } - } - if (stillInView > 0) { - while (OctreeElementPointer elementToKeepInBag = tempBag.extract()) { - if (elementToKeepInBag->isInView(viewCopy)) { - elementBag.insert(elementToKeepInBag); - } - } - } -} - void OctreeQueryNode::packetSent(const NLPacket& packet) { _sentPacketHistory.packetSent(_sequenceNumber, packet); _sequenceNumber++; diff --git a/libraries/octree/src/OctreeQueryNode.h b/libraries/octree/src/OctreeQueryNode.h index fd89a89949..640a7c7ddc 100644 --- a/libraries/octree/src/OctreeQueryNode.h +++ b/libraries/octree/src/OctreeQueryNode.h @@ -46,23 +46,14 @@ public: bool shouldSuppressDuplicatePacket(); unsigned int getAvailable() const { return _octreePacket->bytesAvailableForWrite(); } - int getMaxSearchLevel() const { return _maxSearchLevel; } - void resetMaxSearchLevel() { _maxSearchLevel = 1; } - void incrementMaxSearchLevel() { _maxSearchLevel++; } - int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; } - void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; } - - OctreeElementBag elementBag; OctreeElementExtraEncodeData extraEncodeData; void copyCurrentViewFrustum(ViewFrustum& viewOut) const; - void copyLastKnownViewFrustum(ViewFrustum& viewOut) const; // These are not classic setters because they are calculating and maintaining state // which is set asynchronously through the network receive bool updateCurrentViewFrustum(); - void updateLastKnownViewFrustum(); bool getViewSent() const { return _viewSent; } void setViewSent(bool viewSent); @@ -70,24 +61,13 @@ public: bool getViewFrustumChanging() const { return _viewFrustumChanging; } bool getViewFrustumJustStoppedChanging() const { return _viewFrustumJustStoppedChanging; } - bool moveShouldDump() const; - - quint64 getLastTimeBagEmpty() const { return _lastTimeBagEmpty; } - void setLastTimeBagEmpty() { _lastTimeBagEmpty = _sceneSendStartTime; } - bool hasLodChanged() const { return _lodChanged; } OctreeSceneStats stats; - void dumpOutOfView(); - - quint64 getLastRootTimestamp() const { return _lastRootTimestamp; } - void setLastRootTimestamp(quint64 timestamp) { _lastRootTimestamp = timestamp; } unsigned int getlastOctreePacketLength() const { return _lastOctreePacketLength; } int getDuplicatePacketCount() const { return _duplicatePacketCount; } - void sceneStart(quint64 sceneSendStartTime) { _sceneSendStartTime = sceneSendStartTime; } - void nodeKilled(); bool isShuttingDown() const { return _isShuttingDown; } @@ -118,18 +98,11 @@ private: int _duplicatePacketCount { 0 }; quint64 _firstSuppressedPacket { usecTimestampNow() }; - int _maxSearchLevel { 1 }; - int _maxLevelReachedInLastSearch { 1 }; - mutable QMutex _viewMutex { QMutex::Recursive }; ViewFrustum _currentViewFrustum; - ViewFrustum _lastKnownViewFrustum; - quint64 _lastTimeBagEmpty { 0 }; bool _viewFrustumChanging { false }; bool _viewFrustumJustStoppedChanging { true }; - OctreeSendThread* _octreeSendThread { nullptr }; - // watch for LOD changes int _lastClientBoundaryLevelAdjust { 0 }; float _lastClientOctreeSizeScale { DEFAULT_OCTREE_SIZE_SCALE }; @@ -138,16 +111,12 @@ private: OCTREE_PACKET_SEQUENCE _sequenceNumber { 0 }; - quint64 _lastRootTimestamp { 0 }; - PacketType _myPacketType { PacketType::Unknown }; bool _isShuttingDown { false }; SentPacketHistory _sentPacketHistory; QQueue _nackedSequenceNumbers; - quint64 _sceneSendStartTime = 0; - std::array _lastOctreePayload; QJsonObject _lastCheckJSONParameters; diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 054603440d..c4d5e4a915 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -284,10 +284,6 @@ void OctreeSceneStats::didntFit(const OctreeElementPointer& element) { } } -void OctreeSceneStats::colorBitsWritten() { - _colorBitsWritten++; -} - void OctreeSceneStats::existsBitsWritten() { _existsBitsWritten++; } diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index 78b4dfd26f..e1228609a6 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -79,9 +79,6 @@ public: /// Track that a element was due to be sent, but didn't fit in the packet and was moved to next packet void didntFit(const OctreeElementPointer& element); - /// Track that the color bitmask was was sent as part of computation of a scene - void colorBitsWritten(); - /// Track that the exists in tree bitmask was was sent as part of computation of a scene void existsBitsWritten(); diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index ae4338be6f..b1ceab4149 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -248,22 +248,6 @@ void setOctalCodeSectionValue(unsigned char* octalCode, int section, char sectio } } -unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels) { - int codeLength = numberOfThreeBitSectionsInCode(originalOctalCode); - unsigned char* newCode = NULL; - if (codeLength > chopLevels) { - int newLength = codeLength - chopLevels; - newCode = new unsigned char[newLength+1]; - *newCode = newLength; // set the length byte - - for (int section = chopLevels; section < codeLength; section++) { - char sectionValue = getOctalCodeSectionValue(originalOctalCode, section); - setOctalCodeSectionValue(newCode, section - chopLevels, sectionValue); - } - } - return newCode; -} - bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild) { if (!possibleAncestor || !possibleDescendent) { return false; diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 89c5e6d74e..63cbc58cfa 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -40,8 +40,6 @@ const int UNKNOWN_OCTCODE_LENGTH = -2; /// \param int maxBytes number of bytes that octalCode is expected to be, -1 if unknown int numberOfThreeBitSectionsInCode(const unsigned char* octalCode, int maxBytes = UNKNOWN_OCTCODE_LENGTH); -unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels); - const int CHECK_NODE_ONLY = -1; bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild = CHECK_NODE_ONLY); diff --git a/tests/entities/src/main.cpp b/tests/entities/src/main.cpp index bf79f9d3e9..43a5d2e48d 100644 --- a/tests/entities/src/main.cpp +++ b/tests/entities/src/main.cpp @@ -160,7 +160,6 @@ int main(int argc, char** argv) { QByteArray packet = file.readAll(); EntityItemPointer item = ShapeEntityItem::boxFactory(EntityItemID(), EntityItemProperties()); ReadBitstreamToTreeParams params; - params.bitstreamVersion = 33; auto start = usecTimestampNow(); for (int i = 0; i < 1000; ++i) { From a65f5f7917a21eeaffa7cacd230363a12afdf683 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 18 Apr 2018 13:06:26 -0700 Subject: [PATCH 12/16] making tablet changes --- .../tablet-with-home-button-small-bezel.fbx | Bin 0 -> 190780 bytes .../resources/qml/hifi/tablet/TabletMenu.qml | 4 +- .../qml/hifi/tablet/TabletMenuStack.qml | 2 + .../qml/hifi/tablet/TabletMenuView.qml | 8 +-- scripts/system/libraries/WebTablet.js | 48 +++++++++++------- scripts/system/libraries/utils.js | 14 ++--- 6 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 interface/resources/meshes/tablet-with-home-button-small-bezel.fbx diff --git a/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx b/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx new file mode 100644 index 0000000000000000000000000000000000000000..d94ce304298b3f7085b495e5802bc4af613b5ac1 GIT binary patch literal 190780 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�wa#=&oPL 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

Co_i<#vns6( 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 literal 0 HcmV?d00001 diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 457fe84c3a..57c1163eb8 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -14,8 +14,8 @@ FocusScope { id: tabletMenu objectName: "tabletMenu" - width: 480 - height: 720 + width: parent.width + height: parent.height property var rootMenu: Menu { objectName:"rootMenu" } property var point: Qt.point(50, 50); diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index 4fa29de9a3..4d36a57793 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -16,6 +16,8 @@ import "." Item { id: root anchors.fill: parent + width: parent.width + height: parent.height objectName: "tabletMenuHandlerItem" StackView { diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index 2d4d31b9aa..b632a17e57 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -40,10 +40,10 @@ FocusScope { id: listView x: 0 y: 0 - width: 480 - height: 720 - contentWidth: 480 - contentHeight: 720 + width: parent.width + height: parent.height + contentWidth: parent.width + contentHeight: parent.height objectName: "menuList" topMargin: hifi.dimensions.menuPadding.y diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index a7186f55bd..783b91f5f0 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -15,7 +15,7 @@ Script.include(Script.resolvePath("../libraries/controllers.js")); Script.include(Script.resolvePath("../libraries/Xform.js")); var Y_AXIS = {x: 0, y: 1, z: 0}; -var DEFAULT_DPI = 34; +var DEFAULT_DPI = 31; var DEFAULT_WIDTH = 0.4375; var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees var SENSOR_TO_ROOM_MATRIX = -2; @@ -31,12 +31,12 @@ var DELAY_FOR_30HZ = 33; // milliseconds // will need to be recaclulated if dimensions of fbx model change. -var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269}; +var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx"; -var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; +var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button-small-bezel.fbx"; // returns object with two fields: // * position - position in front of the user @@ -44,14 +44,28 @@ var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home- function calcSpawnInfo(hand, landscape) { var finalPosition; + var LEFT_HAND = Controller.Standard.LeftHand; + var sensorToWorldScale = MyAvatar.sensorToWorldScale; var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; - var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation; - var dominantHandRotation = MyAvatar.getDominantHand() === "right" ? -20 : 20; - var offsetRotation = Quat.fromPitchYawRollDegrees(0, dominantHandRotation, 0); - var forward = Vec3.multiplyQbyV(offsetRotation, Quat.getForward(Quat.cancelOutRollAndPitch(headRot))); - var FORWARD_OFFSET = 0.5 * MyAvatar.sensorToWorldScale; - finalPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward)); - var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); + var headRot = Quat.cancelOutRollAndPitch((HMD.active && Camera.mode === "first person") ? + HMD.orientation : Camera.orientation); + + var right = Quat.getRight(headRot); + var forward = Quat.getForward(headRot); + var up = Quat.getUp(headRot); + + var FORWARD_OFFSET = 0.5 * sensorToWorldScale; + var UP_OFFSET = -0.16 * sensorToWorldScale; + var RIGHT_OFFSET = ((hand === LEFT_HAND) ? -0.18 : 0.18) * sensorToWorldScale; + + var forwardPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward)); + var lateralPosition = Vec3.sum(forwardPosition, Vec3.multiply(RIGHT_OFFSET, right)); + finalPosition = Vec3.sum(lateralPosition, Vec3.multiply(UP_OFFSET, up)); + + var MY_EYES = { x: 0.0, y: 0.15, z: 0.0 }; + var lookAtEndPosition = Vec3.sum(Vec3.multiply(RIGHT_OFFSET, right), Vec3.multiply(FORWARD_OFFSET, forward)); + var orientation = Quat.lookAt(MY_EYES, lookAtEndPosition, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); + return { position: finalPosition, rotation: landscape ? Quat.multiply(orientation, ROT_LANDSCAPE) : Quat.multiply(orientation, ROT_Y_180) @@ -119,11 +133,11 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Overlays.deleteOverlay(this.webOverlayID); } - var RAYPICK_OFFSET = 0.0001; // Sufficient for raypick to reliably intersect tablet screen before tablet model. + var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model. var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor + RAYPICK_OFFSET; - var WEB_ENTITY_Y_OFFSET = 0.004; - var screenWidth = 0.82 * tabletWidth; - var screenHeight = 0.81 * tabletHeight; + var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor; + var screenWidth = 0.9275 * tabletWidth; + var screenHeight = 0.8983 * tabletHeight; this.webOverlayID = Overlays.addOverlay("web3d", { name: "WebTablet Web", url: url, @@ -139,7 +153,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { visible: visible }); - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor) - 0.003; + var HOME_BUTTON_Y_OFFSET = (tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor; // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; this.homeButtonID = Overlays.addOverlay("circle3d", { @@ -277,8 +291,8 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale; var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; - var screenWidth = 0.82 * tabletWidth; - var screenHeight = 0.81 * tabletHeight; + var screenWidth = 0.9275 * tabletWidth; + var screenHeight = 0.8983 * tabletHeight; Overlays.editOverlay(this.webOverlayID, { rotation: Quat.multiply(cameraOrientation, ROT_LANDSCAPE_WINDOW), dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1} diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 6afde85c29..7e9e1d7e6a 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -373,7 +373,6 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) { return; } - var sensorScaleFactor = sensorToWorldScaleOverride || MyAvatar.sensorToWorldScale; var sensorScaleOffsetOverride = 1; var SENSOR_TO_ROOM_MATRIX = 65534; @@ -383,8 +382,8 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) } // will need to be recaclulated if dimensions of fbx model change. - var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269}; - var DEFAULT_DPI = 34; + var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; + var DEFAULT_DPI = 31; var DEFAULT_WIDTH = 0.4375; // scale factor of natural tablet dimensions. @@ -402,9 +401,10 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) // update webOverlay var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model. var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride + RAYPICK_OFFSET; - var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride; - var screenWidth = 0.82 * tabletWidth; - var screenHeight = 0.81 * tabletHeight; + var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor; + print(WEB_ENTITY_Y_OFFSET); + var screenWidth = 0.9275 * tabletWidth; + var screenHeight = 0.8983 * tabletHeight; var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape; Overlays.editOverlay(HMD.tabletScreenID, { localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, @@ -413,7 +413,7 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) }); // update homeButton - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) - 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride; + var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride; // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; Overlays.editOverlay(HMD.homeButtonID, { From a8d6a4e0b6996a57ae5128407d16e50b2982ecd9 Mon Sep 17 00:00:00 2001 From: Clement Date: Wed, 18 Apr 2018 15:05:49 -0700 Subject: [PATCH 13/16] CR --- assignment-client/src/octree/OctreeSendThread.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index f8d566862e..de49bd461c 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -416,7 +416,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* int elapsedmsec = (end - start) / USECS_PER_MSEC; OctreeServer::trackLoopTime(elapsedmsec); - // if after sending packets we've emptied our bag, then we want to remember that we've sent all + // if we've sent everything, then we want to remember that we've sent all // the octree elements from the current view frustum if (!hasSomethingToSend(nodeData)) { nodeData->setViewSent(true); @@ -465,7 +465,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre extraPackingAttempts++; } - // If the bag had contents but is now empty then we know we've sent the entire scene. + // If we had something to send, but now we don't, then we know we've sent the entire scene. bool completedScene = hadSomething; if (completedScene || lastNodeDidntFit) { // we probably want to flush what has accumulated in nodeData but: From e01e7cc7bc3a814d4b11e3c7ce42f1ab33927ffa Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 16 Apr 2018 15:25:04 -0700 Subject: [PATCH 14/16] Be a bit more efficient --- .../resources/qml/hifi/LetterboxMessage.qml | 62 ++++++------------- 1 file changed, 18 insertions(+), 44 deletions(-) diff --git a/interface/resources/qml/hifi/LetterboxMessage.qml b/interface/resources/qml/hifi/LetterboxMessage.qml index c0f3fa1006..8a18d88842 100644 --- a/interface/resources/qml/hifi/LetterboxMessage.qml +++ b/interface/resources/qml/hifi/LetterboxMessage.qml @@ -30,6 +30,16 @@ Item { color: "black" opacity: 0.5 radius: popupRadius + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + acceptedButtons: Qt.LeftButton; + propagateComposedEvents: false; + onClicked: { + letterbox.visible = false; + } + } } Rectangle { id: textContainer; @@ -38,6 +48,14 @@ Item { anchors.centerIn: parent radius: popupRadius color: "white" + + // Prevent dismissing the popup by clicking on the textContainer + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + } + Item { id: contentContainer width: parent.width - 50 @@ -135,48 +153,4 @@ Item { } } } - // Left gray MouseArea - MouseArea { - anchors.left: parent.left; - anchors.right: textContainer.left; - anchors.top: textContainer.top; - anchors.bottom: textContainer.bottom; - acceptedButtons: Qt.LeftButton; - onClicked: { - letterbox.visible = false; - } - } - // Right gray MouseArea - MouseArea { - anchors.left: textContainer.left; - anchors.right: parent.left; - anchors.top: textContainer.top; - anchors.bottom: textContainer.bottom; - acceptedButtons: Qt.LeftButton; - onClicked: { - letterbox.visible = false; - } - } - // Top gray MouseArea - MouseArea { - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: parent.top; - anchors.bottom: textContainer.top; - acceptedButtons: Qt.LeftButton; - onClicked: { - letterbox.visible = false; - } - } - // Bottom gray MouseArea - MouseArea { - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: textContainer.bottom; - anchors.bottom: parent.bottom; - acceptedButtons: Qt.LeftButton; - onClicked: { - letterbox.visible = false; - } - } } From c8bef8651f904343901ff704ec4bbd8446a9435b Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Thu, 19 Apr 2018 10:04:38 +0200 Subject: [PATCH 15/16] Fixed weird specular on hair an teleportation target! Was a stupid mistake... --- libraries/render-utils/src/Haze.slh | 16 ++++++++-------- libraries/render-utils/src/model_translucent.slf | 4 ++-- .../render-utils/src/model_translucent_fade.slf | 2 +- .../src/model_translucent_normal_map.slf | 2 +- .../src/model_translucent_normal_map_fade.slf | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index a9c8ff829e..ab973ba752 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -109,17 +109,17 @@ vec4 computeHazeColor(vec4 fragColor, vec3 fragPositionES, vec3 fragPositionWS, // Convert haze colour from uniform into a vec4 vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); - // Directional light component is a function of the angle from the eye, between the fragment and the sun - vec3 fragToEyeDirWS = normalize(fragPositionWS - eyePositionWS); - - float glareComponent = max(0.0, dot(fragToEyeDirWS, -lightDirectionWS)); - float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); - - vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); - // Use the haze colour for the glare colour, if blend is not enabled vec4 blendedHazeColor; if ((hazeParams.hazeMode & HAZE_MODE_IS_ENABLE_LIGHT_BLEND) == HAZE_MODE_IS_ENABLE_LIGHT_BLEND) { + // Directional light component is a function of the angle from the eye, between the fragment and the sun + vec3 fragToEyeDirWS = normalize(fragPositionWS - eyePositionWS); + + float glareComponent = max(0.0, dot(fragToEyeDirWS, -lightDirectionWS)); + float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); + + vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); + blendedHazeColor = mix(hazeColor, glareColor, power); } else { blendedHazeColor = hazeColor; diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index f109170068..71f76c8a8d 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -64,7 +64,7 @@ void main(void) { vec3 fragNormalWS = normalize(_normalWS); TransformCamera cam = getTransformCamera(); - vec3 fragToEyeWS = fragPositionWS - cam._viewInverse[3].xyz; + vec3 fragToEyeWS = cam._viewInverse[3].xyz - fragPositionWS; vec3 fragToEyeDirWS = normalize(fragToEyeWS); SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragToEyeDirWS); @@ -77,7 +77,7 @@ void main(void) { vec4(0), vec4(0), opacity); } - _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index 47349930de..8b40186448 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -66,7 +66,7 @@ void main(void) { vec3 fragNormalWS = normalize(_normalWS); TransformCamera cam = getTransformCamera(); - vec3 fragToEyeWS = fragPositionWS - cam._viewInverse[3].xyz; + vec3 fragToEyeWS = cam._viewInverse[3].xyz - fragPositionWS; vec3 fragToEyeDirWS = normalize(fragToEyeWS); SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragToEyeDirWS); diff --git a/libraries/render-utils/src/model_translucent_normal_map.slf b/libraries/render-utils/src/model_translucent_normal_map.slf index 89f5f46f6a..320e883bb0 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slf +++ b/libraries/render-utils/src/model_translucent_normal_map.slf @@ -66,7 +66,7 @@ void main(void) { <$evalMaterialNormalLOD(_positionES, normalTex, _normalWS, _tangentWS, fragNormalWS)$> TransformCamera cam = getTransformCamera(); - vec3 fragToEyeWS = fragPositionWS - cam._viewInverse[3].xyz; + vec3 fragToEyeWS = cam._viewInverse[3].xyz - fragPositionWS; vec3 fragToEyeDirWS = normalize(fragToEyeWS); SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragToEyeDirWS); diff --git a/libraries/render-utils/src/model_translucent_normal_map_fade.slf b/libraries/render-utils/src/model_translucent_normal_map_fade.slf index a87167af63..0e114f7fdd 100644 --- a/libraries/render-utils/src/model_translucent_normal_map_fade.slf +++ b/libraries/render-utils/src/model_translucent_normal_map_fade.slf @@ -75,7 +75,7 @@ void main(void) { <$evalMaterialNormalLOD(_positionES, normalTex, _normalWS, _tangentWS, fragNormalWS)$> TransformCamera cam = getTransformCamera(); - vec3 fragToEyeWS = fragPositionWS - cam._viewInverse[3].xyz; + vec3 fragToEyeWS = cam._viewInverse[3].xyz - fragPositionWS; vec3 fragToEyeDirWS = normalize(fragToEyeWS); SurfaceData surfaceWS = initSurfaceData(roughness, fragNormalWS, fragToEyeDirWS); From 764aa0006969d56d579f0e2223dfb5d76b5c3742 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 19 Apr 2018 07:33:14 -0700 Subject: [PATCH 16/16] Coding standard fixes --- interface/src/scripting/AudioDevices.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 34a3630b78..ee615cde20 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -188,7 +188,7 @@ void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD for (auto i = 0; i < _devices.size(); ++i) { std::shared_ptr device = _devices[i]; - bool &isSelected = isHMD ? device->selectedHMD : device->selectedDesktop; + bool& isSelected = isHMD ? device->selectedHMD : device->selectedDesktop; if (isSelected && device->info != selectedDevice) { isSelected = false; } else if (device->info == selectedDevice) { @@ -259,7 +259,7 @@ void AudioDeviceList::onDevicesChanged(const QList& devices) { foreach(const QAudioDeviceInfo& deviceInfo, devices) { for (bool isHMD : {false, true}) { - auto &backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName; + auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName; if (deviceInfo.deviceName() == backupSelectedDeviceName) { QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; selectedDevice = deviceInfo; @@ -278,7 +278,7 @@ void AudioDeviceList::onDevicesChanged(const QList& devices) { for (bool isHMD : {false, true}) { QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; - bool &isSelected = isHMD ? device.selectedHMD : device.selectedDesktop; + bool& isSelected = isHMD ? device.selectedHMD : device.selectedDesktop; if (!selectedDevice.isNull()) { isSelected = (device.info == selectedDevice);