diff --git a/src/noggit/World.cpp b/src/noggit/World.cpp index 580ef111..fe1edd00 100755 --- a/src/noggit/World.cpp +++ b/src/noggit/World.cpp @@ -390,25 +390,11 @@ void World::rotate_selected_models_randomly(float minX, float maxX, float minY, } } - -void World::rotate_selected_models_to_ground_normal(bool smoothNormals) +void World::rotate_model_to_ground_normal(SceneObject* obj, bool smoothNormals) { - ZoneScoped; - if (!_selected_model_count) - return; - selection_updated = true; - for (auto& entry : _current_selection) - { - auto type = entry.index(); - if (type == eEntry_MapChunk) - { - continue; - } - - auto& obj = std::get(entry); NOGGIT_CUR_ACTION->registerObjectTransformed(obj); - updateTilesEntry(entry, model_update::remove); + updateTilesEntry(obj, model_update::remove); glm::vec3 rayPos = obj->pos; math::degrees::vec3& dir = obj->dir; @@ -416,43 +402,43 @@ void World::rotate_selected_models_to_ground_normal(bool smoothNormals) selection_result results; for_chunk_at(rayPos, [&](MapChunk* chunk) - { { - math::ray intersect_ray(rayPos, glm::vec3(0.f, -1.f, 0.f)); - chunk->intersect(intersect_ray, &results); - } - // object is below ground - if (results.empty()) - { - math::ray intersect_ray(rayPos, glm::vec3(0.f, 1.f, 0.f)); - chunk->intersect(intersect_ray, &results); - } - }); + { + math::ray intersect_ray(rayPos, glm::vec3(0.f, -1.f, 0.f)); + chunk->intersect(intersect_ray, &results); + } + // object is below ground + if (results.empty()) + { + math::ray intersect_ray(rayPos, glm::vec3(0.f, 1.f, 0.f)); + chunk->intersect(intersect_ray, &results); + } + }); // !\ todo We shouldn't end up with empty ever (but we do, on completely flat ground) if (results.empty()) { - // just to avoid models disappearing when this happens - updateTilesEntry(entry, model_update::add); - continue; + // just to avoid models disappearing when this happens + updateTilesEntry(obj, model_update::add); + return; } -// We hit the terrain, now we take the normal of this position and use it to get the rotation we want. + // We hit the terrain, now we take the normal of this position and use it to get the rotation we want. auto const& hitChunkInfo = std::get(results.front().second); glm::quat q; glm::vec3 varnormal; // Surface Normal - auto &p0 = hitChunkInfo.chunk->mVertices[std::get<0>(hitChunkInfo.triangle)]; - auto &p1 = hitChunkInfo.chunk->mVertices[std::get<1>(hitChunkInfo.triangle)]; - auto &p2 = hitChunkInfo.chunk->mVertices[std::get<2>(hitChunkInfo.triangle)]; + auto& p0 = hitChunkInfo.chunk->mVertices[std::get<0>(hitChunkInfo.triangle)]; + auto& p1 = hitChunkInfo.chunk->mVertices[std::get<1>(hitChunkInfo.triangle)]; + auto& p2 = hitChunkInfo.chunk->mVertices[std::get<2>(hitChunkInfo.triangle)]; glm::vec3 v1 = p1 - p0; glm::vec3 v2 = p2 - p0; - auto tmpVec = glm::cross(v2 ,v1); + auto tmpVec = glm::cross(v2, v1); varnormal.x = tmpVec.z; varnormal.y = tmpVec.y; varnormal.z = tmpVec.x; @@ -460,34 +446,34 @@ void World::rotate_selected_models_to_ground_normal(bool smoothNormals) // Smooth option, gradient the normal towards closest vertex if (smoothNormals) // Vertex Normal { - auto normalWeights = getBarycentricCoordinatesAt(p0, p1, p2, hitChunkInfo.position, varnormal); + auto normalWeights = getBarycentricCoordinatesAt(p0, p1, p2, hitChunkInfo.position, varnormal); - auto& tile_buffer = hitChunkInfo.chunk->mt->getChunkHeightmapBuffer(); - int chunk_start = (hitChunkInfo.chunk->px * 16 + hitChunkInfo.chunk->py) * mapbufsize * 4; + auto& tile_buffer = hitChunkInfo.chunk->mt->getChunkHeightmapBuffer(); + int chunk_start = (hitChunkInfo.chunk->px * 16 + hitChunkInfo.chunk->py) * mapbufsize * 4; - const auto& vNormal0 = *reinterpret_cast(&tile_buffer[chunk_start + std::get<0>(hitChunkInfo.triangle) * 4]); - const auto& vNormal1 = *reinterpret_cast(&tile_buffer[chunk_start + std::get<1>(hitChunkInfo.triangle) * 4]); - const auto& vNormal2 = *reinterpret_cast(&tile_buffer[chunk_start + std::get<2>(hitChunkInfo.triangle) * 4]); + const auto& vNormal0 = *reinterpret_cast(&tile_buffer[chunk_start + std::get<0>(hitChunkInfo.triangle) * 4]); + const auto& vNormal1 = *reinterpret_cast(&tile_buffer[chunk_start + std::get<1>(hitChunkInfo.triangle) * 4]); + const auto& vNormal2 = *reinterpret_cast(&tile_buffer[chunk_start + std::get<2>(hitChunkInfo.triangle) * 4]); - varnormal.x = - vNormal0.x * normalWeights.x + - vNormal1.x * normalWeights.y + - vNormal2.x * normalWeights.z; + varnormal.x = + vNormal0.x * normalWeights.x + + vNormal1.x * normalWeights.y + + vNormal2.x * normalWeights.z; - varnormal.y = - vNormal0.y * normalWeights.x + - vNormal1.y * normalWeights.y + - vNormal2.y * normalWeights.z; + varnormal.y = + vNormal0.y * normalWeights.x + + vNormal1.y * normalWeights.y + + vNormal2.y * normalWeights.z; - varnormal.z = - vNormal0.z * normalWeights.x + - vNormal1.z * normalWeights.y + - vNormal2.z * normalWeights.z; + varnormal.z = + vNormal0.z * normalWeights.x + + vNormal1.z * normalWeights.y + + vNormal2.z * normalWeights.z; } glm::vec3 worldUp = glm::vec3(0, 1, 0); - glm::vec3 a =glm::cross(worldUp ,varnormal); + glm::vec3 a = glm::cross(worldUp, varnormal); q.x = a.x; q.y = a.y; @@ -527,12 +513,31 @@ void World::rotate_selected_models_to_ground_normal(bool smoothNormals) dir.y = math::degrees(math::radians(eulerAngles.x))._; //Pitch dir.z = math::degrees(math::radians(eulerAngles.y))._; //Yaw - std::get(entry)->recalcExtents(); + obj->recalcExtents(); // yaw (z-axis rotation) double siny_cosp = 2 * (q.w * q.z + q.x * q.y); double cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z); - updateTilesEntry(entry, model_update::add); + updateTilesEntry(obj, model_update::add); +} + +void World::rotate_selected_models_to_ground_normal(bool smoothNormals) +{ + ZoneScoped; + if (!_selected_model_count) + return; + selection_updated = true; + for (auto& entry : _current_selection) + { + auto type = entry.index(); + if (type == eEntry_MapChunk) + { + continue; + } + + auto& obj = std::get(entry); + + rotate_model_to_ground_normal(obj, smoothNormals); } update_selected_model_groups(); } diff --git a/src/noggit/World.h b/src/noggit/World.h index 474749fc..e01685ea 100755 --- a/src/noggit/World.h +++ b/src/noggit/World.h @@ -175,6 +175,7 @@ public: // Checks the normal of the terrain on model origin and rotates to that spot. void rotate_selected_models_to_ground_normal(bool smoothNormals); + void rotate_model_to_ground_normal(SceneObject* obj, bool smoothNormals); bool GetVertex(float x, float z, glm::vec3 *V) const; diff --git a/src/noggit/ui/ObjectEditor.cpp b/src/noggit/ui/ObjectEditor.cpp index f35b3af3..a7471e6b 100755 --- a/src/noggit/ui/ObjectEditor.cpp +++ b/src/noggit/ui/ObjectEditor.cpp @@ -203,6 +203,8 @@ namespace Noggit QRadioButton *selectionButton = new QRadioButton("Selection"); QRadioButton *cameraButton = new QRadioButton("Camera"); + QCheckBox* paste_override_rotate_cb = new QCheckBox("Rotate to Terrain", this); + pasteModeGroup = new QButtonGroup(this); pasteModeGroup->addButton(terrainButton, 0); pasteModeGroup->addButton(selectionButton, 1); @@ -212,6 +214,8 @@ namespace Noggit paste_layout->addWidget(selectionButton, 0, 1); paste_layout->addWidget(cameraButton, 1, 0); + paste_layout->addWidget(paste_override_rotate_cb, 2, 0); + pasteBox->addPage(pasteBox_content); auto object_movement_box (new QGroupBox("Single Selection Movement", this)); @@ -469,6 +473,12 @@ namespace Noggit _use_median_pivot_point = b; }); + paste_override_rotate_cb->setChecked(paste_params->rotate_on_terrain); + connect(paste_override_rotate_cb, &QCheckBox::stateChanged, [=](int s) + { + paste_params->rotate_on_terrain = s; + }); + connect ( pasteModeGroup, qOverload (&QButtonGroup::idClicked) , [&] (int id) { @@ -658,6 +668,12 @@ namespace Noggit new_obj->model->waitForChildrenLoaded(); new_obj->recalcExtents(); + if (paste_params->rotate_on_terrain) + { + // new_obj->pos.y = world->get_ground_height(new_obj->pos).y;// in multi select, objects aren't on the ground + world->rotate_model_to_ground_normal(new_obj, true); // always smooth? + } + // check if pos is valid (not in an interior) wmo group // bool is_indoor = world->isInIndoorWmoGroup(new_obj->extents, new_obj->transformMatrix()); bool is_indoor = false; // TODO Disabled the indoor check until non axis aligned boxes check is implemented @@ -685,6 +701,11 @@ namespace Noggit new_obj->wmo->wait_until_loaded(); new_obj->wmo->waitForChildrenLoaded(); new_obj->recalcExtents(); + + if (paste_params->rotate_on_terrain) + { + world->rotate_model_to_ground_normal(new_obj, true); // always smooth? + } } } } diff --git a/src/noggit/ui/ObjectEditor.h b/src/noggit/ui/ObjectEditor.h index ad897510..0801997e 100755 --- a/src/noggit/ui/ObjectEditor.h +++ b/src/noggit/ui/ObjectEditor.h @@ -44,6 +44,7 @@ namespace Noggit float maxTilt = 5.f; float minScale = 0.9f; float maxScale = 1.1f; + bool rotate_on_terrain = true; }; namespace Ui