* bobToolz: new Aperture Door builder

This commit is contained in:
Garux
2025-07-12 15:27:35 +05:00
parent 05f3f66be7
commit 5f777691fc
12 changed files with 705 additions and 5 deletions
+18
View File
@@ -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;
+1
View File
@@ -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();
+4
View File
@@ -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();
}
+260
View File
@@ -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" );
+67
View File
@@ -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 );
+16
View File
@@ -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;
+1
View File
@@ -57,6 +57,7 @@ void DoFixBrushes();
void DoResetTextures();
void DoBuildStairs();
void DoBuildDoors();
void DoBuildApertureDoors();
void DoPathPlotter();
void DoPitBuilder();
void DoCTFColourChanger();
+1 -1
View File
@@ -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;
+4 -1
View File
@@ -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
View File
@@ -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 ) );
}
}
}
}
}
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
+3 -1
View File
@@ -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 );
+1
View File
@@ -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