教程:3D插件类
本节包含对 PLUGIN_3D 类的两个非常简单的插件的描述,并引导用户完成代码的设置和构建。
基本的 3D 插件
本教程将引导用户开发一个名为 “PLUGIN_3D_DEMO1” 的非常基本的 3D 插件。 本教程的目的只是为了演示一个非常基本的 3D 插件的构造,除了提供一些允许 KiCad 用户在浏览 3D 模型时过滤文件名的过滤字符串之外什么都不做。 这里演示的代码是任何 3D 插件的绝对最低要求,可以用作创建更高级插件的模板。
为了构建演示项目,我们需要以下内容:
KiCad 插件头
KiCad 场景图库 ‘kicad_3dsg’
要自动检测 KiCad 标头和库,我们将使用 CMake FindPackage 脚本; 如果相关的头文件安装到 ‘${KICAD_ROOT_DIR}/kicad’ 并且 KiCad Scene Graph 库安装在 ‘${KICAD_ROOT_DIR}/lib’ 中,则本教程中提供的脚本应该适用于 Linux 和 Windows。
要开始,让我们创建一个项目目录和 FindPackage 脚本:
mkdir demo && cd demo
export DEMO_ROOT=${PWD}
mkdir CMakeModules && cd CMakeModules
cat > FindKICAD.cmake << _EOF
find_path( KICAD_INCLUDE_DIR kicad/plugins/kicad_plugin.h
PATHS ${KICAD_ROOT_DIR}/include $ENV{KICAD_ROOT_DIR}/include
DOC "Kicad plugins header path."
)
if( NOT ${KICAD_INCLUDE_DIR} STREQUAL "KICAD_INCLUDE_DIR-NOTFOUND" )
# 尝试从 sg_version.h 中提取版本信息
# attempt to extract the version information from sg_version.h
find_file( KICAD_SGVERSION sg_version.h
PATHS ${KICAD_INCLUDE_DIR}
PATH_SUFFIXES kicad/plugins/3dapi
NO_DEFAULT_PATH )
if( NOT ${KICAD_SGVERSION} STREQUAL "KICAD_SGVERSION-NOTFOUND" )
# 提取 “#define KICADSG_VERSION*” 行
# extract the "#define KICADSG_VERSION*" lines
file( STRINGS ${KICAD_SGVERSION} _version REGEX "^#define.*KICADSG_VERSION.*" )
foreach( SVAR ${_version} )
string( REGEX MATCH KICADSG_VERSION_[M,A,J,O,R,I,N,P,T,C,H,E,V,I,S]* _VARNAME ${SVAR} )
string( REGEX MATCH [0-9]+ _VALUE ${SVAR} )
if( NOT ${_VARNAME} STREQUAL "" AND NOT ${_VALUE} STREQUAL "" )
set( _${_VARNAME} ${_VALUE} )
endif()
endforeach()
# 确保 NOT SG3D_VERSION* 的计算结果为 “0”
# ensure that NOT SG3D_VERSION* will evaluate to '0'
if( NOT _KICADSG_VERSION_MAJOR )
set( _KICADSG_VERSION_MAJOR 0 )
endif()
if( NOT _KICADSG_VERSION_MINOR )
set( _KICADSG_VERSION_MINOR 0 )
endif()
if( NOT _KICADSG_VERSION_PATCH )
set( _KICADSG_VERSION_PATCH 0 )
endif()
if( NOT _KICADSG_VERSION_REVISION )
set( _KICADSG_VERSION_REVISION 0 )
endif()
set( KICAD_VERSION ${_KICADSG_VERSION_MAJOR}.${_KICADSG_VERSION_MINOR}.${_KICADSG_VERSION_PATCH}.${_KICADSG_VERSION_REVISION} )
unset( KICAD_SGVERSION CACHE )
endif()
endif()
find_library( KICAD_LIBRARY
NAMES kicad_3dsg
PATHS
${KICAD_ROOT_DIR}/lib $ENV{KICAD_ROOT_DIR}/lib
${KICAD_ROOT_DIR}/bin $ENV{KICAD_ROOT_DIR}/bin
DOC "Kicad scenegraph library path."
)
include( FindPackageHandleStandardArgs )
FIND_PACKAGE_HANDLE_STANDARD_ARGS( KICAD
REQUIRED_VARS
KICAD_INCLUDE_DIR
KICAD_LIBRARY
KICAD_VERSION
VERSION_VAR KICAD_VERSION )
mark_as_advanced( KICAD_INCLUDE_DIR )
set( KICAD_VERSION_MAJOR ${_KICADSG_VERSION_MAJOR} CACHE INTERNAL "" )
set( KICAD_VERSION_MINOR ${_KICADSG_VERSION_MINOR} CACHE INTERNAL "" )
set( KICAD_VERSION_PATCH ${_KICADSG_VERSION_PATCH} CACHE INTERNAL "" )
set( KICAD_VERSION_TWEAK ${_KICADSG_VERSION_REVISION} CACHE INTERNAL "" )
_EOF
必须安装 Kicad 及其插件标头; 如果将它们安装到用户目录或 Linux 上的 ‘/opt’ 下,或者您使用的是 Windows,则需要将 ‘KICAD_ROOT_DIR’ 环境变量设置为指向包含 KiCad ‘include’ 和 ‘lib’ 目录的目录。 对于 OS X,此处显示的 FindPackage 脚本可能需要进行一些调整。
要配置和构建教程代码,我们将使用 CMake 并创建一个 ‘CMakeLists.txt’ 脚本文件:
cd ${DEMO_ROOT}
cat > CMakeLists.txt << _EOF
# 声明项目名称
# declare the name of the project
project( PLUGIN_DEMO )
# 检查我们是否有具有所有必需功能的 CMake 版本
# check that we have a version of CMake with all required features
cmake_minimum_required( VERSION 2.8.12 FATAL_ERROR )
# 通知 CMake 何处可以找到 FindKICAD 脚本
# inform CMake of where to find the FindKICAD script
set( CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules )
# 尝试发现已安装的 kicad 标头和库
# attempt to discover the installed kicad headers and library
# 并设置变量: (and set the variables:)
# KICAD_INCLUDE_DIR
# KICAD_LIBRARY
find_package( KICAD 1.0 REQUIRED )
# 将 kicad include 目录添加到编译器的搜索路径中
# add the kicad include directory to the compiler's search path
include_directories( ${KICAD_INCLUDE_DIR}/kicad )
# 创建名为 s3D_plugin_demo1 的插件
# create a plugin named s3d_plugin_demo1
add_library( s3d_plugin_demo1 MODULE
src/s3d_plugin_demo1.cpp
)
_EOF
第一个演示项目非常基础; 它由一个文件组成,除了编译器默认值之外没有外部链接依赖项。 我们首先创建一个源目录:
cd ${DEMO_ROOT}
mkdir src && cd src
export DEMO_SRC=${PWD}
现在我们自己创建插件源:
s3d_plugin_demo1.cpp
#include <iostream>
// 3d_plugin.h 标头定义了 3D 插件所需的功能
// the 3d_plugin.h header defines the functions required of 3D plugins
#include "plugins/3d/3d_plugin.h"
// 定义这个插件的版本信息,不要混淆
// define the version information of this plugin; do not confuse this
// 使用在 3d_plugin.h 中定义的插件类版本
// with the Plugin Class version which is defined in 3d_plugin.h
#define PLUGIN_3D_DEMO1_MAJOR 1
#define PLUGIN_3D_DEMO1_MINOR 0
#define PLUGIN_3D_DEMO1_PATCH 0
#define PLUGIN_3D_DEMO1_REVNO 0
// 实现为用户提供此插件名称的函数
// implement the function which provides users with this plugin's name
const char* GetKicadPluginName( void )
{
return "PLUGIN_3D_DEMO1";
}
// 实现为用户提供此插件版本的功能
// implement the function which provides users with this plugin's version
void GetPluginVersion( unsigned char* Major, unsigned char* Minor,
unsigned char* Patch, unsigned char* Revision )
{
if( Major )
*Major = PLUGIN_3D_DEMO1_MAJOR;
if( Minor )
*Minor = PLUGIN_3D_DEMO1_MINOR;
if( Patch )
*Patch = PLUGIN_3D_DEMO1_PATCH;
if( Revision )
*Revision = PLUGIN_3D_DEMO1_REVNO;
return;
}
// 支持的扩展名数量;在 *NIX 系统上,扩展名为
// number of extensions supported; on *NIX systems the extensions are
// 提供两次-一次小写,一次大写
// provided twice - once in lower case and once in upper case letters
#ifdef _WIN32
#define NEXTS 7
#else
#define NEXTS 14
#endif
// 支持的筛选器集数量
// number of filter sets supported
#define NFILS 5
// 定义此扩展字符串和筛选器字符串
// define the extension strings and filter strings which this
// 插件将提供给用户
// plugin will supply to the user
static char ext0[] = "wrl";
static char ext1[] = "x3d";
static char ext2[] = "emn";
static char ext3[] = "iges";
static char ext4[] = "igs";
static char ext5[] = "stp";
static char ext6[] = "step";
#ifdef _WIN32
static char fil0[] = "VRML 1.0/2.0 (*.wrl)|*.wrl";
static char fil1[] = "X3D (*.x3d)|*.x3d";
static char fil2[] = "IDF 2.0/3.0 (*.emn)|*.emn";
static char fil3[] = "IGESv5.3 (*.igs;*.iges)|*.igs;*.iges";
static char fil4[] = "STEP (*.stp;*.step)|*.stp;*.step";
#else
static char ext7[] = "WRL";
static char ext8[] = "X3D";
static char ext9[] = "EMN";
static char ext10[] = "IGES";
static char ext11[] = "IGS";
static char ext12[] = "STP";
static char ext13[] = "STEP";
static char fil0[] = "VRML 1.0/2.0 (*.wrl;*.WRL)|*.wrl;*.WRL";
static char fil1[] = "X3D (*.x3d;*.X3D)|*.x3d;*.X3D";
static char fil2[] = "IDF 2.0/3.0 (*.emn;*.EMN)|*.emn;*.EMN";
static char fil3[] = "IGESv5.3 (*.igs;*.iges;*.IGS;*.IGES)|*.igs;*.iges;*.IGS;*.IGES";
static char fil4[] = "STEP (*.stp;*.step;*.STP;*.STEP)|*.stp;*.step;*.STP;*.STEP";
#endif
// 实例化一个方便的数据结构来访问
// instantiate a convenient data structure for accessing the
// 扩展和筛选字符串列表
// lists of extension and filter strings
static struct FILE_DATA
{
char const* extensions[NEXTS];
char const* filters[NFILS];
FILE_DATA()
{
extensions[0] = ext0;
extensions[1] = ext1;
extensions[2] = ext2;
extensions[3] = ext3;
extensions[4] = ext4;
extensions[5] = ext5;
extensions[6] = ext6;
filters[0] = fil0;
filters[1] = fil1;
filters[2] = fil2;
filters[3] = fil3;
filters[4] = fil4;
#ifndef _WIN32
extensions[7] = ext7;
extensions[8] = ext8;
extensions[9] = ext9;
extensions[10] = ext10;
extensions[11] = ext11;
extensions[12] = ext12;
extensions[13] = ext13;
#endif
return;
}
} file_data;
// 返回该插件支持的扩展数量
// return the number of extensions supported by this plugin
int GetNExtensions( void )
{
return NEXTS;
}
// 返回索引的扩展字符串
// return the indexed extension string
char const* GetModelExtension( int aIndex )
{
if( aIndex < 0 || aIndex >= NEXTS )
return NULL;
return file_data.extensions[aIndex];
}
// 返回该插件提供的筛选字符串数量
// return the number of filter strings provided by this plugin
int GetNFilters( void )
{
return NFILS;
}
// 返回索引筛选字符串
// return the indexed filter string
char const* GetFileFilter( int aIndex )
{
if( aIndex < 0 || aIndex >= NFILS )
return NULL;
return file_data.filters[aIndex];
}
// 返回 false,因为此插件不提供可视化数据
// return false since this plugin does not provide visualization data
bool CanRender( void )
{
return false;
}
// 返回 null,因为此插件不提供可视化数据
// return NULL since this plugin does not provide visualization data
SCENEGRAPH* Load( char const* aFileName )
{
// 此伪插件不支持渲染任何模型
// this dummy plugin does not support rendering of any models
return NULL;
}
此源文件满足实现 3D 插件的所有最低要求。 该插件不会为渲染模型生成任何数据,但它可以为 KiCad 提供支持的模型文件扩展名和文件扩展名过滤器列表,以增强 3D 模型文件选择对话框。 在 KiCad 中,扩展字符串用于选择可用于加载指定模型的插件; 例如,如果插件是 ‘wrl’,那么 KiCad 将调用声称支持扩展 ‘wrl’ 的每个插件,直到插件返回可视化数据。 每个插件提供的文件过滤器将传递到 3D 文件选择器对话框,以改善浏览 UI。
要构建插件:
cd ${DEMO_ROOT}
# export KICAD_ROOT_DIR if necessary
mkdir build && cd build
cmake .. && make
该插件将被构建但未安装; 如果要加载插件,必须将插件文件复制到 KiCad 的插件目录。
高级 3D 插件
本教程将引导用户开发名为 “PLUGIN_3D_DEMO2” 的 3D 插件。 本教程的目的是演示 KiCad 预览器可以渲染的非常基本的场景图的构造。 该插件声称处理 ‘txt’ 类型的文件。 虽然文件必须存在,以便缓存管理器调用插件,但此插件不处理文件内容; 相反,插件只是创建一个包含一对四面体的场景图。 本教程假定第一个教程已完成,并且已创建 CMakeLists.txt 和 FindKICAD.cmake 脚本文件。
将新的源文件放在与上一个教程的源文件相同的目录中,我们将扩展上一个教程的 CMakeLists.txt 文件来构建本教程。 由于这个插件会为 KiCad 创建一个场景图,我们需要链接到 KiCad 的场景图库 ‘kicad_3dsg’。 KiCad 的场景图库提供了一组可用于构建场景图对象的类; 场景图对象是 3D 缓存管理器使用的中间数据可视化格式。 所有支持模型可视化的插件都必须通过此库将模型数据转换为场景图。
第一步是扩展 ‘CMakeLists.txt’ 来构建这个教程项目:
cd ${DEMO_ROOT}
cat >> CMakeLists.txt << _EOF
add_library( s3d_plugin_demo2 MODULE
src/s3d_plugin_demo2.cpp
)
target_link_libraries( s3d_plugin_demo2 ${KICAD_LIBRARY} )
_EOF
现在我们切换到源目录并创建源文件:
cd ${DEMO_SRC}
s3d_plugin_demo2.cpp
#include <cmath>
// 3D 插件类声明
// 3D Plugin Class declarations
#include "plugins/3d/3d_plugin.h"
// KiCad 场景图形库接口
// interface to KiCad Scene Graph Library
#include "plugins/3dapi/ifsg_all.h"
// 该插件的版本信息
// version information for this plugin
#define PLUGIN_3D_DEMO2_MAJOR 1
#define PLUGIN_3D_DEMO2_MINOR 0
#define PLUGIN_3D_DEMO2_PATCH 0
#define PLUGIN_3D_DEMO2_REVNO 0
// 提供此插件的名称
// provide the name of this plugin
const char* GetKicadPluginName( void )
{
return "PLUGIN_3D_DEMO2";
}
// 提供此插件的版本
// provide the version of this plugin
void GetPluginVersion( unsigned char* Major, unsigned char* Minor,
unsigned char* Patch, unsigned char* Revision )
{
if( Major )
*Major = PLUGIN_3D_DEMO2_MAJOR;
if( Minor )
*Minor = PLUGIN_3D_DEMO2_MINOR;
if( Patch )
*Patch = PLUGIN_3D_DEMO2_PATCH;
if( Revision )
*Revision = PLUGIN_3D_DEMO2_REVNO;
return;
}
// 支持的扩展数量
// number of extensions supported
#ifdef _WIN32
#define NEXTS 1
#else
#define NEXTS 2
#endif
// 支持的筛选器集数量
// number of filter sets supported
#define NFILS 1
static char ext0[] = "txt";
#ifdef _WIN32
static char fil0[] = "demo (*.txt)|*.txt";
#else
static char ext1[] = "TXT";
static char fil0[] = "demo (*.txt;*.TXT)|*.txt;*.TXT";
#endif
static struct FILE_DATA
{
char const* extensions[NEXTS];
char const* filters[NFILS];
FILE_DATA()
{
extensions[0] = ext0;
filters[0] = fil0;
#ifndef _WIN32
extensions[1] = ext1;
#endif
return;
}
} file_data;
int GetNExtensions( void )
{
return NEXTS;
}
char const* GetModelExtension( int aIndex )
{
if( aIndex < 0 || aIndex >= NEXTS )
return NULL;
return file_data.extensions[aIndex];
}
int GetNFilters( void )
{
return NFILS;
}
char const* GetFileFilter( int aIndex )
{
if( aIndex < 0 || aIndex >= NFILS )
return NULL;
return file_data.filters[aIndex];
}
// 返回 true,因为此插件可以提供可视化数据
// return true since this plugin can provide visualization data
bool CanRender( void )
{
return true;
}
// 创建可视化数据
// create the visualization data
SCENEGRAPH* Load( char const* aFileName )
{
// 对于本演示,我们创建一个四面体( TX1),包括
// For this demonstration we create a tetrahedron (tx1) consisting
// SCENEGRAPH (VRML 变换),它依次包含 4
// of a SCENEGRAPH (VRML Transform) which in turn contains 4
// SGSHAPE (VRML Shape) 对象表示
// SGSHAPE (VRML Shape) objects representing each of the sides of
// 四面体。每个形状都与一种颜色关联 (SGAPPEARANCE)
// the tetrahedron. Each Shape is associated with a color (SGAPPEARANCE)
// 和一个 SGFACESET (VRML Geometry->indexedFaceSet)。每个 SGFACESET 是
// and a SGFACESET (VRML Geometry->indexedFaceSet). Each SGFACESET is
// 与顶点列表 (SGCOORDS) 关联,每顶点法线
// associated with a vertex list (SGCOORDS), a per-vertex normals
// list(SGNORMALS) 和坐标索引 (SGCOORDINDEX)。一个形状
// list (SGNORMALS), and a coordinate index (SGCOORDINDEX). One shape
// 用于表示每个面,以便我们可以使用逐顶点逐面
// is used to represent each face so that we may use per-vertex-per-face
// 法线。
// normals.
//
// 四面体又是顶级 SCENEGRAPH(Tx0) 的子级
// The tetrahedron in turn is a child of a top level SCENEGRAPH (tx0)
// 它有第二个 SCENEGRAPH子(TX2),这是一个转换
// which has a second SCENEGRAPH child (tx2) which is a transformation
// 四面体 TX1 (旋转+平移)。这表明
// of the tetrahedron tx1 (rotation + translation). This demonstrates
// 场景图层次结构中组件的重用。
// the reuse of components within the scene graph hierarchy.
// 定义四面体的顶点
// define the vertices of the tetrahedron
// 面 1:0,3,1
// face 1: 0, 3, 1
// 面 2: 0, 2, 3
// face 2: 0, 2, 3
// 面 3: 1, 3, 2
// face 3: 1, 3, 2
// 面 4: 0, 1, 2
// face 4: 0, 1, 2
double SQ2 = sqrt( 0.5 );
SGPOINT vert[4];
vert[0] = SGPOINT( 1.0, 0.0, -SQ2 );
vert[1] = SGPOINT( -1.0, 0.0, -SQ2 );
vert[2] = SGPOINT( 0.0, 1.0, SQ2 );
vert[3] = SGPOINT( 0.0, -1.0, SQ2 );
// 创建顶级转换;这将保存所有其他
// create the top level transform; this will hold all other
// Scenegraph 对象;转换可能包含其他转换和
// scenegraph objects; a transform may hold other transforms and
// 形状
// shapes
IFSG_TRANSFORM* tx0 = new IFSG_TRANSFORM( true );
// 创建将容纳形状的转换
// create the transform which will house the shapes
IFSG_TRANSFORM* tx1 = new IFSG_TRANSFORM( tx0->GetRawPtr() );
// 添加一个形状,我们将使用该形状定义四面体的一个面;
// add a shape which we will use to define one face of the tetrahedron;
// 形状保存 facesets 和外观
// shapes hold facesets and appearances
IFSG_SHAPE* shape = new IFSG_SHAPE( *tx1 );
// 添加 faceset;这些包含坐标列表,坐标索引,
// add a faceset; these contain coordinate lists, coordinate indices,
// 顶点列表、顶点索引,还可能包含颜色列表和
// vertex lists, vertex indices, and may also contain color lists and
// 它们的索引。
// their indices.
IFSG_FACESET* face = new IFSG_FACESET( *shape );
IFSG_COORDS* cp = new IFSG_COORDS( *face );
cp->AddCoord( vert[0] );
cp->AddCoord( vert[3] );
cp->AddCoord( vert[1] );
// 坐标索引-注意:强制三角形;
// coordinate indices - note: enforce triangles;
// 在不一定可能的实际插件中
// in real plugins where it is not necessarily possible
// 若要确定三角形从哪一侧可见,请执行以下操作:
// to determine which side a triangle is visible from,
// 2 必须为每个三角形指定点序
// 2 point orders must be specified for each triangle
IFSG_COORDINDEX* coordIdx = new IFSG_COORDINDEX( *face );
coordIdx->AddIndex( 0 );
coordIdx->AddIndex( 1 );
coordIdx->AddIndex( 2 );
// 创建外观;外观归形状所有
// create an appearance; appearances are owned by shapes
// 文件名将更改为
// magenta
IFSG_APPEARANCE* material = new IFSG_APPEARANCE( *shape);
material->SetSpecular( 0.1, 0.0, 0.1 );
material->SetDiffuse( 0.8, 0.0, 0.8 );
material->SetAmbient( 0.2, 0.2, 0.2 );
material->SetShininess( 0.2 );
// 法线
// normals
IFSG_NORMALS* np = new IFSG_NORMALS( *face );
SGVECTOR nval = S3D::CalcTriNorm( vert[0], vert[3], vert[1] );
np->AddNormal( nval );
np->AddNormal( nval );
np->AddNormal( nval );
//
// 形状 2
// Shape2
// 注意:我们重用 IFSG* 包装器来创建和操作新的
// Note: we reuse the IFSG* wrappers to create and manipulate new
// 数据结构。
// data structures.
//
shape->NewNode( *tx1 );
face->NewNode( *shape );
coordIdx->NewNode( *face );
cp->NewNode( *face );
np->NewNode( *face );
// 顶点
// vertices
cp->AddCoord( vert[0] );
cp->AddCoord( vert[2] );
cp->AddCoord( vert[3] );
// 索引
// indices
coordIdx->AddIndex( 0 );
coordIdx->AddIndex( 1 );
coordIdx->AddIndex( 2 );
// 法线
// normals
nval = S3D::CalcTriNorm( vert[0], vert[2], vert[3] );
np->AddNormal( nval );
np->AddNormal( nval );
np->AddNormal( nval );
// 颜色 (红色)
// color (red)
material->NewNode( *shape );
material->SetSpecular( 0.2, 0.0, 0.0 );
material->SetDiffuse( 0.9, 0.0, 0.0 );
material->SetAmbient( 0.2, 0.2, 0.2 );
material->SetShininess( 0.1 );
//
// 形状 3
// Shape3
//
shape->NewNode( *tx1 );
face->NewNode( *shape );
coordIdx->NewNode( *face );
cp->NewNode( *face );
np->NewNode( *face );
// 顶点
// vertices
cp->AddCoord( vert[1] );
cp->AddCoord( vert[3] );
cp->AddCoord( vert[2] );
// 索引
// indices
coordIdx->AddIndex( 0 );
coordIdx->AddIndex( 1 );
coordIdx->AddIndex( 2 );
// 法线
// normals
nval = S3D::CalcTriNorm( vert[1], vert[3], vert[2] );
np->AddNormal( nval );
np->AddNormal( nval );
np->AddNormal( nval );
// 颜色 (绿色)
// color (green)
material->NewNode( *shape );
material->SetSpecular( 0.0, 0.1, 0.0 );
material->SetDiffuse( 0.0, 0.9, 0.0 );
material->SetAmbient( 0.2, 0.2, 0.2 );
material->SetShininess( 0.1 );
//
// 形状 4
// Shape4
//
shape->NewNode( *tx1 );
face->NewNode( *shape );
coordIdx->NewNode( *face );
cp->NewNode( *face );
np->NewNode( *face );
// 顶点
// vertices
cp->AddCoord( vert[0] );
cp->AddCoord( vert[1] );
cp->AddCoord( vert[2] );
// 索引
// indices
coordIdx->AddIndex( 0 );
coordIdx->AddIndex( 1 );
coordIdx->AddIndex( 2 );
// 法线
// normals
nval = S3D::CalcTriNorm( vert[0], vert[1], vert[2] );
np->AddNormal( nval );
np->AddNormal( nval );
np->AddNormal( nval );
// 颜色 (蓝色)
// color (blue)
material->NewNode( *shape );
material->SetSpecular( 0.0, 0.0, 0.1 );
material->SetDiffuse( 0.0, 0.0, 0.9 );
material->SetAmbient( 0.2, 0.2, 0.2 );
material->SetShininess( 0.1 );
// 创建整个四面体移位 Z+2 并旋转 2/3PI 的副本
// create a copy of the entire tetrahedron shifted Z+2 and rotated 2/3PI
IFSG_TRANSFORM* tx2 = new IFSG_TRANSFORM( tx0->GetRawPtr() );
tx2->AddRefNode( *tx1 );
tx2->SetTranslation( SGPOINT( 0, 0, 2 ) );
tx2->SetRotation( SGVECTOR( 0, 0, 1 ), M_PI*2.0/3.0 );
SGNODE* data = tx0->GetRawPtr();
// 删除包装器
// delete the wrappers
delete shape;
delete face;
delete coordIdx;
delete material;
delete cp;
delete np;
delete tx0;
delete tx1;
delete tx2;
return (SCENEGRAPH*)data;
}