collada: partial support for cameras

This commit is contained in:
Zack Buhman 2026-02-02 19:51:09 -06:00
parent d7183c1b39
commit fcbaf7ab66
6 changed files with 205 additions and 57 deletions

View File

@ -61,7 +61,11 @@ class State:
# effect_textures_by_texcoord: (effect_id, channel): [sampler_sid] # effect_textures_by_texcoord: (effect_id, channel): [sampler_sid]
effect_textures_by_texcoord: Dict[Tuple[str, str], list] effect_textures_by_texcoord: Dict[Tuple[str, str], list]
def __init__(self): # input_filename
input_filename: str
relative_path: bool
def __init__(self, input_filename):
self.vertex_buffer = BytesIO() self.vertex_buffer = BytesIO()
self.index_buffer = BytesIO() self.index_buffer = BytesIO()
self.joints_weights_vertex_buffer = BytesIO() self.joints_weights_vertex_buffer = BytesIO()
@ -76,6 +80,8 @@ class State:
self.image_indices = {} self.image_indices = {}
self.resource_names = {} self.resource_names = {}
self.effect_textures_by_texcoord = defaultdict(list) self.effect_textures_by_texcoord = defaultdict(list)
self.input_filename = input_filename
self.relative_path = False
def _sanitize(name): def _sanitize(name):
return name.replace(' ', '_').replace('-', '_').replace('.', '_').replace('/', '_') return name.replace(' ', '_').replace('-', '_').replace('.', '_').replace('/', '_')
@ -228,7 +234,7 @@ def render_node_transforms(state, collada, node_name, transformation_elements):
yield "}," yield "},"
elif type(transform) is types.Matrix: elif type(transform) is types.Matrix:
yield ".type = transform_type::MATRIX," yield ".type = transform_type::MATRIX,"
yield f".matrix = {render_float_tuple(matrix_transpose(transform.matrix))}," yield f".matrix = {render_float_tuple(matrix_transpose(transform.values))},"
elif type(transform) is types.Rotate: elif type(transform) is types.Rotate:
yield ".type = transform_type::ROTATE," yield ".type = transform_type::ROTATE,"
yield f".rotate = {render_float_tuple(transform.rotate)}," yield f".rotate = {render_float_tuple(transform.rotate)},"
@ -507,7 +513,7 @@ def render_library_visual_scenes(state, collada):
for node_index, node in enumerate(state.linearized_nodes): for node_index, node in enumerate(state.linearized_nodes):
node_name_id = get_node_name_id(node) node_name_id = get_node_name_id(node)
node_name = sanitize_name(state, node_name_id, node) node_name = sanitize_name(state, node_name_id, node)
yield f"&node_{node_name}," yield f"&node_{node_name}, // {node_index}"
yield "};" yield "};"
def render_header(namespace): def render_header(namespace):
@ -852,9 +858,11 @@ def render_library_lights(state, collada):
def image_resource_name(state, uri): def image_resource_name(state, uri):
uri = unquote(uri) uri = unquote(uri)
prefix = "file:///" file_prefix = "file:///"
assert uri.startswith(prefix), uri path = uri[len(file_prefix):] if uri.startswith(file_prefix) else uri
path = uri[len(prefix):] if not os.path.isabs(path) and not os.path.exists(path):
path = os.path.join(os.path.dirname(state.input_filename), path)
state.relative_path = True
assert os.path.exists(path), path assert os.path.exists(path), path
image_extensions = {".png", ".jpg", ".bmp", ".jpeg", ".tiff"} image_extensions = {".png", ".jpg", ".bmp", ".jpeg", ".tiff"}
assert os.path.splitext(path)[1].lower() in image_extensions, path assert os.path.splitext(path)[1].lower() in image_extensions, path
@ -959,14 +967,38 @@ def render_controller(state, collada, controller):
yield "};" yield "};"
def render_library_controllers(state, collada): def render_library_controllers(state, collada):
for library_controller in collada.library_controllers: for library_controllers in collada.library_controllers:
for controller in library_controller.controllers: for controller in library_controllers.controllers:
yield from render_controller(state, collada, controller) yield from render_controller(state, collada, controller)
def render_all(collada, namespace): def render_camera(state, collada, camera):
state = State() camera_name = sanitize_name(state, camera.id, camera)
perspective = camera.optics.technique_common.projection_type
assert type(perspective) is types.Perspective
def nf(f):
if f is None:
return float(0)
else:
return f
yield f"camera const camera_{camera_name} = {{"
yield f".xfov = {nf(perspective.xfov)}f,"
yield f".yfov = {nf(perspective.yfov)}f,"
yield f".znear = {nf(perspective.znear)}f,"
yield f".zfar = {nf(perspective.zfar)}f,"
yield f".aspect_ratio = {nf(perspective.aspect_ratio)}f,"
yield "};"
def render_library_cameras(state, collada):
for library_cameras in collada.library_cameras:
for camera in library_cameras.cameras:
yield from render_camera(state, collada, camera)
def render_all(collada, namespace, input_filename):
state = State(input_filename)
render, out = renderer() render, out = renderer()
render(render_header(namespace)) render(render_header(namespace))
render(render_library_cameras(state, collada))
render(render_library_lights(state, collada)) render(render_library_lights(state, collada))
render(render_library_animations(state, collada)) render(render_library_animations(state, collada))
render(render_library_images(state, collada)) render(render_library_images(state, collada))
@ -996,5 +1028,5 @@ if __name__ == "__main__":
import sys import sys
collada = parse.parse_collada_file(sys.argv[1]) collada = parse.parse_collada_file(sys.argv[1])
state, out = render_all(collada, "test") state, out = render_all(collada, "test", "./test.DAE")
print(out.getvalue()) print(out.getvalue())

View File

@ -65,7 +65,7 @@ def main():
collada = parse.parse_collada_file(input_collada) collada = parse.parse_collada_file(input_collada)
namespace = parse_namespace(input_collada) namespace = parse_namespace(input_collada)
state, out_source = header.render_all(collada, namespace) state, out_source = header.render_all(collada, namespace, input_collada)
with open(output_source, 'wb') as f: with open(output_source, 'wb') as f:
source_buf = out_source.getvalue() source_buf = out_source.getvalue()

View File

@ -649,12 +649,7 @@ def parse_matrix(lookup, sid_lookup, root):
values = [float(i) for i in root.text.strip().split()] values = [float(i) for i in root.text.strip().split()]
assert len(values) == 16 assert len(values) == 16
r0 = tuple(values[0:4]) matrix = types.Matrix(sid, values)
r1 = tuple(values[4:8])
r2 = tuple(values[8:12])
r3 = tuple(values[12:16])
matrix = types.Matrix(sid, tuple([r0, r1, r2, r3]))
lookup_add(sid_lookup, sid, matrix) lookup_add(sid_lookup, sid, matrix)
return matrix return matrix
@ -1022,6 +1017,85 @@ def parse_library_controllers(lookup, root):
lookup_add(lookup, id, library_controllers) lookup_add(lookup, id, library_controllers)
return library_controllers return library_controllers
def parse_perspective(lookup, root):
xfov = None
yfov = None
znear = None
zfar = None
aspect_ratio = None
for child in root.getchildren():
assert len(child.getchildren()) == 0
if child.tag == tag("xfov"):
assert xfov is None
xfov = float(child.text.strip())
if child.tag == tag("yfov"):
assert yfov is None
yfov = float(child.text.strip())
if child.tag == tag("znear"):
assert znear is None
znear = float(child.text.strip())
if child.tag == tag("zfar"):
assert zfar is None
zfar = float(child.text.strip())
if child.tag == tag("aspect_ratio"):
assert aspect_ratio is None
aspect_ratio = float(child.text.strip())
perspective = types.Perspective(xfov, yfov, znear, zfar, aspect_ratio)
return perspective
def parse_technique_common_optics(lookup, root):
projection_type = None
for child in root.getchildren():
if child.tag == tag("perspective"):
assert projection_type is None
projection_type = parse_perspective(lookup, child)
if child.tag == tag("orthographic"):
assert projection_type is None
assert False, child.tag
assert projection_type is not None
optics = types.TechniqueCommon_Optics(projection_type)
return optics
def parse_optics(lookup, root):
technique_common = None
for child in root.getchildren():
if child.tag == tag("technique_common"):
assert technique_common is None
technique_common = parse_technique_common_optics(lookup, child)
assert technique_common is not None
optics = types.Optics(technique_common)
return optics
def parse_camera(lookup, root):
id = root.attrib.get("id")
name = root.attrib.get("name")
optics = None
for child in root.getchildren():
if child.tag == tag("optics"):
assert optics is None
optics = parse_optics(lookup, child)
assert optics is not None
camera = types.Camera(id, name, optics)
lookup_add(lookup, id, camera)
return camera
def parse_library_cameras(lookup, root):
id = root.attrib.get("id")
name = root.attrib.get("name")
cameras = []
for child in root.getchildren():
if child.tag == tag("camera"):
cameras.append(parse_camera(lookup, child))
assert len(cameras) >= 1
library_cameras = types.LibraryCameras(id, name, cameras)
lookup_add(lookup, id, library_cameras)
return library_cameras
def parse_collada(tree): def parse_collada(tree):
root = tree.getroot() root = tree.getroot()
assert root.tag == tag("COLLADA") assert root.tag == tag("COLLADA")
@ -1030,6 +1104,8 @@ def parse_collada(tree):
lookup = {} lookup = {}
for child in root.getchildren(): for child in root.getchildren():
if child.tag == tag("library_cameras"):
collada.library_cameras.append(parse_library_cameras(lookup, child))
if child.tag == tag("library_animations"): if child.tag == tag("library_animations"):
collada.library_animations.append(parse_library_animations(lookup, child)) collada.library_animations.append(parse_library_animations(lookup, child))
if child.tag == tag("library_controllers"): if child.tag == tag("library_controllers"):

View File

@ -519,8 +519,39 @@ class LibraryAnimations:
animations: List[Animation] # 1 or more animations: List[Animation] # 1 or more
@dataclass(frozen=True)
class Perspective:
xfov: float
yfov: float
znear: float
zfar: float
aspect_ratio: float
@dataclass(frozen=True)
class TechniqueCommon_Optics:
projection_type: Union[Perspective]
@dataclass(frozen=True)
class Optics:
technique_common: TechniqueCommon_Optics
@dataclass(frozen=True)
class Camera:
id: Optional[ID]
name: Optional[str]
optics: Optics
@dataclass(frozen=True)
class LibraryCameras:
id: Optional[ID]
name: Optional[str]
cameras: List[Camera] # 1 or more
@dataclass @dataclass
class Collada: class Collada:
library_cameras: List[LibraryCameras]
library_animations: List[LibraryAnimations] library_animations: List[LibraryAnimations]
library_controllers: List[LibraryControllers] library_controllers: List[LibraryControllers]
library_effects: List[LibraryEffects] library_effects: List[LibraryEffects]
@ -533,6 +564,7 @@ class Collada:
_lookup: dict = field(repr=False) _lookup: dict = field(repr=False)
def __init__(self): def __init__(self):
self.library_cameras = []
self.library_animations = [] self.library_animations = []
self.library_controllers = [] self.library_controllers = []
self.library_effects = [] self.library_effects = []

View File

@ -349,6 +349,14 @@ namespace collada {
}; };
*/ */
struct camera {
float xfov;
float yfov;
float znear;
float zfar;
float aspect_ratio;
};
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// scene // scene
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////

View File

@ -543,15 +543,15 @@ float const array_node_bone002_rotationz_angle_input_array[] = {
}; };
float const array_node_bone002_rotationz_angle_output_array[] = { float const array_node_bone002_rotationz_angle_output_array[] = {
180.0f + 180.0f, 180.0f,
230.0f + 180.0f, 230.0f,
180.0f + 180.0f, 180.0f,
130.0f + 180.0f, 130.0f,
180.0f + 180.0f, 180.0f,
230.0f + 180.0f, 230.0f,
180.0f + 180.0f, 180.0f,
130.0f + 180.0f, 130.0f,
180.0f + 180.0f, 180.0f,
}; };
float const array_node_bone002_rotationz_angle_intangent_array[] = { float const array_node_bone002_rotationz_angle_intangent_array[] = {
@ -1660,6 +1660,15 @@ instance_material const instance_geometry_instance_materials_node_cube_0[] = {
.diffuse = { .input_set = -1 }, .diffuse = { .input_set = -1 },
.specular = { .input_set = -1 }, .specular = { .input_set = -1 },
}, },
{
.element_index = 5, // an index into mesh.triangles
.material = &material_material__17_material,
.emission = { .input_set = -1 },
.ambient = { .input_set = -1 },
.diffuse = { .input_set = -1 },
.specular = { .input_set = -1 },
},
{ {
.element_index = 0, // an index into mesh.triangles .element_index = 0, // an index into mesh.triangles
.material = &material_material__16_material, .material = &material_material__16_material,
@ -1678,15 +1687,6 @@ instance_material const instance_geometry_instance_materials_node_cube_0[] = {
.diffuse = { .input_set = -1 }, .diffuse = { .input_set = -1 },
.specular = { .input_set = -1 }, .specular = { .input_set = -1 },
}, },
{
.element_index = 5, // an index into mesh.triangles
.material = &material_material__17_material,
.emission = { .input_set = -1 },
.ambient = { .input_set = -1 },
.diffuse = { .input_set = -1 },
.specular = { .input_set = -1 },
},
{ {
.element_index = 2, // an index into mesh.triangles .element_index = 2, // an index into mesh.triangles
.material = &material_material__19_material, .material = &material_material__19_material,
@ -2044,8 +2044,8 @@ instance_light const instance_lights_node_light[] = {
}; };
channel const * const node_channels_node_light[] = { channel const * const node_channels_node_light[] = {
&node_channel_node_light_translation_y,
&node_channel_node_light_translation_x, &node_channel_node_light_translation_x,
&node_channel_node_light_translation_y,
}; };
node const node_node_light = { node const node_node_light = {
@ -2139,15 +2139,6 @@ int const joint_node_indices_node_box001_geom_box001_skin1[] = {
}; };
instance_material const instance_controller_instance_materials_node_box001_0[] = { instance_material const instance_controller_instance_materials_node_box001_0[] = {
{
.element_index = 0, // an index into mesh.triangles
.material = &material_material__14_material,
.emission = { .input_set = -1 },
.ambient = { .input_set = -1 },
.diffuse = { .input_set = -1 },
.specular = { .input_set = -1 },
},
{ {
.element_index = 1, // an index into mesh.triangles .element_index = 1, // an index into mesh.triangles
.material = &material_material__13_material, .material = &material_material__13_material,
@ -2166,6 +2157,15 @@ instance_material const instance_controller_instance_materials_node_box001_0[] =
.diffuse = { .input_set = -1 }, .diffuse = { .input_set = -1 },
.specular = { .input_set = -1 }, .specular = { .input_set = -1 },
}, },
{
.element_index = 0, // an index into mesh.triangles
.material = &material_material__14_material,
.emission = { .input_set = -1 },
.ambient = { .input_set = -1 },
.diffuse = { .input_set = -1 },
.specular = { .input_set = -1 },
},
{ {
.element_index = 3, // an index into mesh.triangles .element_index = 3, // an index into mesh.triangles
.material = &material_material__16_1_material, .material = &material_material__16_1_material,
@ -2328,17 +2328,17 @@ node const node_node_bone002 = {
}; };
node const * const nodes[] = { node const * const nodes[] = {
&node_node_environmentambientlight, &node_node_environmentambientlight, // 0
&node_node_cube, &node_node_cube, // 1
&node_node_torus, &node_node_torus, // 2
&node_node_cylinder, &node_node_cylinder, // 3
&node_node_plane, &node_node_plane, // 4
&node_node_geosphere, &node_node_geosphere, // 5
&node_node_light, &node_node_light, // 6
&node_node_lightindicator, &node_node_lightindicator, // 7
&node_node_box001, &node_node_box001, // 8
&node_node_bone001, &node_node_bone001, // 9
&node_node_bone002, &node_node_bone002, // 10
}; };
inputs const inputs_list[] = { inputs const inputs_list[] = {