Compare commits
19 Commits
spacecruft
...
sensor-dev
Author | SHA1 | Date |
---|---|---|
Chris Laurel | 47a792e83c | |
Chris Laurel | 07f2c2bf30 | |
Chris Laurel | 7653e220ce | |
Chris Laurel | 3d3e3372bc | |
Chris Laurel | 0cc013f1e8 | |
Chris Laurel | 4b0350b9e3 | |
Chris Laurel | 7512d65311 | |
Chris Laurel | 3617dd6e94 | |
Chris Laurel | d2d7ab8181 | |
Chris Laurel | edbcec6ff8 | |
Chris Laurel | 0dabce8884 | |
Chris Laurel | 6139ea9b4d | |
Chris Laurel | b286ad7274 | |
Chris Laurel | bff08a31dc | |
Chris Laurel | 9d8a301551 | |
Chris Laurel | 9840a67473 | |
Chris Laurel | 9d35943b50 | |
Chris Laurel | 3616bdf92d | |
Chris Laurel | 56d3466bc1 |
|
@ -111,6 +111,7 @@ static const LeapSecondRecord LeapSeconds[] =
|
||||||
{ 32, 2451179.5 }, // 1 Jan 1999
|
{ 32, 2451179.5 }, // 1 Jan 1999
|
||||||
{ 33, 2453736.5 }, // 1 Jan 2006
|
{ 33, 2453736.5 }, // 1 Jan 2006
|
||||||
{ 34, 2454832.5 }, // 1 Jan 2009
|
{ 34, 2454832.5 }, // 1 Jan 2009
|
||||||
|
{ 35, 2456109.5 }, // 1 Jan 2012
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ Body::Body(PlanetarySystem* _system, const string& _name) :
|
||||||
mass(0.0f),
|
mass(0.0f),
|
||||||
albedo(0.5f),
|
albedo(0.5f),
|
||||||
geometryOrientation(Quaternionf::Identity()),
|
geometryOrientation(Quaternionf::Identity()),
|
||||||
|
cullingRadius(0.0f),
|
||||||
geometry(InvalidResource),
|
geometry(InvalidResource),
|
||||||
geometryScale(1.0f),
|
geometryScale(1.0f),
|
||||||
surface(Color(1.0f, 1.0f, 1.0f)),
|
surface(Color(1.0f, 1.0f, 1.0f)),
|
||||||
|
|
|
@ -53,6 +53,39 @@ public:
|
||||||
virtual void loadTextures()
|
virtual void loadTextures()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Return true if the geometry can be drawn multiple times in
|
||||||
|
* different depth ranges to avoid clipping and precision problems.
|
||||||
|
* Drawing multiple times is expensive, so this method should only
|
||||||
|
* return true for geometries that are simple and which need to
|
||||||
|
* be drawn correctly when the camera is positioned very close
|
||||||
|
* (relative to the size of the object.)
|
||||||
|
*/
|
||||||
|
virtual bool isMultidraw() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the visibility flag for the named component of this geometry.
|
||||||
|
* Subclasses of Geometry should customize this method if they have
|
||||||
|
* components with visibility that can be controlled independently.
|
||||||
|
* The default implementation does nothing.
|
||||||
|
*/
|
||||||
|
virtual void setPartVisible(const std::string& partName, bool visible)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check the visibility flag for the named component of this geometry.
|
||||||
|
* Subclasses of Geometry should customize this method if they have
|
||||||
|
* components with visibility that can be controlled independently.
|
||||||
|
* The default implementation always returns false. Implementations
|
||||||
|
* of isPartVisible by subclasses should also return false for
|
||||||
|
* non-existent parts.
|
||||||
|
*/
|
||||||
|
virtual bool isPartVisible(const std::string& partName) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _CELENGINE_GEOMETRY_H_
|
#endif // _CELENGINE_GEOMETRY_H_
|
||||||
|
|
|
@ -123,6 +123,7 @@ ModelGeometry::render(RenderContext& rc, double /* t */)
|
||||||
mesh->getVertexCount() * vertexDesc.stride,
|
mesh->getVertexCount() * vertexDesc.stride,
|
||||||
mesh->getVertexData(),
|
mesh->getVertexData(),
|
||||||
GL_STATIC_DRAW_ARB);
|
GL_STATIC_DRAW_ARB);
|
||||||
|
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2848,6 +2848,7 @@ void Renderer::draw(const Observer& observer,
|
||||||
// large enough to have discernible surface detail are also placed in
|
// large enough to have discernible surface detail are also placed in
|
||||||
// renderList.
|
// renderList.
|
||||||
renderList.clear();
|
renderList.clear();
|
||||||
|
multidrawRenderList.clear();
|
||||||
orbitPathList.clear();
|
orbitPathList.clear();
|
||||||
lightSourceList.clear();
|
lightSourceList.clear();
|
||||||
secondaryIlluminators.clear();
|
secondaryIlluminators.clear();
|
||||||
|
@ -3464,16 +3465,43 @@ void Renderer::draw(const Observer& observer,
|
||||||
nearZ = -nearZ * (float) cos(degToRad(fov / 2)) *
|
nearZ = -nearZ * (float) cos(degToRad(fov / 2)) *
|
||||||
((float) windowHeight / maxSpan);
|
((float) windowHeight / maxSpan);
|
||||||
#endif
|
#endif
|
||||||
if (nearZ > -MinNearPlaneDistance)
|
// Handle multidraw items; these are placed into a separate list. Items in the multidraw
|
||||||
iter->nearZ = -max(MinNearPlaneDistance, radius / 2000.0f);
|
// list may be drawn in multiple depth intervals.
|
||||||
|
bool isMultidraw = false;
|
||||||
|
if (iter->renderableType == RenderListEntry::RenderableBody && iter->body->getGeometry() != InvalidResource)
|
||||||
|
{
|
||||||
|
Geometry* geom = GetGeometryManager()->find(iter->body->getGeometry());
|
||||||
|
if (geom && geom->isMultidraw())
|
||||||
|
{
|
||||||
|
isMultidraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMultidraw)
|
||||||
|
{
|
||||||
|
iter->nearZ = min(-MinNearPlaneDistance, nearZ);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
iter->nearZ = nearZ;
|
{
|
||||||
|
if (nearZ > -MinNearPlaneDistance)
|
||||||
|
{
|
||||||
|
iter->nearZ = -max(MinNearPlaneDistance, radius / 2000.0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iter->nearZ = nearZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!convex)
|
if (!convex)
|
||||||
{
|
{
|
||||||
iter->farZ = center.z() - radius;
|
iter->farZ = center.z() - radius;
|
||||||
if (iter->farZ / iter->nearZ > MaxFarNearRatio * 0.5f)
|
|
||||||
iter->nearZ = iter->farZ / (MaxFarNearRatio * 0.5f);
|
if (!isMultidraw)
|
||||||
|
{
|
||||||
|
if (iter->farZ / iter->nearZ > MaxFarNearRatio * 0.5f)
|
||||||
|
iter->nearZ = iter->farZ / (MaxFarNearRatio * 0.5f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -3509,8 +3537,15 @@ void Renderer::draw(const Observer& observer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*notCulled = *iter;
|
if (isMultidraw)
|
||||||
notCulled++;
|
{
|
||||||
|
multidrawRenderList.push_back(*iter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*notCulled = *iter;
|
||||||
|
notCulled++;
|
||||||
|
}
|
||||||
#ifdef USE_HDR
|
#ifdef USE_HDR
|
||||||
if (iter->discSizeInPixels > 1.0f &&
|
if (iter->discSizeInPixels > 1.0f &&
|
||||||
iter->appMag < starMaxMag)
|
iter->appMag < starMaxMag)
|
||||||
|
@ -3601,6 +3636,14 @@ void Renderer::draw(const Observer& observer,
|
||||||
// overlapping objects. If two objects overlap in depth, we must
|
// overlapping objects. If two objects overlap in depth, we must
|
||||||
// assign them to the same interval.
|
// assign them to the same interval.
|
||||||
|
|
||||||
|
// The code here is more complicated than it should be because there are
|
||||||
|
// three separate sources for renderable objects:
|
||||||
|
// - the main render list (renderList)
|
||||||
|
// - the list of multidraw items (multidrawRenderList)
|
||||||
|
// - the list of orbits
|
||||||
|
// It would be good reduce the complexity by merging the orbit list into
|
||||||
|
// the multidraw list, but this would require significant refactoring of
|
||||||
|
// the renderer code.
|
||||||
depthPartitions.clear();
|
depthPartitions.clear();
|
||||||
int nIntervals = 0;
|
int nIntervals = 0;
|
||||||
float prevNear = -1e12f; // ~ 1 light year
|
float prevNear = -1e12f; // ~ 1 light year
|
||||||
|
@ -3665,7 +3708,21 @@ void Renderer::draw(const Observer& observer,
|
||||||
if (minNearDistance > zNearest)
|
if (minNearDistance > zNearest)
|
||||||
zNearest = minNearDistance;
|
zNearest = minNearDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scan the list of multidraw items and find the closest one. We'll need
|
||||||
|
// adjust the nearest interval to accommodate it.
|
||||||
|
float zFurthest = 0.0;
|
||||||
|
for (i = 0; i < (int) multidrawRenderList.size(); i++)
|
||||||
|
{
|
||||||
|
const RenderListEntry& rle = multidrawRenderList[i];
|
||||||
|
float minNearDistance = min(-MinNearPlaneDistance, rle.nearZ);
|
||||||
|
|
||||||
|
if (minNearDistance > zNearest)
|
||||||
|
zNearest = minNearDistance;
|
||||||
|
if (rle.farZ < zFurthest)
|
||||||
|
zFurthest = rle.farZ;
|
||||||
|
}
|
||||||
|
|
||||||
// Adjust the nearest interval to include the closest marker (if it's
|
// Adjust the nearest interval to include the closest marker (if it's
|
||||||
// closer to the observer than anything else
|
// closer to the observer than anything else
|
||||||
if (!depthSortedAnnotations.empty())
|
if (!depthSortedAnnotations.empty())
|
||||||
|
@ -3689,8 +3746,10 @@ void Renderer::draw(const Observer& observer,
|
||||||
if (zNearest == prevNear)
|
if (zNearest == prevNear)
|
||||||
zNearest = 0.0f;
|
zNearest = 0.0f;
|
||||||
|
|
||||||
// Add one last interval for the span from 0 to the front of the
|
// Add intervals for the span from 0 to the front of the
|
||||||
// nearest object
|
// nearest object. Keep the far/near ratios of the intervals
|
||||||
|
// reasonable so that we don't end up with depth buffer precision
|
||||||
|
// problems when rendering multidraw items.
|
||||||
{
|
{
|
||||||
// TODO: closest object may not be at entry 0, since objects are
|
// TODO: closest object may not be at entry 0, since objects are
|
||||||
// sorted by far distance.
|
// sorted by far distance.
|
||||||
|
@ -3707,17 +3766,30 @@ void Renderer::draw(const Observer& observer,
|
||||||
closest = renderList[0].nearZ * 0.01f;
|
closest = renderList[0].nearZ * 0.01f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
closest = min(closest, -MinNearPlaneDistance);
|
||||||
|
|
||||||
DepthBufferPartition partition;
|
while (prevNear < closest)
|
||||||
partition.index = nIntervals;
|
{
|
||||||
partition.nearZ = closest;
|
float n = std::min(closest, prevNear / 2000.0f);
|
||||||
partition.farZ = prevNear;
|
|
||||||
depthPartitions.push_back(partition);
|
|
||||||
|
|
||||||
nIntervals++;
|
// Prevent the creation of extremely small depth intervals
|
||||||
|
if (-n / MinNearPlaneDistance < 1.1f)
|
||||||
|
{
|
||||||
|
n = -MinNearPlaneDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
DepthBufferPartition partition;
|
||||||
|
partition.index = nIntervals;
|
||||||
|
partition.nearZ = n;
|
||||||
|
partition.farZ = prevNear;
|
||||||
|
depthPartitions.push_back(partition);
|
||||||
|
nIntervals++;
|
||||||
|
|
||||||
|
prevNear = n;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If orbits are enabled, adjust the farthest partition so that it
|
// If orbits are enabled, adjust the farthest interval so that it
|
||||||
// can contain the orbit.
|
// can contain the orbit.
|
||||||
if (!orbitPathList.empty())
|
if (!orbitPathList.empty())
|
||||||
{
|
{
|
||||||
|
@ -3726,6 +3798,13 @@ void Renderer::draw(const Observer& observer,
|
||||||
orbitPathList[orbitPathList.size() - 1].radius);
|
orbitPathList[orbitPathList.size() - 1].radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extend the farthest depth interval to include all multidraw items.
|
||||||
|
if (!multidrawRenderList.empty())
|
||||||
|
{
|
||||||
|
depthPartitions[0].farZ = min(depthPartitions[0].farZ, zFurthest);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// We want to avoid overpartitioning the depth buffer. In this stage, we coalesce
|
// We want to avoid overpartitioning the depth buffer. In this stage, we coalesce
|
||||||
// partitions that have small spans in the depth buffer.
|
// partitions that have small spans in the depth buffer.
|
||||||
// TODO: Implement this step!
|
// TODO: Implement this step!
|
||||||
|
@ -3816,6 +3895,16 @@ void Renderer::draw(const Observer& observer,
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render all multidraw items
|
||||||
|
for (unsigned int j = 0; j < multidrawRenderList.size(); ++j)
|
||||||
|
{
|
||||||
|
const RenderListEntry& rle = multidrawRenderList[j];
|
||||||
|
if (rle.discSizeInPixels > 1.0f && rle.farZ < depthPartitions[interval].nearZ && rle.nearZ > depthPartitions[interval].farZ)
|
||||||
|
{
|
||||||
|
renderItem(rle, observer, m_cameraOrientation, nearPlaneDistance, farPlaneDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Render orbit paths
|
// Render orbit paths
|
||||||
if (!orbitPathList.empty())
|
if (!orbitPathList.empty())
|
||||||
{
|
{
|
||||||
|
@ -5014,7 +5103,8 @@ static void setLightParameters_VP(VertexProcessor& vproc,
|
||||||
static void renderModelDefault(Geometry* geometry,
|
static void renderModelDefault(Geometry* geometry,
|
||||||
const RenderInfo& ri,
|
const RenderInfo& ri,
|
||||||
bool lit,
|
bool lit,
|
||||||
ResourceHandle texOverride)
|
ResourceHandle texOverride,
|
||||||
|
double tsec)
|
||||||
{
|
{
|
||||||
FixedFunctionRenderContext rc;
|
FixedFunctionRenderContext rc;
|
||||||
Material m;
|
Material m;
|
||||||
|
@ -5046,7 +5136,7 @@ static void renderModelDefault(Geometry* geometry,
|
||||||
rc.lock();
|
rc.lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
geometry->render(rc);
|
geometry->render(rc, tsec);
|
||||||
if (geometry->usesTextureType(Material::EmissiveMap))
|
if (geometry->usesTextureType(Material::EmissiveMap))
|
||||||
{
|
{
|
||||||
glDisable(GL_LIGHTING);
|
glDisable(GL_LIGHTING);
|
||||||
|
@ -5056,7 +5146,7 @@ static void renderModelDefault(Geometry* geometry,
|
||||||
rc.setRenderPass(RenderContext::EmissivePass);
|
rc.setRenderPass(RenderContext::EmissivePass);
|
||||||
rc.setMaterial(NULL);
|
rc.setMaterial(NULL);
|
||||||
|
|
||||||
geometry->render(rc);
|
geometry->render(rc, tsec);
|
||||||
|
|
||||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
||||||
}
|
}
|
||||||
|
@ -7140,7 +7230,7 @@ void Renderer::renderObject(const Vector3f& pos,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
renderModelDefault(geometry, ri, lit, texOverride);
|
renderModelDefault(geometry, ri, lit, texOverride, astro::daysToSecs(now - astro::J2000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -653,6 +653,7 @@ class Renderer
|
||||||
PointStarVertexBuffer* pointStarVertexBuffer;
|
PointStarVertexBuffer* pointStarVertexBuffer;
|
||||||
PointStarVertexBuffer* glareVertexBuffer;
|
PointStarVertexBuffer* glareVertexBuffer;
|
||||||
std::vector<RenderListEntry> renderList;
|
std::vector<RenderListEntry> renderList;
|
||||||
|
std::vector<RenderListEntry> multidrawRenderList;
|
||||||
std::vector<SecondaryIlluminator> secondaryIlluminators;
|
std::vector<SecondaryIlluminator> secondaryIlluminators;
|
||||||
std::vector<DepthBufferPartition> depthPartitions;
|
std::vector<DepthBufferPartition> depthPartitions;
|
||||||
std::vector<Particle> glareParticles;
|
std::vector<Particle> glareParticles;
|
||||||
|
@ -683,32 +684,6 @@ class Renderer
|
||||||
|
|
||||||
int currentIntervalIndex;
|
int currentIntervalIndex;
|
||||||
|
|
||||||
|
|
||||||
public:
|
|
||||||
#if 0
|
|
||||||
struct OrbitSample
|
|
||||||
{
|
|
||||||
double t;
|
|
||||||
Point3d pos;
|
|
||||||
|
|
||||||
OrbitSample(const Eigen::Vector3d& _pos, double _t) : t(_t), pos(_pos.x(), _pos.y(), _pos.z()) { }
|
|
||||||
OrbitSample() { }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OrbitSection
|
|
||||||
{
|
|
||||||
Capsuled boundingVolume;
|
|
||||||
uint32 firstSample;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CachedOrbit
|
|
||||||
{
|
|
||||||
std::vector<OrbitSample> trajectory;
|
|
||||||
std::vector<OrbitSection> sections;
|
|
||||||
uint32 lastUsed;
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef std::map<const Orbit*, CurvePlot*> OrbitCache;
|
typedef std::map<const Orbit*, CurvePlot*> OrbitCache;
|
||||||
OrbitCache orbitCache;
|
OrbitCache orbitCache;
|
||||||
|
|
|
@ -0,0 +1,315 @@
|
||||||
|
// sensorgeometry.cpp
|
||||||
|
//
|
||||||
|
// Copyright (C) 2010, Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@gmail.com>
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
#include "sensorgeometry.h"
|
||||||
|
#include "rendcontext.h"
|
||||||
|
#include "texmanager.h"
|
||||||
|
#include "astro.h"
|
||||||
|
#include "body.h"
|
||||||
|
#include "vecgl.h"
|
||||||
|
#include "celmath/mathlib.h"
|
||||||
|
#include "celmath/intersect.h"
|
||||||
|
#include <Eigen/Core>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
using namespace Eigen;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
SensorGeometry::SensorGeometry() :
|
||||||
|
m_observer(NULL),
|
||||||
|
m_target(NULL),
|
||||||
|
m_range(0.0),
|
||||||
|
m_horizontalFov(degToRad(5.0)),
|
||||||
|
m_verticalFov(degToRad(5.0)),
|
||||||
|
m_frustumColor(1.0f, 1.0f, 1.0f),
|
||||||
|
m_frustumBaseColor(1.0f, 1.0f, 1.0f),
|
||||||
|
m_frustumOpacity(0.25f),
|
||||||
|
m_gridOpacity(1.0f),
|
||||||
|
m_shape(EllipticalShape),
|
||||||
|
m_frustumVisible(true),
|
||||||
|
m_frustumBaseVisible(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SensorGeometry::~SensorGeometry()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
SensorGeometry::pick(const Ray3d& /* r */, double& /* distance */) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
SensorGeometry::setFOVs(double horizontalFov, double verticalFov)
|
||||||
|
{
|
||||||
|
m_horizontalFov = horizontalFov;
|
||||||
|
m_verticalFov = verticalFov;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Render the sensor geometry.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SensorGeometry::render(RenderContext& rc, double tsec)
|
||||||
|
{
|
||||||
|
if (m_target == NULL || m_observer == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double jd = astro::secsToDays(tsec) + astro::J2000;
|
||||||
|
|
||||||
|
UniversalCoord obsPos = m_observer->getPosition(jd);
|
||||||
|
UniversalCoord targetPos = m_target->getPosition(jd);
|
||||||
|
|
||||||
|
Vector3d pos = targetPos.offsetFromKm(obsPos);
|
||||||
|
|
||||||
|
Quaterniond q = m_observer->getOrientation(jd);
|
||||||
|
|
||||||
|
const unsigned int sectionCount = 40; // Must be a multiple of 4
|
||||||
|
const unsigned int sliceCount = 10;
|
||||||
|
Vector3d profile[sectionCount];
|
||||||
|
Vector3d footprint[sectionCount];
|
||||||
|
|
||||||
|
Quaterniond obsOrientation = m_observer->getOrientation(jd).conjugate() * m_observer->getGeometryOrientation().cast<double>().conjugate();
|
||||||
|
Quaterniond targetOrientation = m_target->getOrientation(jd).conjugate();
|
||||||
|
Vector3d origin = targetOrientation.conjugate() * -pos;
|
||||||
|
Ellipsoidd targetEllipsoid(m_target->getSemiAxes().cast<double>());
|
||||||
|
|
||||||
|
glDisable(GL_LIGHTING);
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
glDepthMask(GL_FALSE);
|
||||||
|
glDisable(GL_TEXTURE_2D);
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
glPushMatrix();
|
||||||
|
|
||||||
|
// 'Undo' the rotation of the parent body. We are assuming that the observer is
|
||||||
|
// the body to which the sensor geometry is attached.
|
||||||
|
glRotate(obsOrientation.conjugate());
|
||||||
|
|
||||||
|
Matrix3d obsRotation = obsOrientation.toRotationMatrix();
|
||||||
|
|
||||||
|
double horizontalSize = tan(m_horizontalFov);
|
||||||
|
double verticalSize = tan(m_verticalFov);
|
||||||
|
|
||||||
|
// Compute the profile of the frustum; the profile is extruded over the range
|
||||||
|
// of the sensor (or to the intersection) when rendering.
|
||||||
|
if (m_shape == EllipticalShape)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < sectionCount; ++i)
|
||||||
|
{
|
||||||
|
double t = double(i) / double(sectionCount);
|
||||||
|
double theta = t * PI * 2.0;
|
||||||
|
|
||||||
|
// Note: -sin() is used here to reverse the vertex order so that the _outside_
|
||||||
|
// of the frustum is drawn.
|
||||||
|
profile[i] = obsRotation * Vector3d(cos(theta) * horizontalSize, -sin(theta) * verticalSize, 1.0).normalized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < sectionCount; ++i)
|
||||||
|
{
|
||||||
|
double t = double(i) / double(sectionCount);
|
||||||
|
double theta = t * PI * 2.0;
|
||||||
|
|
||||||
|
double u = double((i + sectionCount / 8) % (sectionCount / 4)) / double(sectionCount / 4);
|
||||||
|
double phi = (u - 0.5) * PI / 2;
|
||||||
|
|
||||||
|
// Note: -sin() is used here to reverse the vertex order so that the _outside_
|
||||||
|
// of the frustum is drawn.
|
||||||
|
double l = 1.0 / cos(phi);
|
||||||
|
profile[i] = obsRotation * Vector3d(cos(theta) * horizontalSize * l, -sin(theta) * verticalSize * l, 1.0).normalized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set to true if alternate sides of the frustum should be drawn with different
|
||||||
|
// opacities.
|
||||||
|
bool alternateShading = m_shape == RectangularShape;
|
||||||
|
|
||||||
|
// Compute the 'footprint' of the sensor by finding the intersection of all rays with
|
||||||
|
// the target body. The rendering will not be correct unless the sensor frustum
|
||||||
|
for (unsigned int i = 0; i < sectionCount; ++i)
|
||||||
|
{
|
||||||
|
Vector3d direction = profile[i];
|
||||||
|
Vector3d testDirection = targetOrientation.conjugate() * direction;
|
||||||
|
|
||||||
|
// Draw the sensor frustum out to either the range or the point of
|
||||||
|
// intersection with the target body--whichever is closer.
|
||||||
|
double distance = 0.0;
|
||||||
|
if (testIntersection(Ray3d(origin, testDirection), targetEllipsoid, distance))
|
||||||
|
{
|
||||||
|
distance = std::min(distance, m_range);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
distance = m_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
footprint[i] = distance * direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternateShading)
|
||||||
|
{
|
||||||
|
glShadeModel(GL_FLAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the frustum
|
||||||
|
if (m_frustumVisible)
|
||||||
|
{
|
||||||
|
unsigned int sectionsPerRectSide = sectionCount / 4;
|
||||||
|
|
||||||
|
glColor4f(m_frustumColor.red(), m_frustumColor.green(), m_frustumColor.blue(), m_frustumOpacity);
|
||||||
|
glBegin(GL_TRIANGLE_FAN);
|
||||||
|
glVertex3d(0.0, 0.0, 0.0);
|
||||||
|
for (unsigned int i = 0; i <= sectionCount; ++i)
|
||||||
|
{
|
||||||
|
if (alternateShading)
|
||||||
|
{
|
||||||
|
// Use different opacities for adjacent faces of rectangular frusta; this
|
||||||
|
// makes the geometry easier to understand visually.
|
||||||
|
float alpha = m_frustumOpacity;
|
||||||
|
if (((i + sectionsPerRectSide / 2 - 1) / sectionsPerRectSide) % 2 == 1)
|
||||||
|
{
|
||||||
|
alpha *= 0.5f;
|
||||||
|
}
|
||||||
|
glColor4f(m_frustumColor.red(), m_frustumColor.green(), m_frustumColor.blue(), alpha);
|
||||||
|
}
|
||||||
|
glVertex3dv(footprint[i % sectionCount].data());
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alternateShading)
|
||||||
|
{
|
||||||
|
glShadeModel(GL_SMOOTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
glEnable(GL_LINE_SMOOTH);
|
||||||
|
|
||||||
|
// Draw the footprint outline
|
||||||
|
if (m_frustumBaseVisible)
|
||||||
|
{
|
||||||
|
glColor4f(m_frustumBaseColor.red(), m_frustumBaseColor.green(), m_frustumBaseColor.blue(), m_gridOpacity);
|
||||||
|
glLineWidth(2.0f);
|
||||||
|
glBegin(GL_LINE_LOOP);
|
||||||
|
for (unsigned int i = 0; i < sectionCount; ++i)
|
||||||
|
{
|
||||||
|
glVertex3dv(footprint[i].data());
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
glLineWidth(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_frustumVisible)
|
||||||
|
{
|
||||||
|
glColor4f(m_frustumColor.red(), m_frustumColor.green(), m_frustumColor.blue(), m_frustumOpacity);
|
||||||
|
for (unsigned int slice = 1; slice < sliceCount; ++slice)
|
||||||
|
{
|
||||||
|
// Linear arrangement of slices
|
||||||
|
//double t = double(slice) / double(sliceCount);
|
||||||
|
|
||||||
|
// Exponential arrangement looks better
|
||||||
|
double t = pow(2.0, -double(slice));
|
||||||
|
|
||||||
|
glBegin(GL_LINE_LOOP);
|
||||||
|
for (unsigned int i = 0; i < sectionCount; ++i)
|
||||||
|
{
|
||||||
|
Vector3d v = footprint[i] * t;
|
||||||
|
glVertex3dv(v.data());
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: section count should be evenly divisible by 8
|
||||||
|
glBegin(GL_LINES);
|
||||||
|
if (m_shape == EllipticalShape)
|
||||||
|
{
|
||||||
|
unsigned int rayCount = 8;
|
||||||
|
unsigned int step = sectionCount / rayCount;
|
||||||
|
for (unsigned int i = 0; i < sectionCount; i += step)
|
||||||
|
{
|
||||||
|
glVertex3f(0.0f, 0.0f, 0.0f);
|
||||||
|
glVertex3dv(footprint[i].data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unsigned int step = sectionCount / 4;
|
||||||
|
for (unsigned int i = sectionCount / 8; i < sectionCount; i += step)
|
||||||
|
{
|
||||||
|
glVertex3f(0.0f, 0.0f, 0.0f);
|
||||||
|
glVertex3dv(footprint[i].data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
glPopMatrix();
|
||||||
|
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glEnable(GL_LIGHTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
SensorGeometry::isOpaque() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
SensorGeometry::isNormalized() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
SensorGeometry::setPartVisible(const std::string& partName, bool visible)
|
||||||
|
{
|
||||||
|
std::clog << "setPartVisible: " << partName << std::endl;
|
||||||
|
if (partName == "Frustum")
|
||||||
|
{
|
||||||
|
m_frustumVisible = visible;
|
||||||
|
}
|
||||||
|
else if (partName == "FrustumBase")
|
||||||
|
{
|
||||||
|
m_frustumBaseVisible = visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
SensorGeometry::isPartVisible(const std::string& partName) const
|
||||||
|
{
|
||||||
|
if (partName == "Frustum")
|
||||||
|
{
|
||||||
|
return m_frustumVisible;
|
||||||
|
}
|
||||||
|
else if (partName == "FrustumBase")
|
||||||
|
{
|
||||||
|
return m_frustumBaseVisible;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
// sensorgeometry.h
|
||||||
|
//
|
||||||
|
// Copyright (C) 2010, Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@gmail.com>
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
#ifndef _CELENGINE_SENSOR_GEOMETRY_H_
|
||||||
|
#define _CELENGINE_SENSOR_GEOMETRY_H_
|
||||||
|
|
||||||
|
#include "geometry.h"
|
||||||
|
#include <celutil/color.h>
|
||||||
|
#include <celutil/resmanager.h>
|
||||||
|
|
||||||
|
class Body;
|
||||||
|
|
||||||
|
class SensorGeometry : public Geometry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SensorGeometry();
|
||||||
|
~SensorGeometry();
|
||||||
|
|
||||||
|
enum SensorShape
|
||||||
|
{
|
||||||
|
EllipticalShape,
|
||||||
|
RectangularShape,
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual bool pick(const Ray3d& r, double& distance) const;
|
||||||
|
|
||||||
|
//! Render the model in the current OpenGL context
|
||||||
|
virtual void render(RenderContext&, double t = 0.0);
|
||||||
|
|
||||||
|
virtual bool isOpaque() const;
|
||||||
|
virtual bool isNormalized() const;
|
||||||
|
|
||||||
|
virtual bool isMultidraw() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setPartVisible(const std::string& partName, bool visible);
|
||||||
|
virtual bool isPartVisible(const std::string& partName) const;
|
||||||
|
|
||||||
|
Body* observer() const
|
||||||
|
{
|
||||||
|
return m_observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setObserver(Body* observer)
|
||||||
|
{
|
||||||
|
m_observer = observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Body* target() const
|
||||||
|
{
|
||||||
|
return m_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTarget(Body* target)
|
||||||
|
{
|
||||||
|
m_target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
double range() const
|
||||||
|
{
|
||||||
|
return m_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRange(double range)
|
||||||
|
{
|
||||||
|
m_range = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
SensorShape shape() const
|
||||||
|
{
|
||||||
|
return m_shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setShape(SensorShape shape)
|
||||||
|
{
|
||||||
|
m_shape = shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color frustumColor() const
|
||||||
|
{
|
||||||
|
return m_frustumColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFrustumColor(const Color& color)
|
||||||
|
{
|
||||||
|
m_frustumColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color frustumBaseColor() const
|
||||||
|
{
|
||||||
|
return m_frustumBaseColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFrustumBaseColor(const Color& color)
|
||||||
|
{
|
||||||
|
m_frustumBaseColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
float frustumOpacity() const
|
||||||
|
{
|
||||||
|
return m_frustumOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFrustumOpacity(float opacity)
|
||||||
|
{
|
||||||
|
m_frustumOpacity = opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
float gridOpacity() const
|
||||||
|
{
|
||||||
|
return m_gridOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGridOpacity(float opacity)
|
||||||
|
{
|
||||||
|
m_gridOpacity = opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFOVs(double horizontalFov, double verticalFov);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
Body* m_observer;
|
||||||
|
Body* m_target;
|
||||||
|
double m_range;
|
||||||
|
double m_horizontalFov;
|
||||||
|
double m_verticalFov;
|
||||||
|
Color m_frustumColor;
|
||||||
|
Color m_frustumBaseColor;
|
||||||
|
float m_frustumOpacity;
|
||||||
|
float m_gridOpacity;
|
||||||
|
SensorShape m_shape;
|
||||||
|
bool m_frustumVisible;
|
||||||
|
bool m_frustumBaseVisible;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // !_CELENGINE_SENSOR_GEOMETRY_H_
|
|
@ -29,6 +29,7 @@
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include "texmanager.h"
|
#include "texmanager.h"
|
||||||
#include "meshmanager.h"
|
#include "meshmanager.h"
|
||||||
|
#include "sensorgeometry.h"
|
||||||
#include "universe.h"
|
#include "universe.h"
|
||||||
#include "multitexture.h"
|
#include "multitexture.h"
|
||||||
#include "parseobject.h"
|
#include "parseobject.h"
|
||||||
|
@ -894,6 +895,72 @@ static Body* CreateBody(const string& name,
|
||||||
body->setGeometry(geometryHandle);
|
body->setGeometry(geometryHandle);
|
||||||
body->setGeometryScale(geometryScale);
|
body->setGeometryScale(geometryScale);
|
||||||
}
|
}
|
||||||
|
else if (planetData->getValue("Sensor"))
|
||||||
|
{
|
||||||
|
Hash* sensorData = planetData->getValue("Sensor")->getHash();
|
||||||
|
if (sensorData)
|
||||||
|
{
|
||||||
|
SensorGeometry* sensor = new SensorGeometry();
|
||||||
|
sensor->setObserver(body);
|
||||||
|
|
||||||
|
string targetName;
|
||||||
|
if (sensorData->getString("Target", targetName))
|
||||||
|
{
|
||||||
|
Body* target = universe.findPath(targetName).body();
|
||||||
|
if (target)
|
||||||
|
{
|
||||||
|
sensor->setTarget(target);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cerr << "Can't find target for sensor.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cerr << "No target specified for sensor.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
double range = 1.0;
|
||||||
|
sensorData->getNumber("Range", range);
|
||||||
|
sensor->setRange(range);
|
||||||
|
|
||||||
|
double horizontalFov = 5.0;
|
||||||
|
double verticalFov = 5.0;
|
||||||
|
sensorData->getNumber("HorizontalFOV", horizontalFov);
|
||||||
|
sensorData->getNumber("VerticalFOV", verticalFov);
|
||||||
|
sensor->setFOVs(degToRad(horizontalFov), degToRad(verticalFov));
|
||||||
|
|
||||||
|
// Appearance attributes
|
||||||
|
Color frustumColor(1.0f, 1.0f, 1.0f);
|
||||||
|
Color frustumBaseColor(1.0f, 1.0f, 1.0f);
|
||||||
|
float frustumOpacity = 0.25f;
|
||||||
|
float gridOpacity = 1.0f;
|
||||||
|
sensorData->getColor("FrustumColor", frustumColor);
|
||||||
|
sensorData->getColor("FrustumBaseColor", frustumBaseColor);
|
||||||
|
sensorData->getNumber("FrustumOpacity", frustumOpacity);
|
||||||
|
sensorData->getNumber("GridOpacity", gridOpacity);
|
||||||
|
sensor->setFrustumColor(frustumColor);
|
||||||
|
sensor->setFrustumBaseColor(frustumBaseColor);
|
||||||
|
sensor->setFrustumOpacity(frustumOpacity);
|
||||||
|
sensor->setGridOpacity(gridOpacity);
|
||||||
|
|
||||||
|
string shape = "elliptical";
|
||||||
|
sensorData->getString("Shape", shape);
|
||||||
|
if (compareIgnoringCase(shape, "rectangular") == 0)
|
||||||
|
{
|
||||||
|
sensor->setShape(SensorGeometry::RectangularShape);
|
||||||
|
}
|
||||||
|
|
||||||
|
string resName = string("sensor") + targetName + Selection(body).getName();
|
||||||
|
GeometryInfo info(resName, path, Vector3f::Zero(), 1.0f, false);
|
||||||
|
info.resource = sensor;
|
||||||
|
info.state = ResourceLoaded;
|
||||||
|
ResourceHandle geometryHandle = GetGeometryManager()->getHandle(info);
|
||||||
|
body->setGeometry(geometryHandle);
|
||||||
|
body->setGeometryScale(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the atmosphere
|
// Read the atmosphere
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <celengine/axisarrow.h>
|
#include <celengine/axisarrow.h>
|
||||||
#include <celengine/visibleregion.h>
|
#include <celengine/visibleregion.h>
|
||||||
#include <celengine/planetgrid.h>
|
#include <celengine/planetgrid.h>
|
||||||
|
#include <celengine/meshmanager.h>
|
||||||
#include "celestiacore.h"
|
#include "celestiacore.h"
|
||||||
|
|
||||||
using namespace Eigen;
|
using namespace Eigen;
|
||||||
|
@ -131,6 +132,56 @@ static int object_setvisible(lua_State* l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check the visibility flag for an object component; returns false if the object doesn't
|
||||||
|
// have a component with the specified name
|
||||||
|
static int object_partvisible(lua_State* l)
|
||||||
|
{
|
||||||
|
CelxLua celx(l);
|
||||||
|
celx.checkArgs(2, 2, "One argument expected to object:setpartvisible()");
|
||||||
|
|
||||||
|
Selection* sel = this_object(l);
|
||||||
|
string partName = celx.safeGetString(2, AllErrors, "Argument of object:setpartvisible() must be a string");
|
||||||
|
|
||||||
|
bool visible = false;
|
||||||
|
|
||||||
|
if (sel->body() != NULL && sel->body()->getGeometry() != InvalidResource)
|
||||||
|
{
|
||||||
|
Geometry* geom = GetGeometryManager()->find(sel->body()->getGeometry());
|
||||||
|
if (geom)
|
||||||
|
{
|
||||||
|
visible = geom->isPartVisible(partName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushboolean(l, visible ? 1 : 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set the visibility flag for an object component; has no effect if the object doesn't
|
||||||
|
// have any defined components.
|
||||||
|
static int object_setpartvisible(lua_State* l)
|
||||||
|
{
|
||||||
|
CelxLua celx(l);
|
||||||
|
celx.checkArgs(3, 3, "Two argument expected to object:setpartvisible()");
|
||||||
|
|
||||||
|
Selection* sel = this_object(l);
|
||||||
|
string partName = celx.safeGetString(2, AllErrors, "Argument 1 of object:setpartvisible() must be a string");
|
||||||
|
|
||||||
|
bool visible = celx.safeGetBoolean(3, AllErrors, "Argument 2 of object:setpartvisible() must be a boolean");
|
||||||
|
if (sel->body() != NULL && sel->body()->getGeometry() != InvalidResource)
|
||||||
|
{
|
||||||
|
Geometry* geom = GetGeometryManager()->find(sel->body()->getGeometry());
|
||||||
|
if (geom)
|
||||||
|
{
|
||||||
|
geom->setPartVisible(partName, visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int object_setorbitcolor(lua_State* l)
|
static int object_setorbitcolor(lua_State* l)
|
||||||
{
|
{
|
||||||
CelxLua celx(l);
|
CelxLua celx(l);
|
||||||
|
@ -1306,6 +1357,8 @@ void CreateObjectMetaTable(lua_State* l)
|
||||||
celx.registerMethod("__tostring", object_tostring);
|
celx.registerMethod("__tostring", object_tostring);
|
||||||
celx.registerMethod("visible", object_visible);
|
celx.registerMethod("visible", object_visible);
|
||||||
celx.registerMethod("setvisible", object_setvisible);
|
celx.registerMethod("setvisible", object_setvisible);
|
||||||
|
celx.registerMethod("partvisible", object_partvisible);
|
||||||
|
celx.registerMethod("setpartvisible", object_setpartvisible);
|
||||||
celx.registerMethod("orbitcoloroverridden", object_orbitcoloroverridden);
|
celx.registerMethod("orbitcoloroverridden", object_orbitcoloroverridden);
|
||||||
celx.registerMethod("setorbitcoloroverridden", object_setorbitcoloroverridden);
|
celx.registerMethod("setorbitcoloroverridden", object_setorbitcoloroverridden);
|
||||||
celx.registerMethod("setorbitcolor", object_setorbitcolor);
|
celx.registerMethod("setorbitcolor", object_setorbitcolor);
|
||||||
|
|
Loading…
Reference in New Issue