1180 lines
41 KiB
C++
1180 lines
41 KiB
C++
/*
|
|
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
|
|
For a list of contributors, see the accompanying CONTRIBUTORS file.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "patchmanip.h"
|
|
|
|
#include "debugging/debugging.h"
|
|
|
|
|
|
#include "iselection.h"
|
|
#include "ipatch.h"
|
|
|
|
#include "math/vector.h"
|
|
#include "math/aabb.h"
|
|
#include "generic/callback.h"
|
|
|
|
#include "gtkutil/menu.h"
|
|
#include "gtkutil/image.h"
|
|
#include "map.h"
|
|
#include "mainframe.h"
|
|
#include "commands.h"
|
|
#include "gtkmisc.h"
|
|
#include "gtkdlgs.h"
|
|
#include "texwindow.h"
|
|
#include "xywindow.h"
|
|
#include "select.h"
|
|
#include "patch.h"
|
|
#include "grid.h"
|
|
#include "patchdialog.h"
|
|
|
|
PatchCreator* g_patchCreator = 0;
|
|
|
|
void Scene_PatchConstructPrefab( scene::Graph& graph, const AABB aabb, const char* shader, EPatchPrefab eType, int axis, std::size_t width = 3, std::size_t height = 3, bool redisperse = false ){
|
|
Select_Delete();
|
|
GlobalSelectionSystem().setSelectedAll( false );
|
|
|
|
NodeSmartReference node( g_patchCreator->createPatch() );
|
|
Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( node );
|
|
|
|
Patch* patch = Node_getPatch( node );
|
|
patch->SetShader( shader );
|
|
|
|
patch->ConstructPrefab( aabb, eType, axis, width, height );
|
|
if( redisperse ){
|
|
patch->Redisperse( COL );
|
|
patch->Redisperse( ROW );
|
|
}
|
|
patch->controlPointsChanged();
|
|
|
|
{
|
|
scene::Path patchpath( makeReference( GlobalSceneGraph().root() ) );
|
|
patchpath.push( makeReference( *Map_GetWorldspawn( g_map ) ) );
|
|
patchpath.push( makeReference( node.get() ) );
|
|
Instance_getSelectable( *graph.find( patchpath ) )->setSelected( true );
|
|
}
|
|
}
|
|
|
|
|
|
void Patch_makeCaps( Patch& patch, scene::Instance& instance, EPatchCap type, const char* shader ){
|
|
if ( ( type == EPatchCap::EndCap || type == EPatchCap::IEndCap )
|
|
&& patch.getWidth() != 5 ) {
|
|
globalErrorStream() << "cannot create end-cap - patch width != 5\n";
|
|
return;
|
|
}
|
|
if ( ( type == EPatchCap::Bevel || type == EPatchCap::IBevel )
|
|
&& patch.getWidth() != 3 && patch.getWidth() != 5 ) {
|
|
globalErrorStream() << "cannot create bevel-cap - patch width != 3\n";
|
|
return;
|
|
}
|
|
if ( type == EPatchCap::Cylinder
|
|
&& patch.getWidth() != 9 ) {
|
|
globalErrorStream() << "cannot create cylinder-cap - patch width != 9\n";
|
|
return;
|
|
}
|
|
|
|
{
|
|
NodeSmartReference cap( g_patchCreator->createPatch() );
|
|
Node_getTraversable( instance.path().parent() )->insert( cap );
|
|
|
|
patch.MakeCap( Node_getPatch( cap ), type, ROW, true );
|
|
Node_getPatch( cap )->SetShader( shader );
|
|
|
|
scene::Path path( instance.path() );
|
|
path.pop();
|
|
path.push( makeReference( cap.get() ) );
|
|
selectPath( path, true );
|
|
}
|
|
|
|
{
|
|
NodeSmartReference cap( g_patchCreator->createPatch() );
|
|
Node_getTraversable( instance.path().parent() )->insert( cap );
|
|
|
|
patch.MakeCap( Node_getPatch( cap ), type, ROW, false );
|
|
Node_getPatch( cap )->SetShader( shader );
|
|
|
|
scene::Path path( instance.path() );
|
|
path.pop();
|
|
path.push( makeReference( cap.get() ) );
|
|
selectPath( path, true );
|
|
}
|
|
}
|
|
|
|
|
|
typedef std::vector<scene::Instance*> InstanceVector;
|
|
|
|
class PatchStoreInstance
|
|
{
|
|
InstanceVector& m_instances;
|
|
public:
|
|
PatchStoreInstance( InstanceVector& instances ) : m_instances( instances ){
|
|
}
|
|
void operator()( PatchInstance& patch ) const {
|
|
m_instances.push_back( &patch );
|
|
}
|
|
};
|
|
|
|
void Scene_PatchDoCap_Selected( scene::Graph& graph, const char* shader, EPatchCap type ){
|
|
InstanceVector instances;
|
|
Scene_forEachVisibleSelectedPatchInstance( PatchStoreInstance( instances ) );
|
|
for ( auto i : instances )
|
|
{
|
|
Patch_makeCaps( *Node_getPatch( i->path().top() ), *i, type, shader );
|
|
}
|
|
}
|
|
|
|
void Patch_deform( Patch& patch, scene::Instance& instance, const int deform, const int axis ){
|
|
patch.undoSave();
|
|
|
|
for ( PatchControlIter i = patch.begin(); i != patch.end(); ++i ){
|
|
PatchControl& control = *i;
|
|
int randomNumber = int( deform * ( float( std::rand() ) / float( RAND_MAX ) ) );
|
|
control.m_vertex[ axis ] += randomNumber;
|
|
}
|
|
|
|
patch.controlPointsChanged();
|
|
}
|
|
|
|
void Scene_PatchDeform( scene::Graph& graph, const int deform, const int axis )
|
|
{
|
|
InstanceVector instances;
|
|
Scene_forEachVisibleSelectedPatchInstance( PatchStoreInstance( instances ) );
|
|
for ( auto i : instances )
|
|
{
|
|
Patch_deform( *Node_getPatch( i->path().top() ), *i, deform, axis );
|
|
}
|
|
|
|
}
|
|
|
|
void Patch_thicken( Patch& patch, scene::Instance& instance, const float thickness, bool seams, const int axis ){
|
|
|
|
const auto aabb_small = []( const AABB& aabb ){
|
|
return ( aabb.extents[0] < 0.01 && aabb.extents[1] < 0.01 ) ||
|
|
( aabb.extents[1] < 0.01 && aabb.extents[2] < 0.01 ) ||
|
|
( aabb.extents[0] < 0.01 && aabb.extents[2] < 0.01 );
|
|
};
|
|
|
|
// Create a new patch node
|
|
NodeSmartReference node( g_patchCreator->createPatch() );
|
|
// Insert the node into original's entity
|
|
Node_getTraversable( instance.path().parent() )->insert( node );
|
|
|
|
// Retrieve the contained patch from the node
|
|
Patch* targetPatch = Node_getPatch( node );
|
|
|
|
// Create the opposite patch with the given thickness = distance
|
|
bool no12 = true;
|
|
bool no34 = true;
|
|
targetPatch->createThickenedOpposite( patch, thickness, axis, no12, no34 );
|
|
|
|
{ // Now select the newly created patch
|
|
scene::Path path( instance.parent()->path() );
|
|
path.push( makeReference( node.get() ) );
|
|
selectPath( path, true );
|
|
}
|
|
|
|
if( seams && thickness != 0.0f ){
|
|
int i = no12? 2 : 0;
|
|
int iend = no34? 2 : 4;
|
|
// Now create the four walls
|
|
for ( ; i < iend; ++i ){
|
|
// Allocate new patch
|
|
NodeSmartReference node = NodeSmartReference( g_patchCreator->createPatch() );
|
|
// Insert each node into worldspawn
|
|
Node_getTraversable( instance.path().parent() )->insert( node );
|
|
|
|
// Retrieve the contained patch from the node
|
|
Patch* wallPatch = Node_getPatch( node );
|
|
|
|
// Create the wall patch by passing i as wallIndex
|
|
wallPatch->createThickenedWall( patch, *targetPatch, i );
|
|
|
|
if( aabb_small( wallPatch->localAABB() ) ){
|
|
//globalOutputStream() << "Thicken: Discarding degenerate patch.\n";
|
|
Node_getTraversable( instance.path().parent() )->erase( node );
|
|
}
|
|
else { // Now select the newly created patch
|
|
scene::Path path( instance.parent()->path() );
|
|
path.push( makeReference( node.get() ) );
|
|
selectPath( path, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Invert the target patch so that it faces the opposite direction
|
|
targetPatch->InvertMatrix();
|
|
|
|
if( aabb_small( targetPatch->localAABB() ) ){
|
|
//globalOutputStream() << "Thicken: Discarding degenerate patch.\n";
|
|
Node_getTraversable( instance.path().parent() )->erase( node );
|
|
}
|
|
}
|
|
|
|
void Scene_PatchThicken( scene::Graph& graph, const int thickness, bool seams, const int axis )
|
|
{
|
|
InstanceVector instances;
|
|
Scene_forEachVisibleSelectedPatchInstance( PatchStoreInstance( instances ) );
|
|
for ( auto i : instances )
|
|
{
|
|
Patch_thicken( *Node_getPatch( i->path().top() ), *i, thickness, seams, axis );
|
|
}
|
|
|
|
}
|
|
|
|
Patch* Scene_GetUltimateSelectedVisiblePatch(){
|
|
if ( GlobalSelectionSystem().countSelected() != 0 ) {
|
|
scene::Node& node = GlobalSelectionSystem().ultimateSelected().path().top();
|
|
if ( node.visible() ) {
|
|
return Node_getPatch( node );
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Scene_PatchCapTexture_Selected( scene::Graph& graph ){
|
|
Scene_forEachVisibleSelectedPatch( []( Patch& patch ){ patch.CapTexture(); } );
|
|
SceneChangeNotify();
|
|
}
|
|
|
|
void Scene_PatchProjectTexture_Selected( scene::Graph& graph, const texdef_t& texdef, const Vector3* direction ){
|
|
Scene_forEachVisibleSelectedPatch( [texdef, direction]( Patch& patch ){ patch.ProjectTexture( texdef, direction ); } );
|
|
SceneChangeNotify();
|
|
}
|
|
|
|
void Scene_PatchProjectTexture_Selected( scene::Graph& graph, const TextureProjection& projection, const Vector3& normal ){
|
|
Scene_forEachVisibleSelectedPatch( [projection, normal]( Patch& patch ){ patch.ProjectTexture( projection, normal ); } );
|
|
SceneChangeNotify();
|
|
}
|
|
|
|
void Scene_PatchFlipTexture_Selected( scene::Graph& graph, int axis ){
|
|
Scene_forEachVisibleSelectedPatch( [axis]( Patch& patch ){ patch.FlipTexture( axis ); } );
|
|
}
|
|
|
|
void Scene_PatchNaturalTexture_Selected( scene::Graph& graph ){
|
|
Scene_forEachVisibleSelectedPatch( []( Patch& patch ){ patch.NaturalTexture(); } );
|
|
SceneChangeNotify();
|
|
}
|
|
|
|
void Scene_PatchTileTexture_Selected( scene::Graph& graph, float s, float t ){
|
|
Scene_forEachVisibleSelectedPatch( [s, t]( Patch& patch ){ patch.SetTextureRepeat( s, t ); } );
|
|
SceneChangeNotify();
|
|
}
|
|
|
|
|
|
void Scene_PatchInsertRemove_Selected( scene::Graph& graph, bool bInsert, bool bColumn, bool bFirst ){
|
|
Scene_forEachVisibleSelectedPatch( [bInsert, bColumn, bFirst]( Patch& patch ){ patch.InsertRemove( bInsert, bColumn, bFirst ); } );
|
|
}
|
|
|
|
void Scene_PatchInvert_Selected( scene::Graph& graph ){
|
|
Scene_forEachVisibleSelectedPatch( []( Patch& patch ){ patch.InvertMatrix(); } );
|
|
}
|
|
|
|
void Scene_PatchRedisperse_Selected( scene::Graph& graph, EMatrixMajor major ){
|
|
Scene_forEachVisibleSelectedPatch( [major]( Patch& patch ){ patch.Redisperse( major ); } );
|
|
}
|
|
|
|
void Scene_PatchSmooth_Selected( scene::Graph& graph, EMatrixMajor major ){
|
|
Scene_forEachVisibleSelectedPatch( [major]( Patch& patch ){ patch.Smooth( major ); } );
|
|
}
|
|
|
|
void Scene_PatchTranspose_Selected( scene::Graph& graph ){
|
|
Scene_forEachVisibleSelectedPatch( []( Patch& patch ){ patch.TransposeMatrix(); } );
|
|
}
|
|
|
|
void Scene_PatchSetShader_Selected( scene::Graph& graph, const char* name ){
|
|
Scene_forEachVisibleSelectedPatch( [name]( Patch& patch ){ patch.SetShader( name ); } );
|
|
SceneChangeNotify();
|
|
}
|
|
|
|
void Scene_PatchGetShader_Selected( scene::Graph& graph, CopiedString& name ){
|
|
if ( Patch* patch = Scene_GetUltimateSelectedVisiblePatch() )
|
|
name = patch->GetShader();
|
|
}
|
|
|
|
class PatchSelectByShader
|
|
{
|
|
const char* m_name;
|
|
public:
|
|
inline PatchSelectByShader( const char* name )
|
|
: m_name( name ){
|
|
}
|
|
void operator()( PatchInstance& patch ) const {
|
|
if ( shader_equal( patch.getPatch().GetShader(), m_name ) ) {
|
|
patch.setSelected( true );
|
|
}
|
|
}
|
|
};
|
|
|
|
void Scene_PatchSelectByShader( scene::Graph& graph, const char* name ){
|
|
Scene_forEachVisiblePatchInstance( PatchSelectByShader( name ) );
|
|
}
|
|
|
|
|
|
class PatchFindReplaceShader
|
|
{
|
|
const char* m_find;
|
|
const char* m_replace;
|
|
public:
|
|
PatchFindReplaceShader( const char* find, const char* replace ) : m_find( find ), m_replace( replace ){
|
|
}
|
|
void operator()( Patch& patch ) const {
|
|
if ( shader_equal( patch.GetShader(), m_find ) ) {
|
|
patch.SetShader( m_replace );
|
|
}
|
|
}
|
|
};
|
|
|
|
void Scene_PatchFindReplaceShader( scene::Graph& graph, const char* find, const char* replace ){
|
|
if( !replace ){
|
|
Scene_forEachVisiblePatchInstance( PatchSelectByShader( find ) );
|
|
}
|
|
else{
|
|
Scene_forEachVisiblePatch( PatchFindReplaceShader( find, replace ) );
|
|
}
|
|
}
|
|
|
|
void Scene_PatchFindReplaceShader_Selected( scene::Graph& graph, const char* find, const char* replace ){
|
|
if( !replace ){
|
|
//do nothing, because alternative is replacing to notex
|
|
//perhaps deselect ones with not matching shaders here?
|
|
}
|
|
else{
|
|
Scene_forEachVisibleSelectedPatch( PatchFindReplaceShader( find, replace ) );
|
|
}
|
|
}
|
|
|
|
|
|
AABB PatchCreator_getBounds(){
|
|
AABB aabb( aabb_for_minmax( Select_getWorkZone().d_work_min, Select_getWorkZone().d_work_max ) );
|
|
|
|
float gridSize = GetGridSize();
|
|
|
|
if ( aabb.extents[0] == 0 ) {
|
|
aabb.extents[0] = gridSize;
|
|
}
|
|
if ( aabb.extents[1] == 0 ) {
|
|
aabb.extents[1] = gridSize;
|
|
}
|
|
if ( aabb.extents[2] == 0 ) {
|
|
aabb.extents[2] = gridSize;
|
|
}
|
|
|
|
if ( aabb_valid( aabb ) ) {
|
|
return aabb;
|
|
}
|
|
return AABB( Vector3( 0, 0, 0 ), Vector3( 64, 64, 64 ) );
|
|
}
|
|
|
|
void DoNewPatchDlg( EPatchPrefab prefab, int minrows, int mincols, int defrows, int defcols, int maxrows, int maxcols );
|
|
|
|
void Patch_XactCylinder(){
|
|
UndoableCommand undo( "patchCreateXactCylinder" );
|
|
|
|
DoNewPatchDlg( EPatchPrefab::ExactCylinder, 3, 7, 3, 13, 0, 0 );
|
|
}
|
|
|
|
void Patch_XactSphere(){
|
|
UndoableCommand undo( "patchCreateXactSphere" );
|
|
|
|
DoNewPatchDlg( EPatchPrefab::ExactSphere, 5, 7, 7, 13, 0, 0 );
|
|
}
|
|
|
|
void Patch_XactCone(){
|
|
UndoableCommand undo( "patchCreateXactCone" );
|
|
|
|
DoNewPatchDlg( EPatchPrefab::ExactCone, 3, 7, 3, 13, 0, 0 );
|
|
}
|
|
|
|
void Patch_Cylinder(){
|
|
UndoableCommand undo( "patchCreateCylinder" );
|
|
|
|
Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(), EPatchPrefab::Cylinder, GlobalXYWnd_getCurrentViewType() );
|
|
}
|
|
|
|
void Patch_DenseCylinder(){
|
|
UndoableCommand undo( "patchCreateDenseCylinder" );
|
|
|
|
Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(), EPatchPrefab::DenseCylinder, GlobalXYWnd_getCurrentViewType() );
|
|
}
|
|
|
|
void Patch_VeryDenseCylinder(){
|
|
UndoableCommand undo( "patchCreateVeryDenseCylinder" );
|
|
|
|
Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(), EPatchPrefab::VeryDenseCylinder, GlobalXYWnd_getCurrentViewType() );
|
|
}
|
|
|
|
void Patch_SquareCylinder(){
|
|
UndoableCommand undo( "patchCreateSquareCylinder" );
|
|
|
|
Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(), EPatchPrefab::SqCylinder, GlobalXYWnd_getCurrentViewType() );
|
|
}
|
|
|
|
void Patch_Endcap(){
|
|
UndoableCommand undo( "patchCreateEndCap" );
|
|
|
|
Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(), EPatchPrefab::EndCap, GlobalXYWnd_getCurrentViewType() );
|
|
}
|
|
|
|
void Patch_Bevel(){
|
|
UndoableCommand undo( "patchCreateBevel" );
|
|
|
|
Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(), EPatchPrefab::Bevel, GlobalXYWnd_getCurrentViewType() );
|
|
}
|
|
|
|
void Patch_Sphere(){
|
|
UndoableCommand undo( "patchCreateSphere" );
|
|
|
|
Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(), EPatchPrefab::Sphere, GlobalXYWnd_getCurrentViewType() );
|
|
}
|
|
|
|
void Patch_SquareBevel(){
|
|
}
|
|
|
|
void Patch_SquareEndcap(){
|
|
}
|
|
|
|
void Patch_Cone(){
|
|
UndoableCommand undo( "patchCreateCone" );
|
|
|
|
Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(), EPatchPrefab::Cone, GlobalXYWnd_getCurrentViewType() );
|
|
}
|
|
|
|
void Patch_Plane(){
|
|
UndoableCommand undo( "patchCreatePlane" );
|
|
|
|
DoNewPatchDlg( EPatchPrefab::Plane, 3, 3, 3, 3, 0, 0 );
|
|
}
|
|
|
|
void Patch_InsertFirstColumn(){
|
|
UndoableCommand undo( "patchInsertFirstColumns" );
|
|
|
|
Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, true, true );
|
|
}
|
|
|
|
void Patch_InsertLastColumn(){
|
|
UndoableCommand undo( "patchInsertLastColumns" );
|
|
|
|
Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, true, false );
|
|
}
|
|
|
|
void Patch_InsertFirstRow(){
|
|
UndoableCommand undo( "patchInsertFirstRows" );
|
|
|
|
Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, false, true );
|
|
}
|
|
|
|
void Patch_InsertLastRow(){
|
|
UndoableCommand undo( "patchInsertLastRows" );
|
|
|
|
Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), true, false, false );
|
|
}
|
|
|
|
void Patch_DeleteFirstColumn(){
|
|
UndoableCommand undo( "patchDeleteFirstColumns" );
|
|
|
|
Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), false, true, true );
|
|
}
|
|
|
|
void Patch_DeleteLastColumn(){
|
|
UndoableCommand undo( "patchDeleteLastColumns" );
|
|
|
|
Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), false, true, false );
|
|
}
|
|
|
|
void Patch_DeleteFirstRow(){
|
|
UndoableCommand undo( "patchDeleteFirstRows" );
|
|
|
|
Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), false, false, true );
|
|
}
|
|
|
|
void Patch_DeleteLastRow(){
|
|
UndoableCommand undo( "patchDeleteLastRows" );
|
|
|
|
Scene_PatchInsertRemove_Selected( GlobalSceneGraph(), false, false, false );
|
|
}
|
|
|
|
void Patch_Invert(){
|
|
UndoableCommand undo( "patchInvert" );
|
|
|
|
Scene_PatchInvert_Selected( GlobalSceneGraph() );
|
|
}
|
|
|
|
void Patch_RedisperseRows(){
|
|
UndoableCommand undo( "patchRedisperseRows" );
|
|
|
|
Scene_PatchRedisperse_Selected( GlobalSceneGraph(), ROW );
|
|
}
|
|
|
|
void Patch_RedisperseCols(){
|
|
UndoableCommand undo( "patchRedisperseColumns" );
|
|
|
|
Scene_PatchRedisperse_Selected( GlobalSceneGraph(), COL );
|
|
}
|
|
|
|
void Patch_SmoothRows(){
|
|
UndoableCommand undo( "patchSmoothRows" );
|
|
|
|
Scene_PatchSmooth_Selected( GlobalSceneGraph(), ROW );
|
|
}
|
|
|
|
void Patch_SmoothCols(){
|
|
UndoableCommand undo( "patchSmoothColumns" );
|
|
|
|
Scene_PatchSmooth_Selected( GlobalSceneGraph(), COL );
|
|
}
|
|
|
|
void Patch_Transpose(){
|
|
UndoableCommand undo( "patchTranspose" );
|
|
|
|
Scene_PatchTranspose_Selected( GlobalSceneGraph() );
|
|
}
|
|
|
|
void DoCapDlg();
|
|
|
|
void Patch_Cap(){
|
|
// FIXME: add support for patch cap creation
|
|
// Patch_CapCurrent();
|
|
UndoableCommand undo( "patchPutCaps" );
|
|
|
|
DoCapDlg();
|
|
}
|
|
|
|
///\todo Unfinished.
|
|
void Patch_OverlayOn(){
|
|
}
|
|
|
|
///\todo Unfinished.
|
|
void Patch_OverlayOff(){
|
|
}
|
|
|
|
void Patch_FlipTextureX(){
|
|
UndoableCommand undo( "patchFlipTextureU" );
|
|
|
|
Scene_PatchFlipTexture_Selected( GlobalSceneGraph(), 0 );
|
|
}
|
|
|
|
void Patch_FlipTextureY(){
|
|
UndoableCommand undo( "patchFlipTextureV" );
|
|
|
|
Scene_PatchFlipTexture_Selected( GlobalSceneGraph(), 1 );
|
|
}
|
|
|
|
void Patch_NaturalTexture(){
|
|
UndoableCommand undo( "patchNaturalTexture" );
|
|
|
|
Scene_PatchNaturalTexture_Selected( GlobalSceneGraph() );
|
|
}
|
|
|
|
void Patch_CapTexture(){
|
|
UndoableCommand command( "patchCapTexture" );
|
|
Scene_PatchCapTexture_Selected( GlobalSceneGraph() );
|
|
}
|
|
|
|
void Patch_FitTexture(){
|
|
}
|
|
|
|
void DoPatchDeformDlg();
|
|
|
|
void Patch_Deform(){
|
|
UndoableCommand undo( "patchDeform" );
|
|
|
|
DoPatchDeformDlg();
|
|
}
|
|
|
|
void DoPatchThickenDlg();
|
|
|
|
void Patch_Thicken(){
|
|
UndoableCommand undo( "patchThicken" );
|
|
|
|
DoPatchThickenDlg();
|
|
}
|
|
|
|
|
|
|
|
#include "ifilter.h"
|
|
|
|
|
|
class filter_patch_all : public PatchFilter
|
|
{
|
|
public:
|
|
bool filter( const Patch& patch ) const {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class filter_patch_shader : public PatchFilter
|
|
{
|
|
const char* m_shader;
|
|
public:
|
|
filter_patch_shader( const char* shader ) : m_shader( shader ){
|
|
}
|
|
bool filter( const Patch& patch ) const {
|
|
return shader_equal( patch.GetShader(), m_shader );
|
|
}
|
|
};
|
|
|
|
class filter_patch_flags : public PatchFilter
|
|
{
|
|
int m_flags;
|
|
public:
|
|
filter_patch_flags( int flags ) : m_flags( flags ){
|
|
}
|
|
bool filter( const Patch& patch ) const {
|
|
return ( patch.getShaderFlags() & m_flags ) != 0;
|
|
}
|
|
};
|
|
|
|
|
|
filter_patch_all g_filter_patch_all;
|
|
filter_patch_flags g_filter_patch_clip( QER_CLIP );
|
|
filter_patch_shader g_filter_patch_commonclip( "textures/common/clip" );
|
|
filter_patch_shader g_filter_patch_weapclip( "textures/common/weapclip" );
|
|
filter_patch_flags g_filter_patch_translucent( QER_TRANS | QER_ALPHATEST );
|
|
|
|
void PatchFilters_construct(){
|
|
add_patch_filter( g_filter_patch_all, EXCLUDE_CURVES );
|
|
add_patch_filter( g_filter_patch_clip, EXCLUDE_CLIP );
|
|
add_patch_filter( g_filter_patch_commonclip, EXCLUDE_CLIP );
|
|
add_patch_filter( g_filter_patch_weapclip, EXCLUDE_CLIP );
|
|
add_patch_filter( g_filter_patch_translucent, EXCLUDE_TRANSLUCENT );
|
|
}
|
|
|
|
|
|
#include "preferences.h"
|
|
|
|
void Patch_constructPreferences( PreferencesPage& page ){
|
|
page.appendSpinner( "Patch Subdivide Threshold", g_PatchSubdivideThreshold, 0, 128 );
|
|
}
|
|
void Patch_constructPage( PreferenceGroup& group ){
|
|
PreferencesPage page( group.createPage( "Patches", "Patch Display Preferences" ) );
|
|
Patch_constructPreferences( page );
|
|
}
|
|
void Patch_registerPreferencesPage(){
|
|
PreferencesDialog_addDisplayPage( FreeCaller1<PreferenceGroup&, Patch_constructPage>() );
|
|
}
|
|
|
|
|
|
#include "preferencesystem.h"
|
|
|
|
void PatchPreferences_construct(){
|
|
GlobalPreferenceSystem().registerPreference( "Subdivisions", IntImportStringCaller( g_PatchSubdivideThreshold ), IntExportStringCaller( g_PatchSubdivideThreshold ) );
|
|
}
|
|
|
|
|
|
#include "generic/callback.h"
|
|
|
|
void Patch_registerCommands(){
|
|
GlobalCommands_insert( "InvertCurveTextureX", FreeCaller<Patch_FlipTextureX>(), QKeySequence( "Ctrl+Shift+I" ) );
|
|
GlobalCommands_insert( "InvertCurveTextureY", FreeCaller<Patch_FlipTextureY>(), QKeySequence( "Shift+I" ) );
|
|
GlobalCommands_insert( "NaturalizePatch", FreeCaller<Patch_NaturalTexture>(), QKeySequence( "Ctrl+N" ) );
|
|
GlobalCommands_insert( "PatchCylinder", FreeCaller<Patch_Cylinder>() );
|
|
// GlobalCommands_insert( "PatchDenseCylinder", FreeCaller<Patch_DenseCylinder>() );
|
|
// GlobalCommands_insert( "PatchVeryDenseCylinder", FreeCaller<Patch_VeryDenseCylinder>() );
|
|
GlobalCommands_insert( "PatchSquareCylinder", FreeCaller<Patch_SquareCylinder>() );
|
|
GlobalCommands_insert( "PatchXactCylinder", FreeCaller<Patch_XactCylinder>() );
|
|
GlobalCommands_insert( "PatchXactSphere", FreeCaller<Patch_XactSphere>() );
|
|
GlobalCommands_insert( "PatchXactCone", FreeCaller<Patch_XactCone>() );
|
|
GlobalCommands_insert( "PatchEndCap", FreeCaller<Patch_Endcap>() );
|
|
GlobalCommands_insert( "PatchBevel", FreeCaller<Patch_Bevel>() );
|
|
// GlobalCommands_insert( "PatchSquareBevel", FreeCaller<Patch_SquareBevel>() );
|
|
// GlobalCommands_insert( "PatchSquareEndcap", FreeCaller<Patch_SquareEndcap>() );
|
|
GlobalCommands_insert( "PatchCone", FreeCaller<Patch_Cone>() );
|
|
GlobalCommands_insert( "PatchSphere", FreeCaller<Patch_Sphere>() );
|
|
GlobalCommands_insert( "SimplePatchMesh", FreeCaller<Patch_Plane>(), QKeySequence( "Shift+P" ) );
|
|
GlobalCommands_insert( "PatchInsertFirstColumn", FreeCaller<Patch_InsertFirstColumn>(), QKeySequence( Qt::CTRL + Qt::SHIFT + Qt::Key_Plus + Qt::KeypadModifier ) );
|
|
GlobalCommands_insert( "PatchInsertLastColumn", FreeCaller<Patch_InsertLastColumn>() );
|
|
GlobalCommands_insert( "PatchInsertFirstRow", FreeCaller<Patch_InsertFirstRow>(), QKeySequence( Qt::CTRL + Qt::Key_Plus + Qt::KeypadModifier ) );
|
|
GlobalCommands_insert( "PatchInsertLastRow", FreeCaller<Patch_InsertLastRow>() );
|
|
GlobalCommands_insert( "PatchDeleteFirstColumn", FreeCaller<Patch_DeleteFirstColumn>() );
|
|
GlobalCommands_insert( "PatchDeleteLastColumn", FreeCaller<Patch_DeleteLastColumn>(), QKeySequence( Qt::CTRL + Qt::SHIFT + Qt::Key_Minus + Qt::KeypadModifier ) );
|
|
GlobalCommands_insert( "PatchDeleteFirstRow", FreeCaller<Patch_DeleteFirstRow>() );
|
|
GlobalCommands_insert( "PatchDeleteLastRow", FreeCaller<Patch_DeleteLastRow>(), QKeySequence( Qt::CTRL + Qt::Key_Minus + Qt::KeypadModifier ) );
|
|
GlobalCommands_insert( "InvertCurve", FreeCaller<Patch_Invert>(), QKeySequence( "Ctrl+I" ) );
|
|
//GlobalCommands_insert( "RedisperseRows", FreeCaller<Patch_RedisperseRows>(), QKeySequence( "Ctrl+E" ) );
|
|
GlobalCommands_insert( "RedisperseRows", FreeCaller<Patch_RedisperseRows>() );
|
|
//GlobalCommands_insert( "RedisperseCols", FreeCaller<Patch_RedisperseCols>(), QKeySequence( "Ctrl+Shift+E" ) );
|
|
GlobalCommands_insert( "RedisperseCols", FreeCaller<Patch_RedisperseCols>() );
|
|
GlobalCommands_insert( "SmoothRows", FreeCaller<Patch_SmoothRows>(), QKeySequence( "Ctrl+W" ) );
|
|
GlobalCommands_insert( "SmoothCols", FreeCaller<Patch_SmoothCols>(), QKeySequence( "Ctrl+Shift+W" ) );
|
|
GlobalCommands_insert( "MatrixTranspose", FreeCaller<Patch_Transpose>(), QKeySequence( "Ctrl+Shift+M" ) );
|
|
GlobalCommands_insert( "CapCurrentCurve", FreeCaller<Patch_Cap>(), QKeySequence( "Shift+C" ) );
|
|
// GlobalCommands_insert( "MakeOverlayPatch", FreeCaller<Patch_OverlayOn>(), QKeySequence( "Y" ) );
|
|
// GlobalCommands_insert( "ClearPatchOverlays", FreeCaller<Patch_OverlayOff>(), QKeySequence( "Ctrl+L" ) );
|
|
GlobalCommands_insert( "PatchDeform", FreeCaller<Patch_Deform>() );
|
|
GlobalCommands_insert( "PatchThicken", FreeCaller<Patch_Thicken>(), QKeySequence( "Ctrl+T" ) );
|
|
}
|
|
|
|
void Patch_constructToolbar( QToolBar* toolbar ){
|
|
toolbar_append_button( toolbar, "Put caps on the current patch", "curve_cap.png", "CapCurrentCurve" );
|
|
}
|
|
|
|
void Patch_constructMenu( QMenu* menu ){
|
|
create_menu_item_with_mnemonic( menu, "Simple Patch Mesh...", "SimplePatchMesh" );
|
|
create_menu_item_with_mnemonic( menu, "Bevel", "PatchBevel" );
|
|
create_menu_item_with_mnemonic( menu, "End cap", "PatchEndCap" );
|
|
create_menu_item_with_mnemonic( menu, "Cylinder (9x3)", "PatchCylinder" );
|
|
create_menu_item_with_mnemonic( menu, "Square Cylinder (9x3)", "PatchSquareCylinder" );
|
|
create_menu_item_with_mnemonic( menu, "Exact Cylinder...", "PatchXactCylinder" );
|
|
create_menu_item_with_mnemonic( menu, "Cone (9x3)", "PatchCone" );
|
|
create_menu_item_with_mnemonic( menu, "Exact Cone...", "PatchXactCone" );
|
|
create_menu_item_with_mnemonic( menu, "Sphere (9x5)", "PatchSphere" );
|
|
create_menu_item_with_mnemonic( menu, "Exact Sphere...", "PatchXactSphere" );
|
|
// {
|
|
// QMenu* submenu = menu->addMenu( "More Cylinders" );
|
|
|
|
// submenu->setTearOffEnabled( g_Layout_enableDetachableMenus.m_value );
|
|
|
|
// create_menu_item_with_mnemonic( submenu, "Dense Cylinder", "PatchDenseCylinder" );
|
|
// create_menu_item_with_mnemonic( submenu, "Very Dense Cylinder", "PatchVeryDenseCylinder" );
|
|
// create_menu_item_with_mnemonic( submenu, "Square Cylinder", "PatchSquareCylinder" );
|
|
// }
|
|
// {
|
|
// //not implemented
|
|
// create_menu_item_with_mnemonic( menu, "Square Endcap", "PatchSquareBevel" );
|
|
// create_menu_item_with_mnemonic( menu, "Square Bevel", "PatchSquareEndcap" );
|
|
// }
|
|
menu->addSeparator();
|
|
create_menu_item_with_mnemonic( menu, "Cap Selection", "CapCurrentCurve" );
|
|
menu->addSeparator();
|
|
{
|
|
QMenu* submenu = menu->addMenu( "Insert/Delete" );
|
|
|
|
submenu->setTearOffEnabled( g_Layout_enableDetachableMenus.m_value );
|
|
|
|
create_menu_item_with_mnemonic( submenu, "Insert (2) First Columns", "PatchInsertFirstColumn" );
|
|
create_menu_item_with_mnemonic( submenu, "Insert (2) Last Columns", "PatchInsertLastColumn" );
|
|
submenu->addSeparator();
|
|
create_menu_item_with_mnemonic( submenu, "Insert (2) First Rows", "PatchInsertFirstRow" );
|
|
create_menu_item_with_mnemonic( submenu, "Insert (2) Last Rows", "PatchInsertLastRow" );
|
|
submenu->addSeparator();
|
|
create_menu_item_with_mnemonic( submenu, "Del First (2) Columns", "PatchDeleteFirstColumn" );
|
|
create_menu_item_with_mnemonic( submenu, "Del Last (2) Columns", "PatchDeleteLastColumn" );
|
|
submenu->addSeparator();
|
|
create_menu_item_with_mnemonic( submenu, "Del First (2) Rows", "PatchDeleteFirstRow" );
|
|
create_menu_item_with_mnemonic( submenu, "Del Last (2) Rows", "PatchDeleteLastRow" );
|
|
}
|
|
menu->addSeparator();
|
|
{
|
|
QMenu* submenu = menu->addMenu( "Matrix" );
|
|
|
|
submenu->setTearOffEnabled( g_Layout_enableDetachableMenus.m_value );
|
|
|
|
create_menu_item_with_mnemonic( submenu, "Invert", "InvertCurve" );
|
|
create_menu_item_with_mnemonic( submenu, "Transpose", "MatrixTranspose" );
|
|
|
|
submenu->addSeparator();
|
|
create_menu_item_with_mnemonic( submenu, "Re-disperse Rows", "RedisperseRows" );
|
|
create_menu_item_with_mnemonic( submenu, "Re-disperse Columns", "RedisperseCols" );
|
|
|
|
submenu->addSeparator();
|
|
create_menu_item_with_mnemonic( submenu, "Smooth Rows", "SmoothRows" );
|
|
create_menu_item_with_mnemonic( submenu, "Smooth Columns", "SmoothCols" );
|
|
}
|
|
menu->addSeparator();
|
|
{
|
|
QMenu* submenu = menu->addMenu( "Texture" );
|
|
|
|
submenu->setTearOffEnabled( g_Layout_enableDetachableMenus.m_value );
|
|
|
|
create_menu_item_with_mnemonic( submenu, "Reset Texture", "TextureReset/Cap" );
|
|
create_menu_item_with_mnemonic( submenu, "Naturalize", "NaturalizePatch" );
|
|
create_menu_item_with_mnemonic( submenu, "Invert X", "InvertCurveTextureX" );
|
|
create_menu_item_with_mnemonic( submenu, "Invert Y", "InvertCurveTextureY" );
|
|
|
|
}
|
|
// menu->addSeparator();
|
|
// { //unfinished
|
|
// QMenu* submenu = menu->addMenu( "Overlay" );
|
|
|
|
// submenu->setTearOffEnabled( g_Layout_enableDetachableMenus.m_value );
|
|
|
|
// create_menu_item_with_mnemonic( submenu, "Set", "MakeOverlayPatch" );
|
|
// create_menu_item_with_mnemonic( submenu, "Clear", "ClearPatchOverlays" );
|
|
// }
|
|
menu->addSeparator();
|
|
create_menu_item_with_mnemonic( menu, "Deform...", "PatchDeform" );
|
|
create_menu_item_with_mnemonic( menu, "Thicken...", "PatchThicken" );
|
|
}
|
|
|
|
|
|
#include "gtkutil/dialog.h"
|
|
#include "gtkutil/widget.h"
|
|
#include "gtkutil/spinbox.h"
|
|
|
|
#include <QDialog>
|
|
#include "gtkutil/combobox.h"
|
|
#include <QCheckBox>
|
|
#include <QFormLayout>
|
|
#include <QDialogButtonBox>
|
|
#include <QButtonGroup>
|
|
#include <QRadioButton>
|
|
|
|
void DoNewPatchDlg( EPatchPrefab prefab, int minrows, int mincols, int defrows, int defcols, int maxrows, int maxcols ){
|
|
QDialog dialog( MainFrame_getWindow(), Qt::Dialog | Qt::WindowCloseButtonHint );
|
|
dialog.setWindowTitle( "Patch density" );
|
|
|
|
auto width = new ComboBox;
|
|
auto height = new ComboBox;
|
|
auto redisperseCheckBox = new QCheckBox( "Square" );
|
|
|
|
{
|
|
auto form = new QFormLayout( &dialog );
|
|
form->setSizeConstraint( QLayout::SizeConstraint::SetFixedSize );
|
|
{
|
|
{
|
|
#define D_ITEM( x ) if ( x >= mincols && ( !maxcols || x <= maxcols ) ) width->addItem( # x )
|
|
D_ITEM( 3 );
|
|
D_ITEM( 5 );
|
|
D_ITEM( 7 );
|
|
D_ITEM( 9 );
|
|
D_ITEM( 11 );
|
|
D_ITEM( 13 );
|
|
D_ITEM( 15 );
|
|
D_ITEM( 17 );
|
|
D_ITEM( 19 );
|
|
D_ITEM( 21 );
|
|
D_ITEM( 23 );
|
|
D_ITEM( 25 );
|
|
D_ITEM( 27 );
|
|
D_ITEM( 29 );
|
|
D_ITEM( 31 ); // MAX_PATCH_SIZE is 32, so we should be able to do 31...
|
|
#undef D_ITEM
|
|
form->addRow( "Width:", width );
|
|
}
|
|
{
|
|
#define D_ITEM( x ) if ( x >= minrows && ( !maxrows || x <= maxrows ) ) height->addItem( # x )
|
|
D_ITEM( 3 );
|
|
D_ITEM( 5 );
|
|
D_ITEM( 7 );
|
|
D_ITEM( 9 );
|
|
D_ITEM( 11 );
|
|
D_ITEM( 13 );
|
|
D_ITEM( 15 );
|
|
D_ITEM( 17 );
|
|
D_ITEM( 19 );
|
|
D_ITEM( 21 );
|
|
D_ITEM( 23 );
|
|
D_ITEM( 25 );
|
|
D_ITEM( 27 );
|
|
D_ITEM( 29 );
|
|
D_ITEM( 31 ); // MAX_PATCH_SIZE is 32, so we should be able to do 31...
|
|
#undef D_ITEM
|
|
form->addRow( "Height:", height );
|
|
}
|
|
|
|
if( prefab != EPatchPrefab::Plane ){
|
|
redisperseCheckBox->setToolTip( "Redisperse columns & rows" );
|
|
form->addWidget( redisperseCheckBox );
|
|
}
|
|
}
|
|
{
|
|
auto buttons = new QDialogButtonBox( QDialogButtonBox::StandardButton::Ok | QDialogButtonBox::StandardButton::Cancel );
|
|
form->addWidget( buttons );
|
|
QObject::connect( buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept );
|
|
QObject::connect( buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject );
|
|
}
|
|
}
|
|
|
|
// Initialize dialog
|
|
width->setCurrentIndex( ( defcols - mincols ) / 2 );
|
|
height->setCurrentIndex( ( defrows - minrows ) / 2 );
|
|
|
|
if ( dialog.exec() ) {
|
|
const int w = width->currentIndex() * 2 + mincols;
|
|
const int h = height->currentIndex() * 2 + minrows;
|
|
const bool redisperse = redisperseCheckBox->isChecked();
|
|
Scene_PatchConstructPrefab( GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(), prefab, GlobalXYWnd_getCurrentViewType(), w, h, redisperse );
|
|
}
|
|
}
|
|
|
|
|
|
void DoPatchDeformDlg(){
|
|
QDialog dialog( MainFrame_getWindow(), Qt::Dialog | Qt::WindowCloseButtonHint );
|
|
dialog.setWindowTitle( "Patch deform" );
|
|
|
|
auto spin = new SpinBox( -9999, 9999, 64 );
|
|
|
|
RadioHBox radioBox = RadioHBox_new( (const char*[]){ "X", "Y", "Z" } );
|
|
radioBox.m_radio->button( 2 )->setChecked( true );
|
|
|
|
{
|
|
auto form = new QFormLayout( &dialog );
|
|
form->setSizeConstraint( QLayout::SizeConstraint::SetFixedSize );
|
|
form->addRow( new SpinBoxLabel( "Max deform:", spin ), spin );
|
|
form->addRow( "", radioBox.m_hbox );
|
|
{
|
|
auto buttons = new QDialogButtonBox( QDialogButtonBox::StandardButton::Ok | QDialogButtonBox::StandardButton::Cancel );
|
|
form->addWidget( buttons );
|
|
QObject::connect( buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept );
|
|
QObject::connect( buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject );
|
|
}
|
|
}
|
|
|
|
if ( dialog.exec() ) {
|
|
const int deform = spin->value();
|
|
const int axis = radioBox.m_radio->checkedId();
|
|
Scene_PatchDeform( GlobalSceneGraph(), deform, axis );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void DoCapDlg(){
|
|
QDialog dialog( MainFrame_getWindow(), Qt::Dialog | Qt::WindowCloseButtonHint );
|
|
dialog.setWindowTitle( "Cap" );
|
|
|
|
auto group = new QButtonGroup( &dialog );
|
|
{
|
|
auto form = new QFormLayout( &dialog );
|
|
form->setSizeConstraint( QLayout::SizeConstraint::SetFixedSize );
|
|
{
|
|
const char* iconlabel[][2] = { { "cap_bevel.png", "Bevel" },
|
|
{ "cap_endcap.png", "Endcap" },
|
|
{ "cap_ibevel.png", "Inverted Bevel" },
|
|
{ "cap_iendcap.png", "Inverted Endcap" },
|
|
{ "cap_cylinder.png", "Cylinder" } };
|
|
for( size_t i = 0; i < std::size( iconlabel ); ++i ){
|
|
const auto [ stricon, strlabel ] = iconlabel[i];
|
|
auto label = new QLabel;
|
|
label->setPixmap( new_local_image( stricon ) );
|
|
auto button = new QRadioButton( strlabel );
|
|
group->addButton( button, i ); // set ids 0+, default ones are negative
|
|
form->addRow( label, button );
|
|
}
|
|
for( int i = 0; i < form->count(); ++i )
|
|
form->itemAt( i )->setAlignment( Qt::AlignmentFlag::AlignVCenter );
|
|
}
|
|
{
|
|
auto buttons = new QDialogButtonBox( QDialogButtonBox::StandardButton::Ok | QDialogButtonBox::StandardButton::Cancel );
|
|
form->addWidget( buttons );
|
|
QObject::connect( buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept );
|
|
QObject::connect( buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject );
|
|
}
|
|
}
|
|
|
|
// Initialize dialog
|
|
group->button( 0 )->setChecked( true );
|
|
|
|
if( dialog.exec() ){
|
|
Scene_PatchDoCap_Selected( GlobalSceneGraph(), TextureBrowser_GetSelectedShader(), static_cast<EPatchCap>( group->checkedId() ) );
|
|
}
|
|
}
|
|
|
|
|
|
void DoPatchThickenDlg(){
|
|
QDialog dialog( MainFrame_getWindow(), Qt::Dialog | Qt::WindowCloseButtonHint );
|
|
dialog.setWindowTitle( "Patch thicken" );
|
|
|
|
const int grid = std::max( GetGridSize(), 1.f );
|
|
auto spin = new SpinBox( -9999, 9999, grid, 2, grid );
|
|
|
|
RadioHBox radioBox = RadioHBox_new( (const char*[]){ "X", "Y", "Z", "Normal" } );
|
|
radioBox.m_radio->button( 3 )->setChecked( true );
|
|
|
|
auto check = new QCheckBox( "Side walls" );
|
|
check->setChecked( true );
|
|
|
|
{
|
|
auto form = new QFormLayout( &dialog );
|
|
form->setSizeConstraint( QLayout::SizeConstraint::SetFixedSize );
|
|
form->addRow( new SpinBoxLabel( "Thickness:", spin ), spin );
|
|
form->addRow( "", radioBox.m_hbox );
|
|
form->addWidget( check );
|
|
{
|
|
auto buttons = new QDialogButtonBox( QDialogButtonBox::StandardButton::Ok | QDialogButtonBox::StandardButton::Cancel );
|
|
form->addWidget( buttons );
|
|
QObject::connect( buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept );
|
|
QObject::connect( buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject );
|
|
}
|
|
}
|
|
|
|
if ( dialog.exec() ) {
|
|
const int thickness = spin->value();
|
|
const bool seams = check->isChecked();
|
|
const int axis = radioBox.m_radio->checkedId(); // 3 == Extrude along normals
|
|
|
|
Scene_PatchThicken( GlobalSceneGraph(), thickness, seams, axis );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
class PatchTexdefConstructor
|
|
{
|
|
public:
|
|
brushprimit_texdef_t m_bp;
|
|
Matrix4 m_local2tex;
|
|
Matrix4 m_tex2local;
|
|
Plane3 m_plane;
|
|
// rip from UVManipulator::UpdateFaceData
|
|
PatchTexdefConstructor( Patch *patch ){
|
|
m_plane.normal() = patch->Calculate_AvgNormal();
|
|
m_plane.dist() = vector3_dot( m_plane.normal(), patch->localAABB().origin );
|
|
const size_t patchWidth = patch->getWidth();
|
|
const size_t patchHeight = patch->getHeight();
|
|
{ //! todo force or deduce orthogonal uv axes for convenience
|
|
Vector3 wDir, hDir;
|
|
patch->Calculate_AvgAxes( wDir, hDir );
|
|
vector3_normalise( wDir );
|
|
vector3_normalise( hDir );
|
|
// globalOutputStream() << wDir << " wDir\n";
|
|
// globalOutputStream() << hDir << " hDir\n";
|
|
// globalOutputStream() << m_plane.normal() << " m_plane.normal()\n";
|
|
|
|
/* find longest row and column */
|
|
float wLength = 0, hLength = 0; //!? todo break, if some of these is 0
|
|
std::size_t row = 0, col = 0;
|
|
for ( std::size_t r = 0; r < patchHeight; ++r ){
|
|
float length = 0;
|
|
for ( std::size_t c = 0; c < patchWidth - 1; ++c ){
|
|
length += vector3_length( patch->ctrlAt( r, c + 1 ).m_vertex - patch->ctrlAt( r, c ).m_vertex );
|
|
}
|
|
if( length - wLength > .1f || ( ( r == 0 || r == patchHeight - 1 ) && float_equal_epsilon( length, wLength, .1f ) ) ){ // prioritize first and last rows
|
|
wLength = length;
|
|
row = r;
|
|
}
|
|
}
|
|
for ( std::size_t c = 0; c < patchWidth; ++c ){
|
|
float length = 0;
|
|
for ( std::size_t r = 0; r < patchHeight - 1; ++r ){
|
|
length += vector3_length( patch->ctrlAt( r + 1, c ).m_vertex - patch->ctrlAt( r, c ).m_vertex );
|
|
}
|
|
if( length - hLength > .1f || ( ( c == 0 || c == patchWidth - 1 ) && float_equal_epsilon( length, hLength, .1f ) ) ){
|
|
hLength = length;
|
|
col = c;
|
|
}
|
|
}
|
|
//! todo handle case, when uv start = end, like projection to cylinder
|
|
//! todo consider max uv length to have manipulator size according to patch size
|
|
/* pick 3 points at the found row and column */
|
|
const PatchControl* p0, *p1, *p2;
|
|
Vector3 v0, v1, v2;
|
|
{
|
|
float distW0 = 0, distW1 = 0;
|
|
for ( std::size_t c = 0; c < col; ++c ){
|
|
distW0 += vector3_length( patch->ctrlAt( row, c + 1 ).m_vertex - patch->ctrlAt( row, c ).m_vertex );
|
|
}
|
|
for ( std::size_t c = col; c < patchWidth - 1; ++c ){
|
|
distW1 += vector3_length( patch->ctrlAt( row, c + 1 ).m_vertex - patch->ctrlAt( row, c ).m_vertex );
|
|
}
|
|
float distH0 = 0, distH1 = 0;
|
|
for ( std::size_t r = 0; r < row; ++r ){
|
|
distH0 += vector3_length( patch->ctrlAt( r + 1, col ).m_vertex - patch->ctrlAt( r, col ).m_vertex );
|
|
}
|
|
for ( std::size_t r = row; r < patchHeight - 1; ++r ){
|
|
distH1 += vector3_length( patch->ctrlAt( r + 1, col ).m_vertex - patch->ctrlAt( r, col ).m_vertex );
|
|
}
|
|
|
|
if( ( distW0 > distH0 && distW0 > distH1 ) || ( distW1 > distH0 && distW1 > distH1 ) ){
|
|
p0 = &patch->ctrlAt( 0, col );
|
|
p1 = &patch->ctrlAt( patchHeight - 1, col );
|
|
p2 = distW0 > distW1? &patch->ctrlAt( row, 0 ) : &patch->ctrlAt( row, patchWidth - 1 );
|
|
v0 = p0->m_vertex; //! the altered line, we want realistic offset values
|
|
v1 = v0 + hDir * hLength;
|
|
v2 = v0 + hDir * distH0 + ( distW0 > distW1? ( wDir * -distW0 ) : ( wDir * distW1 ) );
|
|
}
|
|
else{
|
|
p0 = &patch->ctrlAt( row, 0 );
|
|
p1 = &patch->ctrlAt( row, patchWidth - 1 );
|
|
p2 = distH0 > distH1? &patch->ctrlAt( 0, col ) : &patch->ctrlAt( patchHeight - 1, col );
|
|
v0 = p0->m_vertex; //! the altered line, we want realistic offset values
|
|
v1 = v0 + wDir * wLength;
|
|
v2 = v0 + wDir * distW0 + ( distH0 > distH1? ( hDir * -distH0 ) : ( hDir * distH1 ) );
|
|
}
|
|
|
|
if( vector3_dot( plane3_for_points( v0, v1, v2 ).normal(), m_plane.normal() ) < 0 ){
|
|
std::swap( p0, p1 );
|
|
std::swap( v0, v1 );
|
|
}
|
|
}
|
|
const DoubleVector3 vertices[3]{ v0, v1, v2 };
|
|
const DoubleVector3 sts[3]{ DoubleVector3( p0->m_texcoord ),
|
|
DoubleVector3( p1->m_texcoord ),
|
|
DoubleVector3( p2->m_texcoord ) };
|
|
Texdef_Construct_local2tex_from_ST( vertices, sts, m_local2tex );
|
|
m_tex2local = matrix4_affine_inverse( m_local2tex );
|
|
BP_from_ST( m_bp, vertices, sts, plane3_for_points( vertices ).normal() );
|
|
m_bp.removeScale( patch->getShader()->getTexture().width, patch->getShader()->getTexture().height );
|
|
}
|
|
}
|
|
bool valid() const {
|
|
return !( !std::isfinite( m_local2tex[0] ) //nan
|
|
|| !std::isfinite( m_tex2local[0] ) //nan
|
|
|| fabs( vector3_dot( m_plane.normal(), m_tex2local.z().vec3() ) ) < 1e-6 //projected along face
|
|
|| vector3_length_squared( m_tex2local.x().vec3() ) < .01 //srsly scaled down, limit at max 10 textures per world unit
|
|
|| vector3_length_squared( m_tex2local.y().vec3() ) < .01 );
|
|
}
|
|
};
|
|
|
|
void Scene_PatchGetTexdef_Selected( scene::Graph& graph, TextureProjection &projection ){
|
|
if ( Patch* patch = Scene_GetUltimateSelectedVisiblePatch() ){
|
|
PatchTexdefConstructor c( patch );
|
|
if( c.valid() )
|
|
projection.m_brushprimit_texdef = c.m_bp;
|
|
}
|
|
}
|
|
|
|
bool Scene_PatchGetShaderTexdef_Selected( scene::Graph& graph, CopiedString& name, TextureProjection &projection ){
|
|
Patch* patch = nullptr;
|
|
if ( !( patch = Scene_GetUltimateSelectedVisiblePatch() ) )
|
|
Scene_forEachSelectedPatch( [&patch]( PatchInstance& p ){ if( !patch ) patch = &p.getPatch(); } );
|
|
if( patch ){
|
|
name = patch->GetShader();
|
|
PatchTexdefConstructor c( patch );
|
|
if( c.valid() )
|
|
projection.m_brushprimit_texdef = c.m_bp;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Patch_SetTexdef( const float* hShift, const float* vShift, const float* hScale, const float* vScale, const float* rotation ){
|
|
Scene_forEachVisibleSelectedPatch( [hShift, vShift, hScale, vScale, rotation]( Patch& patch ){
|
|
PatchTexdefConstructor c( &patch );
|
|
if( c.valid() ){
|
|
BPTexdef_Assign( c.m_bp, hShift, vShift, hScale, vScale, rotation );
|
|
Matrix4 local2tex;
|
|
c.m_bp.addScale( patch.getShader()->getTexture().width, patch.getShader()->getTexture().height );
|
|
BP_Construct_local2tex( c.m_bp, c.m_plane, local2tex );
|
|
matrix4_multiply_by_matrix4( local2tex, c.m_tex2local );
|
|
patch.undoSave();
|
|
for( auto& p : patch.getControlPoints() )
|
|
p.m_texcoord = matrix4_transformed_point( local2tex, Vector3( p.m_texcoord ) ).vec2();
|
|
patch.controlPointsChanged();
|
|
}
|
|
else{ // fallback //. fixme: this is not cool; may be more valid cases in PatchTexdefConstructor, as in find one good triangle in problematic case
|
|
if( hShift )
|
|
Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), *hShift > 0? 8 : -8, 0 );
|
|
if( vShift )
|
|
Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), 0, *vShift > 0? 8 : -8 );
|
|
if( hScale )
|
|
Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), *hScale > 0? .5 : -.5, 0 );
|
|
if( vScale )
|
|
Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), 0, *vScale > 0? .5 : -.5 );
|
|
if( rotation )
|
|
Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), *rotation > 0? 15 : -15 );
|
|
|
|
}
|
|
Patch_textureChanged();
|
|
} );
|
|
}
|