mirror of
https://github.com/Garux/netradiant-custom.git
synced 2026-09-28 00:00:03 +02:00
* bobToolz: new Aperture Door builder
This commit is contained in:
@@ -26,6 +26,8 @@
|
||||
#include "DPoint.h"
|
||||
#include "DPlane.h"
|
||||
|
||||
#include "math/vector.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -148,6 +150,22 @@ void DWinding::WindingCentre( vec3_t centre ){
|
||||
VectorScale( centre, scale, centre );
|
||||
}
|
||||
|
||||
void DWinding::WindingCentroid( vec3_t centroid ) const {
|
||||
DoubleVector3 cent( 0 ); // 3 times centroid to skip division
|
||||
double areasum = 0; // 2 times area to skip division
|
||||
|
||||
for( int i = 0; i < numpoints - 1; ++i )
|
||||
{
|
||||
const DoubleVector3 po[3] = { vector3_from_array( p[ 0 ] ), vector3_from_array( p[ i ] ), vector3_from_array( p[ i + 1 ] ) };
|
||||
const DoubleVector3 c = po[0] + po[1] + po[2];
|
||||
const double area = vector3_length( vector3_cross( po[1] - po[0], po[2] - po[0] ) );
|
||||
cent += c * area;
|
||||
areasum += area;
|
||||
}
|
||||
|
||||
cent /= areasum * 3;
|
||||
VectorCopy( cent, centroid );
|
||||
}
|
||||
|
||||
DWinding* DWinding::CopyWinding(){
|
||||
DWinding* c = new DWinding;
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
|
||||
void CheckWinding();
|
||||
void WindingCentre( vec3_t centre );
|
||||
void WindingCentroid( vec3_t centroid ) const;
|
||||
void WindingBounds( vec3_t mins, vec3_t maxs );
|
||||
void RemoveColinearPoints();
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ const char* PLUGIN_NAME = "bobToolz";
|
||||
constexpr char PLUGIN_COMMANDS[] = "About...,"
|
||||
"-,"
|
||||
"Stair Builder...,"
|
||||
"Aperture Door...,"
|
||||
"Door Builder...,"
|
||||
"Find Duplicates,"
|
||||
"Intersect...,"
|
||||
@@ -152,6 +153,9 @@ extern "C" void QERPlug_Dispatch( const char *p, vec3_t vMin, vec3_t vMax, bool
|
||||
else if ( string_equal_nocase( p, "door builder..." ) ) {
|
||||
DoBuildDoors();
|
||||
}
|
||||
else if ( string_equal_nocase( p, "aperture door..." ) ) {
|
||||
DoBuildApertureDoors();
|
||||
}
|
||||
else if ( string_equal_nocase( p, "intersect..." ) ) {
|
||||
DoIntersect();
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <QButtonGroup>
|
||||
#include <QRadioButton>
|
||||
#include <QFrame>
|
||||
#include <QTabWidget>
|
||||
#include "gtkutil/spinbox.h"
|
||||
#include "gtkutil/combobox.h"
|
||||
|
||||
@@ -311,6 +312,265 @@ bool DoDoorsBox( DoorRS* rs ){
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DoApertureDoorsBox( ApertureDoorRS* rs ){
|
||||
QDialog dialog( g_pRadiantWnd, Qt::Dialog | Qt::WindowCloseButtonHint );
|
||||
dialog.setWindowTitle( "Aperture Door Builder" );
|
||||
|
||||
QCheckBox *checkOffsetStartAngle, *checkHealth, *checkSlopedSegmentsRoundize, *checkDistance;
|
||||
QComboBox *comboMain, *comboTrim, *comboMain2, *comboTrim2;
|
||||
QSpinBox *spin_segments, *spin_silenceBrush[3];
|
||||
QDoubleSpinBox *spin_openAngle, *spin_speed, *spin_time, *spin_wait, *spin_distance, *spin_innerDepth1, *spin_innerDepth2, *spin_slopedDepth1, *spin_slopedDepth2;
|
||||
QTabWidget *tabs_fromWinding, *tabs_slopedSegments, *tabs_inner, *tabs_silence, *tabs_speed;
|
||||
|
||||
{
|
||||
auto *form = new QFormLayout( &dialog );
|
||||
form->setSizeConstraint( QLayout::SizeConstraint::SetFixedSize );
|
||||
{
|
||||
auto *tabs = tabs_fromWinding = new QTabWidget;
|
||||
form->addRow( tabs );
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "From params" );
|
||||
auto *form = new QFormLayout( containerWidget );
|
||||
{
|
||||
auto spin = spin_segments = new SpinBox( 3, 256, rs->segments );
|
||||
form->addRow( new SpinBoxLabel( "Number Of Segments", spin ), spin );
|
||||
}
|
||||
{
|
||||
form->addWidget( checkOffsetStartAngle = new QCheckBox( "Offset Start Angle" ) );
|
||||
checkOffsetStartAngle->setChecked( rs->offsetStartAngle );
|
||||
}
|
||||
}
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "From winding" );
|
||||
}
|
||||
tabs->setCurrentIndex( rs->fromWinding );
|
||||
}
|
||||
|
||||
{
|
||||
auto *combo = comboMain = new ComboBox;
|
||||
form->addRow( "Main Texture", combo );
|
||||
char buffer[256];
|
||||
combo->addItems( LoadListStore( GetFilename( buffer, "bt/door-tex.txt" ) ) );
|
||||
combo->setEditable( true );
|
||||
combo->lineEdit()->setMaxLength( TexturePath::max_prefixless_length );
|
||||
combo->setCurrentText( rs->textureMain.get_short() );
|
||||
}
|
||||
{
|
||||
auto *combo = comboTrim = new ComboBox;
|
||||
form->addRow( "Trim Texture", combo );
|
||||
char buffer[256];
|
||||
combo->addItems( LoadListStore( GetFilename( buffer, "bt/door-tex-trim.txt" ) ) );
|
||||
combo->setEditable( true );
|
||||
combo->lineEdit()->setMaxLength( TexturePath::max_prefixless_length );
|
||||
combo->setCurrentText( rs->textureTrim.get_short() );
|
||||
}
|
||||
{ // note: used below
|
||||
auto *combo = comboMain2 = new ComboBox;
|
||||
char buffer[256];
|
||||
combo->addItems( LoadListStore( GetFilename( buffer, "bt/door-tex.txt" ) ) );
|
||||
combo->setEditable( true );
|
||||
combo->lineEdit()->setMaxLength( TexturePath::max_prefixless_length );
|
||||
combo->setCurrentText( rs->innerTextureMain.get_short() );
|
||||
}
|
||||
{ // note: used below
|
||||
auto *combo = comboTrim2 = new ComboBox;
|
||||
char buffer[256];
|
||||
combo->addItems( LoadListStore( GetFilename( buffer, "bt/door-tex-trim.txt" ) ) );
|
||||
combo->setEditable( true );
|
||||
combo->lineEdit()->setMaxLength( TexturePath::max_prefixless_length );
|
||||
combo->setCurrentText( rs->innerTextureTrim.get_short() );
|
||||
}
|
||||
|
||||
{
|
||||
auto *tabs = tabs_slopedSegments = new QTabWidget;
|
||||
form->addRow( tabs );
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "Normal segments" );
|
||||
auto *form = new QFormLayout( containerWidget );
|
||||
{
|
||||
auto *tabs = tabs_inner = new QTabWidget;
|
||||
form->addRow( tabs );
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "No extra layer" );
|
||||
}
|
||||
{ // reuse widgets in two tabs
|
||||
auto *form = new QFormLayout;
|
||||
form->addRow( "Main Texture", comboMain2 );
|
||||
form->addRow( "Trim Texture", comboTrim2 );
|
||||
{
|
||||
auto spin = spin_innerDepth1 = new DoubleSpinBox( 0, 999999, rs->innerDepth1 );
|
||||
form->addRow( new SpinBoxLabel( "Depth1", spin ), spin );
|
||||
}
|
||||
{
|
||||
auto spin = spin_innerDepth2 = new DoubleSpinBox( 0, 999999, rs->innerDepth2 );
|
||||
form->addRow( new SpinBoxLabel( "Depth2", spin ), spin );
|
||||
}
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "Segmented" );
|
||||
QObject::connect( tabs, &QTabWidget::currentChanged, [form, containerWidget]( int index ){
|
||||
if( index == int( ApertureDoorRS::Inner::segmented ) )
|
||||
containerWidget->setLayout( form );
|
||||
} );
|
||||
}
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "Sloped" );
|
||||
QObject::connect( tabs, &QTabWidget::currentChanged, [form, containerWidget]( int index ){
|
||||
if( index == int( ApertureDoorRS::Inner::sloped ) )
|
||||
containerWidget->setLayout( form );
|
||||
} );
|
||||
containerWidget->setLayout( form ); // insert somewhere initially
|
||||
}
|
||||
}
|
||||
tabs->setCurrentIndex( int( rs->innerType ) );
|
||||
}
|
||||
}
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "Sloped segments" );
|
||||
auto *form = new QFormLayout( containerWidget );
|
||||
{
|
||||
form->addWidget( checkSlopedSegmentsRoundize = new QCheckBox( "Round outline" ) );
|
||||
checkSlopedSegmentsRoundize->setChecked( rs->slopedSegmentsRoundize );
|
||||
}
|
||||
{
|
||||
auto spin = spin_slopedDepth1 = new DoubleSpinBox( 0, 999999, rs->slopedDepth1 );
|
||||
form->addRow( new SpinBoxLabel( "Depth1", spin ), spin );
|
||||
}
|
||||
{
|
||||
auto spin = spin_slopedDepth2 = new DoubleSpinBox( 0, 999999, rs->slopedDepth2 );
|
||||
form->addRow( new SpinBoxLabel( "Depth2", spin ), spin );
|
||||
}
|
||||
}
|
||||
tabs->setCurrentIndex( rs->slopedSegments );
|
||||
}
|
||||
|
||||
{
|
||||
auto *tabs = tabs_silence = new QTabWidget;
|
||||
form->addRow( tabs );
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "Silence: no" );
|
||||
}
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "Brush" );
|
||||
auto *hbox = new QHBoxLayout( containerWidget );
|
||||
{
|
||||
hbox->addWidget( new QLabel( "Offset" ) );
|
||||
const char *axes[]{ "X: ", "Y: ", "Z: " };
|
||||
for( int i = 0; i != 3; ++i ){
|
||||
auto *spin = spin_silenceBrush[i] = new SpinBox( -999999, 999999, rs->silenceBrushesOffset[i], 0, 256 );
|
||||
spin->setPrefix( axes[i] );
|
||||
hbox->addWidget( spin );
|
||||
}
|
||||
}
|
||||
}
|
||||
tabs->setCurrentIndex( int( rs->silenceType ) );
|
||||
}
|
||||
|
||||
{
|
||||
auto *tabs = tabs_speed = new QTabWidget;
|
||||
form->addRow( tabs );
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "Open time" );
|
||||
auto *form = new QFormLayout( containerWidget );
|
||||
{
|
||||
auto spin = spin_time = new DoubleSpinBox( 0.001, 256, rs->time, 3, 0.1 );
|
||||
form->addRow( new SpinBoxLabel( "Time", spin ), spin );
|
||||
}
|
||||
}
|
||||
{
|
||||
auto *containerWidget = new QWidget;
|
||||
tabs->addTab( containerWidget, "Open speed" );
|
||||
auto *form = new QFormLayout( containerWidget );
|
||||
{
|
||||
auto spin = spin_speed = new DoubleSpinBox( -1, 999999, rs->speed ); // negative speed = infinite speed
|
||||
form->addRow( new SpinBoxLabel( "Speed", spin ), spin );
|
||||
}
|
||||
}
|
||||
tabs->setCurrentIndex( rs->speedUse );
|
||||
}
|
||||
|
||||
{
|
||||
spin_distance = new DoubleSpinBox( -999999, 999999, rs->distance );
|
||||
checkDistance = new QCheckBox( "Set distance" );
|
||||
form->addRow( checkDistance, spin_distance );
|
||||
QObject::connect( checkDistance, &QCheckBox::stateChanged, spin_distance, &QWidget::setEnabled );
|
||||
checkDistance->setChecked( rs->distanceSet );
|
||||
spin_distance->setEnabled( rs->distanceSet );
|
||||
}
|
||||
{
|
||||
auto spin = spin_openAngle = new DoubleSpinBox( -180, 180, rs->openAngle, 3 );
|
||||
auto *label = new SpinBoxLabel( "Open Angle *", spin );
|
||||
form->addRow( label, spin );
|
||||
const QString tip( "0 = seamless\n180 = seamless reverted\n90 = radially\n<0 = overlapping" );
|
||||
spin->setToolTip( tip );
|
||||
label->setToolTip( tip );
|
||||
}
|
||||
{
|
||||
form->addWidget( checkHealth = new QCheckBox( "Health" ) );
|
||||
checkHealth->setChecked( rs->health );
|
||||
}
|
||||
{
|
||||
auto spin = spin_wait = new DoubleSpinBox( -1, 999999, rs->wait );
|
||||
form->addRow( new SpinBoxLabel( "Wait", spin ), spin );
|
||||
}
|
||||
|
||||
{
|
||||
auto buttons = new QDialogButtonBox( QDialogButtonBox::StandardButton::Ok | QDialogButtonBox::StandardButton::Cancel );
|
||||
form->addWidget( buttons );
|
||||
QObject::connect( buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept );
|
||||
QObject::connect( buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject );
|
||||
}
|
||||
}
|
||||
|
||||
if( dialog.exec() ){
|
||||
rs->fromWinding = tabs_fromWinding->currentIndex();
|
||||
rs->segments = spin_segments->value();
|
||||
rs->offsetStartAngle = checkOffsetStartAngle->isChecked();
|
||||
|
||||
rs->textureMain = comboMain->currentText().toLatin1().constData();
|
||||
rs->textureTrim = comboTrim->currentText().toLatin1().constData();
|
||||
|
||||
rs->innerType = ApertureDoorRS::Inner( tabs_inner->currentIndex() );
|
||||
rs->innerTextureMain = comboMain2->currentText().toLatin1().constData();
|
||||
rs->innerTextureTrim = comboTrim2->currentText().toLatin1().constData();
|
||||
rs->innerDepth1 = spin_innerDepth1->value();
|
||||
rs->innerDepth2 = spin_innerDepth2->value();
|
||||
|
||||
rs->slopedSegments = tabs_slopedSegments->currentIndex();
|
||||
rs->slopedSegmentsRoundize = checkSlopedSegmentsRoundize->isChecked();
|
||||
rs->slopedDepth1 = spin_slopedDepth1->value();
|
||||
rs->slopedDepth2 = spin_slopedDepth2->value();
|
||||
|
||||
rs->speedUse = tabs_speed->currentIndex();
|
||||
rs->time = spin_time->value();
|
||||
rs->speed = spin_speed->value();
|
||||
|
||||
rs->silenceType = ApertureDoorRS::Silence( tabs_silence->currentIndex() );
|
||||
rs->silenceBrushesOffset[0] = spin_silenceBrush[0]->value();
|
||||
rs->silenceBrushesOffset[1] = spin_silenceBrush[1]->value();
|
||||
rs->silenceBrushesOffset[2] = spin_silenceBrush[2]->value();
|
||||
|
||||
rs->distanceSet = checkDistance->isChecked();
|
||||
rs->distance = spin_distance->value();
|
||||
rs->openAngle = spin_openAngle->value();
|
||||
|
||||
rs->health = checkHealth->isChecked();
|
||||
rs->wait = spin_wait->value();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
EMessageBoxReturn DoPathPlotterBox( PathPlotterRS* rs ){
|
||||
QDialog dialog( g_pRadiantWnd, Qt::Dialog | Qt::WindowCloseButtonHint );
|
||||
dialog.setWindowTitle( "Path Plotter" );
|
||||
|
||||
@@ -69,6 +69,72 @@ struct DoorRS {
|
||||
int nOrientation;
|
||||
};
|
||||
|
||||
#include "stream/stringstream.h"
|
||||
|
||||
class TexturePath
|
||||
{
|
||||
CopiedString m_path;
|
||||
public:
|
||||
// constructor is not strict wrt "textures/" prefix presence in the input
|
||||
// but this wont construct textures/textures/bla from textures/bla; 🤞🏿 this wont happen
|
||||
TexturePath( const char *path = "" ) : m_path( string_equal_prefix_nocase( path, "textures/" )
|
||||
? path
|
||||
: StringStream<64>( "textures/", path ) )
|
||||
{}
|
||||
const char *get() const {
|
||||
return m_path.c_str();
|
||||
}
|
||||
const char *get_short() const {
|
||||
return m_path.c_str() + prefix_length;
|
||||
}
|
||||
static constexpr int max_length = ( 64 - 1 ); //minus null terminator
|
||||
static constexpr int prefix_length = ( std::size( "textures/" ) - 1 );
|
||||
static constexpr int max_prefixless_length = ( max_length - prefix_length );
|
||||
};
|
||||
|
||||
struct ApertureDoorRS {
|
||||
bool fromWinding = false;
|
||||
int segments = 16;
|
||||
bool offsetStartAngle = false;
|
||||
|
||||
TexturePath textureMain = "textures/base_floor/diamond2c";
|
||||
TexturePath textureTrim = "textures/base_trim/yellow_rustbx128";
|
||||
|
||||
enum class Inner{
|
||||
none = 0,
|
||||
segmented = 1,
|
||||
sloped = 2,
|
||||
} innerType = Inner::none;
|
||||
TexturePath innerTextureMain = "textures/base_floor/clang_floor3blava";
|
||||
TexturePath innerTextureTrim = "textures/base_floor/achtung_clang";
|
||||
double innerDepth1 = 4;
|
||||
double innerDepth2 = 8;
|
||||
|
||||
bool slopedSegments = false;
|
||||
bool slopedSegmentsRoundize = false;
|
||||
double slopedDepth1 = 16;
|
||||
double slopedDepth2 = 8;
|
||||
|
||||
// sound is played in door entity origin
|
||||
// moving origin 1024u away makes it inaudible in Q3
|
||||
// note somehow setting origin via key or origin brush doesn't work; need to extend door body
|
||||
enum class Silence{
|
||||
none = 0,
|
||||
brush = 1,
|
||||
} silenceType = Silence::none;
|
||||
DoubleVector3 silenceBrushesOffset{ 4096, 0, 0 };
|
||||
|
||||
bool speedUse = false; //or time
|
||||
double time = 2;
|
||||
double speed = 40;
|
||||
|
||||
bool distanceSet = false; //or automatic from radius
|
||||
double distance = 256;
|
||||
double openAngle = 0;
|
||||
bool health = true;
|
||||
double wait = .25;
|
||||
};
|
||||
|
||||
struct PathPlotterRS {
|
||||
int nPoints;
|
||||
float fMultiplier;
|
||||
@@ -89,6 +155,7 @@ bool DoPolygonBox( PolygonRS* rs );
|
||||
EMessageBoxReturn DoResetTextureBox( ResetTextureRS* rs );
|
||||
bool DoBuildStairsBox( BuildStairsRS* rs );
|
||||
bool DoDoorsBox( DoorRS* rs );
|
||||
bool DoApertureDoorsBox( ApertureDoorRS* rs );
|
||||
EMessageBoxReturn DoPathPlotterBox( PathPlotterRS* rs );
|
||||
EMessageBoxReturn DoCTFColourChangeBox();
|
||||
bool DoMakeChainBox( MakeChainRS* rs );
|
||||
|
||||
@@ -382,6 +382,22 @@ void DoBuildDoors(){
|
||||
}
|
||||
}
|
||||
|
||||
void DoBuildApertureDoors(){
|
||||
// ensure we have something selected
|
||||
if ( !( GlobalSelectionSystem().countSelected() > 0 && Node_isBrush( GlobalSelectionSystem().ultimateSelected().path().top() ) ) ) {
|
||||
//DoMessageBox( "Invalid number of brushes selected, choose 1 only", "Error", EMessageBoxType::Error );
|
||||
globalErrorStream() << "bobToolz BuildDoors: Invalid number of brushes selected, choose 1 only.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
static ApertureDoorRS rs;
|
||||
// rs.textureMain = GetCurrentTexture();
|
||||
|
||||
if ( DoApertureDoorsBox( &rs ) ) {
|
||||
BuildApertureDoors( GlobalSelectionSystem().ultimateSelected(), rs );
|
||||
}
|
||||
}
|
||||
|
||||
void DoPathPlotter(){
|
||||
UndoableCommand undo( "bobToolz.pathPlotter" );
|
||||
PathPlotterRS rs;
|
||||
|
||||
@@ -57,6 +57,7 @@ void DoFixBrushes();
|
||||
void DoResetTextures();
|
||||
void DoBuildStairs();
|
||||
void DoBuildDoors();
|
||||
void DoBuildApertureDoors();
|
||||
void DoPathPlotter();
|
||||
void DoPitBuilder();
|
||||
void DoCTFColourChanger();
|
||||
|
||||
@@ -277,7 +277,7 @@ const scene::Path* FindEntityFromTargetname( const char* targetname ){
|
||||
return Scene_forEachEntity( EntityFindByKeyValue( "targetname", targetname ) ).result;
|
||||
}
|
||||
|
||||
void FillDefaultTexture( _QERFaceData* faceData, vec3_t va, vec3_t vb, vec3_t vc, const char* texture ){
|
||||
void FillDefaultTexture( _QERFaceData* faceData, const vec3_accu_t va, const vec3_accu_t vb, const vec3_accu_t vc, const char* texture ){
|
||||
faceData->m_texdef.rotate = 0;
|
||||
faceData->m_texdef.scale[0] = 0.5;
|
||||
faceData->m_texdef.scale[1] = 0.5;
|
||||
|
||||
@@ -32,7 +32,10 @@ vec_t Min( vec_t a, vec_t b );
|
||||
const char* GetCurrentTexture();
|
||||
|
||||
class _QERFaceData;
|
||||
void FillDefaultTexture( _QERFaceData* faceData, vec3_t va, vec3_t vb, vec3_t vc, const char* texture );
|
||||
void FillDefaultTexture( _QERFaceData* faceData, const vec3_accu_t va, const vec3_accu_t vb, const vec3_accu_t vc, const char* texture );
|
||||
inline void FillDefaultTexture( _QERFaceData* faceData, const vec3_t va, const vec3_t vb, const vec3_t vc, const char* texture ){
|
||||
FillDefaultTexture( faceData, vec3_accu_t{ va[0], va[1], va[2] }, vec3_accu_t{ vb[0], vb[1], vb[2] }, vec3_accu_t{ vc[0], vc[1], vc[2] }, texture );
|
||||
}
|
||||
|
||||
void BuildMiniPrt( const std::vector<CopiedString>& exclusionList );
|
||||
|
||||
|
||||
+329
-2
@@ -73,8 +73,8 @@ extern bool bFacesAll[];
|
||||
float Deg2Rad( float angle ){
|
||||
return (float)( angle * Q_PI / 180 );
|
||||
}
|
||||
|
||||
void AddFaceWithTexture( scene::Node& brush, vec3_t va, vec3_t vb, vec3_t vc, const char* texture, bool detail ){
|
||||
// points in CCW order
|
||||
void AddFaceWithTexture( scene::Node& brush, const vec3_accu_t va, const vec3_accu_t vb, const vec3_accu_t vc, const char* texture, bool detail ){
|
||||
_QERFaceData faceData;
|
||||
FillDefaultTexture( &faceData, va, vb, vc, texture );
|
||||
if ( detail ) {
|
||||
@@ -82,6 +82,9 @@ void AddFaceWithTexture( scene::Node& brush, vec3_t va, vec3_t vb, vec3_t vc, co
|
||||
}
|
||||
GlobalBrushCreator().Brush_addFace( brush, faceData );
|
||||
}
|
||||
void AddFaceWithTexture( scene::Node& brush, const vec3_t va, const vec3_t vb, const vec3_t vc, const char* texture, bool detail ){
|
||||
AddFaceWithTexture( brush, vec3_accu_t{ va[0], va[1], va[2] }, vec3_accu_t{ vb[0], vb[1], vb[2] }, vec3_accu_t{ vc[0], vc[1], vc[2] }, texture, detail );
|
||||
}
|
||||
|
||||
void AddFaceWithTextureScaled( scene::Node& brush, vec3_t va, vec3_t vb, vec3_t vc,
|
||||
const char* texture, bool bVertScale, bool bHorScale,
|
||||
@@ -587,6 +590,330 @@ void BuildDoorsX2( vec3_t min, vec3_t max,
|
||||
// ResetCurrentTexture();
|
||||
}
|
||||
|
||||
#include "DBrush.h"
|
||||
#include "dialogs/dialogs-gtk.h"
|
||||
|
||||
/*
|
||||
?snap tmp verts for aabb to .125 for lip accuracy (helps) (not relevant when finding out via radiant means)
|
||||
?option to actually snap verts //up to user now in from-winding gen method, except center
|
||||
arbitrary winding
|
||||
add sound cull brushes
|
||||
move dist control
|
||||
time setting
|
||||
?oval shape when generating
|
||||
prefer +face when similar area
|
||||
?explicit ccw setting or better
|
||||
inner layer filling orientation depending on move angle
|
||||
?round expansion in non round shape
|
||||
?way to adjust lip, speed of existing door
|
||||
?reset options on dialog 'cancel'
|
||||
*/
|
||||
|
||||
void BuildApertureDoors( scene::Instance& brushinstance, const class ApertureDoorRS& rs ){
|
||||
auto float2string = [string = std::array<char, 64>()]( float value ) mutable -> const char* {
|
||||
sprintf( string.data(), "%g", value );
|
||||
return string.data();
|
||||
};
|
||||
|
||||
DBrush dbrush;
|
||||
dbrush.LoadFromBrush( brushinstance, false );
|
||||
|
||||
const DPlane *bestplane = nullptr;
|
||||
DWinding bestwinding;
|
||||
{
|
||||
vec_t bestarea = 0;
|
||||
for( const DPlane *plane : dbrush.faceList )
|
||||
{
|
||||
DWinding w = plane->BaseWindingForPlane();
|
||||
for( const DPlane *cplane : dbrush.faceList )
|
||||
{
|
||||
DPlane negcplane( *cplane ); //negate plane to keep proper side
|
||||
VectorNegate( negcplane.normal, negcplane.normal );
|
||||
negcplane._d = -negcplane._d;
|
||||
if( plane != cplane )
|
||||
w.ChopWindingInPlace( &negcplane, .1f );
|
||||
if( w.numpoints < 3 )
|
||||
break;
|
||||
}
|
||||
if( const vec_t area = w.WindingArea(); area > bestarea + 1 //definitely better
|
||||
// in doubts prefer positive direction for selection consistency
|
||||
|| ( area > bestarea - 1 && VectorMax( plane->normal ) > VectorMax( bestplane->normal ) ) ){
|
||||
bestarea = area;
|
||||
bestplane = plane;
|
||||
bestwinding = std::move( w );
|
||||
}
|
||||
}
|
||||
}
|
||||
if( bestplane != nullptr ){
|
||||
UndoableCommand undo( "bobToolz.buildApertureDoor" );
|
||||
|
||||
vec3_t center;
|
||||
bestwinding.WindingCentroid( center );
|
||||
|
||||
double thickness = 32;
|
||||
{
|
||||
DPlane *negplane = nullptr;
|
||||
vec_t bestdot = 1;
|
||||
for( DPlane *plane : dbrush.faceList )
|
||||
{
|
||||
if( vec_t dot = DotProduct( bestplane->normal, plane->normal ); dot < bestdot ){
|
||||
bestdot = dot;
|
||||
negplane = plane;
|
||||
}
|
||||
}
|
||||
if( negplane != nullptr )
|
||||
thickness = -negplane->DistanceToPoint( center );
|
||||
}
|
||||
vec_t radius = std::numeric_limits<vec_t>::max();
|
||||
for( int i = bestwinding.numpoints - 1, j = 0; j < bestwinding.numpoints; i = j++ )
|
||||
{
|
||||
vec3_t r;
|
||||
VectorMid( bestwinding.p[i], bestwinding.p[j], r );
|
||||
VectorSubtract( r, center, r );
|
||||
radius = std::min( radius, VectorLength( r ) );
|
||||
}
|
||||
|
||||
{
|
||||
const DoubleVector3 normal( vector3_from_array( bestplane->normal ) );
|
||||
const DoubleVector3 rot = vector3_normalised( plane3_project_point( Plane3( normal, 0 ), // 0 distance to project vector
|
||||
g_vector3_axes[ vector3_min_abs_component_index( normal ) ],
|
||||
g_vector3_axes[ vector3_max_abs_component_index( normal ) ] ) ) * radius + vector3_from_array( center );
|
||||
|
||||
srand( (unsigned)time( NULL ) );
|
||||
char teamname[256];
|
||||
sprintf( teamname, "t%i", rand() );
|
||||
EntityClass* doorClass = GlobalEntityClassManager().findOrInsert( "func_door", true );
|
||||
|
||||
const DoubleVector3 cent = vector3_from_array( center );
|
||||
std::vector<DoubleVector3> winding;
|
||||
if( rs.fromWinding ){
|
||||
for( const auto& p : std::span( bestwinding.p, bestwinding.numpoints ) )
|
||||
winding.push_back( vector3_from_array( p ) );
|
||||
}
|
||||
else{
|
||||
for( int i = 0; i < rs.segments; ++i )
|
||||
{
|
||||
Matrix4 mat( g_matrix4_identity );
|
||||
// clockwise rotation; clockwise points order
|
||||
matrix4_pivoted_rotate_by_axisangle( mat, normal, -c_2pi / rs.segments * ( i + ( rs.offsetStartAngle? 0.5 : 0 ) ), cent );
|
||||
winding.push_back( matrix4_transformed_point( mat, rot ) );
|
||||
}
|
||||
}
|
||||
|
||||
const double edgespeed0 = rs.speedUse ? rs.speed : ( rs.distanceSet? rs.distance : radius ) / rs.time;
|
||||
double edgespeed = edgespeed0;
|
||||
|
||||
for( size_t i = 0; i < winding.size(); ++i )
|
||||
{
|
||||
const DoubleVector3 rot1 = winding[ i ];
|
||||
const DoubleVector3 rot2 = winding[( i + 1 ) % winding.size()];
|
||||
const DoubleVector3 cent_ = cent - normal * thickness;
|
||||
const DoubleVector3 rot1_ = rot1 - normal * thickness;
|
||||
const DoubleVector3 rot2_ = rot2 - normal * thickness;
|
||||
|
||||
|
||||
NodeSmartReference door( GlobalEntityCreator().createEntity( doorClass ) );
|
||||
|
||||
NodeSmartReference brush( GlobalBrushCreator().createBrush() );
|
||||
AABB bounds;
|
||||
|
||||
if( !rs.slopedSegments ){
|
||||
AddFaceWithTexture( brush, cent.data(), rot1.data(), rot2.data(), rs.textureMain.get(), false );
|
||||
AddFaceWithTexture( brush, cent_.data(), rot2_.data(), rot1_.data(), rs.textureMain.get(), false );
|
||||
AddFaceWithTexture( brush, cent.data(), cent_.data(), rot1_.data(), rs.textureTrim.get(), false );
|
||||
AddFaceWithTexture( brush, cent_.data(), cent.data(), rot2.data(), rs.textureTrim.get(), false );
|
||||
AddFaceWithTexture( brush, rot2.data(), rot1.data(), rot1_.data(), rs.textureTrim.get(), false );
|
||||
|
||||
for( const auto& p : { cent, cent_, rot1, rot1_, rot2, rot2_ } )
|
||||
aabb_extend_by_point_safe( bounds, p );
|
||||
|
||||
if( rs.innerType == ApertureDoorRS::Inner::segmented ){
|
||||
const double depth1 = std::clamp( rs.innerDepth1, 0.0, thickness / 2 - 1 );
|
||||
const double depth2 = std::clamp( rs.innerDepth2, 0.0, thickness / 2 - 1 );
|
||||
|
||||
size_t jend = 1;
|
||||
for( ; jend < winding.size(); ++jend ) //find out jend beforehand for depth step calc
|
||||
{
|
||||
const DoubleVector3 r1 = winding[( i + jend + 0 ) % winding.size()];
|
||||
if( plane3_distance_to_point( plane3_for_points( cent, cent_, rot1_ ), r1 ) > -1 )
|
||||
break;
|
||||
}
|
||||
|
||||
for( size_t j = 1; j < jend; ++j )
|
||||
{
|
||||
const DoubleVector3 extradepth = normal * ( depth1 + ( depth2 - depth1 ) / std::max( size_t( 1 ), jend - 2 ) * ( j - 1 ) );
|
||||
const DoubleVector3 c = cent - extradepth;
|
||||
const DoubleVector3 r1 = winding[( i + j + 0 ) % winding.size()] - extradepth;
|
||||
const DoubleVector3 r2 = winding[( i + j + 1 ) % winding.size()] - extradepth;
|
||||
|
||||
const DoubleVector3 c_ = cent_ + extradepth;
|
||||
const DoubleVector3 r1_ = r1 - normal * thickness + extradepth * 2;
|
||||
const DoubleVector3 r2_ = r2 - normal * thickness + extradepth * 2;
|
||||
|
||||
for( const auto& p : { c, c_, r1, r1_, r2, r2_ } )
|
||||
aabb_extend_by_point_safe( bounds, p );
|
||||
|
||||
NodeSmartReference b( GlobalBrushCreator().createBrush() );
|
||||
|
||||
AddFaceWithTexture( b, c.data(), r1.data(), r2.data(), rs.innerTextureMain.get(), false );
|
||||
AddFaceWithTexture( b, c_.data(), r2_.data(), r1_.data(), rs.innerTextureMain.get(), false );
|
||||
AddFaceWithTexture( b, c.data(), c_.data(), r1_.data(), rs.innerTextureTrim.get(), false );
|
||||
AddFaceWithTexture( b, c_.data(), c.data(), r2.data(), rs.innerTextureTrim.get(), false );
|
||||
AddFaceWithTexture( b, r2.data(), r1.data(), r1_.data(), rs.innerTextureTrim.get(), false );
|
||||
|
||||
Node_getTraversable( door )->insert( b );
|
||||
}
|
||||
}
|
||||
else if( rs.innerType == ApertureDoorRS::Inner::sloped ){
|
||||
NodeSmartReference b( GlobalBrushCreator().createBrush() );
|
||||
AddFaceWithTexture( b, cent.data(), cent_.data(), rot2.data(), rs.innerTextureTrim.get(), false );
|
||||
AddFaceWithTexture( b, cent.data(), cent_.data(), rot1_.data(), rs.innerTextureTrim.get(), false );
|
||||
double mindot = 2;
|
||||
DoubleVector3 r( 0 ); // suppress -Wmaybe-uninitialized
|
||||
for( size_t j = 1; j < winding.size(); ++j )
|
||||
{
|
||||
const DoubleVector3 r1 = winding[( i + j + 0 ) % winding.size()];
|
||||
const DoubleVector3 r2 = winding[( i + j + 1 ) % winding.size()];
|
||||
const DoubleVector3 r2_ = r2 + normal * 16;
|
||||
|
||||
if( plane3_distance_to_point( plane3_for_points( cent, cent_, rot1_ ), r1 ) > -1 )
|
||||
break;
|
||||
|
||||
AddFaceWithTexture( b, r1.data(), r2.data(), r2_.data(), rs.innerTextureTrim.get(), false );
|
||||
|
||||
if( double dot = fabs( vector3_dot( vector3_normalised( rot2 - cent ), vector3_normalised( r2 - cent ) ) ); dot < mindot ){
|
||||
mindot = dot;
|
||||
r = r2;
|
||||
}
|
||||
}
|
||||
const double depth1 = std::clamp( rs.innerDepth1, 0.0, thickness / 2 - 1 );
|
||||
const double depth2 = std::clamp( rs.innerDepth2, 0.0, thickness / 2 - 1 );
|
||||
AddFaceWithTexture( b, ( cent - normal * depth1 ).data(),
|
||||
( rot2 - normal * depth1 ).data(),
|
||||
( r - normal * depth2 ).data(), rs.innerTextureMain.get(), false );
|
||||
AddFaceWithTexture( b, ( rot2_ + normal * depth1 ).data(),
|
||||
( cent_ + normal * depth1 ).data(),
|
||||
( r - normal * ( thickness - depth2 ) ).data(), rs.innerTextureMain.get(), false );
|
||||
Node_getTraversable( door )->insert( b );
|
||||
bounds = AABB(); //invalidate to find out via radiant means
|
||||
}
|
||||
}
|
||||
else if( rs.slopedSegments ){ // bounds via radiant means
|
||||
const DoubleVector3 r1 = rot1 + normal * rs.slopedDepth1;
|
||||
const DoubleVector3 r2 = rot1 + vector3_cross( normal, cent - rot1 ) + normal * rs.slopedDepth2;
|
||||
const DoubleVector3 r4 = cent + ( cent - rot1 );
|
||||
const DoubleVector3 r3 = r4 + vector3_cross( normal, cent - rot1 );
|
||||
const DoubleVector3 r1_ = rot1_ - normal * rs.slopedDepth1;
|
||||
const DoubleVector3 r2_ = rot1_ + vector3_cross( normal, cent - rot1 ) - normal * rs.slopedDepth2;
|
||||
const DoubleVector3 r4_ = cent_ + ( cent - rot1 );
|
||||
const DoubleVector3 r3_ = r4_ + vector3_cross( normal, cent - rot1 );
|
||||
|
||||
AddFaceWithTexture( brush, r1.data(), r2.data(), r3.data(), rs.textureMain.get(), false );
|
||||
AddFaceWithTexture( brush, r2_.data(), r1_.data(), r3_.data(), rs.textureMain.get(), false );
|
||||
AddFaceWithTexture( brush, r1.data(), r4.data(), r4_.data(), rs.textureTrim.get(), false );
|
||||
|
||||
if( !rs.slopedSegmentsRoundize ){
|
||||
AddFaceWithTexture( brush, r2.data(), r1.data(), r1_.data(), rs.textureTrim.get(), false );
|
||||
AddFaceWithTexture( brush, r3.data(), r2.data(), r2_.data(), rs.textureTrim.get(), false );
|
||||
AddFaceWithTexture( brush, r4.data(), r3.data(), r3_.data(), rs.textureTrim.get(), false );
|
||||
}
|
||||
else{ // optional round outline
|
||||
for( size_t j = 0; j < winding.size(); ++j )
|
||||
{
|
||||
const DoubleVector3 r1 = winding[( i + j + 0 ) % winding.size()];
|
||||
const DoubleVector3 r2 = winding[( i + j + 1 ) % winding.size()];
|
||||
const DoubleVector3 r2_ = r2 + normal * 16;
|
||||
|
||||
if( j != 0 && plane3_distance_to_point( plane3_for_points( cent, cent_, rot1_ ), r1 ) > -1 )
|
||||
break;
|
||||
|
||||
AddFaceWithTexture( brush, r1.data(), r2.data(), r2_.data(), rs.textureTrim.get(), false );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rs.silenceType == ApertureDoorRS::Silence::brush ){
|
||||
const DoubleVector3 a = cent + rs.silenceBrushesOffset;
|
||||
const DoubleVector3 b = a + DoubleVector3( 0, 64, 0 );
|
||||
const DoubleVector3 c = a + DoubleVector3( 64, 64, 0 );
|
||||
const DoubleVector3 d = a + DoubleVector3( 64, 0, 0 );
|
||||
const DoubleVector3 a_ = a - DoubleVector3( 0, 0, 64 );
|
||||
const DoubleVector3 b_ = b - DoubleVector3( 0, 0, 64 );
|
||||
const DoubleVector3 c_ = c - DoubleVector3( 0, 0, 64 );
|
||||
const DoubleVector3 d_ = d - DoubleVector3( 0, 0, 64 );
|
||||
|
||||
NodeSmartReference brush( GlobalBrushCreator().createBrush() );
|
||||
AddFaceWithTexture( brush, a.data(), b.data(), c.data(), "textures/common/caulk", false );
|
||||
AddFaceWithTexture( brush, c_.data(), b_.data(), a_.data(), "textures/common/caulk", false );
|
||||
AddFaceWithTexture( brush, b.data(), a.data(), a_.data(), "textures/common/caulk", false );
|
||||
AddFaceWithTexture( brush, c.data(), b.data(), b_.data(), "textures/common/caulk", false );
|
||||
AddFaceWithTexture( brush, d.data(), c.data(), c_.data(), "textures/common/caulk", false );
|
||||
AddFaceWithTexture( brush, a.data(), d.data(), d_.data(), "textures/common/caulk", false );
|
||||
|
||||
Node_getTraversable( door )->insert( brush );
|
||||
|
||||
if( aabb_valid( bounds ) ) // add to bounds, if we construct bounds here
|
||||
for( const auto& p : { a, a_, b, b_, c, c_, d, d_ } )
|
||||
aabb_extend_by_point_safe( bounds, p );
|
||||
}
|
||||
|
||||
DoubleVector3 dir;
|
||||
{
|
||||
Matrix4 mat( g_matrix4_identity );
|
||||
matrix4_rotate_by_axisangle( mat, normal, degrees_to_radians( rs.openAngle ) );
|
||||
// "equal orthogonal speed of edges"
|
||||
// dir = vector3_normalised( matrix4_transformed_direction( mat, vector3_normalised( rot2 - cent ) - vector3_normalised( rot1 - cent ) ) );
|
||||
// winding shape dependent speeds
|
||||
dir = vector3_normalised( matrix4_transformed_direction( mat, rot2 - rot1 ) );
|
||||
|
||||
DoubleVector3 angles( -radians_to_degrees( asin( dir[2] ) ), //PITCH
|
||||
radians_to_degrees( atan2( dir[1], dir[0] ) ), //YAW
|
||||
0 );
|
||||
vector3_snap_to_zero( angles, 0.0001 ); // snap to avoid scientific notation
|
||||
|
||||
char value[64];
|
||||
sprintf( value, "%g %g %g", angles[0], angles[1], angles[2] );
|
||||
Node_getEntity( door )->setKeyValue( "angles", value );
|
||||
}
|
||||
Node_getEntity( door )->setKeyValue( "team", teamname );
|
||||
if( rs.health )
|
||||
Node_getEntity( door )->setKeyValue( "health", "1" );
|
||||
Node_getEntity( door )->setKeyValue( "wait", float2string( rs.wait ) );
|
||||
|
||||
Node_getTraversable( door )->insert( brush );
|
||||
Node_getTraversable( GlobalSceneGraph().root() )->insert( door );
|
||||
|
||||
if( !aabb_valid( bounds ) ){
|
||||
scene::Path path( makeReference( GlobalSceneGraph().root() ) );
|
||||
path.push( makeReference( door.get() ) );
|
||||
bounds = GlobalSceneGraph().find( path )->worldAABB();
|
||||
}
|
||||
{
|
||||
// calculate speeds to work with arbitrary windings as input, shape dependent expansion
|
||||
// is it possible to calculate directly w/o global 'edgespeed' value?
|
||||
// speed.len = edgespeed.len / speed.dot(edgespeed)
|
||||
// edgespeed2.len = speed.len * speed.dot(edgespeed2)
|
||||
const double
|
||||
speed = edgespeed / vector3_dot( vector3_normalised( rot2 - rot1 ), vector3_cross( vector3_normalised( rot1 - cent ), normal ) );
|
||||
edgespeed = speed * vector3_dot( vector3_normalised( rot2 - rot1 ), vector3_cross( vector3_normalised( rot2 - cent ), normal ) );
|
||||
// two versions for "equal orthogonal speed of edges" (also arbitrary windings)
|
||||
// const double speed = rs.speed / ( vector3_length( vector3_normalised( rot2 - cent ) + vector3_normalised( rot1 - cent ) ) / 2 );
|
||||
// const double speed = rs.speed / sin( ( c_pi - acos( vector3_dot( vector3_normalised( rot2 - cent ), vector3_normalised( rot1 - cent ) ) ) ) / 2 );
|
||||
Node_getEntity( door )->setKeyValue( "speed", float2string( speed ) );
|
||||
|
||||
const DoubleVector3 abs_dir( fabs( dir[0] ), fabs( dir[1] ), fabs( dir[2] ) );
|
||||
const auto fullDistance = vector3_dot( abs_dir, DoubleVector3( 2 ) + bounds.extents * 2 ); //there is +2 to bounds in engine somewhere 🧩
|
||||
// distance = DotProduct( abs_movedir, size ) - lip; // game code
|
||||
// VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 );
|
||||
// note: radius is too much for 3/4/5 segments, might want to limit value
|
||||
const float lip = fullDistance - ( rs.distanceSet? rs.distance : radius ) * ( edgespeed0 != 0 ? speed / edgespeed0 : 0 ); // 0 speed and distance inputs are allowed, handle
|
||||
Node_getEntity( door )->setKeyValue( "lip", float2string( lip ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -31,12 +31,13 @@
|
||||
namespace scene
|
||||
{
|
||||
class Node;
|
||||
class Instance;
|
||||
}
|
||||
|
||||
#include "mathlib.h"
|
||||
|
||||
// generic (detail added 12/01/01, for AC+)
|
||||
void AddFaceWithTexture( scene::Node& brush, vec3_t va, vec3_t vb, vec3_t vc, const char* texture, bool detail );
|
||||
void AddFaceWithTexture( scene::Node& brush, const vec3_t va, const vec3_t vb, const vec3_t vc, const char* texture, bool detail );
|
||||
|
||||
// -------------
|
||||
// ---caulked---
|
||||
@@ -47,6 +48,7 @@ void Build_Wedge( int dir, vec3_t min, vec3_t max, bool bUp );
|
||||
// ---textured---
|
||||
// --------------
|
||||
void BuildDoorsX2( vec3_t min, vec3_t max, bool bSclMainHor, bool bSclMainVert, bool bSclTrimHor, bool bSclTrimVert, const char* mainTexture, const char* trimTexture, int direction );
|
||||
void BuildApertureDoors( scene::Instance& brushinstance, const class ApertureDoorRS& rs );
|
||||
void Build_StairStep( vec3_t min, vec3_t max, const char* mainTexture, const char* riserTexture, int direction );
|
||||
void Build_StairStep_Wedge( int dir, vec3_t min, vec3_t max, const char* mainTexture, const char* riserTexture, bool detail );
|
||||
void BuildCornerStairs( vec3_t vMin, vec3_t vMax, int nSteps, const char* mainTexture, const char* riserTex );
|
||||
|
||||
@@ -1123,6 +1123,7 @@ misc...
|
||||
render current leaf convex volume, in white
|
||||
support bsp without vis data
|
||||
add RBSP, FBSP support
|
||||
* bobToolz: new Aperture Door builder
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user