跨平台路径统一,glb格式模型动画播放

This commit is contained in:
Hector 2025-08-11 17:20:11 +08:00
parent 21cd80076e
commit 6a7e6e5657
837 changed files with 137428 additions and 404 deletions

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,8 @@ Description :
import sys
import os
from core.CustomMouseController import CustomMouseController
# 获取 RenderPipelineFile 的路径
render_pipeline_path = './RenderPipelineFile'
# 将该路径添加到 sys.path 中,确保 Python 能够找到它
@ -39,7 +41,7 @@ import platform
# Local imports
from QPanda3D.QMouseWatcherNode import QMouseWatcherNode
from rpcore.render_target import RenderTarget
from RenderPipelineFile.rpcore.render_target import RenderTarget
__all__ = ["Panda3DWorld"]
_global_world_instance=None
@ -51,7 +53,7 @@ class Panda3DWorld(ShowBase):
Panda3DWorld : A class to handle all panda3D world manipulation
"""
def __init__(self, width=1240, height=720, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1),
def __init__(self, width=1254, height=729, is_fullscreen=False, size=1.0, clear_color=LVecBase4f(0, 0.5, 0, 1),
name="qpanda3D"):
global _global_world_instance
@ -61,39 +63,34 @@ class Panda3DWorld(ShowBase):
sort = -100
self.parent = None
# self.width = width
# self.height = height
loadPrcFileData("", "win-size {} {}".format(width, height))
# 设置基本配置
loadPrcFileData("", "show-frame-rate-meter 0")
loadPrcFileData("", "window-type offscreen") # 设置为离屏渲染
loadPrcFileData("", f"win-size {width} {height}")
#loadPrcFileData("", "sync-video #t")
#loadPrcFileData("", "force-flush #t")
loadPrcFileData("", "win-fixed-size #f") # 允许窗口调整大小
if (is_fullscreen):
loadPrcFileData("", "fullscreen #t")
else:
loadPrcFileData("", "window-type offscreen") # Set Panda to draw its main window in an offscreen buffer
#loadPrcFileData("", "show-frame-rate-meter 0") # 可选
self.render_pipeline = RenderPipeline()
self.render_pipeline.pre_showbase_init()
ShowBase.__init__(self)
# 初始化渲染管线并设置可调整大小的标志
self.render_pipeline.create(self)
_global_render_pipeline = self.render_pipeline
#from RenderPipelineFile.toolkit.material_editor.main import MaterialEditor
#self.screenTexture = self.render_pipeline._final_stage.target.color_tex
#self.screenTexture.setComponentType(Texture.T_unsigned_byte)
#self.screenTexture.setFormat(Texture.F_rgba8)
# 创建 Qt 能读的 RGBA8 贴图
self.qt_output_tex = Texture("qt_output_tex")
self.qt_output_tex.set_format(Texture.F_rgba8)
self.qt_output_tex.set_component_type(Texture.T_unsigned_byte)
#self.screenTexture.set_keep_ram_image(True)
# 添加到主窗口渲染输出,并指定复制回 CPU
# # 获取图形管线对象
# gsg = self.win.getGsg()
# host = gsg.getEngine().getHost()
self.win.add_render_texture(self.qt_output_tex, GraphicsOutput.RTM_copy_ram)
#self.screenTexture = Texture()
@ -137,21 +134,23 @@ class Panda3DWorld(ShowBase):
#render_pipeline.set_camera(self.cam)
#添加渲染效果
#添加渲染效果<EFBFBD><EFBFBD>
self.cam = self.render_pipeline._showbase.cam
self.camNode = self.cam.node()
self.camLens = self.camNode.get_lens()
self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam
#self.render_pipeline.daytime_mgr.update()
#
# if clear_color is None:
# self.buff.set_clear_active(GraphicsOutput.RTPColor, False)
# else:
# self.buff.set_clear_color(clear_color)
# self.buff.set_clear_active(GraphicsOutput.RTPColor, True)
self.disableMouse()
# self.disableMouse()
self.mouse_controller = CustomMouseController(self)
self.mouse_controller.setUp()
def render_pipeline(self):
"""获取 RenderPipeline 实例"""
@ -175,6 +174,20 @@ def get_render_pipeline():
"RenderPipeline has not been initialized yet. Please create a Panda3DWorld instance first.")
return _global_render_pipeline
def resize_buffer(self, width: int, height: int):
"""根据新窗口尺寸调整 Panda3D 渲染输出尺寸"""
# 设置 Panda3D 的窗口尺寸offscreen 模式下对应输出区域)
props = WindowProperties()
props.set_size(width, height)
self.win.request_properties(props)
# 重新分配输出贴图的大小
self.qt_output_tex.set_x_size(width)
self.qt_output_tex.set_y_size(height)
# 更新 lens 的 aspect ratio
if self.camLens:
self.camLens.set_film_size(width, height) # 或 set_aspect_ratio(width / height)
# 强制更新窗口(有时在 Qt 内嵌时需要)
self.graphicsEngine.open_windows()

View File

@ -158,23 +158,19 @@ class QPanda3DWidget(QWidget):
print("Unimplemented key. Please send an issue on github to fix this problem")
def resizeEvent(self, evt):
# new_width = evt.size().width()
# new_height = evt.size().height()
#
# # 更新 Panda3D 相机的 Film Size保持视野正常
# lens = self.panda3DWorld.cam.node().get_lens()
# lens.set_film_size(
# self.initial_film_size.width() * new_width / self.initial_size.width(),
# self.initial_film_size.height() * new_height / self.initial_size.height()
# )
#
# if hasattr(self.panda3DWorld, "win"):
# winprops = WindowProperties()
# winprops.set_size(new_width, new_height)
# self.panda3DWorld.win.request_properties(winprops)
width = evt.size().width()
height = evt.size().height()
print(f"width:{width}")
print(f"height:{height}")
from Panda3DWorld import resize_buffer
#resize_buffer(width, height)
lens = self.panda3DWorld.cam.node().get_lens()
lens.set_film_size(self.initial_film_size.width() * evt.size().width() / self.initial_size.width(),
self.initial_film_size.height() * evt.size().height() / self.initial_size.height())
# lens.set_film_size(self.initial_film_size.width() * evt.size().width() / self.initial_size.width(),
# self.initial_film_size.height() * evt.size().height() / self.initial_size.height())
#self.sync_panda3d_window_size(width, height)
#self.panda3DWorld.buff.setSize(evt.size().width(), evt.size().height())
def minimumSizeHint(self):
@ -225,33 +221,112 @@ class QPanda3DWidget(QWidget):
# self.rp_sync_requested = False
# self.update()
# def paintEvent(self, event):
# tex = self.panda3DWorld.qt_output_tex
#
# gsg = base.win.getGsg()
#
# if not tex.hasRamImage():
# base.graphicsEngine.extractTextureData(tex, gsg)
# self.update() # 请求下一帧更新
# return
#
# data = tex.getRamImage().getData()
# width = tex.getXSize()
# height = tex.getYSize()
# expected_len = width * height * 4
#
# if len(data) != expected_len:
# print(f"⚠️ 像素数据长度异常({len(data)} != {expected_len}),跳过绘制")
# self.update()
# return
#
# # 一切正常才绘制
# img = QImage(data, width, height, QImage.Format_ARGB32).mirrored()
#
# painter = QPainter(self)
# painter.drawImage(0, 0, img)
# painter.end()
def paintEvent(self, event):
tex = self.panda3DWorld.qt_output_tex
gsg = base.win.getGsg()
if not gsg:
self.update()
return
if not tex.hasRamImage():
base.graphicsEngine.extractTextureData(tex, gsg)
self.update() # 请求下一帧更新
self.update()
return
data = tex.getRamImage().getData()
width = tex.getXSize()
height = tex.getYSize()
expected_len = width * height * 4
tex_width = tex.getXSize()
tex_height = tex.getYSize()
expected_len = tex_width * tex_height * 4
if len(data) != expected_len:
print(f"⚠️ 像素数据长度异常({len(data)} != {expected_len}),跳过绘制")
self.update()
return
# 一切正常才绘制
img = QImage(data, width, height, QImage.Format_ARGB32).mirrored()
img = QImage(data, tex_width, tex_height, QImage.Format_ARGB32).mirrored()
widget_width = self.width()
widget_height = self.height()
painter = QPainter(self)
painter.drawImage(0, 0, img)
# 【保持宽高比的缩放】
scaled_img = img.scaled(widget_width, widget_height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
# 居中绘制
x_offset = (widget_width - scaled_img.width()) // 2
y_offset = (widget_height - scaled_img.height()) // 2
painter.drawImage(x_offset, y_offset, scaled_img)
painter.end()
def sync_panda3d_window_size(self, width, height):
"""同步 Panda3D 窗口尺寸到 Qt 窗口尺寸"""
try:
from panda3d.core import WindowProperties, LVecBase2i
# 确保尺寸是4的倍数RenderPipeline 要求)
adjusted_width = width - width % 4
adjusted_height = height - height % 4
# 更新窗口属性
props = WindowProperties()
props.setSize(adjusted_width, adjusted_height)
# 对于 offscreen 渲染,直接更新窗口属性
if self.panda3DWorld.win:
self.panda3DWorld.win.request_properties(props)
# 手动触发 RenderPipeline 的尺寸更新逻辑
if hasattr(self.panda3DWorld, 'render_pipeline'):
# 更新全局分辨率
from RenderPipelineFile.rpcore.globals import Globals
Globals.native_resolution = LVecBase2i(adjusted_width, adjusted_height)
# 重新计算渲染分辨率
self.panda3DWorld.render_pipeline._compute_render_resolution()
# 通知各个管理器处理尺寸变化
self.panda3DWorld.render_pipeline.light_mgr.compute_tile_size()
self.panda3DWorld.render_pipeline.stage_mgr.handle_window_resize()
if hasattr(self.panda3DWorld.render_pipeline, 'debugger'):
self.panda3DWorld.render_pipeline.debugger.handle_window_resize()
# 触发插件的窗口尺寸变化钩子
self.panda3DWorld.render_pipeline.plugin_mgr.trigger_hook("window_resized")
print(f"Panda3D 窗口尺寸已同步为: {adjusted_width} x {adjusted_height}")
except Exception as e:
print(f"同步 Panda3D 窗口尺寸失败: {str(e)}")

File diff suppressed because one or more lines are too long

View File

@ -54,7 +54,7 @@ class MainApp(ShowBase):
# model = loader.loadModel("scene2/Scene.bam")
model_0 = self.loader.loadModel("/home/tiger/下载/Benci/source/s65/s65/s65.fbx")
model_0.reparentTo(self.render)
model_0.setScale(0.25)
model_0.setScale(0.01)
model_0.setPos(-8, 42, 0)
model_0.setHpr(0, 90, 0)

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,96 @@
from PyQt5.QtCore import showbase
from direct.task.TaskManagerGlobal import taskMgr
class CustomMouseController:
def __init__(self, showbase):
self.showbase = showbase
# This is used to store which keys are currently pressed.
self.keyMap = {
"mouse1": 0,
"cam-forward": 0,
"cam-backward": 0,
"cam-left": 0,
"cam-right": 0,
"cam-up": 0,
"cam-down": 0
}
# 添加鼠标控制
# self.showbase.accept("mouse1", self.setKey, ["mouse1", True])
# self.showbase.accept("mouse1-up", self.setKey, ["mouse1", False])
self.showbase.accept("w", self.setKey, ["cam-forward", True])
self.showbase.accept("a", self.setKey, ["cam-left", True])
self.showbase.accept("s", self.setKey, ["cam-backward", True])
self.showbase.accept("d", self.setKey, ["cam-right", True])
self.showbase.accept("e", self.setKey, ["cam-up", True])
self.showbase.accept("q", self.setKey, ["cam-down", True])
self.showbase.accept("w-up", self.setKey, ["cam-forward", False])
self.showbase.accept("a-up", self.setKey, ["cam-left", False])
self.showbase.accept("s-up", self.setKey, ["cam-backward", False])
self.showbase.accept("d-up", self.setKey, ["cam-right", False])
self.showbase.accept("e-up", self.setKey, ["cam-up", False])
self.showbase.accept("q-up", self.setKey, ["cam-down", False])
self.last_mouse_x = 0
self.last_mouse_y = 0
self.camera_speed = 25
self.move_speed = 20
def setUp(self, mouse_speed: float = 25, move_speed: float = 20):
taskMgr.add(self.move, "moveTask")
self.camera_speed = mouse_speed
self.move_speed = move_speed
def setKey(self, key, value, arg: str = None):
self.keyMap[key] = value
if key == "mouse1" and value == True and self.keyMap[key]:
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
self.last_mouse_x = mouse_pos.get_x()
self.last_mouse_y = mouse_pos.get_y()
def move(self, task):
dt = self.showbase.clock.dt
if self.keyMap["cam-left"]:
self.showbase.camera.setX(self.showbase.camera, -self.move_speed * dt)
if self.keyMap["cam-right"]:
self.showbase.camera.setX(self.showbase.camera, +self.move_speed * dt)
if self.keyMap["cam-backward"]:
self.showbase.camera.setY(self.showbase.camera, -self.move_speed * dt)
if self.keyMap["cam-forward"]:
self.showbase.camera.setY(self.showbase.camera, +self.move_speed * dt)
if self.keyMap["cam-up"]:
self.showbase.camera.setZ(self.showbase.camera, +self.move_speed * dt)
if self.keyMap["cam-down"]:
self.showbase.camera.setZ(self.showbase.camera, -self.move_speed * dt)
if self.keyMap["mouse1"]:
try:
mouse_pos = self.showbase.mouseWatcherNode.getMouse()
current_x = mouse_pos.get_x()
current_y = mouse_pos.get_y()
# 计算鼠标移动差值
dx = current_x - self.last_mouse_x
dy = current_y - self.last_mouse_y
dy *= -1
# 根据鼠标移动调整摄像机
if abs(dx) > 0.01 or abs(dy) > 0.01:
cam_hpr = self.showbase.camera.get_hpr()
self.showbase.camera.set_hpr(
cam_hpr.x - dx * self.camera_speed,
max(-90, min(90, cam_hpr.y - dy * self.camera_speed)),
0
)
# 更新鼠标位置
self.last_mouse_x = current_x
self.last_mouse_y = current_y
except Exception as e:
print(f"旋转相机失败:{e}")
return task.cont

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -37,8 +37,207 @@ class CoreWorld(Panda3DWorld):
self._setupLighting()
self._setupGround()
self._loadFont()
#self.load_and_play_fbx_model()
print("✓ 核心世界初始化完成")
def load_and_play_fbx_model(self):
"""加载 glTF 模型并播放动画"""
try:
from direct.actor.Actor import Actor
# 使用 Actor 类加载 glTF 模型
self.model = Actor("/home/tiger/Women.glb")
print("模型加载成功!")
self.model.reparentTo(self.render)
self.model.setPos(0, 10, 0)
self.model.setScale(10)
# 列出所有可用动画
anims = self.model.getAnimNames()
print("可用动画:", anims)
# 播放特定动画
if anims:
self.model.loop('Armature|mixamo.com|Layer0')
except Exception as e:
print(f"模型加载失败: {e}")
return None
def diagnose_fbx_loading(self, fbx_path):
"""诊断FBX加载状态"""
print("=== FBX加载诊断 ===")
# 检查文件存在
import os
if not os.path.exists(fbx_path):
print("❌ FBX文件不存在")
return None
try:
# 尝试加载
actor = Actor(fbx_path)
# 检查动画
anims = actor.getAnimNames()
print(f"✓ 找到 {len(anims)} 个动画: {anims}")
# 检查PartBundle
bundles = actor.getPartBundles()
print(f"✓ 找到 {len(bundles)} 个PartBundle")
# 测试动画控制器
if anims:
control = actor.getAnimControl(anims[0])
if control:
print(f"✓ 动画控制器创建成功: {anims[0]}")
return actor
else:
print(f"❌ 动画控制器创建失败: {anims[0]}")
return actor
except Exception as e:
print(f"❌ FBX加载异常: {e}")
return None
def diagnose_actor_animation(self,model_path, anim_path, anim_name="walk"):
print("=== 开始诊断 Actor 动画问题 ===")
import os
from direct.actor.Actor import Actor
from panda3d.core import Loader
from panda3d.core import Filename
# 检查文件存在性
print(f"1. 文件检查:")
print(f" 模型文件存在: {os.path.exists(model_path)}")
print(f" 动画文件存在: {os.path.exists(anim_path)}")
if not os.path.exists(model_path):
print("❌ 模型文件不存在,请检查路径")
return None
if not os.path.exists(anim_path):
print("❌ 动画文件不存在,请检查路径")
return None
# 2. 检查模型结构
print(f"2. 模型结构检查:")
try:
# 先用普通loader加载检查结构
temp_model = self.loader.loadModel(model_path)
if temp_model is None:
print("❌ 无法加载模型文件")
return None
# 检查Character节点
bundleNP = temp_model.find("**/+Character")
if bundleNP.isEmpty():
print("❌ 模型不包含Character节点 - 这是动画模型必需的")
print(" 建议: 确保模型文件是正确的角色模型,包含骨骼结构")
return None
else:
print("✓ 模型包含Character节点")
temp_model.removeNode() # 清理临时模型
except Exception as e:
print(f"❌ 模型加载失败: {e}")
return None
# 3. 检查动画文件结构
print(f"3. 动画文件检查:")
try:
temp_anim = self.loader.loadModel(anim_path)
if temp_anim is None:
print("❌ 无法加载动画文件")
return None
# 检查AnimBundleNode
animBundleNP = temp_anim.find('**/+AnimBundleNode')
if animBundleNP.isEmpty():
print("❌ 动画文件不包含AnimBundleNode")
print(" 建议: 确保动画文件是正确导出的动画数据")
return None
else:
print("✓ 动画文件包含AnimBundleNode")
temp_anim.removeNode() # 清理临时动画
except Exception as e:
print(f"❌ 动画文件加载失败: {e}")
return None
# 4. 尝试创建Actor
print(f"4. Actor创建测试:")
# 方法1: 构造函数方式
try:
print(" 尝试方法1: 构造函数加载")
actor = Actor(model_path, {anim_name: anim_path})
print(f" 动画列表: {actor.getAnimNames()}")
# 强制同步绑定
print(" 尝试强制绑定动画...")
actor.bindAnim(anim_name, allowAsyncBind=False)
control = actor.getAnimControl(anim_name)
if control is not None:
print("✓ 方法1成功 - 动画控制器创建成功")
return actor
else:
print("❌ 方法1失败 - 动画控制器为None")
except Exception as e:
print(f"❌ 方法1异常: {e}")
# 方法2: 分步加载
try:
print(" 尝试方法2: 分步加载")
actor = Actor()
actor.loadModel(model_path)
actor.loadAnims({anim_name: anim_path})
# 强制绑定
actor.bindAnim(anim_name, allowAsyncBind=False)
control = actor.getAnimControl(anim_name)
if control is not None:
print("✓ 方法2成功 - 动画控制器创建成功")
return actor
else:
print("❌ 方法2失败 - 动画控制器为None")
except Exception as e:
print(f"❌ 方法2异常: {e}")
# 5. 深度诊断
print(f"5. 深度诊断:")
try:
actor = Actor()
actor.loadModel(model_path)
# 检查PartBundle
bundles = actor.getPartBundles()
print(f" PartBundle数量: {len(bundles)}")
if len(bundles) == 0:
print("❌ 没有找到PartBundle - 模型可能不是正确的角色模型")
return None
# 检查动画绑定过程
print(" 尝试手动绑定动画...")
actor.loadAnims({anim_name: anim_path})
# 获取详细的绑定信息
actor_info = actor.getActorInfo()
print(f" Actor信息: {actor_info}")
return None
except Exception as e:
print(f"❌ 深度诊断异常: {e}")
return None
print("❌ 所有方法都失败了")
return None
def _setYCModel(self):
model = self.loader.loadModel("/home/tiger/文档/Tzjyc_GLTF/tzjyc.gltf")
@ -48,19 +247,8 @@ class CoreWorld(Panda3DWorld):
model.setHpr(0, 90, 0)
def _setArm1(self):
model1 = self.loader.loadModel("/home/tiger/下载/JQB1.fbx")
model2 = self.loader.loadModel("/home/tiger/下载/JQB2.fbx")
model1.reparentTo(self.render)
model1.setPos(0, 5, 0)
model1.setHpr(0, 90, 0)
model1.setScale(0.01)
model2.reparentTo(self.render)
model2.setPos(0, -5, 0)
model2.setHpr(0, 90, 0)
model2.setScale(0.01)
def _setupCamera(self):
"""设置相机位置和朝向"""
self.cam.setPos(0, -50, 20)
self.cam.lookAt(0, 0, 0)
@ -163,7 +351,7 @@ class CoreWorld(Panda3DWorld):
if hasattr(self, 'win') and self.win:
width = self.win.getXSize()
height = self.win.getYSize()
print(f"从Panda3D窗口获取尺寸: {width} x {height}")
print(f"从Panda3D窗口获取尺寸!!!!!!!!!!!!!!!!!!!!!: {width} x {height}")
return width, height
# 最后的默认值

View File

@ -369,6 +369,9 @@ class MyWorld(CoreWorld):
def updatePropertyPanel(self, item):
"""更新属性面板显示 - 代理到property_panel"""
return self.property_panel.updatePropertyPanel(item)
def addAnimationPanel(self,originmodel,filepath):
return self.property_panel._addAnimationPanel(originmodel,filepath)
def updateGUIPropertyPanel(self, gui_element):
"""更新GUI元素属性面板 - 代理到property_panel"""

53
pandatool/CMakeLists.txt Normal file
View File

@ -0,0 +1,53 @@
if(NOT BUILD_PANDA)
message(FATAL_ERROR "Cannot build pandatool without panda! Please enable the BUILD_PANDA option.")
endif()
# Include pandatool source directories
add_subdirectory(src/assimp)
add_subdirectory(src/bam)
add_subdirectory(src/converter)
add_subdirectory(src/daeegg)
add_subdirectory(src/daeprogs)
add_subdirectory(src/deploy-stub)
add_subdirectory(src/dxf)
add_subdirectory(src/dxfegg)
add_subdirectory(src/dxfprogs)
add_subdirectory(src/eggbase)
add_subdirectory(src/eggcharbase)
add_subdirectory(src/eggprogs)
add_subdirectory(src/egg-mkfont)
add_subdirectory(src/egg-optchar)
add_subdirectory(src/egg-palettize)
add_subdirectory(src/egg-qtess)
add_subdirectory(src/flt)
add_subdirectory(src/fltegg)
add_subdirectory(src/fltprogs)
add_subdirectory(src/gtk-stats)
add_subdirectory(src/imagebase)
add_subdirectory(src/imageprogs)
add_subdirectory(src/lwo)
add_subdirectory(src/lwoegg)
add_subdirectory(src/lwoprogs)
add_subdirectory(src/mac-stats)
add_subdirectory(src/miscprogs)
add_subdirectory(src/objegg)
add_subdirectory(src/objprogs)
add_subdirectory(src/palettizer)
add_subdirectory(src/pandatoolbase)
add_subdirectory(src/pfmprogs)
add_subdirectory(src/progbase)
add_subdirectory(src/pstatserver)
add_subdirectory(src/ptloader)
add_subdirectory(src/text-stats)
add_subdirectory(src/vrml)
add_subdirectory(src/vrmlegg)
add_subdirectory(src/vrmlprogs)
add_subdirectory(src/win-stats)
add_subdirectory(src/xfile)
add_subdirectory(src/xfileegg)
add_subdirectory(src/xfileprogs)
if(BUILD_TOOLS)
export_targets(Tools)
endif()
export_targets(ToolsDevel NAMESPACE "Panda3D::Tools::" COMPONENT ToolsDevel)

View File

@ -0,0 +1,39 @@
if(NOT HAVE_ASSIMP)
return()
endif()
set(P3ASSIMP_HEADERS
assimpLoader.h assimpLoader.I
config_assimp.h
loaderFileTypeAssimp.h
pandaIOStream.h
pandaIOSystem.h
pandaLogger.h
)
set(P3ASSIMP_SOURCES
assimpLoader.cxx
config_assimp.cxx
loaderFileTypeAssimp.cxx
pandaIOStream.cxx
pandaIOSystem.cxx
pandaLogger.cxx
)
composite_sources(p3assimp P3ASSIMP_SOURCES)
add_library(p3assimp ${MODULE_TYPE} ${P3ASSIMP_HEADERS} ${P3ASSIMP_SOURCES})
set_target_properties(p3assimp PROPERTIES DEFINE_SYMBOL BUILDING_ASSIMP)
target_link_libraries(p3assimp PRIVATE p3pandatoolbase)
target_link_libraries(p3assimp PUBLIC PKG::ASSIMP)
if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang)$")
# Do not re-export symbols from these libraries.
target_link_options(p3assimp PRIVATE "LINKER:--exclude-libs,libassimp.a")
target_link_options(p3assimp PRIVATE "LINKER:--exclude-libs,libIrrXML.a")
endif()
if(BUILD_SHARED_LIBS)
# We can't install this if we're doing a static build, because it depends on
# a static library that isn't installed.
install(TARGETS p3assimp EXPORT Assimp COMPONENT Assimp DESTINATION ${MODULE_DESTINATION})
endif()

View File

@ -0,0 +1,12 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file assimpLoader.I
* @author rdb
* @date 2011-03-29
*/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,94 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file assimpLoader.h
* @author rdb
* @date 2011-03-29
*/
#ifndef ASSIMPLOADER_H
#define ASSIMPLOADER_H
#include "config_assimp.h"
#include "filename.h"
#include "modelRoot.h"
#include "texture.h"
#include "textureStage.h"
#include "pmap.h"
#include <assimp/scene.h>
#include <assimp/Importer.hpp>
class Character;
class CharacterJointBundle;
class PartGroup;
class AnimBundle;
class AnimGroup;
struct char_cmp {
bool operator () (const char *a, const char *b) const {
return strcmp(a,b) < 0;
}
};
typedef pmap<const char *, const aiNode *, char_cmp> BoneMap;
/**
* Class that interfaces with Assimp and builds Panda nodes to represent the
* Assimp structures. The loader should be reusable.
*/
class AssimpLoader : public TypedReferenceCount {
public:
AssimpLoader();
virtual ~AssimpLoader();
void get_extensions(std::string &ext) const;
bool read(const Filename &filename);
void build_graph();
public:
bool _error;
PT(ModelRoot) _root;
Filename _filename;
Mutex _lock;
private:
Assimp::Importer _importer;
const aiScene *_scene;
struct Geoms {
PT(Geom) _points;
PT(Geom) _lines;
PT(Geom) _triangles;
PT(Character) _character;
unsigned int _mat_index = 0;
};
// These arrays are temporarily used during the build_graph run.
PT(Texture) *_textures;
CPT(RenderState) *_mat_states;
Geoms *_geoms;
BoneMap _bonemap;
const aiNode *find_node(const aiNode &root, const aiString &name);
void load_texture(size_t index);
void load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype,
TextureStage::Mode mode, CPT(TextureAttrib) &tattr,
CPT(TexMatrixAttrib) &tmattr);
void load_material(size_t index);
void create_joint(Character *character, CharacterJointBundle *bundle, PartGroup *parent, const aiNode &node);
void create_anim_channel(const aiAnimation &anim, AnimBundle *bundle, AnimGroup *parent, const aiNode &node);
void load_mesh(size_t index);
bool load_node(const aiNode &node, PandaNode *parent, bool under_joint = false);
void load_light(const aiLight &light);
};
#include "assimpLoader.I"
#endif

View File

@ -0,0 +1,114 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file config_assimp.cxx
* @author rdb
* @date 2011-03-29
*/
#include "config_assimp.h"
#include "loaderFileTypeAssimp.h"
#include "dconfig.h"
#include "loaderFileTypeRegistry.h"
ConfigureDef(config_assimp);
NotifyCategoryDef(assimp, "");
ConfigureFn(config_assimp) {
init_libassimp();
}
ConfigVariableBool assimp_calc_tangent_space
("assimp-calc-tangent-space", false,
PRC_DESC("Calculates tangents and binormals for meshes imported via Assimp."));
ConfigVariableBool assimp_join_identical_vertices
("assimp-join-identical-vertices", true,
PRC_DESC("Merges duplicate vertices. Set this to false if you want each "
"vertex to only be in use on one triangle."));
ConfigVariableBool assimp_improve_cache_locality
("assimp-improve-cache-locality", true,
PRC_DESC("Improves rendering performance of the loaded meshes by reordering "
"triangles for better vertex cache locality. Set this to false if "
"you need geometry to be loaded in the exact order that it was "
"specified in the file, or to improve load performance."));
ConfigVariableBool assimp_remove_redundant_materials
("assimp-remove-redundant-materials", true,
PRC_DESC("Removes redundant/unreferenced materials from assets."));
ConfigVariableBool assimp_fix_infacing_normals
("assimp-fix-infacing-normals", false,
PRC_DESC("Determines which normal vectors are facing inward and inverts them "
"so that they are facing outward."));
ConfigVariableBool assimp_optimize_meshes
("assimp-optimize-meshes", true,
PRC_DESC("Reduces the number of draw calls by unifying geometry with the same "
"materials. Especially effective in conjunction with "
"assimp-optimize-graph and assimp-remove-redundant-materials."));
ConfigVariableBool assimp_optimize_graph
("assimp-optimize-graph", false,
PRC_DESC("Optimizes the scene geometry by flattening the scene hierarchy. "
"This is very efficient (combined with assimp-optimize-meshes), but "
"it may result the hierarchy to become lost, so it is disabled by "
"default."));
ConfigVariableBool assimp_flip_winding_order
("assimp-flip-winding-order", false,
PRC_DESC("Set this true to flip the winding order of all models loaded via "
"the Assimp loader. Note that you may need to clear the model-cache "
"after changing this."));
ConfigVariableBool assimp_gen_normals
("assimp-gen-normals", false,
PRC_DESC("Set this true to generate normals (if absent from file) on import. "
"See assimp-smooth-normal-angle for more information. "
"Note that you may need to clear the model-cache after "
"changing this."));
ConfigVariableDouble assimp_smooth_normal_angle
("assimp-smooth-normal-angle", 0.0,
PRC_DESC("Set this to anything other than 0.0 in degrees (so 180.0 is PI) to "
"specify the maximum angle that may be between two face normals at "
"the same vertex position that are smoothed together. Sometimes "
"referred to as 'crease angle'. Only has effect if "
"assimp-gen-normals is set to true and the file does not contain "
"normals. Note that you may need to clear the model-cache after "
"changing this."));
ConfigVariableBool assimp_collapse_dummy_root_node
("assimp-collapse-dummy-root-node", true,
PRC_DESC("If set to true, collapses the root node that Assimp creates, if it "
"appears to be a synthetic dummy root node and contains no meshes. "
"This variable is new as of Panda3D 1.10.13 and will become true by "
"default as of Panda3D 1.11.0."));
/**
* Initializes the library. This must be called at least once before any of
* the functions or classes in this library can be used. Normally it will be
* called by the static initializers and need not be called explicitly, but
* special cases exist.
*/
void
init_libassimp() {
static bool initialized = false;
if (initialized) {
return;
}
initialized = true;
LoaderFileTypeAssimp::init_type();
LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr();
reg->register_type(new LoaderFileTypeAssimp);
}

View File

@ -0,0 +1,40 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file config_assimp.h
* @author rdb
* @date 2011-03-29
*/
#ifndef CONFIG_ASSIMP_H
#define CONFIG_ASSIMP_H
#include "pandatoolbase.h"
#include "notifyCategoryProxy.h"
#include "configVariableBool.h"
#include "configVariableDouble.h"
#include "dconfig.h"
ConfigureDecl(config_assimp, EXPCL_ASSIMP, EXPTP_ASSIMP);
NotifyCategoryDecl(assimp, EXPCL_ASSIMP, EXPTP_ASSIMP);
extern ConfigVariableBool assimp_calc_tangent_space;
extern ConfigVariableBool assimp_join_identical_vertices;
extern ConfigVariableBool assimp_improve_cache_locality;
extern ConfigVariableBool assimp_remove_redundant_materials;
extern ConfigVariableBool assimp_fix_infacing_normals;
extern ConfigVariableBool assimp_optimize_meshes;
extern ConfigVariableBool assimp_optimize_graph;
extern ConfigVariableBool assimp_flip_winding_order;
extern ConfigVariableBool assimp_gen_normals;
extern ConfigVariableDouble assimp_smooth_normal_angle;
extern ConfigVariableBool assimp_collapse_dummy_root_node;
extern EXPCL_ASSIMP void init_libassimp();
#endif

View File

@ -0,0 +1,133 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file loaderFileTypeAssimp.cxx
* @author rdb
* @date 2011-03-29
*/
#include "loaderFileTypeAssimp.h"
#include "config_assimp.h"
#include "assimpLoader.h"
#include <assimp/cimport.h>
using std::string;
TypeHandle LoaderFileTypeAssimp::_type_handle;
/**
*
*/
LoaderFileTypeAssimp::
LoaderFileTypeAssimp() {
}
/**
*
*/
LoaderFileTypeAssimp::
~LoaderFileTypeAssimp() {
}
/**
*
*/
string LoaderFileTypeAssimp::
get_name() const {
return "Assimp Importer";
}
/**
*
*/
string LoaderFileTypeAssimp::
get_extension() const {
return "";
}
/**
* Returns a space-separated list of extension, in addition to the one
* returned by get_extension(), that are recognized by this converter.
*/
string LoaderFileTypeAssimp::
get_additional_extensions() const {
// This may be called at static init time, so ensure it is constructed now.
static ConfigVariableString assimp_disable_extensions
("assimp-disable-extensions", "gltf glb",
PRC_DESC("A list of extensions (without preceding dot) that should not be "
"loaded via the Assimp loader, even if Assimp supports these "
"formats. It is useful to set this for eg. gltf and glb files "
"to prevent them from being accidentally loaded via the Assimp "
"plug-in instead of via a superior plug-in like panda3d-gltf."));
bool has_disabled_exts = !assimp_disable_extensions.empty();
aiString aexts;
aiGetExtensionList(&aexts);
char *buffer = (char *)alloca(aexts.length + 2);
char *p = buffer;
// The format is like: *.mdc;*.mdl;*.mesh.xml;*.mot
char *sub = strtok(aexts.data, ";");
while (sub != nullptr) {
bool enabled = true;
if (has_disabled_exts) {
for (size_t i = 0; i < assimp_disable_extensions.get_num_words(); ++i) {
std::string disabled_ext = assimp_disable_extensions.get_word(i);
if (strcmp(sub + 2, disabled_ext.c_str()) == 0) {
enabled = false;
break;
}
}
}
if (enabled) {
*(p++) = ' ';
size_t len = strlen(sub + 2);
memcpy(p, sub + 2, len);
p += len;
}
sub = strtok(nullptr, ";");
}
// Strip first space
++buffer;
return std::string(buffer, p - buffer);
}
/**
* Returns true if this file type can transparently load compressed files
* (with a .pz or .gz extension), false otherwise.
*/
bool LoaderFileTypeAssimp::
supports_compressed() const {
return true;
}
/**
*
*/
PT(PandaNode) LoaderFileTypeAssimp::
load_file(const Filename &path, const LoaderOptions &options,
BamCacheRecord *record) const {
assimp_cat.info()
<< "Reading " << path << "\n";
AssimpLoader loader;
loader.local_object();
if (!loader.read(path)) {
return nullptr;
}
loader.build_graph();
return DCAST(PandaNode, loader._root);
}

View File

@ -0,0 +1,57 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file loaderFileTypeAssimp.h
* @author rdb
* @date 2011-03-29
*/
#ifndef LOADERFILETYPEASSIMP_H
#define LOADERFILETYPEASSIMP_H
#include "config_assimp.h"
#include "loaderFileType.h"
class AssimpLoader;
/**
* This defines the Loader interface that uses the Assimp library to load
* various model formats.
*/
class EXPCL_ASSIMP LoaderFileTypeAssimp : public LoaderFileType {
public:
LoaderFileTypeAssimp();
virtual ~LoaderFileTypeAssimp();
virtual std::string get_name() const;
virtual std::string get_extension() const;
virtual std::string get_additional_extensions() const;
virtual bool supports_compressed() const;
virtual PT(PandaNode) load_file(const Filename &path, const LoaderOptions &options,
BamCacheRecord *record) const;
public:
static TypeHandle get_class_type() {
return _type_handle;
}
static void init_type() {
LoaderFileType::init_type();
register_type(_type_handle, "LoaderFileTypeAssimp",
LoaderFileType::get_class_type());
}
virtual TypeHandle get_type() const {
return get_class_type();
}
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
private:
static TypeHandle _type_handle;
};
#endif

View File

@ -0,0 +1,7 @@
#include "config_assimp.cxx"
#include "assimpLoader.cxx"
#include "loaderFileTypeAssimp.cxx"
#include "pandaIOStream.cxx"
#include "pandaIOSystem.cxx"
#include "pandaLogger.cxx"

View File

@ -0,0 +1,107 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pandaIOStream.cxx
* @author rdb
* @date 2011-03-29
*/
#include "pandaIOStream.h"
using std::ios;
/**
*
*/
PandaIOStream::
PandaIOStream(std::istream &stream) : _istream(stream) {
}
/**
* Returns the size of this file.
*/
size_t PandaIOStream::
FileSize() const {
_istream.clear();
std::streampos cur = _istream.tellg();
_istream.seekg(0, ios::end);
std::streampos end = _istream.tellg();
_istream.seekg(cur);
return end;
}
/**
* See fflush.
*/
void PandaIOStream::
Flush() {
nassertv(false);
}
/**
* See fread.
*/
size_t PandaIOStream::
Read(void *buffer, size_t size, size_t count) {
_istream.read((char *)buffer, size * count);
if (_istream.eof()) {
// Gracefully handle EOF.
_istream.clear(ios::eofbit);
}
return _istream.gcount() / size;
}
/**
* See fseek.
*/
aiReturn PandaIOStream::
Seek(size_t offset, aiOrigin origin) {
switch (origin) {
case aiOrigin_SET:
_istream.seekg(offset, ios::beg);
break;
case aiOrigin_CUR:
_istream.seekg(offset, ios::cur);
break;
case aiOrigin_END:
_istream.seekg(offset, ios::end);
break;
default:
// Keep compiler happy
nassertr(false, AI_FAILURE);
break;
}
if (_istream.good()) {
return AI_SUCCESS;
} else {
return AI_FAILURE;
}
}
/**
* See ftell.
*/
size_t PandaIOStream::
Tell() const {
return _istream.tellg();
}
/**
* See fwrite.
*/
size_t PandaIOStream::
Write(const void *buffer, size_t size, size_t count) {
nassertr(false, 0);
return 0;
}

View File

@ -0,0 +1,45 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pandaIOStream.h
* @author rdb
* @date 2011-03-29
*/
#ifndef PANDAIOSTREAM_H
#define PANDAIOSTREAM_H
#include "config_assimp.h"
#include <assimp/IOStream.hpp>
class PandaIOSystem;
/**
* Custom implementation of Assimp::IOStream. It simply wraps around an
* istream object, and is unable to write.
*/
class PandaIOStream : public Assimp::IOStream {
public:
PandaIOStream(std::istream &stream);
virtual ~PandaIOStream() {};
size_t FileSize() const;
void Flush();
size_t Read(void *pvBuffer, size_t pSize, size_t pCount);
aiReturn Seek(size_t pOffset, aiOrigin pOrigin);
size_t Tell() const;
size_t Write(const void *buffer, size_t size, size_t count);
private:
std::istream &_istream;
friend class PandaIOSystem;
};
#endif

View File

@ -0,0 +1,85 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pandaIOSystem.cxx
* @author rdb
* @date 2011-03-29
*/
#include "pandaIOSystem.h"
#include "pandaIOStream.h"
/**
* Initializes the object with the given VFS, or the global one if none was
* specified.
*/
PandaIOSystem::
PandaIOSystem(VirtualFileSystem *vfs) : _vfs(vfs) {
}
/**
* Returns true if the file exists, duh.
*/
bool PandaIOSystem::
Exists(const char *file) const {
Filename fn = Filename::from_os_specific(file);
return _vfs->exists(fn);
}
/**
* Closes the indicated file stream.
*/
void PandaIOSystem::
Close(Assimp::IOStream *file) {
PandaIOStream *pstr = (PandaIOStream*) file;
_vfs->close_read_file(&pstr->_istream);
}
/**
* Returns true if the two paths point to the same file, false if not.
*/
bool PandaIOSystem::
ComparePaths(const char *p1, const char *p2) const {
Filename fn1 = Filename::from_os_specific(p1);
Filename fn2 = Filename::from_os_specific(p2);
fn1.make_canonical();
fn2.make_canonical();
return fn1 == fn2;
}
/**
* Returns the path separator for this operating system.
*/
char PandaIOSystem::
getOsSeparator() const {
#ifdef _WIN32
return '\\';
#else
return '/';
#endif
}
/**
* Opens the indicated file.
*/
Assimp::IOStream *PandaIOSystem::
Open(const char *file, const char *mode) {
Filename fn = Filename::from_os_specific(file);
if (mode[0] == 'r') {
std::istream *stream = _vfs->open_read_file(file, true);
if (stream == nullptr) {
return nullptr;
}
return new PandaIOStream(*stream);
} else {
nassert_raise("write mode not implemented");
return nullptr;
}
}

View File

@ -0,0 +1,40 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pandaIOSystem.h
* @author rdb
* @date 2011-03-29
*/
#ifndef PANDAIOSYSTEM_H
#define PANDAIOSYSTEM_H
#include "config_assimp.h"
#include "virtualFileSystem.h"
#include <assimp/IOSystem.hpp>
/**
* Custom implementation of Assimp::IOSystem.
*/
class PandaIOSystem : public Assimp::IOSystem {
public:
PandaIOSystem(VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr());
virtual ~PandaIOSystem() {};
void Close(Assimp::IOStream *file);
bool ComparePaths(const char *p1, const char *p2) const;
bool Exists(const char *file) const;
char getOsSeparator() const;
Assimp::IOStream *Open(const char *file, const char *mode);
private:
VirtualFileSystem *_vfs;
};
#endif

View File

@ -0,0 +1,71 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pandaLogger.cxx
* @author rdb
* @date 2011-05-05
*/
#include "pandaLogger.h"
#include <assimp/DefaultLogger.hpp>
PandaLogger *PandaLogger::_ptr = nullptr;
/**
* Makes sure there's a global PandaLogger object and makes sure that it is
* Assimp's default logger.
*/
void PandaLogger::
set_default() {
if (_ptr == nullptr) {
_ptr = new PandaLogger;
}
if (_ptr != Assimp::DefaultLogger::get()) {
Assimp::DefaultLogger::set(_ptr);
}
}
/**
*
*/
void PandaLogger::OnDebug(const char *message) {
if (assimp_cat.is_debug()) {
assimp_cat.debug() << message << "\n";
}
}
/**
*
*/
void PandaLogger::OnVerboseDebug(const char *message) {
if (assimp_cat.is_spam()) {
assimp_cat.spam() << message << "\n";
}
}
/**
*
*/
void PandaLogger::OnError(const char *message) {
assimp_cat.error() << message << "\n";
}
/**
*
*/
void PandaLogger::OnInfo(const char *message) {
assimp_cat.info() << message << "\n";
}
/**
*
*/
void PandaLogger::OnWarn(const char *message) {
assimp_cat.warning() << message << "\n";
}

View File

@ -0,0 +1,52 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pandaLogger.h
* @author rdb
* @date 2011-05-05
*/
#ifndef PANDALOGGER_H
#define PANDALOGGER_H
#include "config_assimp.h"
#include <assimp/Logger.hpp>
/**
* Custom implementation of Assimp::Logger. It simply wraps around the
* assimp_cat methods.
*/
class PandaLogger : public Assimp::Logger {
public:
static void set_default();
protected:
INLINE bool attachStream(Assimp::LogStream*, unsigned int) {
return false;
};
INLINE bool detachStream(Assimp::LogStream*, unsigned int) {
return false;
};
// Kept for compatibility with Assimp 4.x
INLINE bool detatchStream(Assimp::LogStream*, unsigned int) {
return false;
};
void OnDebug(const char *message);
void OnVerboseDebug(const char *message);
void OnError(const char *message);
void OnInfo(const char *message);
void OnWarn(const char *message);
private:
static PandaLogger *_ptr;
};
#endif

View File

@ -0,0 +1,27 @@
if(NOT BUILD_TOOLS)
return()
endif()
add_executable(bam-info bamInfo.cxx bamInfo.h)
target_link_libraries(bam-info p3progbase panda)
install(TARGETS bam-info EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})
if(HAVE_EGG)
add_executable(egg2bam eggToBam.cxx eggToBam.h)
target_link_libraries(egg2bam p3eggbase p3progbase panda)
install(TARGETS egg2bam EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})
if(HAVE_SQUISH)
target_compile_definitions(egg2bam PRIVATE HAVE_SQUISH)
endif()
add_executable(bam2egg bamToEgg.cxx bamToEgg.h)
target_link_libraries(bam2egg p3converter p3eggbase p3progbase panda)
install(TARGETS bam2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})
add_executable(pts2bam ptsToBam.cxx ptsToBam.h)
target_link_libraries(pts2bam p3progbase pandaegg panda)
install(TARGETS pts2bam EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()

View File

@ -0,0 +1,328 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file bamInfo.cxx
* @author drose
* @date 2000-07-02
*/
#include "bamInfo.h"
#include "bamFile.h"
#include "pandaNode.h"
#include "geomNode.h"
#include "texture.h"
#include "recorderHeader.h"
#include "recorderFrame.h"
#include "recorderTable.h"
#include "dcast.h"
#include "pvector.h"
#include "bamCacheRecord.h"
#include "bamCacheIndex.h"
/**
*
*/
BamInfo::
BamInfo() {
set_program_brief("describe the contents of .bam files");
set_program_description
("This program scans one or more Bam files--Panda's Binary Animation "
"and Models native binary format--and describes their contents.");
clear_runlines();
add_runline("[opts] input.bam [input.bam ... ]");
add_option
("ls", "", 0,
"List the scene graph hierarchy in the bam file.",
&BamInfo::dispatch_none, &_ls);
add_option
("t", "", 0,
"List explicitly each transition in the hierarchy.",
&BamInfo::dispatch_none, &_verbose_transitions);
add_option
("g", "", 0,
"Output verbose information about each Geom in the Bam file.",
&BamInfo::dispatch_none, &_verbose_geoms);
_num_scene_graphs = 0;
}
/**
*
*/
void BamInfo::
run() {
bool okflag = true;
Filenames::const_iterator fi;
for (fi = _filenames.begin(); fi != _filenames.end(); ++fi) {
if (!get_info(*fi)) {
okflag = false;
}
}
if (_num_scene_graphs > 0) {
nout << "\nScene graph statistics:\n";
_analyzer.write(nout, 2);
}
nout << "\n";
if (!okflag) {
// Exit with an error if any of the files was unreadable.
exit(1);
}
}
/**
*
*/
bool BamInfo::
handle_args(ProgramBase::Args &args) {
if (args.empty()) {
nout << "You must specify the Bam file(s) to read on the command line.\n";
return false;
}
ProgramBase::Args::const_iterator ai;
for (ai = args.begin(); ai != args.end(); ++ai) {
_filenames.push_back(*ai);
}
return true;
}
/**
* Reads a single Bam file and displays its contents. Returns true if
* successful, false on error.
*/
bool BamInfo::
get_info(const Filename &filename) {
BamFile bam_file;
if (!bam_file.open_read(filename)) {
nout << "Unable to read.\n";
return false;
}
const char *endian = "little-endian";
if (bam_file.get_file_endian() == BamEnums::BE_bigendian) {
endian = "big-endian";
}
int float_width = 32;
if (bam_file.get_file_stdfloat_double()) {
float_width = 64;
}
nout << filename << " : Bam version " << bam_file.get_file_major_ver()
<< "." << bam_file.get_file_minor_ver()
<< ", " << endian << ", " << float_width << "-bit floats.\n";
Objects objects;
TypedWritable *object = bam_file.read_object();
if (object != nullptr &&
object->is_exact_type(BamCacheRecord::get_class_type())) {
// Here's a special case: if the first object in the file is a
// BamCacheRecord, it's a cache data file; in this case, we output the
// cache record, and then pretend it doesn't exist.
DCAST(BamCacheRecord, object)->write(nout, 2);
nout << "\n";
object = bam_file.read_object();
}
while (object != nullptr || !bam_file.is_eof()) {
if (object != nullptr) {
objects.push_back(object);
}
object = bam_file.read_object();
}
if (!bam_file.resolve()) {
nout << "Unable to fully resolve file.\n";
return false;
}
// We can't close the bam file until we have examined the objects, since
// closing it will decrement reference counts.
if (objects.size() == 1 &&
objects[0]->is_of_type(PandaNode::get_class_type())) {
describe_scene_graph(DCAST(PandaNode, objects[0]));
} else if (objects.size() == 1 &&
objects[0]->is_of_type(Texture::get_class_type())) {
describe_texture(DCAST(Texture, objects[0]));
} else if (objects.size() == 1 &&
objects[0]->is_of_type(BamCacheIndex::get_class_type())) {
describe_cache_index(DCAST(BamCacheIndex, objects[0]));
} else if (!objects.empty() && objects[0]->is_of_type(RecorderHeader::get_class_type())) {
describe_session(DCAST(RecorderHeader, objects[0]), objects);
} else {
nout << "file contains " << objects.size() << " objects:\n";
for (int i = 0; i < (int)objects.size(); i++) {
describe_general_object(objects[i]);
}
}
return true;
}
/**
* Called for Bam files that contain a single scene graph and no other
* objects. This should describe that scene graph in some meaningful way.
*/
void BamInfo::
describe_scene_graph(PandaNode *node) {
// Parent the node to our own scene graph root, so we can (a) guarantee it
// won't accidentally be deleted before we're done, (b) easily determine the
// bounding volume of the scene, and (c) report statistics on all the bam
// file's scene graphs together when we've finished.
PT(PandaNode) root = new PandaNode("root");
root->add_child(node);
_num_scene_graphs++;
int num_nodes = _analyzer.get_num_nodes();
_analyzer.add_node(node);
num_nodes = _analyzer.get_num_nodes() - num_nodes;
nout << " " << num_nodes << " nodes, bounding volume is "
<< *root->get_bounds() << "\n";
if (_ls || _verbose_geoms || _verbose_transitions) {
list_hierarchy(node, 0);
}
}
/**
* Called for Bam files that contain a Texture object.
*/
void BamInfo::
describe_texture(Texture *tex) {
tex->write(nout, 2);
}
/**
* Called for Bam files that contain a BamCacheIndex object.
*/
void BamInfo::
describe_cache_index(BamCacheIndex *index) {
index->write(nout, 2);
}
/**
* Called for Bam files that contain a recorded session table.
*/
void BamInfo::
describe_session(RecorderHeader *header, const BamInfo::Objects &objects) {
char time_buffer[1024];
strftime(time_buffer, 1024, "%c",
localtime(&header->_start_time));
pset<std::string> recorders;
double last_timestamp = 0.0;
for (size_t i = 1; i < objects.size(); i++) {
if (objects[i]->is_of_type(RecorderFrame::get_class_type())) {
RecorderFrame *frame = DCAST(RecorderFrame, objects[i]);
if (frame->_table_changed) {
RecorderTable::Recorders::const_iterator ri;
for (ri = frame->_table->_recorders.begin();
ri != frame->_table->_recorders.end();
++ri) {
recorders.insert((*ri).first);
}
}
last_timestamp = frame->_timestamp;
}
}
nout << "Session, " << last_timestamp
<< " secs, " << objects.size() - 1 << " frames, "
<< time_buffer << ".\n"
<< "Recorders:";
for (pset<std::string>::iterator ni = recorders.begin();
ni != recorders.end();
++ni) {
nout << " " << (*ni);
}
nout << "\n";
}
/**
* Called for Bam files that contain multiple objects which may or may not be
* scene graph nodes. This should describe each object in some meaningful
* way.
*/
void BamInfo::
describe_general_object(TypedWritable *object) {
nassertv(object != nullptr);
nout << " " << object->get_type() << "\n";
}
/**
* Outputs the hierarchy and all of the verbose GeomNode information.
*/
void BamInfo::
list_hierarchy(PandaNode *node, int indent_level) {
indent(nout, indent_level) << *node;
if (_verbose_transitions) {
nout << "\n";
if (!node->get_transform()->is_identity()) {
node->get_transform()->write(nout, indent_level);
}
if (!node->get_state()->is_empty()) {
node->get_state()->write(nout, indent_level);
}
if (!node->get_effects()->is_empty()) {
node->get_effects()->write(nout, indent_level);
}
} else {
if (!node->get_transform()->is_identity()) {
nout << " " << *node->get_transform();
}
if (!node->get_state()->is_empty()) {
nout << " " << *node->get_state();
}
if (!node->get_effects()->is_empty()) {
nout << " " << *node->get_effects();
}
nout << "\n";
}
if (_verbose_geoms && node->is_geom_node()) {
GeomNode *geom_node;
DCAST_INTO_V(geom_node, node);
geom_node->write_verbose(nout, indent_level);
}
int num_children = node->get_num_children();
for (int i = 0; i < num_children; i++) {
PandaNode *child = node->get_child(i);
list_hierarchy(child, indent_level + 2);
}
}
int main(int argc, char *argv[]) {
BamInfo prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,65 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file bamInfo.h
* @author drose
* @date 2000-07-02
*/
#ifndef BAMINFO_H
#define BAMINFO_H
#include "pandatoolbase.h"
#include "programBase.h"
#include "filename.h"
#include "sceneGraphAnalyzer.h"
#include "pvector.h"
class TypedWritable;
class PandaNode;
class Texture;
class BamCacheIndex;
class RecorderHeader;
/**
*
*/
class BamInfo : public ProgramBase {
public:
BamInfo();
void run();
protected:
virtual bool handle_args(Args &args);
private:
typedef pvector<TypedWritable *> Objects;
bool get_info(const Filename &filename);
void describe_scene_graph(PandaNode *node);
void describe_texture(Texture *tex);
void describe_cache_index(BamCacheIndex *index);
void describe_session(RecorderHeader *header, const Objects &objects);
void describe_general_object(TypedWritable *object);
void list_hierarchy(PandaNode *node, int indent_level);
typedef pvector<Filename> Filenames;
Filenames _filenames;
bool _ls;
bool _verbose_transitions;
bool _verbose_geoms;
int _num_scene_graphs;
SceneGraphAnalyzer _analyzer;
};
#endif

View File

@ -0,0 +1,104 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file bamToEgg.cxx
* @author drose
* @date 2001-06-25
*/
#include "bamToEgg.h"
#include "save_egg_file.h"
#include "string_utils.h"
#include "bamFile.h"
#include "bamCacheRecord.h"
/**
*
*/
BamToEgg::
BamToEgg() :
SomethingToEgg("bam", ".bam")
{
set_program_brief("convert a native Panda .bam file to an .egg file");
set_program_description
("This program converts native Panda bam files to egg. The conversion "
"is somewhat incomplete; running egg2bam followed by bam2egg should not "
"be expected to yield the same egg file you started with.");
redescribe_option
("cs",
"Specify the coordinate system of the input " + _format_name +
" file. By default, this is taken from the Config.prc file, which "
"is currently " + format_string(get_default_coordinate_system()) + ".");
_coordinate_system = get_default_coordinate_system();
}
/**
*
*/
void BamToEgg::
run() {
BamFile bam_file;
if (!bam_file.open_read(_input_filename)) {
nout << "Unable to read " << _input_filename << "\n";
exit(1);
}
nout << _input_filename << " : Bam version "
<< bam_file.get_file_major_ver() << "."
<< bam_file.get_file_minor_ver() << "\n";
typedef pvector<TypedWritable *> Objects;
Objects objects;
TypedWritable *object = bam_file.read_object();
if (object != nullptr &&
object->is_exact_type(BamCacheRecord::get_class_type())) {
// Here's a special case: if the first object in the file is a
// BamCacheRecord, it's really a cache data file and not a true bam file;
// but skip over the cache data record and let the user treat it like an
// ordinary bam file.
object = bam_file.read_object();
}
while (object != nullptr || !bam_file.is_eof()) {
if (object != nullptr) {
ReferenceCount *ref_ptr = object->as_reference_count();
if (ref_ptr != nullptr) {
ref_ptr->ref();
}
objects.push_back(object);
}
object = bam_file.read_object();
}
bam_file.resolve();
bam_file.close();
_data->set_coordinate_system(_coordinate_system);
if (objects.size() == 1 &&
objects[0]->is_of_type(PandaNode::get_class_type())) {
PandaNode *node = DCAST(PandaNode, objects[0]);
save_egg_data(_data, node);
} else {
nout << "File does not contain a scene graph.\n";
exit(1);
}
write_egg_file();
}
int main(int argc, char *argv[]) {
BamToEgg prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,34 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file bamToEgg.h
* @author drose
* @date 2001-06-25
*/
#ifndef BAMTOEGG_H
#define BAMTOEGG_H
#include "pandatoolbase.h"
#include "somethingToEgg.h"
/**
* This program reads a bam file, for instance as written out from a real-time
* interaction session, and generates a corresponding egg file.
*/
class BamToEgg : public SomethingToEgg {
public:
BamToEgg();
void run();
private:
};
#endif

View File

@ -0,0 +1,491 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToBam.cxx
* @author drose
* @date 2000-06-28
*/
#include "eggToBam.h"
#include "config_putil.h"
#include "bamFile.h"
#include "load_egg_file.h"
#include "config_egg2pg.h"
#include "config_gobj.h"
#include "config_chan.h"
#include "pandaNode.h"
#include "geomNode.h"
#include "renderState.h"
#include "textureAttrib.h"
#include "dcast.h"
#include "graphicsPipeSelection.h"
#include "graphicsEngine.h"
#include "graphicsBuffer.h"
#include "graphicsStateGuardian.h"
#include "load_prc_file.h"
#include "windowProperties.h"
#include "frameBufferProperties.h"
/**
*
*/
EggToBam::
EggToBam() :
EggToSomething("Bam", ".bam", true, false)
{
set_program_brief("convert .egg files to .bam files");
set_program_description
("This program reads Egg files and outputs Bam files, the binary format "
"suitable for direct loading of animation and models into Panda. Bam "
"files are tied to a particular version of Panda, so should not be "
"considered replacements for egg files, but they tend to be smaller and "
"load much faster than the equivalent egg files.");
// -f is always in effect for egg2bam. It doesn't make sense to provide it
// as an option to the user.
remove_option("f");
add_path_replace_options();
add_path_store_options();
add_option
("flatten", "flag", 0,
"Specifies whether to flatten the egg hierarchy after it is loaded. "
"If flag is zero, the egg hierarchy will not be flattened, but will "
"instead be written to the bam file exactly as it is. If flag is "
"non-zero, the hierarchy will be flattened so that unnecessary nodes "
"(usually group nodes with only one child) are eliminated. The default "
"if this is not specified is taken from the egg-flatten Config.prc "
"variable.",
&EggToBam::dispatch_int, &_has_egg_flatten, &_egg_flatten);
add_option
("combine-geoms", "flag", 0,
"Specifies whether to combine sibling GeomNodes into a common GeomNode "
"when possible. This flag is only respected if flatten, above, is also "
"enabled (or implicitly true from the Config.prc file). The default if "
"this is not specified is taken from the egg-combine-geoms Config.prc "
"variable.",
&EggToBam::dispatch_int, &_has_egg_combine_geoms, &_egg_combine_geoms);
add_option
("suppress-hidden", "flag", 0,
"Specifies whether to suppress hidden geometry. If this is nonzero, "
"egg geometry tagged as \"hidden\" will be removed from the final "
"scene graph; otherwise, it will be preserved (but stashed). The "
"default is nonzero, to remove it.",
&EggToBam::dispatch_int, nullptr, &_egg_suppress_hidden);
add_option
("ls", "", 0,
"Writes a scene graph listing to standard output after the egg "
"file has been loaded, showing the nodes that will be written out.",
&EggToBam::dispatch_none, &_ls);
add_option
("C", "quality", 0,
"Specify the quality level for lossy channel compression. If this "
"is specified, the animation channels will be compressed at this "
"quality level, which is normally an integer value between 0 and 100, "
"inclusive, where higher numbers produce larger files with greater "
"quality. Generally, 95 is the highest useful quality level. Use "
"-NC (described below) to disable channel compression. If neither "
"option is specified, the default comes from the Config.prc file.",
&EggToBam::dispatch_int, &_has_compression_quality, &_compression_quality);
add_option
("NC", "", 0,
"Turn off lossy compression of animation channels. Channels will be "
"written exactly as they are, losslessly.",
&EggToBam::dispatch_none, &_compression_off);
add_option
("rawtex", "", 0,
"Record texture data directly in the bam file, instead of storing "
"a reference to the texture elsewhere on disk. The textures are "
"stored uncompressed, unless -ctex is also specified. "
"A particular texture that is encoded into "
"multiple different bam files in this way cannot be unified into "
"the same part of texture memory if the different bam files are loaded "
"together. That being said, this can sometimes be a convenient "
"way to ensure the bam file is completely self-contained.",
&EggToBam::dispatch_none, &_tex_rawdata);
add_option
("txo", "", 0,
"Rather than writing texture data directly into the bam file, as in "
"-rawtex, create a texture object for each referenced texture. A "
"texture object is a kind of mini-bam file, with a .txo extension, "
"that contains all of the data needed to recreate a texture, including "
"its image contents, filter and wrap settings, and so on. 3-D textures "
"and cube maps can also be represented in a single .txo file. Texture "
"object files, like bam files, are tied to a particular version of "
"Panda.",
&EggToBam::dispatch_none, &_tex_txo);
#ifdef HAVE_ZLIB
add_option
("txopz", "", 0,
"In addition to writing texture object files as above, compress each "
"one using pzip to a .txo.pz file. In many cases, this will yield a "
"disk file size comparable to that achieved by png compression. This "
"is an on-disk compression only, and does not affect the amount of "
"RAM or texture memory consumed by the texture when it is loaded.",
&EggToBam::dispatch_none, &_tex_txopz);
#endif // HAVE_ZLIB
add_option
("ctex", "", 0,
#ifdef HAVE_SQUISH
"Pre-compress the texture images using the libsquish library, when "
"using -rawtex or -txo. "
#else
"Asks the graphics card to pre-compress the texture images when using "
"-rawtex or -txo. "
#endif // HAVE_SQUISH
#ifdef HAVE_ZLIB
"This is unrelated to the on-disk compression achieved "
"via -txopz (and it may be used in conjunction with that parameter). "
#endif // HAVE_ZLIB
"This will result in a smaller RAM and texture memory footprint for "
"the texture images. The same "
"effect can be achieved at load time by setting compressed-textures in "
"your Config.prc file; but -ctex pre-compresses the "
"textures so that they do not need to be compressed at load time. "
#ifndef HAVE_SQUISH
"Note that, since your Panda is not compiled with the libsquish "
"library, using -ctex will make .txo files that are only guaranteed "
"to load on the particular graphics card that was used to "
"generate them."
#endif // HAVE_SQUISH
,
&EggToBam::dispatch_none, &_tex_ctex);
add_option
("mipmap", "", 0,
"Records the pre-generated mipmap levels in the texture object file "
"when using -rawtex or -txo, regardless of the texture filter mode. This "
"will increase the size of the texture object file by about 33%, but "
"it prevents the need to compute the mipmaps at runtime. The default "
"is to record mipmap levels only when the texture uses a mipmap "
"filter mode.",
&EggToBam::dispatch_none, &_tex_mipmap);
add_option
("ctexq", "quality", 0,
"Specifies the compression quality to use when performing the "
"texture compression requested by -ctex. This may be one of "
"'default', 'fastest', 'normal', or 'best'. The default is 'best'. "
"Set it to 'default' to use whatever is specified by the Config.prc "
"file. This is a global setting only; individual texture quality "
"settings appearing within the egg file will override this.",
&EggToBam::dispatch_string, nullptr, &_ctex_quality);
add_option
("load-display", "display name", 0,
"Specifies the particular display module to load to perform the texture "
"compression requested by -ctex. If this is omitted, the default is "
"taken from the Config.prc file."
#ifdef HAVE_SQUISH
" Since your Panda has libsquish compiled in, this is not necessary; "
"Panda can compress textures without loading a display module."
#endif // HAVE_SQUISH
,
&EggToBam::dispatch_string, nullptr, &_load_display);
redescribe_option
("cs",
"Specify the coordinate system of the resulting " + _format_name +
" file. This may be "
"one of 'y-up', 'z-up', 'y-up-left', or 'z-up-left'. The default "
"is z-up.");
_force_complete = true;
_egg_flatten = 0;
_egg_combine_geoms = 0;
_egg_suppress_hidden = 1;
_tex_txopz = false;
_ctex_quality = "best";
}
/**
*
*/
void EggToBam::
run() {
if (_has_egg_flatten) {
// If the user specified some -flatten, we need to set the corresponding
// Config.prc variable.
egg_flatten = (_egg_flatten != 0);
}
if (_has_egg_combine_geoms) {
// Ditto with -combine_geoms.
egg_combine_geoms = (_egg_combine_geoms != 0);
}
// We always set egg_suppress_hidden.
egg_suppress_hidden = _egg_suppress_hidden;
if (_compression_off) {
// If the user specified -NC, turn off channel compression.
compress_channels = false;
} else if (_has_compression_quality) {
// Otherwise, if the user specified a compression quality with -C, use
// that quality level.
compress_channels = true;
compress_chan_quality = _compression_quality;
}
if (_ctex_quality != "default") {
// Override the user's config file with the command-line parameter for
// texture compression.
std::string prc = "texture-quality-level " + _ctex_quality;
load_prc_file_data("prc", prc);
}
if (!_got_coordinate_system) {
// If the user didn't specify otherwise, ensure the coordinate system is
// Z-up.
_data->set_coordinate_system(CS_zup_right);
}
PT(PandaNode) root = load_egg_data(_data);
if (root == nullptr) {
nout << "Unable to build scene graph from egg file.\n";
exit(1);
}
if (_tex_ctex) {
#ifndef HAVE_SQUISH
if (!make_buffer()) {
nout << "Unable to initialize graphics context; cannot compress textures.\n";
exit(1);
}
#endif // HAVE_SQUISH
}
if (_tex_txo || _tex_txopz || (_tex_ctex && _tex_rawdata)) {
collect_textures(root);
Textures::iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
Texture *tex = (*ti);
tex->get_ram_image();
bool want_mipmaps = (_tex_mipmap || tex->uses_mipmaps());
if (want_mipmaps) {
// Generate mipmap levels.
tex->generate_ram_mipmap_images();
}
if (_tex_ctex) {
#ifdef HAVE_SQUISH
if (!tex->compress_ram_image()) {
nout << " couldn't compress " << tex->get_name() << "\n";
}
tex->set_compression(Texture::CM_on);
#else // HAVE_SQUISH
tex->set_keep_ram_image(true);
bool has_mipmap_levels = (tex->get_num_ram_mipmap_images() > 1);
if (!_engine->extract_texture_data(tex, _gsg)) {
nout << " couldn't compress " << tex->get_name() << "\n";
}
if (!has_mipmap_levels && !want_mipmaps) {
// Make sure we didn't accidentally introduce mipmap levels by
// rendezvousing through the graphics card.
tex->clear_ram_mipmap_images();
}
tex->set_keep_ram_image(false);
#endif // HAVE_SQUISH
}
if (_tex_txo || _tex_txopz) {
convert_txo(tex);
}
}
}
if (_ls) {
root->ls(nout, 0);
}
// This should be guaranteed because we pass false to the constructor,
// above.
nassertv(has_output_filename());
Filename filename = get_output_filename();
filename.make_dir();
nout << "Writing " << filename << "\n";
BamFile bam_file;
if (!bam_file.open_write(filename)) {
nout << "Error in writing.\n";
exit(1);
}
if (!bam_file.write_object(root)) {
nout << "Error in writing.\n";
exit(1);
}
}
/**
* Does something with the additional arguments on the command line (after all
* the -options have been parsed). Returns true if the arguments are good,
* false otherwise.
*/
bool EggToBam::
handle_args(ProgramBase::Args &args) {
// If the user specified a path store option, we need to set the bam-
// texture-mode Config.prc variable directly to support this (otherwise the
// bam code will do what it wants to do anyway).
if (_tex_rawdata) {
bam_texture_mode = BamFile::BTM_rawdata;
} else if (_got_path_store) {
bam_texture_mode = BamFile::BTM_unchanged;
} else {
// Otherwise, the default path store is absolute; then the bam-texture-
// mode can do the appropriate thing to it.
_path_replace->_path_store = PS_absolute;
}
return EggToSomething::handle_args(args);
}
/**
* Recursively walks the scene graph, looking for Texture references.
*/
void EggToBam::
collect_textures(PandaNode *node) {
collect_textures(node->get_state());
if (node->is_geom_node()) {
GeomNode *geom_node = DCAST(GeomNode, node);
int num_geoms = geom_node->get_num_geoms();
for (int i = 0; i < num_geoms; ++i) {
collect_textures(geom_node->get_geom_state(i));
}
}
PandaNode::Children children = node->get_children();
int num_children = children.get_num_children();
for (int i = 0; i < num_children; ++i) {
collect_textures(children.get_child(i));
}
}
/**
* Recursively walks the scene graph, looking for Texture references.
*/
void EggToBam::
collect_textures(const RenderState *state) {
const TextureAttrib *tex_attrib = DCAST(TextureAttrib, state->get_attrib(TextureAttrib::get_class_type()));
if (tex_attrib != nullptr) {
int num_on_stages = tex_attrib->get_num_on_stages();
for (int i = 0; i < num_on_stages; ++i) {
_textures.insert(tex_attrib->get_on_texture(tex_attrib->get_on_stage(i)));
}
}
}
/**
* If the indicated Texture was not already loaded from a txo file, writes it
* to a txo file and updates the Texture object to reference the new file.
*/
void EggToBam::
convert_txo(Texture *tex) {
if (!tex->get_loaded_from_txo()) {
Filename fullpath = tex->get_fullpath().get_filename_index(0);
if (_tex_txopz) {
fullpath.set_extension("txo.pz");
// We use this clumsy syntax so that the new extension appears to be two
// separate extensions, .txo followed by .pz, which is what
// Texture::write() expects to find.
fullpath = Filename(fullpath.get_fullpath());
} else {
fullpath.set_extension("txo");
}
if (tex->write(fullpath)) {
nout << " Writing " << fullpath;
if (tex->get_ram_image_compression() != Texture::CM_off) {
nout << " (compressed " << tex->get_ram_image_compression() << ")";
}
nout << "\n";
tex->set_loaded_from_txo();
tex->set_fullpath(fullpath);
tex->clear_alpha_fullpath();
Filename filename = tex->get_filename().get_filename_index(0);
if (_tex_txopz) {
filename.set_extension("txo.pz");
filename = Filename(filename.get_fullpath());
} else {
filename.set_extension("txo");
}
tex->set_filename(filename);
tex->clear_alpha_filename();
}
}
}
/**
* Creates a GraphicsBuffer for communicating with the graphics card.
*/
bool EggToBam::
make_buffer() {
if (!_load_display.empty()) {
// Override the user's config file with the command-line parameter.
std::string prc = "load-display " + _load_display;
load_prc_file_data("prc", prc);
}
GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr();
_pipe = selection->make_default_pipe();
if (_pipe == nullptr) {
nout << "Unable to create graphics pipe.\n";
return false;
}
_engine = new GraphicsEngine;
FrameBufferProperties fbprops = FrameBufferProperties::get_default();
// Some graphics drivers can only create single-buffered offscreen buffers.
// So request that.
fbprops.set_back_buffers(0);
WindowProperties winprops;
winprops.set_size(1, 1);
winprops.set_origin(0, 0);
winprops.set_undecorated(true);
winprops.set_open(true);
winprops.set_z_order(WindowProperties::Z_bottom);
// We don't care how big the buffer is; we just need it to manifest the GSG.
_buffer = _engine->make_output(_pipe, "buffer", 0,
fbprops, winprops,
GraphicsPipe::BF_fb_props_optional);
_engine->open_windows();
if (_buffer == nullptr || !_buffer->is_valid()) {
nout << "Unable to create graphics window.\n";
return false;
}
_gsg = _buffer->get_gsg();
return true;
}
int main(int argc, char *argv[]) {
EggToBam prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,77 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToBam.h
* @author drose
* @date 2000-06-28
*/
#ifndef EGGTOBAM_H
#define EGGTOBAM_H
#include "pandatoolbase.h"
#include "eggToSomething.h"
#include "pset.h"
#include "graphicsPipe.h"
class PandaNode;
class RenderState;
class Texture;
class GraphicsEngine;
class GraphicsStateGuardian;
class GraphicsOutput;
/**
*
*/
class EggToBam : public EggToSomething {
public:
EggToBam();
void run();
protected:
virtual bool handle_args(Args &args);
private:
void collect_textures(PandaNode *node);
void collect_textures(const RenderState *state);
void convert_txo(Texture *tex);
bool make_buffer();
private:
typedef pset<Texture *> Textures;
Textures _textures;
bool _has_egg_flatten;
int _egg_flatten;
bool _has_egg_combine_geoms;
int _egg_combine_geoms;
bool _egg_suppress_hidden;
bool _ls;
bool _has_compression_quality;
int _compression_quality;
bool _compression_off;
bool _tex_rawdata;
bool _tex_txo;
bool _tex_txopz;
bool _tex_ctex;
bool _tex_mipmap;
std::string _ctex_quality;
std::string _load_display;
// The rest of this is required to support -ctex.
PT(GraphicsPipe) _pipe;
GraphicsStateGuardian *_gsg;
GraphicsEngine *_engine;
GraphicsOutput *_buffer;
};
#endif

View File

@ -0,0 +1,238 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file ptsToBam.cxx
* @author drose
* @date 2000-06-28
*/
#include "ptsToBam.h"
#include "config_putil.h"
#include "geomPoints.h"
#include "bamFile.h"
#include "pandaNode.h"
#include "geomNode.h"
#include "dcast.h"
#include "string_utils.h"
#include "config_egg2pg.h"
using std::string;
/**
*
*/
PtsToBam::
PtsToBam() : WithOutputFile(true, false, true)
{
set_program_brief("convert point cloud data into a .bam file");
set_program_description
("This program reads a point clound in a pts file and outputs a bam files, "
"suitable for viewing in Panda.");
clear_runlines();
add_runline("[opts] input.pts output.bam");
add_runline("[opts] -o output.bam input.pts");
add_option
("o", "filename", 0,
"Specify the filename to which the resulting .bam file will be written. "
"If this option is omitted, the last parameter name is taken to be the "
"name of the output file.",
&PtsToBam::dispatch_filename, &_got_output_filename, &_output_filename);
add_option
("d", "divisor", 0,
"Decimates the point cloud by the indicated divisor. The number of points\n"
"added is 1/divisor; numbers larger than 1.0 mean correspondingly fewer\n"
"points.",
&PtsToBam::dispatch_double, nullptr, &_decimate_divisor);
_decimate_divisor = 1.0;
}
/**
*
*/
void PtsToBam::
run() {
pifstream pts;
_pts_filename.set_text();
if (!_pts_filename.open_read(pts)) {
nout << "Cannot open " << _pts_filename << "\n";
exit(1);
}
_gnode = new GeomNode(_pts_filename.get_basename());
_num_points_expected = 0;
_num_points_found = 0;
_num_points_added = 0;
_decimate_factor = 1.0 / std::max(1.0, _decimate_divisor);
_line_number = 0;
_point_number = 0;
_decimated_point_number = 0.0;
_num_vdatas = 0;
string line;
while (std::getline(pts, line)) {
process_line(line);
}
close_vertex_data();
nout << "\nFound " << _num_points_found << " points of " << _num_points_expected << " expected.\n";
nout << "Generated " << _num_points_added << " points to bam file.\n";
// This should be guaranteed because we pass false to the constructor,
// above.
nassertv(has_output_filename());
Filename filename = get_output_filename();
filename.make_dir();
nout << "Writing " << filename << "\n";
BamFile bam_file;
if (!bam_file.open_write(filename)) {
nout << "Error in writing.\n";
exit(1);
}
if (!bam_file.write_object(_gnode.p())) {
nout << "Error in writing.\n";
exit(1);
}
}
/**
*
*/
bool PtsToBam::
handle_args(ProgramBase::Args &args) {
if (args.empty()) {
nout << "You must specify the pts file to read on the command line.\n";
return false;
}
if (args.size() > 1) {
nout << "Specify only one pts on the command line.\n";
return false;
}
_pts_filename = Filename::from_os_specific(args[0]);
return true;
}
/**
* Reads a single line from the pts file.
*/
void PtsToBam::
process_line(const string &line) {
_line_number++;
if (_line_number % 1000000 == 0) {
std::cerr << "." << std::flush;
}
if (line.empty() || !isdigit(line[0])) {
return;
}
if (_line_number == 1) {
// The first line might be just the number of points.
vector_string words;
tokenize(trim(line), words, " \t", true);
if (words.size() == 1) {
string tail;
_num_points_expected = string_to_int(words[0], tail);
nout << "Expecting " << _num_points_expected << " points, will generate "
<< (int)(_num_points_expected * _decimate_factor) << "\n";
return;
}
}
// Here we might have a point.
_num_points_found++;
_decimated_point_number += _decimate_factor;
int point_number = int(_decimated_point_number);
if (point_number > _point_number) {
_point_number = point_number;
vector_string words;
tokenize(trim(line), words, " \t", true);
if (words.size() >= 3) {
add_point(words);
}
}
}
/**
* Adds a point from the pts file.
*/
void PtsToBam::
add_point(const vector_string &words) {
if (_data == nullptr || _data->get_num_rows() >= egg_max_vertices) {
open_vertex_data();
}
string tail;
double x, y, z;
x = string_to_double(words[0], tail);
y = string_to_double(words[1], tail);
z = string_to_double(words[2], tail);
_vertex.add_data3d(x, y, z);
_num_points_added++;
}
/**
* Creates a new GeomVertexData.
*/
void PtsToBam::
open_vertex_data() {
if (_data != nullptr) {
close_vertex_data();
}
CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3();
_data = new GeomVertexData("pts", format, GeomEnums::UH_static);
_vertex = GeomVertexWriter(_data, "vertex");
}
/**
* Closes a previous GeomVertexData and adds it to the scene graph.
*/
void PtsToBam::
close_vertex_data() {
if (_data == nullptr) {
return;
}
_num_vdatas++;
nout << "\nGenerating " << _num_points_added << " points in " << _num_vdatas << " GeomVertexDatas\n";
PT(Geom) geom = new Geom(_data);
int num_vertices = _data->get_num_rows();
int vertices_so_far = 0;
while (num_vertices > 0) {
int this_num_vertices = std::min(num_vertices, (int)egg_max_indices);
PT(GeomPrimitive) points = new GeomPoints(GeomEnums::UH_static);
points->add_consecutive_vertices(vertices_so_far, this_num_vertices);
geom->add_primitive(points);
vertices_so_far += this_num_vertices;
num_vertices -= this_num_vertices;
}
_gnode->add_geom(geom);
_data = nullptr;
}
int main(int argc, char *argv[]) {
PtsToBam prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,64 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file ptsToBam.h
* @author drose
* @date 2000-06-28
*/
#ifndef PTSTOBAM_H
#define PTSTOBAM_H
#include "pandatoolbase.h"
#include "programBase.h"
#include "withOutputFile.h"
#include "filename.h"
#include "vector_string.h"
#include "geomVertexData.h"
#include "geomVertexWriter.h"
#include "geomNode.h"
/**
*
*/
class PtsToBam : public ProgramBase, public WithOutputFile {
public:
PtsToBam();
void run();
protected:
virtual bool handle_args(Args &args);
private:
void process_line(const std::string &line);
void add_point(const vector_string &words);
void open_vertex_data();
void close_vertex_data();
private:
Filename _pts_filename;
double _decimate_divisor;
double _decimate_factor;
int _line_number;
int _point_number;
int _num_points_expected;
int _num_points_found;
int _num_points_added;
int _num_vdatas;
double _decimated_point_number;
PT(GeomNode) _gnode;
PT(GeomVertexData) _data;
GeomVertexWriter _vertex;
};
#endif

View File

@ -0,0 +1,23 @@
if(NOT HAVE_EGG)
return()
endif()
set(P3CONVERTER_HEADERS
somethingToEggConverter.h somethingToEggConverter.I
eggToSomethingConverter.h eggToSomethingConverter.I
)
set(P3CONVERTER_SOURCES
somethingToEggConverter.cxx eggToSomethingConverter.cxx
)
add_library(p3converter STATIC ${P3CONVERTER_HEADERS} ${P3CONVERTER_SOURCES})
target_link_libraries(p3converter p3pandatoolbase pandaegg)
install(TARGETS p3converter
EXPORT ToolsDevel COMPONENT ToolsDevel
DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d
ARCHIVE COMPONENT ToolsDevel)
install(FILES ${P3CONVERTER_HEADERS} COMPONENT ToolsDevel DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/panda3d)

View File

@ -0,0 +1,64 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToSomethingConverter.I
* @author drose
* @date 2012-09-26
*/
/**
* Resets the error flag to the no-error state. had_error() will return false
* until a new error is generated.
*/
INLINE void EggToSomethingConverter::
clear_error() {
_error = false;
}
/**
* Returns true if an error was detected during the conversion process, false
* otherwise.
*/
INLINE bool EggToSomethingConverter::
had_error() const {
return _error;
}
/**
* Sets the EggData to NULL and makes the converter invalid.
*/
INLINE void EggToSomethingConverter::
clear_egg_data() {
set_egg_data(nullptr);
}
/**
* Returns the EggData structure.
*/
INLINE EggData *EggToSomethingConverter::
get_egg_data() {
return _egg_data;
}
/**
* Specifies the units that the EggData has already been scaled to. This is
* informational only; if the target file format supports it, this information
* will be written to the header.
*/
void EggToSomethingConverter::
set_output_units(DistanceUnit output_units) {
_output_units = output_units;
}
/**
* Returns the value supplied to set_output_units().
*/
DistanceUnit EggToSomethingConverter::
get_output_units() const {
return _output_units;
}

View File

@ -0,0 +1,69 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToSomethingConverter.cxx
* @author drose
* @date 2001-04-26
*/
#include "eggToSomethingConverter.h"
#include "eggData.h"
/**
*
*/
EggToSomethingConverter::
EggToSomethingConverter() {
_egg_data = nullptr;
_error = false;
}
/**
*
*/
EggToSomethingConverter::
EggToSomethingConverter(const EggToSomethingConverter &copy) {
_egg_data = nullptr;
_error = false;
}
/**
*
*/
EggToSomethingConverter::
~EggToSomethingConverter() {
clear_egg_data();
}
/**
* Sets the egg data that will be filled in when convert_file() is called.
* This must be called before convert_file().
*/
void EggToSomethingConverter::
set_egg_data(EggData *egg_data) {
_egg_data = egg_data;
}
/**
* Returns a space-separated list of extension, in addition to the one
* returned by get_extension(), that are recognized by this converter.
*/
std::string EggToSomethingConverter::
get_additional_extensions() const {
return std::string();
}
/**
* Returns true if this file type can transparently save compressed files
* (with a .pz extension), false otherwise.
*/
bool EggToSomethingConverter::
supports_compressed() const {
return false;
}

View File

@ -0,0 +1,71 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToSomethingConverter.h
* @author drose
* @date 2012-09-26
*/
#ifndef EGGTOSOMETHINGCONVERTER_H
#define EGGTOSOMETHINGCONVERTER_H
#include "pandatoolbase.h"
#include "filename.h"
#include "pointerTo.h"
#include "distanceUnit.h"
#include "coordinateSystem.h"
class EggData;
class EggGroupNode;
/**
* This is a base class for a family of converter classes that manage a
* conversion from egg format to some other file type.
*
* Classes of this type can be used to implement egg2xxx converter programs,
* as well as LoaderFileTypeXXX run-time savers.
*/
class EggToSomethingConverter {
public:
EggToSomethingConverter();
EggToSomethingConverter(const EggToSomethingConverter &copy);
virtual ~EggToSomethingConverter();
virtual EggToSomethingConverter *make_copy()=0;
INLINE void clear_error();
INLINE bool had_error() const;
void set_egg_data(EggData *egg_data);
INLINE void clear_egg_data();
INLINE EggData *get_egg_data();
INLINE void set_output_units(DistanceUnit output_units);
INLINE DistanceUnit get_output_units() const;
INLINE void set_output_coordinate_system(CoordinateSystem output_coordinate_system) const;
INLINE CoordinateSystem get_output_coordinate_system() const;
virtual std::string get_name() const=0;
virtual std::string get_extension() const=0;
virtual std::string get_additional_extensions() const;
virtual bool supports_compressed() const;
virtual bool write_file(const Filename &filename)=0;
protected:
PT(EggData) _egg_data;
DistanceUnit _output_units;
CoordinateSystem _output_coordinate_system;
bool _error;
};
#include "eggToSomethingConverter.I"
#endif

View File

@ -0,0 +1,391 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file somethingToEggConverter.I
* @author drose
* @date 2001-04-26
*/
/**
* Resets the error flag to the no-error state. had_error() will return false
* until a new error is generated.
*/
INLINE void SomethingToEggConverter::
clear_error() {
_error = false;
}
/**
* Returns true if an error was detected during the conversion process (unless
* _allow_errors is true), false otherwise.
*/
INLINE bool SomethingToEggConverter::
had_error() const {
return !_allow_errors && (_error || _path_replace->had_error());
}
/**
* Replaces the PathReplace object (which specifies how to mangle paths from
* the source to the destination egg file) with a new one.
*/
INLINE void SomethingToEggConverter::
set_path_replace(PathReplace *path_replace) {
_path_replace = path_replace;
}
/**
* Returns a pointer to the PathReplace object associated with this converter.
* If the converter is non-const, this returns a non-const pointer, which can
* be adjusted.
*/
INLINE PathReplace *SomethingToEggConverter::
get_path_replace() {
return _path_replace;
}
/**
* Returns a pointer to the PathReplace object associated with this converter.
* If the converter is non-const, this returns a non-const pointer, which can
* be adjusted.
*/
INLINE const PathReplace *SomethingToEggConverter::
get_path_replace() const {
return _path_replace;
}
/**
* Specifies how source animation will be converted into egg structures. The
* default is AC_none, which means animation tables will be ignored. This is
* only meaningful for converters that understand animation.
*/
INLINE void SomethingToEggConverter::
set_animation_convert(AnimationConvert animation_convert) {
_animation_convert = animation_convert;
}
/**
* Returns how source animation will be converted into egg structures.
*/
INLINE AnimationConvert SomethingToEggConverter::
get_animation_convert() const {
return _animation_convert;
}
/**
* Specifies the name of the character generated. This name should match
* between all the model and channel egg files for a particular character and
* its associated animations.
*/
INLINE void SomethingToEggConverter::
set_character_name(const std::string &character_name) {
_character_name = character_name;
}
/**
* Returns the name of the character generated. See set_character_name().
*/
INLINE const std::string &SomethingToEggConverter::
get_character_name() const {
return _character_name;
}
/**
* Specifies the starting frame of the animation to convert, in the units
* specified by set_input_frame_rate(). If this is unspecified, the starting
* frame is taken from the source, for instance from the first frame of the
* animation slider.
*/
INLINE void SomethingToEggConverter::
set_start_frame(double start_frame) {
_start_frame = start_frame;
_control_flags |= CF_start_frame;
}
/**
* Returns true if the starting frame has been explicitly specified via
* set_start_frame(), or false if the starting frame should be implicit based
* on the source.
*/
INLINE bool SomethingToEggConverter::
has_start_frame() const {
return (_control_flags & CF_start_frame) != 0;
}
/**
* Returns the value set by a previous call to set_start_frame(). It is an
* error to call this if has_start_frame() returns false.
*/
INLINE double SomethingToEggConverter::
get_start_frame() const {
nassertr(has_start_frame(), 0.0);
return _start_frame;
}
/**
* Removes the value previously set by set_start_frame().
*/
INLINE void SomethingToEggConverter::
clear_start_frame() {
_start_frame = 0.0;
_control_flags &= ~CF_start_frame;
}
/**
* Specifies the ending frame of the animation to convert, in the units
* specified by set_input_frame_rate(). If this is unspecified, the ending
* frame is taken from the source, for instance from the last frame of the
* animation slider.
*/
INLINE void SomethingToEggConverter::
set_end_frame(double end_frame) {
_end_frame = end_frame;
_control_flags |= CF_end_frame;
}
/**
* Returns true if the ending frame has been explicitly specified via
* set_end_frame(), or false if the ending frame should be implicit based on
* the source.
*/
INLINE bool SomethingToEggConverter::
has_end_frame() const {
return (_control_flags & CF_end_frame) != 0;
}
/**
* Returns the value set by a previous call to set_end_frame(). It is an
* error to call this if has_end_frame() returns false.
*/
INLINE double SomethingToEggConverter::
get_end_frame() const {
nassertr(has_end_frame(), 0.0);
return _end_frame;
}
/**
* Removes the value previously set by set_end_frame().
*/
INLINE void SomethingToEggConverter::
clear_end_frame() {
_end_frame = 0.0;
_control_flags &= ~CF_end_frame;
}
/**
* Specifies the increment between frames to extract. This is the amount to
* increment the time slider (in units of internal_frame_rate) between
* extracting each frame. If this is not specified, the default is taken from
* the animation package, or 1.0 if the animation package does not specified a
* frame increment.
*/
INLINE void SomethingToEggConverter::
set_frame_inc(double frame_inc) {
_frame_inc = frame_inc;
_control_flags |= CF_frame_inc;
}
/**
* Returns true if the frame increment has been explicitly specified via
* set_frame_inc(), or false if the ending frame should be implicit based on
* the source.
*/
INLINE bool SomethingToEggConverter::
has_frame_inc() const {
return (_control_flags & CF_frame_inc) != 0;
}
/**
* Returns the value set by a previous call to set_frame_inc(). It is an
* error to call this if has_frame_inc() returns false.
*/
INLINE double SomethingToEggConverter::
get_frame_inc() const {
nassertr(has_frame_inc(), 0.0);
return _frame_inc;
}
/**
* Removes the value previously set by set_frame_inc().
*/
INLINE void SomethingToEggConverter::
clear_frame_inc() {
_frame_inc = 0.0;
_control_flags &= ~CF_frame_inc;
}
/**
* Specifies the frame of animation to represent the neutral pose of the
* model.
*/
INLINE void SomethingToEggConverter::
set_neutral_frame(double neutral_frame) {
_neutral_frame = neutral_frame;
_control_flags |= CF_neutral_frame;
}
/**
* Returns true if the neutral frame has been explicitly specified via
* set_neutral_frame(), or false otherwise.
*/
INLINE bool SomethingToEggConverter::
has_neutral_frame() const {
return (_control_flags & CF_neutral_frame) != 0;
}
/**
* Returns the value set by a previous call to set_neutral_frame(). It is an
* error to call this if has_neutral_frame() returns false.
*/
INLINE double SomethingToEggConverter::
get_neutral_frame() const {
nassertr(has_neutral_frame(), 0.0);
return _neutral_frame;
}
/**
* Removes the value previously set by set_neutral_frame().
*/
INLINE void SomethingToEggConverter::
clear_neutral_frame() {
_neutral_frame = 0.0;
_control_flags &= ~CF_neutral_frame;
}
/**
* Specifies the number of frames per second that is represented by the
* "frame" unit in the animation package. If this is omitted, it is taken
* from whatever the file header indicates. Some animation packages do not
* encode a frame rate, in which case the default if this is omitted is the
* same as the output frame rate.
*/
INLINE void SomethingToEggConverter::
set_input_frame_rate(double input_frame_rate) {
_input_frame_rate = input_frame_rate;
_control_flags |= CF_input_frame_rate;
}
/**
* Returns true if the frame rate has been explicitly specified via
* set_input_frame_rate(), or false otherwise.
*/
INLINE bool SomethingToEggConverter::
has_input_frame_rate() const {
return (_control_flags & CF_input_frame_rate) != 0;
}
/**
* Returns the value set by a previous call to set_input_frame_rate(). It is
* an error to call this if has_input_frame_rate() returns false.
*/
INLINE double SomethingToEggConverter::
get_input_frame_rate() const {
nassertr(has_input_frame_rate(), 0.0);
return _input_frame_rate;
}
/**
* Removes the value previously set by set_input_frame_rate().
*/
INLINE void SomethingToEggConverter::
clear_input_frame_rate() {
_input_frame_rate = 0.0;
_control_flags &= ~CF_input_frame_rate;
}
/**
* Specifies the number of frames per second that the resulting animation
* should be played at. If this is omitted, it is taken to be the same as the
* input frame rate.
*/
INLINE void SomethingToEggConverter::
set_output_frame_rate(double output_frame_rate) {
_output_frame_rate = output_frame_rate;
_control_flags |= CF_output_frame_rate;
}
/**
* Returns true if the frame rate has been explicitly specified via
* set_output_frame_rate(), or false otherwise.
*/
INLINE bool SomethingToEggConverter::
has_output_frame_rate() const {
return (_control_flags & CF_output_frame_rate) != 0;
}
/**
* Returns the value set by a previous call to set_output_frame_rate(). It is
* an error to call this if has_output_frame_rate() returns false.
*/
INLINE double SomethingToEggConverter::
get_output_frame_rate() const {
nassertr(has_output_frame_rate(), 0.0);
return _output_frame_rate;
}
/**
* Removes the value previously set by set_output_frame_rate().
*/
INLINE void SomethingToEggConverter::
clear_output_frame_rate() {
_output_frame_rate = 0.0;
_control_flags &= ~CF_output_frame_rate;
}
/**
* Returns the default frame rate if nothing is specified for input_frame_rate
* or output_frame_rate, and the animation package does not have an implicit
* frame rate.
*/
INLINE double SomethingToEggConverter::
get_default_frame_rate() {
return 24.0;
}
/**
* Sets the merge_externals flag. When this is true, external references
* within the source file are read in and merged directly; otherwise, only a
* reference to a similarly-named egg file is inserted.
*/
INLINE void SomethingToEggConverter::
set_merge_externals(bool merge_externals) {
_merge_externals = merge_externals;
}
/**
* Returns the current state of the merge_externals flag. See
* set_merge_externals().
*/
INLINE bool SomethingToEggConverter::
get_merge_externals() const {
return _merge_externals;
}
/**
* Sets the EggData to NULL and makes the converter invalid.
*/
INLINE void SomethingToEggConverter::
clear_egg_data() {
set_egg_data(nullptr);
}
/**
* Returns the EggData structure.
*/
INLINE EggData *SomethingToEggConverter::
get_egg_data() {
return _egg_data;
}
/**
* Converts the indicated model filename to a relative or absolute or whatever
* filename, according to _path_replace.
*/
INLINE Filename SomethingToEggConverter::
convert_model_path(const Filename &orig_filename) {
return _path_replace->convert_path(orig_filename);
}

View File

@ -0,0 +1,165 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file somethingToEggConverter.cxx
* @author drose
* @date 2001-04-26
*/
#include "somethingToEggConverter.h"
#include "eggData.h"
#include "eggExternalReference.h"
/**
*
*/
SomethingToEggConverter::
SomethingToEggConverter() {
_allow_errors = false;
_path_replace = new PathReplace;
_path_replace->_path_store = PS_absolute;
_animation_convert = AC_none;
_start_frame = 0.0;
_end_frame = 0.0;
_frame_inc = 0.0;
_neutral_frame = 0.0;
_input_frame_rate = 0.0;
_output_frame_rate = 0.0;
_control_flags = 0;
_merge_externals = false;
_egg_data = nullptr;
_error = false;
}
/**
*
*/
SomethingToEggConverter::
SomethingToEggConverter(const SomethingToEggConverter &copy) :
_allow_errors(copy._allow_errors),
_path_replace(copy._path_replace),
_merge_externals(copy._merge_externals)
{
_egg_data = nullptr;
_error = false;
}
/**
*
*/
SomethingToEggConverter::
~SomethingToEggConverter() {
clear_egg_data();
}
/**
* Sets the egg data that will be filled in when convert_file() is called.
* This must be called before convert_file().
*/
void SomethingToEggConverter::
set_egg_data(EggData *egg_data) {
_egg_data = egg_data;
}
/**
* Returns a space-separated list of extension, in addition to the one
* returned by get_extension(), that are recognized by this converter.
*/
std::string SomethingToEggConverter::
get_additional_extensions() const {
return std::string();
}
/**
* Returns true if this file type can transparently load compressed files
* (with a .pz extension), false otherwise.
*/
bool SomethingToEggConverter::
supports_compressed() const {
return false;
}
/**
* Returns true if this converter can directly convert the model type to
* internal Panda memory structures, given the indicated options, or false
* otherwise. If this returns true, then convert_to_node() may be called to
* perform the conversion, which may be faster than calling convert_file() if
* the ultimate goal is a PandaNode anyway.
*/
bool SomethingToEggConverter::
supports_convert_to_node(const LoaderOptions &options) const {
return false;
}
/**
* This may be called after convert_file() has been called and returned true,
* indicating a successful conversion. It will return the distance units
* represented by the converted egg file, if known, or DU_invalid if not
* known.
*/
DistanceUnit SomethingToEggConverter::
get_input_units() {
return DU_invalid;
}
/**
* Reads the input file and directly produces a ready-to-render model file as
* a PandaNode. Returns NULL on failure, or if it is not supported. (This
* functionality is not supported by all converter types; see
* supports_convert_to_node()).
*/
PT(PandaNode) SomethingToEggConverter::
convert_to_node(const LoaderOptions &options, const Filename &filename) {
return nullptr;
}
/**
* Handles an external reference in the source file. If the merge_externals
* flag is true (see set_merge_externals()), this causes the named file to be
* read in and converted, and the converted egg geometry is parented to
* egg_parent. Otherwise, only a reference to a similarly named egg file is
* parented to egg_parent.
*
* The parameters orig_filename and searchpath are as those passed to
* convert_model_path().
*
* Returns true on success, false on failure.
*/
bool SomethingToEggConverter::
handle_external_reference(EggGroupNode *egg_parent,
const Filename &ref_filename) {
if (_merge_externals) {
SomethingToEggConverter *ext = make_copy();
PT(EggData) egg_data = new EggData;
egg_data->set_coordinate_system(get_egg_data()->get_coordinate_system());
ext->set_egg_data(egg_data);
if (!ext->convert_file(ref_filename)) {
delete ext;
nout << "Unable to read external reference: " << ref_filename << "\n";
_error = true;
return false;
}
egg_parent->steal_children(*egg_data);
delete ext;
return true;
} else {
// If we're installing external references instead of reading them, we
// should make it into an egg filename.
Filename filename = ref_filename;
filename.set_extension("egg");
EggExternalReference *egg_ref = new EggExternalReference("", filename);
egg_parent->add_child(egg_ref);
}
return true;
}

View File

@ -0,0 +1,148 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file somethingToEggConverter.h
* @author drose
* @date 2001-04-17
*/
#ifndef SOMETHINGTOEGGCONVERTER_H
#define SOMETHINGTOEGGCONVERTER_H
#include "pandatoolbase.h"
#include "filename.h"
#include "config_putil.h" // for get_model_path()
#include "animationConvert.h"
#include "pathReplace.h"
#include "pointerTo.h"
#include "distanceUnit.h"
#include "pandaNode.h"
class EggData;
class EggGroupNode;
class LoaderOptions;
/**
* This is a base class for a family of converter classes that manage a
* conversion from some file type to egg format.
*
* Classes of this type can be used to implement xxx2egg converter programs,
* as well as LoaderFileTypeXXX run-time loaders.
*/
class SomethingToEggConverter {
public:
SomethingToEggConverter();
SomethingToEggConverter(const SomethingToEggConverter &copy);
virtual ~SomethingToEggConverter();
virtual SomethingToEggConverter *make_copy()=0;
INLINE void clear_error();
INLINE bool had_error() const;
INLINE void set_path_replace(PathReplace *path_replace);
INLINE PathReplace *get_path_replace();
INLINE const PathReplace *get_path_replace() const;
// These methods dealing with animation and frame rate are only relevant to
// converter types that understand animation.
INLINE void set_animation_convert(AnimationConvert animation_convert);
INLINE AnimationConvert get_animation_convert() const;
INLINE void set_character_name(const std::string &character_name);
INLINE const std::string &get_character_name() const;
INLINE void set_start_frame(double start_frame);
INLINE bool has_start_frame() const;
INLINE double get_start_frame() const;
INLINE void clear_start_frame();
INLINE void set_end_frame(double end_frame);
INLINE bool has_end_frame() const;
INLINE double get_end_frame() const;
INLINE void clear_end_frame();
INLINE void set_frame_inc(double frame_inc);
INLINE bool has_frame_inc() const;
INLINE double get_frame_inc() const;
INLINE void clear_frame_inc();
INLINE void set_neutral_frame(double neutral_frame);
INLINE bool has_neutral_frame() const;
INLINE double get_neutral_frame() const;
INLINE void clear_neutral_frame();
INLINE void set_input_frame_rate(double input_frame_rate);
INLINE bool has_input_frame_rate() const;
INLINE double get_input_frame_rate() const;
INLINE void clear_input_frame_rate();
INLINE void set_output_frame_rate(double output_frame_rate);
INLINE bool has_output_frame_rate() const;
INLINE double get_output_frame_rate() const;
INLINE void clear_output_frame_rate();
INLINE static double get_default_frame_rate();
INLINE void set_merge_externals(bool merge_externals);
INLINE bool get_merge_externals() const;
void set_egg_data(EggData *egg_data);
INLINE void clear_egg_data();
INLINE EggData *get_egg_data();
virtual std::string get_name() const=0;
virtual std::string get_extension() const=0;
virtual std::string get_additional_extensions() const;
virtual bool supports_compressed() const;
virtual bool supports_convert_to_node(const LoaderOptions &options) const;
virtual bool convert_file(const Filename &filename)=0;
virtual PT(PandaNode) convert_to_node(const LoaderOptions &options, const Filename &filename);
virtual DistanceUnit get_input_units();
bool handle_external_reference(EggGroupNode *egg_parent,
const Filename &ref_filename);
INLINE Filename convert_model_path(const Filename &orig_filename);
// Set this true to treat errors as warnings and generate output anyway.
bool _allow_errors;
protected:
PT(PathReplace) _path_replace;
AnimationConvert _animation_convert;
std::string _character_name;
double _start_frame;
double _end_frame;
double _frame_inc;
double _neutral_frame;
double _input_frame_rate; // frames per second
double _output_frame_rate; // frames per second
enum ControlFlags {
CF_start_frame = 0x0001,
CF_end_frame = 0x0002,
CF_frame_inc = 0x0004,
CF_neutral_frame = 0x0008,
CF_input_frame_rate = 0x0010,
CF_output_frame_rate = 0x0020,
};
int _control_flags;
bool _merge_externals;
PT(EggData) _egg_data;
bool _error;
};
#include "somethingToEggConverter.I"
#endif

View File

@ -0,0 +1,26 @@
if(NOT HAVE_EGG OR NOT HAVE_FCOLLADA)
return()
endif()
set(P3DAEEGG_HEADERS
config_daeegg.h
daeCharacter.h
daeMaterials.h
daeToEggConverter.h
fcollada_utils.h
pre_fcollada_include.h
)
set(P3DAEEGG_SOURCES
config_daeegg.cxx
daeCharacter.cxx
daeMaterials.cxx
daeToEggConverter.cxx
)
add_library(p3daeegg STATIC ${P3DAEEGG_HEADERS} ${P3DAEEGG_SOURCES})
target_link_libraries(p3daeegg p3eggbase
PKG::FCOLLADA)
# This is only needed for binaries in the pandatool package. It is not useful
# for user applications, so it is not installed.

View File

@ -0,0 +1,43 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file config_daeegg.cxx
* @author rdb
* @date 2008-10-30
*/
#include "config_daeegg.h"
#include "daeCharacter.h"
#include "daeMaterials.h"
#include "dconfig.h"
Configure(config_daeegg);
NotifyCategoryDef(daeegg, "");
ConfigureFn(config_daeegg) {
init_libdaeegg();
}
/**
* Initializes the library. This must be called at least once before any of
* the functions or classes in this library can be used. Normally it will be
* called by the static initializers and need not be called explicitly, but
* special cases exist.
*/
void
init_libdaeegg() {
static bool initialized = false;
if (initialized) {
return;
}
initialized = true;
DaeCharacter::init_type();
DaeMaterials::init_type();
}

View File

@ -0,0 +1,24 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file config_daeegg.h
* @author rdb
* @date 2008-10-30
*/
#ifndef CONFIG_DAEEGG_H
#define CONFIG_DAEEGG_H
#include "pandatoolbase.h"
#include "notifyCategoryProxy.h"
NotifyCategoryDeclNoExport(daeegg);
extern void init_libdaeegg();
#endif

View File

@ -0,0 +1,312 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file daeCharacter.cxx
* @author rdb
* @date 2008-11-24
*/
#include "daeCharacter.h"
#include "config_daeegg.h"
#include "fcollada_utils.h"
#include "pt_EggVertex.h"
#include "eggXfmSAnim.h"
#include "daeToEggConverter.h"
#include "daeMaterials.h"
#include "eggExternalReference.h"
#include <FCDocument/FCDocument.h>
#include <FCDocument/FCDController.h>
#include <FCDocument/FCDGeometry.h>
#include <FCDocument/FCDSceneNodeTools.h>
#include <FCDocument/FCDSceneNode.h>
#include <FCDocument/FCDTransform.h>
#include <FCDocument/FCDAnimated.h>
#include <FCDocument/FCDAnimationCurve.h>
#include <FCDocument/FCDAnimationKey.h>
TypeHandle DaeCharacter::_type_handle;
/**
*
*/
DaeCharacter::
DaeCharacter(EggGroup *node_group, const FCDControllerInstance *instance) :
_node_group(node_group),
_skin_mesh(nullptr),
_instance(instance),
_bind_shape_mat(LMatrix4d::ident_mat()),
_name(node_group->get_name()),
_skin_controller(nullptr) {
// If it's a skin controller, add the controller joints.
const FCDController *controller = (const FCDController *)instance->GetEntity();
if (controller == nullptr) {
return;
}
_skin_mesh = controller->GetBaseGeometry()->GetMesh();
if (controller->IsSkin()) {
_skin_controller = controller->GetSkinController();
_bind_shape_mat = DAEToEggConverter::convert_matrix(_skin_controller->GetBindShapeTransform());
}
}
/**
* Binds the joints to the character. This means changing them to the bind
* pose. It is necessary to call this before process_skin_geometry.
*
* Returns the root group.
*/
void DaeCharacter::
bind_joints(JointMap &joint_map) {
_joints.clear();
size_t num_joints = _skin_controller->GetJointCount();
_joints.reserve(num_joints);
// Record the bind pose for each joint.
for (size_t j = 0; j < num_joints; ++j) {
const FCDSkinControllerJoint *skin_joint = _skin_controller->GetJoint(j);
std::string sid = FROM_FSTRING(skin_joint->GetId());
LMatrix4d bind_pose;
bind_pose.invert_from(DAEToEggConverter::convert_matrix(
skin_joint->GetBindPoseInverse()));
// Check that we already encountered this joint during traversal.
JointMap::iterator ji = joint_map.find(sid);
if (ji != joint_map.end()) {
Joint &joint = ji->second;
if (joint._character != nullptr) {
// In some cases, though, multiple controllers share the same joints.
// We can't support this without duplicating the joint structure, so
// we check if the bind poses are the same.
if (!joint._bind_pose.almost_equal(bind_pose, 0.0001)) {
// Ugh. What else could we do?
daeegg_cat.error()
<< "Multiple controllers share joint with sid " << sid
<< ", with different bind poses.\n";
}
} else {
// Mark the joint as being controlled by this character.
joint._bind_pose = bind_pose;
joint._character = this;
}
_joints.push_back(joint);
} else {
daeegg_cat.warning()
<< "Unknown joint sid being referenced: '" << sid << "'\n";
// We still have to add a dummy joint or the index will be off.
_joints.push_back(Joint(nullptr, nullptr));
}
}
}
/**
* Traverses through the character hierarchy in order to bind the mesh to the
* character. This involves reorienting the joints to match the bind pose.
*
* It is important that this is called only once.
*/
void DaeCharacter::
adjust_joints(FCDSceneNode *node, const JointMap &joint_map,
const LMatrix4d &transform) {
LMatrix4d this_transform = transform;
if (node->IsJoint()) {
std::string sid = FROM_FSTRING(node->GetSubId());
JointMap::const_iterator ji = joint_map.find(sid);
if (ji != joint_map.end()) {
const Joint &joint = ji->second;
// Panda needs the joints to be in bind pose. Not fun! We copy the
// joint transform to the default pose, though, so that Panda will
// restore the joint transformation after binding.
if (joint._character == this) {
LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat *
invert(transform);
// LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat *
// joint._group->get_parent()->get_node_frame_inv();
this_transform = bind_pose * this_transform;
joint._group->set_default_pose(*joint._group);
joint._group->set_transform3d(bind_pose);
/*
PT(EggGroup) sphere = new EggGroup;
sphere->add_uniform_scale(0.1);
sphere->set_group_type(EggGroup::GT_instance);
sphere->add_child(new EggExternalReference("", "jack.egg"));
joint._group->add_child(sphere);
*/
}
}
} else {
// this_transform = DAEToEggConverter::convert_matrix(node->ToMatrix());
}
// Loop through the children joints
for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
// if (node->GetChild(ch)->IsJoint()) {
adjust_joints(node->GetChild(ch), joint_map, this_transform);
// }
}
}
/**
* Adds the influences for the given vertex.
*/
void DaeCharacter::
influence_vertex(int index, EggVertex *vertex) {
const FCDSkinControllerVertex *influence = _skin_controller->GetVertexInfluence(index);
for (size_t pa = 0; pa < influence->GetPairCount(); ++pa) {
const FCDJointWeightPair* jwpair = influence->GetPair(pa);
if (jwpair->jointIndex >= 0 && jwpair->jointIndex < (int)_joints.size()) {
EggGroup *joint = _joints[jwpair->jointIndex]._group.p();
if (joint != nullptr) {
joint->ref_vertex(vertex, jwpair->weight);
}
} else {
daeegg_cat.error()
<< "Invalid joint index: " << jwpair->jointIndex << "\n";
}
}
}
/**
* Collects all animation keys of animations applied to this character.
*/
void DaeCharacter::
collect_keys(pset<float> &keys) {
#if FCOLLADA_VERSION < 0x00030005
FCDSceneNodeList roots = _instance->FindSkeletonNodes();
#else
FCDSceneNodeList roots;
_instance->FindSkeletonNodes(roots);
#endif
for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
r_collect_keys(*it, keys);
}
}
/**
* Collects all animation keys found for the given node tree.
*/
void DaeCharacter::
r_collect_keys(FCDSceneNode* node, pset<float> &keys) {
FCDAnimatedList animateds;
// Collect all the animation curves
for (size_t t = 0; t < node->GetTransformCount(); ++t) {
FCDTransform *transform = node->GetTransform(t);
FCDAnimated *animated = transform->GetAnimated();
if (animated != nullptr) {
const FCDAnimationCurveListList &all_curves = animated->GetCurves();
for (size_t ci = 0; ci < all_curves.size(); ++ci) {
const FCDAnimationCurveTrackList &curves = all_curves[ci];
if (curves.empty()) {
continue;
}
size_t num_keys = curves.front()->GetKeyCount();
const FCDAnimationKey **curve_keys = curves.front()->GetKeys();
for (size_t c = 0; c < num_keys; ++c) {
keys.insert(curve_keys[c]->input);
}
}
}
}
}
/**
* Processes a joint node and its transforms.
*/
void DaeCharacter::
build_table(EggTable *parent, FCDSceneNode* node, const pset<float> &keys) {
nassertv(node != nullptr);
if (!node->IsJoint()) {
for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
build_table(parent, node->GetChild(ch), keys);
}
return;
}
std::string node_id = FROM_FSTRING(node->GetDaeId());
PT(EggTable) table = new EggTable(node_id);
table->set_table_type(EggTable::TT_table);
parent->add_child(table);
PT(EggXfmSAnim) xform = new EggXfmSAnim("xform");
table->add_child(xform);
// Generate the sampled animation and loop through the matrices
FCDAnimatedList animateds;
// Collect all the animation curves
for (size_t t = 0; t < node->GetTransformCount(); ++t) {
FCDTransform *transform = node->GetTransform(t);
FCDAnimated *animated = transform->GetAnimated();
if (animated != nullptr) {
if (animated->HasCurve()) {
animateds.push_back(animated);
}
}
}
// Sample the scene node transform
float last_key;
float timing_total = 0;
pset<float>::const_iterator ki;
for (ki = keys.begin(); ki != keys.end(); ++ki) {
for (FCDAnimatedList::iterator it = animateds.begin(); it != animateds.end(); ++it) {
// Sample each animated, which changes the transform values directly
(*it)->Evaluate(*ki);
}
if (ki != keys.begin()) {
timing_total += (*ki - last_key);
}
last_key = *ki;
// Retrieve the new transform matrix for the COLLADA scene node
FMMatrix44 fmat = node->ToMatrix();
// Work around issue in buggy exporters (like ColladaMax)
if (IS_NEARLY_ZERO(fmat[3][3])) {
fmat[3][3] = 1;
}
xform->add_data(DAEToEggConverter::convert_matrix(fmat));
}
// Quantize the FPS, otherwise Panda complains about FPS mismatches.
float fps = cfloor(((keys.size() - 1) / timing_total) * 100 + 0.5f) * 0.01f;
xform->set_fps(fps);
// Loop through the children joints
for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
// if (node->GetChild(ch)->IsJoint()) {
build_table(table, node->GetChild(ch), keys);
// }
}
}

View File

@ -0,0 +1,95 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file daeCharacter.h
* @author rdb
* @date 2008-11-24
*/
#ifndef DAECHARACTER_H
#define DAECHARACTER_H
#include "pandatoolbase.h"
#include "typedReferenceCount.h"
#include "typeHandle.h"
#include "eggTable.h"
#include "epvector.h"
#include "pre_fcollada_include.h"
#include <FCollada.h>
#include <FCDocument/FCDSceneNode.h>
#include <FCDocument/FCDControllerInstance.h>
#include <FCDocument/FCDSkinController.h>
#include <FCDocument/FCDGeometryMesh.h>
class DAEToEggConverter;
/**
* Class representing an animated character.
*/
class DaeCharacter : public TypedReferenceCount {
public:
DaeCharacter(EggGroup *node_group, const FCDControllerInstance* controller_instance);
struct Joint {
INLINE Joint(EggGroup *group, const FCDSceneNode *scene_node) :
_bind_pose(LMatrix4d::ident_mat()),
_group(group),
_scene_node(scene_node),
_character(nullptr) {}
LMatrix4d _bind_pose;
PT(EggGroup) _group;
const FCDSceneNode *_scene_node;
DaeCharacter *_character;
};
typedef epvector<Joint> Joints;
typedef pmap<std::string, Joint> JointMap;
void bind_joints(JointMap &joint_map);
void adjust_joints(FCDSceneNode *node, const JointMap &joint_map,
const LMatrix4d &transform = LMatrix4d::ident_mat());
void influence_vertex(int index, EggVertex *vertex);
void collect_keys(pset<float> &keys);
void r_collect_keys(FCDSceneNode *node, pset<float> &keys);
void build_table(EggTable *parent, FCDSceneNode* node, const pset<float> &keys);
public:
PT(EggGroup) _node_group;
const FCDGeometryMesh *_skin_mesh;
const FCDControllerInstance *_instance;
LMatrix4d _bind_shape_mat;
private:
std::string _name;
const FCDSkinController *_skin_controller;
Joints _joints;
JointMap _bound_joints;
public:
virtual TypeHandle get_type() const {
return get_class_type();
}
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
static TypeHandle get_class_type() {
return _type_handle;
}
static void init_type() {
TypedReferenceCount::init_type();
register_type(_type_handle, "DaeCharacter",
TypedReferenceCount::get_class_type());
}
private:
static TypeHandle _type_handle;
};
#endif

View File

@ -0,0 +1,451 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file daeMaterials.cxx
* @author rdb
* @date 2008-10-03
*/
#include "daeMaterials.h"
#include "config_daeegg.h"
#include "fcollada_utils.h"
#include <FCDocument/FCDocument.h>
#include <FCDocument/FCDMaterial.h>
#include <FCDocument/FCDEffect.h>
#include <FCDocument/FCDTexture.h>
#include <FCDocument/FCDEffectParameterSampler.h>
#include <FCDocument/FCDImage.h>
#include "filename.h"
#include "string_utils.h"
using std::endl;
using std::string;
TypeHandle DaeMaterials::_type_handle;
// luminance function, based on the ISOCIE color standards see ITU-R
// Recommendation BT.709-4
#define luminance(c) ((c[0] * 0.212671 + c[1] * 0.715160 + c[2] * 0.072169))
/**
*
*/
DaeMaterials::
DaeMaterials(const FCDGeometryInstance* geometry_instance) {
for (size_t mi = 0; mi < geometry_instance->GetMaterialInstanceCount(); ++mi) {
add_material_instance(geometry_instance->GetMaterialInstance(mi));
}
}
/**
* Adds a material instance. Normally automatically done by constructor.
*/
void DaeMaterials::add_material_instance(const FCDMaterialInstance* instance) {
nassertv(instance != nullptr);
const string semantic (FROM_FSTRING(instance->GetSemantic()));
if (_materials.count(semantic) > 0) {
daeegg_cat.warning() << "Ignoring duplicate material with semantic " << semantic << endl;
return;
}
_materials[semantic] = new DaeMaterial();
// Load in the uvsets
for (size_t vib = 0; vib < instance->GetVertexInputBindingCount(); ++vib) {
const FCDMaterialInstanceBindVertexInput* mivib = instance->GetVertexInputBinding(vib);
assert(mivib != nullptr);
PT(DaeVertexInputBinding) bvi = new DaeVertexInputBinding();
bvi->_input_set = mivib->inputSet;
#if FCOLLADA_VERSION >= 0x00030005
bvi->_input_semantic = mivib->GetInputSemantic();
bvi->_semantic = *mivib->semantic;
#else
bvi->_input_semantic = mivib->inputSemantic;
bvi->_semantic = FROM_FSTRING(mivib->semantic);
#endif
_materials[semantic]->_uvsets.push_back(bvi);
}
// Handle the material stuff
if (daeegg_cat.is_spam()) {
daeegg_cat.spam() << "Trying to process material with semantic " << semantic << endl;
}
PT_EggMaterial egg_material = new EggMaterial(semantic);
pvector<PT_EggTexture> egg_textures;
const FCDEffect* effect = instance->GetMaterial()->GetEffect();
if (effect == nullptr) {
if (daeegg_cat.is_debug()) {
daeegg_cat.debug() << "Ignoring material (semantic: " << semantic << ") without assigned effect" << endl;
}
} else {
// Grab the common profile effect
const FCDEffectStandard* effect_common = (FCDEffectStandard *)effect->FindProfile(FUDaeProfileType::COMMON);
if (effect_common == nullptr) {
daeegg_cat.info() << "Ignoring effect referenced by material with semantic " << semantic
<< " because it has no common profile" << endl;
} else {
if (daeegg_cat.is_spam()) {
daeegg_cat.spam() << "Processing effect, material semantic is " << semantic << endl;
}
// Set the material parameters
egg_material->set_amb(TO_COLOR(effect_common->GetAmbientColor()));
// We already process transparency using blend modes LVecBase4 diffuse =
// TO_COLOR(effect_common->GetDiffuseColor());
// diffuse.set_w(diffuse.get_w() * (1.0f -
// effect_common->GetOpacity())); egg_material->set_diff(diffuse);
egg_material->set_diff(TO_COLOR(effect_common->GetDiffuseColor()));
egg_material->set_emit(TO_COLOR(effect_common->GetEmissionColor()) * effect_common->GetEmissionFactor());
egg_material->set_shininess(effect_common->GetShininess());
egg_material->set_spec(TO_COLOR(effect_common->GetSpecularColor()));
// Now try to load in the textures
process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::DIFFUSE, EggTexture::ET_modulate);
process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::BUMP, EggTexture::ET_normal);
process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::SPECULAR, EggTexture::ET_modulate_gloss);
process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::SPECULAR_LEVEL, EggTexture::ET_gloss);
process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::TRANSPARENT, EggTexture::ET_unspecified, EggTexture::F_alpha);
process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::EMISSION, EggTexture::ET_add);
#if FCOLLADA_VERSION < 0x00030005
process_texture_bucket(semantic, effect_common, FUDaeTextureChannel::OPACITY, EggTexture::ET_unspecified, EggTexture::F_alpha);
#endif
// Now, calculate the color blend stuff.
_materials[semantic]->_blend = convert_blend(effect_common->GetTransparencyMode(),
TO_COLOR(effect_common->GetTranslucencyColor()),
effect_common->GetTranslucencyFactor());
}
// Find an <extra> tag to support some extra stuff from extensions
process_extra(semantic, effect->GetExtra());
}
if (daeegg_cat.is_spam()) {
daeegg_cat.spam() << "Found " << egg_textures.size() << " textures in material" << endl;
}
_materials[semantic]->_egg_material = egg_material;
}
/**
* Processes the given texture bucket and gives the textures in it the given
* envtype and format.
*/
void DaeMaterials::
process_texture_bucket(const string semantic, const FCDEffectStandard* effect_common, FUDaeTextureChannel::Channel bucket, EggTexture::EnvType envtype, EggTexture::Format format) {
for (size_t tx = 0; tx < effect_common->GetTextureCount(bucket); ++tx) {
const FCDImage* image = effect_common->GetTexture(bucket, tx)->GetImage();
if (image == nullptr) {
daeegg_cat.warning() << "Texture references a nonexisting image!" << endl;
} else {
const FCDEffectParameterSampler* sampler = effect_common->GetTexture(bucket, tx)->GetSampler();
// FCollada only supplies absolute paths. We need to grab the document
// location ourselves and make the image path absolute.
Filename texpath;
if (image->GetDocument()) {
Filename docpath = Filename::from_os_specific(FROM_FSTRING(image->GetDocument()->GetFileUrl()));
docpath.make_canonical();
texpath = Filename::from_os_specific(FROM_FSTRING(image->GetFilename()));
texpath.make_canonical();
texpath.make_relative_to(docpath.get_dirname(), true);
if (daeegg_cat.is_debug()) {
daeegg_cat.debug() << "Found texture with path " << texpath << endl;
}
} else {
// Never mind.
texpath = Filename::from_os_specific(FROM_FSTRING(image->GetFilename()));
}
PT_EggTexture egg_texture = new EggTexture(FROM_FSTRING(image->GetDaeId()), texpath.to_os_generic());
// Find a set of UV coordinates
const FCDEffectParameterInt* uvset = effect_common->GetTexture(bucket, tx)->GetSet();
if (uvset != nullptr) {
if (daeegg_cat.is_debug()) {
daeegg_cat.debug() << "Texture has uv name '" << FROM_FSTRING(uvset->GetSemantic()) << "'\n";
}
string uvset_semantic (FROM_FSTRING(uvset->GetSemantic()));
// Only set the UV name if this UV set actually exists.
for (size_t i = 0; i < _materials[semantic]->_uvsets.size(); ++i) {
if (_materials[semantic]->_uvsets[i]->_semantic == uvset_semantic) {
egg_texture->set_uv_name(uvset_semantic);
break;
}
}
}
// Apply sampler stuff
if (sampler != nullptr) {
egg_texture->set_texture_type(convert_texture_type(sampler->GetSamplerType()));
egg_texture->set_wrap_u(convert_wrap_mode(sampler->GetWrapS()));
if (sampler->GetSamplerType() != FCDEffectParameterSampler::SAMPLER1D) {
egg_texture->set_wrap_v(convert_wrap_mode(sampler->GetWrapT()));
}
if (sampler->GetSamplerType() == FCDEffectParameterSampler::SAMPLER3D) {
egg_texture->set_wrap_w(convert_wrap_mode(sampler->GetWrapP()));
}
egg_texture->set_minfilter(convert_filter_type(sampler->GetMinFilter()));
egg_texture->set_magfilter(convert_filter_type(sampler->GetMagFilter()));
if (envtype != EggTexture::ET_unspecified) {
egg_texture->set_env_type(envtype);
}
if (format != EggTexture::F_unspecified) {
egg_texture->set_format(format);
}
}
_materials[semantic]->_egg_textures.push_back(egg_texture);
}
}
}
/**
* Processes the extra data in the given <extra> tag. If the given element is
* NULL, it just silently returns.
*/
void DaeMaterials::
process_extra(const string semantic, const FCDExtra* extra) {
if (extra == nullptr) return;
const FCDEType* etype = extra->GetDefaultType();
if (etype == nullptr) return;
for (size_t et = 0; et < etype->GetTechniqueCount(); ++et) {
const FCDENode* enode = ((const FCDENode*)(etype->GetTechnique(et)))->FindChildNode("double_sided");
if (enode != nullptr) {
string content = trim(enode->GetContent());
if (content == "1" || content == "true") {
_materials[semantic]->_double_sided = true;
} else if (content == "0" || content == "false") {
_materials[semantic]->_double_sided = false;
} else {
daeegg_cat.warning() << "Expected <double_sided> tag to be either 1 or 0, found '" << content << "' instead" << endl;
}
}
}
}
/**
* Applies the stuff to the given EggPrimitive.
*/
void DaeMaterials::
apply_to_primitive(const string semantic, const PT(EggPrimitive) to) {
if (_materials.count(semantic) > 0) {
to->set_material(_materials[semantic]->_egg_material);
for (pvector<PT_EggTexture>::iterator it = _materials[semantic]->_egg_textures.begin(); it != _materials[semantic]->_egg_textures.end(); ++it) {
if (daeegg_cat.is_spam()) {
daeegg_cat.spam() << "Applying texture " << (*it)->get_name() << " from material with semantic " << semantic << endl;
}
to->add_texture(*it);
}
to->set_bface_flag(_materials[semantic]->_double_sided);
}
}
/**
* Applies the colorblend stuff to the given EggGroup.
*/
void DaeMaterials::
apply_to_group(const string semantic, const PT(EggGroup) to, bool invert_transparency) {
if (_materials.count(semantic) > 0) {
PT(DaeBlendSettings) blend = _materials[semantic]->_blend;
if (blend && blend->_enabled) {
to->set_blend_mode(EggGroup::BM_add);
to->set_blend_color(blend->_color);
if (invert_transparency) {
to->set_blend_operand_a(blend->_operand_b);
to->set_blend_operand_b(blend->_operand_a);
} else {
to->set_blend_operand_a(blend->_operand_a);
to->set_blend_operand_b(blend->_operand_b);
}
} else if (blend && invert_transparency) {
to->set_blend_mode(EggGroup::BM_add);
to->set_blend_color(blend->_color);
to->set_blend_operand_a(blend->_operand_b);
to->set_blend_operand_b(blend->_operand_a);
}
}
}
/**
* Returns the semantic of the uvset with the specified input set, or an empty
* string if the given material has no input set.
*/
const string DaeMaterials::
get_uvset_name(const string semantic, FUDaeGeometryInput::Semantic input_semantic, int32 input_set) {
if (_materials.count(semantic) > 0) {
if (input_set == -1 && _materials[semantic]->_uvsets.size() == 1) {
return _materials[semantic]->_uvsets[0]->_semantic;
} else {
for (size_t i = 0; i < _materials[semantic]->_uvsets.size(); ++i) {
if (_materials[semantic]->_uvsets[i]->_input_set == input_set &&
_materials[semantic]->_uvsets[i]->_input_semantic == input_semantic) {
return _materials[semantic]->_uvsets[i]->_semantic;
}
}
// If we can't find it, let's look again, but don't care for the
// input_semantic this time. The reason for this is that some tools
// export textangents and texbinormals bound to a uvset with input
// semantic TEXCOORD.
for (size_t i = 0; i < _materials[semantic]->_uvsets.size(); ++i) {
if (_materials[semantic]->_uvsets[i]->_input_set == input_set) {
if (daeegg_cat.is_debug()) {
daeegg_cat.debug() << "Using uv set with non-matching input semantic " << _materials[semantic]->_uvsets[i]->_semantic << "\n";
}
return _materials[semantic]->_uvsets[i]->_semantic;
}
}
if (daeegg_cat.is_debug()) {
daeegg_cat.debug() << "No uv set binding found for input set " << input_set << "\n";
}
}
}
return "";
}
/**
* Converts an FCollada sampler type to the EggTexture texture type
* equivalent.
*/
EggTexture::TextureType DaeMaterials::
convert_texture_type(const FCDEffectParameterSampler::SamplerType orig_type) {
switch (orig_type) {
case FCDEffectParameterSampler::SAMPLER1D:
return EggTexture::TT_1d_texture;
case FCDEffectParameterSampler::SAMPLER2D:
return EggTexture::TT_2d_texture;
case FCDEffectParameterSampler::SAMPLER3D:
return EggTexture::TT_3d_texture;
case FCDEffectParameterSampler::SAMPLERCUBE:
return EggTexture::TT_cube_map;
default:
daeegg_cat.warning() << "Invalid sampler type found" << endl;
}
return EggTexture::TT_unspecified;
}
/**
* Converts an FCollada wrap mode to the EggTexture wrap mode equivalent.
*/
EggTexture::WrapMode DaeMaterials::
convert_wrap_mode(const FUDaeTextureWrapMode::WrapMode orig_mode) {
switch (orig_mode) {
case FUDaeTextureWrapMode::NONE:
// FIXME: this shouldnt be unspecified
return EggTexture::WM_unspecified;
case FUDaeTextureWrapMode::WRAP:
return EggTexture::WM_repeat;
case FUDaeTextureWrapMode::MIRROR:
return EggTexture::WM_mirror;
case FUDaeTextureWrapMode::CLAMP:
return EggTexture::WM_clamp;
case FUDaeTextureWrapMode::BORDER:
return EggTexture::WM_border_color;
case FUDaeTextureWrapMode::UNKNOWN:
return EggTexture::WM_unspecified;
default:
daeegg_cat.warning() << "Invalid wrap mode found: " << FUDaeTextureWrapMode::ToString(orig_mode) << endl;
}
return EggTexture::WM_unspecified;
}
/**
* Converts an FCollada filter function to the EggTexture wrap type
* equivalent.
*/
EggTexture::FilterType DaeMaterials::
convert_filter_type(const FUDaeTextureFilterFunction::FilterFunction orig_type) {
switch (orig_type) {
case FUDaeTextureFilterFunction::NONE:
// FIXME: this shouldnt be unspecified
return EggTexture::FT_unspecified;
case FUDaeTextureFilterFunction::NEAREST:
return EggTexture::FT_nearest;
case FUDaeTextureFilterFunction::LINEAR:
return EggTexture::FT_linear;
case FUDaeTextureFilterFunction::NEAREST_MIPMAP_NEAREST:
return EggTexture::FT_nearest_mipmap_nearest;
case FUDaeTextureFilterFunction::LINEAR_MIPMAP_NEAREST:
return EggTexture::FT_linear_mipmap_nearest;
case FUDaeTextureFilterFunction::NEAREST_MIPMAP_LINEAR:
return EggTexture::FT_nearest_mipmap_linear;
case FUDaeTextureFilterFunction::LINEAR_MIPMAP_LINEAR:
return EggTexture::FT_linear_mipmap_linear;
case FUDaeTextureFilterFunction::UNKNOWN:
return EggTexture::FT_unspecified;
default:
daeegg_cat.warning() << "Unknown filter type found: " << FUDaeTextureFilterFunction::ToString(orig_type) << endl;
}
return EggTexture::FT_unspecified;
}
/**
* Converts collada blend attribs to Panda's equivalents.
*/
PT(DaeMaterials::DaeBlendSettings) DaeMaterials::
convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparent, double transparency) {
// Create the DaeBlendSettings and fill it with some defaults.
PT(DaeBlendSettings) blend = new DaeBlendSettings();
blend->_enabled = true;
blend->_color = LColor::zero();
blend->_operand_a = EggGroup::BO_unspecified;
blend->_operand_b = EggGroup::BO_unspecified;
// First fill in the color value.
if (mode == FCDEffectStandard::A_ONE) {// || mode == FCDEffectStandard::A_ZERO) {
double value = transparent[3] * transparency;
blend->_color = LColor(value, value, value, value);
} else if (mode == FCDEffectStandard::RGB_ZERO) {//|| mode == FCDEffectStandard::RGB_ONE) {
blend->_color = transparent * transparency;
blend->_color[3] = luminance(blend->_color);
} else {
daeegg_cat.error() << "Unknown opaque type found!" << endl;
blend->_enabled = false;
return blend;
}
// Now figure out the operands.
if (mode == FCDEffectStandard::RGB_ZERO) {// || mode == FCDEffectStandard::A_ZERO) {
blend->_operand_a = EggGroup::BO_one_minus_constant_color;
blend->_operand_b = EggGroup::BO_constant_color;
} else if (mode == FCDEffectStandard::A_ONE) {// || mode == FCDEffectStandard::RGB_ONE) {
blend->_operand_a = EggGroup::BO_constant_color;
blend->_operand_b = EggGroup::BO_one_minus_constant_color;
} else {
daeegg_cat.error() << "Unknown opaque type found!" << endl;
blend->_enabled = false;
return blend;
}
// See if we can optimize out the color.
if (blend->_operand_a == EggGroup::BO_constant_color) {
if (blend->_color == LColor::zero()) {
blend->_operand_a = EggGroup::BO_zero;
} else if (blend->_color == LColor(1, 1, 1, 1)) {
blend->_operand_a = EggGroup::BO_one;
}
}
if (blend->_operand_b == EggGroup::BO_constant_color) {
if (blend->_color == LColor::zero()) {
blend->_operand_b = EggGroup::BO_zero;
} else if (blend->_color == LColor(1, 1, 1, 1)) {
blend->_operand_b = EggGroup::BO_one;
}
}
if (blend->_operand_a == EggGroup::BO_one_minus_constant_color) {
if (blend->_color == LColor::zero()) {
blend->_operand_a = EggGroup::BO_one;
} else if (blend->_color == LColor(1, 1, 1, 1)) {
blend->_operand_a = EggGroup::BO_zero;
}
}
if (blend->_operand_b == EggGroup::BO_one_minus_constant_color) {
if (blend->_color == LColor::zero()) {
blend->_operand_b = EggGroup::BO_one;
} else if (blend->_color == LColor(1, 1, 1, 1)) {
blend->_operand_b = EggGroup::BO_zero;
}
}
// See if we can entirely disable the blend.
if (blend->_operand_a == EggGroup::BO_one && blend->_operand_b == EggGroup::BO_zero) {
blend->_enabled = false;
}
return blend;
}

View File

@ -0,0 +1,101 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file daeMaterials.h
* @author rdb
* @date 2008-10-03
*/
#ifndef DAEMATERIALS_H
#define DAEMATERIALS_H
#include "pandatoolbase.h"
#include "eggMaterial.h"
#include "eggTexture.h"
#include "eggPrimitive.h"
#include "eggGroup.h"
#include "pointerTo.h"
#include "pt_EggTexture.h"
#include "pt_EggMaterial.h"
#include "pre_fcollada_include.h"
#include <FCollada.h>
#include <FCDocument/FCDGeometryInstance.h>
#include <FCDocument/FCDMaterialInstance.h>
#include <FCDocument/FCDEffectStandard.h>
#include <FCDocument/FCDEffectParameterSampler.h>
#include <FCDocument/FCDExtra.h>
/**
* This class is seperated from the converter file because otherwise it would
* get too big and needlessly complicated.
*/
class DaeMaterials : public TypedReferenceCount {
public:
DaeMaterials(const FCDGeometryInstance* geometry_instance);
virtual ~DaeMaterials() {};
void add_material_instance(const FCDMaterialInstance* instance);
void apply_to_primitive(const std::string semantic, const PT(EggPrimitive) to);
void apply_to_group(const std::string semantic, const PT(EggGroup) to, bool invert_transparency=false);
const std::string get_uvset_name(const std::string semantic, FUDaeGeometryInput::Semantic input_semantic, int32 input_set);
static EggTexture::TextureType convert_texture_type(const FCDEffectParameterSampler::SamplerType orig_type);
static EggTexture::WrapMode convert_wrap_mode(const FUDaeTextureWrapMode::WrapMode orig_mode);
static EggTexture::FilterType convert_filter_type(const FUDaeTextureFilterFunction::FilterFunction orig_type);
private:
// Holds stuff for color blend attribs.
struct DaeBlendSettings : public ReferenceCount {
bool _enabled;
LColor _color;
EggGroup::BlendOperand _operand_a;
EggGroup::BlendOperand _operand_b;
};
// Holds information to bind texcoord inputs to textures.
struct DaeVertexInputBinding : public ReferenceCount {
int32 _input_set;
FUDaeGeometryInput::Semantic _input_semantic;
std::string _semantic;
};
// Holds stuff for an individual material.
struct DaeMaterial : public ReferenceCount {
pvector<PT_EggTexture> _egg_textures;
PT_EggMaterial _egg_material;
bool _double_sided;
pvector<PT(DaeVertexInputBinding)> _uvsets;
PT(DaeBlendSettings) _blend;
};
void process_texture_bucket(const std::string semantic, const FCDEffectStandard* effect_common, FUDaeTextureChannel::Channel bucket, EggTexture::EnvType envtype = EggTexture::ET_unspecified, EggTexture::Format format = EggTexture::F_unspecified);
void process_extra(const std::string semantic, const FCDExtra* extra);
static PT(DaeBlendSettings) convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparent, double transparency);
pmap<const std::string, PT(DaeMaterial)> _materials;
public:
virtual TypeHandle get_type() const {
return get_class_type();
}
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
static TypeHandle get_class_type() {
return _type_handle;
}
static void init_type() {
TypedReferenceCount::init_type();
register_type(_type_handle, "DaeMaterials",
TypedReferenceCount::get_class_type());
}
private:
static TypeHandle _type_handle;
};
#endif

View File

@ -0,0 +1,851 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file daeToEggConverter.cxx
* @author rdb
* @date 2008-05-08
*/
#include "daeToEggConverter.h"
#include "fcollada_utils.h"
#include "config_daeegg.h"
#include "daeCharacter.h"
#include "dcast.h"
#include "string_utils.h"
#include "eggData.h"
#include "eggPrimitive.h"
#include "eggLine.h"
#include "eggPolygon.h"
#include "eggTriangleFan.h"
#include "eggTriangleStrip.h"
#include "eggPoint.h"
#include "eggXfmSAnim.h"
#include "eggSAnimData.h"
#include "pt_EggVertex.h"
#include <FCDocument/FCDAsset.h>
#include <FCDocument/FCDocumentTools.h>
#include <FCDocument/FCDSceneNode.h>
#include <FCDocument/FCDSceneNodeTools.h>
#include <FCDocument/FCDGeometry.h>
#include <FCDocument/FCDGeometryInstance.h>
#include <FCDocument/FCDGeometryPolygons.h>
#include <FCDocument/FCDGeometrySource.h>
#include <FCDocument/FCDSkinController.h>
#include <FCDocument/FCDController.h>
#include <FCDocument/FCDControllerInstance.h>
#include <FCDocument/FCDMorphController.h>
#include <FCDocument/FCDMaterialInstance.h>
#include <FCDocument/FCDExtra.h>
#include <FCDocument/FCDEffect.h>
#include <FCDocument/FCDEffectStandard.h>
#if FCOLLADA_VERSION >= 0x00030005
#include <FCDocument/FCDGeometryPolygonsInput.h>
#endif
using std::endl;
using std::string;
/**
*
*/
DAEToEggConverter::
DAEToEggConverter() {
_unit_name = "meter";
_unit_meters = 1.0;
_document = nullptr;
_table = nullptr;
_error_handler = nullptr;
_invert_transparency = false;
}
/**
*
*/
DAEToEggConverter::
DAEToEggConverter(const DAEToEggConverter &copy) :
SomethingToEggConverter(copy)
{
}
/**
*
*/
DAEToEggConverter::
~DAEToEggConverter() {
delete _error_handler;
}
/**
* Allocates and returns a new copy of the converter.
*/
SomethingToEggConverter *DAEToEggConverter::
make_copy() {
return new DAEToEggConverter(*this);
}
/**
* Returns the English name of the file type this converter supports.
*/
string DAEToEggConverter::
get_name() const {
return "COLLADA";
}
/**
* Returns the common extension of the file type this converter supports.
*/
string DAEToEggConverter::
get_extension() const {
return "dae";
}
/**
* Handles the reading of the input file and converting it to egg. Returns
* true if successful, false otherwise.
*/
bool DAEToEggConverter::
convert_file(const Filename &filename) {
// Reset stuff
clear_error();
_joints.clear();
if (_error_handler == nullptr) {
_error_handler = new FUErrorSimpleHandler;
}
// The default coordinate system is Y-up
if (_egg_data->get_coordinate_system() == CS_default) {
_egg_data->set_coordinate_system(CS_yup_right);
}
// Read the file
FCollada::Initialize();
_document = FCollada::LoadDocument(filename.to_os_specific().c_str());
if (_document == nullptr) {
daeegg_cat.error() << "Failed to load document: " << _error_handler->GetErrorString() << endl;
FCollada::Release();
return false;
}
// Make sure the file uses consistent coordinate system and length
if (_document->GetAsset() != nullptr) {
FCDocumentTools::StandardizeUpAxisAndLength(_document);
}
// Process the scene
process_asset();
PT(EggGroup) scene_group;
string model_name = _character_name;
FCDSceneNode* visual_scene = _document->GetVisualSceneInstance();
if (visual_scene != nullptr) {
if (model_name.empty()) {
// By lack of anything better...
model_name = FROM_FSTRING(visual_scene->GetName());
}
scene_group = new EggGroup(model_name);
_egg_data->add_child(scene_group);
for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
process_node(scene_group, visual_scene->GetChild(ch));
}
} else {
daeegg_cat.warning()
<< "No visual scene instance found in COLLADA document.\n";
}
// Now process the characters. This depends on information from collected
// joints, which is why it's done in a second step.
if (get_animation_convert() != AC_none) {
Characters::iterator it;
DaeCharacter *character;
for (it = _characters.begin(); it != _characters.end(); ++it) {
character = *it;
if (get_animation_convert() != AC_chan) {
character->bind_joints(_joints);
const FCDGeometryMesh *mesh = character->_skin_mesh;
if (mesh != nullptr) {
PT(DaeMaterials) materials = new DaeMaterials(character->_instance);
if (daeegg_cat.is_spam()) {
daeegg_cat.spam() << "Processing mesh for controller\n";
}
process_mesh(character->_node_group, mesh, materials, character);
}
}
}
// Put the joints in bind pose.
for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
character->adjust_joints(visual_scene->GetChild(ch), _joints, LMatrix4d::ident_mat());
}
if (scene_group != nullptr) {
// Mark the scene as character.
if (get_animation_convert() == AC_chan) {
_egg_data->remove_child(scene_group);
} else {
scene_group->set_dart_type(EggGroup::DT_default);
}
}
if (get_animation_convert() != AC_model) {
_table = new EggTable();
_table->set_table_type(EggTable::TT_table);
_egg_data->add_child(_table);
PT(EggTable) bundle = new EggTable(model_name);
bundle->set_table_type(EggTable::TT_bundle);
_table->add_child(bundle);
PT(EggTable) skeleton = new EggTable("<skeleton>");
skeleton->set_table_type(EggTable::TT_table);
bundle->add_child(skeleton);
pset<float> keys;
Characters::iterator it;
DaeCharacter *character;
for (it = _characters.begin(); it != _characters.end(); ++it) {
character = *it;
// Collect key frame timings.
if (get_animation_convert() == AC_both ||
get_animation_convert() == AC_chan) {
character->collect_keys(keys);
}
}
if (_frame_inc != 0.0) {
// A frame increment was given, this means that we have to sample the
// animation.
float start, end;
if (_end_frame != _start_frame) {
start = _start_frame;
end = _end_frame;
} else {
// No range was given. Infer the frame range from the keys.
start = *keys.begin();
end = *keys.rbegin();
}
keys.clear();
for (float t = start; t <= end; t += _frame_inc) {
keys.insert(t);
}
} else {
// No sampling parameters given; not necessarily a failure, since the
// animation may already be sampled. We use the key frames as
// animation frames.
if (_end_frame != 0.0) {
// An end frame was given, chop off all keys after that.
float end = _end_frame;
pset<float>::iterator ki;
for (ki = keys.begin(); ki != keys.end(); ++ki) {
if (*ki > end && !IS_THRESHOLD_EQUAL(*ki, end, 0.001)) {
keys.erase(ki, keys.end());
break;
}
}
}
if (_start_frame != 0.0) {
// A start frame was given, chop off all keys before that.
float start = _start_frame;
pset<float>::iterator ki;
for (ki = keys.begin(); ki != keys.end(); ++ki) {
if (*ki > start && !IS_THRESHOLD_EQUAL(*ki, start, 0.001)) {
keys.erase(keys.begin(), ki);
break;
}
}
}
// Check that this does indeed look like a sampled animation; if not,
// issue an appropriate warning.
pset<float>::const_iterator ki = keys.begin();
if (ki != keys.end()) {
float last = *ki;
float diff = 0;
for (++ki; ki != keys.end(); ++ki) {
if (diff != 0 && !IS_THRESHOLD_EQUAL((*ki - last), diff, 0.001)) {
daeegg_cat.error()
<< "This does not appear to be a sampled animation.\n"
<< "Specify the -sf, -ef and -if options to indicate how the "
<< "animations should be sampled.\n";
break;
}
diff = (*ki - last);
last = *ki;
}
}
}
// It doesn't really matter which character we grab for this as it'll
// iterate over the whole graph right now anyway.
for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
character->build_table(skeleton, visual_scene->GetChild(ch), keys);
}
}
}
// Clean up and return
SAFE_DELETE(visual_scene);
SAFE_DELETE(_document);
FCollada::Release();
return true;
}
/**
* This may be called after convert_file() has been called and returned true,
* indicating a successful conversion. It will return the distance units
* represented by the converted egg file, if known, or DU_invalid if not
* known.
*/
DistanceUnit DAEToEggConverter::
get_input_units() {
if (IS_NEARLY_EQUAL(_unit_meters, 0.001)) {
return DU_millimeters;
}
if (IS_NEARLY_EQUAL(_unit_meters, 0.01)) {
return DU_centimeters;
}
if (IS_NEARLY_EQUAL(_unit_meters, 1.0)) {
return DU_meters;
}
if (IS_NEARLY_EQUAL(_unit_meters, 1000.0)) {
return DU_kilometers;
}
if (IS_NEARLY_EQUAL(_unit_meters, 3.0 * 12.0 * 0.0254)) {
return DU_yards;
}
if (IS_NEARLY_EQUAL(_unit_meters, 12.0 * 0.0254)) {
return DU_feet;
}
if (IS_NEARLY_EQUAL(_unit_meters, 0.0254)) {
return DU_inches;
}
if (IS_NEARLY_EQUAL(_unit_meters, 1852.0)) {
return DU_nautical_miles;
}
if (IS_NEARLY_EQUAL(_unit_meters, 5280.0 * 12.0 * 0.0254)) {
return DU_statute_miles;
}
// Whatever.
return DU_invalid;
}
void DAEToEggConverter::
process_asset() {
const FCDAsset *asset = _document->GetAsset();
if (_document->GetAsset() == nullptr) {
return;
}
_unit_name = FROM_FSTRING(asset->GetUnitName());
_unit_meters = asset->GetUnitConversionFactor();
// Read out the coordinate system
FMVector3 up_axis = asset->GetUpAxis();
if (up_axis == FMVector3(0, 1, 0)) {
_egg_data->set_coordinate_system(CS_yup_right);
} else if (up_axis == FMVector3(0, 0, 1)) {
_egg_data->set_coordinate_system(CS_zup_right);
} else {
_egg_data->set_coordinate_system(CS_invalid);
daeegg_cat.warning() << "Unrecognized coordinate system!\n";
}
}
// Process the node. If forced is true, it will even process it if its known
// to be a skeleton root.
void DAEToEggConverter::
process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced) {
nassertv(node != nullptr);
string node_id = FROM_FSTRING(node->GetDaeId());
if (daeegg_cat.is_spam()) {
daeegg_cat.spam() << "Processing node with ID '" << node_id << "'" << endl;
}
// Create an egg group for this node
PT(EggGroup) node_group = new EggGroup(FROM_FSTRING(node->GetDaeId()));
process_extra(node_group, node->GetExtra());
parent->add_child(node_group);
// Check if its a joint
if (node->IsJoint()) {
string sid = FROM_FSTRING(node->GetSubId());
node_group->set_group_type(EggGroup::GT_joint);
if (!_joints.insert(DaeCharacter::JointMap::value_type(sid,
DaeCharacter::Joint(node_group, node))).second) {
daeegg_cat.error()
<< "Joint with sid " << sid << " occurs more than once!\n";
}
}
// Loop through the transforms and apply them (in reverse order)
for (size_t tr = node->GetTransformCount(); tr > 0; --tr) {
apply_transform(node_group, node->GetTransform(tr - 1));
}
// node_group->set_transform3d(convert_matrix(node->ToMatrix()));
// Loop through the instances and process them
for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
process_instance(node_group, node->GetInstance(in));
}
// Loop through the children and recursively process them
for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
process_node(DCAST(EggGroupNode, node_group), node->GetChild(ch));
}
// Loop through any possible scene node instances and process those, too.
for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
const FCDEntity *entity = node->GetInstance(in)->GetEntity();
if (entity && entity->GetType() == FCDEntity::SCENE_NODE) {
process_node(node_group, (const FCDSceneNode*) entity);
}
}
}
void DAEToEggConverter::
process_instance(EggGroup *parent, const FCDEntityInstance* instance) {
nassertv(instance != nullptr);
nassertv(instance->GetEntity() != nullptr);
// Check what kind of instance this is
switch (instance->GetType()) {
case FCDEntityInstance::GEOMETRY:
{
if (get_animation_convert() != AC_chan) {
const FCDGeometry* geometry = (const FCDGeometry*) instance->GetEntity();
assert(geometry != nullptr);
if (geometry->IsMesh()) {
// Now, handle the mesh.
process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
}
if (geometry->IsSpline()) {
process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
}
}
}
break;
case FCDEntityInstance::CONTROLLER:
// Add the dart tag and process the controller instance
// parent->set_dart_type(EggGroup::DT_default);
process_controller(parent, (const FCDControllerInstance*) instance);
break;
case FCDEntityInstance::MATERIAL:
// We don't process this directly, handled per-geometry instead.
break;
case FCDEntityInstance::SIMPLE:
{
// Grab the entity and check its type.
const FCDEntity* entity = instance->GetEntity();
if (entity->GetType() != FCDEntity::SCENE_NODE) {
daeegg_cat.warning() << "Unsupported entity type found" << endl;
}
}
break;
default:
daeegg_cat.warning() << "Unsupported instance type found" << endl;
}
}
// Processes the given mesh.
void DAEToEggConverter::
process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh,
DaeMaterials *materials, DaeCharacter *character) {
nassertv(mesh != nullptr);
if (daeegg_cat.is_debug()) {
daeegg_cat.debug() << "Processing mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << endl;
}
// Create the egg stuff to hold this mesh
PT(EggGroup) mesh_group = new EggGroup(FROM_FSTRING(mesh->GetDaeId()));
parent->add_child(mesh_group);
PT(EggVertexPool) mesh_pool = new EggVertexPool(FROM_FSTRING(mesh->GetDaeId()));
mesh_group->add_child(mesh_pool);
// First retrieve the vertex source
if (mesh->GetSourceCount() == 0) {
if (daeegg_cat.is_debug()) {
daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no sources" << endl;
}
return;
}
const FCDGeometrySource* vsource = mesh->FindSourceByType(FUDaeGeometryInput::POSITION);
if (vsource == nullptr) {
if (daeegg_cat.is_debug()) {
daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no source for POSITION data" << endl;
}
return;
}
// Loop through the polygon groups and add them
if (daeegg_cat.is_spam()) {
daeegg_cat.spam() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has " << mesh->GetPolygonsCount() << " polygon groups" << endl;
}
if (mesh->GetPolygonsCount() == 0) return;
// This is an array of pointers, I know. But since they are refcounted, I
// don't have a better idea.
PT(EggGroup) *primitive_holders = new PT(EggGroup) [mesh->GetPolygonsCount()];
for (size_t gr = 0; gr < mesh->GetPolygonsCount(); ++gr) {
const FCDGeometryPolygons* polygons = mesh->GetPolygons(gr);
string material_semantic = FROM_FSTRING(polygons->GetMaterialSemantic());
// Stores which group holds the primitives.
PT(EggGroup) primitiveholder;
// If we have materials, make a group for each material. Then, apply the
// material's per-group stuff.
if (materials != nullptr && (!polygons->GetMaterialSemantic().empty()) && mesh->GetPolygonsCount() > 1) {
// primitiveholder = new EggGroup(FROM_FSTRING(mesh->GetDaeId()) + "." +
// material_semantic);
primitiveholder = new EggGroup;
mesh_group->add_child(primitiveholder);
} else {
primitiveholder = mesh_group;
}
primitive_holders[gr] = primitiveholder;
// Apply the per-group data of the materials, if we have it.
if (materials != nullptr) {
materials->apply_to_group(material_semantic, primitiveholder, _invert_transparency);
}
// Find the position sources
const FCDGeometryPolygonsInput* pinput = polygons->FindInput(FUDaeGeometryInput::POSITION);
assert(pinput != nullptr);
const uint32* indices = pinput->GetIndices();
// Find the normal sources
const FCDGeometrySource* nsource = mesh->FindSourceByType(FUDaeGeometryInput::NORMAL);
const FCDGeometryPolygonsInput* ninput = polygons->FindInput(FUDaeGeometryInput::NORMAL);
const uint32* nindices;
if (ninput != nullptr) nindices = ninput->GetIndices();
// Find texcoord sources
const FCDGeometrySource* tcsource = mesh->FindSourceByType(FUDaeGeometryInput::TEXCOORD);
const FCDGeometryPolygonsInput* tcinput = polygons->FindInput(FUDaeGeometryInput::TEXCOORD);
const uint32* tcindices;
if (tcinput != nullptr) tcindices = tcinput->GetIndices();
// Find vcolor sources
const FCDGeometrySource* csource = mesh->FindSourceByType(FUDaeGeometryInput::COLOR);
const FCDGeometryPolygonsInput* cinput = polygons->FindInput(FUDaeGeometryInput::COLOR);
const uint32* cindices;
if (cinput != nullptr) cindices = cinput->GetIndices();
// Find binormal sources
const FCDGeometrySource* bsource = mesh->FindSourceByType(FUDaeGeometryInput::TEXBINORMAL);
const FCDGeometryPolygonsInput* binput = polygons->FindInput(FUDaeGeometryInput::TEXBINORMAL);
const uint32* bindices;
if (binput != nullptr) bindices = binput->GetIndices();
// Find tangent sources
const FCDGeometrySource* tsource = mesh->FindSourceByType(FUDaeGeometryInput::TEXTANGENT);
const FCDGeometryPolygonsInput* tinput = polygons->FindInput(FUDaeGeometryInput::TEXTANGENT);
const uint32* tindices;
if (tinput != nullptr) tindices = tinput->GetIndices();
// Get a name for potential coordinate sets
string tcsetname;
if (materials != nullptr && tcinput != nullptr) {
if (daeegg_cat.is_debug()) {
daeegg_cat.debug()
<< "Assigning texcoord set " << tcinput->GetSet()
<< " to semantic '" << material_semantic << "'\n";
}
tcsetname = materials->get_uvset_name(material_semantic,
FUDaeGeometryInput::TEXCOORD, tcinput->GetSet());
}
string tbsetname;
if (materials != nullptr && binput != nullptr) {
if (daeegg_cat.is_debug()) {
daeegg_cat.debug()
<< "Assigning texbinormal set " << binput->GetSet()
<< " to semantic '" << material_semantic << "'\n";
}
tbsetname = materials->get_uvset_name(material_semantic,
FUDaeGeometryInput::TEXBINORMAL, binput->GetSet());
}
string ttsetname;
if (materials != nullptr && tinput != nullptr) {
if (daeegg_cat.is_debug()) {
daeegg_cat.debug()
<< "Assigning textangent set " << tinput->GetSet()
<< " to semantic '" << material_semantic << "'\n";
}
ttsetname = materials->get_uvset_name(material_semantic,
FUDaeGeometryInput::TEXTANGENT, tinput->GetSet());
}
// Loop through the indices and add the vertices.
for (size_t ix = 0; ix < pinput->GetIndexCount(); ++ix) {
PT_EggVertex vertex = mesh_pool->make_new_vertex();
const float* data = &vsource->GetData()[indices[ix]*3];
vertex->set_pos(LPoint3d(data[0], data[1], data[2]));
if (character != nullptr) {
// If this is skinned geometry, add the vertex influences.
character->influence_vertex(indices[ix], vertex);
}
// Process the normal
if (nsource != nullptr && ninput != nullptr) {
assert(nsource->GetStride() == 3);
data = &nsource->GetData()[nindices[ix]*3];
vertex->set_normal(LVecBase3d(data[0], data[1], data[2]));
}
// Process the texcoords
if (tcsource != nullptr && tcinput != nullptr) {
assert(tcsource->GetStride() == 2 || tcsource->GetStride() == 3);
data = &tcsource->GetData()[tcindices[ix]*tcsource->GetStride()];
if (tcsource->GetStride() == 2) {
vertex->set_uv(tcsetname, LPoint2d(data[0], data[1]));
} else {
vertex->set_uvw(tcsetname, LPoint3d(data[0], data[1], data[2]));
}
}
// Process the color
if (csource != nullptr && cinput != nullptr) {
assert(csource->GetStride() == 3 || csource->GetStride() == 4);
if (csource->GetStride() == 3) {
data = &csource->GetData()[cindices[ix]*3];
vertex->set_color(LColor(data[0], data[1], data[2], 1.0f));
} else {
data = &csource->GetData()[cindices[ix]*4];
vertex->set_color(LColor(data[0], data[1], data[2], data[3]));
}
}
// Possibly add a UV object
if ((bsource != nullptr && binput != nullptr) || (tsource != nullptr && tinput != nullptr)) {
if (bsource != nullptr && binput != nullptr) {
assert(bsource->GetStride() == 3);
data = &bsource->GetData()[bindices[ix]*3];
PT(EggVertexUV) uv_obj = vertex->modify_uv_obj(tbsetname);
if (uv_obj == nullptr) {
uv_obj = new EggVertexUV(tbsetname, LTexCoordd());
}
uv_obj->set_binormal(LVecBase3d(data[0], data[1], data[2]));
}
if (tsource != nullptr && tinput != nullptr) {
assert(tsource->GetStride() == 3);
data = &tsource->GetData()[tindices[ix]*3];
PT(EggVertexUV) uv_obj = vertex->modify_uv_obj(ttsetname);
if (uv_obj == nullptr) {
uv_obj = new EggVertexUV(ttsetname, LTexCoordd());
}
uv_obj->set_tangent(LVecBase3d(data[0], data[1], data[2]));
}
}
vertex->transform(parent->get_node_to_vertex());
}
}
// Loop again for the polygons
for (size_t gr = 0; gr < mesh->GetPolygonsCount(); ++gr) {
const FCDGeometryPolygons* polygons = mesh->GetPolygons(gr);
// Now loop through the faces
uint32 offset = 0;
for (size_t fa = 0; fa < polygons->GetFaceVertexCountCount(); ++fa) {
PT(EggPrimitive) primitive = nullptr;
// Create a primitive that matches the fcollada type
switch (polygons->GetPrimitiveType()) {
case FCDGeometryPolygons::LINES:
primitive = new EggLine();
break;
case FCDGeometryPolygons::POLYGONS:
primitive = new EggPolygon();
break;
case FCDGeometryPolygons::TRIANGLE_FANS:
primitive = new EggTriangleFan();
break;
case FCDGeometryPolygons::TRIANGLE_STRIPS:
primitive = new EggTriangleStrip();
break;
case FCDGeometryPolygons::POINTS:
primitive = new EggPoint();
break;
case FCDGeometryPolygons::LINE_STRIPS:
daeegg_cat.warning() << "Linestrips not yet supported!" << endl;
break;
default:
daeegg_cat.warning() << "Unsupported primitive type found!" << endl;
}
if (primitive != nullptr) {
primitive_holders[gr]->add_child(primitive);
if (materials != nullptr) {
materials->apply_to_primitive(FROM_FSTRING(polygons->GetMaterialSemantic()), primitive);
}
for (size_t ve = 0; ve < polygons->GetFaceVertexCount(fa); ++ve) {
assert(mesh_pool->has_vertex(ve + polygons->GetFaceVertexOffset() + offset));
primitive->add_vertex(mesh_pool->get_vertex(ve + polygons->GetFaceVertexOffset() + offset));
}
}
offset += polygons->GetFaceVertexCount(fa);
}
}
delete[] primitive_holders;
}
void DAEToEggConverter::
process_spline(EggGroup *parent, const string group_name, FCDGeometrySpline* geometry_spline) {
assert(geometry_spline != nullptr);
PT(EggGroup) result = new EggGroup(group_name);
parent->add_child(result);
// TODO: if its not a nurbs, make it convert between the types
if (geometry_spline->GetType() != FUDaeSplineType::NURBS) {
daeegg_cat.warning() << "Only NURBS curves are supported (yet)!" << endl;
} else {
// Loop through the splines
for (size_t sp = 0; sp < geometry_spline->GetSplineCount(); ++sp) {
process_spline(result, geometry_spline->GetSpline(sp));
}
}
}
void DAEToEggConverter::
process_spline(EggGroup *parent, const FCDSpline* spline) {
assert(spline != nullptr);
nassertv(spline->GetSplineType() == FUDaeSplineType::NURBS);
// Now load in the nurbs curve to the egg library
PT(EggNurbsCurve) nurbs_curve = new EggNurbsCurve(FROM_FSTRING(spline->GetName()));
parent->add_child(nurbs_curve);
// TODO: what value is this?
nurbs_curve->setup(0, ((const FCDNURBSSpline*) spline)->GetKnotCount());
for (size_t kn = 0; kn < ((const FCDNURBSSpline*) spline)->GetKnotCount(); ++kn) {
const float* knot = ((const FCDNURBSSpline*) spline)->GetKnot(kn);
assert(knot != nullptr);
nurbs_curve->set_knot(kn, *knot);
}
for (size_t cv = 0; cv < spline->GetCVCount(); ++cv) {
PT_EggVertex c_vtx = new EggVertex();
c_vtx->set_pos(TO_VEC3(*spline->GetCV(cv)));
c_vtx->transform(parent->get_node_to_vertex());
nurbs_curve->add_vertex(c_vtx);
}
}
void DAEToEggConverter::
process_controller(EggGroup *parent, const FCDControllerInstance *instance) {
assert(instance != nullptr);
const FCDController* controller = (const FCDController *)instance->GetEntity();
assert(controller != nullptr);
if (get_animation_convert() == AC_none) {
// If we're exporting a static mesh, export the base geometry as-is.
const FCDGeometryMesh *mesh = controller->GetBaseGeometry()->GetMesh();
if (mesh != nullptr) {
PT(DaeMaterials) materials = new DaeMaterials(instance);
if (daeegg_cat.is_spam()) {
daeegg_cat.spam() << "Processing mesh for controller\n";
}
process_mesh(parent, mesh, materials);
}
} else {
// Add a character for this to the table, the mesh is processed later
PT(DaeCharacter) character = new DaeCharacter(parent, instance);
_characters.push_back(character);
}
if (controller->IsMorph()) {
assert(controller != nullptr);
const FCDMorphController* morph_controller = controller->GetMorphController();
assert(morph_controller != nullptr);
PT(EggTable) bundle = new EggTable(parent->get_name());
bundle->set_table_type(EggTable::TT_bundle);
PT(EggTable) morph = new EggTable("morph");
morph->set_table_type(EggTable::TT_table);
bundle->add_child(morph);
// Loop through the morph targets.
for (size_t mt = 0; mt < morph_controller->GetTargetCount(); ++mt) {
const FCDMorphTarget* morph_target = morph_controller->GetTarget(mt);
assert(morph_target != nullptr);
PT(EggSAnimData) target = new EggSAnimData(FROM_FSTRING(morph_target->GetGeometry()->GetName()));
if (morph_target->IsAnimated()) {
// TODO
} else {
target->add_data(morph_target->GetWeight());
}
morph->add_child(target);
}
}
}
void DAEToEggConverter::
process_extra(EggGroup *group, const FCDExtra* extra) {
if (extra == nullptr) {
return;
}
nassertv(group != nullptr);
const FCDEType* etype = extra->GetDefaultType();
if (etype == nullptr) {
return;
}
const FCDENode* enode = (const FCDENode*) etype->FindTechnique("PANDA3D");
if (enode == nullptr) {
return;
}
FCDENodeList tags;
enode->FindChildrenNodes("param", tags);
for (FCDENodeList::iterator it = tags.begin(); it != tags.end(); ++it) {
const FCDEAttribute* attr = (*it)->FindAttribute("sid");
if (attr) {
group->set_tag(FROM_FSTRING(attr->GetValue()), (*it)->GetContent());
}
}
}
LMatrix4d DAEToEggConverter::
convert_matrix(const FMMatrix44 &matrix) {
return LMatrix4d(
matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3],
matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3]);
}
void DAEToEggConverter::
apply_transform(EggGroup *to, const FCDTransform* from) {
assert(from != nullptr);
assert(to != nullptr);
// to->set_transform3d(convert_matrix(from->ToMatrix()) *
// to->get_transform3d());
switch (from->GetType()) {
case FCDTransform::TRANSLATION:
{
const FCDTTranslation *trans = (const FCDTTranslation *)from;
to->add_translate3d(TO_VEC3(trans->GetTranslation()));
}
break;
case FCDTransform::ROTATION:
{
const FCDTRotation *rot = (const FCDTRotation *)from;
to->add_rotate3d(rot->GetAngle(), TO_VEC3(rot->GetAxis()));
}
break;
case FCDTransform::SCALE:
{
const FCDTScale *scale = (const FCDTScale *)from;
to->add_scale3d(TO_VEC3(scale->GetScale()));
}
break;
default:
// Either a matrix, or something we can't handle.
to->add_matrix4(convert_matrix(from->ToMatrix()));
break;
}
}

View File

@ -0,0 +1,87 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file daeToEggConverter.h
* @author rdb
* @date 2008-05-08
*/
#ifndef DAETOEGGCONVERTER_H
#define DAETOEGGCONVERTER_H
#include "pandatoolbase.h"
#include "somethingToEggConverter.h"
#include "eggGroup.h"
#include "eggMaterial.h"
#include "eggTexture.h"
#include "eggTable.h"
#include "eggNurbsCurve.h"
#include "pre_fcollada_include.h"
#include <FCollada.h>
#include <FCDocument/FCDocument.h>
#include <FCDocument/FCDTransform.h>
#include <FCDocument/FCDEntityInstance.h>
#include <FCDocument/FCDControllerInstance.h>
#include <FCDocument/FCDGeometryMesh.h>
#include <FCDocument/FCDGeometrySpline.h>
#include <FCDocument/FCDMaterial.h>
#include <FMath/FMMatrix44.h>
#include "daeMaterials.h"
#include "daeCharacter.h"
#include "pvector.h" // Include last
/**
* This class supervises the construction of an EggData structure from a DAE
* file.
*/
class DAEToEggConverter : public SomethingToEggConverter {
public:
DAEToEggConverter();
DAEToEggConverter(const DAEToEggConverter &copy);
~DAEToEggConverter();
virtual SomethingToEggConverter *make_copy();
virtual std::string get_name() const;
virtual std::string get_extension() const;
virtual bool convert_file(const Filename &filename);
virtual DistanceUnit get_input_units();
bool _invert_transparency;
private:
std::string _unit_name;
double _unit_meters;
PT(EggTable) _table;
FCDocument* _document;
FUErrorSimpleHandler* _error_handler;
DaeCharacter::JointMap _joints;
typedef pvector<PT(DaeCharacter)> Characters;
Characters _characters;
void process_asset();
void process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced = false);
void process_instance(EggGroup *parent, const FCDEntityInstance* instance);
void process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh,
DaeMaterials *materials, DaeCharacter *character = nullptr);
void process_spline(EggGroup *parent, const std::string group_name, FCDGeometrySpline* geometry_spline);
void process_spline(EggGroup *parent, const FCDSpline* spline);
void process_controller(EggGroup *parent, const FCDControllerInstance* instance);
void process_extra(EggGroup *group, const FCDExtra* extra);
static LMatrix4d convert_matrix(const FMMatrix44& matrix);
void apply_transform(EggGroup *to, const FCDTransform* from);
friend class DaeCharacter;
};
#endif

View File

@ -0,0 +1,38 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file fcollada_utils.h
* @author rdb
* @date 2008-12-22
*/
// This file defines some conversion tools for conversion between FCollada and
// Panda3D
#ifndef FCOLLADA_UTILS_H
#define FCOLLADA_UTILS_H
#include "pre_fcollada_include.h"
#include <FCollada.h>
// Useful conversion stuff
inline LVecBase3d TO_VEC3(FMVector3 v) {
return LVecBase3d(v.x, v.y, v.z);
}
inline LVecBase4d TO_VEC4(FMVector4 v) {
return LVecBase4d(v.x, v.y, v.z, v.w);
}
inline LColor TO_COLOR(FMVector4 v) {
return LColor(v.x, v.y, v.z, v.w);
}
#define FROM_VEC3(v) (FMVector3(v[0], v[1], v[2]))
#define FROM_VEC4(v) (FMVector4(v[0], v[1], v[2], v[3]))
#define FROM_MAT4(v) (FMMatrix44(v.getData()))
#define FROM_FSTRING(fs) (std::string(fs.c_str()))
#endif

View File

@ -0,0 +1,5 @@
#include "config_daeegg.cxx"
#include "daeCharacter.cxx"
#include "daeMaterials.cxx"
#include "daeToEggConverter.cxx"

View File

@ -0,0 +1,45 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pre_fcollada_include.h
* @author rdb
* @date 2008-10-04
*/
// This file defines some stuff that need to be defined before one includes
// FCollada.h
#ifndef PRE_FCOLLADA_INCLUDE_H
#define PRE_FCOLLADA_INCLUDE_H
#ifdef FCOLLADA_VERSION
#error You must include pre_fcollada_include.h before including FCollada.h!
#endif
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#include <winsock2.h>
#endif
// FCollada expects LINUX to be defined on linux
#ifdef IS_LINUX
#ifndef LINUX
#define LINUX
#endif
#endif
#define NO_LIBXML
#define FCOLLADA_NOMINMAX
// FCollada does use global min/max.
using std::min;
using std::max;
#endif

View File

@ -0,0 +1,11 @@
if(NOT BUILD_TOOLS)
return()
endif()
if(HAVE_EGG AND HAVE_FCOLLADA)
add_executable(dae2egg daeToEgg.cxx daeToEgg.h)
target_link_libraries(dae2egg p3daeegg p3eggbase p3progbase)
install(TARGETS dae2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()

View File

@ -0,0 +1,82 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file daeToEgg.cxx
* @author rdb
* @date 2008-05-08
*/
#include "daeToEgg.h"
#include "daeToEggConverter.h"
/**
*
*/
DAEToEgg::
DAEToEgg():
SomethingToEgg("COLLADA", ".dae")
{
add_animation_options();
add_units_options();
add_normals_options();
add_transform_options();
add_option
("invtrans", "", false,
"Import the .dae file using inverted transparency. "
"This is useful when importing COLLADA files from some authoring tools "
"that export models with inverted transparency, such as Google SketchUp.",
&SomethingToEgg::dispatch_none, &_invert_transparency);
set_program_brief("convert COLLADA assets into .egg files");
set_program_description
("This program converts .dae files (COLLADA Digital Asset Exchange) to .egg.");
_coordinate_system = CS_yup_right;
_animation_convert = AC_both;
}
/**
*
*/
void DAEToEgg::
run() {
if (_animation_convert != AC_both && _animation_convert != AC_none &&
_animation_convert != AC_chan && _animation_convert != AC_model) {
std::cerr << "Unsupported animation convert option.\n";
exit(1);
}
nout << "Reading " << _input_filename << "\n";
_data->set_coordinate_system(_coordinate_system);
DAEToEggConverter converter;
converter.set_egg_data(_data);
converter._allow_errors = _allow_errors;
converter._invert_transparency = _invert_transparency;
apply_parameters(converter);
if (!converter.convert_file(_input_filename)) {
nout << "Errors in conversion.\n";
exit(1);
}
write_egg_file();
nout << "\n";
}
int main(int argc, char *argv[]) {
DAEToEgg prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,35 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file daeToEgg.h
* @author rdb
* @date 2008-05-08
*/
#ifndef DAETOEGG_H
#define DAETOEGG_H
#include "pandatoolbase.h"
#include "somethingToEgg.h"
#include "daeToEggConverter.h"
/**
* A program to read a DAE file and generate an egg file.
*/
class DAEToEgg : public SomethingToEgg {
public:
DAEToEgg();
void run();
private:
bool _invert_transparency;
};
#endif

View File

@ -0,0 +1,173 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToDAE.cxx
* @author rdb
* @date 2008-10-04
*/
#include "eggToDAE.h"
#include "dcast.h"
#include "pandaVersion.h"
#include <FCDocument/FCDocument.h>
#include <FCDocument/FCDAsset.h>
#include <FCDocument/FCDTransform.h>
// Useful conversion stuff
#define TO_VEC3(v) (LVecBase3d(v[0], v[1], v[2]))
#define TO_VEC4(v) (LVecBase4d(v[0], v[1], v[2], v[3]))
#define TO_COLOR(v) (LColor(v[0], v[1], v[2], v[3]))
#define FROM_VEC3(v) (FMVector3(v[0], v[1], v[2]))
#define FROM_VEC4(v) (FMVector4(v[0], v[1], v[2], v[3]))
#define FROM_MAT4(v) (FMMatrix44(v.get_data()))
#define FROM_FSTRING(fs) (fs.c_str())
using std::cerr;
/**
*
*/
EggToDAE::
EggToDAE() :
EggToSomething("COLLADA", ".dae", true, false)
{
set_binary_output(false);
set_program_brief("convert .egg files into COLLADA asset files");
set_program_description
("This program converts files from the egg format to the COLLADA "
".dae (Digital Asset Exchange) format.");
_document = nullptr;
}
/**
*
*/
void EggToDAE::
run() {
nassertv(has_output_filename());
nassertv(_data != nullptr);
FCollada::Initialize();
_document = FCollada::NewTopDocument();
// Add the contributor part to the asset
FCDAssetContributor* contributor = _document->GetAsset()->AddContributor();
const char* user_name = getenv("USER");
if (user_name == nullptr) user_name = getenv("USERNAME");
if (user_name != nullptr) contributor->SetAuthor(TO_FSTRING(user_name));
// contributor->SetSourceData();
char authoring_tool[1024];
snprintf(authoring_tool, 1024, "Panda3D %s eggToDAE converter | FCollada v%d.%02d", PANDA_VERSION_STR, FCOLLADA_VERSION >> 16, FCOLLADA_VERSION & 0xFFFF);
authoring_tool[1023] = 0;
contributor->SetAuthoringTool(TO_FSTRING(authoring_tool));
// Set coordinate system
switch (_data->get_coordinate_system()) {
case CS_zup_right:
_document->GetAsset()->SetUpAxis(FMVector3::ZAxis);
break;
case CS_yup_right:
_document->GetAsset()->SetUpAxis(FMVector3::YAxis);
break;
}
// Now actually start processing the data.
FCDSceneNode* visual_scene = _document->AddVisualScene();
for (EggGroupNode::iterator it = _data->begin(); it != _data->end(); ++it) {
if ((*it)->is_of_type(EggGroup::get_class_type())) {
process_node(visual_scene, DCAST(EggGroup, *it));
}
}
// We're done here.
FCollada::SaveDocument(_document, get_output_filename().to_os_specific().c_str());
SAFE_DELETE(_document);
FCollada::Release();
// if (!out) { nout << "An error occurred while writing.\n"; exit(1); }
}
void EggToDAE::process_node(FCDSceneNode* parent, const PT(EggGroup) node) {
assert(node != nullptr);
FCDSceneNode* scene_node = parent->AddChildNode();
// Set the parameters
scene_node->SetDaeId(node->get_name().c_str());
scene_node->SetJointFlag(node->is_joint());
// Apply the transforms
apply_transform(scene_node, node);
// Recursively process sub-nodes
for (EggGroupNode::iterator it = node->begin(); it != node->end(); ++it) {
if ((*it)->is_of_type(EggGroup::get_class_type())) {
process_node(scene_node, DCAST(EggGroup, *it));
}
}
}
void EggToDAE::apply_transform(FCDSceneNode* to, const PT(EggGroup) from) {
assert(to != nullptr);
assert(from != nullptr);
for (int co = 0; co < from->get_num_components(); ++co) {
switch (from->get_component_type(co)) {
case EggTransform::CT_translate2d:
cerr << "Warning: ignoring non-supported 2d translation\n";
break;
case EggTransform::CT_rotate2d:
cerr << "Warning: ignoring non-supported 2d rotation\n";
break;
case EggTransform::CT_scale2d:
cerr << "Warning: ignoring non-supported 2d scaling\n";
break;
case EggTransform::CT_matrix3:
cerr << "Warning: ignoring non-supported 2d matrix\n";
break;
case EggTransform::CT_translate3d: {
FCDTTranslation* new_transform = (FCDTTranslation*) to->AddTransform(FCDTransform::TRANSLATION);
new_transform->SetTranslation(FROM_VEC3(from->get_component_vec3(co)));
break; }
case EggTransform::CT_rotate3d: {
FCDTRotation* new_transform = (FCDTRotation*) to->AddTransform(FCDTransform::ROTATION);
new_transform->SetRotation(FROM_VEC3(from->get_component_vec3(co)), from->get_component_number(co));
break; }
case EggTransform::CT_scale3d: {
FCDTScale* new_transform = (FCDTScale*) to->AddTransform(FCDTransform::SCALE);
new_transform->SetScale(FROM_VEC3(from->get_component_vec3(co)));
break; }
case EggTransform::CT_matrix4: {
FCDTMatrix* new_transform = (FCDTMatrix*) to->AddTransform(FCDTransform::MATRIX);
new_transform->SetTransform(FROM_MAT4(from->get_component_mat4(co)));
break; }
case EggTransform::CT_rotx: {
FCDTRotation* new_transform = (FCDTRotation*) to->AddTransform(FCDTransform::ROTATION);
new_transform->SetRotation(FMVector3::XAxis, from->get_component_number(co));
break; }
case EggTransform::CT_roty: {
FCDTRotation* new_transform = (FCDTRotation*) to->AddTransform(FCDTransform::ROTATION);
new_transform->SetRotation(FMVector3::YAxis, from->get_component_number(co));
break; }
case EggTransform::CT_rotz: {
FCDTRotation* new_transform = (FCDTRotation*) to->AddTransform(FCDTransform::ROTATION);
new_transform->SetRotation(FMVector3::ZAxis, from->get_component_number(co));
break; }
case EggTransform::CT_uniform_scale: {
FCDTScale* new_transform = (FCDTScale*) to->AddTransform(FCDTransform::SCALE);
new_transform->SetScale(from->get_component_number(co), from->get_component_number(co), from->get_component_number(co));
break; }
default:
cerr << "Warning: ignoring invalid transform\n";
}
}
}
int main(int argc, char *argv[]) {
EggToDAE prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,43 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToDAE.h
* @author rdb
* @date 2008-10-04
*/
#ifndef EGGTODAE_H
#define EGGTODAE_H
#include "pandatoolbase.h"
#include "eggToSomething.h"
#include "eggGroup.h"
#include "eggTransform.h"
#include "pre_fcollada_include.h"
#include <FCollada.h>
#include <FCDocument/FCDSceneNode.h>
/**
* A program to read an egg file and write a DAE file.
*/
class EggToDAE : public EggToSomething {
public:
EggToDAE();
void run();
private:
FCDocument* _document;
void process_node(FCDSceneNode* parent, const PT(EggGroup) node);
void apply_transform(FCDSceneNode* to, const PT(EggGroup) from);
};
#endif

View File

@ -0,0 +1,42 @@
if(NOT HAVE_PYTHON)
return()
endif()
add_executable(deploy-stub deploy-stub.c)
if(IS_OSX)
target_link_options(deploy-stub PRIVATE -sectcreate __PANDA __panda /dev/null)
set_target_properties(deploy-stub PROPERTIES
INSTALL_RPATH "@executable_path"
BUILD_WITH_INSTALL_RPATH ON)
elseif(WIN32)
target_sources(deploy-stub PRIVATE frozen_dllmain.c)
elseif(IS_LINUX OR IS_FREEBSD)
set_target_properties(deploy-stub PROPERTIES
INSTALL_RPATH "$ORIGIN"
BUILD_WITH_INSTALL_RPATH ON)
target_link_options(deploy-stub PRIVATE -Wl,--disable-new-dtags -Wl,-z,origin -rdynamic)
target_link_libraries(deploy-stub m)
endif()
target_link_libraries(deploy-stub Python::Python)
install(TARGETS deploy-stub)
if(WIN32 OR IS_OSX)
add_executable(deploy-stubw WIN32 deploy-stub.c)
if(IS_OSX)
target_link_options(deploy-stubw PRIVATE -sectcreate __PANDA __panda /dev/null)
set_target_properties(deploy-stubw PROPERTIES
INSTALL_RPATH "@executable_path/../Frameworks"
BUILD_WITH_INSTALL_RPATH ON)
target_compile_definitions(deploy-stubw PRIVATE MACOS_APP_BUNDLE=1)
elseif(WIN32)
target_sources(deploy-stubw PRIVATE frozen_dllmain.c)
endif()
target_link_libraries(deploy-stubw Python::Python)
install(TARGETS deploy-stubw)
endif()

View File

@ -0,0 +1,25 @@
package org.jnius;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Special support for pyjnius.
*/
public class NativeInvocationHandler implements InvocationHandler {
private long _ptr;
public NativeInvocationHandler(long ptr) {
_ptr = ptr;
}
public long getPythonObjectPointer() {
return _ptr;
}
public Object invoke(Object proxy, Method method, Object[] args) {
return invoke0(proxy, method, args);
}
native Object invoke0(Object proxy, Method method, Object[] args);
}

View File

@ -0,0 +1,53 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file android_log.c
* @author rdb
* @date 2021-12-10
*/
#undef _POSIX_C_SOURCE
#undef _XOPEN_SOURCE
#define PY_SSIZE_T_CLEAN 1
#include "Python.h"
#include <android/log.h>
/**
* Writes a message to the Android log.
*/
static PyObject *
_py_write(PyObject *self, PyObject *args) {
int prio;
char *tag;
char *text;
if (PyArg_ParseTuple(args, "iss", &prio, &tag, &text)) {
__android_log_write(prio, tag, text);
Py_RETURN_NONE;
}
return NULL;
}
static PyMethodDef python_simple_funcs[] = {
{ "write", &_py_write, METH_VARARGS },
{ NULL, NULL }
};
static struct PyModuleDef android_log_module = {
PyModuleDef_HEAD_INIT,
"android_log",
NULL,
-1,
python_simple_funcs,
NULL, NULL, NULL, NULL
};
__attribute__((visibility("default")))
PyObject *PyInit_android_log() {
return PyModule_Create(&android_log_module);
}

View File

@ -0,0 +1,337 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file android_main.cxx
* @author rdb
* @date 2021-12-06
*/
#include "config_android.h"
#include "config_putil.h"
#include "virtualFileMountAndroidAsset.h"
#include "virtualFileSystem.h"
#include "filename.h"
#include "thread.h"
#include "urlSpec.h"
#include "android_native_app_glue.h"
#include "Python.h"
#include "structmember.h"
#include <sys/mman.h>
#include <android/log.h>
#include <thread>
// Leave room for future expansion.
#define MAX_NUM_POINTERS 24
// Define an exposed symbol where we store the offset to the module data.
extern "C" {
__attribute__((__visibility__("default"), used))
volatile struct {
uint64_t blob_offset;
uint64_t blob_size;
uint16_t version;
uint16_t num_pointers;
uint16_t codepage;
uint16_t flags;
uint64_t reserved;
void *pointers[MAX_NUM_POINTERS];
// The reason we initialize it to -1 is because otherwise, smart linkers may
// end up putting it in the .bss section for zero-initialized data.
} blobinfo = {(uint64_t)-1};
}
// Defined in android_log.c
extern "C" PyObject *PyInit_android_log();
/**
* Maps the binary blob at the given memory address to memory, and returns the
* pointer to the beginning of it.
*/
static void *map_blob(const char *path, off_t offset, size_t size) {
FILE *runtime = fopen(path, "rb");
assert(runtime != NULL);
void *blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset);
assert(blob != MAP_FAILED);
fclose(runtime);
return blob;
}
/**
* The inverse of map_blob.
*/
static void unmap_blob(void *blob) {
if (blob) {
munmap(blob, blobinfo.blob_size);
}
}
/**
* This function is called by native_app_glue to initialize the program.
*
* Note that this does not run in the main thread, but in a thread created
* specifically for this activity by android_native_app_glue.
*
* Unlike the regular deploy-stub, we need to interface directly with the
* Panda3D libraries here, since we can't pass the pointers from Java to Panda
* through the Python interpreter easily.
*/
void android_main(struct android_app *app) {
panda_android_app = app;
// Attach the app thread to the Java VM.
JNIEnv *env;
ANativeActivity *activity = app->activity;
int attach_status = activity->vm->AttachCurrentThread(&env, nullptr);
if (attach_status < 0 || env == nullptr) {
android_cat.error() << "Failed to attach thread to JVM!\n";
return;
}
jclass activity_class = env->GetObjectClass(activity->clazz);
// Get the current Java thread name. This just helps with debugging.
jmethodID methodID = env->GetStaticMethodID(activity_class, "getCurrentThreadName", "()Ljava/lang/String;");
jstring jthread_name = (jstring) env->CallStaticObjectMethod(activity_class, methodID);
std::string thread_name;
if (jthread_name != nullptr) {
const char *c_str = env->GetStringUTFChars(jthread_name, nullptr);
thread_name.assign(c_str);
env->ReleaseStringUTFChars(jthread_name, c_str);
}
// Before we make any Panda calls, we must make the thread known to Panda.
// This will also cause the JNIEnv pointer to be stored on the thread.
// Note that we must keep a reference to this thread around.
PT(Thread) current_thread = Thread::bind_thread(thread_name, "android_app");
android_cat.info()
<< "New native activity started on " << *current_thread << "\n";
// Fetch the data directory.
jmethodID get_appinfo = env->GetMethodID(activity_class, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
jobject appinfo = env->CallObjectMethod(activity->clazz, get_appinfo);
jclass appinfo_class = env->GetObjectClass(appinfo);
// Fetch the path to the data directory.
jfieldID datadir_field = env->GetFieldID(appinfo_class, "dataDir", "Ljava/lang/String;");
jstring datadir = (jstring) env->GetObjectField(appinfo, datadir_field);
const char *data_path = env->GetStringUTFChars(datadir, nullptr);
if (data_path != nullptr) {
Filename::_internal_data_dir = data_path;
android_cat.info() << "Path to data: " << data_path << "\n";
env->ReleaseStringUTFChars(datadir, data_path);
}
// Get the cache directory. Set the model-path to this location.
methodID = env->GetMethodID(activity_class, "getCacheDirString", "()Ljava/lang/String;");
jstring jcache_dir = (jstring) env->CallObjectMethod(activity->clazz, methodID);
if (jcache_dir != nullptr) {
const char *cache_dir;
cache_dir = env->GetStringUTFChars(jcache_dir, nullptr);
android_cat.info() << "Path to cache: " << cache_dir << "\n";
ConfigVariableFilename model_cache_dir("model-cache-dir", Filename());
model_cache_dir.set_value(cache_dir);
env->ReleaseStringUTFChars(jcache_dir, cache_dir);
}
// Fetch the path to the library directory.
jfieldID libdir_field = env->GetFieldID(appinfo_class, "nativeLibraryDir", "Ljava/lang/String;");
jstring libdir_jstr = (jstring) env->GetObjectField(appinfo, libdir_field);
const char *libdir = env->GetStringUTFChars(libdir_jstr, nullptr);
if (libdir != nullptr) {
std::string dtool_name = std::string(libdir) + "/libp3dtool.so";
ExecutionEnvironment::set_dtool_name(dtool_name);
android_cat.info() << "Path to dtool: " << dtool_name << "\n";
}
// Get the path to the APK.
methodID = env->GetMethodID(activity_class, "getPackageCodePath", "()Ljava/lang/String;");
jstring code_path = (jstring) env->CallObjectMethod(activity->clazz, methodID);
const char *apk_path;
apk_path = env->GetStringUTFChars(code_path, nullptr);
android_cat.info() << "Path to APK: " << apk_path << "\n";
// Get the path to the native library.
methodID = env->GetMethodID(activity_class, "getNativeLibraryPath", "()Ljava/lang/String;");
jstring lib_path_jstr = (jstring) env->CallObjectMethod(activity->clazz, methodID);
const char *lib_path;
lib_path = env->GetStringUTFChars(lib_path_jstr, nullptr);
android_cat.info() << "Path to native library: " << lib_path << "\n";
ExecutionEnvironment::set_binary_name(lib_path);
// Map the blob to memory
void *blob = map_blob(lib_path, (off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size);
env->ReleaseStringUTFChars(lib_path_jstr, lib_path);
assert(blob != NULL);
assert(blobinfo.num_pointers <= MAX_NUM_POINTERS);
for (uint32_t i = 0; i < blobinfo.num_pointers; ++i) {
// Only offset if the pointer is non-NULL. Except for the first
// pointer, which may never be NULL and usually (but not always)
// points to the beginning of the blob.
if (i == 0 || blobinfo.pointers[i] != nullptr) {
blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob);
}
}
// Now load the configuration files.
ConfigPage *page = nullptr;
ConfigPageManager *cp_mgr;
const char *prc_data = (char *)blobinfo.pointers[1];
if (prc_data != nullptr) {
cp_mgr = ConfigPageManager::get_global_ptr();
std::istringstream in(prc_data);
page = cp_mgr->make_explicit_page("builtin");
page->read_prc(in);
}
// Mount the assets directory.
Filename apk_fn(apk_path);
PT(VirtualFileMountAndroidAsset) asset_mount;
asset_mount = new VirtualFileMountAndroidAsset(app->activity->assetManager, apk_fn);
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
//Filename asset_dir(apk_fn.get_dirname(), "assets");
Filename asset_dir("/android_asset");
vfs->mount(asset_mount, asset_dir, 0);
// Release the apk_path.
env->ReleaseStringUTFChars(code_path, apk_path);
// Now add the asset directory to the model-path.
//TODO: prevent it from adding the directory multiple times.
get_model_path().append_directory(asset_dir);
// Offset the pointers in the module table using the base mmap address.
struct _frozen *moddef = (struct _frozen *)blobinfo.pointers[0];
while (moddef->name) {
moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob);
if (moddef->code != nullptr) {
moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob);
}
//__android_log_print(ANDROID_LOG_DEBUG, "Panda3D", "MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size);
moddef++;
}
PyImport_FrozenModules = (struct _frozen *)blobinfo.pointers[0];
PyPreConfig preconfig;
PyPreConfig_InitIsolatedConfig(&preconfig);
preconfig.utf8_mode = 1;
PyStatus status = Py_PreInitialize(&preconfig);
if (PyStatus_Exception(status)) {
env->ReleaseStringUTFChars(libdir_jstr, libdir);
Py_ExitStatusException(status);
return;
}
// Register the android_log module.
if (PyImport_AppendInittab("android_log", &PyInit_android_log) < 0) {
android_cat.error()
<< "Failed to register android_log module.\n";
env->ReleaseStringUTFChars(libdir_jstr, libdir);
return;
}
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
config.pathconfig_warnings = 0; /* Suppress errors from getpath.c */
config.buffered_stdio = 0;
config.configure_c_stdio = 0;
config.write_bytecode = 0;
PyConfig_SetBytesString(&config, &config.platlibdir, libdir);
env->ReleaseStringUTFChars(libdir_jstr, libdir);
status = Py_InitializeFromConfig(&config);
PyConfig_Clear(&config);
if (PyStatus_Exception(status)) {
Py_ExitStatusException(status);
return;
}
while (!app->destroyRequested) {
// Call the main module. This will not return until the app is done.
android_cat.info() << "Importing __main__\n";
int n = PyImport_ImportFrozenModule("__main__");
if (n == 0) {
Py_FatalError("__main__ not frozen");
break;
}
if (n < 0) {
if (!PyErr_ExceptionMatches(PyExc_SystemExit)) {
PyErr_Print();
} else {
PyErr_Clear();
}
}
if (app->destroyRequested) {
// The app closed responding to a destroy request.
break;
}
// Ask Android to clean up the activity.
android_cat.info() << "Exited from __main__, finishing activity\n";
ANativeActivity_finish(activity);
// We still need to keep an event loop going until Android gives us leave
// to end the process.
while (!app->destroyRequested) {
int looper_id;
struct android_poll_source *source;
auto result = ALooper_pollOnce(-1, &looper_id, nullptr, (void **)&source);
if (looper_id == LOOPER_ID_MAIN) {
int8_t cmd = android_app_read_cmd(app);
android_app_pre_exec_cmd(app, cmd);
android_app_post_exec_cmd(app, cmd);
// I don't think we can get a resume command after we call finish(),
// but let's handle it just in case.
if (cmd == APP_CMD_RESUME || cmd == APP_CMD_DESTROY) {
break;
}
} else if (source != nullptr) {
source->process(app, source);
}
}
}
Py_Finalize();
android_cat.info() << "Destroy requested, exiting from android_main\n";
vfs->unmount(asset_mount);
if (page != nullptr) {
cp_mgr->delete_explicit_page(page);
}
unmap_blob(blob);
// Detach the thread before exiting.
activity->vm->DetachCurrentThread();
_exit(0);
}

View File

@ -0,0 +1,767 @@
/* Python interpreter main program for frozen scripts */
#include "Python.h"
#ifdef _WIN32
# include "malloc.h"
# include <Shlobj.h>
#else
# include <sys/mman.h>
# include <pwd.h>
#endif
#ifdef __FreeBSD__
# include <sys/sysctl.h>
#endif
#ifdef __APPLE__
# include <mach-o/dyld.h>
# include <libgen.h>
#endif
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <locale.h>
#include "structmember.h"
/* Leave room for future expansion. We only read pointer 0, but there are
other pointers that are being read by configPageManager.cxx. */
#define MAX_NUM_POINTERS 24
/* Stored in the flags field of the blobinfo structure below. */
enum Flags {
F_log_append = 1,
F_log_filename_strftime = 2,
F_keep_docstrings = 4,
};
/* Define an exposed symbol where we store the offset to the module data. */
#ifdef _MSC_VER
__declspec(dllexport)
#else
__attribute__((__visibility__("default"), used))
#endif
volatile struct {
uint64_t blob_offset;
uint64_t blob_size;
uint16_t version;
uint16_t num_pointers;
uint16_t codepage;
uint16_t flags;
uint64_t reserved;
void *pointers[MAX_NUM_POINTERS];
// The reason we initialize it to -1 is because otherwise, smart linkers may
// end up putting it in the .bss section for zero-initialized data.
} blobinfo = {(uint64_t)-1};
#ifdef _WIN32
// These placeholders can have their names changed by deploy-stub.
__declspec(dllexport) DWORD SymbolPlaceholder___________________ = 0x00000001;
__declspec(dllexport) DWORD SymbolPlaceholder__ = 0x00000001;
#endif
#ifdef MS_WINDOWS
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
extern void PyWinFreeze_ExeInit(void);
extern void PyWinFreeze_ExeTerm(void);
static struct _inittab extensions[] = {
{0, 0},
};
# define WIN_UNICODE
#endif
#ifdef _WIN32
static wchar_t *log_pathw = NULL;
#endif
#if PY_VERSION_HEX >= 0x030b0000
typedef struct {
const char *name;
const unsigned char *code;
int size;
} ModuleDef;
#else
typedef struct _frozen ModuleDef;
#endif
/**
* Sets the main_dir field of the blobinfo structure, but only if it wasn't
* already set.
*/
static void set_main_dir(char *main_dir) {
if (blobinfo.num_pointers >= 10) {
if (blobinfo.num_pointers == 10) {
++blobinfo.num_pointers;
blobinfo.pointers[10] = NULL;
}
if (blobinfo.pointers[10] == NULL) {
blobinfo.pointers[10] = main_dir;
}
}
}
/**
* Creates the parent directories of the given path. Returns 1 on success.
*/
#ifdef _WIN32
static int mkdir_parent(const wchar_t *path) {
// Copy the path to a temporary buffer.
wchar_t buffer[4096];
size_t buflen = wcslen(path);
if (buflen + 1 >= _countof(buffer)) {
return 0;
}
wcscpy_s(buffer, _countof(buffer), path);
// Seek back to find the last path separator.
while (buflen-- > 0) {
if (buffer[buflen] == '/' || buffer[buflen] == '\\') {
buffer[buflen] = 0;
break;
}
}
if (buflen == (size_t)-1 || buflen == 0) {
// There was no path separator, or this was the root directory.
return 0;
}
if (CreateDirectoryW(buffer, NULL) != 0) {
// Success!
return 1;
}
// Failed.
DWORD last_error = GetLastError();
if (last_error == ERROR_ALREADY_EXISTS) {
// Not really an error: the directory is already there.
return 1;
}
if (last_error == ERROR_PATH_NOT_FOUND) {
// We need to make the parent directory first.
if (mkdir_parent(buffer)) {
// Parent successfully created. Try again to make the child.
if (CreateDirectoryW(buffer, NULL) != 0) {
// Got it!
return 1;
}
}
}
return 0;
}
#else
static int mkdir_parent(const char *path) {
// Copy the path to a temporary buffer.
char buffer[4096];
size_t buflen = strlen(path);
if (buflen + 1 >= sizeof(buffer)) {
return 0;
}
strcpy(buffer, path);
// Seek back to find the last path separator.
while (buflen-- > 0) {
if (buffer[buflen] == '/') {
buffer[buflen] = 0;
break;
}
}
if (buflen == (size_t)-1 || buflen == 0) {
// There was no path separator, or this was the root directory.
return 0;
}
if (mkdir(buffer, 0755) == 0) {
// Success!
return 1;
}
// Failed.
if (errno == EEXIST) {
// Not really an error: the directory is already there.
return 1;
}
if (errno == ENOENT || errno == EACCES) {
// We need to make the parent directory first.
if (mkdir_parent(buffer)) {
// Parent successfully created. Try again to make the child.
if (mkdir(buffer, 0755) == 0) {
// Got it!
return 1;
}
}
}
return 0;
}
#endif
/**
* Redirects the output streams to point to the log file with the given path.
*
* @param path specifies the location of log file, may start with ~
* @param append should be nonzero if it should not truncate the log file.
*/
static int setup_logging(const char *path, int append) {
#ifdef _WIN32
// Does it start with a tilde? Perform tilde expansion if so.
wchar_t *pathw = (wchar_t *)malloc(sizeof(wchar_t) * MAX_PATH);
pathw[0] = 0;
size_t offset = 0;
if (path[0] == '~' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) {
// Strip off the tilde.
++path;
// Get the home directory path for the current user.
if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathw))) {
free(pathw);
return 0;
}
offset = wcslen(pathw);
}
// We need to convert the rest of the path from UTF-8 to UTF-16.
if (MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw + offset,
(int)(MAX_PATH - offset)) == 0) {
free(pathw);
return 0;
}
DWORD access = append ? FILE_APPEND_DATA : (GENERIC_READ | GENERIC_WRITE);
int creation = append ? OPEN_ALWAYS : CREATE_ALWAYS;
HANDLE handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE) {
// Make the parent directories first.
mkdir_parent(pathw);
handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
}
if (handle == INVALID_HANDLE_VALUE) {
free(pathw);
return 0;
}
log_pathw = pathw;
if (append) {
SetFilePointer(handle, 0, NULL, FILE_END);
}
SetStdHandle(STD_OUTPUT_HANDLE, handle);
SetStdHandle(STD_ERROR_HANDLE, handle);
// If we are running under the UCRT in a GUI application, we can't be sure
// that we have valid fds for stdout and stderr, so we have to set them up.
// One way to do this is to reopen them to something silly (like NUL).
if (_fileno(stdout) < 0) {
_close(1);
_wfreopen(L"\\\\.\\NUL", L"w", stdout);
}
if (_fileno(stderr) < 0) {
_close(2);
_wfreopen(L"\\\\.\\NUL", L"w", stderr);
}
// Now replace the stdout and stderr file descriptors with one pointing to
// our desired handle.
int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | _O_APPEND);
_dup2(fd, _fileno(stdout));
_dup2(fd, _fileno(stderr));
_close(fd);
return 1;
#else
// Does it start with a tilde? Perform tilde expansion if so.
char buffer[PATH_MAX * 2];
size_t offset = 0;
if (path[0] == '~' && (path[1] == 0 || path[1] == '/')) {
// Strip off the tilde.
++path;
// Get the home directory path for the current user.
const char *home_dir = getenv("HOME");
if (home_dir == NULL) {
home_dir = getpwuid(getuid())->pw_dir;
}
offset = strlen(home_dir);
assert(offset < sizeof(buffer));
strncpy(buffer, home_dir, sizeof(buffer));
}
// Copy over the rest of the path.
strcpy(buffer + offset, path);
mode_t mode = O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC);
int fd = open(buffer, mode, 0644);
if (fd == -1) {
// Make the parent directories first.
mkdir_parent(buffer);
fd = open(buffer, mode, 0644);
}
if (fd == -1) {
perror(buffer);
return 0;
}
fflush(stdout);
fflush(stderr);
dup2(fd, 1);
dup2(fd, 2);
if (close(fd) < 0) {
perror("setup_logging: close");
}
return 1;
#endif
}
/**
* Sets the line_buffering property on a TextIOWrapper object.
*/
static int enable_line_buffering(PyObject *file) {
#if PY_VERSION_HEX >= 0x03070000
/* Python 3.7 has a useful reconfigure() method. */
PyObject *kwargs = _PyDict_NewPresized(1);
PyDict_SetItemString(kwargs, "line_buffering", Py_True);
PyObject *args = PyTuple_New(0);
PyObject *method = PyObject_GetAttrString(file, "reconfigure");
if (method != NULL) {
PyObject *result = PyObject_Call(method, args, kwargs);
Py_DECREF(method);
Py_DECREF(kwargs);
Py_DECREF(args);
if (result != NULL) {
Py_DECREF(result);
} else {
PyErr_Clear();
return 0;
}
} else {
Py_DECREF(kwargs);
Py_DECREF(args);
PyErr_Clear();
return 0;
}
#else
/* Older versions just don't expose a way to reconfigure(), but it's still
safe to override the property; we just have to use a hack to do it,
because it's officially marked "readonly". */
PyTypeObject *type = Py_TYPE(file);
PyMemberDef *member = type->tp_members;
while (member != NULL && member->name != NULL) {
if (strcmp(member->name, "line_buffering") == 0) {
*((char *)file + member->offset) = 1;
return 1;
}
++member;
}
fflush(stdout);
#endif
return 1;
}
/* Main program */
#ifdef WIN_UNICODE
int Py_FrozenMain(int argc, wchar_t **argv)
#else
int Py_FrozenMain(int argc, char **argv)
#endif
{
char *p;
int n, sts = 1;
int unbuffered = 0;
#ifndef NDEBUG
int inspect = 0;
#endif
#ifndef WIN_UNICODE
int i;
char *oldloc;
wchar_t **argv_copy = NULL;
/* We need a second copies, as Python might modify the first one. */
wchar_t **argv_copy2 = NULL;
if (argc > 0) {
argv_copy = (wchar_t **)alloca(sizeof(wchar_t *) * argc);
argv_copy2 = (wchar_t **)alloca(sizeof(wchar_t *) * argc);
}
#endif
Py_FrozenFlag = 1; /* Suppress errors from getpath.c */
Py_NoSiteFlag = 0;
Py_NoUserSiteDirectory = 1;
#if PY_VERSION_HEX >= 0x03020000
if (blobinfo.flags & F_keep_docstrings) {
Py_OptimizeFlag = 1;
} else {
Py_OptimizeFlag = 2;
}
#endif
#ifndef NDEBUG
if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
inspect = 1;
#endif
if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
unbuffered = 1;
if (unbuffered) {
setbuf(stdin, (char *)NULL);
setbuf(stdout, (char *)NULL);
setbuf(stderr, (char *)NULL);
}
#ifndef WIN_UNICODE
oldloc = setlocale(LC_ALL, NULL);
setlocale(LC_ALL, "");
for (i = 0; i < argc; i++) {
argv_copy[i] = Py_DecodeLocale(argv[i], NULL);
argv_copy2[i] = argv_copy[i];
if (!argv_copy[i]) {
fprintf(stderr, "Unable to decode the command line argument #%i\n",
i + 1);
argc = i;
goto error;
}
}
setlocale(LC_ALL, oldloc);
#endif
#ifdef MS_WINDOWS
PyImport_ExtendInittab(extensions);
#endif /* MS_WINDOWS */
if (argc >= 1) {
#ifndef WIN_UNICODE
Py_SetProgramName(argv_copy[0]);
#else
Py_SetProgramName(argv[0]);
#endif
}
Py_Initialize();
#ifdef MS_WINDOWS
PyWinFreeze_ExeInit();
#endif
#ifdef MS_WINDOWS
/* Ensure that line buffering is enabled on the output streams. */
if (!unbuffered) {
PyObject *sys_stream;
sys_stream = PySys_GetObject("__stdout__");
if (sys_stream && !enable_line_buffering(sys_stream)) {
fprintf(stderr, "Failed to enable line buffering on sys.stdout\n");
fflush(stderr);
}
sys_stream = PySys_GetObject("__stderr__");
if (sys_stream && !enable_line_buffering(sys_stream)) {
fprintf(stderr, "Failed to enable line buffering on sys.stderr\n");
fflush(stderr);
}
}
#endif
if (Py_VerboseFlag)
fprintf(stderr, "Python %s\n%s\n",
Py_GetVersion(), Py_GetCopyright());
#ifndef WIN_UNICODE
PySys_SetArgv(argc, argv_copy);
#else
PySys_SetArgv(argc, argv);
#endif
#ifdef MACOS_APP_BUNDLE
// Add the Frameworks directory to sys.path.
char buffer[PATH_MAX];
uint32_t bufsize = sizeof(buffer);
if (_NSGetExecutablePath(buffer, &bufsize) != 0) {
assert(false);
return 1;
}
char resolved[PATH_MAX];
if (!realpath(buffer, resolved)) {
perror("realpath");
return 1;
}
const char *dir = dirname(resolved);
sprintf(buffer, "%s/../Frameworks", dir);
PyObject *sys_path = PyList_New(1);
PyList_SET_ITEM(sys_path, 0, PyUnicode_FromString(buffer));
PySys_SetObject("path", sys_path);
Py_DECREF(sys_path);
// Now, store a path to the Resources directory into the main_dir pointer,
// for ConfigPageManager to read out and assign to MAIN_DIR.
sprintf(buffer, "%s/../Resources", dir);
set_main_dir(buffer);
// Finally, chdir to it, so that regular Python files are read from the
// right location.
chdir(buffer);
#endif
n = PyImport_ImportFrozenModule("__main__");
if (n == 0)
Py_FatalError("__main__ not frozen");
if (n < 0) {
PyErr_Print();
sts = 1;
}
else
sts = 0;
#ifndef NDEBUG
if (inspect && isatty((int)fileno(stdin)))
sts = PyRun_AnyFile(stdin, "<stdin>") != 0;
#endif
#ifdef MS_WINDOWS
PyWinFreeze_ExeTerm();
#endif
Py_Finalize();
#ifndef WIN_UNICODE
error:
if (argv_copy2) {
for (i = 0; i < argc; i++) {
PyMem_RawFree(argv_copy2[i]);
}
}
#endif
return sts;
}
/**
* Maps the binary blob at the given memory address to memory, and returns the
* pointer to the beginning of it.
*/
static void *map_blob(off_t offset, size_t size) {
void *blob;
FILE *runtime;
#ifdef _WIN32
wchar_t buffer[2048];
GetModuleFileNameW(NULL, buffer, 2048);
runtime = _wfopen(buffer, L"rb");
#elif defined(__FreeBSD__)
size_t bufsize = 4096;
char buffer[4096];
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
mib[3] = getpid();
if (sysctl(mib, 4, (void *)buffer, &bufsize, NULL, 0) == -1) {
perror("sysctl");
return NULL;
}
runtime = fopen(buffer, "rb");
#elif defined(__APPLE__)
char buffer[4096];
uint32_t bufsize = sizeof(buffer);
if (_NSGetExecutablePath(buffer, &bufsize) != 0) {
return NULL;
}
runtime = fopen(buffer, "rb");
#else
char buffer[4096];
ssize_t pathlen = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1);
if (pathlen <= 0) {
perror("readlink(/proc/self/exe)");
return NULL;
}
buffer[pathlen] = '\0';
runtime = fopen(buffer, "rb");
#endif
// Get offsets. In version 0, we read it from the end of the file.
if (blobinfo.version == 0) {
uint64_t end, begin;
fseek(runtime, -8, SEEK_END);
end = ftell(runtime);
fread(&begin, 8, 1, runtime);
offset = (off_t)begin;
size = (size_t)(end - begin);
}
// mmap the section indicated by the offset (or malloc/fread on windows)
#ifdef _WIN32
blob = (void *)malloc(size);
assert(blob != NULL);
fseek(runtime, (long)offset, SEEK_SET);
fread(blob, size, 1, runtime);
#else
blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset);
assert(blob != MAP_FAILED);
#endif
fclose(runtime);
return blob;
}
/**
* The inverse of map_blob.
*/
static void unmap_blob(void *blob) {
if (blob) {
#ifdef _WIN32
free(blob);
#else
munmap(blob, blobinfo.blob_size);
#endif
}
}
/**
* Main entry point to deploy-stub.
*/
#ifdef _WIN32
int wmain(int argc, wchar_t *argv[]) {
#else
int main(int argc, char *argv[]) {
#endif
int retval;
ModuleDef *moddef;
const char *log_filename;
void *blob = NULL;
log_filename = NULL;
#ifdef __APPLE__
// Strip a -psn_xxx argument passed in by macOS when run from an .app bundle.
if (argc > 1 && strncmp(argv[1], "-psn_", 5) == 0) {
argv[1] = argv[0];
++argv;
--argc;
}
#endif
/*
printf("blob_offset: %d\n", (int)blobinfo.blob_offset);
printf("blob_size: %d\n", (int)blobinfo.blob_size);
printf("version: %d\n", (int)blobinfo.version);
printf("num_pointers: %d\n", (int)blobinfo.num_pointers);
printf("codepage: %d\n", (int)blobinfo.codepage);
printf("flags: %d\n", (int)blobinfo.flags);
printf("reserved: %d\n", (int)blobinfo.reserved);
*/
// If we have a blob offset, we have to map the blob to memory.
if (blobinfo.version == 0 || blobinfo.blob_offset != 0) {
void *blob = map_blob((off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size);
assert(blob != NULL);
// Offset the pointers in the header using the base mmap address.
if (blobinfo.version > 0 && blobinfo.num_pointers > 0) {
uint32_t i;
assert(blobinfo.num_pointers <= MAX_NUM_POINTERS);
for (i = 0; i < blobinfo.num_pointers; ++i) {
// Only offset if the pointer is non-NULL. Except for the first
// pointer, which may never be NULL and usually (but not always)
// points to the beginning of the blob.
if (i == 0 || blobinfo.pointers[i] != 0) {
blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob);
}
}
if (blobinfo.num_pointers >= 12) {
log_filename = blobinfo.pointers[11];
}
} else {
blobinfo.pointers[0] = blob;
}
// Offset the pointers in the module table using the base mmap address.
moddef = blobinfo.pointers[0];
#if PY_VERSION_HEX < 0x030b0000
PyImport_FrozenModules = moddef;
#endif
while (moddef->name) {
moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob);
if (moddef->code != 0) {
moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob);
}
//printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size);
moddef++;
}
// In Python 3.11, we need to convert this to the new structure format.
#if PY_VERSION_HEX >= 0x030b0000
ModuleDef *moddef_end = moddef;
ptrdiff_t num_modules = moddef - (ModuleDef *)blobinfo.pointers[0];
struct _frozen *new_moddef = (struct _frozen *)calloc(num_modules + 1, sizeof(struct _frozen));
PyImport_FrozenModules = new_moddef;
for (moddef = blobinfo.pointers[0]; moddef < moddef_end; ++moddef) {
new_moddef->name = moddef->name;
new_moddef->code = moddef->code;
new_moddef->size = moddef->size < 0 ? -(moddef->size) : moddef->size;
new_moddef->is_package = moddef->size < 0;
#if PY_VERSION_HEX < 0x030d0000 // 3.13
new_moddef->get_code = NULL;
#endif
new_moddef++;
}
#endif
} else {
PyImport_FrozenModules = blobinfo.pointers[0];
}
if (log_filename != NULL) {
char log_filename_buf[4096];
if (blobinfo.flags & F_log_filename_strftime) {
log_filename_buf[0] = 0;
time_t now = time(NULL);
if (strftime(log_filename_buf, sizeof(log_filename_buf), log_filename, localtime(&now)) > 0) {
log_filename = log_filename_buf;
}
}
setup_logging(log_filename, (blobinfo.flags & F_log_append) != 0);
}
#ifdef _WIN32
if (blobinfo.codepage != 0) {
SetConsoleCP(blobinfo.codepage);
SetConsoleOutputCP(blobinfo.codepage);
}
#endif
// Run frozen application
retval = Py_FrozenMain(argc, argv);
fflush(stdout);
fflush(stderr);
#if PY_VERSION_HEX >= 0x030b0000
free((void *)PyImport_FrozenModules);
PyImport_FrozenModules = NULL;
#endif
unmap_blob(blob);
return retval;
}
#ifdef WIN_UNICODE
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *lpCmdLine, int nCmdShow) {
return wmain(__argc, __wargv);
}
#elif defined(_WIN32)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char *lpCmdLine, int nCmdShow) {
return main(__argc, __argv);
}
#endif

View File

@ -0,0 +1,134 @@
/* FreezeDLLMain.cpp
This is a DLLMain suitable for frozen applications/DLLs on
a Windows platform.
The general problem is that many Python extension modules may define
DLL main functions, but when statically linked together to form
a frozen application, this DLLMain symbol exists multiple times.
The solution is:
* Each module checks for a frozen build, and if so, defines its DLLMain
function as "__declspec(dllexport) DllMain%module%"
(eg, DllMainpythoncom, or DllMainpywintypes)
* The frozen .EXE/.DLL links against this module, which provides
the single DllMain.
* This DllMain attempts to locate and call the DllMain for each
of the extension modules.
* This code also has hooks to "simulate" DllMain when used from
a frozen .EXE.
At this stage, there is a static table of "possibly embedded modules".
This should change to something better, but it will work OK for now.
Note that this scheme does not handle dependencies in the order
of DllMain calls - except it does call pywintypes first :-)
As an example of how an extension module with a DllMain should be
changed, here is a snippet from the pythoncom extension module.
// end of example code from pythoncom's DllMain.cpp
#ifndef BUILD_FREEZE
#define DLLMAIN DllMain
#define DLLMAIN_DECL
#else
#define DLLMAIN DllMainpythoncom
#define DLLMAIN_DECL __declspec(dllexport)
#endif
extern "C" DLLMAIN_DECL
BOOL WINAPI DLLMAIN(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
// end of example code from pythoncom's DllMain.cpp
***************************************************************************/
#include "windows.h"
static char *possibleModules[] = {
"pywintypes",
"pythoncom",
"win32ui",
NULL,
};
BOOL CallModuleDllMain(char *modName, DWORD dwReason);
/*
Called by a frozen .EXE only, so that built-in extension
modules are initialized correctly
*/
void PyWinFreeze_ExeInit(void)
{
char **modName;
for (modName = possibleModules;*modName;*modName++) {
/* printf("Initialising '%s'\n", *modName); */
CallModuleDllMain(*modName, DLL_PROCESS_ATTACH);
}
}
/*
Called by a frozen .EXE only, so that built-in extension
modules are cleaned up
*/
void PyWinFreeze_ExeTerm(void)
{
// Must go backwards
char **modName;
for (modName = possibleModules+(sizeof(possibleModules) / sizeof(char *))-2;
modName >= possibleModules;
*modName--) {
/* printf("Terminating '%s'\n", *modName);*/
CallModuleDllMain(*modName, DLL_PROCESS_DETACH);
}
}
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
BOOL ret = TRUE;
switch (dwReason) {
case DLL_PROCESS_ATTACH:
{
char **modName;
for (modName = possibleModules;*modName;*modName++) {
BOOL ok = CallModuleDllMain(*modName, dwReason);
if (!ok)
ret = FALSE;
}
break;
}
case DLL_PROCESS_DETACH:
{
// Must go backwards
char **modName;
for (modName = possibleModules+(sizeof(possibleModules) / sizeof(char *))-2;
modName >= possibleModules;
*modName--)
CallModuleDllMain(*modName, DLL_PROCESS_DETACH);
break;
}
}
return ret;
}
BOOL CallModuleDllMain(char *modName, DWORD dwReason)
{
BOOL (WINAPI * pfndllmain)(HINSTANCE, DWORD, LPVOID);
char funcName[255];
HMODULE hmod = GetModuleHandleW(NULL);
strcpy(funcName, "_DllMain");
strcat(funcName, modName);
strcat(funcName, "@12"); // stdcall convention.
pfndllmain = (BOOL (WINAPI *)(HINSTANCE, DWORD, LPVOID))GetProcAddress(hmod, funcName);
if (pfndllmain==NULL) {
/* No function by that name exported - then that module does
not appear in our frozen program - return OK
*/
return TRUE;
}
return (*pfndllmain)(hmod, dwReason, NULL);
}

View File

@ -0,0 +1,20 @@
set(P3DXF_HEADERS
dxfFile.h
dxfLayer.h
dxfLayerMap.h
dxfVertex.h
)
set(P3DXF_SOURCES
dxfFile.cxx
dxfLayer.cxx
dxfLayerMap.cxx
dxfVertex.cxx
)
composite_sources(p3dxf P3DXF_SOURCES)
add_library(p3dxf STATIC ${P3DXF_HEADERS} ${P3DXF_SOURCES})
target_link_libraries(p3dxf p3pandatoolbase)
# This is only needed for binaries in the pandatool package. It is not useful
# for user applications, so it is not installed.

View File

@ -0,0 +1,937 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfFile.cxx
* @author drose
* @date 2004-05-04
*/
#include "dxfFile.h"
#include "string_utils.h"
#include "virtualFileSystem.h"
using std::istream;
using std::ostream;
using std::string;
DXFFile::Color DXFFile::_colors[DXF_num_colors] = {
{ 1, 1, 1 }, // Color 0 is not used.
{ 1, 0, 0 }, // Color 1 = Red
{ 1, 1, 0 }, // Color 2 = Yellow
{ 0, 1, 0 }, // Color 3 = Green
{ 0, 1, 1 }, // Color 4 = Cyan
{ 0, 0, 1 }, // Color 5 = Blue
{ 1, 0, 1 }, // Color 6 = Magenta
{ 1, 1, 1 }, // Color 7 = Black/White
{ 0.3, 0.3, 0.3 }, // Color 8 = Gray
{ 0.7, 0.7, 0.7 }, // Color 9 = Gray
{ 1, 0, 0 }, // Remaining colors are from the fancy palette.
{ 1, 0.5, 0.5 },
{ 0.65, 0, 0 },
{ 0.65, 0.325, 0.325 },
{ 0.5, 0, 0 },
{ 0.5, 0.25, 0.25 },
{ 0.3, 0, 0 },
{ 0.3, 0.15, 0.15 },
{ 0.15, 0, 0 },
{ 0.15, 0.075, 0.075 },
{ 1, 0.25, 0 },
{ 1, 0.625, 0.5 },
{ 0.65, 0.1625, 0 },
{ 0.65, 0.4063, 0.325 },
{ 0.5, 0.125, 0 },
{ 0.5, 0.3125, 0.25 },
{ 0.3, 0.075, 0 },
{ 0.3, 0.1875, 0.15 },
{ 0.15, 0.0375, 0 },
{ 0.15, 0.0938, 0.075 },
{ 1, 0.5, 0 },
{ 1, 0.75, 0.5 },
{ 0.65, 0.325, 0 },
{ 0.65, 0.4875, 0.325 },
{ 0.5, 0.25, 0 },
{ 0.5, 0.375, 0.25 },
{ 0.3, 0.15, 0 },
{ 0.3, 0.225, 0.15 },
{ 0.15, 0.075, 0 },
{ 0.15, 0.1125, 0.075 },
{ 1, 0.75, 0 },
{ 1, 0.875, 0.5 },
{ 0.65, 0.4875, 0 },
{ 0.65, 0.5688, 0.325 },
{ 0.5, 0.375, 0 },
{ 0.5, 0.4375, 0.25 },
{ 0.3, 0.225, 0 },
{ 0.3, 0.2625, 0.15 },
{ 0.15, 0.1125, 0 },
{ 0.15, 0.1313, 0.075 },
{ 1, 1, 0 },
{ 1, 1, 0.5 },
{ 0.65, 0.65, 0 },
{ 0.65, 0.65, 0.325 },
{ 0.5, 0.5, 0 },
{ 0.5, 0.5, 0.25 },
{ 0.3, 0.3, 0 },
{ 0.3, 0.3, 0.15 },
{ 0.15, 0.15, 0 },
{ 0.15, 0.15, 0.075 },
{ 0.75, 1, 0 },
{ 0.875, 1, 0.5 },
{ 0.4875, 0.65, 0 },
{ 0.5688, 0.65, 0.325 },
{ 0.375, 0.5, 0 },
{ 0.4375, 0.5, 0.25 },
{ 0.225, 0.3, 0 },
{ 0.2625, 0.3, 0.15 },
{ 0.1125, 0.15, 0 },
{ 0.1313, 0.15, 0.075 },
{ 0.5, 1, 0 },
{ 0.75, 1, 0.5 },
{ 0.325, 0.65, 0 },
{ 0.4875, 0.65, 0.325 },
{ 0.25, 0.5, 0 },
{ 0.375, 0.5, 0.25 },
{ 0.15, 0.3, 0 },
{ 0.225, 0.3, 0.15 },
{ 0.075, 0.15, 0 },
{ 0.1125, 0.15, 0.075 },
{ 0.25, 1, 0 },
{ 0.625, 1, 0.5 },
{ 0.1625, 0.65, 0 },
{ 0.4063, 0.65, 0.325 },
{ 0.125, 0.5, 0 },
{ 0.3125, 0.5, 0.25 },
{ 0.075, 0.3, 0 },
{ 0.1875, 0.3, 0.15 },
{ 0.0375, 0.15, 0 },
{ 0.0938, 0.15, 0.075 },
{ 0, 1, 0 },
{ 0.5, 1, 0.5 },
{ 0, 0.65, 0 },
{ 0.325, 0.65, 0.325 },
{ 0, 0.5, 0 },
{ 0.25, 0.5, 0.25 },
{ 0, 0.3, 0 },
{ 0.15, 0.3, 0.15 },
{ 0, 0.15, 0 },
{ 0.075, 0.15, 0.075 },
{ 0, 1, 0.25 },
{ 0.5, 1, 0.625 },
{ 0, 0.65, 0.1625 },
{ 0.325, 0.65, 0.4063 },
{ 0, 0.5, 0.125 },
{ 0.25, 0.5, 0.3125 },
{ 0, 0.3, 0.075 },
{ 0.15, 0.3, 0.1875 },
{ 0, 0.15, 0.0375 },
{ 0.075, 0.15, 0.0938 },
{ 0, 1, 0.5 },
{ 0.5, 1, 0.75 },
{ 0, 0.65, 0.325 },
{ 0.325, 0.65, 0.4875 },
{ 0, 0.5, 0.25 },
{ 0.25, 0.5, 0.375 },
{ 0, 0.3, 0.15 },
{ 0.15, 0.3, 0.225 },
{ 0, 0.15, 0.075 },
{ 0.075, 0.15, 0.1125 },
{ 0, 1, 0.75 },
{ 0.5, 1, 0.875 },
{ 0, 0.65, 0.4875 },
{ 0.325, 0.65, 0.5688 },
{ 0, 0.5, 0.375 },
{ 0.25, 0.5, 0.4375 },
{ 0, 0.3, 0.225 },
{ 0.15, 0.3, 0.2625 },
{ 0, 0.15, 0.1125 },
{ 0.075, 0.15, 0.1313 },
{ 0, 1, 1 },
{ 0.5, 1, 1 },
{ 0, 0.65, 0.65 },
{ 0.325, 0.65, 0.65 },
{ 0, 0.5, 0.5 },
{ 0.25, 0.5, 0.5 },
{ 0, 0.3, 0.3 },
{ 0.15, 0.3, 0.3 },
{ 0, 0.15, 0.15 },
{ 0.075, 0.15, 0.15 },
{ 0, 0.75, 1 },
{ 0.5, 0.875, 1 },
{ 0, 0.4875, 0.65 },
{ 0.325, 0.5688, 0.65 },
{ 0, 0.375, 0.5 },
{ 0.25, 0.4375, 0.5 },
{ 0, 0.225, 0.3 },
{ 0.15, 0.2625, 0.3 },
{ 0, 0.1125, 0.15 },
{ 0.075, 0.1313, 0.15 },
{ 0, 0.5, 1 },
{ 0.5, 0.75, 1 },
{ 0, 0.325, 0.65 },
{ 0.325, 0.4875, 0.65 },
{ 0, 0.25, 0.5 },
{ 0.25, 0.375, 0.5 },
{ 0, 0.15, 0.3 },
{ 0.15, 0.225, 0.3 },
{ 0, 0.075, 0.15 },
{ 0.075, 0.1125, 0.15 },
{ 0, 0.25, 1 },
{ 0.5, 0.625, 1 },
{ 0, 0.1625, 0.65 },
{ 0.325, 0.4063, 0.65 },
{ 0, 0.125, 0.5 },
{ 0.25, 0.3125, 0.5 },
{ 0, 0.075, 0.3 },
{ 0.15, 0.1875, 0.3 },
{ 0, 0.0375, 0.15 },
{ 0.075, 0.0938, 0.15 },
{ 0, 0, 1 },
{ 0.5, 0.5, 1 },
{ 0, 0, 0.65 },
{ 0.325, 0.325, 0.65 },
{ 0, 0, 0.5 },
{ 0.25, 0.25, 0.5 },
{ 0, 0, 0.3 },
{ 0.15, 0.15, 0.3 },
{ 0, 0, 0.15 },
{ 0.075, 0.075, 0.15 },
{ 0.25, 0, 1 },
{ 0.625, 0.5, 1 },
{ 0.1625, 0, 0.65 },
{ 0.4063, 0.325, 0.65 },
{ 0.125, 0, 0.5 },
{ 0.3125, 0.25, 0.5 },
{ 0.075, 0, 0.3 },
{ 0.1875, 0.15, 0.3 },
{ 0.0375, 0, 0.15 },
{ 0.0938, 0.075, 0.15 },
{ 0.5, 0, 1 },
{ 0.75, 0.5, 1 },
{ 0.325, 0, 0.65 },
{ 0.4875, 0.325, 0.65 },
{ 0.25, 0, 0.5 },
{ 0.375, 0.25, 0.5 },
{ 0.15, 0, 0.3 },
{ 0.225, 0.15, 0.3 },
{ 0.075, 0, 0.15 },
{ 0.1125, 0.075, 0.15 },
{ 0.75, 0, 1 },
{ 0.875, 0.5, 1 },
{ 0.4875, 0, 0.65 },
{ 0.5688, 0.325, 0.65 },
{ 0.375, 0, 0.5 },
{ 0.4375, 0.25, 0.5 },
{ 0.225, 0, 0.3 },
{ 0.2625, 0.15, 0.3 },
{ 0.1125, 0, 0.15 },
{ 0.1313, 0.075, 0.15 },
{ 1, 0, 1 },
{ 1, 0.5, 1 },
{ 0.65, 0, 0.65 },
{ 0.65, 0.325, 0.65 },
{ 0.5, 0, 0.5 },
{ 0.5, 0.25, 0.5 },
{ 0.3, 0, 0.3 },
{ 0.3, 0.15, 0.3 },
{ 0.15, 0, 0.15 },
{ 0.15, 0.075, 0.15 },
{ 1, 0, 0.75 },
{ 1, 0.5, 0.875 },
{ 0.65, 0, 0.4875 },
{ 0.65, 0.325, 0.5688 },
{ 0.5, 0, 0.375 },
{ 0.5, 0.25, 0.4375 },
{ 0.3, 0, 0.225 },
{ 0.3, 0.15, 0.2625 },
{ 0.15, 0, 0.1125 },
{ 0.15, 0.075, 0.1313 },
{ 1, 0, 0.5 },
{ 1, 0.5, 0.75 },
{ 0.65, 0, 0.325 },
{ 0.65, 0.325, 0.4875 },
{ 0.5, 0, 0.25 },
{ 0.5, 0.25, 0.375 },
{ 0.3, 0, 0.15 },
{ 0.3, 0.15, 0.225 },
{ 0.15, 0, 0.075 },
{ 0.15, 0.075, 0.1125 },
{ 1, 0, 0.25 },
{ 1, 0.5, 0.625 },
{ 0.65, 0, 0.1625 },
{ 0.65, 0.325, 0.4063 },
{ 0.5, 0, 0.125 },
{ 0.5, 0.25, 0.3125 },
{ 0.3, 0, 0.075 },
{ 0.3, 0.15, 0.1875 },
{ 0.15, 0, 0.0375 },
{ 0.15, 0.075, 0.0938 },
{ 0.33, 0.33, 0.33 },
{ 0.464, 0.464, 0.464 },
{ 0.598, 0.598, 0.598 },
{ 0.732, 0.732, 0.732 },
{ 0.866, 0.866, 0.866 },
{ 1, 1, 1 },
};
/**
*
*/
DXFFile::
DXFFile() {
_in = nullptr;
_owns_in = false;
_layer = nullptr;
reset_entity();
_color_index = -1;
}
/**
*
*/
DXFFile::
~DXFFile() {
if (_owns_in) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
vfs->close_read_file(_in);
}
}
/**
* Opens the indicated filename and reads it as a DXF file.
*/
void DXFFile::
process(Filename filename) {
filename.set_text();
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
istream *in = vfs->open_read_file(filename, true);
if (in == nullptr) {
return;
}
process(in, true);
}
/**
* Reads the indicated stream as a DXF file. If owns_in is true, then the
* istream will be deleted via vfs->close_read_file() when the DXFFile object
* destructs.
*/
void DXFFile::
process(istream *in, bool owns_in) {
if (_owns_in) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
vfs->close_read_file(_in);
}
_in = in;
_owns_in = owns_in;
_state = ST_top;
begin_file();
while (_state != ST_done && _state != ST_error) {
if (get_group()) {
switch (_state) {
case ST_top:
state_top();
break;
case ST_section:
state_section();
break;
case ST_entity:
state_entity();
break;
case ST_verts:
state_verts();
break;
default:
break;
}
}
}
}
/**
* A hook for user code, if desired. This function is called whenever
* processing begins on the DXF file.
*/
void DXFFile::
begin_file() {
}
/**
* A hook for user code, if desired. This function is called whenever a new
* section in the DXF file is encountered.
*/
void DXFFile::
begin_section() {
}
/**
* A hook for user code, if desired. This function is called whenever a
* vertex is read from the DXF file. This function has the default behavior
* of adding the vertex to the _verts list, so that when done_entity() is
* called later, it will have the complete list of vertices available to it.
*/
void DXFFile::
done_vertex() {
DXFVertex v;
v._p = _p;
_verts.push_back(v);
}
/**
* This is the primary hook for user code. This function is called when an
* entity is read from the DXF file. This may be something like a polygon,
* point, or a polygon mesh: any geometry. It is up to the user code to
* override this function and do something interesting with each piece of
* geometry that is read.
*/
void DXFFile::
done_entity() {
}
/**
* A hook for user code, if desired. This function is called as each section
* in the DXF file is finished.
*/
void DXFFile::
end_section() {
}
/**
* A hook for user code, if desired. This function is called when the DXF
* processing is complete.
*/
void DXFFile::
end_file() {
}
/**
* A hook for user code, if desired. This function is called when some
* unexpected error occurs while reading the DXF file.
*/
void DXFFile::
error() {
nout << "Error!\n";
}
/**
* Returns the index of the closest matching AutoCAD color to the indicated r,
* g, b.
*/
int DXFFile::
find_color(double r, double g, double b) {
double best_diff = 4.0; // 4 is greater than our expected max, 3.
int best_index = 7;
for (int i = 0; i < 255; i++) {
double diff = ((r - _colors[i].r) * (r - _colors[i].r) +
(g - _colors[i].g) * (g - _colors[i].g) +
(b - _colors[i].b) * (b - _colors[i].b));
if (diff < best_diff) {
best_diff = diff;
best_index = i;
}
}
return best_index;
}
/**
* This is a convenience function to return the r,g,b color of the current
* entity (at the time of done_entity()). It's based on the _color_index
* value that was read from the DXF file.
*/
const DXFFile::Color &DXFFile::
get_color() const {
if (_color_index >= 0 && _color_index <= 255) {
return _colors[_color_index];
}
return _colors[0];
}
/**
* Assuming the current entity is a planar-based entity, for instance, a 2-d
* polygon (as opposed to a 3-d polygon), this converts the coordinates from
* the funny planar coordinate system to the world coordinates. It converts
* the _p value of the entity, as well as all vertices in the _verts list.
*/
void DXFFile::
ocs_2_wcs() {
compute_ocs();
// Convert the entity's position.
_p = _p * _ocs2wcs;
// Maybe we have these coordinates too.
_q = _q * _ocs2wcs;
_r = _r * _ocs2wcs;
_s = _s * _ocs2wcs;
// If there are any vertices, convert them too.
DXFVertices::iterator vi;
for (vi = _verts.begin(); vi != _verts.end(); ++vi) {
(*vi)._p = (*vi)._p * _ocs2wcs;
}
}
/**
* Computes the matrix used to convert from the planar coordinate system to
* world coordinates.
*/
void DXFFile::
compute_ocs() {
// A 2-d entity's vertices might be defined in an "Object Coordinate System"
// which has a funny definition. Its Z axis is defined by _z, and its X and
// Y axes are inferred from that. The origin is the same as the world
// coordinate system's origin.
// The Z axis is _z. Determine the x and y axes.
LVector3d x, y;
if (fabs(_z[0]) < 1.0/64.0 && fabs(_z[1]) < 1.0/64.0) {
x = cross(LVector3d(0.0, 1.0, 0.0), _z);
} else {
x = cross(LVector3d(0.0, 0.0, 1.0), _z);
}
x.normalize();
y = cross(x, _z);
y.normalize();
// Now build a rotate matrix from these vectors.
LMatrix4d
ocs( x[0], x[1], x[2], 0,
y[0], y[1], y[2], 0,
_z[0], _z[1], _z[2], 0,
0, 0, 0, 1);
_ocs2wcs.invert_from(ocs);
}
/**
* Reads the next code, string pair from the DXF file. This is the basic unit
* of data in a DXF file.
*/
bool DXFFile::
get_group() {
istream &in = *_in;
do {
in >> _code;
if (!in) {
change_state(ST_error);
return false;
}
// Now skip past exactly one newline character and any number of other
// whitespace characters.
while (in && in.peek() != '\n') {
in.get();
}
in.get();
while (in && isspace(in.peek()) && in.peek() != '\n') {
in.get();
}
std::getline(in, _string);
_string = trim_right(_string);
if (!in) {
change_state(ST_error);
return false;
}
// If we just read a comment, go back and get another one.
} while (_code == 999);
return true;
}
/**
* Called as new nodes are read to update the internal state correctly.
*/
void DXFFile::
change_state(State new_state) {
if (_state == ST_verts) {
done_vertex();
_p.set(0.0, 0.0, 0.0);
_q.set(0.0, 0.0, 0.0);
_r.set(0.0, 0.0, 0.0);
_s.set(0.0, 0.0, 0.0);
}
if ((_state == ST_entity || _state == ST_verts) &&
new_state != ST_verts) {
// We finish an entity when we read a new entity, or when we've read the
// last vertex (if we were scanning the vertices after an entity).
done_entity();
reset_entity();
}
switch (new_state) {
case ST_top:
end_section();
break;
case ST_done:
end_file();
break;
default:
break;
}
_state = new_state;
}
/**
*
*/
void DXFFile::
change_section(Section new_section) {
change_state(ST_section);
_section = new_section;
begin_section();
}
/**
* Given a newly read layer name, sets the _layer pointer to point to the
* associate layer. If the layer name has not been encountered before,
* creates a new layer definition.
*/
void DXFFile::
change_layer(const string &layer_name) {
if (_layer == nullptr || _layer->get_name() != layer_name) {
_layer = _layers.get_layer(layer_name, this);
}
}
/**
*
*/
void DXFFile::
change_entity(Entity new_entity) {
if (new_entity == EN_vertex && _vertices_follow) {
// If we read a new vertex and we're still scanning the vertices that
// follow an entity, keep scanning it--we haven't finished the entity yet.
change_state(ST_verts);
} else {
// Otherwise, begin a new entity.
change_state(ST_entity);
_entity = new_entity;
}
}
/**
* Resets the current entity to its initial, default state prior to reading a
* new entity.
*/
void DXFFile::
reset_entity() {
_p.set(0.0, 0.0, 0.0);
_q.set(0.0, 0.0, 0.0);
_r.set(0.0, 0.0, 0.0);
_s.set(0.0, 0.0, 0.0);
_z.set(0.0, 0.0, 1.0);
_vertices_follow = false;
// _color_index = -1;
_verts.erase(_verts.begin(), _verts.end());
}
/**
* Does the DXF processing when we are at the top of the file, outside of any
* section.
*/
void DXFFile::
state_top() {
if (_code != 0) {
nout << "Group code 0 not found at top level; found code " << _code
<< " instead.\n";
change_state(ST_error);
} else {
if (_string == "SECTION") {
if (get_group()) {
if (_code != 2) {
nout << "Group code 0 not immediately followed by code 2; found code "
<< _code << " instead.\n";
} else {
if (_string == "HEADER") {
change_section(SE_header);
} else if (_string == "TABLES") {
change_section(SE_tables);
} else if (_string == "BLOCKS") {
change_section(SE_blocks);
} else if (_string == "ENTITIES") {
change_section(SE_entities);
} else if (_string == "OBJECTS") {
change_section(SE_objects);
} else {
change_section(SE_unknown);
}
}
}
} else if (_string == "EOF") {
change_state(ST_done);
} else {
nout << "Unexpected section at top level: '" << _string << "'\n";
change_state(ST_error);
}
}
}
/**
* Does the DXF processing when we are within some section.
*/
void DXFFile::
state_section() {
string tail;
switch (_code) {
case 0:
if (_string == "ENDSEC") {
change_state(ST_top);
} else {
if (_section == SE_entities) {
if (_string == "3DFACE") {
change_entity(EN_3dface);
} else if (_string == "POINT") {
change_entity(EN_point);
} else if (_string == "INSERT") {
change_entity(EN_insert);
} else if (_string == "VERTEX") {
change_entity(EN_vertex);
} else if (_string == "POLYLINE") {
change_entity(EN_polyline);
} else {
change_entity(EN_unknown);
}
}
}
break;
case 8:
change_layer(_string);
break;
case 62: // Color.
_color_index = string_to_int(_string, tail);
break;
default:
break;
}
}
/**
* Does the DXF processing when we are reading an entity.
*/
void DXFFile::
state_entity() {
string tail;
switch (_code) {
case 0:
state_section();
break;
case 8:
change_layer(_string);
break;
case 10:
_p[0] = string_to_double(_string, tail);
break;
case 11:
_q[0] = string_to_double(_string, tail);
break;
case 12:
_r[0] = string_to_double(_string, tail);
break;
case 13:
_s[0] = string_to_double(_string, tail);
break;
case 20:
_p[1] = string_to_double(_string, tail);
break;
case 21:
_q[1] = string_to_double(_string, tail);
break;
case 22:
_r[1] = string_to_double(_string, tail);
break;
case 23:
_s[1] = string_to_double(_string, tail);
break;
case 30:
_p[2] = string_to_double(_string, tail);
break;
case 31:
_q[2] = string_to_double(_string, tail);
break;
case 32:
_r[2] = string_to_double(_string, tail);
break;
case 33:
_s[2] = string_to_double(_string, tail);
break;
case 62: // Color.
_color_index = string_to_int(_string, tail);
break;
case 66: // Vertices-follow.
_vertices_follow = (string_to_int(_string, tail) != 0);
break;
case 70: // Polyline flags.
_flags = string_to_int(_string, tail);
break;
case 210:
_z[0] = string_to_double(_string, tail);
break;
case 220:
_z[1] = string_to_double(_string, tail);
break;
case 230:
_z[2] = string_to_double(_string, tail);
break;
default:
break;
}
}
/**
* Does the DXF processing when we are reading the list of vertices that might
* follow an entity.
*/
void DXFFile::
state_verts() {
string tail;
switch (_code) {
case 0:
state_section();
break;
case 8:
change_layer(_string);
break;
case 10:
_p[0] = string_to_double(_string, tail);
break;
case 20:
_p[1] = string_to_double(_string, tail);
break;
case 30:
_p[2] = string_to_double(_string, tail);
break;
default:
break;
}
}
ostream &operator << (ostream &out, const DXFFile::State &state) {
switch (state) {
case DXFFile::ST_top:
return out << "ST_top";
case DXFFile::ST_section:
return out << "ST_section";
case DXFFile::ST_entity:
return out << "ST_entity";
case DXFFile::ST_verts:
return out << "ST_verts";
case DXFFile::ST_error:
return out << "ST_error";
case DXFFile::ST_done:
return out << "ST_done";
}
return out << "Unknown state";
}
ostream &operator << (ostream &out, const DXFFile::Section &section) {
switch (section) {
case DXFFile::SE_unknown:
return out << "SE_unknown";
case DXFFile::SE_header:
return out << "SE_header";
case DXFFile::SE_tables:
return out << "SE_tables";
case DXFFile::SE_blocks:
return out << "SE_blocks";
case DXFFile::SE_entities:
return out << "SE_entities";
case DXFFile::SE_objects:
return out << "SE_objects";
}
return out << "Unknown section";
}
ostream &operator << (ostream &out, const DXFFile::Entity &entity) {
switch (entity) {
case DXFFile::EN_unknown:
return out << "EN_unknown";
case DXFFile::EN_3dface:
return out << "EN_3dface";
case DXFFile::EN_point:
return out << "EN_point";
case DXFFile::EN_insert:
return out << "EN_insert";
case DXFFile::EN_vertex:
return out << "EN_vertex";
case DXFFile::EN_polyline:
return out << "EN_polyline";
}
return out << "Unknown entity";
}

168
pandatool/src/dxf/dxfFile.h Normal file
View File

@ -0,0 +1,168 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfFile.h
* @author drose
* @date 2004-05-04
*/
#ifndef DXFFILE_H
#define DXFFILE_H
#include "pandatoolbase.h"
#include "dxfLayer.h"
#include "dxfLayerMap.h"
#include "dxfVertex.h"
#include "luse.h"
#include "filename.h"
static const int DXF_max_line = 256;
static const int DXF_num_colors = 256;
/**
* A generic DXF-reading class. This class can read a DXF file but doesn't
* actually do anything with the data; it's intended to be inherited from and
* the appropriate functions overridden (particularly DoneEntity()).
*/
class DXFFile : public MemoryBase {
public:
DXFFile();
virtual ~DXFFile();
void process(Filename filename);
void process(std::istream *in, bool owns_in);
// These functions are called as the file is processed. These are the main
// hooks for redefining how the class should dispense its data. As each
// function is called, the state stored in the DXFFile class reflects the
// data that was most recently read.
virtual void begin_file();
virtual void begin_section();
virtual void done_vertex();
virtual void done_entity();
virtual void end_section();
virtual void end_file();
virtual void error();
// new_layer() is called whenever the DXFFile class encounters a new Layer
// definition, and must allocate a DXFLayer instance. This function is
// provided so that user code may force allocate of a specialized DXFLayer
// instance instead.
virtual DXFLayer *new_layer(const std::string &name) {
return new DXFLayer(name);
}
enum State {
ST_top,
ST_section,
ST_entity,
ST_verts,
ST_error,
ST_done,
};
enum Section {
SE_unknown,
SE_header,
SE_tables,
SE_blocks,
SE_entities,
SE_objects,
};
enum Entity {
EN_unknown,
EN_3dface,
EN_point,
EN_insert,
EN_vertex,
EN_polyline,
};
enum PolylineFlags {
PF_closed = 0x01,
PF_curve_fit = 0x02,
PF_spline_fit = 0x04,
PF_3d = 0x08,
PF_3d_mesh = 0x10,
PF_closed_n = 0x20,
PF_polyface = 0x40,
PF_continuous_linetype = 0x80,
};
// This is a table of standard Autocad colors. DXF files can store only a
// limited range of colors; specifically, the 255 colors defined by Autocad.
struct Color {
double r, g, b;
};
static Color _colors[DXF_num_colors];
// find_color() returns the index of the closest matching AutoCAD color to
// the indicated r, g, b.
static int find_color(double r, double g, double b);
// get_color() returns the r,g,b of the current entity. It is valid at the
// time done_entity() is called.
const Color &get_color() const;
// Some entities are defined in world coordinates, in 3-d space; other
// entities are inherently 2-d in nature and are defined in planar
// coordinates and must be converted to 3-d space. Call this function from
// done_entity() to convert a 2-d entity to 3-d world coordinates.
void ocs_2_wcs();
// These members indicate the current state and describe properties of the
// current thing being processed. They are valid at done_entity(), and at
// other times.
int _flags;
Section _section;
Entity _entity;
LPoint3d _p, _q, _r, _s;
LVector3d _z;
int _color_index;
DXFLayer *_layer;
// _verts is the list of vertices associated with the current entity. It is
// valid at the time done_entity() is called.
DXFVertices _verts;
// This is the set of layers encountered within the DXF file.
DXFLayerMap _layers;
protected:
State _state;
bool _vertices_follow;
LMatrix4d _ocs2wcs;
std::istream *_in;
bool _owns_in;
int _code;
std::string _string;
void compute_ocs();
bool get_group();
void change_state(State new_state);
void change_section(Section new_section);
void change_layer(const std::string &layer_name);
void change_entity(Entity new_entity);
void reset_entity();
void state_top();
void state_section();
void state_entity();
void state_verts();
};
std::ostream &operator << (std::ostream &out, const DXFFile::State &state);
std::ostream &operator << (std::ostream &out, const DXFFile::Section &section);
std::ostream &operator << (std::ostream &out, const DXFFile::Entity &entity);
#endif

View File

@ -0,0 +1,29 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfLayer.cxx
* @author drose
* @date 2004-05-04
*/
#include "dxfLayer.h"
/**
*
*/
DXFLayer::
DXFLayer(const std::string &name) : Namable(name) {
}
/**
*
*/
DXFLayer::
~DXFLayer() {
}

View File

@ -0,0 +1,34 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfLayer.h
* @author drose
* @date 2004-05-04
*/
#ifndef DXFLAYER_H
#define DXFLAYER_H
#include "pandatoolbase.h"
#include "namable.h"
/**
* This represents a "layer" as read from the DXF file. A layer may be
* defined by reading the header part of the file, or it may be implicitly
* defined by an entity's having referenced it.
*
* User code may derive from DXFLayer to associate private data with each
* layer, if desired.
*/
class DXFLayer : public Namable {
public:
DXFLayer(const std::string &name);
virtual ~DXFLayer();
};
#endif

View File

@ -0,0 +1,38 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfLayerMap.cxx
* @author drose
* @date 2004-05-04
*/
#include "dxfLayerMap.h"
#include "dxfFile.h"
/**
* Looks up the layer name in the map, and returns a pointer to the associated
* DXFLayer. If this is the first time this layer name has been used, creates
* a new DXFLayer by the given name. In this case, it calls
* dxffile->new_layer() to create the layer, allowing user code to override
* this function to create a specialized time, if desired.
*/
DXFLayer *DXFLayerMap::
get_layer(const std::string &name, DXFFile *dxffile) {
iterator lmi;
lmi = find(name);
if (lmi != end()) {
// The layer was already here.
return (*lmi).second;
}
// Need a new layer.
DXFLayer *layer = dxffile->new_layer(name);
(*this)[name] = layer;
return layer;
}

View File

@ -0,0 +1,33 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfLayerMap.h
* @author drose
* @date 2004-05-04
*/
#ifndef DXFLAYERMAP_H
#define DXFLAYERMAP_H
#include "pandatoolbase.h"
#include "pmap.h"
class DXFLayer;
class DXFFile;
/**
* A map of string (layer name) to DXFLayer: that is, the layers of a file
* ordered by name. This is used as a lookup within DXFFile to locate the
* layer associated with a particular entity.
*/
class DXFLayerMap : public pmap<std::string, DXFLayer *> {
public:
DXFLayer *get_layer(const std::string &name, DXFFile *dxffile);
};
#endif

View File

@ -0,0 +1,31 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfVertex.cxx
* @author drose
* @date 2004-05-04
*/
#include "dxfVertex.h"
/**
* This defines a unique ordering for vertices so that the DXFVertexMap can
* group identical vertices together.
*/
int DXFVertex::
operator < (const DXFVertex &other) const {
if (fabs(_p[0] - other._p[0]) > 0.0001) {
return _p[0] < other._p[0];
} else if (fabs(_p[1] - other._p[1]) > 0.0001) {
return _p[1] < other._p[1];
} else if (fabs(_p[2] - other._p[2]) > 0.0001) {
return _p[2] < other._p[2];
}
return false;
}

View File

@ -0,0 +1,38 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfVertex.h
* @author drose
* @date 2004-05-04
*/
#ifndef DXFVERTEX_H
#define DXFVERTEX_H
#include "pandatoolbase.h"
#include "pvector.h"
#include "luse.h"
/**
* Stored within DXFFile, this is the basic Vertex data of a DXF file. When
* DXFFile::DoneEntity() is called, if the entity is a type to have vertices,
* then DXFFile::_verts contains a list of all the vertices that belong to the
* entity.
*/
class DXFVertex {
public:
DXFVertex() { }
DXFVertex(const LPoint3d &p) : _p(p) { }
int operator < (const DXFVertex &other) const;
LPoint3d _p;
};
typedef pvector<DXFVertex> DXFVertices;
#endif

View File

@ -0,0 +1,4 @@
#include "dxfFile.cxx"
#include "dxfLayer.cxx"
#include "dxfLayerMap.cxx"
#include "dxfVertex.cxx"

View File

@ -0,0 +1,19 @@
if(NOT HAVE_EGG)
return()
endif()
set(P3DXFEGG_HEADERS
dxfToEggConverter.h
dxfToEggLayer.h
)
set(P3DXFEGG_SOURCES
dxfToEggConverter.cxx
dxfToEggLayer.cxx
)
add_library(p3dxfegg STATIC ${P3DXFEGG_HEADERS} ${P3DXFEGG_SOURCES})
target_link_libraries(p3dxfegg p3dxf p3eggbase)
# This is only needed for binaries in the pandatool package. It is not useful
# for user applications, so it is not installed.

View File

@ -0,0 +1,145 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfToEggConverter.cxx
* @author drose
* @date 2004-05-04
*/
#include "dxfToEggConverter.h"
#include "dxfToEggLayer.h"
#include "eggData.h"
/**
*
*/
DXFToEggConverter::
DXFToEggConverter() {
}
/**
*
*/
DXFToEggConverter::
DXFToEggConverter(const DXFToEggConverter &copy) :
SomethingToEggConverter(copy)
{
}
/**
*
*/
DXFToEggConverter::
~DXFToEggConverter() {
}
/**
* Allocates and returns a new copy of the converter.
*/
SomethingToEggConverter *DXFToEggConverter::
make_copy() {
return new DXFToEggConverter(*this);
}
/**
* Returns the English name of the file type this converter supports.
*/
std::string DXFToEggConverter::
get_name() const {
return "DXF";
}
/**
* Returns the common extension of the file type this converter supports.
*/
std::string DXFToEggConverter::
get_extension() const {
return "dxf";
}
/**
* Returns true if this file type can transparently load compressed files
* (with a .pz extension), false otherwise.
*/
bool DXFToEggConverter::
supports_compressed() const {
return true;
}
/**
* Handles the reading of the input file and converting it to egg. Returns
* true if successful, false otherwise.
*/
bool DXFToEggConverter::
convert_file(const Filename &filename) {
clear_error();
if (_egg_data->get_coordinate_system() == CS_default) {
_egg_data->set_coordinate_system(CS_zup_right);
}
process(filename);
return !had_error();
}
/**
*
*/
DXFLayer *DXFToEggConverter::
new_layer(const std::string &name) {
return new DXFToEggLayer(name, get_egg_data());
}
/**
* If the entity is a polygon, creates the corresponding egg polygon.
*/
void DXFToEggConverter::
done_entity() {
if (_entity == EN_polyline) {
// A Polyline is either an unclosed series of connected line segments, or
// a closed polygon of arbitrary complexity.
if ((_flags & PF_3d) == 0) {
// it's a 2-d polygon; convert it to 3-d coordinates.
ocs_2_wcs();
}
if (_flags & PF_closed) {
// it's closed; create a polygon.
nassertv(_layer!=nullptr);
((DXFToEggLayer *)_layer)->add_polygon(this);
} else {
// It's open; create a series of line segments.
nassertv(_layer!=nullptr);
((DXFToEggLayer *)_layer)->add_line(this);
}
} else if (_entity == EN_3dface) {
// DXF can also represent a polygon as a 3DFace. This might be either a
// quad or a triangle (if two of the vertices are the same). We'll add
// the vertices to our list of vertices and then define the polygon.
_verts.clear();
_verts.push_back(DXFVertex(_s));
_verts.push_back(DXFVertex(_r));
_verts.push_back(DXFVertex(_q));
_verts.push_back(DXFVertex(_p));
nassertv(_layer!=nullptr);
((DXFToEggLayer *)_layer)->add_polygon(this);
}
}
/**
* A hook for user code, if desired. This function is called when some
* unexpected error occurs while reading the DXF file.
*/
void DXFToEggConverter::
error() {
_error = true;
}

View File

@ -0,0 +1,48 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfToEggConverter.h
* @author drose
* @date 2004-05-04
*/
#ifndef DXFTOEGGCONVERTER_H
#define DXFTOEGGCONVERTER_H
#include "pandatoolbase.h"
#include "somethingToEggConverter.h"
#include "dxfFile.h"
/**
* This class supervises the construction of an EggData structure from a DXF
* file.
*/
class DXFToEggConverter : public SomethingToEggConverter, public DXFFile {
public:
DXFToEggConverter();
DXFToEggConverter(const DXFToEggConverter &copy);
~DXFToEggConverter();
virtual SomethingToEggConverter *make_copy();
virtual std::string get_name() const;
virtual std::string get_extension() const;
virtual bool supports_compressed() const;
virtual bool convert_file(const Filename &filename);
protected:
virtual DXFLayer *new_layer(const std::string &name);
virtual void done_entity();
virtual void error();
bool _error;
};
#endif

View File

@ -0,0 +1,99 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfToEggLayer.cxx
* @author drose
* @date 2004-05-04
*/
#include "dxfToEggLayer.h"
#include "dxfToEggConverter.h"
#include "dxfFile.h"
#include "eggGroup.h"
#include "eggPolygon.h"
#include "eggLine.h"
#include "eggVertex.h"
#include "eggVertexPool.h"
/**
*
*/
DXFToEggLayer::
DXFToEggLayer(const std::string &name, EggGroupNode *parent) : DXFLayer(name) {
_group = new EggGroup(name);
parent->add_child(_group);
_vpool = new EggVertexPool(name);
_group->add_child(_vpool);
}
/**
* Given that done_entity() has just been called and that the current entity
* represents a polygon, adds the corresponding polygon to the layer's
* EggGroup and vertex pool.
*/
void DXFToEggLayer::
add_polygon(const DXFToEggConverter *entity) {
EggPolygon *poly = new EggPolygon;
_group->add_child(poly);
const DXFFile::Color &color = entity->get_color();
poly->set_color(LColor(color.r, color.g, color.b, 1.0));
// A polyline's vertices are stored in the attached vector by dxf.cxx. They
// were defined in the DXF file using a series of "VERTEX" entries.
// For a 3dface, the vertices are defined explicitly as part of the entity;
// but in this case, they were added to the vector before add_polygon() was
// called.
DXFVertices::const_iterator vi;
for (vi = entity->_verts.begin();
vi != entity->_verts.end();
++vi) {
poly->add_vertex(add_vertex(*vi));
}
poly->cleanup();
}
/**
* Similar to add_polygon(), but adds a set of point lights instead.
*/
void DXFToEggLayer::
add_line(const DXFToEggConverter *entity) {
EggLine *line = new EggLine;
_group->add_child(line);
const DXFFile::Color &color = entity->get_color();
line->set_color(LColor(color.r, color.g, color.b, 1.0));
DXFVertices::const_iterator vi;
for (vi = entity->_verts.begin();
vi != entity->_verts.end();
++vi) {
line->add_vertex(add_vertex(*vi));
}
}
/**
* Adds a unique vertex to the layer's vertex pool and returns it. If the
* vertex was already defined previously, returns the original definition.
* This is designed to share the common vertices within a layer.
*/
EggVertex *DXFToEggLayer::
add_vertex(const DXFVertex &vert) {
EggVertex egg_vert;
egg_vert.set_pos(vert._p);
return _vpool->create_unique_vertex(egg_vert);
}

View File

@ -0,0 +1,48 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfToEggLayer.h
* @author drose
* @date 2004-05-04
*/
#ifndef DXFTOEGGLAYER_H
#define DXFTOEGGLAYER_H
#include "pandatoolbase.h"
#include "dxfLayer.h"
#include "eggVertexPool.h"
#include "eggGroup.h"
#include "pointerTo.h"
class EggGroupNode;
class EggVertex;
class DXFVertex;
class DXFToEggConverter;
/**
* The specialization of DXFLayer used by DXFToEggConverter. It contains a
* pointer to an EggGroup and a vertex pool; these are used to build up
* polygons grouped by layer in the egg file as each polygon is read from the
* DXF file.
*/
class DXFToEggLayer : public DXFLayer {
public:
DXFToEggLayer(const std::string &name, EggGroupNode *parent);
void add_polygon(const DXFToEggConverter *entity);
void add_line(const DXFToEggConverter *entity);
EggVertex *add_vertex(const DXFVertex &vertex);
PT(EggVertexPool) _vpool;
PT(EggGroup) _group;
};
#endif

View File

@ -0,0 +1,19 @@
if(NOT BUILD_TOOLS)
return()
endif()
add_executable(dxf-points dxfPoints.cxx dxfPoints.h)
target_link_libraries(dxf-points p3progbase p3dxf)
install(TARGETS dxf-points EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})
if(HAVE_EGG)
add_executable(egg2dxf eggToDXF.cxx eggToDXF.h eggToDXFLayer.cxx eggToDXFLayer.h)
target_link_libraries(egg2dxf p3dxfegg p3eggbase p3progbase)
install(TARGETS egg2dxf EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})
add_executable(dxf2egg dxfToEgg.cxx dxfToEgg.h)
target_link_libraries(dxf2egg p3dxfegg p3eggbase p3progbase)
install(TARGETS dxf2egg EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()

View File

@ -0,0 +1,89 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfPoints.cxx
* @author drose
* @date 2004-05-04
*/
#include "dxfPoints.h"
/**
*
*/
DXFPoints::
DXFPoints() :
WithOutputFile(true, true, false)
{
// Indicate the extension name we expect the user to supply for output
// files.
_preferred_extension = ".txt";
set_program_brief("extract points from AutoCAD .dxf files");
set_program_description
("This program reads an AutoCAD .dxf file and generates a simple "
"list of all the points contained within it, one per line, to a "
"text file, or to standard output.");
clear_runlines();
add_runline("[opts] input.dxf > output.txt");
add_runline("[opts] -o output.txt input.dxf");
add_runline("[opts] input.dxf output.txt");
}
/**
*
*/
void DXFPoints::
run() {
// Invoke the DXFFile base class to process the input file.
process(_input_filename);
}
/**
* This is inherited from DXFFile, and gets called as each entity (face, line,
* whatever) has finished processing.
*/
void DXFPoints::
done_entity() {
if (_entity == EN_point) {
get_output() << _p << "\n";
} else if (_entity == EN_insert) {
ocs_2_wcs();
get_output() << _p << "\n";
}
}
/**
*
*/
bool DXFPoints::
handle_args(ProgramBase::Args &args) {
if (args.empty()) {
nout << "You must specify the .dxf file to read on the command line.\n";
return false;
} else if (args.size() != 1) {
nout << "You must specify only one .dxf file to read on the command line.\n";
return false;
}
_input_filename = args[0];
return true;
}
int main(int argc, char *argv[]) {
DXFPoints prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,41 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfPoints.h
* @author drose
* @date 2004-05-04
*/
#ifndef DXFPOINTS_H
#define DXFPOINTS_H
#include "pandatoolbase.h"
#include "programBase.h"
#include "withOutputFile.h"
#include "dxfFile.h"
/**
* A simple program to read a dxf file and list the points contained within it
* to a text file.
*/
class DXFPoints : public ProgramBase, public WithOutputFile, public DXFFile {
public:
DXFPoints();
void run();
virtual void done_entity();
protected:
virtual bool handle_args(Args &args);
Filename _input_filename;
};
#endif

View File

@ -0,0 +1,74 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfToEgg.cxx
* @author drose
* @date 2004-05-04
*/
#include "dxfToEgg.h"
#include "dxfToEggConverter.h"
/**
*
*/
DXFToEgg::
DXFToEgg() :
SomethingToEgg("DXF", ".dxf")
{
add_units_options();
add_normals_options();
add_transform_options();
set_program_brief("convert AutoCAD .dxf files to .egg files");
set_program_description
("This program converts DXF (AutoCAD interchange format) to egg. It "
"only converts polygon data, with no fancy tricks. DXF does not support "
"hierarchical databases, so dxf2egg creates a single group at the root "
"level for each layer in the DXF file.");
redescribe_option
("cs",
"Specify the coordinate system of the input " + _format_name +
" file. Normally, this is z-up.");
_coordinate_system = CS_zup_right;
}
/**
*
*/
void DXFToEgg::
run() {
nout << "Reading " << _input_filename << "\n";
_data->set_coordinate_system(_coordinate_system);
DXFToEggConverter converter;
converter.set_egg_data(_data);
converter._allow_errors = _allow_errors;
apply_parameters(converter);
if (!converter.convert_file(_input_filename)) {
nout << "Errors in conversion.\n";
exit(1);
}
write_egg_file();
nout << "\n";
}
int main(int argc, char *argv[]) {
DXFToEgg prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,32 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file dxfToEgg.h
* @author drose
* @date 2004-05-04
*/
#ifndef DXFTOEGG_H
#define DXFTOEGG_H
#include "pandatoolbase.h"
#include "somethingToEgg.h"
#include "dxfToEggConverter.h"
/**
* A program to read a DXF file and generate an egg file.
*/
class DXFToEgg : public SomethingToEgg {
public:
DXFToEgg();
void run();
};
#endif

View File

@ -0,0 +1,149 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToDXF.cxx
* @author drose
* @date 2004-05-04
*/
#include "eggToDXF.h"
#include "eggPolygon.h"
#include "dcast.h"
/**
*
*/
EggToDXF::
EggToDXF() :
EggToSomething("DXF", ".dxf", true, false)
{
set_binary_output(true);
set_program_brief("convert .egg files to AutoCAD .dxf files");
set_program_description
("This program converts files from egg format to AutoCAD DXF format. "
"Since DXF does not support nested hierarchies, vertex normals, or any "
"fancy stuff you are probably used to, there is some information lost "
"in the conversion");
add_option
("p", "", 0,
"Use POLYLINE to represent polygons instead of the default, 3DFACE.",
&EggToDXF::dispatch_none, &_use_polyline);
_coordinate_system = CS_zup_right;
_got_coordinate_system = true;
}
/**
*
*/
void EggToDXF::
run() {
get_layers(_data);
if (_layers.empty()) {
nout << "Egg file contains no polygons. Output file not written.\n";
exit(1);
}
// uniquify_names("layer", _layers.begin(), _layers.end());
std::ostream &out = get_output();
// Autodesk says we don't need the header, but some DXF-reading programs
// might get confused if it's missing. We'll write an empty header.
out << "0\nSECTION\n"
<< "2\nHEADER\n"
<< "0\nENDSEC\n";
write_tables(out);
write_entities(out);
out << "0\nEOF\n"; // Mark end of file.
if (!out) {
nout << "An error occurred while writing.\n";
exit(1);
}
}
/**
* Traverses the hierarchy, looking for groups that contain polygons. Any
* such groups are deemed to be layers, and are added to the layers set.
*/
void EggToDXF::
get_layers(EggGroupNode *group) {
bool has_polys = false;
EggToDXFLayer layer(this, group);
EggGroupNode::iterator ci;
for (ci = group->begin(); ci != group->end(); ++ci) {
EggNode *child = (*ci);
if (child->is_of_type(EggPolygon::get_class_type())) {
EggPolygon *poly = DCAST(EggPolygon, child);
has_polys = true;
layer.add_color(poly->get_color());
} else if (child->is_of_type(EggGroupNode::get_class_type())) {
get_layers(DCAST(EggGroupNode, child));
}
}
if (has_polys) {
layer.choose_overall_color();
_layers.push_back(layer);
}
}
/**
* Writes out the "layers", e.g. groups. This is just the layers definition
* in the tables section at the beginning of the file; the actual geometry
* gets written later, in write_entities().
*/
void EggToDXF::
write_tables(std::ostream &out) {
out << "0\nSECTION\n"
<< "2\nTABLES\n" // Begin TABLES section.
<< "0\nTABLE\n"
<< "2\nLAYER\n" // Define LAYERS.
<< "70\n" << _layers.size() << "\n";
EggToDXFLayers::iterator li;
for (li = _layers.begin(); li != _layers.end(); ++li) {
(*li).write_layer(out);
}
out << "0\nENDTAB\n" // End LAYERS definition.
<< "0\nENDSEC\n"; // End TABLES section.
}
/**
* Writes out the "entities", e.g. polygons, defined for all layers.
*/
void EggToDXF::
write_entities(std::ostream &out) {
out << "0\nSECTION\n"
<< "2\nENTITIES\n"; // Begin ENTITIES section.
EggToDXFLayers::iterator li;
for (li = _layers.begin(); li != _layers.end(); ++li) {
(*li).write_entities(out);
}
out << "0\nENDSEC\n"; // End ENTITIES section.
}
int main(int argc, char *argv[]) {
EggToDXF prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,43 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToDXF.h
* @author drose
* @date 2004-05-04
*/
#ifndef EGGTODXF_H
#define EGGTODXF_H
#include "pandatoolbase.h"
#include "eggToSomething.h"
#include "eggToDXFLayer.h"
class EggGroupNode;
/**
* A program to read an egg file and write a DXF file.
*/
class EggToDXF : public EggToSomething {
public:
EggToDXF();
void run();
bool _use_polyline;
private:
void get_layers(EggGroupNode *group);
void write_tables(std::ostream &out);
void write_entities(std::ostream &out);
EggToDXFLayers _layers;
};
#endif

View File

@ -0,0 +1,219 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToDXFLayer.cxx
* @author drose
* @date 2004-05-04
*/
#include "eggToDXFLayer.h"
#include "eggToDXF.h"
#include "dxfFile.h"
#include "eggGroup.h"
#include "eggGroupNode.h"
#include "eggPolygon.h"
#include "dcast.h"
using std::ostream;
/**
*
*/
EggToDXFLayer::
EggToDXFLayer(EggToDXF *egg2dxf, EggGroupNode *group) :
_egg2dxf(egg2dxf), _group(group)
{
_layer_color = -1;
}
/**
*
*/
EggToDXFLayer::
EggToDXFLayer(const EggToDXFLayer &copy) :
_egg2dxf(copy._egg2dxf),
_group(copy._group),
_layer_color(copy._layer_color)
{
// The copy constructor doesn't bother with the ColorCounts.
}
/**
*
*/
void EggToDXFLayer::
operator = (const EggToDXFLayer &copy) {
_egg2dxf = copy._egg2dxf;
_group = copy._group;
_layer_color = copy._layer_color;
// The copy constructor doesn't bother with the ColorCounts.
}
/**
* Records that one polygon is defined using the indicated color. This will
* get accumulated; the color used by the majority of polygons will become the
* layer color.
*/
void EggToDXFLayer::
add_color(const LColor &color) {
int autocad_color = get_autocad_color(color);
ColorCounts::iterator cci;
cci = _color_counts.find(autocad_color);
if (cci == _color_counts.end()) {
// The first time a particular color was used. Count it once.
_color_counts[autocad_color] = 1;
} else {
// This color has been used before. Count it again.
(*cci).second++;
}
}
/**
* After all polygons have been accounted for, chooses the polygon color that
* occurred most often as the layer color.
*/
void EggToDXFLayer::
choose_overall_color() {
int max_count = 0;
ColorCounts::iterator cci;
for (cci = _color_counts.begin(); cci != _color_counts.end(); ++cci) {
int count = (*cci).second;
if (count > max_count) {
_layer_color = (*cci).first;
max_count = count;
}
}
}
/**
* Writes the layer definition into the table at the beginning of the DXF
* file. This does not write the actual geometry; that gets done later by
* write_entities().
*/
void EggToDXFLayer::
write_layer(ostream &out) {
out << "0\nLAYER\n"
<< "2\n" << _group->get_name() << "\n"
<< "70\n0\n"
<< "62\n" << _layer_color << "\n"
<< "6\nCONTINUOUS\n";
}
/**
* Writes a polygon as a POLYLINE entity.
*/
void EggToDXFLayer::
write_polyline(EggPolygon *poly, ostream &out) {
out << "0\nPOLYLINE\n"
<< "8\n" << _group->get_name() << "\n"
<< "66\n1\n"
<< "70\n1\n"
<< "62\n" << get_autocad_color(poly->get_color()) << "\n";
// Since DXF uses a clockwise ordering convention, we must reverse the order
// in which we write out the vertices.
EggPolygon::reverse_iterator vi;
for (vi = poly->rbegin(); vi != poly->rend(); ++vi) {
EggVertex *vtx = (*vi);
LVecBase3d pos = vtx->get_pos3() * _group->get_vertex_frame();
out << "0\nVERTEX\n"
<< "10\n" << pos[0] << "\n"
<< "20\n" << pos[1] << "\n"
<< "30\n" << pos[2] << "\n";
}
out << "0\nSEQEND\n";
}
/**
* Writes a polygon as a 3DFACE entity.
*/
void EggToDXFLayer::
write_3d_face(EggPolygon *poly, ostream &out) {
if (poly->size() > 4) {
// If we have a big polygon, we have to triangulate it, since 3DFaces can
// only be tris and quads.
PT(EggGroup) group = new EggGroup;
poly->triangulate_into(group, true);
EggGroupNode::iterator ci;
for (ci = group->begin(); ci != group->end(); ++ci) {
EggNode *child = (*ci);
if (child->is_of_type(EggPolygon::get_class_type())) {
write_3d_face(DCAST(EggPolygon, child), out);
}
}
} else if (poly->size() > 2) {
// Otherwise, if we have a tri or a quad, just write it out.
out << "0\n3DFACE\n"
<< "8\n" << _group->get_name() << "\n";
// Since DXF uses a clockwise ordering convention, we must reverse the
// order in which we write out the vertices.
int i;
EggPolygon::reverse_iterator vi;
for (i = 0, vi = poly->rbegin(); vi != poly->rend(); ++i, ++vi) {
EggVertex *vtx = (*vi);
LVecBase3d pos = vtx->get_pos3() * _group->get_vertex_frame();
out << 10 + i << "\n" << pos[0] << "\n"
<< 20 + i << "\n" << pos[1] << "\n"
<< 30 + i << "\n" << pos[2] << "\n";
if (i == 2 && poly->size() == 3) {
// A special case for triangles: repeat the last vertex.
out << 11 + i << "\n" << pos[0] << "\n"
<< 21 + i << "\n" << pos[1] << "\n"
<< 31 + i << "\n" << pos[2] << "\n";
}
}
}
}
/**
* Writes out the "entities", e.g. polygons, defined for the current layer.
*/
void EggToDXFLayer::
write_entities(ostream &out) {
EggGroupNode::iterator ci;
for (ci = _group->begin(); ci != _group->end(); ++ci) {
EggNode *child = (*ci);
if (child->is_of_type(EggPolygon::get_class_type())) {
EggPolygon *poly = DCAST(EggPolygon, child);
if (_egg2dxf->_use_polyline) {
write_polyline(poly, out);
} else {
write_3d_face(poly, out);
}
}
}
}
/**
* Returns the AutoCAD color index that most closely matches the indicated
* EggColor.
*/
int EggToDXFLayer::
get_autocad_color(const LColor &color) {
typedef pmap<LColor, int> ColorMap;
static ColorMap _map;
ColorMap::iterator cmi;
cmi = _map.find(color);
if (cmi != _map.end()) {
return (*cmi).second;
}
int result = DXFFile::find_color(color[0], color[1], color[2]);
_map[color] = result;
return result;
}

View File

@ -0,0 +1,56 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggToDXFLayer.h
* @author drose
* @date 2004-05-04
*/
#ifndef EGGTODXFLAYER_H
#define EGGTODXFLAYER_H
#include "pandatoolbase.h"
#include "pmap.h"
#include "pvector.h"
#include "luse.h"
class EggToDXF;
class EggPolygon;
class EggGroupNode;
/**
* A single layer in the DXF file to be written by EggToDXF.
*/
class EggToDXFLayer {
public:
EggToDXFLayer(EggToDXF *egg2dxf, EggGroupNode *group);
EggToDXFLayer(const EggToDXFLayer &copy);
void operator = (const EggToDXFLayer &copy);
void add_color(const LColor &color);
void choose_overall_color();
void write_layer(std::ostream &out);
void write_polyline(EggPolygon *poly, std::ostream &out);
void write_3d_face(EggPolygon *poly, std::ostream &out);
void write_entities(std::ostream &out);
private:
int get_autocad_color(const LColor &color);
typedef pmap<int, int> ColorCounts;
ColorCounts _color_counts;
EggToDXF *_egg2dxf;
EggGroupNode *_group;
int _layer_color;
};
typedef pvector<EggToDXFLayer> EggToDXFLayers;
#endif

View File

@ -0,0 +1,25 @@
if(NOT BUILD_TOOLS)
return()
endif()
if(NOT HAVE_EGG OR NOT HAVE_FREETYPE)
return()
endif()
set(P3EGG_MKFONT_HEADERS
eggMakeFont.h
rangeDescription.h rangeDescription.I
rangeIterator.h rangeIterator.I
)
set(P3EGG_MKFONT_SOURCES
eggMakeFont.cxx
rangeDescription.cxx
rangeIterator.cxx
)
composite_sources(egg-mkfont P3EGG_MKFONT_SOURCES)
add_executable(egg-mkfont ${P3EGG_MKFONT_HEADERS} ${P3EGG_MKFONT_SOURCES})
target_link_libraries(egg-mkfont p3palettizer p3eggbase p3pandatoolbase)
install(TARGETS egg-mkfont EXPORT Tools COMPONENT Tools DESTINATION ${CMAKE_INSTALL_BINDIR})

View File

@ -0,0 +1,5 @@
#include "eggMakeFont.cxx"
#include "rangeDescription.cxx"
#include "rangeIterator.cxx"

View File

@ -0,0 +1,745 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggMakeFont.cxx
* @author drose
* @date 2001-02-16
*/
#include "eggMakeFont.h"
#include "rangeIterator.h"
#include "palettizer.h"
#include "filenameUnifier.h"
#include "eggFile.h"
#include "textureImage.h"
#include "sourceTextureImage.h"
#include "pnmTextMaker.h"
#include "pnmTextGlyph.h"
#include "eggData.h"
#include "eggGroup.h"
#include "eggPoint.h"
#include "eggPolygon.h"
#include "eggTexture.h"
#include "eggVertexPool.h"
#include "eggVertex.h"
#include "string_utils.h"
#include "dcast.h"
#include <ctype.h>
using std::string;
/**
*
*/
EggMakeFont::
EggMakeFont() : EggWriter(true, false) {
set_program_brief("generates .egg files with rasterized font glyphs");
set_program_description
("egg-mkfont uses the FreeType library to generate an egg file "
"and a series of texture images from a font file "
"input, such as a TTF file. The resulting egg file "
"can be loaded in Panda as a font for rendering text, even "
"if FreeType is not compiled into the executing Panda.\n\n"
"egg-mkfont will normally run the generated egg file through "
"egg-palettize automatically as part of the generation process. "
"This collects the individual glyph textures into a small number "
"of texture maps. If you intend to run the font through egg-palettize "
"yourself later, you may choose to omit this step.");
clear_runlines();
add_runline("[opts] -o output.egg font");
add_runline("[opts] font output.egg");
add_option
("fg", "r,g,b[,a]", 0,
"Specifies the foreground color of the generated texture map. The "
"default is white: 1,1,1,1, which leads to the most flexibility "
"as the color can be modulated at runtime to any suitable color.",
&EggMakeFont::dispatch_color, nullptr, &_fg[0]);
add_option
("bg", "r,g,b[,a]", 0,
"Specifies the background color of the generated texture map. The "
"default is transparent: 1,1,1,0, which allows the text to be "
"visible against any color background by placing a polygon of a "
"suitable color behind it. If the alpha component of either -fg "
"or -bg is not 1, the generated texture images will include an "
"alpha component; if both colors specify an alpha component of 1 "
"(or do not specify an alpha compenent), then the generated images "
"will not include an alpha component.",
&EggMakeFont::dispatch_color, nullptr, &_bg[0]);
add_option
("interior", "r,g,b[,a]", 0,
"Specifies the color to render the interior part of a hollow font. "
"This is a special effect that involves analysis of the bitmap after "
"the font has been rendered, and so is more effective when the pixel "
"size is large. It also implies -noaa (but you can use a scale "
"factor with -sf to achieve antialiasing).",
&EggMakeFont::dispatch_color, &_got_interior, &_interior[0]);
add_option
("chars", "range", 0,
"Specifies the characters of the font that are used. The range "
"specification may include combinations of decimal or hex unicode "
"values (where hex values are identified with a leading 0x), separated "
"by commas and hyphens to indicate ranges, e.g. '32-126,0xfa0-0xfff'. "
"It also may specify ranges of ASCII characters by enclosing them "
"within square brackets, e.g. '[A-Za-z0-9]'. If this is not specified, "
"the default set has all ASCII characters and an assorted set of "
"latin-1 characters, diacritics and punctuation marks.",
&EggMakeFont::dispatch_range, nullptr, &_range);
add_option
("extra", "file.egg", 0,
"Specifies additional externally-painted glyphs to mix into the "
"generated egg file. The named egg file is expected to contain one "
"or more groups, each of which is named with the decimal unicode "
"number of a character and should contain one polygon. These groups "
"are simply copied into the output egg file as if they were generated "
"locally. This option may be repeated.",
&EggMakeFont::dispatch_vector_string, nullptr, &_extra_filenames);
add_option
("ppu", "pixels", 0,
"Specify the pixels per unit. This is the number of pixels in the "
"generated texture map that are used for each onscreen unit (or each "
"10 points of font; see -ps). Setting this number larger results in "
"an easier-to-read font, but at the cost of more texture memory. "
"The default is 40.",
&EggMakeFont::dispatch_double, nullptr, &_pixels_per_unit);
add_option
("ps", "size", 0,
"Specify the point size of the resulting font. This controls the "
"apparent size of the font when it is rendered onscreen. By convention, "
"a 10 point font is 1 screen unit high, so the default is 10.",
&EggMakeFont::dispatch_double, nullptr, &_point_size);
add_option
("sdf", "", 0,
"If this is set, a signed distance field will be generated, which "
"results in crisp text even when the text is enlarged or zoomed in.",
&EggMakeFont::dispatch_true, nullptr, &_generate_distance_field);
add_option
("pm", "n", 0,
"The number of extra pixels around a single character in the "
"generated polygon. This may be a floating-point number. The "
"default is 1.",
&EggMakeFont::dispatch_double, nullptr, &_poly_margin);
add_option
("tm", "n", 0,
"The number of extra pixels around each character in the texture map. "
"This may only be an integer. The default is 2. This is meaningful "
"when -nopal is also used; in the normal case, use -pm to control "
"both the polygon size and the texture map spacing.",
&EggMakeFont::dispatch_int, nullptr, &_tex_margin);
add_option
("rm", "n", 0,
"The amount of padding in screen units to place around the glyph when "
"rendered. This differs from -pm in that it has no effect on the "
"generated texture map, only on the generated egg. Use this in order to "
"space the characters out in case they appear to be too close together "
"when rendered. The default is 0.",
&EggMakeFont::dispatch_double, nullptr, &_render_margin);
add_option
("sf", "factor", 0,
"The scale factor of the generated image. This is the factor by which "
"the font image is generated oversized, then reduced to its final size, "
"to improve antialiasing. If the specified font contains one "
"or more fixed-size fonts instead of a scalable font, the scale factor "
"may be automatically adjusted as necessary to scale the closest-"
"matching font to the desired pixel size. The default is 2.",
&EggMakeFont::dispatch_double, &_got_scale_factor, &_scale_factor);
add_option
("noaa", "", 0,
"Disable low-level antialiasing by the Freetype library. "
"This is unrelated to the antialiasing that is applied due to the "
"scale factor specified by -sf; you may have either one, neither, or "
"both kinds of antialiasing enabled.",
&EggMakeFont::dispatch_none, &_no_native_aa);
add_option
("nopal", "", 0,
"Don't run egg-palettize automatically on the output file, but "
"just output the raw egg file and all of its individual texture "
"images, one for each glyph.",
&EggMakeFont::dispatch_none, &_no_palettize);
add_option
("nr", "", 0,
"Don't actually reduce the images after applying the scale factor, but "
"leave them at their inflated sizes. Presumably you will reduce "
"them later, for instance with egg-palettize.",
&EggMakeFont::dispatch_none, &_no_reduce);
add_option
("gp", "pattern", 0,
"The pattern to be used to generate the glyph texture images. This "
"string will be passed to sprintf to generate the actual file name; it "
"should contain the string %d or %x (or some variant such as %03d) "
"which will be filled in with the Unicode number of each symbol. "
"If it is omitted, the default is based on the name of the egg file. "
"This is used only if -nopal is specified; in the normal case, "
"without -nopal, use -pp instead.",
&EggMakeFont::dispatch_string, nullptr, &_output_glyph_pattern);
add_option
("pp", "pattern", 0,
"The pattern to be used to generate the palette texture images. This "
"string is effectively passed to egg-palettize as the -tn option, and "
"thus should contain %i for the palette index number. This is used "
"if -nopal is not specified.",
&EggMakeFont::dispatch_string, nullptr, &_output_palette_pattern);
add_option
("palsize", "xsize,ysize", 0,
"Specify the size of the palette texture images. This is used if "
"-nopal is not specified.",
&EggMakeFont::dispatch_int_pair, nullptr, _palette_size);
add_option
("face", "index", 0,
"Specify the face index of the particular face within the font file "
"to use. Some font files contain multiple faces, indexed beginning "
"at 0. The default is face 0.",
&EggMakeFont::dispatch_int, nullptr, &_face_index);
_fg.set(1.0, 1.0, 1.0, 1.0);
_bg.set(1.0, 1.0, 1.0, 0.0);
_interior.set(1.0, 1.0, 1.0, 1.0);
_pixels_per_unit = 40.0;
_point_size = 10.0;
_poly_margin = 1.0;
_tex_margin = 2;
_render_margin = 0.0;
_palette_size[0] = _palette_size[1] = 512;
_face_index = 0;
_generate_distance_field = false;
_text_maker = nullptr;
_vpool = nullptr;
_group = nullptr;
}
/**
* Does something with the additional arguments on the command line (after all
* the -options have been parsed). Returns true if the arguments are good,
* false otherwise.
*/
bool EggMakeFont::
handle_args(ProgramBase::Args &args) {
if (args.empty()) {
nout << "Must specify name of font file on command line.\n";
return false;
}
_input_font_filename = args[0];
args.pop_front();
return EggWriter::handle_args(args);
}
/**
*
*/
void EggMakeFont::
run() {
if (has_output_filename() && !get_output_filename().get_dirname().empty()) {
FilenameUnifier::set_rel_dirname(get_output_filename().get_dirname());
} else {
FilenameUnifier::set_rel_dirname(".");
}
_text_maker = new PNMTextMaker(_input_font_filename, _face_index);
if (!_text_maker->is_valid()) {
exit(1);
}
if (_got_interior) {
_no_native_aa = true;
}
if (!_got_scale_factor) {
// The default scale factor is 4 if we are not using FreeType's antialias,
// or 2 if we are.
if (_generate_distance_field) {
_scale_factor = 1.0;
} else if (_no_native_aa) {
_scale_factor = 4.0;
} else {
_scale_factor = 2.0;
}
}
_text_maker->set_point_size(_point_size);
_text_maker->set_native_antialias(!_no_native_aa);
_text_maker->set_interior_flag(_got_interior);
_text_maker->set_pixels_per_unit(_pixels_per_unit);
_text_maker->set_scale_factor(_scale_factor);
// The text_maker may have had to adjust the pixels per unit and the scale
// factor according to what the font supports.
_pixels_per_unit = _text_maker->get_pixels_per_unit();
_scale_factor = _text_maker->get_scale_factor();
if (_text_maker->get_font_pixel_size() != 0) {
nout << "Using " << _text_maker->get_font_pixel_size() << "-pixel font.\n";
}
// Now we may want to tweak the scale factor so that fonts will actually be
// generated big. We have to do this after we have already send the current
// _scale_factor through the _text_maker for validation.
_palettize_scale_factor = _scale_factor;
if (_scale_factor != 1.0 && (_no_reduce || !_no_palettize)) {
// If _no_reduce is true (-nr was specified), we want to keep the glyph
// textures full-sized, because the user asked for that.
// If _no_palettize is false (-nopal was not specified), we still want to
// keep the glyph textures full-sized, because the palettizer will reduce
// them later.
_tex_margin = (int)(_tex_margin * _scale_factor);
_poly_margin *= _scale_factor;
_pixels_per_unit *= _scale_factor;
_scale_factor = 1.0;
_text_maker->set_pixels_per_unit(_pixels_per_unit);
_text_maker->set_scale_factor(1.0);
}
if (_no_reduce) {
// If -nr was specified, but we're still palettizing, we don't even want
// to reduce the palette images. Instead, we'll generate extra-large
// palette images.
_palette_size[0] = (int)(_palette_size[0] * _palettize_scale_factor);
_palette_size[1] = (int)(_palette_size[1] * _palettize_scale_factor);
_palettize_scale_factor = 1.0;
}
if (_range.is_empty()) {
// If there's no specified range, the default is the entire ASCII set.
_range.add_range(0x20, 0x7e);
_range.add_singleton(0xa1); // Upside down exclamation mark
_range.add_singleton(0xa9); // Copyright sign
_range.add_singleton(0xab); // Left double angle quote
// _range.add_singleton(0xae); Registered sign
_range.add_singleton(0xb0); // Degree symbol
_range.add_singleton(0xb5); // Mu/micro
_range.add_singleton(0xb8); // Cedilla
_range.add_singleton(0xbb); // Right double angle quote
_range.add_singleton(0xbf); // Upside down question mark
_range.add_singleton(0xc6); // AE ligature
_range.add_singleton(0xc7); // C cedilla
// _range.add_singleton(0xd0); Upper-case Eth _range.add_singleton(0xd8);
// Upper-case O with line _range.add_singleton(0xde); Upper-case Thorn
_range.add_singleton(0xdf); // German Eszet
_range.add_singleton(0xe6); // ae ligature
_range.add_singleton(0xe7); // c cedilla
_range.add_singleton(0xf0); // Lower-case Eth
_range.add_singleton(0xf8); // Lower-case O with line
_range.add_singleton(0xfe); // Lower-case Thorn
// _range.add_singleton(0x03c0); pi
// Dotless i and j, for combining purposes.
_range.add_singleton(0x0131);
_range.add_singleton(0x0237);
// And general punctuation. These don't take up much space anyway.
_range.add_range(0x2018, 0x201f);
_range.add_singleton(0x2026); // Ellipses
// Also add all the combining diacritic marks.
_range.add_range(0x0300, 0x030f);
}
if (_output_glyph_pattern.empty()) {
// Create a default texture filename pattern.
_output_glyph_pattern = get_output_filename().get_fullpath_wo_extension() + "%03d.png";
}
if (_output_palette_pattern.empty()) {
// Create a default texture filename pattern.
_output_palette_pattern = get_output_filename().get_fullpath_wo_extension() + "_%i";
}
// Figure out how many channels we need based on the foreground and
// background colors.
bool needs_alpha = (_fg[3] != 1.0 || _bg[3] != 1.0 || _interior[3] != 1.0);
bool needs_color = (_fg[0] != _fg[1] || _fg[1] != _fg[2] ||
_bg[0] != _bg[1] || _bg[1] != _bg[2] ||
_interior[0] != _interior[1] || _interior[1] != _interior[2]);
if (needs_alpha) {
if (needs_color) {
_num_channels = 4;
_format = EggTexture::F_rgba;
} else {
if (_fg[0] == 1.0 && _bg[0] == 1.0 && _interior[0] == 1.0) {
// A special case: we only need an alpha channel. Copy the alpha data
// into the color channels so we can write out a one-channel image.
_fg[0] = _fg[1] = _fg[2] = _fg[3];
_bg[0] = _bg[1] = _bg[2] = _bg[3];
_interior[0] = _interior[1] = _interior[2] = _interior[3];
_num_channels = 1;
_format = EggTexture::F_alpha;
} else {
_num_channels = 2;
_format = EggTexture::F_luminance_alpha;
}
}
} else {
if (needs_color) {
_num_channels = 3;
_format = EggTexture::F_rgb;
} else {
_num_channels = 1;
_format = EggTexture::F_luminance;
}
}
// Create a global Palettizer object. We'll use this even if the user
// specified -nopal, if nothing else just to hold all of the TextureImage
// pointers.
pal = new Palettizer;
pal->_generated_image_pattern = _output_palette_pattern;
pal->_omit_solitary = false;
pal->_round_uvs = false;
// Generate a txa script for the palettizer. We have the palettizer reduce
// all of the texture images by the inverse of our scale factor.
char buffer[1024];
sprintf(buffer, ":margin 0;:coverage 1000;:background %f %f %f %f;:palette %d %d;*: %f%% keep-format",
_bg[0], _bg[1], _bg[2], _bg[3],
_palette_size[0], _palette_size[1],
100.0 / _palettize_scale_factor);
std::istringstream txa_script(buffer);
pal->read_txa_file(txa_script, "default script");
pal->all_params_set();
// Now create all the egg structures. We can't use _data, since we want to
// pass this object to the palettizer, which will try to up its reference
// count.
PT(EggData) egg_data = new EggData;
_group = new EggGroup();
egg_data->add_child(_group);
append_command_comment(egg_data);
_vpool = new EggVertexPool("vpool");
_group->add_child(_vpool);
// Make the group a sequence, as a convenience. If we view the egg file
// directly we can see all the characters one at a time.
_group->set_switch_flag(true);
_group->set_switch_fps(2.0);
double margin = _poly_margin;
if (_generate_distance_field) {
// Distance fields are always rendered with binary alpha.
_group->set_alpha_mode(EggRenderMode::AM_binary);
// Fudged to make most fonts fit on 512x256.
if (_poly_margin >= 1) {
margin += 3.5;
_poly_margin -= 0.5;
}
_text_maker->set_distance_field_radius(4);
}
// Also create an egg group indicating the font's design size and poly
// margin.
EggGroup *ds_group = new EggGroup("ds");
_group->add_child(ds_group);
EggVertex *vtx = make_vertex(LPoint2d(margin / _pixels_per_unit, _text_maker->get_line_height()));
EggPoint *point = new EggPoint;
ds_group->add_child(point);
point->add_vertex(vtx);
// Finally, add the characters, one at a time.
RangeIterator ri(_range);
do {
add_character(ri.get_code());
} while (ri.next());
// If there are extra glyphs, pick them up.
if (!_extra_filenames.empty()) {
vector_string::const_iterator si;
for (si = _extra_filenames.begin(); si != _extra_filenames.end(); ++si) {
add_extra_glyphs(*si);
}
}
if (_no_palettize) {
// Ok, no palettize step; just write out the egg file and all of the
// textures.
Textures::iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
TextureImage *texture = (*ti);
texture->write(texture->read_source_image());
}
egg_data->write_egg(get_output());
} else {
// Pass the generated egg structure through egg-palettize, without writing
// it to disk first.
string name = get_output_filename().get_basename();
EggFile *egg_file = pal->get_egg_file(name);
egg_file->from_command_line(egg_data, "", get_output_filename(),
get_exec_command());
pal->add_command_line_egg(egg_file);
pal->process_all(true, "");
pal->optimal_resize();
pal->generate_images(true);
if (!pal->write_eggs()) {
exit(1);
}
// pal->report_pi();
}
}
/**
*
*/
bool EggMakeFont::
dispatch_range(const string &, const string &arg, void *var) {
RangeDescription *ip = (RangeDescription *)var;
return ip->parse_parameter(arg);
}
/**
* Allocates and returns a new vertex from the vertex pool representing the
* indicated 2-d coordinates.
*/
EggVertex *EggMakeFont::
make_vertex(const LPoint2d &xy) {
return
_vpool->make_new_vertex(LPoint3d::origin(_coordinate_system) +
LVector3d::rfu(xy[0], 0.0, xy[1], _coordinate_system));
}
/**
* Generates the indicated character and adds it to the font description.
*/
void EggMakeFont::
add_character(int code) {
PNMTextGlyph *glyph = _text_maker->get_glyph(code);
if (glyph == nullptr) {
nout << "No definition in font for character " << code << ".\n";
return;
}
make_geom(glyph, code);
}
/**
* Creates the actual geometry for the glyph.
*/
void EggMakeFont::
make_geom(PNMTextGlyph *glyph, int character) {
// Create an egg group to hold the polygon.
string group_name = format_string(character);
EggGroup *group = new EggGroup(group_name);
_group->add_child(group);
if (glyph->get_width() != 0 && glyph->get_height() != 0) {
int bitmap_top = glyph->get_top();
int bitmap_left = glyph->get_left();
double tex_x_size = glyph->get_width();
double tex_y_size = glyph->get_height();
double poly_margin = _poly_margin;
double x_origin = _tex_margin;
double y_origin = _tex_margin;
double page_y_size = tex_y_size + _tex_margin * 2;
double page_x_size = tex_x_size + _tex_margin * 2;
// Determine the corners of the rectangle in geometric units.
double tex_poly_margin = poly_margin / _pixels_per_unit;
double origin_y = bitmap_top / _pixels_per_unit;
double origin_x = bitmap_left / _pixels_per_unit;
double top = origin_y + tex_poly_margin;
double left = origin_x - tex_poly_margin;
double bottom = origin_y - tex_y_size / _pixels_per_unit - tex_poly_margin;
double right = origin_x + tex_x_size / _pixels_per_unit + tex_poly_margin;
// And the corresponding corners in UV units.
double uv_top = 1.0f - (double)(y_origin - poly_margin) / page_y_size;
double uv_left = (double)(x_origin - poly_margin) / page_x_size;
double uv_bottom = 1.0f - (double)(y_origin + poly_margin + tex_y_size) / page_y_size;
double uv_right = (double)(x_origin + poly_margin + tex_x_size) / page_x_size;
// Create the vertices for the polygon.
EggVertex *v1 = make_vertex(LPoint2d(left, bottom));
EggVertex *v2 = make_vertex(LPoint2d(right, bottom));
EggVertex *v3 = make_vertex(LPoint2d(right, top));
EggVertex *v4 = make_vertex(LPoint2d(left, top));
v1->set_uv(LTexCoordd(uv_left, uv_bottom));
v2->set_uv(LTexCoordd(uv_right, uv_bottom));
v3->set_uv(LTexCoordd(uv_right, uv_top));
v4->set_uv(LTexCoordd(uv_left, uv_top));
EggPolygon *poly = new EggPolygon();
group->add_child(poly);
poly->set_texture(get_tref(glyph, character));
poly->add_vertex(v1);
poly->add_vertex(v2);
poly->add_vertex(v3);
poly->add_vertex(v4);
}
// Now create a single point where the origin of the next character will be.
EggVertex *v0 = make_vertex(LPoint2d(glyph->get_advance() / _pixels_per_unit + _render_margin, 0.0));
EggPoint *point = new EggPoint;
group->add_child(point);
point->add_vertex(v0);
}
/**
* Returns the egg texture reference for a particular glyph, creating it if it
* has not already been created.
*/
EggTexture *EggMakeFont::
get_tref(PNMTextGlyph *glyph, int character) {
TRefs::iterator ti = _trefs.find(glyph);
if (ti != _trefs.end()) {
return (*ti).second;
}
EggTexture *tref = make_tref(glyph, character);
_trefs[glyph] = tref;
return tref;
}
/**
* Generates a texture image for the indicated glyph, and returns its egg
* reference.
*/
EggTexture *EggMakeFont::
make_tref(PNMTextGlyph *glyph, int character) {
char buffer[1024];
sprintf(buffer, _output_glyph_pattern.c_str(), character);
Filename texture_filename = buffer;
PNMImage image(glyph->get_width() + _tex_margin * 2,
glyph->get_height() + _tex_margin * 2, _num_channels);
image.fill(_bg[0], _bg[1], _bg[2]);
if (image.has_alpha()) {
image.alpha_fill(_bg[3]);
}
if (_got_interior) {
glyph->place(image, -glyph->get_left() + _tex_margin,
glyph->get_top() + _tex_margin, _fg, _interior);
} else {
glyph->place(image, -glyph->get_left() + _tex_margin,
glyph->get_top() + _tex_margin, _fg);
}
// We don't write the image to disk immediately, since it might just get
// palettized. But we do record it in a TextureImage object within the
// global Palettizer, so that it may be written out later.
string name = texture_filename.get_basename_wo_extension();
TextureImage *texture = pal->get_texture(name);
_textures.push_back(texture);
texture->set_filename("", texture_filename);
SourceTextureImage *source = texture->get_source(texture_filename, "", 0);
texture->set_source_image(image);
source->set_header(image);
EggTexture *tref = new EggTexture(name, texture_filename);
tref->set_format(_format);
tref->set_wrap_mode(EggTexture::WM_clamp);
tref->set_minfilter(EggTexture::FT_linear_mipmap_linear);
tref->set_magfilter(EggTexture::FT_linear);
tref->set_quality_level(EggTexture::QL_best);
return tref;
}
/**
* Reads the indicated filename and adds any numbered groups into the current
* egg file.
*/
void EggMakeFont::
add_extra_glyphs(const Filename &extra_filename) {
PT(EggData) extra_data = new EggData;
if (!extra_data->read(extra_filename)) {
return;
}
_group->steal_children(*extra_data);
}
/**
* Recursively searches for numbered groups in the indicated egg file, and
* copies them to the current egg file.
*/
void EggMakeFont::
r_add_extra_glyphs(EggGroupNode *egg_group) {
if (egg_group->is_of_type(EggGroup::get_class_type())) {
EggGroup *group = DCAST(EggGroup, egg_group);
if (is_numeric(group->get_name())) {
EggGroup *new_group = new EggGroup(group->get_name());
_group->add_child(new_group);
new_group->steal_children(*group);
return;
}
}
EggGroupNode::iterator ci;
for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
EggNode *child = (*ci);
if (child->is_of_type(EggGroupNode::get_class_type())) {
r_add_extra_glyphs(DCAST(EggGroupNode, child));
}
}
}
/**
* Returns true if the indicated string is all numeric digits, false
* otherwise.
*/
bool EggMakeFont::
is_numeric(const string &str) {
if (str.empty()) {
return false;
}
string::const_iterator si;
for (si = str.begin(); si != str.end(); ++si) {
if (!isdigit(*si)) {
return false;
}
}
return true;
}
int main(int argc, char *argv[]) {
EggMakeFont prog;
prog.parse_command_line(argc, argv);
prog.run();
return 0;
}

View File

@ -0,0 +1,100 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file eggMakeFont.h
* @author drose
* @date 2001-02-16
*/
#ifndef EGGMAKEFONT_H
#define EGGMAKEFONT_H
#include "pandatoolbase.h"
#include "rangeDescription.h"
#include "eggWriter.h"
#include "eggTexture.h"
#include "pmap.h"
#include "pvector.h"
#include "vector_string.h"
class PNMTextMaker;
class PNMTextGlyph;
class EggVertexPool;
class EggGroup;
class TextureImage;
/**
* This program uses FreeType to generate an egg file and a series of texture
* images from a font file input, such as a TTF file. The resulting egg file
* can be loaded in Panda as a StaticTextFont object for rendering text, even
* if FreeType is not compiled into the executing Panda.
*/
class EggMakeFont : public EggWriter {
public:
EggMakeFont();
protected:
virtual bool handle_args(Args &args);
public:
void run();
private:
static bool dispatch_range(const std::string &, const std::string &arg, void *var);
EggVertex *make_vertex(const LPoint2d &xy);
void add_character(int code);
void make_geom(PNMTextGlyph *glyph, int character);
EggTexture *get_tref(PNMTextGlyph *glyph, int character);
EggTexture *make_tref(PNMTextGlyph *glyph, int character);
void add_extra_glyphs(const Filename &extra_filename);
void r_add_extra_glyphs(EggGroupNode *egg_group);
static bool is_numeric(const std::string &str);
private:
LColor _fg, _bg, _interior;
bool _got_interior;
RangeDescription _range;
vector_string _extra_filenames;
double _pixels_per_unit;
double _point_size;
double _poly_margin;
int _tex_margin;
double _render_margin;
bool _got_scale_factor;
double _scale_factor;
bool _no_reduce;
bool _no_native_aa;
bool _no_palettize;
int _palette_size[2];
bool _generate_distance_field;
double _palettize_scale_factor;
Filename _input_font_filename;
int _face_index;
std::string _output_glyph_pattern;
std::string _output_palette_pattern;
PNMTextMaker *_text_maker;
EggTexture::Format _format;
int _num_channels;
EggVertexPool *_vpool;
EggGroup *_group;
typedef pmap<PNMTextGlyph *, EggTexture *> TRefs;
TRefs _trefs;
typedef pvector<TextureImage *> Textures;
Textures _textures;
};
#endif

View File

@ -0,0 +1,61 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file rangeDescription.I
* @author drose
* @date 2003-09-07
*/
/**
*
*/
INLINE void RangeDescription::
add_singleton(int code) {
_range_list.push_back(Range(code));
}
/**
*
*/
INLINE void RangeDescription::
add_range(int from_code, int to_code) {
_range_list.push_back(Range(from_code, to_code));
}
/**
* Returns true if there are no codes described in the range.
*/
INLINE bool RangeDescription::
is_empty() const {
return _range_list.empty();
}
/**
*
*/
INLINE RangeDescription::Range::
Range(int code) :
_from_code(code),
_to_code(code)
{
}
/**
*
*/
INLINE RangeDescription::Range::
Range(int from_code, int to_code) :
_from_code(from_code),
_to_code(to_code)
{
}
INLINE std::ostream &operator << (std::ostream &out, const RangeDescription &range) {
range.output(out);
return out;
}

Some files were not shown because too many files have changed in this diff Show More