Polylight Changes... return a new effect on all modifiers
This commit is contained in:
parent
8b5014d01e
commit
2ca64d594d
|
|
@ -27,115 +27,6 @@ INLINE PolylightEffect::
|
|||
PolylightEffect() {
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::is_enabled
|
||||
// Access: Published
|
||||
// Description: Returns state of the effect: enabled/disabled
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE bool PolylightEffect::
|
||||
is_enabled() const {
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::enable
|
||||
// Access: Published
|
||||
// Description: Enables the effect
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void PolylightEffect::
|
||||
enable() {
|
||||
_enabled=true;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::disable
|
||||
// Access: Published
|
||||
// Description: Disables the effect
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void PolylightEffect::
|
||||
disable() {
|
||||
_enabled=false;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::add_light
|
||||
// Access: Published
|
||||
// Description: Add a PolylightNode object to this effect and give it a
|
||||
// unique name. Returns true if light added
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE bool PolylightEffect::
|
||||
add_light(const string &lightname, const NodePath &newlight) {
|
||||
nassertr(newlight.node()->is_of_type(PolylightNode::get_class_type()) ,false);
|
||||
nassertr(newlight.node() != (PolylightNode *)NULL,false);
|
||||
|
||||
// Check if light name is unique
|
||||
if (_lightgroup.find(lightname) != _lightgroup.end()) {
|
||||
cerr << "Light name already exists. Please choose another name\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the light
|
||||
pair<string,NodePath> _light_to_add(lightname, newlight);
|
||||
_lightgroup.insert(_light_to_add);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::remove_light
|
||||
// Access: Published
|
||||
// Description: Remove a light from this effect. Returns true on
|
||||
// success.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE bool PolylightEffect::
|
||||
remove_light(const string &lightname) {
|
||||
|
||||
// Check if light name exists
|
||||
if (_lightgroup.find(lightname) == _lightgroup.end()) {
|
||||
cerr << "Light name does not exist\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove light
|
||||
_lightgroup.erase(lightname);
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::remove_all
|
||||
// Access: Published
|
||||
// Description: Remove all the lights from this effect. Returns
|
||||
// true on success.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE bool PolylightEffect::
|
||||
remove_all() {
|
||||
LIGHTGROUP::const_iterator light_iter;
|
||||
for (light_iter = _lightgroup.begin(); light_iter != _lightgroup.end(); light_iter++){
|
||||
string lightname = light_iter->first;
|
||||
_lightgroup.erase(lightname);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::set_weight
|
||||
// Access: Published
|
||||
// Description: weight is a constant you add (generally 1 and above)
|
||||
// to make the colorscale brighten the existing color
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE bool PolylightEffect::
|
||||
set_weight(float w) {
|
||||
// nassertr(w >= 0.0 && w <= 1.0 ,false);
|
||||
_weight = w;
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::get_weight
|
||||
// Access: Published
|
||||
|
|
@ -146,26 +37,6 @@ get_weight() const {
|
|||
return _weight;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::set_contrib
|
||||
// Access: Published
|
||||
// Description: Set the effect contribution type
|
||||
// The effects of all the light colors are finally
|
||||
// divided by the number of lights. The variable
|
||||
// _contribution_type is a string that controls how
|
||||
// this division occurs.
|
||||
// "proximal" : A light only contributes if the node
|
||||
// is inside its volume
|
||||
// "all" : All lights added to the effect are used in
|
||||
// division irrespective of their light volumes
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE bool PolylightEffect::
|
||||
set_contrib(PolylightEffect::Contrib_Type type) {
|
||||
nassertr(type == CALL || type == CPROXIMAL,false);
|
||||
_contribution_type = type;
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::get_contrib
|
||||
// Access: Published
|
||||
|
|
@ -176,18 +47,6 @@ get_contrib() const {
|
|||
return _contribution_type;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::set_effect_center
|
||||
// Access: Published
|
||||
// Description: Set the center point of the effect...generally 0,0,0
|
||||
// and hence the pivot point of the model
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void PolylightEffect::
|
||||
set_effect_center(LPoint3f effect_center) {
|
||||
_effect_center = effect_center;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::get_effect_center
|
||||
// Access: Published
|
||||
|
|
|
|||
|
|
@ -36,13 +36,40 @@ TypeHandle PolylightEffect::_type_handle;
|
|||
CPT(RenderEffect) PolylightEffect::
|
||||
make() {
|
||||
PolylightEffect *effect = new PolylightEffect;
|
||||
effect->enable();
|
||||
effect->set_contrib(CPROXIMAL);
|
||||
effect->set_weight(0.9);
|
||||
effect->_contribution_type = CPROXIMAL;
|
||||
effect->_weight = 0.9;
|
||||
effect->_effect_center = LPoint3f(0.0,0.0,0.0);
|
||||
return return_new(effect);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::make
|
||||
// Access: Published, Static
|
||||
// Description: Constructs a new PolylightEffect object.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
CPT(RenderEffect) PolylightEffect::
|
||||
make(float weight, Contrib_Type contrib, LPoint3f effect_center) {
|
||||
PolylightEffect *effect = new PolylightEffect;
|
||||
effect->_contribution_type = contrib;
|
||||
effect->_weight = weight;
|
||||
effect->_effect_center = effect_center;
|
||||
return return_new(effect);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::make
|
||||
// Access: Published, Static
|
||||
// Description: Constructs a new PolylightEffect object.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
CPT(RenderEffect) PolylightEffect::
|
||||
make(float weight, Contrib_Type contrib, LPoint3f effect_center, LIGHTGROUP lights) {
|
||||
PolylightEffect *effect = new PolylightEffect;
|
||||
effect->_contribution_type = contrib;
|
||||
effect->_weight = weight;
|
||||
effect->_effect_center = effect_center;
|
||||
effect->_lightgroup = lights;
|
||||
return return_new(effect);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::has_cull_callback
|
||||
|
|
@ -55,7 +82,7 @@ make() {
|
|||
////////////////////////////////////////////////////////////////////
|
||||
bool PolylightEffect::
|
||||
has_cull_callback() const {
|
||||
return is_enabled();
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -79,11 +106,9 @@ void PolylightEffect::
|
|||
cull_callback(CullTraverser *, CullTraverserData &data,
|
||||
CPT(TransformState) &node_transform,
|
||||
CPT(RenderState) &node_state) const {
|
||||
if (is_enabled()) {
|
||||
CPT(RenderAttrib) poly_light_attrib = do_poly_light(&data, node_transform);
|
||||
CPT(RenderState) poly_light_state = RenderState::make(poly_light_attrib);
|
||||
node_state = node_state->compose(poly_light_state);
|
||||
}
|
||||
CPT(RenderAttrib) poly_light_attrib = do_poly_light(&data, node_transform);
|
||||
CPT(RenderState) poly_light_state = RenderState::make(poly_light_attrib);
|
||||
node_state = node_state->compose(poly_light_state);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -104,82 +129,78 @@ do_poly_light(const CullTraverserData *data, const TransformState *node_transfor
|
|||
r = 1.0;
|
||||
g = 1.0;
|
||||
b = 1.0;
|
||||
if (is_enabled()) {
|
||||
LIGHTGROUP::const_iterator light_iter;
|
||||
// Cycle through all the lights in this effect's lightgroup
|
||||
for (light_iter = _lightgroup.begin(); light_iter != _lightgroup.end(); light_iter++){
|
||||
const PolylightNode *light = DCAST(PolylightNode,light_iter->second.node());
|
||||
// light holds the current PolylightNode
|
||||
if (light->is_enabled()) { // if enabled get all the properties
|
||||
float light_radius = light->get_radius();
|
||||
PolylightNode::Attenuation_Type light_attenuation = light->get_attenuation();
|
||||
float light_a0 = light->get_a0();
|
||||
float light_a1 = light->get_a1();
|
||||
float light_a2 = light->get_a2();
|
||||
if (light_a0 == 0 && light_a1 == 0 && light_a2 == 0) { // To prevent division by zero
|
||||
light_a0 = 1.0;
|
||||
}
|
||||
Colorf light_color;
|
||||
if (light->is_flickering()) { // If flickering, modify color
|
||||
light_color = light->flicker();
|
||||
} else {
|
||||
light_color = light->get_color_scenegraph();
|
||||
}
|
||||
// Calculate the distance of the node from the light
|
||||
//dist = light_iter->second->get_distance(data->_node_path.get_node_path());
|
||||
const NodePath lightnp = light_iter->second;
|
||||
LPoint3f point = data->_node_path.get_node_path().get_relative_point(lightnp,
|
||||
light->get_pos());
|
||||
dist = (point - _effect_center).length();
|
||||
LIGHTGROUP::const_iterator light_iter;
|
||||
// Cycle through all the lights in this effect's lightgroup
|
||||
for (light_iter = _lightgroup.begin(); light_iter != _lightgroup.end(); light_iter++){
|
||||
const PolylightNode *light = DCAST(PolylightNode, (*light_iter).node());
|
||||
// light holds the current PolylightNode
|
||||
if (light->is_enabled()) { // if enabled get all the properties
|
||||
float light_radius = light->get_radius();
|
||||
PolylightNode::Attenuation_Type light_attenuation = light->get_attenuation();
|
||||
float light_a0 = light->get_a0();
|
||||
float light_a1 = light->get_a1();
|
||||
float light_a2 = light->get_a2();
|
||||
if (light_a0 == 0 && light_a1 == 0 && light_a2 == 0) { // To prevent division by zero
|
||||
light_a0 = 1.0;
|
||||
}
|
||||
Colorf light_color;
|
||||
if (light->is_flickering()) { // If flickering, modify color
|
||||
light_color = light->flicker();
|
||||
} else {
|
||||
light_color = light->get_color_scenegraph();
|
||||
}
|
||||
// Calculate the distance of the node from the light
|
||||
//dist = light_iter->second->get_distance(data->_node_path.get_node_path());
|
||||
const NodePath lightnp = *light_iter;
|
||||
LPoint3f point = data->_node_path.get_node_path().get_relative_point(lightnp,
|
||||
light->get_pos());
|
||||
dist = (point - _effect_center).length();
|
||||
if (dist < light_radius) { // If node is in range of this light
|
||||
if (light_attenuation == PolylightNode::ALINEAR) {
|
||||
light_scale = (light_radius - dist)/light_radius;
|
||||
} else if (light_attenuation == PolylightNode::AQUADRATIC) {
|
||||
fd = 1.0 / (light_a0 + light_a1 * dist + light_a2 * dist * dist);
|
||||
if (fd < 1.0) {
|
||||
light_scale = fd;
|
||||
} else {
|
||||
light_scale = 1.0;
|
||||
}
|
||||
} else {
|
||||
light_scale = 1.0;
|
||||
}
|
||||
// Keep accumulating each lights contribution... we divide by
|
||||
// number of lights later.
|
||||
Rcollect += light_color[0] * light_scale;
|
||||
Gcollect += light_color[1] * light_scale;
|
||||
Bcollect += light_color[2] * light_scale;
|
||||
num_lights++;
|
||||
} // if dist< radius
|
||||
} // if light is enabled
|
||||
} // for all lights
|
||||
|
||||
if (dist < light_radius) { // If node is in range of this light
|
||||
if (light_attenuation == PolylightNode::ALINEAR) {
|
||||
light_scale = (light_radius - dist)/light_radius;
|
||||
} else if (light_attenuation == PolylightNode::AQUADRATIC) {
|
||||
fd = 1.0 / (light_a0 + light_a1 * dist + light_a2 * dist * dist);
|
||||
if (fd < 1.0) {
|
||||
light_scale = fd;
|
||||
} else {
|
||||
light_scale = 1.0;
|
||||
}
|
||||
} else {
|
||||
light_scale = 1.0;
|
||||
}
|
||||
// Keep accumulating each lights contribution... we divide by
|
||||
// number of lights later.
|
||||
Rcollect += light_color[0] * light_scale;
|
||||
Gcollect += light_color[1] * light_scale;
|
||||
Bcollect += light_color[2] * light_scale;
|
||||
num_lights++;
|
||||
} // if dist< radius
|
||||
} // if light is enabled
|
||||
} // for all lights
|
||||
|
||||
|
||||
if ( _contribution_type == CALL) {
|
||||
// Sometimes to prevent snapping of color at light volume boundaries
|
||||
// just divide total contribution by all the lights in the effect
|
||||
// whether or not they contribute color
|
||||
num_lights = _lightgroup.size();
|
||||
}
|
||||
|
||||
if (num_lights == 0) {
|
||||
no_lights_closeby = true;
|
||||
num_lights = 1;
|
||||
}
|
||||
Rcollect /= num_lights;
|
||||
Gcollect /= num_lights;
|
||||
Bcollect /= num_lights;
|
||||
|
||||
if (!no_lights_closeby) {
|
||||
//r = 1.0 + ((1.0 - _weight) + Rcollect * _weight);
|
||||
//g = 1.0 + ((1.0 - _weight) + Gcollect * _weight);
|
||||
//b = 1.0 + ((1.0 - _weight) + Bcollect * _weight);
|
||||
r = _weight + Rcollect;
|
||||
g = _weight + Gcollect;
|
||||
b = _weight + Bcollect;
|
||||
}
|
||||
if ( _contribution_type == CALL) {
|
||||
// Sometimes to prevent snapping of color at light volume boundaries
|
||||
// just divide total contribution by all the lights in the effect
|
||||
// whether or not they contribute color
|
||||
num_lights = _lightgroup.size();
|
||||
}
|
||||
if (num_lights == 0) {
|
||||
no_lights_closeby = true;
|
||||
num_lights = 1;
|
||||
}
|
||||
Rcollect /= num_lights;
|
||||
Gcollect /= num_lights;
|
||||
Bcollect /= num_lights;
|
||||
if (!no_lights_closeby) {
|
||||
//r = 1.0 + ((1.0 - _weight) + Rcollect * _weight);
|
||||
//g = 1.0 + ((1.0 - _weight) + Gcollect * _weight);
|
||||
//b = 1.0 + ((1.0 - _weight) + Bcollect * _weight);
|
||||
r = _weight + Rcollect;
|
||||
g = _weight + Gcollect;
|
||||
b = _weight + Bcollect;
|
||||
}
|
||||
|
||||
return ColorScaleAttrib::make(LVecBase4f(r, g, b, 1.0));
|
||||
}
|
||||
|
||||
|
|
@ -204,10 +225,6 @@ compare_to_impl(const RenderEffect *other) const {
|
|||
const PolylightEffect *ta;
|
||||
DCAST_INTO_R(ta, other, 0);
|
||||
|
||||
if (_enabled != ta->_enabled) {
|
||||
return _enabled ? 1 : -1;
|
||||
}
|
||||
|
||||
if (_contribution_type != ta->_contribution_type) {
|
||||
return _contribution_type < ta->_contribution_type ? -1 : 1;
|
||||
}
|
||||
|
|
@ -223,3 +240,86 @@ compare_to_impl(const RenderEffect *other) const {
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::add_light
|
||||
// Access: Published
|
||||
// Description: Add a PolylightNode object to this effect and return
|
||||
// a new effect
|
||||
////////////////////////////////////////////////////////////////////
|
||||
CPT(RenderEffect) PolylightEffect::
|
||||
add_light(const NodePath &newlight) {
|
||||
_lightgroup.push_back(newlight);
|
||||
return make(_weight,_contribution_type, _effect_center,
|
||||
_lightgroup);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::remove_light
|
||||
// Access: Published
|
||||
// Description: Remove a light from this effect. Return the new updated
|
||||
// effect
|
||||
////////////////////////////////////////////////////////////////////
|
||||
CPT(RenderEffect) PolylightEffect::
|
||||
remove_light(const NodePath &newlight) {
|
||||
|
||||
LIGHTGROUP::iterator light_iter;
|
||||
light_iter = find(_lightgroup.begin(),_lightgroup.end(), newlight);
|
||||
if(light_iter == _lightgroup.end()) {
|
||||
cerr << "Light Not Found!\n";
|
||||
} else {
|
||||
// Remove light
|
||||
_lightgroup.erase(light_iter);
|
||||
}
|
||||
return make(_weight, _contribution_type, _effect_center,
|
||||
_lightgroup);
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::set_weight
|
||||
// Access: Published
|
||||
// Description: Set weight and return a new effect... the reason
|
||||
// this couldnt be done through make was because
|
||||
// that would return a new effect without the
|
||||
// lightgroup which is static and cant be accessed
|
||||
// Here, we just pass that to the make
|
||||
////////////////////////////////////////////////////////////////////
|
||||
CPT(RenderEffect) PolylightEffect::
|
||||
set_weight(float w) {
|
||||
return make(w,_contribution_type, _effect_center,
|
||||
_lightgroup);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::set_contrib
|
||||
// Access: Published
|
||||
// Description: Set Contrib Type and return a new effect... the reason
|
||||
// this couldnt be done through make was because
|
||||
// that would return a new effect without the
|
||||
// lightgroup which is static and cant be accessed
|
||||
// Here, we just pass that to the make
|
||||
////////////////////////////////////////////////////////////////////
|
||||
CPT(RenderEffect) PolylightEffect::
|
||||
set_contrib(Contrib_Type ct) {
|
||||
return make(_weight, ct, _effect_center,
|
||||
_lightgroup);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: PolylightEffect::set_effect_center
|
||||
// Access: Published
|
||||
// Description: Set weight and return a new effect... the reason
|
||||
// this couldnt be done through make was because
|
||||
// that would return a new effect without the
|
||||
// lightgroup which is static and cant be accessed
|
||||
// Here, we just pass that to the make
|
||||
////////////////////////////////////////////////////////////////////
|
||||
CPT(RenderEffect) PolylightEffect::
|
||||
set_effect_center(LPoint3f ec) {
|
||||
return make(_weight,_contribution_type, ec,
|
||||
_lightgroup);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,15 +34,13 @@
|
|||
// Class : PolylightEffect
|
||||
// Description : A PolylightEffect can be used on a node to define a
|
||||
// LightGroup for that node. A LightGroup contains
|
||||
// Polylights which are essentially nodes that add
|
||||
// PolylightNodes which are essentially nodes that add
|
||||
// color to the polygons of a model based on distance.
|
||||
// PolylightNode is a cheap way to get lighting effects
|
||||
// specially for night scenes
|
||||
////////////////////////////////////////////////////////////////////
|
||||
class EXPCL_PANDA PolylightEffect : public RenderEffect {
|
||||
private:
|
||||
INLINE PolylightEffect();
|
||||
|
||||
|
||||
|
||||
PUBLISHED:
|
||||
enum Contrib_Type {
|
||||
|
|
@ -50,18 +48,25 @@ PUBLISHED:
|
|||
CALL,
|
||||
};
|
||||
|
||||
private:
|
||||
INLINE PolylightEffect();
|
||||
Contrib_Type _contribution_type;
|
||||
float _weight;
|
||||
typedef pvector< NodePath > LIGHTGROUP;
|
||||
LIGHTGROUP _lightgroup;
|
||||
LPoint3f _effect_center;
|
||||
|
||||
PUBLISHED:
|
||||
static CPT(RenderEffect) make();
|
||||
INLINE void enable();
|
||||
INLINE void disable();
|
||||
INLINE bool add_light(const string &lightname, const NodePath &newlight);
|
||||
INLINE bool remove_light(const string &lightname);
|
||||
INLINE bool remove_all();
|
||||
INLINE bool set_weight(float w);
|
||||
static CPT(RenderEffect) make(float weight, Contrib_Type contrib, LPoint3f effect_center);
|
||||
static CPT(RenderEffect) make(float weight, Contrib_Type contrib, LPoint3f effect_center, LIGHTGROUP lights);
|
||||
CPT(RenderEffect) add_light(const NodePath &newlight);
|
||||
CPT(RenderEffect) remove_light(const NodePath &newlight);
|
||||
CPT(RenderEffect) set_weight(float w);
|
||||
CPT(RenderEffect) set_contrib(Contrib_Type c);
|
||||
CPT(RenderEffect) set_effect_center(LPoint3f ec);
|
||||
INLINE float get_weight() const;
|
||||
INLINE bool set_contrib(Contrib_Type type);
|
||||
INLINE Contrib_Type get_contrib() const;
|
||||
INLINE bool is_enabled()const;
|
||||
INLINE void set_effect_center(LPoint3f effect_center);
|
||||
INLINE LPoint3f get_effect_center()const;
|
||||
|
||||
public:
|
||||
|
|
@ -75,15 +80,6 @@ public:
|
|||
protected:
|
||||
virtual int compare_to_impl(const RenderEffect *other) const;
|
||||
|
||||
private:
|
||||
bool _enabled;
|
||||
Contrib_Type _contribution_type;
|
||||
float _weight;
|
||||
typedef pmap<string, NodePath> LIGHTGROUP;
|
||||
LIGHTGROUP _lightgroup;
|
||||
LPoint3f _effect_center;
|
||||
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
|
|
|
|||
Loading…
Reference in New Issue