Commit 736ff124 authored by Sebastian Eichelbaum's avatar Sebastian Eichelbaum
Browse files

[MERGE]

parents 262f9f00 fe90714f
wiebel Alexander_Wiebel
wiebel@hegel.informatik.uni-leipzig.de Alexander_Wiebel
openwalnut@dergrosse.de Alexander_Wiebel
wiebel@openwalnut.org Alexander_Wiebel
wiebel_openwalnut@dergrosse.de Alexander_Wiebel
wiebel@bsv_2008a Alexander_Wiebel
math Mathias_Goldau
lmath@voltaire.informatik.uni-leipzig.de Mathias_Goldau
math@informatik.uni-leipzig.de Mathias_Goldau
schurade@spinat.cbs.mpg.de Ralph_Schurade
schurade Ralph_Schurade
ebaum Sebastian_Eichelbaum
eichelbaum@informatik.uni-leipzig.de Sebastian_Eichelbaum
......@@ -15,4 +17,12 @@ hlawitschka@ucdavis.edu Mario_Hlawitschka
mai02igw@informatik.uni-leipzig.de Robin_Ledig
mai02ifw@informatik.uni-leipzig.de Robin_Ledig
mai02igw@studserv.uni-leipzig.de Robin_Ledig
heine@informatik.uni-leipzig.de Christian_Heine
\ No newline at end of file
heine@informatik.uni-leipzig.de Christian_Heine
reichenbach@berkeley.informatik.uni-leipzig.de Andre_Reichenbach
reichenbach Andre_Reichenbach
philips@informatik.uni-leipzig.de Stefan_Philips
frohl.robert@googlemail.com Robert_Frohl
dirk_albrecht@gmx.net Dirk_Albrecht
albrecht Dirk_Albrecht
mstuber Marcus_Stuber
skiunke Sebastian_Kiunke
\ No newline at end of file
......@@ -43,17 +43,17 @@ compatiblesToolBarStyle = 0 # this pref allows the compatibles toolbar to have a
# 2 -> left
# 3 -> right
# 4 -> hide completely
# 5 -> inside the dataset browser ( not possible for compatiblesToolBarPos! This will be mapped to right (=3) )
# 5 -> inside the control panel ( not possible for compatiblesToolBarPos! This will be mapped to right (=3) )
toolBarPos = 0 # default position of the toolbars
compatiblesToolBarPos = 0 # position of the compatibles toolbar (overrides toolBarPos)
####################################################################
# Dataset Browser
# Control panel
# combineTreeAndRoiAndTextureSorter = yes # should the module tree, ROI tree and the Texture Sorter be combined in ONE Tab widget?
# dsbWidth = 400 # the width of the dataset browser
# dsbInvisibleByDefault = yes # hide the dataset browser on startup. Can be shown using F9.
# dsbFloatingByDefault = yes # should the dataset browser be undocked on startup?
# dsbWidth = 400 # the width of the control panel
# dsbInvisibleByDefault = yes # hide the control panel on startup. Can be shown using F9.
# dsbFloatingByDefault = yes # should the control panel be undocked on startup?
####################################################################
# Other GUI settings
......
......@@ -49,17 +49,17 @@ compatiblesToolBarStyle = 0 # this pref allows the compatibles toolbar to have a
# 2 -> left
# 3 -> right
# 4 -> hide completely
# 5 -> inside the dataset browser ( not possible for compatiblesToolBarPos! This will be mapped to right (=3) )
# 5 -> inside the control panel ( not possible for compatiblesToolBarPos! This will be mapped to right (=3) )
toolBarPos = 0 # default position of the toolbars
compatiblesToolBarPos = 0 # position of the compatibles toolbar (overrides toolBarPos)
####################################################################
# Dataset Browser
# Control panel
# combineTreeAndRoiAndTextureSorter = yes # should the module tree, ROI tree and the Texture Sorter be combined in ONE Tab widget?
# dsbWidth = 400 # the width of the dataset browser
# dsbInvisibleByDefault = yes # hide the dataset browser on startup. Can be shown using F9.
# dsbFloatingByDefault = yes # should the dataset browser be undocked on startup?
# dsbWidth = 400 # the width of the control panel
# dsbInvisibleByDefault = yes # hide the control panel on startup. Can be shown using F9.
# dsbFloatingByDefault = yes # should the control panel be undocked on startup?
####################################################################
# Other GUI settings
......
......@@ -87,7 +87,7 @@ ELSE()
IF( NOT VERSION LESS 2.6.3 )
SET ( CMAKE_PREFIX_PATH "$ENV{ProgramFiles}/OpenSceneGraph" )
ENDIF()
FIND_PACKAGE( OpenSceneGraph ${MIN_OSG_VERSION} REQUIRED osgUtil osgDB osgViewer osgText osgGA osgSim )
FIND_PACKAGE( OpenSceneGraph ${MIN_OSG_VERSION} REQUIRED osgUtil osgDB osgViewer osgText osgGA osgSim osgWidget )
INCLUDE_DIRECTORIES( ${OPENSCENEGRAPH_INCLUDE_DIRS} )
ENDIF()
......@@ -267,15 +267,17 @@ ADD_CUSTOM_TARGET( tags DEPENDS ctags DEPENDS cscope )
#-------------------------------------------------------------------------------------------------------------
# Determines the number of cores available on this machine
SET( NUM_CORES_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../tools/numCores )
SET( NUM_CORES_BINARY_DIR ${PROJECT_BINARY_DIR}/numCores )
SET( NUM_CORES_BINARY ${NUM_CORES_BINARY_DIR}/numCores )
ADD_CUSTOM_TARGET( numCores
IF( NOT CMAKE_GENERATOR MATCHES "Visual Studio" )
SET( NUM_CORES_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../tools/numCores )
SET( NUM_CORES_BINARY_DIR ${PROJECT_BINARY_DIR}/numCores )
SET( NUM_CORES_BINARY ${NUM_CORES_BINARY_DIR}/numCores )
ADD_CUSTOM_TARGET( numCores
COMMAND ${CMAKE_COMMAND} -E make_directory ${NUM_CORES_BINARY_DIR}
COMMAND cd ${NUM_CORES_BINARY_DIR} && ${CMAKE_COMMAND} ${NUM_CORES_SOURCE_DIR} > /dev/null
COMMAND $(MAKE) -C ${NUM_CORES_BINARY_DIR} > /dev/null
COMMENT "Determines the number of cores available on this machine"
)
ENDIF()
#-------------------------------------------------------------------------------------------------------------
# Checks style guide lines via the BrainLint tool against all source code files
......
......@@ -22,6 +22,7 @@
//
//---------------------------------------------------------------------------
#include <cmath>
#include <cassert>
#include <string>
#include <vector>
......@@ -224,6 +225,20 @@ bool WColor::operator!=( const WColor &rhs ) const
return !( *this == rhs );
}
void WColor::inverse()
{
m_red = std::abs( 1. - m_red );
m_green = std::abs( 1. - m_green );
m_blue = std::abs( 1. - m_blue );
}
void WColor::average( const WColor& other )
{
m_red = ( m_red + other.getRed() ) / 2.0;
m_green = ( m_green + other.getGreen() ) / 2.0;
m_blue = ( m_blue + other.getBlue() ) / 2.0;
}
const WColor WColor::green( 0.0, 1.0, 0.0, 1.0 );
const WColor WColor::red( 1.0, 0.0, 0.0, 1.0 );
const WColor WColor::blue( 0.0, 0.0, 1.0, 1.0 );
......@@ -109,6 +109,18 @@ public:
*/
void setRGB( double r, double g, double b );
/**
* Computes the inverse of this color in means of RGB space.
*/
void inverse();
/**
* Computes the arithmetic mean of this and the other color. This is done component wisely.
* For example red mixed with green will become yellow.
*
* \param other The other color to mix in here :D.
*/
void average( const WColor& other );
/**
* Compares two WColor instances on all four channels.
......
......@@ -4,5 +4,6 @@ FILE( GLOB COMMON_DATASTRUCTURES_SRC "*.cpp" "*.h" )
IF( OW_COMPILE_TESTS )
CXXTEST_ADD_TESTS_FROM_LIST( "${COMMON_DATASTRUCTURES_SRC}"
"OWcommon" # no libs for linking required
"WColoredVertices.cpp" # is just an container, no logic to test
)
ENDIF( OW_COMPILE_TESTS )
//---------------------------------------------------------------------------
//
// Project: OpenWalnut ( http://www.openwalnut.org )
//
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
// For more information see http://www.openwalnut.org/copying
//
// This file is part of OpenWalnut.
//
// OpenWalnut is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenWalnut is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------
#include <map>
#include <boost/shared_ptr.hpp>
#include "WColoredVertices.h"
// init _static_ member variable and provide a linker reference to it
boost::shared_ptr< WPrototyped > WColoredVertices::m_prototype = boost::shared_ptr< WPrototyped >();
boost::shared_ptr< WPrototyped > WColoredVertices::getPrototype()
{
if ( !m_prototype )
{
m_prototype = boost::shared_ptr< WPrototyped >( new WColoredVertices() );
}
return m_prototype;
}
WColoredVertices::WColoredVertices()
{
}
WColoredVertices::WColoredVertices( const std::map< size_t, WColor >& data )
: m_data( data )
{
}
WColoredVertices::~WColoredVertices()
{
}
//---------------------------------------------------------------------------
//
// Project: OpenWalnut ( http://www.openwalnut.org )
//
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
// For more information see http://www.openwalnut.org/copying
//
// This file is part of OpenWalnut.
//
// OpenWalnut is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenWalnut is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------
#ifndef WCOLOREDVERTICES_H
#define WCOLOREDVERTICES_H
#include <map>
#include <string>
#include "../WTransferable.h"
#include "../WColor.h"
#include "../WExportCommon.h"
/**
* Represents a std::map where for each vertex ID a color is stored.
*/
class OWCOMMON_EXPORT WColoredVertices : public WTransferable // NOLINT
{
public:
/**
* Default constructor.
*/
WColoredVertices();
/**
* Initialize this with the given map.
*
* \param data The map
*/
explicit WColoredVertices( const std::map< size_t, WColor >& data );
/**
* Cleans up this instance.
*/
virtual ~WColoredVertices();
/**
* Gets the name of this prototype.
*
* \return the name.
*/
virtual const std::string getName() const;
/**
* Gets the description for this prototype.
*
* \return the description
*/
virtual const std::string getDescription() const;
/**
* Returns a prototype instantiated with the true type of the deriving class.
*
* \return the prototype.
*/
static boost::shared_ptr< WPrototyped > getPrototype();
/**
* Reference to the data.
*
* \return Reference to the map of ids and colors.
*/
const std::map< size_t, WColor >& getData() const;
/**
* Replace (copies) the internal data with the given one.
*
* \param data The ID-Color map
*/
void setData( const std::map< size_t, WColor >& data );
protected:
static boost::shared_ptr< WPrototyped > m_prototype; //!< The prototype as singleton.
private:
std::map< size_t, WColor > m_data; //!< stores the vertex ids and colors
};
inline const std::string WColoredVertices::getName() const
{
return "WColoredVertices";
}
inline const std::string WColoredVertices::getDescription() const
{
return "Represents a std::map where for each vertex ID a color is stored.";
}
inline const std::map< size_t, WColor >& WColoredVertices::getData() const
{
return m_data;
}
inline void WColoredVertices::setData( const std::map< size_t, WColor >& data )
{
m_data = data;
}
#endif // WCOLOREDVERTICES_H
......@@ -25,12 +25,16 @@
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>
#include "../math/WLine.h"
#include "../math/WMath.h"
#include "../math/WPlane.h"
#include "WTriangleMesh.h"
#include "WUnionFind.h"
......@@ -251,96 +255,117 @@ void WTriangleMesh::computeVertNormals()
m_computedVertNormals = true;
}
std::ostream& tm_utils::operator<<( std::ostream& os, const WTriangleMesh& rhs )
{
std::stringstream ss;
ss << "WTriangleMesh( #vertices=" << rhs.getNumVertices() << " #triangles=" << rhs.getNumTriangles() << " )" << std::endl;
using string_utils::operator<<;
size_t count = 0;
ss << std::endl;
const std::vector< Triangle > triangles = rhs.getTriangles();
const std::vector< wmath::WPosition > vertices = rhs.getVertices();
for( std::vector< Triangle >::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle, ++count )
{
std::stringstream prefix;
prefix << "triangle: " << count << "[ ";
std::string indent( prefix.str().size(), ' ' );
ss << prefix.str() << vertices[ triangle->pointID[0] ] << std::endl;
ss << indent << vertices[ triangle->pointID[1] ] << std::endl;
ss << indent << vertices[ triangle->pointID[2] ] << std::endl;
ss << std::string( indent.size() - 2, ' ' ) << "]" << std::endl;
}
return os << ss.str();
}
boost::shared_ptr< std::list< boost::shared_ptr< WTriangleMesh > > > tm_utils::componentDecomposition( const WTriangleMesh& mesh )
{
WUnionFind uf( mesh.getNumVertices() ); // idea: every vertex in own component, then successivley join in accordance with the triangles
const std::vector< Triangle >& triangles = mesh.getTriangles();
for( std::vector< Triangle >::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle )
{
uf.merge( triangle->pointID[0], triangle->pointID[1] );
uf.merge( triangle->pointID[0], triangle->pointID[2] ); // uf.merge( triangle->pointID[2], triangle->pointID[1] ); they are already in same
}
// ATTENTION: The reason for using the complex BucketType instead of pasting vertices directly into a new WTriangleMesh
// is performance! For example: If there are many vertices reused inside the former WTriangleMesh mesh, then we want
// to reuse them in the new components too. Hence we must determine if a certain vertex is already inside the new component.
// Since the vertices are organized in a vector, we can use std::find( v.begin, v.end(), vertexToLookUp ) which results
// in O(N^2) or we could use faster lookUp via key and value leading to the map and the somehow complicated BucketType.
typedef std::map< wmath::WPosition, size_t > VertexType; // look up fast if a vertex is already inside the new mesh!
typedef std::vector< Triangle > TriangleType;
typedef std::pair< VertexType, TriangleType > BucketType; // Later on the Bucket will be transformed into the new WTriangleMesh component
std::map< size_t, BucketType > buckets; // Key identify with the cannonical element from UnionFind the new connected component
const std::vector< wmath::WPosition >& vertices = mesh.getVertices();
for( std::vector< Triangle >::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle )
{
size_t component = uf.find( triangle->pointID[0] );
if( buckets.find( component ) == buckets.end() )
{
buckets[ component ] = BucketType( VertexType(), TriangleType() ); // create new bucket
}
// Note: We discard the order of the points and indices, but semantically the structure remains the same
VertexType& mapRef = buckets[ component ].first; // short hand alias
Triangle x = { { 0, 0, 0 } }; // NOLINT
for( int i = 0; i < 3; ++i )
{
size_t id = 0;
const wmath::WPosition& vertex = vertices[ triangle->pointID[i] ];
if( mapRef.find( vertex ) == mapRef.end() )
{
id = mapRef.size(); // since size might change in next line
mapRef[ vertex ] = id;
}
else
{
id = mapRef[ vertex ];
}
x.pointID[i] = id;
}
buckets[ component ].second.push_back( x );
}
boost::shared_ptr< std::list< boost::shared_ptr< WTriangleMesh > > > result( new std::list< boost::shared_ptr< WTriangleMesh > >() );
for( std::map< size_t, BucketType >::const_iterator cit = buckets.begin(); cit != buckets.end(); ++cit )
{
std::vector< wmath::WPosition > newVertices;
newVertices.resize( cit->second.first.size() );
for( VertexType::const_iterator vit = cit->second.first.begin(); vit != cit->second.first.end(); ++vit )
{
newVertices.at( vit->second ) = vit->first; // if you are sure that vit->second is always valid replace at() call with operator[]
}
boost::shared_ptr< WTriangleMesh > newMesh( new WTriangleMesh() );
newMesh->resizeVertices( newVertices.size() );
newMesh->setVertices( newVertices );
newMesh->resizeTriangles( cit->second.second.size() );
newMesh->setTriangles( cit->second.second );
result->push_back( newMesh );
}
return result;
}
//std::ostream& tm_utils::operator<<( std::ostream& os, const WTriangleMesh& rhs )
//{
// std::stringstream ss;
// ss << "WTriangleMesh( #vertices=" << rhs.getNumVertices() << " #triangles=" << rhs.getNumTriangles() << " )" << std::endl;
// using string_utils::operator<<;
// size_t count = 0;
// ss << std::endl;
// const std::vector< Triangle > triangles = rhs.getTriangles();
// const std::vector< wmath::WPosition > vertices = rhs.getVertices();
// for( std::vector< Triangle >::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle, ++count )
// {
// std::stringstream prefix;
// prefix << "triangle: " << count << "[ ";
// std::string indent( prefix.str().size(), ' ' );
// ss << prefix.str() << vertices[ triangle->pointID[0] ] << std::endl;
// ss << indent << vertices[ triangle->pointID[1] ] << std::endl;
// ss << indent << vertices[ triangle->pointID[2] ] << std::endl;
// ss << std::string( indent.size() - 2, ' ' ) << "]" << std::endl;
// }
// return os << ss.str();
//}
//
//boost::shared_ptr< std::list< boost::shared_ptr< WTriangleMesh > > > tm_utils::componentDecomposition( const WTriangleMesh& mesh )
//{
// WUnionFind uf( mesh.getNumVertices() ); // idea: every vertex in own component, then successivley join in accordance with the triangles
//
// const std::vector< Triangle >& triangles = mesh.getTriangles();
// for( std::vector< Triangle >::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle )
// {
// uf.merge( triangle->pointID[0], triangle->pointID[1] );
// uf.merge( triangle->pointID[0], triangle->pointID[2] ); // uf.merge( triangle->pointID[2], triangle->pointID[1] ); they are already in same
// }
//
// // ATTENTION: The reason for using the complex BucketType instead of pasting vertices directly into a new WTriangleMesh
// // is performance! For example: If there are many vertices reused inside the former WTriangleMesh mesh, then we want
// // to reuse them in the new components too. Hence we must determine if a certain vertex is already inside the new component.
// // Since the vertices are organized in a vector, we can use std::find( v.begin, v.end(), vertexToLookUp ) which results
// // in O(N^2) or we could use faster lookUp via key and value leading to the map and the somehow complicated BucketType.
// typedef std::map< wmath::WPosition, size_t > VertexType; // look up fast if a vertex is already inside the new mesh!
// typedef std::vector< Triangle > TriangleType;
// typedef std::pair< VertexType, TriangleType > BucketType; // Later on the Bucket will be transformed into the new WTriangleMesh component
// std::map< size_t, BucketType > buckets; // Key identify with the cannonical element from UnionFind the new connected component
//
// const std::vector< wmath::WPosition >& vertices = mesh.getVertices();
// for( std::vector< Triangle >::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle )
// {
// size_t component = uf.find( triangle->pointID[0] );
// if( buckets.find( component ) == buckets.end() )
// {
// buckets[ component ] = BucketType( VertexType(), TriangleType() ); // create new bucket
// }
//
// // Note: We discard the order of the points and indices, but semantically the structure remains the same
// VertexType& mapRef = buckets[ component ].first; // short hand alias
// Triangle x = { { 0, 0, 0 } }; // NOLINT
// for( int i = 0; i < 3; ++i )
// {
// size_t id = 0;
// const wmath::WPosition& vertex = vertices[ triangle->pointID[i] ];
// if( mapRef.find( vertex ) == mapRef.end() )
// {
// id = mapRef.size(); // since size might change in next line
// mapRef[ vertex ] = id;
// }
// else
// {
// id = mapRef[ vertex ];
// }
// x.pointID[i] = id;
// }
//
// buckets[ component ].second.push_back( x );
// }
//
// boost::shared_ptr< std::list< boost::shared_ptr< WTriangleMesh > > > result( new std::list< boost::shared_ptr< WTriangleMesh > >() );
// for( std::map< size_t, BucketType >::const_iterator cit = buckets.begin(); cit != buckets.end(); ++cit )
// {
// std::vector< wmath::WPosition > newVertices;
// newVertices.resize( cit->second.first.size() );
// for( VertexType::const_iterator vit = cit->second.first.begin(); vit != cit->second.first.end(); ++vit )
// {
// newVertices.at( vit->second ) = vit->first; // if you are sure that vit->second is always valid replace at() call with operator[]
// }
// boost::shared_ptr< WTriangleMesh > newMesh( new WTriangleMesh() );
// newMesh->resizeVertices( newVertices.size() );
// newMesh->setVertices( newVertices );
// newMesh->resizeTriangles( cit->second.second.size() );
// newMesh->setTriangles( cit->second.second );
// result->push_back( newMesh );
// }
//
// return result;
//}
//
//boost::shared_ptr< std::set< size_t > > tm_utils::intersection( const WTriangleMesh& mesh, const WPlane& plane )
//{
// boost::shared_ptr< std::set< size_t > > result( new std::set< size_t > );
// const std::vector< wmath::WPosition >& vertices = mesh.getVertices();
// const std::vector< Triangle >& triangles = mesh.getTriangles();
// for( std::vector< Triangle >::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle )
// {
// if( wmath::testIntersectTriangle( vertices[ triangle->pointID[0] ],
// vertices[ triangle->pointID[1] ],
// vertices[ triangle->pointID[2] ],
// plane ) )
// {
// result->insert( triangle->pointID[0] );
// result->insert( triangle->pointID[1] );
// result->insert( triangle->pointID[2] );
// }
// }
//
// return result;
//}
......@@ -28,15 +28,17 @@
#include <algorithm>
#include <iostream>
#include <list>
#include <set>
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>
#include "../math/WLine.h"
#include "../math/WPlane.h"
#include "../math/WPosition.h"
#include "../math/WVector3D.h"
#include "../WTransferable.h"
#include "../WExportCommon.h"
/**
......
//---------------------------------------------------------------------------
//
// Project: OpenWalnut ( http://www.openwalnut.org )
//
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
// For more information see http://www.openwalnut.org/copying
//
// This file is part of OpenWalnut.
//
// OpenWalnut is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenWalnut is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License