worldspawn/src/surfacedialog.cpp

2507 lines
80 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
*/
//
// Surface Dialog
//
// Leonardo Zide (leo@lokigames.com)
//
#include "surfacedialog.h"
#include <gtk/gtk.h>
#include "debugging/debugging.h"
#include "warnings.h"
#include "iscenegraph.h"
#include "itexdef.h"
#include "iundo.h"
#include "iselection.h"
#include <gdk/gdkkeysyms.h>
#include "signal/isignal.h"
#include "generic/object.h"
#include "math/vector.h"
#include "texturelib.h"
#include "shaderlib.h"
#include "stringio.h"
#include "gtkutil/idledraw.h"
#include "gtkutil/dialog.h"
#include "gtkutil/entry.h"
#include "gtkutil/nonmodal.h"
#include "gtkutil/pointer.h"
#include "gtkutil/glwidget.h" //Shamus: For Textool
#include "gtkutil/button.h"
#include "map.h"
#include "select.h"
#include "patchmanip.h"
#include "brushmanip.h"
#include "patchdialog.h"
#include "preferences.h"
#include "brush_primit.h"
#include "xywindow.h"
#include "mainframe.h"
#include "gtkdlgs.h"
#include "dialog.h"
#include "brush.h" //Shamus: for Textool
#include "patch.h"
#include "commands.h"
#include "stream/stringstream.h"
#include "grid.h"
#include "textureentry.h"
//NOTE: Proper functioning of Textool currently requires that the "#if 1" lines in
// brush_primit.h be changed to "#if 0". add/removeScale screws this up ATM. :-)
// Plus, Radiant seems to work just fine without that stuff. ;-)
#define TEXTOOL_ENABLED 0
#if TEXTOOL_ENABLED
namespace TexTool
{
//Shamus: Textool function prototypes
gboolean size_allocate( ui::Widget, GtkAllocation *, gpointer );
gboolean expose( ui::Widget, GdkEventExpose *, gpointer );
gboolean button_press( ui::Widget, GdkEventButton *, gpointer );
gboolean button_release( ui::Widget, GdkEventButton *, gpointer );
gboolean motion( ui::Widget, GdkEventMotion *, gpointer );
void flipX( ui::ToggleButton, gpointer );
void flipY( ui::ToggleButton, gpointer );
//End Textool function prototypes
//Shamus: Textool globals
ui::Widget g_textoolWin;
//End Textool globals
void queueDraw(){
gtk_widget_queue_draw( g_textoolWin );
}
}
#endif
inline void spin_button_set_step(ui::SpinButton spin, gfloat step)
{
#if 1
gtk_adjustment_set_step_increment(gtk_spin_button_get_adjustment(spin), step);
#else
GValue gvalue = GValue_default();
g_value_init( &gvalue, G_TYPE_DOUBLE );
g_value_set_double( &gvalue, step );
g_object_set( G_OBJECT( gtk_spin_button_get_adjustment( spin ) ), "step-increment", &gvalue, NULL );
#endif
}
class Increment {
float &m_f;
public:
ui::SpinButton m_spin;
ui::Entry m_entry;
Increment(float &f) : m_f(f), m_spin(ui::null), m_entry(ui::null)
{
}
void cancel()
{
entry_set_float(m_entry, m_f);
}
typedef MemberCaller<Increment, void (), &Increment::cancel> CancelCaller;
void apply()
{
m_f = static_cast<float>( entry_get_float(m_entry));
spin_button_set_step(m_spin, m_f);
}
typedef MemberCaller<Increment, void (), &Increment::apply> ApplyCaller;
};
void SurfaceInspector_GridChange();
class SurfaceInspector : public Dialog {
ui::Window BuildDialog();
NonModalEntry m_textureEntry;
NonModalSpinner m_hshiftSpinner;
NonModalEntry m_hshiftEntry;
NonModalSpinner m_vshiftSpinner;
NonModalEntry m_vshiftEntry;
NonModalSpinner m_hscaleSpinner;
NonModalEntry m_hscaleEntry;
NonModalSpinner m_vscaleSpinner;
NonModalEntry m_vscaleEntry;
NonModalSpinner m_rotateSpinner;
NonModalEntry m_rotateEntry;
IdleDraw m_idleDraw;
GtkCheckButton *m_surfaceFlags[32];
GtkCheckButton *m_contentFlags[32];
NonModalEntry m_valueEntry;
ui::Entry m_valueEntryWidget{ui::null};
public:
WindowPositionTracker m_positionTracker;
// Dialog Data
float m_fitHorizontal;
float m_fitVertical;
Increment m_hshiftIncrement;
Increment m_vshiftIncrement;
Increment m_hscaleIncrement;
Increment m_vscaleIncrement;
Increment m_rotateIncrement;
ui::Entry m_texture{ui::null};
SurfaceInspector() :
m_textureEntry(ApplyShaderCaller(*this), UpdateCaller(*this)),
m_hshiftSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)),
m_hshiftEntry(Increment::ApplyCaller(m_hshiftIncrement), Increment::CancelCaller(m_hshiftIncrement)),
m_vshiftSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)),
m_vshiftEntry(Increment::ApplyCaller(m_vshiftIncrement), Increment::CancelCaller(m_vshiftIncrement)),
m_hscaleSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)),
m_hscaleEntry(Increment::ApplyCaller(m_hscaleIncrement), Increment::CancelCaller(m_hscaleIncrement)),
m_vscaleSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)),
m_vscaleEntry(Increment::ApplyCaller(m_vscaleIncrement), Increment::CancelCaller(m_vscaleIncrement)),
m_rotateSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)),
m_rotateEntry(Increment::ApplyCaller(m_rotateIncrement), Increment::CancelCaller(m_rotateIncrement)),
m_idleDraw(UpdateCaller(*this)),
m_valueEntry(ApplyFlagsCaller(*this), UpdateCaller(*this)),
m_hshiftIncrement(g_si_globals.shift[0]),
m_vshiftIncrement(g_si_globals.shift[1]),
m_hscaleIncrement(g_si_globals.scale[0]),
m_vscaleIncrement(g_si_globals.scale[1]),
m_rotateIncrement(g_si_globals.rotate)
{
m_fitVertical = 1;
m_fitHorizontal = 1;
m_positionTracker.setPosition(c_default_window_pos);
}
void constructWindow(ui::Window main_window)
{
m_parent = main_window;
Create();
AddGridChangeCallback(FreeCaller<void(), SurfaceInspector_GridChange>());
}
void destroyWindow()
{
Destroy();
}
bool visible()
{
return GetWidget().visible();
}
void queueDraw()
{
if (visible()) {
m_idleDraw.queueDraw();
}
}
void Update();
typedef MemberCaller<SurfaceInspector, void (), &SurfaceInspector::Update> UpdateCaller;
void ApplyShader();
typedef MemberCaller<SurfaceInspector, void (), &SurfaceInspector::ApplyShader> ApplyShaderCaller;
void ApplyTexdef();
typedef MemberCaller<SurfaceInspector, void (), &SurfaceInspector::ApplyTexdef> ApplyTexdefCaller;
void ApplyFlags();
typedef MemberCaller<SurfaceInspector, void (), &SurfaceInspector::ApplyFlags> ApplyFlagsCaller;
};
namespace {
SurfaceInspector *g_SurfaceInspector;
inline SurfaceInspector &getSurfaceInspector()
{
ASSERT_NOTNULL(g_SurfaceInspector);
return *g_SurfaceInspector;
}
}
void SurfaceInspector_constructWindow(ui::Window main_window)
{
getSurfaceInspector().constructWindow(main_window);
}
void SurfaceInspector_destroyWindow()
{
getSurfaceInspector().destroyWindow();
}
void SurfaceInspector_queueDraw()
{
getSurfaceInspector().queueDraw();
}
namespace {
CopiedString g_selectedShader;
TextureProjection g_selectedTexdef;
ContentsFlagsValue g_selectedFlags;
size_t g_selectedShaderSize[2];
}
void SurfaceInspector_SetSelectedShader(const char *shader)
{
g_selectedShader = shader;
SurfaceInspector_queueDraw();
}
void SurfaceInspector_SetSelectedTexdef(const TextureProjection &projection)
{
g_selectedTexdef = projection;
SurfaceInspector_queueDraw();
}
void SurfaceInspector_SetSelectedFlags(const ContentsFlagsValue &flags)
{
g_selectedFlags = flags;
SurfaceInspector_queueDraw();
}
static bool s_texture_selection_dirty = false;
void SurfaceInspector_updateSelection()
{
s_texture_selection_dirty = true;
SurfaceInspector_queueDraw();
#if TEXTOOL_ENABLED
if ( g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES ) {
TexTool::queueDraw();
//globalOutputStream() << "textool texture changed..\n";
}
#endif
}
void SurfaceInspector_SelectionChanged(const Selectable &selectable)
{
SurfaceInspector_updateSelection();
}
void SurfaceInspector_SetCurrent_FromSelected()
{
if (s_texture_selection_dirty == true) {
s_texture_selection_dirty = false;
if (!g_SelectedFaceInstances.empty()) {
TextureProjection projection;
//This *may* be the point before it fucks up... Let's see!
//Yep, there was a call to removeScale in there...
Scene_BrushGetTexdef_Component_Selected(GlobalSceneGraph(), projection);
SurfaceInspector_SetSelectedTexdef(projection);
Scene_BrushGetShaderSize_Component_Selected(GlobalSceneGraph(), g_selectedShaderSize[0],
g_selectedShaderSize[1]);
g_selectedTexdef.m_brushprimit_texdef.coords[0][2] = float_mod(
g_selectedTexdef.m_brushprimit_texdef.coords[0][2], (float) g_selectedShaderSize[0]);
g_selectedTexdef.m_brushprimit_texdef.coords[1][2] = float_mod(
g_selectedTexdef.m_brushprimit_texdef.coords[1][2], (float) g_selectedShaderSize[1]);
CopiedString name;
Scene_BrushGetShader_Component_Selected(GlobalSceneGraph(), name);
if (string_not_empty(name.c_str())) {
SurfaceInspector_SetSelectedShader(name.c_str());
}
ContentsFlagsValue flags;
Scene_BrushGetFlags_Component_Selected(GlobalSceneGraph(), flags);
SurfaceInspector_SetSelectedFlags(flags);
} else {
TextureProjection projection;
Scene_BrushGetTexdef_Selected(GlobalSceneGraph(), projection);
SurfaceInspector_SetSelectedTexdef(projection);
CopiedString name;
Scene_BrushGetShader_Selected(GlobalSceneGraph(), name);
if (string_empty(name.c_str())) {
Scene_PatchGetShader_Selected(GlobalSceneGraph(), name);
}
if (string_not_empty(name.c_str())) {
SurfaceInspector_SetSelectedShader(name.c_str());
}
ContentsFlagsValue flags(0, 0, 0, false);
Scene_BrushGetFlags_Selected(GlobalSceneGraph(), flags);
SurfaceInspector_SetSelectedFlags(flags);
}
}
}
const char *SurfaceInspector_GetSelectedShader()
{
SurfaceInspector_SetCurrent_FromSelected();
return g_selectedShader.c_str();
}
const TextureProjection &SurfaceInspector_GetSelectedTexdef()
{
SurfaceInspector_SetCurrent_FromSelected();
return g_selectedTexdef;
}
const ContentsFlagsValue &SurfaceInspector_GetSelectedFlags()
{
SurfaceInspector_SetCurrent_FromSelected();
return g_selectedFlags;
}
/*
===================================================
SURFACE INSPECTOR
===================================================
*/
si_globals_t g_si_globals;
// make the shift increments match the grid settings
// the objective being that the shift+arrows shortcuts move the texture by the corresponding grid size
// this depends on a scale value if you have selected a particular texture on which you want it to work:
// we move the textures in pixels, not world units. (i.e. increment values are in pixel)
// depending on the texture scale it doesn't take the same amount of pixels to move of GetGridSize()
// increment * scale = gridsize
// hscale and vscale are optional parameters, if they are zero they will be set to the default scale
// NOTE: the default scale depends if you are using BP mode or regular.
// For regular it's 0.5f (128 pixels cover 64 world units), for BP it's simply 1.0f
// see fenris #2810
void DoSnapTToGrid(float hscale, float vscale)
{
g_si_globals.shift[0] = static_cast<float>( float_to_integer(static_cast<float>( GetGridSize()) / hscale));
g_si_globals.shift[1] = static_cast<float>( float_to_integer(static_cast<float>( GetGridSize()) / vscale));
getSurfaceInspector().queueDraw();
}
void SurfaceInspector_GridChange()
{
if (g_si_globals.m_bSnapTToGrid) {
DoSnapTToGrid(Texdef_getDefaultTextureScale(), Texdef_getDefaultTextureScale());
}
}
// make the shift increments match the grid settings
// the objective being that the shift+arrows shortcuts move the texture by the corresponding grid size
// this depends on the current texture scale used?
// we move the textures in pixels, not world units. (i.e. increment values are in pixel)
// depending on the texture scale it doesn't take the same amount of pixels to move of GetGridSize()
// increment * scale = gridsize
static void OnBtnMatchGrid(ui::Widget widget, gpointer data)
{
float hscale, vscale;
hscale = static_cast<float>( gtk_spin_button_get_value(getSurfaceInspector().m_hscaleIncrement.m_spin));
vscale = static_cast<float>( gtk_spin_button_get_value(getSurfaceInspector().m_vscaleIncrement.m_spin));
if (hscale == 0.0f || vscale == 0.0f) {
globalOutputStream() << "ERROR: unexpected scale == 0.0f\n";
return;
}
DoSnapTToGrid(hscale, vscale);
}
// DoSurface will always try to show the surface inspector
// or update it because something new has been selected
// Shamus: It does get called when the SI is hidden, but not when you select something new. ;-)
void DoSurface(void)
{
if (!getSurfaceInspector().GetWidget()) {
getSurfaceInspector().Create();
}
getSurfaceInspector().Update();
getSurfaceInspector().importData();
getSurfaceInspector().ShowDlg();
}
void SurfaceInspector_toggleShown()
{
if (getSurfaceInspector().visible()) {
getSurfaceInspector().HideDlg();
} else {
DoSurface();
}
}
void SurfaceInspector_FitTexture()
{
UndoableCommand undo("textureAutoFit");
Select_FitTexture(getSurfaceInspector().m_fitHorizontal, getSurfaceInspector().m_fitVertical);
}
void SurfaceInspector_AlignTopLeft()
{
Select_AlignTexture(0);
}
void SurfaceInspector_AlignTop()
{
Select_AlignTexture(1);
}
void SurfaceInspector_AlignTopRight()
{
Select_AlignTexture(2);
}
void SurfaceInspector_AlignLeft()
{
Select_AlignTexture(3);
}
void SurfaceInspector_AlignCenter()
{
Select_AlignTexture(4);
}
void SurfaceInspector_AlignRight()
{
Select_AlignTexture(5);
}
void SurfaceInspector_AlignBottomLeft()
{
Select_AlignTexture(6);
}
void SurfaceInspector_AlignBottom()
{
Select_AlignTexture(7);
}
void SurfaceInspector_AlignBottomRight()
{
Select_AlignTexture(8);
}
static void OnBtnPatchdetails(ui::Widget widget, gpointer data)
{
Patch_CapTexture();
}
static void OnBtnPatchnatural(ui::Widget widget, gpointer data)
{
Patch_NaturalTexture();
}
static void OnBtnPatchreset(ui::Widget widget, gpointer data)
{
Patch_ResetTexture();
}
static void OnBtnPatchFit(ui::Widget widget, gpointer data)
{
Patch_FitTexture();
}
static void OnBtnAxial(ui::Widget widget, gpointer data)
{
//globalOutputStream() << "--> [OnBtnAxial]...\n";
UndoableCommand undo("textureDefault");
TextureProjection projection;
//globalOutputStream() << " TexDef_Construct_Default()...\n";
TexDef_Construct_Default(projection);
//globalOutputStream() << " Select_SetTexdef()...\n";
#if TEXTOOL_ENABLED
//Shamus:
if ( g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES ) {
// Scale up texture width/height if in BP mode...
//NOTE: This may not be correct any more! :-P
if ( !g_SelectedFaceInstances.empty() ) {
Face & face = g_SelectedFaceInstances.last().getFace();
float x = face.getShader().m_state->getTexture().width;
float y = face.getShader().m_state->getTexture().height;
projection.m_brushprimit_texdef.coords[0][0] /= x;
projection.m_brushprimit_texdef.coords[0][1] /= y;
projection.m_brushprimit_texdef.coords[1][0] /= x;
projection.m_brushprimit_texdef.coords[1][1] /= y;
}
}
#endif
Select_SetTexdef(projection, true);
}
static void OnBtnFaceFit(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_FitTexture();
}
static void OnBtnTopLeft(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_AlignTopLeft();
}
static void OnBtnTopCenter(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_AlignTop();
}
static void OnBtnTopRight(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_AlignTopRight();
}
static void OnBtnMiddleLeft(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_AlignLeft();
}
static void OnBtnMiddleCenter(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_AlignCenter();
}
static void OnBtnMiddleRight(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_AlignRight();
}
static void OnBtnBottomLeft(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_AlignBottomLeft();
}
static void OnBtnBottomCenter(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_AlignBottom();
}
static void OnBtnBottomRight(ui::Widget widget, gpointer data)
{
getSurfaceInspector().exportData();
SurfaceInspector_AlignBottomRight();
}
typedef const char *FlagName;
const FlagName surfaceflagNamesDefault[32] = {
"surf1",
"surf2",
"surf3",
"surf4",
"surf5",
"surf6",
"surf7",
"surf8",
"surf9",
"surf10",
"surf11",
"surf12",
"surf13",
"surf14",
"surf15",
"surf16",
"surf17",
"surf18",
"surf19",
"surf20",
"surf21",
"surf22",
"surf23",
"surf24",
"surf25",
"surf26",
"surf27",
"surf28",
"surf29",
"surf30",
"surf31",
"surf32"
};
const FlagName contentflagNamesDefault[32] = {
"cont1",
"cont2",
"cont3",
"cont4",
"cont5",
"cont6",
"cont7",
"cont8",
"cont9",
"cont10",
"cont11",
"cont12",
"cont13",
"cont14",
"cont15",
"cont16",
"cont17",
"cont18",
"cont19",
"cont20",
"cont21",
"cont22",
"cont23",
"cont24",
"cont25",
"cont26",
"cont27",
"cont28",
"cont29",
"cont30",
"cont31",
"cont32"
};
const char *getSurfaceFlagName(std::size_t bit)
{
const char *value = g_pGameDescription->getKeyValue(surfaceflagNamesDefault[bit]);
if (string_empty(value)) {
return surfaceflagNamesDefault[bit];
}
return value;
}
const char *getContentFlagName(std::size_t bit)
{
const char *value = g_pGameDescription->getKeyValue(contentflagNamesDefault[bit]);
if (string_empty(value)) {
return contentflagNamesDefault[bit];
}
return value;
}
// =============================================================================
// SurfaceInspector class
guint togglebutton_connect_toggled(ui::ToggleButton button, const Callback<void()> &callback)
{
return g_signal_connect_swapped(G_OBJECT(button), "toggled", G_CALLBACK(callback.getThunk()),
callback.getEnvironment());
}
ui::Window SurfaceInspector::BuildDialog()
{
ui::Window window = ui::Window(create_floating_window("Surface Inspector", m_parent));
m_positionTracker.connect(window);
global_accel_connect_window(window);
window_connect_focus_in_clear_focus_widget(window);
{
// replaced by only the vbox:
auto vbox = ui::VBox(FALSE, 5);
vbox.show();
window.add(vbox);
gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
{
auto hbox2 = ui::HBox(FALSE, 5);
hbox2.show();
vbox.pack_start(hbox2, FALSE, FALSE, 0);
{
ui::Widget label = ui::Label("Texture");
label.show();
hbox2.pack_start(label, FALSE, TRUE, 0);
}
{
auto entry = ui::Entry(ui::New);
entry.show();
hbox2.pack_start(entry, TRUE, TRUE, 0);
m_texture = entry;
m_textureEntry.connect(entry);
GlobalTextureEntryCompletion::instance().connect(entry);
}
}
{
auto table = ui::Table(6, 4, FALSE);
table.show();
vbox.pack_start(table, FALSE, FALSE, 0);
gtk_table_set_row_spacings(table, 5);
gtk_table_set_col_spacings(table, 5);
{
ui::Widget label = ui::Label("Horizontal shift");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
}
{
auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 2);
m_hshiftIncrement.m_spin = spin;
m_hshiftSpinner.connect(spin);
spin.show();
table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
spin.dimensions(60, -1);
}
{
ui::Widget label = ui::Label("Step");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {2, 3, 0, 1}, {GTK_FILL, 0});
}
{
auto entry = ui::Entry(ui::New);
entry.show();
table.attach(entry, {3, 4, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
entry.dimensions(50, -1);
m_hshiftIncrement.m_entry = entry;
m_hshiftEntry.connect(entry);
}
{
ui::Widget label = ui::Label("Vertical shift");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
}
{
auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 2);
m_vshiftIncrement.m_spin = spin;
m_vshiftSpinner.connect(spin);
spin.show();
table.attach(spin, {1, 2, 1, 2}, {GTK_FILL, 0});
spin.dimensions(60, -1);
}
{
ui::Widget label = ui::Label("Step");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {2, 3, 1, 2}, {GTK_FILL, 0});
}
{
auto entry = ui::Entry(ui::New);
entry.show();
table.attach(entry, {3, 4, 1, 2}, {GTK_FILL, 0});
entry.dimensions(50, -1);
m_vshiftIncrement.m_entry = entry;
m_vshiftEntry.connect(entry);
}
{
ui::Widget label = ui::Label("Horizontal stretch");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
}
{
auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 5);
m_hscaleIncrement.m_spin = spin;
m_hscaleSpinner.connect(spin);
spin.show();
table.attach(spin, {1, 2, 2, 3}, {GTK_FILL, 0});
spin.dimensions(60, -1);
}
{
ui::Widget label = ui::Label("Step");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {2, 3, 2, 3}, {GTK_FILL, 0});
}
{
auto entry = ui::Entry(ui::New);
entry.show();
table.attach(entry, {3, 4, 2, 3}, {GTK_FILL, 0});
entry.dimensions(50, -1);
m_hscaleIncrement.m_entry = entry;
m_hscaleEntry.connect(entry);
}
{
ui::Widget label = ui::Label("Vertical stretch");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {0, 1, 3, 4}, {GTK_FILL, 0});
}
{
auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 5);
m_vscaleIncrement.m_spin = spin;
m_vscaleSpinner.connect(spin);
spin.show();
table.attach(spin, {1, 2, 3, 4}, {GTK_FILL, 0});
spin.dimensions(60, -1);
}
{
ui::Widget label = ui::Label("Step");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {2, 3, 3, 4}, {GTK_FILL, 0});
}
{
auto entry = ui::Entry(ui::New);
entry.show();
table.attach(entry, {3, 4, 3, 4}, {GTK_FILL, 0});
entry.dimensions(50, -1);
m_vscaleIncrement.m_entry = entry;
m_vscaleEntry.connect(entry);
}
{
ui::Widget label = ui::Label("Rotate");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {0, 1, 4, 5}, {GTK_FILL, 0});
}
{
auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 2);
m_rotateIncrement.m_spin = spin;
m_rotateSpinner.connect(spin);
spin.show();
table.attach(spin, {1, 2, 4, 5}, {GTK_FILL, 0});
spin.dimensions(60, -1);
gtk_spin_button_set_wrap(spin, TRUE);
}
{
ui::Widget label = ui::Label("Step");
label.show();
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
table.attach(label, {2, 3, 4, 5}, {GTK_FILL, 0});
}
{
auto entry = ui::Entry(ui::New);
entry.show();
table.attach(entry, {3, 4, 4, 5}, {GTK_FILL, 0});
entry.dimensions(50, -1);
m_rotateIncrement.m_entry = entry;
m_rotateEntry.connect(entry);
}
{
// match grid button
ui::Widget button = ui::Button("Match Grid");
button.show();
table.attach(button, {2, 4, 5, 6}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked", G_CALLBACK(OnBtnMatchGrid), 0);
}
}
{
auto frame = ui::Frame("Texturing");
frame.show();
vbox.pack_start(frame, FALSE, FALSE, 0);
{
auto table = ui::Table(4, 4, FALSE);
table.show();
frame.add(table);
gtk_table_set_row_spacings(table, 5);
gtk_table_set_col_spacings(table, 5);
gtk_container_set_border_width(GTK_CONTAINER(table), 5);
{
ui::Widget label = ui::Label("Brush");
label.show();
table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
}
{
ui::Widget label = ui::Label("Patch");
label.show();
table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
}
{
ui::Widget label = ui::Label("Width");
label.show();
table.attach(label, {2, 3, 0, 1}, {GTK_FILL, 0});
}
{
ui::Widget label = ui::Label("Height");
label.show();
table.attach(label, {3, 4, 0, 1}, {GTK_FILL, 0});
}
{
ui::Widget button = ui::Button("Axial");
button.show();
table.attach(button, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnAxial), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Fit");
button.show();
table.attach(button, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnFaceFit), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("CAP");
button.show();
table.attach(button, {0, 1, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnPatchdetails), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Set...");
button.show();
table.attach(button, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnPatchreset), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Natural");
button.show();
table.attach(button, {2, 3, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnPatchnatural), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Fit");
button.show();
table.attach(button, {3, 4, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnPatchFit), 0);
button.dimensions(60, -1);
}
{
auto spin = ui::SpinButton(ui::Adjustment(1, 0, 1 << 16, 1, 10, 0), 0, 6);
spin.show();
table.attach(spin, {2, 3, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
spin.dimensions(60, -1);
AddDialogData(spin, m_fitHorizontal);
}
{
auto spin = ui::SpinButton(ui::Adjustment(1, 0, 1 << 16, 1, 10, 0), 0, 6);
spin.show();
table.attach(spin, {3, 4, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
spin.dimensions(60, -1);
AddDialogData(spin, m_fitVertical);
}
}
}
{
auto frame = ui::Frame("Alignment");
frame.show();
vbox.pack_start(frame, FALSE, FALSE, 0);
{
auto table = ui::Table(3, 3, FALSE);
table.show();
frame.add(table);
gtk_table_set_row_spacings(table, 5);
gtk_table_set_col_spacings(table, 5);
gtk_container_set_border_width(GTK_CONTAINER(table), 5);
{
ui::Widget button = ui::Button("Top-Left");
button.show();
table.attach(button, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnTopLeft), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Top");
button.show();
table.attach(button, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnTopCenter), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Top-Right");
button.show();
table.attach(button, {2, 3, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnTopRight), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Left");
button.show();
table.attach(button, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnMiddleLeft), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Center");
button.show();
table.attach(button, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnMiddleCenter), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Right");
button.show();
table.attach(button, {2, 3, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnMiddleRight), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Bottom-Left");
button.show();
table.attach(button, {0, 1, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnBottomLeft), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Bottom");
button.show();
table.attach(button, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnBottomCenter), 0);
button.dimensions(60, -1);
}
{
ui::Widget button = ui::Button("Bottom-Right");
button.show();
table.attach(button, {2, 3, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
button.connect("clicked",
G_CALLBACK(OnBtnBottomRight), 0);
button.dimensions(60, -1);
}
}
}
if (!string_empty(g_pGameDescription->getKeyValue("si_flags"))) {
{
auto frame = ui::Frame("Surface Flags");
frame.show();
vbox.pack_start(frame, TRUE, TRUE, 0);
{
auto vbox3 = ui::VBox(FALSE, 4);
//gtk_container_set_border_width(GTK_CONTAINER(vbox3), 4);
vbox3.show();
frame.add(vbox3);
{
auto table = ui::Table(8, 4, FALSE);
table.show();
vbox3.pack_start(table, TRUE, TRUE, 0);
gtk_table_set_row_spacings(table, 0);
gtk_table_set_col_spacings(table, 0);
GtkCheckButton **p = m_surfaceFlags;
for (unsigned int c = 0; c != 4; ++c) {
for (unsigned int r = 0; r != 8; ++r) {
auto check = ui::CheckButton(getSurfaceFlagName(c * 8 + r));
check.show();
table.attach(check, {c, c + 1, r, r + 1}, {GTK_EXPAND | GTK_FILL, 0});
*p++ = check;
guint handler_id = togglebutton_connect_toggled(check, ApplyFlagsCaller(*this));
g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler_id));
}
}
}
}
}
{
auto frame = ui::Frame("Content Flags");
frame.show();
vbox.pack_start(frame, TRUE, TRUE, 0);
{
auto vbox3 = ui::VBox(FALSE, 4);
//gtk_container_set_border_width(GTK_CONTAINER(vbox3), 4);
vbox3.show();
frame.add(vbox3);
{
auto table = ui::Table(8, 4, FALSE);
table.show();
vbox3.pack_start(table, TRUE, TRUE, 0);
gtk_table_set_row_spacings(table, 0);
gtk_table_set_col_spacings(table, 0);
GtkCheckButton **p = m_contentFlags;
for (unsigned int c = 0; c != 4; ++c) {
for (unsigned int r = 0; r != 8; ++r) {
auto check = ui::CheckButton(getContentFlagName(c * 8 + r));
check.show();
table.attach(check, {c, c + 1, r, r + 1}, {GTK_EXPAND | GTK_FILL, 0});
*p++ = check;
guint handler_id = togglebutton_connect_toggled(check, ApplyFlagsCaller(*this));
g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler_id));
}
}
// not allowed to modify detail flag using Surface Inspector
gtk_widget_set_sensitive(ui::CheckButton::from(m_contentFlags[BRUSH_DETAIL_FLAG]), FALSE);
}
}
}
{
auto frame = ui::Frame("Value");
frame.show();
vbox.pack_start(frame, TRUE, TRUE, 0);
{
auto vbox3 = ui::VBox(FALSE, 4);
gtk_container_set_border_width(GTK_CONTAINER(vbox3), 4);
vbox3.show();
frame.add(vbox3);
{
auto entry = ui::Entry(ui::New);
entry.show();
vbox3.pack_start(entry, TRUE, TRUE, 0);
m_valueEntryWidget = entry;
m_valueEntry.connect(entry);
}
}
}
}
#if TEXTOOL_ENABLED
if ( g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES ) {
// Shamus: Textool goodies...
ui::Widget frame = ui::Frame( "Textool" );
frame.show();
vbox.pack_start( frame, FALSE, FALSE, 0 );
{
//Prolly should make this a member or global var, so the SI can draw on it...
TexTool::g_textoolWin = glwidget_new( FALSE );
// --> Dunno, but this stuff may be necessary... (Looks like it!)
g_object_ref( TexTool::g_textoolWin );
gtk_widget_set_events( TexTool::g_textoolWin, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK );
gtk_widget_set_can_focus( TexTool::g_textoolWin, true );
// <-- end stuff...
TexTool::g_textoolWin.show();
TexTool::g_textoolWin.dimensions( -1, 240 ); //Yeah!
frame.add(TexTool::g_textoolWin);
TexTool::g_textoolWin.connect( "size_allocate", G_CALLBACK( TexTool::size_allocate ), NULL );
TexTool::g_textoolWin.connect( "expose_event", G_CALLBACK( TexTool::expose ), NULL );
TexTool::g_textoolWin.connect( "button_press_event", G_CALLBACK( TexTool::button_press ), NULL );
TexTool::g_textoolWin.connect( "button_release_event", G_CALLBACK( TexTool::button_release ), NULL );
TexTool::g_textoolWin.connect( "motion_notify_event", G_CALLBACK( TexTool::motion ), NULL );
}
{
ui::Widget hbox = ui::HBox( FALSE, 5 );
hbox.show();
vbox.pack_start( hbox, FALSE, FALSE, 0 );
// Checkboxes go here... (Flip X/Y)
ui::Widget flipX = ui::CheckButton( "Flip X axis" );
ui::Widget flipY = ui::CheckButton( "Flip Y axis" );
flipX.show();
flipY.show();
hbox.pack_start( flipX, FALSE, FALSE, 0 );
hbox.pack_start( flipY, FALSE, FALSE, 0 );
//Instead of this, we probably need to create a vbox to put into the frame, then the
//window, then the hbox. !!! FIX !!!
// frame.add(hbox);
//Hmm. Do we really need g_object_set_data? Mebbe not... And we don't! :-)
// g_object_set_data(G_OBJECT(flipX), "handler", gint_to_pointer(flipX.connect("toggled", G_CALLBACK(TexTool::flipX), 0)));
// g_object_set_data(G_OBJECT(flipY), "handler", gint_to_pointer(flipY.connect("toggled", G_CALLBACK(TexTool::flipY), 0)));
//Instead, just do:
flipX.connect( "toggled", G_CALLBACK( TexTool::flipX ), NULL );
flipY.connect( "toggled", G_CALLBACK( TexTool::flipY ), NULL );
}
}
#endif
}
/* make small */
gtk_window_set_default_size(GTK_WINDOW(window), 8, 8);
return window;
}
/*
==============
Update
Set the fields to the current texdef (i.e. map/texdef -> dialog widgets)
if faces selected (instead of brushes) -> will read this face texdef, else current texdef
if only patches selected, will read the patch texdef
===============
*/
void spin_button_set_value_no_signal(ui::SpinButton spin, gdouble value)
{
guint handler_id = gpointer_to_int(g_object_get_data(G_OBJECT(spin), "handler"));
g_signal_handler_block(G_OBJECT(gtk_spin_button_get_adjustment(spin)), handler_id);
gtk_spin_button_set_value(spin, value);
g_signal_handler_unblock(G_OBJECT(gtk_spin_button_get_adjustment(spin)), handler_id);
}
void spin_button_set_step_increment(ui::SpinButton spin, gdouble value)
{
auto adjust = gtk_spin_button_get_adjustment(spin);
gtk_adjustment_set_step_increment(adjust, value);
}
void SurfaceInspector::Update()
{
const char *name = SurfaceInspector_GetSelectedShader();
if (shader_is_texture(name)) {
m_texture.text(shader_get_textureName(name));
} else {
m_texture.text("");
}
texdef_t shiftScaleRotate;
//Shamus: This is where we get into trouble--the BP code tries to convert to a "faked"
//shift, rotate & scale values from the brush face, which seems to screw up for some reason.
//!!! FIX !!!
/*globalOutputStream() << "--> SI::Update. About to do ShiftScaleRotate_fromFace()...\n";
SurfaceInspector_GetSelectedBPTexdef();
globalOutputStream() << "BP: (" << g_selectedBrushPrimitTexdef.coords[0][0] << ", " << g_selectedBrushPrimitTexdef.coords[0][1] << ")("
<< g_selectedBrushPrimitTexdef.coords[1][0] << ", " << g_selectedBrushPrimitTexdef.coords[1][1] << ")("
<< g_selectedBrushPrimitTexdef.coords[0][2] << ", " << g_selectedBrushPrimitTexdef.coords[1][2] << ") SurfaceInspector::Update\n";//*/
//Ok, it's screwed up *before* we get here...
ShiftScaleRotate_fromFace(shiftScaleRotate, SurfaceInspector_GetSelectedTexdef());
// normalize again to hide the ridiculously high scale values that get created when using texlock
shiftScaleRotate.shift[0] = float_mod(shiftScaleRotate.shift[0], (float) g_selectedShaderSize[0]);
shiftScaleRotate.shift[1] = float_mod(shiftScaleRotate.shift[1], (float) g_selectedShaderSize[1]);
{
spin_button_set_value_no_signal(m_hshiftIncrement.m_spin, shiftScaleRotate.shift[0]);
spin_button_set_step_increment(m_hshiftIncrement.m_spin, g_si_globals.shift[0]);
entry_set_float(m_hshiftIncrement.m_entry, g_si_globals.shift[0]);
}
{
spin_button_set_value_no_signal(m_vshiftIncrement.m_spin, shiftScaleRotate.shift[1]);
spin_button_set_step_increment(m_vshiftIncrement.m_spin, g_si_globals.shift[1]);
entry_set_float(m_vshiftIncrement.m_entry, g_si_globals.shift[1]);
}
{
spin_button_set_value_no_signal(m_hscaleIncrement.m_spin, shiftScaleRotate.scale[0]);
spin_button_set_step_increment(m_hscaleIncrement.m_spin, g_si_globals.scale[0]);
entry_set_float(m_hscaleIncrement.m_entry, g_si_globals.scale[0]);
}
{
spin_button_set_value_no_signal(m_vscaleIncrement.m_spin, shiftScaleRotate.scale[1]);
spin_button_set_step_increment(m_vscaleIncrement.m_spin, g_si_globals.scale[1]);
entry_set_float(m_vscaleIncrement.m_entry, g_si_globals.scale[1]);
}
{
spin_button_set_value_no_signal(m_rotateIncrement.m_spin, shiftScaleRotate.rotate);
spin_button_set_step_increment(m_rotateIncrement.m_spin, g_si_globals.rotate);
entry_set_float(m_rotateIncrement.m_entry, g_si_globals.rotate);
}
if (!string_empty(g_pGameDescription->getKeyValue("si_flags"))) {
ContentsFlagsValue flags(SurfaceInspector_GetSelectedFlags());
entry_set_int(m_valueEntryWidget, flags.m_value);
for (GtkCheckButton **p = m_surfaceFlags; p != m_surfaceFlags + 32; ++p) {
toggle_button_set_active_no_signal(ui::CheckButton::from(*p),
flags.m_surfaceFlags & (1 << (p - m_surfaceFlags)));
}
for (GtkCheckButton **p = m_contentFlags; p != m_contentFlags + 32; ++p) {
toggle_button_set_active_no_signal(ui::CheckButton::from(*p),
flags.m_contentFlags & (1 << (p - m_contentFlags)));
}
}
}
/*
==============
Apply
Reads the fields to get the current texdef (i.e. widgets -> MAP)
in brush primitive mode, grab the fake shift scale rot and compute a new texture matrix
===============
*/
void SurfaceInspector::ApplyShader()
{
StringOutputStream name(256);
name << GlobalTexturePrefix_get() << gtk_entry_get_text(m_texture);
// TTimo: detect and refuse invalid texture names (at least the ones with spaces)
if (!texdef_name_valid(name.c_str())) {
globalErrorStream() << "invalid texture name '" << name.c_str() << "'\n";
SurfaceInspector_queueDraw();
return;
}
UndoableCommand undo("textureNameSetSelected");
Select_SetShader(name.c_str());
}
void SurfaceInspector::ApplyTexdef()
{
texdef_t shiftScaleRotate;
shiftScaleRotate.shift[0] = static_cast<float>( gtk_spin_button_get_value(m_hshiftIncrement.m_spin));
shiftScaleRotate.shift[1] = static_cast<float>( gtk_spin_button_get_value(m_vshiftIncrement.m_spin));
shiftScaleRotate.scale[0] = static_cast<float>( gtk_spin_button_get_value(m_hscaleIncrement.m_spin));
shiftScaleRotate.scale[1] = static_cast<float>( gtk_spin_button_get_value(m_vscaleIncrement.m_spin));
shiftScaleRotate.rotate = static_cast<float>( gtk_spin_button_get_value(m_rotateIncrement.m_spin));
TextureProjection projection;
//Shamus: This is the other place that screws up, it seems, since it doesn't seem to do the
//conversion from the face (I think) and so bogus values end up in the thing... !!! FIX !!!
//This is actually OK. :-P
ShiftScaleRotate_toFace(shiftScaleRotate, projection);
UndoableCommand undo("textureProjectionSetSelected");
Select_SetTexdef(projection, true);
}
void SurfaceInspector::ApplyFlags()
{
unsigned int surfaceflags = 0;
for (GtkCheckButton **p = m_surfaceFlags; p != m_surfaceFlags + 32; ++p) {
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(*p))) {
surfaceflags |= (1 << (p - m_surfaceFlags));
}
}
unsigned int contentflags = 0;
for (GtkCheckButton **p = m_contentFlags; p != m_contentFlags + 32; ++p) {
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(*p))) {
contentflags |= (1 << (p - m_contentFlags));
}
}
int value = entry_get_int(m_valueEntryWidget);
UndoableCommand undo("flagsSetSelected");
Select_SetFlags(ContentsFlagsValue(surfaceflags, contentflags, value, true));
}
void Face_getTexture(Face &face, CopiedString &shader, TextureProjection &projection, ContentsFlagsValue &flags)
{
shader = face.GetShader();
face.GetTexdef(projection);
flags = face.getShader().m_flags;
}
typedef Function<void (Face &, CopiedString &, TextureProjection &,
ContentsFlagsValue &), Face_getTexture> FaceGetTexture;
void
Face_setTexture(Face &face, const char *shader, const TextureProjection &projection, const ContentsFlagsValue &flags)
{
face.SetShader(shader);
face.SetTexdef(projection, false);
face.SetFlags(flags);
}
typedef Function<void (Face &, const char *, const TextureProjection &,
const ContentsFlagsValue &), Face_setTexture> FaceSetTexture;
void Patch_getTexture(Patch &patch, CopiedString &shader, TextureProjection &projection, ContentsFlagsValue &flags)
{
shader = patch.GetShader();
projection = TextureProjection(texdef_t(), brushprimit_texdef_t(), Vector3(0, 0, 0), Vector3(0, 0, 0));
flags = ContentsFlagsValue(0, 0, 0, false);
}
typedef Function<void (Patch &, CopiedString &, TextureProjection &,
ContentsFlagsValue &), Patch_getTexture> PatchGetTexture;
void
Patch_setTexture(Patch &patch, const char *shader, const TextureProjection &projection, const ContentsFlagsValue &flags)
{
patch.SetShader(shader);
}
typedef Function<void (Patch &, const char *, const TextureProjection &,
const ContentsFlagsValue &), Patch_setTexture> PatchSetTexture;
typedef Callback<void (CopiedString &, TextureProjection &, ContentsFlagsValue &)> GetTextureCallback;
typedef Callback<void (const char *, const TextureProjection &, const ContentsFlagsValue &)> SetTextureCallback;
struct Texturable {
GetTextureCallback getTexture;
SetTextureCallback setTexture;
};
void Face_getClosest(Face &face, SelectionTest &test, SelectionIntersection &bestIntersection, Texturable &texturable)
{
SelectionIntersection intersection;
face.testSelect(test, intersection);
if (intersection.valid()
&& SelectionIntersection_closer(intersection, bestIntersection)) {
bestIntersection = intersection;
texturable.setTexture = makeCallback(FaceSetTexture(), face);
texturable.getTexture = makeCallback(FaceGetTexture(), face);
}
}
class OccludeSelector : public Selector {
SelectionIntersection &m_bestIntersection;
bool &m_occluded;
public:
OccludeSelector(SelectionIntersection &bestIntersection, bool &occluded) : m_bestIntersection(bestIntersection),
m_occluded(occluded)
{
m_occluded = false;
}
void pushSelectable(Selectable &selectable)
{
}
void popSelectable()
{
}
void addIntersection(const SelectionIntersection &intersection)
{
if (SelectionIntersection_closer(intersection, m_bestIntersection)) {
m_bestIntersection = intersection;
m_occluded = true;
}
}
};
class BrushGetClosestFaceVisibleWalker : public scene::Graph::Walker {
SelectionTest &m_test;
Texturable &m_texturable;
mutable SelectionIntersection m_bestIntersection;
public:
BrushGetClosestFaceVisibleWalker(SelectionTest &test, Texturable &texturable) : m_test(test),
m_texturable(texturable)
{
}
bool pre(const scene::Path &path, scene::Instance &instance) const
{
if (path.top().get().visible()) {
BrushInstance *brush = Instance_getBrush(instance);
if (brush != 0) {
m_test.BeginMesh(brush->localToWorld());
for (Brush::const_iterator i = brush->getBrush().begin(); i != brush->getBrush().end(); ++i) {
Face_getClosest(*(*i), m_test, m_bestIntersection, m_texturable);
}
} else {
SelectionTestable *selectionTestable = Instance_getSelectionTestable(instance);
if (selectionTestable) {
bool occluded;
OccludeSelector selector(m_bestIntersection, occluded);
selectionTestable->testSelect(selector, m_test);
if (occluded) {
Patch *patch = Node_getPatch(path.top());
if (patch != 0) {
m_texturable.setTexture = makeCallback(PatchSetTexture(), *patch);
m_texturable.getTexture = makeCallback(PatchGetTexture(), *patch);
} else {
m_texturable = Texturable();
}
}
}
}
}
return true;
}
};
Texturable Scene_getClosestTexturable(scene::Graph &graph, SelectionTest &test)
{
Texturable texturable;
graph.traverse(BrushGetClosestFaceVisibleWalker(test, texturable));
return texturable;
}
bool
Scene_getClosestTexture(scene::Graph &graph, SelectionTest &test, CopiedString &shader, TextureProjection &projection,
ContentsFlagsValue &flags)
{
Texturable texturable = Scene_getClosestTexturable(graph, test);
if (texturable.getTexture != GetTextureCallback()) {
texturable.getTexture(shader, projection, flags);
return true;
}
return false;
}
void Scene_setClosestTexture(scene::Graph &graph, SelectionTest &test, const char *shader,
const TextureProjection &projection, const ContentsFlagsValue &flags)
{
Texturable texturable = Scene_getClosestTexturable(graph, test);
if (texturable.setTexture != SetTextureCallback()) {
texturable.setTexture(shader, projection, flags);
}
}
class FaceTexture {
public:
TextureProjection m_projection;
ContentsFlagsValue m_flags;
};
FaceTexture g_faceTextureClipboard;
void FaceTextureClipboard_setDefault()
{
g_faceTextureClipboard.m_flags = ContentsFlagsValue(0, 0, 0, false);
TexDef_Construct_Default(g_faceTextureClipboard.m_projection);
}
void TextureClipboard_textureSelected(const char *shader)
{
FaceTextureClipboard_setDefault();
}
class TextureBrowser;
extern TextureBrowser g_TextureBrowser;
void TextureBrowser_SetSelectedShader(TextureBrowser &textureBrowser, const char *shader);
const char *TextureBrowser_GetSelectedShader(TextureBrowser &textureBrowser);
void Scene_copyClosestTexture(SelectionTest &test)
{
CopiedString shader;
if (Scene_getClosestTexture(GlobalSceneGraph(), test, shader, g_faceTextureClipboard.m_projection,
g_faceTextureClipboard.m_flags)) {
TextureBrowser_SetSelectedShader(g_TextureBrowser, shader.c_str());
}
}
void Scene_applyClosestTexture(SelectionTest &test)
{
UndoableCommand command("facePaintTexture");
Scene_setClosestTexture(GlobalSceneGraph(), test, TextureBrowser_GetSelectedShader(g_TextureBrowser),
g_faceTextureClipboard.m_projection, g_faceTextureClipboard.m_flags);
SceneChangeNotify();
}
void SelectedFaces_copyTexture()
{
if (!g_SelectedFaceInstances.empty()) {
Face &face = g_SelectedFaceInstances.last().getFace();
face.GetTexdef(g_faceTextureClipboard.m_projection);
g_faceTextureClipboard.m_flags = face.getShader().m_flags;
TextureBrowser_SetSelectedShader(g_TextureBrowser, face.getShader().getShader());
}
}
void FaceInstance_pasteTexture(FaceInstance &faceInstance)
{
faceInstance.getFace().SetTexdef(g_faceTextureClipboard.m_projection, false);
faceInstance.getFace().SetShader(TextureBrowser_GetSelectedShader(g_TextureBrowser));
faceInstance.getFace().SetFlags(g_faceTextureClipboard.m_flags);
SceneChangeNotify();
}
bool SelectedFaces_empty()
{
return g_SelectedFaceInstances.empty();
}
void SelectedFaces_pasteTexture()
{
UndoableCommand command("facePasteTexture");
g_SelectedFaceInstances.foreach(FaceInstance_pasteTexture);
}
void SurfaceInspector_constructPreferences(PreferencesPage &page)
{
page.appendCheckBox("", "Surface Inspector Increments Match Grid", g_si_globals.m_bSnapTToGrid);
}
void SurfaceInspector_constructPage(PreferenceGroup &group)
{
PreferencesPage page(group.createPage("Surface Inspector", "Surface Inspector Preferences"));
SurfaceInspector_constructPreferences(page);
}
void SurfaceInspector_registerPreferencesPage()
{
PreferencesDialog_addSettingsPage(makeCallbackF(SurfaceInspector_constructPage));
}
void SurfaceInspector_registerCommands()
{
GlobalCommands_insert("FitTexture", makeCallbackF(SurfaceInspector_FitTexture),
Accelerator('B', (GdkModifierType) GDK_SHIFT_MASK));
GlobalCommands_insert("SurfaceInspector", makeCallbackF(SurfaceInspector_toggleShown), Accelerator('S'));
GlobalCommands_insert("FaceCopyTexture", makeCallbackF(SelectedFaces_copyTexture));
GlobalCommands_insert("FacePasteTexture", makeCallbackF(SelectedFaces_pasteTexture));
}
#include "preferencesystem.h"
void SurfaceInspector_Construct()
{
g_SurfaceInspector = new SurfaceInspector;
SurfaceInspector_registerCommands();
FaceTextureClipboard_setDefault();
GlobalPreferenceSystem().registerPreference("SurfaceWnd", make_property<WindowPositionTracker_String>(
getSurfaceInspector().m_positionTracker));
GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Scale1", make_property_string(g_si_globals.scale[0]));
GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Scale2", make_property_string(g_si_globals.scale[1]));
GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Shift1", make_property_string(g_si_globals.shift[0]));
GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Shift2", make_property_string(g_si_globals.shift[1]));
GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Rotate", make_property_string(g_si_globals.rotate));
GlobalPreferenceSystem().registerPreference("SnapTToGrid", make_property_string(g_si_globals.m_bSnapTToGrid));
typedef FreeCaller<void (
const Selectable &), SurfaceInspector_SelectionChanged> SurfaceInspectorSelectionChangedCaller;
GlobalSelectionSystem().addSelectionChangeCallback(SurfaceInspectorSelectionChangedCaller());
typedef FreeCaller<void (), SurfaceInspector_updateSelection> SurfaceInspectorUpdateSelectionCaller;
Brush_addTextureChangedCallback(SurfaceInspectorUpdateSelectionCaller());
Patch_addTextureChangedCallback(SurfaceInspectorUpdateSelectionCaller());
SurfaceInspector_registerPreferencesPage();
}
void SurfaceInspector_Destroy()
{
delete g_SurfaceInspector;
}
#if TEXTOOL_ENABLED
namespace TexTool { // namespace hides these symbols from other object-files
//
//Shamus: Textool functions, including GTK+ callbacks
//
//NOTE: Black screen when TT first comes up is caused by an uninitialized Extent... !!! FIX !!!
// But... You can see down below that it *is* initialized! WTF?
struct Extent
{
float minX, minY, maxX, maxY;
float width( void ) {
return fabs( maxX - minX );
}
float height( void ) {
return fabs( maxY - minY );
}
};
//This seems to control the texture scale... (Yep! ;-)
Extent extents = { -2.0f, -2.0f, +2.0f, +2.0f };
brushprimit_texdef_t tm; // Texture transform matrix
Vector2 pts[c_brush_maxFaces];
Vector2 center;
int numPts;
int textureNum;
Vector2 textureSize;
Vector2 windowSize;
#define VP_PADDING 1.2
#define PI 3.14159265358979
bool lButtonDown = false;
bool rButtonDown = false;
//int dragPoint;
//int anchorPoint;
bool haveAnchor = false;
brushprimit_texdef_t currentBP;
brushprimit_texdef_t origBP; // Original brush primitive (before we muck it up)
float controlRadius = 5.0f;
float rotationAngle = 0.0f;
float rotationAngle2 = 0.0f;
float oldRotationAngle;
Vector2 rotationPoint;
bool translatingX = false; // Widget state variables
bool translatingY = false;
bool scalingX = false;
bool scalingY = false;
bool rotating = false;
bool resizingX = false; // Not sure what this means... :-/
bool resizingY = false;
float origAngle, origScaleX, origScaleY;
Vector2 oldCenter;
// Function prototypes (move up to top later...)
void DrawCircularArc( Vector2 ctr, float startAngle, float endAngle, float radius );
void CopyPointsFromSelectedFace( void ){
// Make sure that there's a face and winding to get!
if ( g_SelectedFaceInstances.empty() ) {
numPts = 0;
return;
}
Face & face = g_SelectedFaceInstances.last().getFace();
textureNum = face.getShader().m_state->getTexture().texture_number;
textureSize.x() = face.getShader().m_state->getTexture().width;
textureSize.y() = face.getShader().m_state->getTexture().height;
//globalOutputStream() << "--> Texture #" << textureNum << ": " << textureSize.x() << " x " << textureSize.y() << "...\n";
currentBP = SurfaceInspector_GetSelectedTexdef().m_brushprimit_texdef;
face.EmitTextureCoordinates();
Winding & w = face.getWinding();
int count = 0;
for ( Winding::const_iterator i = w.begin(); i != w.end(); i++ )
{
//globalOutputStream() << (*i).texcoord.x() << " " << (*i).texcoord.y() << ", ";
pts[count].x() = ( *i ).texcoord.x();
pts[count].y() = ( *i ).texcoord.y();
count++;
}
numPts = count;
//globalOutputStream() << " ..copied points\n";
}
brushprimit_texdef_t bp;
//This approach is probably wrongheaded and just not right anyway. So, !!! FIX !!! [DONE]
void CommitChanges( void ){
texdef_t t; // Throwaway, since this is BP only
bp.coords[0][0] = tm.coords[0][0] * origBP.coords[0][0] + tm.coords[0][1] * origBP.coords[1][0];
bp.coords[0][1] = tm.coords[0][0] * origBP.coords[0][1] + tm.coords[0][1] * origBP.coords[1][1];
bp.coords[0][2] = tm.coords[0][0] * origBP.coords[0][2] + tm.coords[0][1] * origBP.coords[1][2] + tm.coords[0][2];
//Ok, this works for translation...
// bp.coords[0][2] = tm.coords[0][0] * origBP.coords[0][2] + tm.coords[0][1] * origBP.coords[1][2] + tm.coords[0][2] * textureSize.x();
bp.coords[1][0] = tm.coords[1][0] * origBP.coords[0][0] + tm.coords[1][1] * origBP.coords[1][0];
bp.coords[1][1] = tm.coords[1][0] * origBP.coords[0][1] + tm.coords[1][1] * origBP.coords[1][1];
bp.coords[1][2] = tm.coords[1][0] * origBP.coords[0][2] + tm.coords[1][1] * origBP.coords[1][2] + tm.coords[1][2];
// bp.coords[1][2] = tm.coords[1][0] * origBP.coords[0][2] + tm.coords[1][1] * origBP.coords[1][2] + tm.coords[1][2] * textureSize.y();
//This doesn't work: g_brush_texture_changed();
// Let's try this:
//Note: We should only set an undo *after* the button has been released... !!! FIX !!!
//Definitely *should* have an undo, though!
// UndoableCommand undo("textureProjectionSetSelected");
Select_SetTexdef( TextureProjection( t, bp, Vector3( 0, 0, 0 ), Vector3( 0, 0, 0 ) ) );
//This is working, but for some reason the translate is causing the rest of the SI
//widgets to yield bad readings... !!! FIX !!!
//I.e., click on textool window, translate face wireframe, then controls go crazy. Dunno why.
//It's because there were some uncommented out add/removeScale functions in brush.h and a
//removeScale in brushmanip.cpp... :-/
//Translate isn't working at all now... :-(
//It's because we need to multiply in some scaling factor (prolly the texture width/height)
//Yep. :-P
}
void UpdateControlPoints( void ){
CommitChanges();
// Init texture transform matrix
tm.coords[0][0] = 1.0f; tm.coords[0][1] = 0.0f; tm.coords[0][2] = 0.0f;
tm.coords[1][0] = 0.0f; tm.coords[1][1] = 1.0f; tm.coords[1][2] = 0.0f;
}
/*
For shifting we have:
*/
/*
The code that should provide reasonable defaults, but doesn't for some reason:
It's scaling the BP by 128 for some reason, between the time it's created and the
time we get back to the SI widgets:
static void OnBtnAxial(GtkWidget *widget, gpointer data)
{
UndoableCommand undo("textureDefault");
TextureProjection projection;
TexDef_Construct_Default(projection);
Select_SetTexdef(projection);
}
Select_SetTexdef() calls Scene_BrushSetTexdef_Component_Selected(GlobalSceneGraph(), projection)
which is in brushmanip.h: This eventually calls
Texdef_Assign(m_texdef, texdef, m_brushprimit_texdef, brushprimit_texdef) in class Face...
which just copies from brushpr to m_brushpr...
*/
//Small problem with this thing: It's scaled to the texture which is all screwed up... !!! FIX !!! [DONE]
//Prolly should separate out the grid drawing so that we can draw it behind the polygon.
const float gridWidth = 1.3f; // Let's try an absolute height... WORKS!!!
// NOTE that 2.0 is the height of the viewport. Dunno why... Should make collision
// detection easier...
const float gridRadius = gridWidth * 0.5f;
typedef const float WidgetColor[3];
const WidgetColor widgetColor[10] = {
{ 1.0000f, 0.2000f, 0.0000f }, // Red
{ 0.9137f, 0.9765f, 0.4980f }, // Yellow
{ 0.0000f, 0.6000f, 0.3216f }, // Green
{ 0.6157f, 0.7726f, 0.8196f }, // Cyan
{ 0.4980f, 0.5000f, 0.4716f }, // Grey
// Highlight colors
{ 1.0000f, 0.6000f, 0.4000f }, // Light Red
{ 1.0000f, 1.0000f, 0.8980f }, // Light Yellow
{ 0.4000f, 1.0000f, 0.7216f }, // Light Green
{ 1.0000f, 1.0000f, 1.0000f }, // Light Cyan
{ 0.8980f, 0.9000f, 0.8716f } // Light Grey
};
#define COLOR_RED 0
#define COLOR_YELLOW 1
#define COLOR_GREEN 2
#define COLOR_CYAN 3
#define COLOR_GREY 4
#define COLOR_LT_RED 5
#define COLOR_LT_YELLOW 6
#define COLOR_LT_GREEN 7
#define COLOR_LT_CYAN 8
#define COLOR_LT_GREY 9
void DrawControlWidgets( void ){
//Note that the grid should go *behind* the face outline... !!! FIX !!!
// Grid
float xStart = center.x() - ( gridWidth / 2.0f );
float yStart = center.y() - ( gridWidth / 2.0f );
float xScale = ( extents.height() / extents.width() ) * ( textureSize.y() / textureSize.x() );
glPushMatrix();
//Small problem with this approach: Changing the center point in the TX code doesn't seem to
//change anything here--prolly because we load a new identity matrix. A couple of ways to fix
//this would be to get rid of that code, or change the center to a new point by taking into
//account the transforms that we toss with the new identity matrix. Dunno which is better.
glLoadIdentity();
glScalef( xScale, 1.0, 1.0 ); // Will that square it up? Yup.
glRotatef( static_cast<float>( radians_to_degrees( atan2( -currentBP.coords[0][1], currentBP.coords[0][0] ) ) ), 0.0, 0.0, -1.0 );
glTranslatef( -center.x(), -center.y(), 0.0 );
// Circle
glColor3fv( translatingX && translatingY ? widgetColor[COLOR_LT_YELLOW] : widgetColor[COLOR_YELLOW] );
glBegin( GL_LINE_LOOP );
DrawCircularArc( center, 0, 2.0f * PI, gridRadius * 0.16 );
glEnd();
// Axes
glBegin( GL_LINES );
glColor3fv( translatingY && !translatingX ? widgetColor[COLOR_LT_GREEN] : widgetColor[COLOR_GREEN] );
glVertex2f( center.x(), center.y() + ( gridRadius * 0.16 ) );
glVertex2f( center.x(), center.y() + ( gridRadius * 1.00 ) );
glColor3fv( translatingX && !translatingY ? widgetColor[COLOR_LT_RED] : widgetColor[COLOR_RED] );
glVertex2f( center.x() + ( gridRadius * 0.16 ), center.y() );
glVertex2f( center.x() + ( gridRadius * 1.00 ), center.y() );
glEnd();
// Arrowheads
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glBegin( GL_TRIANGLES );
glColor3fv( translatingY && !translatingX ? widgetColor[COLOR_LT_GREEN] : widgetColor[COLOR_GREEN] );
glVertex2f( center.x(), center.y() + ( gridRadius * 1.10 ) );
glVertex2f( center.x() + ( gridRadius * 0.06 ), center.y() + ( gridRadius * 0.94 ) );
glVertex2f( center.x() - ( gridRadius * 0.06 ), center.y() + ( gridRadius * 0.94 ) );
glColor3fv( translatingX && !translatingY ? widgetColor[COLOR_LT_RED] : widgetColor[COLOR_RED] );
glVertex2f( center.x() + ( gridRadius * 1.10 ), center.y() );
glVertex2f( center.x() + ( gridRadius * 0.94 ), center.y() + ( gridRadius * 0.06 ) );
glVertex2f( center.x() + ( gridRadius * 0.94 ), center.y() - ( gridRadius * 0.06 ) );
glEnd();
// Arc
glBegin( GL_LINE_STRIP );
glColor3fv( rotating ? widgetColor[COLOR_LT_CYAN] : widgetColor[COLOR_CYAN] );
DrawCircularArc( center, 0.03f * PI, 0.47f * PI, gridRadius * 0.90 );
glEnd();
// Boxes
glColor3fv( scalingY && !scalingX ? widgetColor[COLOR_LT_GREEN] : widgetColor[COLOR_GREEN] );
glBegin( GL_LINES );
glVertex2f( center.x() + ( gridRadius * 0.20 ), center.y() + ( gridRadius * 1.50 ) );
glVertex2f( center.x() - ( gridRadius * 0.20 ), center.y() + ( gridRadius * 1.50 ) );
glEnd();
glBegin( GL_LINE_LOOP );
glVertex2f( center.x() + ( gridRadius * 0.10 ), center.y() + ( gridRadius * 1.40 ) );
glVertex2f( center.x() - ( gridRadius * 0.10 ), center.y() + ( gridRadius * 1.40 ) );
glVertex2f( center.x() - ( gridRadius * 0.10 ), center.y() + ( gridRadius * 1.20 ) );
glVertex2f( center.x() + ( gridRadius * 0.10 ), center.y() + ( gridRadius * 1.20 ) );
glEnd();
glColor3fv( scalingX && !scalingY ? widgetColor[COLOR_LT_RED] : widgetColor[COLOR_RED] );
glBegin( GL_LINES );
glVertex2f( center.x() + ( gridRadius * 1.50 ), center.y() + ( gridRadius * 0.20 ) );
glVertex2f( center.x() + ( gridRadius * 1.50 ), center.y() - ( gridRadius * 0.20 ) );
glEnd();
glBegin( GL_LINE_LOOP );
glVertex2f( center.x() + ( gridRadius * 1.40 ), center.y() + ( gridRadius * 0.10 ) );
glVertex2f( center.x() + ( gridRadius * 1.40 ), center.y() - ( gridRadius * 0.10 ) );
glVertex2f( center.x() + ( gridRadius * 1.20 ), center.y() - ( gridRadius * 0.10 ) );
glVertex2f( center.x() + ( gridRadius * 1.20 ), center.y() + ( gridRadius * 0.10 ) );
glEnd();
glColor3fv( scalingX && scalingY ? widgetColor[COLOR_LT_CYAN] : widgetColor[COLOR_CYAN] );
glBegin( GL_LINE_STRIP );
glVertex2f( center.x() + ( gridRadius * 1.50 ), center.y() + ( gridRadius * 1.10 ) );
glVertex2f( center.x() + ( gridRadius * 1.50 ), center.y() + ( gridRadius * 1.50 ) );
glVertex2f( center.x() + ( gridRadius * 1.10 ), center.y() + ( gridRadius * 1.50 ) );
glEnd();
glBegin( GL_LINE_LOOP );
glVertex2f( center.x() + ( gridRadius * 1.40 ), center.y() + ( gridRadius * 1.40 ) );
glVertex2f( center.x() + ( gridRadius * 1.40 ), center.y() + ( gridRadius * 1.20 ) );
glVertex2f( center.x() + ( gridRadius * 1.20 ), center.y() + ( gridRadius * 1.20 ) );
glVertex2f( center.x() + ( gridRadius * 1.20 ), center.y() + ( gridRadius * 1.40 ) );
glEnd();
glPopMatrix();
}
void DrawControlPoints( void ){
glColor3f( 1, 1, 1 );
glBegin( GL_LINE_LOOP );
for ( int i = 0; i < numPts; i++ )
glVertex2f( pts[i].x(), pts[i].y() );
glEnd();
}
// Note: Setup and all that jazz must be done by the caller!
void DrawCircularArc( Vector2 ctr, float startAngle, float endAngle, float radius ){
float stepSize = ( 2.0f * PI ) / 200.0f;
for ( float angle = startAngle; angle <= endAngle; angle += stepSize )
glVertex2f( ctr.x() + radius * cos( angle ), ctr.y() + radius * sin( angle ) );
}
void focus(){
if ( numPts == 0 ) {
return;
}
// Find selected texture's extents...
extents.minX = extents.maxX = pts[0].x(),
extents.minY = extents.maxY = pts[0].y();
for ( int i = 1; i < numPts; i++ )
{
if ( pts[i].x() < extents.minX ) {
extents.minX = pts[i].x();
}
if ( pts[i].x() > extents.maxX ) {
extents.maxX = pts[i].x();
}
if ( pts[i].y() < extents.minY ) {
extents.minY = pts[i].y();
}
if ( pts[i].y() > extents.maxY ) {
extents.maxY = pts[i].y();
}
}
// Do some viewport fitting stuff...
//globalOutputStream() << "--> Center: " << center.x() << ", " << center.y() << "\n";
//globalOutputStream() << "--> Extents (stage 1): " << extents.minX << ", "
// << extents.maxX << ", " << extents.minY << ", " << extents.maxY << "\n";
// TTimo: Apply a ratio to get the area we'll draw.
center.x() = 0.5f * ( extents.minX + extents.maxX ),
center.y() = 0.5f * ( extents.minY + extents.maxY );
extents.minX = center.x() + VP_PADDING * ( extents.minX - center.x() ),
extents.minY = center.y() + VP_PADDING * ( extents.minY - center.y() ),
extents.maxX = center.x() + VP_PADDING * ( extents.maxX - center.x() ),
extents.maxY = center.y() + VP_PADDING * ( extents.maxY - center.y() );
//globalOutputStream() << "--> Extents (stage 2): " << extents.minX << ", "
// << extents.maxX << ", " << extents.minY << ", " << extents.maxY << "\n";
// TTimo: We want a texture with the same X / Y ratio.
// TTimo: Compute XY space / window size ratio.
float SSize = extents.width(), TSize = extents.height();
float ratioX = textureSize.x() * extents.width() / windowSize.x(),
ratioY = textureSize.y() * extents.height() / windowSize.y();
//globalOutputStream() << "--> Texture size: " << textureSize.x() << ", " << textureSize.y() << "\n";
//globalOutputStream() << "--> Window size: " << windowSize.x() << ", " << windowSize.y() << "\n";
if ( ratioX > ratioY ) {
TSize = ( windowSize.y() * ratioX ) / textureSize.y();
// TSize = extents.width() * (windowSize.y() / windowSize.x()) * (textureSize.x() / textureSize.y());
}
else
{
SSize = ( windowSize.x() * ratioY ) / textureSize.x();
// SSize = extents.height() * (windowSize.x() / windowSize.y()) * (textureSize.y() / textureSize.x());
}
extents.minX = center.x() - 0.5f * SSize, extents.maxX = center.x() + 0.5f * SSize,
extents.minY = center.y() - 0.5f * TSize, extents.maxY = center.y() + 0.5f * TSize;
//globalOutputStream() << "--> Extents (stage 3): " << extents.minX << ", "
// << extents.maxX << ", " << extents.minY << ", " << extents.maxY << "\n";
}
gboolean size_allocate( ui::Widget win, GtkAllocation * a, gpointer ){
windowSize.x() = a->width;
windowSize.y() = a->height;
queueDraw();
return false;
}
gboolean expose( ui::Widget win, GdkEventExpose * e, gpointer ){
// globalOutputStream() << "--> Textool Window was exposed!\n";
// globalOutputStream() << " (window width/height: " << cc << "/" << e->area.height << ")\n";
// windowSize.x() = e->area.width, windowSize.y() = e->area.height;
//This needs to go elsewhere...
// InitTextool();
if ( glwidget_make_current( win ) == FALSE ) {
globalOutputStream() << " FAILED to make current! Oh, the agony! :-(\n";
return true;
}
CopyPointsFromSelectedFace();
if ( !lButtonDown ) {
focus();
}
// Probably should init button/anchor states here as well...
// rotationAngle = 0.0f;
glClearColor( 0, 0, 0, 0 );
glViewport( 0, 0, e->area.width, e->area.height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
//???
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glDisable( GL_DEPTH_TEST );
glDisable( GL_BLEND );
glOrtho( extents.minX, extents.maxX, extents.maxY, extents.minY, -1, 1 );
glColor3f( 1, 1, 1 );
// draw the texture background
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glBindTexture( GL_TEXTURE_2D, textureNum );
glEnable( GL_TEXTURE_2D );
glBegin( GL_QUADS );
glTexCoord2f( extents.minX, extents.minY );
glVertex2f( extents.minX, extents.minY );
glTexCoord2f( extents.maxX, extents.minY );
glVertex2f( extents.maxX, extents.minY );
glTexCoord2f( extents.maxX, extents.maxY );
glVertex2f( extents.maxX, extents.maxY );
glTexCoord2f( extents.minX, extents.maxY );
glVertex2f( extents.minX, extents.maxY );
glEnd();
glDisable( GL_TEXTURE_2D );
// draw the texture-space grid
glColor3fv( widgetColor[COLOR_GREY] );
glBegin( GL_LINES );
const int gridSubdivisions = 8;
const float gridExtents = 4.0f;
for ( int i = 0; i < gridSubdivisions + 1; ++i )
{
float y = i * ( gridExtents / float(gridSubdivisions) );
float x = i * ( gridExtents / float(gridSubdivisions) );
glVertex2f( 0, y );
glVertex2f( gridExtents, y );
glVertex2f( x, 0 );
glVertex2f( x, gridExtents );
}
glEnd();
DrawControlPoints();
DrawControlWidgets();
//???
// reset the current texture
// glBindTexture(GL_TEXTURE_2D, 0);
// glFinish();
glwidget_swap_buffers( win );
return false;
}
/*int FindSelectedPoint(int x, int y)
{
for(int i=0; i<numPts; i++)
{
int nx = (int)(windowSize.x() * (pts[i].x() - extents.minX) / extents.width());
int ny = (int)(windowSize.y() * (pts[i].y() - extents.minY) / extents.height());
if (abs(nx - x) <= 3 && abs(ny - y) <= 3)
return i;
}
return -1;
}//*/
Vector2 trans;
Vector2 trans2;
Vector2 dragPoint; // Defined in terms of window space (+x/-y)
Vector2 oldTrans;
gboolean button_press( ui::Widget win, GdkEventButton * e, gpointer ){
// globalOutputStream() << "--> Textool button press...\n";
if ( e->button == 1 ) {
lButtonDown = true;
GlobalUndoSystem().start();
origBP = currentBP;
//globalOutputStream() << "--> Original BP: [" << origBP.coords[0][0] << "][" << origBP.coords[0][1] << "][" << origBP.coords[0][2] << "]\n";
//globalOutputStream() << " [" << origBP.coords[1][0] << "][" << origBP.coords[1][1] << "][" << origBP.coords[1][2] << "]\n";
//float angle = atan2(origBP.coords[0][1], origBP.coords[0][0]) * 180.0f / 3.141592653589f;
origAngle = ( origBP.coords[0][1] > 0 ? PI : -PI ); // Could also be -PI... !!! FIX !!! [DONE]
if ( origBP.coords[0][0] != 0.0f ) {
origAngle = atan( origBP.coords[0][1] / origBP.coords[0][0] );
}
origScaleX = origBP.coords[0][0] / cos( origAngle );
origScaleY = origBP.coords[1][1] / cos( origAngle );
rotationAngle = origAngle;
oldCenter[0] = oldCenter[1] = 0;
//globalOutputStream() << "--> BP stats: ang=" << origAngle * RAD_TO_DEG << ", scale=" << origScaleX << "/" << origScaleY << "\n";
//Should also set the Flip X/Y checkboxes here as well... !!! FIX !!!
//Also: should reverse texture left/right up/down instead of flipping the points...
//disnowok
//float nx = windowSize.x() * (e->x - extents.minX) / (extents.maxX - extents.minX);
//float ny = windowSize.y() * (e->y - extents.minY) / (extents.maxY - extents.minY);
//disdoes...
//But I want it to scroll the texture window, not the points... !!! FIX !!!
//Actually, should scroll the texture window only when mouse is down on no widgets...
float nx = e->x / windowSize.x() * extents.width() + extents.minX;
float ny = e->y / windowSize.y() * extents.height() + extents.minY;
trans.x() = -tm.coords[0][0] * nx - tm.coords[0][1] * ny;
trans.y() = -tm.coords[1][0] * nx - tm.coords[1][1] * ny;
dragPoint.x() = e->x, dragPoint.y() = e->y;
trans2.x() = nx, trans2.y() = ny;
oldRotationAngle = rotationAngle;
// oldTrans.x() = tm.coords[0][2] - nx * textureSize.x();
// oldTrans.y() = tm.coords[1][2] - ny * textureSize.y();
oldTrans.x() = tm.coords[0][2];
oldTrans.y() = tm.coords[1][2];
oldCenter.x() = center.x();
oldCenter.y() = center.y();
queueDraw();
return true;
}
/* else if (e->button == 3)
{
rButtonDown = true;
}//*/
//globalOutputStream() << "(" << (haveAnchor ? "anchor" : "released") << ")\n";
return false;
}
gboolean button_release( ui::Widget win, GdkEventButton * e, gpointer ){
// globalOutputStream() << "--> Textool button release...\n";
if ( e->button == 1 ) {
/* float ptx = e->x / windowSize.x() * extents.width() + extents.minX;
float pty = e->y / windowSize.y() * extents.height() + extents.minY;
//This prolly should go into the mouse move code...
//Doesn't work correctly anyway...
if (translatingX || translatingY)
center.x() = ptx, center.y() = pty;//*/
lButtonDown = false;
if ( translatingX || translatingY ) {
GlobalUndoSystem().finish( "translateTexture" );
}
else if ( rotating ) {
GlobalUndoSystem().finish( "rotateTexture" );
}
else if ( scalingX || scalingY ) {
GlobalUndoSystem().finish( "scaleTexture" );
}
else if ( resizingX || resizingY ) {
GlobalUndoSystem().finish( "resizeTexture" );
}
else
{
GlobalUndoSystem().finish( "textoolUnknown" );
}
rotating = translatingX = translatingY = scalingX = scalingY
= resizingX = resizingY = false;
queueDraw();
}
else if ( e->button == 3 ) {
rButtonDown = false;
}
return true;
}
/*
void C2DView::GridForWindow( float c[2], int x, int y)
{
SpaceForWindow( c, x, y );
if ( !m_bDoGrid )
return;
c[0] /= m_GridStep[0];
c[1] /= m_GridStep[1];
c[0] = (float)floor( c[0] + 0.5f );
c[1] = (float)floor( c[1] + 0.5f );
c[0] *= m_GridStep[0];
c[1] *= m_GridStep[1];
}
void C2DView::SpaceForWindow( float c[2], int x, int y)
{
c[0] = ((float)(x))/((float)(m_rect.right-m_rect.left))*(m_Maxs[0]-m_Mins[0])+m_Mins[0];
c[1] = ((float)(y))/((float)(m_rect.bottom-m_rect.top))*(m_Maxs[1]-m_Mins[1])+m_Mins[1];
}
*/
gboolean motion( ui::Widget win, GdkEventMotion * e, gpointer ){
// globalOutputStream() << "--> Textool motion...\n";
if ( lButtonDown ) {
if ( translatingX || translatingY ) {
float ptx = e->x / windowSize.x() * extents.width() + extents.minX;
float pty = e->y / windowSize.y() * extents.height() + extents.minY;
//Need to fix this to take the rotation angle into account, so that it moves along
//the rotated X/Y axis...
if ( translatingX ) {
// tm.coords[0][2] = (trans.x() + ptx) * textureSize.x();
//This works, but only when the angle is zero. !!! FIX !!! [DONE]
// tm.coords[0][2] = oldCenter.x() + (ptx * textureSize.x());
tm.coords[0][2] = oldTrans.x() + ( ptx - trans2.x() ) * textureSize.x();
// center.x() = oldCenter.x() + (ptx - trans2.x());
}
if ( translatingY ) {
// tm.coords[1][2] = (trans.y() + pty) * textureSize.y();
// tm.coords[1][2] = oldCenter.y() + (pty * textureSize.y());
tm.coords[1][2] = oldTrans.y() + ( pty - trans2.y() ) * textureSize.y();
// center.y() = oldCenter.y() + (pty - trans2.y());
}
//Need to update center.x/y() so that the widget translates as well. Also, oldCenter
//is badly named... Should be oldTrans or something like that... !!! FIX !!!
//Changing center.x/y() here doesn't seem to change anything... :-/
UpdateControlPoints();
}
else if ( rotating ) {
// Shamus: New rotate code
int cx = (int)( windowSize.x() * ( center.x() - extents.minX ) / extents.width() );
int cy = (int)( windowSize.y() * ( center.y() - extents.minY ) / extents.height() );
Vector3 v1( dragPoint.x() - cx, dragPoint.y() - cy, 0 ), v2( e->x - cx, e->y - cy, 0 );
vector3_normalise( v1 );
vector3_normalise( v2 );
float c = vector3_dot( v1, v2 );
Vector3 cross = vector3_cross( v1, v2 );
float s = vector3_length( cross );
if ( cross[2] > 0 ) {
s = -s;
}
// Problem with this: arcsin/cos seems to only return -90 to 90 and 0 to 180...
// Can't derive angle from that!
//rotationAngle = asin(s);// * 180.0f / 3.141592653589f;
rotationAngle = acos( c );
//rotationAngle2 = asin(s);
if ( cross[2] < 0 ) {
rotationAngle = -rotationAngle;
}
//NO! DOESN'T WORK! rotationAngle -= 45.0f * DEG_TO_RAD;
//Let's try this:
//No wok.
/*c = cos(rotationAngle - oldRotationAngle);
s = sin(rotationAngle - oldRotationAngle);
rotationAngle += oldRotationAngle;
//c += cos(oldRotationAngle);
//s += sin(oldRotationAngle);
//rotationAngle += oldRotationAngle;
//c %= 2.0 * PI;
//s %= 2.0 * PI;
//rotationAngle %= 2.0 * PI;//*/
//This is wrong... Hmm...
//It seems to shear the texture instead of rotating it... !!! FIX !!!
// Now it rotates correctly. Seems TTimo was overcomplicating things here... ;-)
// Seems like what needs to happen here is multiplying these rotations by tm... !!! FIX !!!
// See brush_primit.cpp line 244 (Texdef_EmitTextureCoordinates()) for where texcoords come from...
tm.coords[0][0] = c;
tm.coords[0][1] = s;
tm.coords[1][0] = -s;
tm.coords[1][1] = c;
//It doesn't work anymore... Dunno why...
//tm.coords[0][2] = -trans.x(); // This works!!! Yeah!!!
//tm.coords[1][2] = -trans.y();
//nope.
//tm.coords[0][2] = rotationPoint.x(); // This works, but strangely...
//tm.coords[1][2] = rotationPoint.y();
//tm.coords[0][2] = 0;// center.x() / 2.0f;
//tm.coords[1][2] = 0;// center.y() / 2.0f;
//No.
//tm.coords[0][2] = -(center.x() * textureSize.x());
//tm.coords[1][2] = -(center.y() * textureSize.y());
//Eh? No, but seems to be getting closer...
/*float ptx = e->x / windowSize.x() * extents.width() + extents.minX;
float pty = e->y / windowSize.y() * extents.height() + extents.minY;
tm.coords[0][2] = -c * center.x() - s * center.y() + ptx;
tm.coords[1][2] = s * center.x() - c * center.x() + pty;//*/
//Kinda works, but center drifts around on non-square textures...
/*tm.coords[0][2] = (-c * center.x() - s * center.y()) * textureSize.x();
tm.coords[1][2] = ( s * center.x() - c * center.y()) * textureSize.y();//*/
//Rotates correctly, but not around the actual center of the face's points...
/*tm.coords[0][2] = -c * center.x() * textureSize.x() - s * center.y() * textureSize.y();
tm.coords[1][2] = s * center.x() * textureSize.x() - c * center.y() * textureSize.y();//*/
//Yes!!!
tm.coords[0][2] = ( -c * center.x() * textureSize.x() - s * center.y() * textureSize.y() ) + center.x() * textureSize.x();
tm.coords[1][2] = ( s * center.x() * textureSize.x() - c * center.y() * textureSize.y() ) + center.y() * textureSize.y(); //*/
//This doesn't work...
//And this is the wrong place for this anyway (I'm pretty sure).
/*tm.coords[0][2] += oldCenter.x();
tm.coords[1][2] += oldCenter.y();//*/
UpdateControlPoints(); // will cause a redraw
}
return true;
}
else // Check for widget mouseovers
{
Vector2 tran;
float nx = e->x / windowSize.x() * extents.width() + extents.minX;
float ny = e->y / windowSize.y() * extents.height() + extents.minY;
// Translate nx/y to the "center" point...
nx -= center.x();
ny -= center.y();
ny = -ny; // Flip Y-axis so that increasing numbers move up
tran.x() = tm.coords[0][0] * nx + tm.coords[0][1] * ny;
tran.y() = tm.coords[1][0] * nx + tm.coords[1][1] * ny;
//This doesn't seem to generate a valid distance from the center--for some reason it
//calculates a fixed number every time
//Look at nx/y above: they're getting fixed there! !!! FIX !!! [DONE]
float dist = sqrt( ( nx * nx ) + ( ny * ny ) );
// Normalize to the 2.0 = height standard (for now)
//globalOutputStream() << "--> Distance before: " << dist;
dist = dist * 2.0f / extents.height();
//globalOutputStream() << ". After: " << dist;
tran.x() = tran.x() * 2.0f / extents.height();
tran.y() = tran.y() * 2.0f / extents.height();
//globalOutputStream() << ". Trans: " << tran.x() << ", " << tran.y() << "\n";
//Let's try this instead...
//Interesting! It seems that e->x/y are rotated
//(no, they're not--the TM above is what's doing it...)
nx = ( ( e->x / windowSize.y() ) * 2.0f ) - ( windowSize.x() / windowSize.y() );
ny = ( ( e->y / windowSize.y() ) * 2.0f ) - ( windowSize.y() / windowSize.y() );
ny = -ny;
//Cool! It works! Now just need to do rotation...
rotating = translatingX = translatingY = scalingX = scalingY
= resizingX = resizingY = false;
if ( dist < ( gridRadius * 0.16f ) ) {
translatingX = translatingY = true;
}
else if ( dist > ( gridRadius * 0.16f ) && dist < ( gridRadius * 1.10f )
&& fabs( ny ) < ( gridRadius * 0.05f ) && nx > 0 ) {
translatingX = true;
}
else if ( dist > ( gridRadius * 0.16f ) && dist < ( gridRadius * 1.10f )
&& fabs( nx ) < ( gridRadius * 0.05f ) && ny > 0 ) {
translatingY = true;
}
// Should tighten up the angle on this, or put this test after the axis tests...
else if ( tran.x() > 0 && tran.y() > 0
&& ( dist > ( gridRadius * 0.82f ) && dist < ( gridRadius * 0.98f ) ) ) {
rotating = true;
}
queueDraw();
return true;
}
return false;
}
//It seems the fake tex coords conversion is screwing this stuff up... !!! FIX !!!
//This is still wrong... Prolly need to do something with the oldScaleX/Y stuff...
void flipX( ui::ToggleButton, gpointer ){
// globalOutputStream() << "--> Flip X...\n";
//Shamus:
// SurfaceInspector_GetSelectedBPTexdef(); // Refresh g_selectedBrushPrimitTexdef...
// tm.coords[0][0] = -tm.coords[0][0];
// tm.coords[1][0] = -tm.coords[1][0];
// tm.coords[0][0] = -tm.coords[0][0]; // This should be correct now...Nope.
// tm.coords[1][1] = -tm.coords[1][1];
tm.coords[0][0] = -tm.coords[0][0]; // This should be correct now...
tm.coords[1][0] = -tm.coords[1][0];
// tm.coords[2][0] = -tm.coords[2][0];//wil wok? no.
UpdateControlPoints();
}
void flipY( ui::ToggleButton, gpointer ){
// globalOutputStream() << "--> Flip Y...\n";
// tm.coords[0][1] = -tm.coords[0][1];
// tm.coords[1][1] = -tm.coords[1][1];
// tm.coords[0][1] = -tm.coords[0][1]; // This should be correct now...Nope.
// tm.coords[1][0] = -tm.coords[1][0];
tm.coords[0][1] = -tm.coords[0][1]; // This should be correct now...
tm.coords[1][1] = -tm.coords[1][1];
// tm.coords[2][1] = -tm.coords[2][1];//wil wok? no.
UpdateControlPoints();
}
} // end namespace TexTool
#endif