mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-07 12:10:52 -06:00
Merge
This commit is contained in:
@@ -10,31 +10,25 @@ return {
|
||||
Textures = {
|
||||
ColorTextures = {
|
||||
{
|
||||
Name = "ESRI Imagery World 2D",
|
||||
FilePath = "map_service_configs/ESRI_Imagery_World_2D.wms",
|
||||
Name = "VIIRS_SNPP_CorrectedReflectance_TrueColor",
|
||||
FilePath = "map_service_configs/VIIRS_SNPP_CorrectedReflectance_TrueColor.xml"
|
||||
},
|
||||
{
|
||||
Name = "MODIS_Terra_CorrectedReflectance_TrueColor",
|
||||
FilePath = "map_service_configs/MODIS_Terra_CorrectedReflectance_TrueColor.xml"
|
||||
},
|
||||
{
|
||||
Name = "MODIS_Water_Mask",
|
||||
FilePath = "map_service_configs/MODIS_Water_Mask.xml"
|
||||
},
|
||||
|
||||
|
||||
--[[
|
||||
{
|
||||
Name = "Coastlines",
|
||||
FilePath = "map_service_configs/Coastlines.xml",
|
||||
},
|
||||
{
|
||||
Name = "VIIRS_SNPP_CorrectedReflectance_TrueColor",
|
||||
FilePath = "map_service_configs/VIIRS_SNPP_CorrectedReflectance_TrueColor.xml"
|
||||
},
|
||||
{
|
||||
Name = "ESRI Imagery World 2D",
|
||||
FilePath = "map_service_configs/ESRI_Imagery_World_2D.wms",
|
||||
},
|
||||
|
||||
{
|
||||
Name = "MODIS_Terra_CorrectedReflectance_TrueColor",
|
||||
FilePath = "map_service_configs/MODIS_Terra_CorrectedReflectance_TrueColor.xml",
|
||||
},
|
||||
|
||||
--]]
|
||||
},
|
||||
HeightMaps = {
|
||||
{
|
||||
|
||||
Submodule ext/ghoul updated: 5c9f358af8...f3420c0b5c
@@ -121,7 +121,7 @@ namespace openspace {
|
||||
|
||||
Scalar scaleFactor = _owner->lodScaleFactor * ellipsoid.minimumRadius();;
|
||||
Scalar projectedScaleFactor = scaleFactor / distance;
|
||||
int desiredLevel = floor(log2(projectedScaleFactor));
|
||||
int desiredLevel = ceil(log2(projectedScaleFactor));
|
||||
|
||||
// clamp level
|
||||
desiredLevel = glm::clamp(desiredLevel, _owner->minSplitDepth, _owner->maxSplitDepth);
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace openspace {
|
||||
, doFrustumCulling(properties::BoolProperty("doFrustumCulling", "doFrustumCulling"))
|
||||
, doHorizonCulling(properties::BoolProperty("doHorizonCulling", "doHorizonCulling"))
|
||||
, mergeInvisible(properties::BoolProperty("mergeInvisible", "mergeInvisible", true))
|
||||
, lodScaleFactor(properties::FloatProperty("lodScaleFactor", "lodScaleFactor", 10.0f, 0.0f, 100.0f))
|
||||
, lodScaleFactor(properties::FloatProperty("lodScaleFactor", "lodScaleFactor", 5.0f, 0.0f, 20.0f))
|
||||
, initChunkVisible(properties::BoolProperty("initChunkVisible", "initChunkVisible", true))
|
||||
, renderSmallChunksFirst(properties::BoolProperty("renderSmallChunksFirst", "renderSmallChunksFirst", true))
|
||||
{
|
||||
@@ -129,7 +129,10 @@ namespace openspace {
|
||||
std::shared_ptr<TileProvider> colorTextureProvider = std::shared_ptr<TileProvider>(
|
||||
new TileProvider(tileReader, cacheSize, frameUntilFlushRequestQueue));
|
||||
|
||||
_tileProviderManager->addColorTexture(name, colorTextureProvider);
|
||||
_tileProviderManager->addColorTexture(name, colorTextureProvider, true);
|
||||
|
||||
// Create property for this tile provider
|
||||
_activeColorLayers.push_back(properties::BoolProperty(name, name, true));
|
||||
}
|
||||
|
||||
ghoul::Dictionary heightMapsDictionary;
|
||||
@@ -157,8 +160,20 @@ namespace openspace {
|
||||
new TileProvider(tileReader, cacheSize, frameUntilFlushRequestQueue));
|
||||
|
||||
|
||||
_tileProviderManager->addHeightMap(name, heightMapProvider);
|
||||
_tileProviderManager->addHeightMap(name, heightMapProvider, true);
|
||||
|
||||
// Create property for this tile provider
|
||||
_activeHeightMapLayers.push_back(properties::BoolProperty(name, name, true));
|
||||
}
|
||||
|
||||
// Add properties for the tile providers
|
||||
for (auto it = _activeColorLayers.begin(); it != _activeColorLayers.end(); it++) {
|
||||
addProperty(*it);
|
||||
}
|
||||
for (auto it = _activeHeightMapLayers.begin(); it != _activeHeightMapLayers.end(); it++) {
|
||||
addProperty(*it);
|
||||
}
|
||||
|
||||
|
||||
_chunkedLodGlobe = std::shared_ptr<ChunkedLodGlobe>(
|
||||
new ChunkedLodGlobe(_ellipsoid, patchSegments, _tileProviderManager));
|
||||
@@ -197,17 +212,31 @@ namespace openspace {
|
||||
_chunkedLodGlobe->setSaveCamera(nullptr);
|
||||
}
|
||||
}
|
||||
_chunkedLodGlobe->doFrustumCulling = doFrustumCulling.value();
|
||||
_chunkedLodGlobe->doHorizonCulling = doHorizonCulling.value();
|
||||
_chunkedLodGlobe->mergeInvisible = mergeInvisible.value();
|
||||
_chunkedLodGlobe->lodScaleFactor= lodScaleFactor.value();
|
||||
_chunkedLodGlobe->initChunkVisible = initChunkVisible.value();
|
||||
|
||||
_distanceSwitch.render(data);
|
||||
}
|
||||
|
||||
void RenderableGlobe::update(const UpdateData& data) {
|
||||
_time = data.time;
|
||||
_distanceSwitch.update(data);
|
||||
|
||||
_chunkedLodGlobe->doFrustumCulling = doFrustumCulling.value();
|
||||
_chunkedLodGlobe->doHorizonCulling = doHorizonCulling.value();
|
||||
_chunkedLodGlobe->mergeInvisible = mergeInvisible.value();
|
||||
_chunkedLodGlobe->lodScaleFactor = lodScaleFactor.value();
|
||||
_chunkedLodGlobe->initChunkVisible = initChunkVisible.value();
|
||||
|
||||
std::vector<TileProviderManager::TileProviderWithName>& colorTextureProviders =
|
||||
_tileProviderManager->colorTextureProviders();
|
||||
std::vector<TileProviderManager::TileProviderWithName>& heightMapProviders =
|
||||
_tileProviderManager->heightMapProviders();
|
||||
|
||||
for (size_t i = 0; i < colorTextureProviders.size(); i++) {
|
||||
colorTextureProviders[i].isActive = _activeColorLayers[i].value();
|
||||
}
|
||||
for (size_t i = 0; i < heightMapProviders.size(); i++) {
|
||||
heightMapProviders[i].isActive = _activeHeightMapLayers[i].value();
|
||||
}
|
||||
}
|
||||
|
||||
glm::dvec3 RenderableGlobe::geodeticSurfaceProjection(glm::dvec3 position) {
|
||||
|
||||
@@ -88,6 +88,9 @@ private:
|
||||
|
||||
properties::BoolProperty _saveOrThrowCamera;
|
||||
|
||||
std::vector<properties::BoolProperty> _activeColorLayers;
|
||||
std::vector<properties::BoolProperty> _activeHeightMapLayers;
|
||||
|
||||
DistanceSwitch _distanceSwitch;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,29 +44,57 @@ namespace openspace {
|
||||
|
||||
void TileProviderManager::addHeightMap(
|
||||
std::string name,
|
||||
std::shared_ptr<TileProvider> tileProvider)
|
||||
std::shared_ptr<TileProvider> tileProvider,
|
||||
bool isActive)
|
||||
{
|
||||
_heightMapProviders.push_back(tileProvider);
|
||||
_heightMapProviders.push_back({ name , tileProvider, isActive});
|
||||
}
|
||||
|
||||
void TileProviderManager::addColorTexture(
|
||||
std::string name,
|
||||
std::shared_ptr<TileProvider> tileProvider)
|
||||
std::shared_ptr<TileProvider> tileProvider,
|
||||
bool isActive)
|
||||
{
|
||||
_colorTextureProviders.push_back(tileProvider);
|
||||
|
||||
_colorTextureProviders.push_back({ name , tileProvider, isActive });
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<TileProvider> >&
|
||||
std::vector<TileProviderManager::TileProviderWithName>&
|
||||
TileProviderManager::heightMapProviders()
|
||||
{
|
||||
return _heightMapProviders;
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<TileProvider> >&
|
||||
std::vector<TileProviderManager::TileProviderWithName>&
|
||||
TileProviderManager::colorTextureProviders()
|
||||
{
|
||||
return _colorTextureProviders;
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<TileProvider> >
|
||||
TileProviderManager::getActiveHeightMapProviders()
|
||||
{
|
||||
std::vector<std::shared_ptr<TileProvider> > tileProviders;
|
||||
for (auto it = _heightMapProviders.begin(); it != _heightMapProviders.end(); it++)
|
||||
{
|
||||
if (it->isActive) {
|
||||
tileProviders.push_back(it->tileProvider);
|
||||
}
|
||||
}
|
||||
return tileProviders;
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<TileProvider> >
|
||||
TileProviderManager::getActiveColorTextureProviders()
|
||||
{
|
||||
std::vector<std::shared_ptr<TileProvider> > tileProviders;
|
||||
for (auto it = _colorTextureProviders.begin(); it != _colorTextureProviders.end(); it++)
|
||||
{
|
||||
if (it->isActive) {
|
||||
tileProviders.push_back(it->tileProvider);
|
||||
}
|
||||
}
|
||||
return tileProviders;
|
||||
}
|
||||
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -37,23 +37,38 @@ namespace openspace {
|
||||
|
||||
class TileProviderManager {
|
||||
public:
|
||||
struct TileProviderWithName {
|
||||
std::string name;
|
||||
std::shared_ptr<TileProvider> tileProvider;
|
||||
bool isActive;
|
||||
};
|
||||
|
||||
TileProviderManager();
|
||||
~TileProviderManager();
|
||||
|
||||
static ThreadPool tileRequestThreadPool;
|
||||
|
||||
void addHeightMap(std::string name, std::shared_ptr<TileProvider> tileProvider);
|
||||
void addColorTexture(std::string name, std::shared_ptr<TileProvider> tileProvider);
|
||||
void addHeightMap(
|
||||
std::string name,
|
||||
std::shared_ptr<TileProvider> tileProvider,
|
||||
bool isActive);
|
||||
void addColorTexture(
|
||||
std::string name,
|
||||
std::shared_ptr<TileProvider> tileProvider,
|
||||
bool isActive);
|
||||
/*
|
||||
std::shared_ptr<TileProvider> getHeightMap(std::string name);
|
||||
std::shared_ptr<TileProvider> getColorTexture(std::string name);
|
||||
*/
|
||||
const std::vector<std::shared_ptr<TileProvider> >& heightMapProviders();
|
||||
const std::vector<std::shared_ptr<TileProvider> >& colorTextureProviders();
|
||||
const std::vector<std::shared_ptr<TileProvider> > getActiveHeightMapProviders();
|
||||
const std::vector<std::shared_ptr<TileProvider> > getActiveColorTextureProviders();
|
||||
|
||||
std::vector<TileProviderWithName>& heightMapProviders();
|
||||
std::vector<TileProviderWithName>& colorTextureProviders();
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<TileProvider> > _heightMapProviders;
|
||||
std::vector<std::shared_ptr<TileProvider> > _colorTextureProviders;
|
||||
std::vector<TileProviderWithName> _heightMapProviders;
|
||||
std::vector<TileProviderWithName> _colorTextureProviders;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -107,6 +107,9 @@ namespace openspace {
|
||||
textureTypes[i].keyNumLayers, textureTypes[i].numLayers);
|
||||
}
|
||||
|
||||
// Remove old program
|
||||
_programObject.release();
|
||||
|
||||
_programObject = OsEng.renderEngine().buildRenderProgram(
|
||||
_shaderName,
|
||||
_vsPath,
|
||||
|
||||
@@ -98,10 +98,9 @@ namespace openspace {
|
||||
}
|
||||
|
||||
|
||||
Tile TileProvider::getHighestResolutionTile(ChunkIndex chunkIndex) {
|
||||
TileUvTransform uvTransform;
|
||||
uvTransform.uvOffset = glm::vec2(0, 0);
|
||||
uvTransform.uvScale = glm::vec2(1, 1);
|
||||
Tile TileProvider::getHighestResolutionTile(
|
||||
ChunkIndex chunkIndex,
|
||||
TileUvTransform uvTransform) {
|
||||
|
||||
int maximumLevel = _asyncTextureDataProvider->getTextureDataProvider()->getMaximumLevel();
|
||||
|
||||
@@ -113,6 +112,20 @@ namespace openspace {
|
||||
return getOrEnqueueHighestResolutionTile(chunkIndex, uvTransform);
|
||||
}
|
||||
|
||||
Tile TileProvider::getHighestResolutionParentTile(ChunkIndex chunkIndex, int levelOffset) {
|
||||
TileUvTransform uvTransform;
|
||||
uvTransform.uvOffset = glm::vec2(0, 0);
|
||||
uvTransform.uvScale = glm::vec2(1, 1);
|
||||
|
||||
for (int i = 0; i < levelOffset && chunkIndex.level > 2; i++) {
|
||||
transformFromParent(chunkIndex, uvTransform);
|
||||
chunkIndex = chunkIndex.parent();
|
||||
}
|
||||
|
||||
Tile toReturn = getHighestResolutionTile(chunkIndex, uvTransform);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
Tile TileProvider::getOrEnqueueHighestResolutionTile(const ChunkIndex& chunkIndex,
|
||||
TileUvTransform& uvTransform)
|
||||
{
|
||||
|
||||
@@ -81,7 +81,15 @@ namespace openspace {
|
||||
|
||||
~TileProvider();
|
||||
|
||||
Tile getHighestResolutionTile(ChunkIndex chunkIndex);
|
||||
Tile getHighestResolutionTile(
|
||||
ChunkIndex chunkIndex,
|
||||
TileUvTransform uvTransform = {glm::vec2(0.0f,0.0f), glm::vec2(1.0f,1.0f)});
|
||||
/**
|
||||
\param levelOffset gives a tile from a parent chunk with that particular
|
||||
offset. For example levelOffset = 1 gives the first parent and levelOffset = 2
|
||||
gives the grand parent.
|
||||
*/
|
||||
Tile getHighestResolutionParentTile(ChunkIndex chunkIndex, int levelOffset = 1);
|
||||
|
||||
std::shared_ptr<Texture> getOrStartFetchingTile(ChunkIndex chunkIndex);
|
||||
std::shared_ptr<Texture> getDefaultTexture();
|
||||
|
||||
@@ -68,12 +68,12 @@ namespace openspace {
|
||||
}
|
||||
|
||||
void PatchRenderer::update() {
|
||||
auto heightMapProviders = _tileProviderManager->heightMapProviders();
|
||||
auto heightMapProviders = _tileProviderManager->getActiveHeightMapProviders();
|
||||
for (auto iter = heightMapProviders.begin(); iter != heightMapProviders.end(); iter++)
|
||||
{
|
||||
iter->get()->prerender();
|
||||
}
|
||||
auto colorTextureProviders = _tileProviderManager->colorTextureProviders();
|
||||
auto colorTextureProviders = _tileProviderManager->getActiveColorTextureProviders();
|
||||
for (auto iter = colorTextureProviders.begin(); iter != colorTextureProviders.end(); iter++)
|
||||
{
|
||||
iter->get()->prerender();
|
||||
@@ -100,24 +100,6 @@ namespace openspace {
|
||||
"LocalChunkedLodPatch",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_vs.glsl",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_fs.glsl"));
|
||||
|
||||
/*
|
||||
_programObjectGlobalRendering = OsEng.renderEngine().buildRenderProgram(
|
||||
"GlobalChunkedLodPatch",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_vs.glsl",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/globalchunkedlodpatch_fs.glsl");
|
||||
ghoul_assert(_programObjectGlobalRendering != nullptr, "Failed to initialize programObject!");
|
||||
|
||||
_programObjectLocalRendering = OsEng.renderEngine().buildRenderProgram(
|
||||
"LocalChunkedLodPatch",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_vs.glsl",
|
||||
"${MODULE_GLOBEBROWSING}/shaders/localchunkedlodpatch_fs.glsl");
|
||||
ghoul_assert(_programObjectLocalRendering != nullptr, "Failed to initialize programObject!");
|
||||
using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError;
|
||||
_programObjectGlobalRendering->setIgnoreSubroutineUniformLocationError(IgnoreError::Yes);
|
||||
_programObjectLocalRendering->setIgnoreSubroutineUniformLocationError(IgnoreError::Yes);
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
void ChunkRenderer::renderChunk(const Chunk& chunk, const RenderData& data) {
|
||||
@@ -133,8 +115,8 @@ namespace openspace {
|
||||
using namespace glm;
|
||||
|
||||
// All providers of tiles
|
||||
auto heightMapProviders = _tileProviderManager->heightMapProviders();
|
||||
auto colorTextureProviders = _tileProviderManager->colorTextureProviders();
|
||||
auto heightMapProviders = _tileProviderManager->getActiveHeightMapProviders();
|
||||
auto colorTextureProviders = _tileProviderManager->getActiveColorTextureProviders();
|
||||
|
||||
int numHeightMapProviders = heightMapProviders.size();
|
||||
int numColorTextureProviders = colorTextureProviders.size();
|
||||
@@ -162,10 +144,18 @@ namespace openspace {
|
||||
programObject->activate();
|
||||
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitHeight;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitHeightParent1;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitHeightParent2;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitColor;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitColorParent1;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitColorParent2;
|
||||
|
||||
texUnitHeight.resize(numHeightMapProviders);
|
||||
texUnitHeightParent1.resize(numHeightMapProviders);
|
||||
texUnitHeightParent2.resize(numHeightMapProviders);
|
||||
texUnitColor.resize(numColorTextureProviders);
|
||||
texUnitColorParent1.resize(numColorTextureProviders);
|
||||
texUnitColorParent2.resize(numColorTextureProviders);
|
||||
|
||||
|
||||
// Go through all the height map providers
|
||||
@@ -176,6 +166,8 @@ namespace openspace {
|
||||
auto tileProvider = it->get();
|
||||
// Get the texture that should be used for rendering
|
||||
Tile tile = tileProvider->getHighestResolutionTile(chunk.index());
|
||||
Tile tileParent1 = tileProvider->getHighestResolutionParentTile(chunk.index(), 1);
|
||||
Tile tileParent2 = tileProvider->getHighestResolutionParentTile(chunk.index(), 2);
|
||||
TileDepthTransform depthTransform = tileProvider->depthTransform();
|
||||
|
||||
// The texture needs a unit to sample from
|
||||
@@ -193,6 +185,41 @@ namespace openspace {
|
||||
indexedTileKey + ".uvTransform.uvOffset",
|
||||
tile.uvTransform.uvOffset);
|
||||
|
||||
// Blend tile with two parents
|
||||
// The texture needs a unit to sample from
|
||||
texUnitHeightParent1[i].activate();
|
||||
tileParent1.texture->bind();
|
||||
|
||||
std::string indexedTileKeyParent1 = "heightTilesParent1[" + std::to_string(i) + "]";
|
||||
// Send uniforms for the tile to the shader
|
||||
programObject->setUniform(indexedTileKeyParent1 + ".textureSampler", texUnitHeightParent1[i]);
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent1 + ".uvTransform.uvScale",
|
||||
tileParent1.uvTransform.uvScale);
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent1 + ".uvTransform.uvOffset",
|
||||
tileParent1.uvTransform.uvOffset);
|
||||
|
||||
|
||||
|
||||
// The texture needs a unit to sample from
|
||||
texUnitHeightParent2[i].activate();
|
||||
tileParent2.texture->bind();
|
||||
|
||||
std::string indexedTileKeyParent2 = "heightTilesParent2[" + std::to_string(i) + "]";
|
||||
// Send uniforms for the tile to the shader
|
||||
programObject->setUniform(indexedTileKeyParent2 + ".textureSampler", texUnitHeightParent2[i]);
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent2 + ".uvTransform.uvScale",
|
||||
tileParent2.uvTransform.uvScale);
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent2 + ".uvTransform.uvOffset",
|
||||
tileParent2.uvTransform.uvOffset);
|
||||
|
||||
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKey + ".depthTransform.depthScale",
|
||||
depthTransform.depthScale);
|
||||
@@ -210,6 +237,8 @@ namespace openspace {
|
||||
auto tileProvider = it->get();
|
||||
// Get the texture that should be used for rendering
|
||||
Tile tile = tileProvider->getHighestResolutionTile(chunk.index());
|
||||
Tile tileParent1 = tileProvider->getHighestResolutionParentTile(chunk.index(), 1);
|
||||
Tile tileParent2 = tileProvider->getHighestResolutionParentTile(chunk.index(), 2);
|
||||
|
||||
// The texture needs a unit to sample from
|
||||
texUnitColor[i].activate();
|
||||
@@ -225,6 +254,38 @@ namespace openspace {
|
||||
programObject->setUniform(
|
||||
indexedTileKey + ".uvTransform.uvOffset",
|
||||
tile.uvTransform.uvOffset);
|
||||
|
||||
// Blend tile with two parents
|
||||
// The texture needs a unit to sample from
|
||||
texUnitColorParent1[i].activate();
|
||||
tileParent1.texture->bind();
|
||||
|
||||
std::string indexedTileKeyParent1 = "colorTilesParent1[" + std::to_string(i) + "]";
|
||||
// Send uniforms for the tile to the shader
|
||||
programObject->setUniform(indexedTileKeyParent1 + ".textureSampler", texUnitColorParent1[i]);
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent1 + ".uvTransform.uvScale",
|
||||
tileParent1.uvTransform.uvScale);
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent1 + ".uvTransform.uvOffset",
|
||||
tileParent1.uvTransform.uvOffset);
|
||||
|
||||
|
||||
// The texture needs a unit to sample from
|
||||
texUnitColorParent2[i].activate();
|
||||
tileParent2.texture->bind();
|
||||
|
||||
std::string indexedTileKeyParent2 = "colorTilesParent2[" + std::to_string(i) + "]";
|
||||
// Send uniforms for the tile to the shader
|
||||
programObject->setUniform(indexedTileKeyParent2 + ".textureSampler", texUnitColorParent2[i]);
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent2 + ".uvTransform.uvScale",
|
||||
tileParent2.uvTransform.uvScale);
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent2 + ".uvTransform.uvOffset",
|
||||
tileParent2.uvTransform.uvOffset);
|
||||
|
||||
i++;
|
||||
}
|
||||
@@ -240,6 +301,12 @@ namespace openspace {
|
||||
* viewTransform * modelTransform;
|
||||
const Ellipsoid& ellipsoid = chunk.owner()->ellipsoid();
|
||||
|
||||
vec3 pointClosestToCamera = chunk.owner()->ellipsoid().cartesianSurfacePosition(chunk.surfacePatch().closestPoint(chunk.owner()->ellipsoid().cartesianToGeodetic2(data.camera.positionVec3())));
|
||||
|
||||
|
||||
|
||||
float distanceScaleFactor = chunk.owner()->lodScaleFactor * chunk.owner()->ellipsoid().minimumRadius();
|
||||
|
||||
// Upload the uniform variables
|
||||
programObject->setUniform("modelViewProjectionTransform", modelViewProjectionTransform);
|
||||
programObject->setUniform("minLatLon", vec2(swCorner.toLonLatVec2()));
|
||||
@@ -247,6 +314,14 @@ namespace openspace {
|
||||
programObject->setUniform("radiiSquared", vec3(ellipsoid.radiiSquared()));
|
||||
programObject->setUniform("xSegments", _grid->xSegments());
|
||||
|
||||
// The length of the skirts is proportional to its size
|
||||
programObject->setUniform("skirtLength", static_cast<float>(chunk.surfacePatch().halfSize().lat * 1000000));
|
||||
|
||||
programObject->setUniform("cameraPosition", vec3(data.camera.positionVec3()));
|
||||
programObject->setUniform("distanceScaleFactor", distanceScaleFactor);
|
||||
programObject->setUniform("chunkLevel", chunk.index().level);
|
||||
|
||||
|
||||
// OpenGL rendering settings
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_CULL_FACE);
|
||||
@@ -257,7 +332,6 @@ namespace openspace {
|
||||
|
||||
// disable shader
|
||||
programObject->deactivate();
|
||||
|
||||
}
|
||||
|
||||
void ChunkRenderer::renderChunkLocally(const Chunk& chunk, const RenderData& data)
|
||||
@@ -265,8 +339,8 @@ namespace openspace {
|
||||
using namespace glm;
|
||||
|
||||
// All providers of tiles
|
||||
auto heightMapProviders = _tileProviderManager->heightMapProviders();
|
||||
auto colorTextureProviders = _tileProviderManager->colorTextureProviders();
|
||||
auto heightMapProviders = _tileProviderManager->getActiveHeightMapProviders();
|
||||
auto colorTextureProviders = _tileProviderManager->getActiveColorTextureProviders();
|
||||
|
||||
int numHeightMapProviders = heightMapProviders.size();
|
||||
int numColorTextureProviders = colorTextureProviders.size();
|
||||
@@ -293,22 +367,31 @@ namespace openspace {
|
||||
// Activate the shader program
|
||||
programObject->activate();
|
||||
|
||||
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitHeight;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitHeightParent1;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitHeightParent2;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitColor;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitColorParent1;
|
||||
std::vector<ghoul::opengl::TextureUnit> texUnitColorParent2;
|
||||
|
||||
texUnitHeight.resize(numHeightMapProviders);
|
||||
texUnitHeightParent1.resize(numHeightMapProviders);
|
||||
texUnitHeightParent2.resize(numHeightMapProviders);
|
||||
texUnitColor.resize(numColorTextureProviders);
|
||||
texUnitColorParent1.resize(numColorTextureProviders);
|
||||
texUnitColorParent2.resize(numColorTextureProviders);
|
||||
|
||||
|
||||
// Go through all the height map providers
|
||||
int i = 0;
|
||||
for (auto it = heightMapProviders.begin(); it != heightMapProviders.end(); it++)
|
||||
{
|
||||
texUnitHeight.push_back(ghoul::opengl::TextureUnit());
|
||||
auto tileProvider = it->get();
|
||||
|
||||
// Get the texture that should be used for rendering
|
||||
Tile tile = tileProvider->getHighestResolutionTile(chunk.index());
|
||||
Tile tileParent1 = tileProvider->getHighestResolutionParentTile(chunk.index(), 1);
|
||||
Tile tileParent2 = tileProvider->getHighestResolutionParentTile(chunk.index(), 2);
|
||||
TileDepthTransform depthTransform = tileProvider->depthTransform();
|
||||
|
||||
// The texture needs a unit to sample from
|
||||
@@ -326,6 +409,40 @@ namespace openspace {
|
||||
indexedTileKey + ".uvTransform.uvOffset",
|
||||
tile.uvTransform.uvOffset);
|
||||
|
||||
// Blend tile with two parents
|
||||
// The texture needs a unit to sample from
|
||||
texUnitHeightParent1[i].activate();
|
||||
tileParent1.texture->bind();
|
||||
|
||||
std::string indexedTileKeyParent1 = "heightTilesParent1[" + std::to_string(i) + "]";
|
||||
// Send uniforms for the tile to the shader
|
||||
programObject->setUniform(indexedTileKeyParent1 + ".textureSampler", texUnitHeightParent1[i]);
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent1 + ".uvTransform.uvScale",
|
||||
tileParent1.uvTransform.uvScale);
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent1 + ".uvTransform.uvOffset",
|
||||
tileParent1.uvTransform.uvOffset);
|
||||
|
||||
|
||||
// The texture needs a unit to sample from
|
||||
texUnitHeightParent2[i].activate();
|
||||
tileParent2.texture->bind();
|
||||
|
||||
std::string indexedTileKeyParent2 = "heightTilesParent2[" + std::to_string(i) + "]";
|
||||
// Send uniforms for the tile to the shader
|
||||
programObject->setUniform(indexedTileKeyParent2 + ".textureSampler", texUnitHeightParent2[i]);
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent2 + ".uvTransform.uvScale",
|
||||
tileParent2.uvTransform.uvScale);
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent2 + ".uvTransform.uvOffset",
|
||||
tileParent2.uvTransform.uvOffset);
|
||||
|
||||
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKey + ".depthTransform.depthScale",
|
||||
depthTransform.depthScale);
|
||||
@@ -341,9 +458,10 @@ namespace openspace {
|
||||
for (auto it = colorTextureProviders.begin(); it != colorTextureProviders.end(); it++)
|
||||
{
|
||||
auto tileProvider = it->get();
|
||||
|
||||
// Get the texture that should be used for rendering
|
||||
Tile tile = tileProvider->getHighestResolutionTile(chunk.index());
|
||||
Tile tileParent1 = tileProvider->getHighestResolutionParentTile(chunk.index(), 1);
|
||||
Tile tileParent2 = tileProvider->getHighestResolutionParentTile(chunk.index(), 2);
|
||||
|
||||
// The texture needs a unit to sample from
|
||||
texUnitColor[i].activate();
|
||||
@@ -360,10 +478,41 @@ namespace openspace {
|
||||
indexedTileKey + ".uvTransform.uvOffset",
|
||||
tile.uvTransform.uvOffset);
|
||||
|
||||
// Blend tile with two parents
|
||||
// The texture needs a unit to sample from
|
||||
texUnitColorParent1[i].activate();
|
||||
tileParent1.texture->bind();
|
||||
|
||||
std::string indexedTileKeyParent1 = "colorTilesParent1[" + std::to_string(i) + "]";
|
||||
// Send uniforms for the tile to the shader
|
||||
programObject->setUniform(indexedTileKeyParent1 + ".textureSampler", texUnitColorParent1[i]);
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent1 + ".uvTransform.uvScale",
|
||||
tileParent1.uvTransform.uvScale);
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent1 + ".uvTransform.uvOffset",
|
||||
tileParent1.uvTransform.uvOffset);
|
||||
|
||||
|
||||
// The texture needs a unit to sample from
|
||||
texUnitColorParent2[i].activate();
|
||||
tileParent2.texture->bind();
|
||||
|
||||
std::string indexedTileKeyParent2 = "colorTilesParent2[" + std::to_string(i) + "]";
|
||||
// Send uniforms for the tile to the shader
|
||||
programObject->setUniform(indexedTileKeyParent2 + ".textureSampler", texUnitColorParent2[i]);
|
||||
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent2 + ".uvTransform.uvScale",
|
||||
tileParent2.uvTransform.uvScale);
|
||||
programObject->setUniform(
|
||||
indexedTileKeyParent2 + ".uvTransform.uvOffset",
|
||||
tileParent2.uvTransform.uvOffset);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
|
||||
// Calculate other uniform variables needed for rendering
|
||||
|
||||
// TODO : Model transform should be fetched as a matrix directly.
|
||||
@@ -409,6 +558,12 @@ namespace openspace {
|
||||
data.camera.projectionMatrix());
|
||||
|
||||
programObject->setUniform("xSegments", _grid->xSegments());
|
||||
// The length of the skirts is proportional to its size
|
||||
programObject->setUniform("skirtLength", static_cast<float>(chunk.surfacePatch().halfSize().lat * 1000000));
|
||||
|
||||
float distanceScaleFactor = chunk.owner()->lodScaleFactor * chunk.owner()->ellipsoid().minimumRadius();
|
||||
programObject->setUniform("distanceScaleFactor", distanceScaleFactor);
|
||||
programObject->setUniform("chunkLevel", chunk.index().level);
|
||||
|
||||
// OpenGL rendering settings
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
@@ -421,6 +576,4 @@ namespace openspace {
|
||||
// disable shader
|
||||
programObject->deactivate();
|
||||
}
|
||||
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -31,19 +31,49 @@
|
||||
#define NUMLAYERS_HEIGHTMAP #{numLayersHeight}
|
||||
|
||||
uniform TextureTile colorTiles[NUMLAYERS_COLORTEXTURE];
|
||||
uniform TextureTile colorTilesParent1[NUMLAYERS_COLORTEXTURE];
|
||||
uniform TextureTile colorTilesParent2[NUMLAYERS_COLORTEXTURE];
|
||||
|
||||
in vec4 fs_position;
|
||||
in vec2 fs_uv;
|
||||
|
||||
in vec3 positionWorldSpace;
|
||||
|
||||
uniform vec3 cameraPosition;
|
||||
uniform float distanceScaleFactor;
|
||||
uniform int chunkLevel;
|
||||
|
||||
Fragment getFragment() {
|
||||
Fragment frag;
|
||||
|
||||
#for i in 0..#{numLayersColor}
|
||||
// Calculate desired level based on distance
|
||||
float distToFrag = length(positionWorldSpace - cameraPosition);
|
||||
float projectedScaleFactor = distanceScaleFactor / distToFrag;
|
||||
float desiredLevel = log2(projectedScaleFactor);
|
||||
|
||||
// x increases with distance
|
||||
float x = chunkLevel - desiredLevel;
|
||||
float w1 = clamp(1 - x, 0 , 1);
|
||||
float w2 = (clamp(x, 0 , 1) - clamp(x - 1, 0 , 1));
|
||||
float w3 = clamp(x - 1, 0 , 1);
|
||||
|
||||
#for j in 1..#{numLayersColor}
|
||||
{
|
||||
int i = #{j} - 1;
|
||||
vec2 samplePos =
|
||||
colorTiles[#{i}].uvTransform.uvScale * fs_uv +
|
||||
colorTiles[#{i}].uvTransform.uvOffset;
|
||||
vec4 colorSample = texture(colorTiles[#{i}].textureSampler, samplePos);
|
||||
colorTiles[i].uvTransform.uvScale * fs_uv +
|
||||
colorTiles[i].uvTransform.uvOffset;
|
||||
vec2 samplePosParent1 =
|
||||
colorTilesParent1[i].uvTransform.uvScale * fs_uv +
|
||||
colorTilesParent1[i].uvTransform.uvOffset;
|
||||
vec2 samplePosParent2 =
|
||||
colorTilesParent2[i].uvTransform.uvScale * fs_uv +
|
||||
colorTilesParent2[i].uvTransform.uvOffset;
|
||||
|
||||
vec4 colorSample =
|
||||
w1 * texture(colorTiles[i].textureSampler, samplePos) +
|
||||
w2 * texture(colorTilesParent1[i].textureSampler, samplePosParent1) +
|
||||
w3 * texture(colorTilesParent2[i].textureSampler, samplePosParent2);
|
||||
frag.color = blendOver(frag.color, colorSample);
|
||||
}
|
||||
#endfor
|
||||
|
||||
@@ -39,13 +39,21 @@ uniform vec2 lonLatScalingFactor;
|
||||
|
||||
uniform int xSegments;
|
||||
uniform int ySegments;
|
||||
uniform float skirtLength;
|
||||
|
||||
uniform TextureTile heightTiles[NUMLAYERS_HEIGHTMAP];
|
||||
uniform TextureTile heightTilesParent1[NUMLAYERS_HEIGHTMAP];
|
||||
uniform TextureTile heightTilesParent2[NUMLAYERS_HEIGHTMAP];
|
||||
|
||||
uniform vec3 cameraPosition;
|
||||
uniform float distanceScaleFactor;
|
||||
uniform int chunkLevel;
|
||||
|
||||
layout(location = 1) in vec2 in_uv;
|
||||
|
||||
out vec2 fs_uv;
|
||||
out vec4 fs_position;
|
||||
out vec3 positionWorldSpace;
|
||||
|
||||
PositionNormalPair globalInterpolation() {
|
||||
vec2 lonLatInput;
|
||||
@@ -58,21 +66,43 @@ PositionNormalPair globalInterpolation() {
|
||||
void main()
|
||||
{
|
||||
PositionNormalPair pair = globalInterpolation();
|
||||
positionWorldSpace = pair.position;
|
||||
|
||||
float height = 0;
|
||||
|
||||
#for i in 0..#{numLayersHeight}
|
||||
{
|
||||
vec2 samplePos =
|
||||
heightTiles[#{i}].uvTransform.uvScale * in_uv +
|
||||
heightTiles[#{i}].uvTransform.uvOffset;
|
||||
// Calculate desired level based on distance
|
||||
float distToVertex = length(positionWorldSpace - cameraPosition);
|
||||
float projectedScaleFactor = distanceScaleFactor / distToVertex;
|
||||
float desiredLevel = log2(projectedScaleFactor);
|
||||
|
||||
float sampledValue = texture(heightTiles[#{i}].textureSampler, samplePos).r;
|
||||
// x increases with distance
|
||||
float x = chunkLevel - desiredLevel;
|
||||
float w1 = clamp(1 - x, 0 , 1);
|
||||
float w2 = (clamp(x, 0 , 1) - clamp(x - 1, 0 , 1));
|
||||
float w3 = clamp(x - 1, 0 , 1);
|
||||
|
||||
#for j in 1..#{numLayersHeight}
|
||||
{
|
||||
int i = #{j} - 1;
|
||||
vec2 samplePos =
|
||||
heightTiles[i].uvTransform.uvScale * in_uv +
|
||||
heightTiles[i].uvTransform.uvOffset;
|
||||
vec2 samplePosParent1 =
|
||||
heightTilesParent1[i].uvTransform.uvScale * in_uv +
|
||||
heightTilesParent1[i].uvTransform.uvOffset;
|
||||
vec2 samplePosParent2 =
|
||||
heightTilesParent2[i].uvTransform.uvScale * in_uv +
|
||||
heightTilesParent2[i].uvTransform.uvOffset;
|
||||
|
||||
float sampledValue =
|
||||
w1 * texture(heightTiles[i].textureSampler, samplePos).r +
|
||||
w2 * texture(heightTilesParent1[i].textureSampler, samplePosParent1).r +
|
||||
w3 * texture(heightTilesParent2[i].textureSampler, samplePosParent2).r;
|
||||
|
||||
// TODO : Some kind of blending here. Now it just writes over
|
||||
height = (sampledValue *
|
||||
heightTiles[#{i}].depthTransform.depthScale +
|
||||
heightTiles[#{i}].depthTransform.depthOffset);
|
||||
heightTiles[i].depthTransform.depthScale +
|
||||
heightTiles[i].depthTransform.depthOffset);
|
||||
|
||||
// Skirts
|
||||
int vertexIDx = gl_VertexID % (xSegments + 3);
|
||||
@@ -81,7 +111,7 @@ void main()
|
||||
vertexIDy == 0 ||
|
||||
vertexIDx == (xSegments + 2) ||
|
||||
vertexIDy == (xSegments + 2) ) {
|
||||
height = 0;
|
||||
height -= skirtLength;
|
||||
}
|
||||
}
|
||||
#endfor
|
||||
|
||||
@@ -31,19 +31,48 @@
|
||||
#define NUMLAYERS_HEIGHTMAP #{numLayersHeight}
|
||||
|
||||
uniform TextureTile colorTiles[NUMLAYERS_COLORTEXTURE];
|
||||
uniform TextureTile colorTilesParent1[NUMLAYERS_COLORTEXTURE];
|
||||
uniform TextureTile colorTilesParent2[NUMLAYERS_COLORTEXTURE];
|
||||
|
||||
in vec4 fs_position;
|
||||
in vec2 fs_uv;
|
||||
in vec3 positionCameraSpace;
|
||||
|
||||
uniform float distanceScaleFactor;
|
||||
uniform int chunkLevel;
|
||||
|
||||
Fragment getFragment() {
|
||||
Fragment frag;
|
||||
|
||||
#for i in 0..#{numLayersColor}
|
||||
// Calculate desired level based on distance
|
||||
float distToFrag = length(positionCameraSpace);
|
||||
float projectedScaleFactor = distanceScaleFactor / distToFrag;
|
||||
float desiredLevel = log2(projectedScaleFactor);
|
||||
|
||||
// x increases with distance
|
||||
float x = chunkLevel - desiredLevel;
|
||||
float w1 = clamp(1 - x, 0 , 1);
|
||||
float w2 = (clamp(x, 0 , 1) - clamp(x - 1, 0 , 1));
|
||||
float w3 = clamp(x - 1, 0 , 1);
|
||||
|
||||
#for j in 1..#{numLayersColor}
|
||||
{
|
||||
int i = #{j} - 1;
|
||||
|
||||
vec2 samplePos =
|
||||
colorTiles[#{i}].uvTransform.uvScale * fs_uv +
|
||||
colorTiles[#{i}].uvTransform.uvOffset;
|
||||
vec4 colorSample = texture(colorTiles[#{i}].textureSampler, samplePos);
|
||||
colorTiles[i].uvTransform.uvScale * fs_uv +
|
||||
colorTiles[i].uvTransform.uvOffset;
|
||||
vec2 samplePosParent1 =
|
||||
colorTilesParent1[i].uvTransform.uvScale * fs_uv +
|
||||
colorTilesParent1[i].uvTransform.uvOffset;
|
||||
vec2 samplePosParent2 =
|
||||
colorTilesParent2[i].uvTransform.uvScale * fs_uv +
|
||||
colorTilesParent2[i].uvTransform.uvOffset;
|
||||
|
||||
vec4 colorSample =
|
||||
w1 * texture(colorTiles[i].textureSampler, samplePos) +
|
||||
w2 * texture(colorTilesParent1[i].textureSampler, samplePosParent1) +
|
||||
w3 * texture(colorTilesParent2[i].textureSampler, samplePosParent2);
|
||||
frag.color = blendOver(frag.color, colorSample);
|
||||
}
|
||||
#endfor
|
||||
|
||||
@@ -41,14 +41,21 @@ uniform vec3 p11;
|
||||
uniform vec3 patchNormalCameraSpace;
|
||||
|
||||
uniform TextureTile heightTiles[NUMLAYERS_HEIGHTMAP];
|
||||
uniform TextureTile heightTilesParent1[NUMLAYERS_HEIGHTMAP];
|
||||
uniform TextureTile heightTilesParent2[NUMLAYERS_HEIGHTMAP];
|
||||
|
||||
uniform int xSegments;
|
||||
uniform int ySegments;
|
||||
uniform float skirtLength;
|
||||
|
||||
uniform float distanceScaleFactor;
|
||||
uniform int chunkLevel;
|
||||
|
||||
layout(location = 1) in vec2 in_uv;
|
||||
|
||||
out vec2 fs_uv;
|
||||
out vec4 fs_position;
|
||||
out vec3 positionCameraSpace;
|
||||
|
||||
vec3 bilinearInterpolation(vec2 uv) {
|
||||
// Bilinear interpolation
|
||||
@@ -65,18 +72,41 @@ void main()
|
||||
|
||||
float height = 0;
|
||||
|
||||
#for i in 0..#{numLayersHeight}
|
||||
{
|
||||
vec2 samplePos =
|
||||
heightTiles[#{i}].uvTransform.uvScale * in_uv +
|
||||
heightTiles[#{i}].uvTransform.uvOffset;
|
||||
positionCameraSpace = p;
|
||||
|
||||
float sampledValue = texture(heightTiles[#{i}].textureSampler, samplePos).r;
|
||||
// Calculate desired level based on distance
|
||||
float distToVertex = length(positionCameraSpace);
|
||||
float projectedScaleFactor = distanceScaleFactor / distToVertex;
|
||||
float desiredLevel = log2(projectedScaleFactor);
|
||||
|
||||
// x increases with distance
|
||||
float x = chunkLevel - desiredLevel;
|
||||
float w1 = clamp(1 - x, 0 , 1);
|
||||
float w2 = (clamp(x, 0 , 1) - clamp(x - 1, 0 , 1));
|
||||
float w3 = clamp(x - 1, 0 , 1);
|
||||
|
||||
#for j in 1..#{numLayersHeight}
|
||||
{
|
||||
int i = #{j} - 1;
|
||||
vec2 samplePos =
|
||||
heightTiles[i].uvTransform.uvScale * in_uv +
|
||||
heightTiles[i].uvTransform.uvOffset;
|
||||
vec2 samplePosParent1 =
|
||||
heightTilesParent1[i].uvTransform.uvScale * in_uv +
|
||||
heightTilesParent1[i].uvTransform.uvOffset;
|
||||
vec2 samplePosParent2 =
|
||||
heightTilesParent2[i].uvTransform.uvScale * in_uv +
|
||||
heightTilesParent2[i].uvTransform.uvOffset;
|
||||
|
||||
float sampledValue =
|
||||
w1 * texture(heightTiles[i].textureSampler, samplePos).r +
|
||||
w2 * texture(heightTilesParent1[i].textureSampler, samplePosParent1).r +
|
||||
w3 * texture(heightTilesParent2[i].textureSampler, samplePosParent2).r;
|
||||
|
||||
// TODO : Some kind of blending here. Now it just writes over
|
||||
height = (sampledValue *
|
||||
heightTiles[#{i}].depthTransform.depthScale +
|
||||
heightTiles[#{i}].depthTransform.depthOffset);
|
||||
heightTiles[i].depthTransform.depthScale +
|
||||
heightTiles[i].depthTransform.depthOffset);
|
||||
|
||||
// Skirts
|
||||
int vertexIDx = gl_VertexID % (xSegments + 3);
|
||||
@@ -85,7 +115,7 @@ void main()
|
||||
vertexIDy == 0 ||
|
||||
vertexIDx == (xSegments + 2) ||
|
||||
vertexIDy == (xSegments + 2) ) {
|
||||
height = 0;
|
||||
height -= skirtLength;
|
||||
}
|
||||
}
|
||||
#endfor
|
||||
|
||||
Reference in New Issue
Block a user