mirror of
https://github.com/Garux/netradiant-custom.git
synced 2026-09-28 00:00:03 +02:00
1693 lines
68 KiB
C++
1693 lines
68 KiB
C++
/*
|
|
Copyright (C) 2001-2006, William Joseph.
|
|
All Rights Reserved.
|
|
|
|
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 "selection_mtor_uv.h"
|
|
|
|
#include "selection_.h"
|
|
#include "selection_render.h"
|
|
#include "selection_volume.h"
|
|
#include "brush.h"
|
|
#include "patch.h"
|
|
#include "iglrender.h"
|
|
|
|
class UVManipulatorImpl final : public UVManipulator, public Manipulatable
|
|
{
|
|
struct RenderablePoints : public OpenGLRenderable
|
|
{
|
|
std::vector<PointVertex> m_points;
|
|
void render( RenderStateFlags state ) const override {
|
|
gl().glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_points[0].colour );
|
|
gl().glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_points[0].vertex );
|
|
gl().glDrawArrays( GL_POINTS, 0, m_points.size() );
|
|
}
|
|
};
|
|
struct RenderableLines : public OpenGLRenderable
|
|
{
|
|
std::vector<PointVertex> m_lines;
|
|
|
|
void render( RenderStateFlags state ) const override {
|
|
if( m_lines.size() != 0 ){
|
|
gl().glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_lines[0].colour );
|
|
gl().glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_lines[0].vertex );
|
|
gl().glDrawArrays( GL_LINES, 0, m_lines.size() );
|
|
}
|
|
}
|
|
};
|
|
typedef Array<PatchControl> PatchControlArray;
|
|
struct RenderablePatchTexture : public OpenGLRenderable
|
|
{
|
|
std::vector<RenderIndex> m_trianglesIndices;
|
|
const PatchControlArray* m_patchControlArray;
|
|
|
|
void render( RenderStateFlags state ) const override {
|
|
if( state & RENDER_FILL ){
|
|
const std::vector<Vector3> normals( m_patchControlArray->size(), g_vector3_axis_z );
|
|
gl().glNormalPointer( GL_FLOAT, sizeof( Vector3 ), normals.data() );
|
|
gl().glVertexPointer( 2, GL_FLOAT, sizeof( PatchControl ), &m_patchControlArray->data()->m_texcoord );
|
|
gl().glTexCoordPointer( 2, GL_FLOAT, sizeof( PatchControl ), &m_patchControlArray->data()->m_texcoord );
|
|
gl().glDrawElements( GL_TRIANGLES, GLsizei( m_trianglesIndices.size() ), RenderIndexTypeID, m_trianglesIndices.data() );
|
|
}
|
|
}
|
|
};
|
|
const Colour4b m_cWhite { 255, 255, 255, 255 };
|
|
const Colour4b m_cGray { 255, 255, 255, 125 };
|
|
const Colour4b m_cGrayer{ 100, 100, 100, 150 };
|
|
const Colour4b m_cRed { 255, 0, 0, 255 };
|
|
const Colour4b m_cGreen { 0, 255, 0, 255 };
|
|
const Colour4b m_cGree { 0, 150, 0, 255 };
|
|
const Colour4b m_cPink { 255, 0, 255, 255 };
|
|
const Colour4b m_cPin { 150, 0, 150, 255 };
|
|
const Colour4b m_cOrange{ 255, 125, 0, 255 };
|
|
const Colour4b m_cOrang { 255, 125, 0, 125 };
|
|
|
|
enum EUVSelection{
|
|
eNone,
|
|
ePivot,
|
|
eGridU,
|
|
eGridV,
|
|
ePatchPoint,
|
|
ePatchRow,
|
|
ePatchColumn,
|
|
eCircle,
|
|
ePivotU,
|
|
ePivotV,
|
|
eU,
|
|
eV,
|
|
eUV,
|
|
eSkewU,
|
|
eSkewV,
|
|
eTex,
|
|
} m_selection;
|
|
PointVertex* m_selectedU = 0; // must nullify this on m_Ulines, m_Vlines change
|
|
PointVertex* m_selectedV = 0;
|
|
int m_selectedPatchIndex = -1;
|
|
bool m_isSelected = false;
|
|
|
|
class UVSelector : public Selector {
|
|
SelectionIntersection m_bestIntersection;
|
|
public:
|
|
EUVSelection m_selection = eNone;
|
|
int m_index = -1;
|
|
UVSelector() : m_bestIntersection( SelectionIntersection() ) {
|
|
}
|
|
void pushSelectable( Selectable& selectable ) override {
|
|
}
|
|
void popSelectable() override {
|
|
m_bestIntersection = SelectionIntersection();
|
|
}
|
|
void addIntersection( const SelectionIntersection& intersection ) override {
|
|
if( SelectionIntersection_closer( intersection, m_bestIntersection ) ) {
|
|
m_bestIntersection = intersection;
|
|
}
|
|
}
|
|
void addIntersection( const SelectionIntersection& intersection, EUVSelection selection, int index ) {
|
|
if( SelectionIntersection_closer( intersection, m_bestIntersection ) ) {
|
|
m_bestIntersection = intersection;
|
|
m_selection = selection;
|
|
m_index = index;
|
|
}
|
|
}
|
|
void addIntersection( const SelectionIntersection& intersection, EUVSelection selection ) {
|
|
if( SelectionIntersection_closer( intersection, m_bestIntersection ) ) {
|
|
m_bestIntersection = intersection;
|
|
m_selection = selection;
|
|
}
|
|
}
|
|
bool isSelected() {
|
|
return m_bestIntersection.valid();
|
|
}
|
|
};
|
|
|
|
Face* m_face = 0;
|
|
Plane3 m_plane;
|
|
std::size_t m_width, m_height;
|
|
TextureProjection m_projection;
|
|
|
|
Matrix4 m_local2tex; //real projection
|
|
Matrix4 m_tex2local; //real unprojection aka projection space basis aka texture axes
|
|
Matrix4 m_faceLocal2tex; //x,y projected to the face for z = const
|
|
Matrix4 m_faceTex2local;
|
|
Vector3 m_origin;
|
|
|
|
RenderablePivot m_pivot;
|
|
Matrix4 m_pivot2world0; // original
|
|
Matrix4 m_pivot2world; // transformed during transformation
|
|
RenderablePoint m_pivotPoint;
|
|
RenderableLines m_pivotLines;
|
|
Matrix4 m_pivotLines2world;
|
|
/* lines in uv space */
|
|
RenderableLines m_Ulines;
|
|
RenderableLines m_Vlines;
|
|
Matrix4 m_lines2world; // line * ( transform during transformation ) * m_faceTex2local = world
|
|
|
|
unsigned int m_gridU = 1; // n - 1 of U directed sub lines, 1-16
|
|
unsigned int m_gridV = 1;
|
|
RenderablePoint m_gridPointU; // control of U grid lines density, rendered on V axis
|
|
RenderablePoint m_gridPointV;
|
|
Vector2 m_gridSign; // orientation of controls relative to origin
|
|
|
|
RenderableCircle m_circle;
|
|
Matrix4 m_circle2world;
|
|
|
|
Patch* m_patch = 0; //tracking face/patch mode by only nonzero pointer
|
|
std::size_t m_patchWidth;
|
|
std::size_t m_patchHeight;
|
|
PatchControlArray m_patchCtrl;
|
|
RenderablePoints m_patchRenderPoints;
|
|
RenderableLines m_patchRenderLattice;
|
|
RenderablePatchTexture m_patchRenderTex;
|
|
const Shader* m_state_patch_raw = 0; // original patch texture shader
|
|
Shader* m_state_patch = 0; // local patch texture overlay
|
|
const char* m_state_patch_name = "$uvtool/patchtexture";
|
|
|
|
public:
|
|
UVManipulatorImpl() : m_pivot( 32 ), m_circle( 8 << 3 ) {
|
|
draw_circle( 8, 1, m_circle.m_vertices.data(), RemapXYZ() );
|
|
m_circle.setColour( m_cGray );
|
|
m_pivotPoint.setColour( m_cWhite );
|
|
m_gridPointU.setColour( m_cWhite );
|
|
m_gridPointV.setColour( m_cWhite );
|
|
m_pivotLines.m_lines.resize( 4, PointVertex( vertex3f_identity, m_cWhite ) );
|
|
}
|
|
~UVManipulatorImpl() {
|
|
patchShaderDestroy();
|
|
}
|
|
|
|
private:
|
|
void patchShaderConstruct(){
|
|
patchShaderDestroy();
|
|
|
|
OpenGLState state;
|
|
GlobalOpenGLStateLibrary().getDefaultState( state );
|
|
state.m_state = RENDER_FILL /*| RENDER_CULLFACE*/ | RENDER_TEXTURE | RENDER_COLOURWRITE | RENDER_LIGHTING | RENDER_SMOOTH;
|
|
state.m_sort = OpenGLState::eSortOverlayLast;
|
|
state.m_texture = m_patch->getShader()->getTexture().texture_number;
|
|
|
|
GlobalOpenGLStateLibrary().insert( m_state_patch_name, state );
|
|
m_state_patch = GlobalShaderCache().capture( m_state_patch_name );
|
|
}
|
|
|
|
void patchShaderDestroy(){
|
|
if( m_state_patch ){
|
|
m_state_patch = 0;
|
|
GlobalShaderCache().release( m_state_patch_name );
|
|
GlobalOpenGLStateLibrary().erase( m_state_patch_name );
|
|
}
|
|
}
|
|
bool patchCtrl_isInside( std::size_t i ) const {
|
|
return ( i % 2 || ( i / m_patchWidth ) % 2 );
|
|
}
|
|
template<typename Functor>
|
|
void forEachEdge( const Functor& functor ) const {
|
|
if( m_face ){
|
|
const Winding& winding = m_face->getWinding();
|
|
for( Winding::const_iterator next = winding.begin(), i = winding.end() - 1; next != winding.end(); i = next, ++next )
|
|
functor( ( *i ).vertex, ( *next ).vertex );
|
|
}
|
|
else if( m_patch ){
|
|
for( std::vector<PointVertex>::const_iterator i = m_patchRenderLattice.m_lines.begin(); i != m_patchRenderLattice.m_lines.end(); ++++i ){
|
|
const Vector3 p0( matrix4_transformed_point( m_faceTex2local, ( *i ).vertex ) );
|
|
const Vector3 p1( matrix4_transformed_point( m_faceTex2local, ( *( i + 1 ) ).vertex ) );
|
|
if( vector3_length_squared( p1 - p0 ) > 0.1 )
|
|
functor( p0, p1 );
|
|
}
|
|
}
|
|
}
|
|
template<typename Functor>
|
|
void forEachPoint( const Functor& functor ) const {
|
|
if( m_face ){
|
|
const Winding& winding = m_face->getWinding();
|
|
for( const auto& v : winding )
|
|
functor( v.vertex );
|
|
}
|
|
else if( m_patch ){
|
|
for( const auto& v : m_patchCtrl )
|
|
functor( matrix4_transformed_point( m_faceTex2local, Vector3( v.m_texcoord, 0 ) ) );
|
|
}
|
|
}
|
|
template<typename Functor>
|
|
void forEachUVPoint( const Functor& functor ) const {
|
|
if( m_face ){
|
|
const Winding& winding = m_face->getWinding();
|
|
for( const auto& v : winding )
|
|
functor( matrix4_transformed_point( m_faceLocal2tex, v.vertex ) );
|
|
}
|
|
else if( m_patch ){
|
|
for( const auto& v : m_patchCtrl )
|
|
functor( Vector3( v.m_texcoord, 0 ) );
|
|
}
|
|
}
|
|
bool projection_valid() const {
|
|
return !( !std::isfinite( m_local2tex[0] ) //nan
|
|
|| !std::isfinite( m_tex2local[0] ) //nan
|
|
|| std::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
|
|
|| vector3_length_squared( m_tex2local.x().vec3() ) > 1e9 //very upscaled or product of nearly nan
|
|
|| vector3_length_squared( m_tex2local.y().vec3() ) > 1e9 );
|
|
}
|
|
void UpdateFaceData( bool updateOrigin, bool updateLines = true ) {
|
|
//!? todo fewer outer quads for large textures
|
|
//!? todo auto subdivisions num, based on tex size and world scale
|
|
//! todo update on undo/redo, when face stays the same, but transformed
|
|
//! todo update on nudgeSelectedLeft and the rest, qe tool move w/o projection change or with tex lock off
|
|
//+ todo put default origin to winding's UV aabb corner
|
|
//+ todo disable 3d workzone in this manipulator mode
|
|
if( m_face ){
|
|
m_plane = m_face->getPlane().plane3();
|
|
m_width = m_face->getShader().width();
|
|
m_height = m_face->getShader().height();
|
|
// m_face->GetTexdef( m_projection );
|
|
m_projection = m_face->getTexdef().m_projection;
|
|
|
|
Texdef_Construct_local2tex( m_projection, m_width, m_height, m_plane.normal(), m_local2tex );
|
|
m_tex2local = matrix4_affine_inverse( m_local2tex );
|
|
}
|
|
else if( m_patch ){
|
|
m_plane.normal() = m_patch->Calculate_AvgNormal();
|
|
m_plane.dist() = vector3_dot( m_plane.normal(), m_patch->localAABB().origin );
|
|
m_patchWidth = m_patch->getWidth();
|
|
m_patchHeight = m_patch->getHeight();
|
|
m_patchCtrl = m_patch->getControlPoints();
|
|
m_state_patch_raw = m_patch->getShader();
|
|
patchShaderConstruct();
|
|
{ //! todo force or deduce orthogonal uv axes for convenience
|
|
Vector3 wDir, hDir;
|
|
m_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 < m_patchHeight; ++r ){
|
|
float length = 0;
|
|
for ( std::size_t c = 0; c < m_patchWidth - 1; ++c ){
|
|
length += vector3_length( m_patch->ctrlAt( r, c + 1 ).m_vertex - m_patch->ctrlAt( r, c ).m_vertex );
|
|
}
|
|
if( length - wLength > .1f || ( ( r == 0 || r == m_patchHeight - 1 ) && float_equal_epsilon( length, wLength, .1f ) ) ){ // prioritize first and last rows
|
|
wLength = length;
|
|
row = r;
|
|
}
|
|
}
|
|
for ( std::size_t c = 0; c < m_patchWidth; ++c ){
|
|
float length = 0;
|
|
for ( std::size_t r = 0; r < m_patchHeight - 1; ++r ){
|
|
length += vector3_length( m_patch->ctrlAt( r + 1, c ).m_vertex - m_patch->ctrlAt( r, c ).m_vertex );
|
|
}
|
|
if( length - hLength > .1f || ( ( c == 0 || c == m_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( m_patch->ctrlAt( row, c + 1 ).m_vertex - m_patch->ctrlAt( row, c ).m_vertex );
|
|
}
|
|
for ( std::size_t c = col; c < m_patchWidth - 1; ++c ){
|
|
distW1 += vector3_length( m_patch->ctrlAt( row, c + 1 ).m_vertex - m_patch->ctrlAt( row, c ).m_vertex );
|
|
}
|
|
float distH0 = 0, distH1 = 0;
|
|
for ( std::size_t r = 0; r < row; ++r ){
|
|
distH0 += vector3_length( m_patch->ctrlAt( r + 1, col ).m_vertex - m_patch->ctrlAt( r, col ).m_vertex );
|
|
}
|
|
for ( std::size_t r = row; r < m_patchHeight - 1; ++r ){
|
|
distH1 += vector3_length( m_patch->ctrlAt( r + 1, col ).m_vertex - m_patch->ctrlAt( r, col ).m_vertex );
|
|
}
|
|
|
|
if( ( distW0 > distH0 && distW0 > distH1 ) || ( distW1 > distH0 && distW1 > distH1 ) ){
|
|
p0 = &m_patch->ctrlAt( 0, col );
|
|
p1 = &m_patch->ctrlAt( m_patchHeight - 1, col );
|
|
p2 = distW0 > distW1? &m_patch->ctrlAt( row, 0 ) : &m_patch->ctrlAt( row, m_patchWidth - 1 );
|
|
v0 = m_patch->localAABB().origin
|
|
+ hDir * vector3_dot( m_patch->localAABB().extents, Vector3( std::fabs( hDir.x() ), std::fabs( hDir.y() ), std::fabs( hDir.z() ) ) ) * 1.1
|
|
+ wDir * ( distW0 - wLength / 2 );
|
|
v1 = v0 + hDir * hLength;
|
|
v2 = v0 + hDir * distH0 + ( distW0 > distW1? ( wDir * -distW0 ) : ( wDir * distW1 ) );
|
|
}
|
|
else{
|
|
p0 = &m_patch->ctrlAt( row, 0 );
|
|
p1 = &m_patch->ctrlAt( row, m_patchWidth - 1 );
|
|
p2 = distH0 > distH1? &m_patch->ctrlAt( 0, col ) : &m_patch->ctrlAt( m_patchHeight - 1, col );
|
|
v0 = m_patch->localAABB().origin
|
|
+ wDir * vector3_dot( m_patch->localAABB().extents, Vector3( std::fabs( wDir.x() ), std::fabs( wDir.y() ), std::fabs( wDir.z() ) ) ) * 1.1
|
|
+ hDir * ( distH0 - hLength / 2 );
|
|
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 PlanePoints vertices{ v0, v1, v2 };
|
|
const DoubleVector3 sts[3]{ DoubleVector3( p0->m_texcoord, 0 ),
|
|
DoubleVector3( p1->m_texcoord, 0 ),
|
|
DoubleVector3( p2->m_texcoord, 0 ) };
|
|
Texdef_Construct_local2tex_from_ST( vertices, sts, m_local2tex );
|
|
m_tex2local = matrix4_affine_inverse( m_local2tex );
|
|
}
|
|
}
|
|
|
|
// globalOutputStream() << m_local2tex << " m_local2tex\n";
|
|
// globalOutputStream() << m_tex2local << " m_tex2local\n";
|
|
/* error checking */
|
|
if( !projection_valid() ){
|
|
m_selectedU = m_selectedV = 0;
|
|
m_Ulines.m_lines.clear();
|
|
m_Vlines.m_lines.clear();
|
|
m_selectedPatchIndex = -1;
|
|
return;
|
|
}
|
|
|
|
m_faceTex2local = m_tex2local;
|
|
m_faceTex2local.x().vec3() = plane3_project_point( Plane3( m_plane.normal(), 0 ), m_tex2local.x().vec3(), m_tex2local.z().vec3() );
|
|
m_faceTex2local.y().vec3() = plane3_project_point( Plane3( m_plane.normal(), 0 ), m_tex2local.y().vec3(), m_tex2local.z().vec3() );
|
|
m_faceTex2local = matrix4_multiplied_by_matrix4( // adjust to have UV's z = 0: move the plane along m_tex2local.z() so that plane.dist() = 0
|
|
matrix4_translation_for_vec3(
|
|
m_tex2local.z().vec3() * ( m_plane.dist() - vector3_dot( m_plane.normal(), m_tex2local.t().vec3() ) )
|
|
/ vector3_dot( m_plane.normal(), m_tex2local.z().vec3() )
|
|
),
|
|
m_faceTex2local );
|
|
m_faceLocal2tex = matrix4_affine_inverse( m_faceTex2local );
|
|
|
|
if( m_patch ){
|
|
m_patchRenderPoints.m_points.clear();
|
|
m_patchRenderPoints.m_points.reserve( m_patchWidth * m_patchHeight );
|
|
for( std::size_t i = 0; i < m_patchCtrl.size(); ++i ){
|
|
m_patchRenderPoints.m_points.emplace_back( vertex3f_for_vector3( Vector3( m_patchCtrl[i].m_texcoord, 0 ) ), patchCtrl_isInside( i )? m_cPin : m_cGree );
|
|
}
|
|
|
|
m_patchRenderLattice.m_lines.clear();
|
|
m_patchRenderLattice.m_lines.reserve( ( ( m_patchWidth - 1 ) * m_patchHeight + ( m_patchHeight - 1 ) * m_patchWidth ) * 2 );
|
|
for ( std::size_t r = 0; r < m_patchHeight; ++r ){
|
|
for ( std::size_t c = 0; c < m_patchWidth - 1; ++c ){
|
|
const Vector2& a = m_patch->ctrlAt( r, c ).m_texcoord;
|
|
const Vector2& b = m_patch->ctrlAt( r, c + 1 ).m_texcoord;
|
|
m_patchRenderLattice.m_lines.emplace_back( vertex3f_for_vector3( Vector3( a, 0 ) ), m_cOrang );
|
|
m_patchRenderLattice.m_lines.emplace_back( vertex3f_for_vector3( Vector3( b, 0 ) ), m_cOrang );
|
|
}
|
|
}
|
|
for ( std::size_t c = 0; c < m_patchWidth; ++c ){
|
|
for ( std::size_t r = 0; r < m_patchHeight - 1; ++r ){
|
|
const Vector2& a = m_patch->ctrlAt( r, c ).m_texcoord;
|
|
const Vector2& b = m_patch->ctrlAt( r + 1, c ).m_texcoord;
|
|
m_patchRenderLattice.m_lines.emplace_back( vertex3f_for_vector3( Vector3( a, 0 ) ), m_cOrang );
|
|
m_patchRenderLattice.m_lines.emplace_back( vertex3f_for_vector3( Vector3( b, 0 ) ), m_cOrang );
|
|
}
|
|
}
|
|
|
|
m_patchRenderTex.m_trianglesIndices.clear();
|
|
m_patchRenderTex.m_trianglesIndices.reserve( ( m_patchHeight - 1 ) * ( m_patchWidth - 1 ) * 2 * 3 );
|
|
const PatchControlArray& pc = m_patch->getControlPointsTransformed();
|
|
m_patchRenderTex.m_patchControlArray = &pc;
|
|
const double degenerate_epsilon = 1e-5;
|
|
for ( std::size_t r = 0; r < m_patchHeight - 1; ++r ){
|
|
for ( std::size_t c = 0; c < m_patchWidth - 1; ++c ){
|
|
const RenderIndex i0 = m_patchWidth * r + c;
|
|
const RenderIndex i1 = m_patchWidth * ( r + 1 ) + c;
|
|
const RenderIndex i2 = m_patchWidth * ( r + 1 ) + c + 1;
|
|
const RenderIndex i3 = m_patchWidth * r + c + 1;
|
|
double cross = vector2_cross( pc[i2].m_texcoord - pc[i0].m_texcoord, pc[i1].m_texcoord - pc[i0].m_texcoord );
|
|
if( !float_equal_epsilon( cross, 0, degenerate_epsilon ) ){
|
|
m_patchRenderTex.m_trianglesIndices.push_back( i0 );
|
|
m_patchRenderTex.m_trianglesIndices.push_back( i1 );
|
|
m_patchRenderTex.m_trianglesIndices.push_back( i2 );
|
|
if( cross < 0 )
|
|
std::swap( *( m_patchRenderTex.m_trianglesIndices.end() - 1 ), *( m_patchRenderTex.m_trianglesIndices.end() - 2 ) );
|
|
}
|
|
cross = vector2_cross( pc[i3].m_texcoord - pc[i0].m_texcoord, pc[i2].m_texcoord - pc[i0].m_texcoord );
|
|
if( !float_equal_epsilon( cross, 0, degenerate_epsilon ) ){
|
|
m_patchRenderTex.m_trianglesIndices.push_back( i0 );
|
|
m_patchRenderTex.m_trianglesIndices.push_back( i2 );
|
|
m_patchRenderTex.m_trianglesIndices.push_back( i3 );
|
|
if( cross < 0 )
|
|
std::swap( *( m_patchRenderTex.m_trianglesIndices.end() - 1 ), *( m_patchRenderTex.m_trianglesIndices.end() - 2 ) );
|
|
}
|
|
}
|
|
}
|
|
if( m_patchRenderTex.m_trianglesIndices.size() == 0 ){ // try to make at least one triangle or more
|
|
RenderIndex i0 = 0, i1 = 1, i2;
|
|
for( ; i1 < pc.size(); ++i1 ){
|
|
if( vector2_length( pc[i1].m_texcoord - pc[i0].m_texcoord ) > degenerate_epsilon ){
|
|
i2 = i1 + 1;
|
|
for( ; i2 < pc.size(); ++i2 ){
|
|
const double cross = vector2_cross( pc[i2].m_texcoord - pc[i0].m_texcoord, pc[i1].m_texcoord - pc[i0].m_texcoord );
|
|
if( !float_equal_epsilon( cross, 0, degenerate_epsilon ) ){
|
|
m_patchRenderTex.m_trianglesIndices.push_back( i0 );
|
|
m_patchRenderTex.m_trianglesIndices.push_back( i1 );
|
|
m_patchRenderTex.m_trianglesIndices.push_back( i2 );
|
|
if( cross < 0 )
|
|
std::swap( *( m_patchRenderTex.m_trianglesIndices.end() - 1 ), *( m_patchRenderTex.m_trianglesIndices.end() - 2 ) );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector2 min( FLT_MAX, FLT_MAX );
|
|
Vector2 max( -FLT_MAX, -FLT_MAX );
|
|
forEachUVPoint( [&]( const Vector3& point ){
|
|
min.x() = std::min( min.x(), point.x() );
|
|
max.x() = std::max( max.x(), point.x() );
|
|
min.y() = std::min( min.y(), point.y() );
|
|
max.y() = std::max( max.y(), point.y() );
|
|
} );
|
|
|
|
if( updateOrigin )
|
|
m_origin = matrix4_transformed_point( m_faceTex2local, Vector3( min, 0 ) );
|
|
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_faceLocal2tex, m_origin );
|
|
|
|
{ // grid grain controls, on the polygon side of origin
|
|
m_gridSign.x() = max.y() - uv_origin.y() >= uv_origin.y() - min.y()? 1 : -1;
|
|
m_gridSign.y() = max.x() - uv_origin.x() >= uv_origin.x() - min.x()? 1 : -1;
|
|
m_gridPointU.m_point.vertex = Vertex3f( uv_origin.x(),
|
|
float_to_integer( uv_origin.y() + m_gridSign.x() * .25 ) + m_gridSign.x() * ( 1 - 1.0 / std::max( float( m_gridU ), 1.8f ) ),
|
|
0 );
|
|
m_gridPointV.m_point.vertex = Vertex3f( float_to_integer( uv_origin.x() + m_gridSign.y() * .25 ) + m_gridSign.y() * ( 1 - 1.0 / std::max( float( m_gridV ), 1.8f ) ),
|
|
uv_origin.y(),
|
|
0 );
|
|
}
|
|
|
|
m_pivot2world = m_tex2local;
|
|
vector3_normalise( m_pivot2world.x().vec3() );
|
|
vector3_normalise( m_pivot2world.y().vec3() );
|
|
m_pivot2world.t().vec3() = m_origin;
|
|
m_pivot2world0 = m_pivot2world;
|
|
|
|
{
|
|
float bestDist = 0;
|
|
forEachPoint( [&]( const Vector3& point ){
|
|
const float dist = vector3_length_squared( point - m_origin );
|
|
if( dist > bestDist ){
|
|
bestDist = dist;
|
|
}
|
|
} );
|
|
bestDist = sqrt( bestDist );
|
|
m_circle2world = g_matrix4_identity;
|
|
ComputeAxisBase( m_plane.normal(), m_circle2world.x().vec3(), m_circle2world.y().vec3() );
|
|
m_circle2world.x().vec3() *= bestDist;
|
|
m_circle2world.y().vec3() *= bestDist;
|
|
m_circle2world.z().vec3() = m_plane.normal();
|
|
m_circle2world.t().vec3() = m_origin;
|
|
}
|
|
|
|
min -= Vector2( 5, 5 );
|
|
max += Vector2( 5, 5 );
|
|
min.x() = float_to_integer( min.x() );
|
|
min.y() = float_to_integer( min.y() );
|
|
max.x() = float_to_integer( max.x() );
|
|
max.y() = float_to_integer( max.y() );
|
|
|
|
m_selectedU = m_selectedV = 0;
|
|
m_selectedPatchIndex = -1;
|
|
m_lines2world = m_faceTex2local;
|
|
m_pivotLines2world = m_faceTex2local;
|
|
if( updateLines ){
|
|
const int imax = float_to_integer( max.y() - min.y() ) + 1;
|
|
m_Ulines.m_lines.clear();
|
|
m_Ulines.m_lines.reserve( ( imax + ( m_gridU - 1 ) * ( imax - 1 ) ) * 2 );
|
|
for( int i = 0; i < imax; ++i ){
|
|
if( i != 0 ){
|
|
for( std::size_t j = m_gridU - 1; j != 0; --j ){ //subgrid lines
|
|
m_Ulines.m_lines.emplace_back( Vertex3f( min.x(), min.y() + i - static_cast<float>( j ) / m_gridU, 0 ), m_cGrayer );
|
|
m_Ulines.m_lines.emplace_back( Vertex3f( max.x(), min.y() + i - static_cast<float>( j ) / m_gridU, 0 ), m_cGrayer );
|
|
}
|
|
}
|
|
m_Ulines.m_lines.emplace_back( Vertex3f( min.x(), min.y() + i, 0 ), m_cGray );
|
|
m_Ulines.m_lines.emplace_back( Vertex3f( max.x(), min.y() + i, 0 ), m_cGray );
|
|
}
|
|
}
|
|
if( updateLines ){
|
|
const int imax = float_to_integer( max.x() - min.x() ) + 1;
|
|
m_Vlines.m_lines.clear();
|
|
m_Vlines.m_lines.reserve( ( imax + ( m_gridV - 1 ) * ( imax - 1 ) ) * 2 );
|
|
for( int i = 0; i < imax; ++i ){
|
|
if( i != 0 ){
|
|
for( std::size_t j = m_gridV - 1; j != 0; --j ){
|
|
m_Vlines.m_lines.emplace_back( Vertex3f( min.x() + i - static_cast<float>( j ) / m_gridV, min.y(), 0 ), m_cGrayer );
|
|
m_Vlines.m_lines.emplace_back( Vertex3f( min.x() + i - static_cast<float>( j ) / m_gridV, max.y(), 0 ), m_cGrayer );
|
|
}
|
|
}
|
|
m_Vlines.m_lines.emplace_back( Vertex3f( min.x() + i, min.y(), 0 ), m_cGray );
|
|
m_Vlines.m_lines.emplace_back( Vertex3f( min.x() + i, max.y(), 0 ), m_cGray );
|
|
}
|
|
}
|
|
{
|
|
{ // u pivot line
|
|
m_pivotLines.m_lines[0].vertex = Vertex3f( min.x(), uv_origin.y(), 0 );
|
|
m_pivotLines.m_lines[1].vertex = Vertex3f( max.x(), uv_origin.y(), 0 );
|
|
}
|
|
{ // v pivot line
|
|
m_pivotLines.m_lines[2].vertex = Vertex3f( uv_origin.x(), min.y(), 0 );
|
|
m_pivotLines.m_lines[3].vertex = Vertex3f( uv_origin.x(), max.y(), 0 );
|
|
}
|
|
}
|
|
}
|
|
bool UpdateData() {
|
|
if( !g_SelectedFaceInstances.empty() ){
|
|
Face* face = &g_SelectedFaceInstances.last().getFace();
|
|
if( m_face != face ){
|
|
m_face = face;
|
|
m_patch = 0;
|
|
UpdateFaceData( true );
|
|
}
|
|
else if( memcmp( &m_projection, &m_face->getTexdef().m_projection, sizeof( TextureProjection ) ) != 0
|
|
|| m_width != m_face->getShader().width()
|
|
|| m_height != m_face->getShader().height() ) {
|
|
UpdateFaceData( !projection_valid() ); // updateOrigin when prev state was invalid on the same face
|
|
}
|
|
return projection_valid();
|
|
}
|
|
else if( GlobalSelectionSystem().countSelected() != 0 ){
|
|
Patch* patch = Node_getPatch( GlobalSelectionSystem().ultimateSelected().path().top() );
|
|
if( patch ){
|
|
if( m_patch != patch ){
|
|
m_patch = patch;
|
|
m_face = 0;
|
|
UpdateFaceData( true );
|
|
}
|
|
else if( m_patchWidth != m_patch->getWidth()
|
|
|| m_patchHeight != m_patch->getHeight()
|
|
|| memcmp( m_patchCtrl.data(), m_patch->getControlPoints().data(), sizeof( *m_patchCtrl.data() ) * m_patchCtrl.size() ) != 0
|
|
|| m_state_patch_raw != m_patch->getShader() ){
|
|
UpdateFaceData( !projection_valid() ); // updateOrigin when prev state was invalid on the same patch
|
|
}
|
|
return projection_valid();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
public:
|
|
void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world ) override {
|
|
if( volume.fill() && UpdateData() ){
|
|
if( m_patch ){
|
|
renderer.SetState( const_cast<Shader*>( m_state_patch ), Renderer::eFullMaterials );
|
|
renderer.addRenderable( m_patchRenderTex, m_lines2world );
|
|
}
|
|
renderer.SetState( m_state_line, Renderer::eFullMaterials );
|
|
renderer.addRenderable( m_Ulines, m_lines2world );
|
|
renderer.addRenderable( m_Vlines, m_lines2world );
|
|
renderer.addRenderable( m_pivotLines, m_pivotLines2world );
|
|
if( m_patch )
|
|
renderer.addRenderable( m_patchRenderLattice, m_faceTex2local );
|
|
|
|
//fix pivot position for better visibility
|
|
m_pivot.render( renderer, volume, matrix4_multiplied_by_matrix4( matrix4_translation_for_vec3( vector3_normalised( volume.getViewer() - m_origin ) ), m_pivot2world ) );
|
|
|
|
renderer.addRenderable( m_circle, m_circle2world );
|
|
|
|
renderer.SetState( m_state_point, Renderer::eFullMaterials );
|
|
if( m_patch )
|
|
renderer.addRenderable( m_patchRenderPoints, m_faceTex2local );
|
|
renderer.addRenderable( m_pivotPoint, m_pivot2world );
|
|
renderer.addRenderable( m_gridPointU, m_pivotLines2world );
|
|
renderer.addRenderable( m_gridPointV, m_pivotLines2world );
|
|
}
|
|
}
|
|
void testSelect( const View& view, const Matrix4& pivot2world ) override {
|
|
//!? todo fix: eUV selection possibility may be blocked by the circle
|
|
if( !view.fill() || !UpdateData() ){
|
|
m_isSelected = false;
|
|
return;
|
|
}
|
|
|
|
UVSelector selector;
|
|
|
|
if( g_modifiers == c_modifierAlt ) // only try skew with alt // note also grabs eTex
|
|
goto testSelectUVlines;
|
|
if( g_modifiers != c_modifierNone )
|
|
return applySelection( selector.m_selection, nullptr, nullptr, selector.m_index );
|
|
|
|
{ // try pivot point
|
|
const Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_pivot2world ) );
|
|
SelectionIntersection best;
|
|
Point_BestPoint( local2view, m_pivotPoint.m_point.vertex, best );
|
|
selector.addIntersection( best, ePivot );
|
|
}
|
|
|
|
if( !selector.isSelected() ){ // try grid control points
|
|
const Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_faceTex2local ) );
|
|
SelectionIntersection best;
|
|
Point_BestPoint( local2view, m_gridPointU.m_point.vertex, best );
|
|
selector.addIntersection( best, eGridU );
|
|
Point_BestPoint( local2view, m_gridPointV.m_point.vertex, best );
|
|
selector.addIntersection( best, eGridV );
|
|
}
|
|
|
|
if( !selector.isSelected() && m_patch ){ // try patch points
|
|
const Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_faceTex2local ) );
|
|
SelectionIntersection best;
|
|
for( std::size_t i = 0; i < m_patchRenderPoints.m_points.size(); ++i ){
|
|
Point_BestPoint( local2view, m_patchRenderPoints.m_points[i], best );
|
|
selector.addIntersection( best, ePatchPoint, i );
|
|
}
|
|
}
|
|
if( !selector.isSelected() && m_patch ){ // try patch rows, columns
|
|
const Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_faceTex2local ) );
|
|
SelectionIntersection best;
|
|
for ( std::size_t r = 0; r < m_patchHeight; ++r ){
|
|
for ( std::size_t c = 0; c < m_patchWidth - 1; ++c ){
|
|
Line_BestPoint( local2view, &m_patchRenderLattice.m_lines[( r * ( m_patchWidth - 1 ) + c ) * 2], best );
|
|
selector.addIntersection( best, ePatchRow, r );
|
|
}
|
|
}
|
|
for ( std::size_t c = 0; c < m_patchWidth; ++c ){
|
|
for ( std::size_t r = 0; r < m_patchHeight - 1; ++r ){
|
|
Line_BestPoint( local2view, &m_patchRenderLattice.m_lines[( m_patchWidth - 1 ) * m_patchHeight * 2 + ( c * ( m_patchHeight - 1 ) + r ) * 2], best );
|
|
selector.addIntersection( best, ePatchColumn, c );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !selector.isSelected() ){ // try circle
|
|
const Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_circle2world ) );
|
|
SelectionIntersection best;
|
|
LineLoop_BestPoint( local2view, m_circle.m_vertices.data(), m_circle.m_vertices.size(), best );
|
|
selector.addIntersection( best, eCircle );
|
|
}
|
|
|
|
if( !selector.isSelected() ){ // try pivot lines
|
|
const Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_faceTex2local ) );
|
|
SelectionIntersection best;
|
|
Line_BestPoint( local2view, &m_pivotLines.m_lines[0], best );
|
|
selector.addIntersection( best, ePivotU );
|
|
Line_BestPoint( local2view, &m_pivotLines.m_lines[2], best );
|
|
selector.addIntersection( best, ePivotV );
|
|
}
|
|
testSelectUVlines:
|
|
PointVertex* selectedU = 0;
|
|
PointVertex* selectedV = 0;
|
|
EUVSelection& selection = selector.m_selection;
|
|
if( !selector.isSelected() ){ // try UV lines
|
|
/*
|
|
-|------
|
|
|
|
|
|
|
|
V line center| - - tex U center - -
|
|
| tex
|
|
| V
|
|
-cross-|-----U line center-----|
|
|
|
|
|
*/
|
|
// special fuckage with the grid for better distinguishing of user's intentions
|
|
// better picking of tex, only line for skew or scale with dense grid
|
|
const Matrix4 screen2world( matrix4_full_inverse( view.GetViewMatrix() ) );
|
|
const DoubleRay ray = ray_for_points( vector4_projected( matrix4_transformed_vector4( screen2world, BasicVector4<double>( 0, 0, -1, 1 ) ) ),
|
|
vector4_projected( matrix4_transformed_vector4( screen2world, BasicVector4<double>( 0, 0, 1, 1 ) ) ) );
|
|
const DoubleVector3 hit = ray_intersect_plane( ray, m_plane );
|
|
const Vector3 uvhit = matrix4_transformed_point( m_faceLocal2tex, hit );
|
|
if( std::fabs( vector3_dot( ray.direction, m_plane.normal() ) ) > 1e-6
|
|
&& !m_Ulines.m_lines.empty()
|
|
&& !m_Vlines.m_lines.empty()
|
|
&& matrix4_transformed_vector4( view.GetViewMatrix(), Vector4( hit, 1 ) ).w() > 0 ){
|
|
PointVertex* closestU = &m_Ulines.m_lines[std::min( m_Ulines.m_lines.size() - 2,
|
|
static_cast<std::size_t>( float_to_integer( std::max( 0.f, uvhit.y() - m_Ulines.m_lines.front().vertex.y() ) * m_gridU ) * 2 ) )];
|
|
PointVertex* closestV = &m_Vlines.m_lines[std::min( m_Vlines.m_lines.size() - 2,
|
|
static_cast<std::size_t>( float_to_integer( std::max( 0.f, uvhit.x() - m_Vlines.m_lines.front().vertex.x() ) * m_gridV ) * 2 ) )];
|
|
const Vector2 sign( uvhit.y() > closestU->vertex.y()? 1 : -1, uvhit.x() > closestV->vertex.x()? 1 : -1 ); //hit in positive or negative part of lines u, v
|
|
const PointVertex pCross( Vertex3f( closestV->vertex.x(), closestU->vertex.y(), 0 ) );
|
|
const PointVertex pUcenter( Vertex3f( closestV->vertex.x() + sign.y() / ( m_gridV * 2 ), closestU->vertex.y(), 0 ) );
|
|
const PointVertex pVcenter( Vertex3f( closestV->vertex.x(), closestU->vertex.y() + sign.x() / ( m_gridU * 2 ), 0 ) );
|
|
|
|
PointVertex pTexUcenter[2]{ *closestU, *( closestU + 1 ) };
|
|
pTexUcenter[0].vertex.y() = pTexUcenter[1].vertex.y() = pVcenter.vertex.y();
|
|
PointVertex pTexVcenter[2]{ *closestV, *( closestV + 1 ) };
|
|
pTexVcenter[0].vertex.x() = pTexVcenter[1].vertex.x() = pUcenter.vertex.x();
|
|
|
|
SelectionIntersection iCross, iUcenter, iVcenter, iTexUcenter, iTexVcenter, iU, iV, iNull;
|
|
|
|
const Matrix4 local2view( matrix4_multiplied_by_matrix4( view.GetViewMatrix(), m_faceTex2local ) );
|
|
#if defined( DEBUG_SELECTION )
|
|
g_render_clipped.construct( view.GetViewMatrix() );
|
|
#endif
|
|
Line_BestPoint( local2view, closestU, iU );
|
|
Line_BestPoint( local2view, closestV, iV );
|
|
Line_BestPoint( local2view, pTexUcenter, iTexUcenter );
|
|
Line_BestPoint( local2view, pTexVcenter, iTexVcenter );
|
|
const bool uselected = iU < iNull;
|
|
const bool vselected = iV < iNull;
|
|
if( !uselected && !vselected ){ //no lines hit, definitely tex
|
|
selection = eTex;
|
|
}
|
|
else if( ( !uselected || iTexUcenter < iU ) && ( !vselected || iTexVcenter < iV ) ){ //yes lines, but tex ones are closer
|
|
selection = eTex;
|
|
}
|
|
else if( uselected != vselected ){ //only line selected
|
|
if( uselected ){
|
|
selection = g_modifiers == c_modifierAlt? eSkewU : eU;
|
|
selectedU = closestU;
|
|
}
|
|
else{
|
|
selection = g_modifiers == c_modifierAlt? eSkewV : eV;
|
|
selectedV = closestV;
|
|
}
|
|
}
|
|
else{ //two lines hit
|
|
if( g_modifiers == c_modifierAlt ){ //pick only line for skew
|
|
if( iU < iV ){
|
|
selection = eSkewU;
|
|
selectedU = closestU;
|
|
}
|
|
else{
|
|
selection = eSkewV;
|
|
selectedV = closestV;
|
|
}
|
|
}
|
|
else{
|
|
Point_BestPoint( local2view, pUcenter, iUcenter );
|
|
Point_BestPoint( local2view, pVcenter, iVcenter );
|
|
Point_BestPoint( local2view, pCross, iCross );
|
|
const bool ucenter = iUcenter < iNull;
|
|
const bool vcenter = iVcenter < iNull;
|
|
if( !ucenter && !vcenter ){ // no centers, definitely two lines
|
|
selection = eUV;
|
|
selectedU = closestU;
|
|
selectedV = closestV;
|
|
}
|
|
else if( iCross < iUcenter && iCross < iVcenter ){ // some center(s), cross is closer = two lines
|
|
selection = eUV;
|
|
selectedU = closestU;
|
|
selectedV = closestV;
|
|
}
|
|
else{ // some center(s), pick closest line
|
|
if( iUcenter < iVcenter ){
|
|
selection = eU;
|
|
selectedU = closestU;
|
|
}
|
|
else{
|
|
selection = eV;
|
|
selectedV = closestV;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
applySelection( selector.m_selection, selectedU, selectedV, selector.m_index );
|
|
}
|
|
private:
|
|
void applySelection( EUVSelection selection, PointVertex* selectedU, PointVertex* selectedV, int selectedPatchIndex ){
|
|
if( m_selection != selection
|
|
|| m_selectedU != selectedU
|
|
|| m_selectedV != selectedV
|
|
|| m_selectedPatchIndex != selectedPatchIndex ){
|
|
if( m_selection != selection ){
|
|
switch ( m_selection )
|
|
{
|
|
case ePivot:
|
|
m_pivotPoint.m_point.colour = m_cWhite;
|
|
break;
|
|
case eGridU:
|
|
m_gridPointU.m_point.colour = m_cWhite;
|
|
break;
|
|
case eGridV:
|
|
m_gridPointV.m_point.colour = m_cWhite;
|
|
break;
|
|
case eCircle:
|
|
m_circle.setColour( m_cGray );
|
|
break;
|
|
case ePivotU:
|
|
m_pivotLines.m_lines[0].colour = m_cWhite;
|
|
m_pivotLines.m_lines[1].colour = m_cWhite;
|
|
break;
|
|
case ePivotV:
|
|
m_pivotLines.m_lines[2].colour = m_cWhite;
|
|
m_pivotLines.m_lines[3].colour = m_cWhite;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
switch ( selection )
|
|
{
|
|
case ePivot:
|
|
m_pivotPoint.m_point.colour = m_cRed;
|
|
break;
|
|
case eGridU:
|
|
m_gridPointU.m_point.colour = m_cRed;
|
|
break;
|
|
case eGridV:
|
|
m_gridPointV.m_point.colour = m_cRed;
|
|
break;
|
|
case eCircle:
|
|
m_circle.setColour( g_colour_selected );
|
|
break;
|
|
case ePivotU:
|
|
m_pivotLines.m_lines[0].colour = m_cRed;
|
|
m_pivotLines.m_lines[1].colour = m_cRed;
|
|
break;
|
|
case ePivotV:
|
|
m_pivotLines.m_lines[2].colour = m_cRed;
|
|
m_pivotLines.m_lines[3].colour = m_cRed;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const Colour4b colour_selected = g_modifiers == c_modifierAlt? m_cGreen : g_colour_selected;
|
|
if( m_selectedU != selectedU || m_selection != selection ){ // selected line changed or not, but scale<->skew modes exchanged
|
|
if( m_selectedU )
|
|
m_selectedU->colour =
|
|
( m_selectedU + 1 )->colour = ( ( m_selectedU - &m_Ulines.m_lines[0] ) / 2 ) % m_gridU == 0? m_cGray : m_cGrayer;
|
|
if( selectedU )
|
|
selectedU->colour =
|
|
( selectedU + 1 )->colour = colour_selected;
|
|
}
|
|
if( m_selectedV != selectedV || m_selection != selection ){
|
|
if( m_selectedV )
|
|
m_selectedV->colour =
|
|
( m_selectedV + 1 )->colour = ( ( m_selectedV - &m_Vlines.m_lines[0] ) / 2 ) % m_gridV == 0? m_cGray : m_cGrayer;
|
|
if( selectedV )
|
|
selectedV->colour =
|
|
( selectedV + 1 )->colour = colour_selected;
|
|
}
|
|
|
|
if( m_selectedPatchIndex != selectedPatchIndex || m_selection != selection ){
|
|
if( m_selectedPatchIndex >= 0 ){
|
|
switch ( m_selection )
|
|
{
|
|
case ePatchPoint:
|
|
m_patchRenderPoints.m_points[m_selectedPatchIndex].colour = patchCtrl_isInside( m_selectedPatchIndex )? m_cPin : m_cGree;
|
|
break;
|
|
case ePatchRow:
|
|
for ( std::size_t c = 0; c < m_patchWidth - 1; ++c ){
|
|
const std::size_t i = ( m_selectedPatchIndex * ( m_patchWidth - 1 ) + c ) * 2;
|
|
m_patchRenderLattice.m_lines[i].colour =
|
|
m_patchRenderLattice.m_lines[i + 1].colour = m_cOrang;
|
|
}
|
|
for ( std::size_t c = 0; c < m_patchWidth; ++c ){
|
|
const std::size_t i = m_selectedPatchIndex * m_patchWidth + c;
|
|
m_patchRenderPoints.m_points[i].colour = patchCtrl_isInside( i )? m_cPin : m_cGree;
|
|
}
|
|
break;
|
|
case ePatchColumn:
|
|
for ( std::size_t r = 0; r < m_patchHeight - 1; ++r ){
|
|
const std::size_t i = ( m_patchWidth - 1 ) * m_patchHeight * 2 + ( m_selectedPatchIndex * ( m_patchHeight - 1 ) + r ) * 2;
|
|
m_patchRenderLattice.m_lines[i].colour =
|
|
m_patchRenderLattice.m_lines[i + 1].colour = m_cOrang;
|
|
}
|
|
for ( std::size_t r = 0; r < m_patchHeight; ++r ){
|
|
const std::size_t i = r * m_patchWidth + m_selectedPatchIndex;
|
|
m_patchRenderPoints.m_points[i].colour = patchCtrl_isInside( i )? m_cPin : m_cGree;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if( selectedPatchIndex >= 0 ){
|
|
switch ( selection )
|
|
{
|
|
case ePatchPoint:
|
|
m_patchRenderPoints.m_points[selectedPatchIndex].colour = patchCtrl_isInside( selectedPatchIndex )? m_cPink : m_cGreen;
|
|
break;
|
|
case ePatchRow:
|
|
for ( std::size_t c = 0; c < m_patchWidth - 1; ++c ){
|
|
const std::size_t i = ( selectedPatchIndex * ( m_patchWidth - 1 ) + c ) * 2;
|
|
m_patchRenderLattice.m_lines[i].colour =
|
|
m_patchRenderLattice.m_lines[i + 1].colour = m_cOrange;
|
|
}
|
|
for ( std::size_t c = 0; c < m_patchWidth; ++c ){
|
|
const std::size_t i = selectedPatchIndex * m_patchWidth + c;
|
|
m_patchRenderPoints.m_points[i].colour = patchCtrl_isInside( i )? m_cPink : m_cGreen;
|
|
}
|
|
break;
|
|
case ePatchColumn:
|
|
for ( std::size_t r = 0; r < m_patchHeight - 1; ++r ){
|
|
const std::size_t i = ( m_patchWidth - 1 ) * m_patchHeight * 2 + ( selectedPatchIndex * ( m_patchHeight - 1 ) + r ) * 2;
|
|
m_patchRenderLattice.m_lines[i].colour =
|
|
m_patchRenderLattice.m_lines[i + 1].colour = m_cOrange;
|
|
}
|
|
for ( std::size_t r = 0; r < m_patchHeight; ++r ){
|
|
const std::size_t i = r * m_patchWidth + selectedPatchIndex;
|
|
m_patchRenderPoints.m_points[i].colour = patchCtrl_isInside( i )? m_cPink : m_cGreen;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_selection = selection;
|
|
m_selectedU = selectedU;
|
|
m_selectedV = selectedV;
|
|
m_selectedPatchIndex = selectedPatchIndex;
|
|
SceneChangeNotify();
|
|
}
|
|
m_isSelected = ( selection != eNone );
|
|
}
|
|
void commitTransform( const Matrix4& transform ) const {
|
|
if( m_face ){
|
|
m_face->transform_texdef( transform, m_origin ); //! todo make SI update after Brush_textureChanged(); same problem after brush moved with tex lock
|
|
} // also after Patch_textureChanged(); calling them now in this->freezeTransform() works good nuff
|
|
else if( m_patch ){
|
|
const Matrix4 uvTransform = transform_local2object( matrix4_affine_inverse( transform ), m_faceLocal2tex, m_faceTex2local );
|
|
for( std::size_t i = 0; i < m_patchCtrl.size(); ++i ){
|
|
const Vector3 uv = matrix4_transformed_point( uvTransform, Vector3( m_patchCtrl[i].m_texcoord, 0 ) );
|
|
m_patch->getControlPointsTransformed()[i].m_texcoord = uv.vec2();
|
|
}
|
|
// m_patch->controlPointsChanged();
|
|
m_patch->UpdateCachedData();
|
|
}
|
|
SceneChangeNotify();
|
|
}
|
|
/* Manipulatable */
|
|
Vector3 m_start;
|
|
public:
|
|
void Construct( const Matrix4& device2manip, const DeviceVector device_point, const AABB& bounds, const Vector3& transform_origin ) override {
|
|
m_start = point_on_plane( m_plane, m_view->GetViewMatrix(), device_point );
|
|
}
|
|
//!? fix meaningless undo on grid/origin change, then click tex or lines
|
|
//!? todo no snap mode with alt modifier
|
|
void Transform( const Matrix4& manip2object, const Matrix4& device2manip, const DeviceVector device_point ) override {
|
|
const Vector3 current = point_on_plane( m_plane, m_view->GetViewMatrix(), device_point );
|
|
|
|
const bool snap = g_modifiers.shift(), snapHard = g_modifiers.ctrl();
|
|
|
|
const class Snapper
|
|
{
|
|
float m_x; //uv axis to screen coef
|
|
float m_y;
|
|
public:
|
|
Snapper( const Vector3& current, const Matrix4& faceTex2local ) {
|
|
Vector3 scale( m_view->GetViewport().x().x(), m_view->GetViewport().y().y(), 0 );
|
|
scale /= float{ std::max( scale.x(), scale.y() ) }; // normalise to be consistent over screen width & height
|
|
const Matrix4 proj = matrix4_multiplied_by_matrix4( matrix4_scale_for_vec3( scale ), m_view->GetViewMatrix() );
|
|
// get unary world displacements over uv axes to screenspace
|
|
const Vector3 curr = vector4_projected( matrix4_transformed_vector4( proj, Vector4( current, 1 ) ) );
|
|
const Vector3 x = vector4_projected( matrix4_transformed_vector4( proj, Vector4( current + vector3_normalised( faceTex2local.x().vec3() ), 1 ) ) );
|
|
const Vector3 y = vector4_projected( matrix4_transformed_vector4( proj, Vector4( current + vector3_normalised( faceTex2local.y().vec3() ), 1 ) ) );
|
|
m_x = vector3_length( x - curr ) * vector3_length( faceTex2local.x().vec3() ); // consider uv space scaling
|
|
m_y = vector3_length( y - curr ) * vector3_length( faceTex2local.y().vec3() );
|
|
}
|
|
bool x_snaps( float uv_dist, float epsilon = .01f ) const {
|
|
return uv_dist * m_x < epsilon;
|
|
}
|
|
bool y_snaps( float uv_dist, float epsilon = .01f ) const {
|
|
return uv_dist * m_y < epsilon;
|
|
}
|
|
} snapper( current, m_faceTex2local );
|
|
|
|
switch ( m_selection )
|
|
{
|
|
case ePivot:
|
|
{
|
|
const Vector3 uv_origin_start = matrix4_transformed_point( m_faceLocal2tex, m_origin );
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_faceLocal2tex, current );
|
|
float bestDistU = FLT_MAX;
|
|
float bestDistV = FLT_MAX;
|
|
float snapToU = 0;
|
|
float snapToV = 0;
|
|
for( std::vector<PointVertex>::const_iterator i = m_Ulines.m_lines.begin(); i != m_Ulines.m_lines.end(); ++++i ){
|
|
const float dist = std::fabs( ( *i ).vertex.y() - uv_origin.y() );
|
|
if( dist < bestDistU ){
|
|
bestDistU = dist;
|
|
snapToU = ( *i ).vertex.y();
|
|
}
|
|
}
|
|
for( std::vector<PointVertex>::const_iterator i = m_Vlines.m_lines.begin(); i != m_Vlines.m_lines.end(); ++++i ){
|
|
const float dist = std::fabs( ( *i ).vertex.x() - uv_origin.x() );
|
|
if( dist < bestDistV ){
|
|
bestDistV = dist;
|
|
snapToV = ( *i ).vertex.x();
|
|
}
|
|
}
|
|
forEachUVPoint( [&]( const Vector3& point ){
|
|
const float distU = std::fabs( point.y() - uv_origin.y() );
|
|
if( distU < bestDistU ){
|
|
bestDistU = distU;
|
|
snapToU = point.y();
|
|
}
|
|
const float distV = std::fabs( point.x() - uv_origin.x() );
|
|
if( distV < bestDistV ){
|
|
bestDistV = distV;
|
|
snapToV = point.x();
|
|
}
|
|
} );
|
|
Vector3 result( uv_origin_start );
|
|
if( snapper.y_snaps( bestDistU ) || snapHard ){
|
|
result.y() = snapToU;
|
|
}
|
|
else{
|
|
result.y() = uv_origin.y();
|
|
}
|
|
if( snapper.x_snaps( bestDistV ) || snapHard ){
|
|
result.x() = snapToV;
|
|
}
|
|
else{
|
|
result.x() = uv_origin.x();
|
|
}
|
|
m_origin = matrix4_transformed_point( m_faceTex2local, result );
|
|
UpdateFaceData( false, false );
|
|
SceneChangeNotify();
|
|
}
|
|
break;
|
|
case ePivotU:
|
|
{
|
|
const Vector3 uv_origin_start = matrix4_transformed_point( m_faceLocal2tex, m_origin );
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_faceLocal2tex, current );
|
|
float bestDist = FLT_MAX;
|
|
float snapTo = 0;
|
|
for( std::vector<PointVertex>::const_iterator i = m_Ulines.m_lines.begin(); i != m_Ulines.m_lines.end(); ++++i ){
|
|
const float dist = std::fabs( ( *i ).vertex.y() - uv_origin.y() );
|
|
if( dist < bestDist ){
|
|
bestDist = dist;
|
|
snapTo = ( *i ).vertex.y();
|
|
}
|
|
}
|
|
forEachUVPoint( [&]( const Vector3& point ){
|
|
const float dist = std::fabs( point.y() - uv_origin.y() );
|
|
if( dist < bestDist ){
|
|
bestDist = dist;
|
|
snapTo = point.y();
|
|
}
|
|
} );
|
|
Vector3 result( uv_origin_start );
|
|
if( snapper.y_snaps( bestDist ) || snapHard ){
|
|
result.y() = snapTo;
|
|
}
|
|
else{
|
|
result.y() = uv_origin.y();
|
|
}
|
|
m_origin = matrix4_transformed_point( m_faceTex2local, result );
|
|
UpdateFaceData( false, false );
|
|
SceneChangeNotify();
|
|
}
|
|
break;
|
|
case ePivotV:
|
|
{
|
|
const Vector3 uv_origin_start = matrix4_transformed_point( m_faceLocal2tex, m_origin );
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_faceLocal2tex, current );
|
|
float bestDist = FLT_MAX;
|
|
float snapTo = 0;
|
|
for( std::vector<PointVertex>::const_iterator i = m_Vlines.m_lines.begin(); i != m_Vlines.m_lines.end(); ++++i ){
|
|
const float dist = std::fabs( ( *i ).vertex.x() - uv_origin.x() );
|
|
if( dist < bestDist ){
|
|
bestDist = dist;
|
|
snapTo = ( *i ).vertex.x();
|
|
}
|
|
}
|
|
forEachUVPoint( [&]( const Vector3& point ){
|
|
const float dist = std::fabs( point.x() - uv_origin.x() );
|
|
if( dist < bestDist ){
|
|
bestDist = dist;
|
|
snapTo = point.x();
|
|
}
|
|
} );
|
|
Vector3 result( uv_origin_start );
|
|
if( snapper.x_snaps( bestDist ) || snapHard ){
|
|
result.x() = snapTo;
|
|
}
|
|
else{
|
|
result.x() = uv_origin.x();
|
|
}
|
|
m_origin = matrix4_transformed_point( m_faceTex2local, result );
|
|
UpdateFaceData( false, false );
|
|
SceneChangeNotify();
|
|
}
|
|
break;
|
|
case eGridU:
|
|
{
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_faceLocal2tex, m_origin );
|
|
const Vector3 uv_current = matrix4_transformed_point( m_faceLocal2tex, current );
|
|
|
|
const float dist = std::max( ( float_to_integer( uv_origin.y() + m_gridSign.x() * .25 ) + m_gridSign.x() - uv_current.y() ) * m_gridSign.x(), .01f );
|
|
unsigned int grid = std::max( 1, std::min( 16, int( 1 / dist ) ) );
|
|
|
|
if( snapHard ){ // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
|
grid--;
|
|
grid |= grid >> 1;
|
|
grid |= grid >> 2;
|
|
grid |= grid >> 4;
|
|
grid |= grid >> 8;
|
|
grid |= grid >> 16;
|
|
grid++;
|
|
}
|
|
|
|
if( m_gridU != grid || ( snap && m_gridV != grid ) ){
|
|
m_gridU = grid;
|
|
if( snap )
|
|
m_gridV = grid;
|
|
UpdateFaceData( false );
|
|
SceneChangeNotify();
|
|
}
|
|
}
|
|
break;
|
|
case eGridV:
|
|
{
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_faceLocal2tex, m_origin );
|
|
const Vector3 uv_current = matrix4_transformed_point( m_faceLocal2tex, current );
|
|
|
|
const float dist = std::max( ( float_to_integer( uv_origin.x() + m_gridSign.y() * .25 ) + m_gridSign.y() - uv_current.x() ) * m_gridSign.y(), .01f );
|
|
unsigned int grid = std::max( 1, std::min( 16, int( 1 / dist ) ) );
|
|
|
|
if( snapHard ){ // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
|
grid--;
|
|
grid |= grid >> 1;
|
|
grid |= grid >> 2;
|
|
grid |= grid >> 4;
|
|
grid |= grid >> 8;
|
|
grid |= grid >> 16;
|
|
grid++;
|
|
}
|
|
|
|
if( m_gridV != grid || ( snap && m_gridU != grid ) ){
|
|
m_gridV = grid;
|
|
if( snap )
|
|
m_gridU = grid;
|
|
UpdateFaceData( false );
|
|
SceneChangeNotify();
|
|
}
|
|
}
|
|
break;
|
|
case eCircle:
|
|
{
|
|
Vector3 from = m_start - m_origin;
|
|
constrain_to_axis( from, m_tex2local.z().vec3() );
|
|
Vector3 to = current - m_origin;
|
|
constrain_to_axis( to, m_tex2local.z().vec3() );
|
|
Matrix4 rot = g_matrix4_identity;
|
|
if( snap ){
|
|
matrix4_pivoted_rotate_by_axisangle( rot,
|
|
m_tex2local.z().vec3(),
|
|
float_snapped( angle_for_axis( from, to, m_tex2local.z().vec3() ), static_cast<float>( c_pi / 12.0 ) ),
|
|
m_origin );
|
|
}
|
|
else{
|
|
matrix4_pivoted_rotate_by_axisangle( rot,
|
|
m_tex2local.z().vec3(),
|
|
angle_for_axis( from, to, m_tex2local.z().vec3() ),
|
|
m_origin );
|
|
}
|
|
{ // snap
|
|
const Vector3 uvec = vector3_normalised( matrix4_transformed_direction( rot, m_tex2local.x().vec3() ) );
|
|
const Vector3 vvec = vector3_normalised( matrix4_transformed_direction( rot, m_tex2local.y().vec3() ) );
|
|
float bestDot = 0;
|
|
Vector3 bestTo;
|
|
bool V = false;
|
|
forEachEdge( [&]( const Vector3& point0, const Vector3& point1 ){
|
|
Vector3 vec( point1 - point0 );
|
|
constrain_to_axis( vec, m_tex2local.z().vec3() );
|
|
const float dotU = std::fabs( vector3_dot( uvec, vec ) );
|
|
if( dotU > bestDot ){
|
|
bestDot = dotU;
|
|
bestTo = vector3_dot( uvec, vec ) > 0? vec : -vec;
|
|
V = false;
|
|
}
|
|
const float dotV = std::fabs( vector3_dot( vvec, vec ) );
|
|
if( dotV > bestDot ){
|
|
bestDot = dotV;
|
|
bestTo = vector3_dot( vvec, vec ) > 0? vec : -vec;
|
|
V = true;
|
|
}
|
|
} );
|
|
if( bestDot > 0.9994f || snapHard ){
|
|
const Vector3 bestFrom = vector3_normalised( V? m_tex2local.y().vec3() : m_tex2local.x().vec3() );
|
|
rot = g_matrix4_identity;
|
|
matrix4_pivoted_rotate_by_axisangle( rot,
|
|
m_tex2local.z().vec3(),
|
|
angle_for_axis( bestFrom, bestTo, m_tex2local.z().vec3() ),
|
|
m_origin );
|
|
}
|
|
}
|
|
|
|
Matrix4 faceTex2local = matrix4_multiplied_by_matrix4( rot, m_tex2local );
|
|
faceTex2local.x().vec3() = plane3_project_point( Plane3( m_plane.normal(), 0 ), faceTex2local.x().vec3(), m_tex2local.z().vec3() );
|
|
faceTex2local.y().vec3() = plane3_project_point( Plane3( m_plane.normal(), 0 ), faceTex2local.y().vec3(), m_tex2local.z().vec3() );
|
|
faceTex2local = matrix4_multiplied_by_matrix4( // adjust to have UV's z = 0: move the plane along m_tex2local.z() so that plane.dist() = 0
|
|
matrix4_translation_for_vec3(
|
|
m_tex2local.z().vec3() * ( m_plane.dist() - vector3_dot( m_plane.normal(), faceTex2local.t().vec3() ) )
|
|
/ vector3_dot( m_plane.normal(), m_tex2local.z().vec3() )
|
|
),
|
|
faceTex2local );
|
|
m_lines2world = m_pivotLines2world = faceTex2local;
|
|
|
|
m_pivot2world = matrix4_multiplied_by_matrix4( rot, m_pivot2world0 );
|
|
|
|
commitTransform( rot );
|
|
}
|
|
break;
|
|
case eU: //!? todo modifier or default snap to set scale u = scale v
|
|
{
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_local2tex, m_origin );
|
|
const Vector3 uv_start = m_selectedU->vertex;
|
|
const Vector3 uv_current = m_selectedU->vertex + matrix4_transformed_point( m_local2tex, current ) - matrix4_transformed_point( m_local2tex, m_start );
|
|
float bestDist = FLT_MAX;
|
|
float snapTo = 0;
|
|
forEachUVPoint( [&]( const Vector3& point ){
|
|
const float dist = std::fabs( point.y() - uv_current.y() );
|
|
if( dist < bestDist ){
|
|
bestDist = dist;
|
|
snapTo = point.y();
|
|
}
|
|
} );
|
|
Vector3 result( 1, uv_current.y(), 1 );
|
|
if( snapper.y_snaps( bestDist ) || snapHard ){
|
|
result.y() = snapTo;
|
|
}
|
|
result.y() = ( result.y() - uv_origin.y() ) / ( uv_start.y() - uv_origin.y() );
|
|
|
|
if( snap )
|
|
result.x() = std::fabs( result.y() );
|
|
|
|
/* prevent scaling to 0, limit at max 10 textures per world unit */
|
|
if( vector3_length_squared( m_tex2local.y().vec3() * result.y() ) < .01 )
|
|
return;
|
|
|
|
Matrix4 scale = g_matrix4_identity;
|
|
matrix4_pivoted_scale_by_vec3( scale, result, uv_origin );
|
|
scale = transform_local2object( scale, m_tex2local, m_local2tex );
|
|
{
|
|
Matrix4 linescale = g_matrix4_identity;
|
|
matrix4_pivoted_scale_by_vec3( linescale, result, matrix4_transformed_point( m_faceLocal2tex, m_origin ) );
|
|
m_lines2world = m_pivotLines2world = matrix4_multiplied_by_matrix4( m_faceTex2local, linescale );
|
|
|
|
m_pivot2world = matrix4_multiplied_by_matrix4( m_pivot2world0, matrix4_scale_for_vec3( result ) );
|
|
}
|
|
commitTransform( scale );
|
|
}
|
|
break;
|
|
case eV:
|
|
{
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_local2tex, m_origin );
|
|
const Vector3 uv_start = m_selectedV->vertex;
|
|
const Vector3 uv_current = m_selectedV->vertex + matrix4_transformed_point( m_local2tex, current ) - matrix4_transformed_point( m_local2tex, m_start );
|
|
float bestDist = FLT_MAX;
|
|
float snapTo = 0;
|
|
forEachUVPoint( [&]( const Vector3& point ){
|
|
const float dist = std::fabs( point.x() - uv_current.x() );
|
|
if( dist < bestDist ){
|
|
bestDist = dist;
|
|
snapTo = point.x();
|
|
}
|
|
} );
|
|
Vector3 result( uv_current.x(), 1, 1 );
|
|
if( snapper.x_snaps( bestDist ) || snapHard ){
|
|
result.x() = snapTo;
|
|
}
|
|
result.x() = ( result.x() - uv_origin.x() ) / ( uv_start.x() - uv_origin.x() );
|
|
|
|
if( snap )
|
|
result.y() = std::fabs( result.x() );
|
|
|
|
/* prevent scaling to 0, limit at max 10 textures per world unit */
|
|
if( vector3_length_squared( m_tex2local.x().vec3() * result.x() ) < .01 )
|
|
return;
|
|
|
|
Matrix4 scale = g_matrix4_identity;
|
|
matrix4_pivoted_scale_by_vec3( scale, result, uv_origin );
|
|
scale = transform_local2object( scale, m_tex2local, m_local2tex );
|
|
{
|
|
Matrix4 linescale = g_matrix4_identity;
|
|
matrix4_pivoted_scale_by_vec3( linescale, result, matrix4_transformed_point( m_faceLocal2tex, m_origin ) );
|
|
m_lines2world = m_pivotLines2world = matrix4_multiplied_by_matrix4( m_faceTex2local, linescale );
|
|
|
|
m_pivot2world = matrix4_multiplied_by_matrix4( m_pivot2world0, matrix4_scale_for_vec3( result ) );
|
|
}
|
|
commitTransform( scale );
|
|
}
|
|
break;
|
|
case eUV:
|
|
{
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_local2tex, m_origin );
|
|
const Vector3 uv_start{ m_selectedV->vertex.x(), m_selectedU->vertex.y(), 0 };
|
|
const Vector3 uv_current{ ( m_selectedV->vertex + matrix4_transformed_point( m_local2tex, current ) - matrix4_transformed_point( m_local2tex, m_start ) ).x(),
|
|
( m_selectedU->vertex + matrix4_transformed_point( m_local2tex, current ) - matrix4_transformed_point( m_local2tex, m_start ) ).y(),
|
|
0 };
|
|
float bestDistU = FLT_MAX;
|
|
float snapToU = 0;
|
|
float bestDistV = FLT_MAX;
|
|
float snapToV = 0;
|
|
forEachUVPoint( [&]( const Vector3& point ){
|
|
const float distU = std::fabs( point.y() - uv_current.y() );
|
|
if( distU < bestDistU ){
|
|
bestDistU = distU;
|
|
snapToU = point.y();
|
|
}
|
|
const float distV = std::fabs( point.x() - uv_current.x() );
|
|
if( distV < bestDistV ){
|
|
bestDistV = distV;
|
|
snapToV = point.x();
|
|
}
|
|
} );
|
|
|
|
Vector3 result( uv_current.x(), uv_current.y(), 1 );
|
|
if( snapper.y_snaps( bestDistU ) || snapHard ){
|
|
result.y() = snapToU;
|
|
}
|
|
result.y() = ( result.y() - uv_origin.y() ) / ( uv_start.y() - uv_origin.y() );
|
|
|
|
if( snapper.x_snaps( bestDistV ) || snapHard ){
|
|
result.x() = snapToV;
|
|
}
|
|
result.x() = ( result.x() - uv_origin.x() ) / ( uv_start.x() - uv_origin.x() );
|
|
|
|
if( snap ){
|
|
const std::size_t best = std::fabs( result.x() ) > std::fabs( result.y() )? 0 : 1;
|
|
result[( best + 1 ) % 2] = std::copysign( result[best], result[( best + 1 ) % 2] );
|
|
}
|
|
|
|
/* prevent scaling to 0, limit at max 10 textures per world unit */
|
|
if( vector3_length_squared( m_tex2local.x().vec3() * result.x() ) < .01 ||
|
|
vector3_length_squared( m_tex2local.y().vec3() * result.y() ) < .01 )
|
|
return;
|
|
|
|
Matrix4 scale = g_matrix4_identity;
|
|
matrix4_pivoted_scale_by_vec3( scale, result, uv_origin );
|
|
scale = transform_local2object( scale, m_tex2local, m_local2tex );
|
|
{
|
|
Matrix4 linescale = g_matrix4_identity;
|
|
matrix4_pivoted_scale_by_vec3( linescale, result, matrix4_transformed_point( m_faceLocal2tex, m_origin ) );
|
|
m_lines2world = m_pivotLines2world = matrix4_multiplied_by_matrix4( m_faceTex2local, linescale );
|
|
|
|
m_pivot2world = matrix4_multiplied_by_matrix4( m_pivot2world0, matrix4_scale_for_vec3( result ) );
|
|
}
|
|
commitTransform( scale );
|
|
}
|
|
break;
|
|
case eSkewU:
|
|
{
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_faceLocal2tex, m_origin );
|
|
const Vector3 uv_move = matrix4_transformed_point( m_faceLocal2tex, current ) - matrix4_transformed_point( m_faceLocal2tex, m_start );
|
|
Matrix4 skew( g_matrix4_identity );
|
|
skew[4] = uv_move.x() / ( m_selectedU->vertex - uv_origin ).y();
|
|
|
|
const Vector3 skewed = matrix4_transformed_direction( skew, g_vector3_axis_y );
|
|
const float uv_y_measure_dist = ( m_selectedU->vertex - uv_origin ).y();
|
|
float bestDist = FLT_MAX;
|
|
Vector3 bestTo;
|
|
const auto snap_to_edge = [&]( const Vector3 edge ){
|
|
if( std::fabs( edge.y() ) > 1e-5f ){ // don't snap so, that one axis = the other
|
|
const float dist = std::fabs( edge.x() * uv_y_measure_dist / edge.y() - skewed.x() * uv_y_measure_dist / skewed.y() );
|
|
if( dist < bestDist ){
|
|
bestDist = dist;
|
|
bestTo = edge;
|
|
}
|
|
}
|
|
};
|
|
forEachEdge( [&]( const Vector3& point0, const Vector3& point1 ){
|
|
snap_to_edge( matrix4_transformed_point( m_faceLocal2tex, point1 ) - matrix4_transformed_point( m_faceLocal2tex, point0 ) );
|
|
} );
|
|
forEachPoint( [&]( const Vector3& point ){
|
|
const Vector3 po = matrix4_transformed_point( m_faceLocal2tex, point );
|
|
for( std::vector<PointVertex>::const_iterator i = m_Vlines.m_lines.cbegin(); i != m_Vlines.m_lines.cend(); ++++i ){
|
|
snap_to_edge( po - Vector3( i->vertex.x(), uv_origin.y(), 0 ) );
|
|
}
|
|
snap_to_edge( po - Vector3( uv_origin.x(), uv_origin.y(), 0 ) );
|
|
} );
|
|
if( snapper.x_snaps( bestDist, .015f ) || snapHard ){ //!? todo add snap: make manipulated axis orthogonal to the other
|
|
skew[4] = bestTo.x() / bestTo.y();
|
|
}
|
|
|
|
{
|
|
Matrix4 mat( g_matrix4_identity );
|
|
matrix4_translate_by_vec3( mat, uv_origin );
|
|
matrix4_multiply_by_matrix4( mat, skew );
|
|
matrix4_translate_by_vec3( mat, -uv_origin );
|
|
skew = mat;
|
|
}
|
|
|
|
m_lines2world = m_pivotLines2world = matrix4_multiplied_by_matrix4( m_faceTex2local, skew );
|
|
m_pivot2world = transform_local2object( skew, m_tex2local, m_local2tex );
|
|
matrix4_multiply_by_matrix4( m_pivot2world, m_pivot2world0 );
|
|
|
|
skew = transform_local2object( skew, m_faceTex2local, m_faceLocal2tex );
|
|
commitTransform( skew );
|
|
}
|
|
break;
|
|
case eSkewV:
|
|
{
|
|
const Vector3 uv_origin = matrix4_transformed_point( m_faceLocal2tex, m_origin );
|
|
const Vector3 uv_move = matrix4_transformed_point( m_faceLocal2tex, current ) - matrix4_transformed_point( m_faceLocal2tex, m_start );
|
|
Matrix4 skew( g_matrix4_identity );
|
|
skew[1] = uv_move.y() / ( m_selectedV->vertex - uv_origin ).x();
|
|
|
|
const Vector3 skewed = matrix4_transformed_direction( skew, g_vector3_axis_x );
|
|
const float uv_x_measure_dist = ( m_selectedV->vertex - uv_origin ).x();
|
|
float bestDist = FLT_MAX;
|
|
Vector3 bestTo;
|
|
const auto snap_to_edge = [&]( const Vector3 edge ){
|
|
if( std::fabs( edge.x() ) > 1e-5f ){ // don't snap so, that one axis = the other
|
|
const float dist = std::fabs( edge.y() * uv_x_measure_dist / edge.x() - skewed.y() * uv_x_measure_dist / skewed.x() );
|
|
if( dist < bestDist ){
|
|
bestDist = dist;
|
|
bestTo = edge;
|
|
}
|
|
}
|
|
};
|
|
forEachEdge( [&]( const Vector3& point0, const Vector3& point1 ){
|
|
snap_to_edge( matrix4_transformed_point( m_faceLocal2tex, point1 ) - matrix4_transformed_point( m_faceLocal2tex, point0 ) );
|
|
} );
|
|
forEachPoint( [&]( const Vector3& point ){
|
|
const Vector3 po = matrix4_transformed_point( m_faceLocal2tex, point );
|
|
for( std::vector<PointVertex>::const_iterator i = m_Ulines.m_lines.cbegin(); i != m_Ulines.m_lines.cend(); ++++i ){
|
|
snap_to_edge( po - Vector3( uv_origin.x(), i->vertex.y(), 0 ) );
|
|
}
|
|
snap_to_edge( po - Vector3( uv_origin.x(), uv_origin.y(), 0 ) );
|
|
} );
|
|
if( snapper.y_snaps( bestDist, .015f ) || snapHard ){ //!? todo add snap: make manipulated axis orthogonal to the other
|
|
skew[1] = bestTo.y() / bestTo.x();
|
|
}
|
|
|
|
{
|
|
Matrix4 mat( g_matrix4_identity );
|
|
matrix4_translate_by_vec3( mat, uv_origin );
|
|
matrix4_multiply_by_matrix4( mat, skew );
|
|
matrix4_translate_by_vec3( mat, -uv_origin );
|
|
skew = mat;
|
|
}
|
|
|
|
m_lines2world = m_pivotLines2world = matrix4_multiplied_by_matrix4( m_faceTex2local, skew );
|
|
m_pivot2world = transform_local2object( skew, m_tex2local, m_local2tex );
|
|
matrix4_multiply_by_matrix4( m_pivot2world, m_pivot2world0 );
|
|
|
|
skew = transform_local2object( skew, m_faceTex2local, m_faceLocal2tex );
|
|
commitTransform( skew );
|
|
}
|
|
break;
|
|
case eTex:
|
|
{
|
|
const Vector3 uvstart = matrix4_transformed_point( m_faceLocal2tex, m_start );
|
|
const Vector3 uvcurrent = matrix4_transformed_point( m_faceLocal2tex, current );
|
|
const Vector3 uvmove = uvcurrent - uvstart;
|
|
float bestDistU = FLT_MAX;
|
|
float bestDistV = FLT_MAX;
|
|
float snapMoveU = 0;
|
|
float snapMoveV = 0;
|
|
// snap uvmove
|
|
const auto functor = [&]( const Vector3& point ){
|
|
for( auto it = m_Ulines.m_lines.cbegin(); it != m_Ulines.m_lines.cend(); ++++it ){
|
|
const float dist = point.y() - ( ( *it ).vertex.y() + uvmove.y() );
|
|
if( std::fabs( dist ) < bestDistU ){
|
|
bestDistU = std::fabs( dist );
|
|
snapMoveU = uvmove.y() + dist;
|
|
}
|
|
}
|
|
for( auto it = m_Vlines.m_lines.cbegin(); it != m_Vlines.m_lines.cend(); ++++it ){
|
|
const float dist = point.x() - ( ( *it ).vertex.x() + uvmove.x() );
|
|
if( std::fabs( dist ) < bestDistV ){
|
|
bestDistV = std::fabs( dist );
|
|
snapMoveV = uvmove.x() + dist;
|
|
}
|
|
}
|
|
};
|
|
forEachUVPoint( functor );
|
|
functor( matrix4_transformed_point( m_faceLocal2tex, m_origin ) );
|
|
|
|
Vector3 result( uvmove );
|
|
if( snapper.y_snaps( bestDistU ) || snapHard ){
|
|
result.y() = snapMoveU;
|
|
}
|
|
if( snapper.x_snaps( bestDistV ) || snapHard ){
|
|
result.x() = snapMoveV;
|
|
}
|
|
|
|
if( snap ){
|
|
auto& smaller = std::fabs( uvmove.x() * vector3_length( m_faceTex2local.x().vec3() ) ) <
|
|
std::fabs( uvmove.y() * vector3_length( m_faceTex2local.y().vec3() ) )? result.x() : result.y();
|
|
smaller = 0;
|
|
}
|
|
|
|
result = translation_local2object( result, m_faceTex2local, m_faceLocal2tex );
|
|
|
|
const Matrix4 translation = matrix4_translation_for_vec3( result );
|
|
|
|
m_lines2world = matrix4_multiplied_by_matrix4( translation, m_faceTex2local );
|
|
|
|
commitTransform( translation );
|
|
}
|
|
break;
|
|
case ePatchPoint:
|
|
case ePatchRow:
|
|
case ePatchColumn:
|
|
{
|
|
std::vector<std::size_t> indices;
|
|
if( m_selection == ePatchPoint )
|
|
indices.push_back( m_selectedPatchIndex );
|
|
else if( m_selection == ePatchRow )
|
|
for ( std::size_t c = 0; c < m_patchWidth; ++c )
|
|
indices.push_back( m_selectedPatchIndex * m_patchWidth + c );
|
|
else if( m_selection == ePatchColumn )
|
|
for ( std::size_t r = 0; r < m_patchHeight; ++r )
|
|
indices.push_back( r * m_patchWidth + m_selectedPatchIndex );
|
|
|
|
const Vector3 uvstart = matrix4_transformed_point( m_faceLocal2tex, m_start );
|
|
const Vector3 uvcurrent = matrix4_transformed_point( m_faceLocal2tex, current );
|
|
const Vector3 uvmove = uvcurrent - uvstart;
|
|
float bestDistU = FLT_MAX;
|
|
float bestDistV = FLT_MAX;
|
|
float snapMoveU = 0;
|
|
float snapMoveV = 0;
|
|
// snap uvmove
|
|
for( std::size_t index : indices ){
|
|
for( std::vector<PointVertex>::const_iterator i = m_Ulines.m_lines.begin(); i != m_Ulines.m_lines.end(); ++++i ){
|
|
const float dist = m_patchCtrl[index].m_texcoord.y() + uvmove.y() - ( *i ).vertex.y();
|
|
if( std::fabs( dist ) < bestDistU ){
|
|
bestDistU = std::fabs( dist );
|
|
snapMoveU = uvmove.y() - dist;
|
|
}
|
|
}
|
|
for( std::vector<PointVertex>::const_iterator i = m_Vlines.m_lines.begin(); i != m_Vlines.m_lines.end(); ++++i ){
|
|
const float dist = m_patchCtrl[index].m_texcoord.x() + uvmove.x() - ( *i ).vertex.x();
|
|
if( std::fabs( dist ) < bestDistV ){
|
|
bestDistV = std::fabs( dist );
|
|
snapMoveV = uvmove.x() - dist;
|
|
}
|
|
}
|
|
const Vector3 origin = matrix4_transformed_point( m_faceLocal2tex, m_origin );
|
|
{
|
|
const float dist = m_patchCtrl[index].m_texcoord.y() + uvmove.y() - origin.y();
|
|
if( std::fabs( dist ) < bestDistU ){
|
|
bestDistU = std::fabs( dist );
|
|
snapMoveU = uvmove.y() - dist;
|
|
}
|
|
}
|
|
{
|
|
const float dist = m_patchCtrl[index].m_texcoord.x() + uvmove.x() - origin.x();
|
|
if( std::fabs( dist ) < bestDistV ){
|
|
bestDistV = std::fabs( dist );
|
|
snapMoveV = uvmove.x() - dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector3 result( uvmove );
|
|
if( snapper.y_snaps( bestDistU ) || snapHard ){
|
|
result.y() = snapMoveU;
|
|
}
|
|
if( snapper.x_snaps( bestDistV ) || snapHard ){
|
|
result.x() = snapMoveV;
|
|
}
|
|
|
|
if( snap ){
|
|
auto& smaller = std::fabs( uvmove.x() * vector3_length( m_faceTex2local.x().vec3() ) ) <
|
|
std::fabs( uvmove.y() * vector3_length( m_faceTex2local.y().vec3() ) )? result.x() : result.y();
|
|
smaller = 0;
|
|
}
|
|
|
|
const Matrix4 translation = matrix4_translation_for_vec3( result );
|
|
for( std::size_t i : indices ){
|
|
const Vector3 uv = matrix4_transformed_point( translation, Vector3( m_patchCtrl[i].m_texcoord, 0 ) );
|
|
m_patch->getControlPointsTransformed()[i].m_texcoord = uv.vec2();
|
|
m_patchRenderPoints.m_points[i].vertex = vertex3f_for_vector3( uv );
|
|
}
|
|
|
|
// update lattice renderable entirely
|
|
for ( std::size_t r = 0; r < m_patchHeight; ++r ){
|
|
for ( std::size_t c = 0; c < m_patchWidth - 1; ++c ){
|
|
const Vector2& a = m_patch->getControlPointsTransformed()[r * m_patchWidth + c].m_texcoord;
|
|
const Vector2& b = m_patch->getControlPointsTransformed()[r * m_patchWidth + c + 1].m_texcoord;
|
|
m_patchRenderLattice.m_lines[( r * ( m_patchWidth - 1 ) + c ) * 2].vertex = vertex3f_for_vector3( Vector3( a, 0 ) );
|
|
m_patchRenderLattice.m_lines[( r * ( m_patchWidth - 1 ) + c ) * 2 + 1].vertex = vertex3f_for_vector3( Vector3( b, 0 ) );
|
|
}
|
|
}
|
|
for ( std::size_t c = 0; c < m_patchWidth; ++c ){
|
|
for ( std::size_t r = 0; r < m_patchHeight - 1; ++r ){
|
|
const Vector2& a = m_patch->getControlPointsTransformed()[r * m_patchWidth + c].m_texcoord;
|
|
const Vector2& b = m_patch->getControlPointsTransformed()[( r + 1 ) * m_patchWidth + c].m_texcoord;
|
|
m_patchRenderLattice.m_lines[( m_patchWidth - 1 ) * m_patchHeight * 2 + ( c * ( m_patchHeight - 1 ) + r ) * 2].vertex = vertex3f_for_vector3( Vector3( a, 0 ) );
|
|
m_patchRenderLattice.m_lines[( m_patchWidth - 1 ) * m_patchHeight * 2 + ( c * ( m_patchHeight - 1 ) + r ) * 2 + 1].vertex = vertex3f_for_vector3( Vector3( b, 0 ) );
|
|
}
|
|
}
|
|
|
|
m_patch->UpdateCachedData();
|
|
SceneChangeNotify();
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void freezeTransform() override {
|
|
if( m_selection == eCircle
|
|
|| m_selection == eU
|
|
|| m_selection == eV
|
|
|| m_selection == eUV
|
|
|| m_selection == eSkewU
|
|
|| m_selection == eSkewV
|
|
|| m_selection == eTex
|
|
|| m_selection == ePatchPoint
|
|
|| m_selection == ePatchRow
|
|
|| m_selection == ePatchColumn )
|
|
{
|
|
if( m_face ){
|
|
m_face->freezeTransform();
|
|
Brush_textureChanged();
|
|
}
|
|
else if( m_patch ){
|
|
m_patch->freezeTransform();
|
|
Patch_textureChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
Manipulatable* GetManipulatable() override {
|
|
return this;
|
|
}
|
|
|
|
void setSelected( bool select ) override {
|
|
m_isSelected = select;
|
|
}
|
|
bool isSelected() const override {
|
|
return m_isSelected;
|
|
}
|
|
};
|
|
|
|
UVManipulator* New_UVManipulator(){
|
|
return new UVManipulatorImpl;
|
|
}
|