was showing shader of last selected brush, texdef of 1st now shows properties of single primitive at 1st tries to show last selected primitive to be responsible to selection prefers brushes over patches as general rule
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 <QComboBox>
|
|
#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::Window | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint );
|
|
dialog.setWindowTitle( "Patch density" );
|
|
|
|
auto width = new QComboBox;
|
|
auto height = new QComboBox;
|
|
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::Window | Qt::CustomizeWindowHint | 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::Window | Qt::CustomizeWindowHint | 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::Window | Qt::CustomizeWindowHint | 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();
|
|
} );
|
|
}
|