Skip to content

Commit

Permalink
feat: new "--no_normals" option to not include normals in 3D tiles (#159
Browse files Browse the repository at this point in the history
)

* Added new "--no_normals" option to not include normals in 3D tiles

* Updated tests
  • Loading branch information
andreiveselov authored Sep 8, 2023
1 parent 11e1d7a commit 87b06c2
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 14 deletions.
18 changes: 18 additions & 0 deletions py3dtilers/Common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,24 @@ Project the features on another CRS. The `crs_in` flag allows to specify the inp
<tiler> <input> --crs_in EPSG:3946 --crs_out EPSG:4171
```

### No Normals

| Tiler | |
| ------------ | ------------------ |
| CityTiler | :heavy_check_mark: |
| ObjTiler | :heavy_check_mark: |
| GeojsonTiler | :heavy_check_mark: |
| IfcTiler | :heavy_check_mark: |
| TilesetTiler | :heavy_check_mark: |

Optionally disable creation of normals in 3D tiles:

```bash
<tiler> <input> --no_normals
```

This could be very useful for 3D tiles created out Photogrammetry OBJ meshes. If normals are not present, Cesium wil display tiles using flat lighning.

### With texture

| Tiler | |
Expand Down
8 changes: 7 additions & 1 deletion py3dtilers/Common/tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ def __init__(self):
dest='with_texture',
action='store_true',
help='Adds texture to 3DTiles when defined')

self.parser.add_argument('--no_normals',
dest='no_normals',
action='store_true',
help='If specified, no normals will be written to glTf, useful for Photogrammetry meshes')

self.parser.add_argument('--quality',
nargs='?',
Expand Down Expand Up @@ -244,11 +249,12 @@ def create_tileset_from_groups(self, groups: Groups, extension_name=None):
"""
create_loa = self.args.loa is not None
geometric_errors = self.args.geometric_error if hasattr(self.args, 'geometric_error') else [None, None, None]
with_normals = False if self.args.no_normals else True

tree = LodTree(groups, self.args.lod1, create_loa, self.args.with_texture, geometric_errors, self.args.texture_lods)

self.create_output_directory()
return FromGeometryTreeToTileset.convert_to_tileset(tree, self.args, extension_name, self.get_output_dir())
return FromGeometryTreeToTileset.convert_to_tileset(tree, self.args, extension_name, self.get_output_dir(), with_normals=with_normals)

def create_output_directory(self):
"""
Expand Down
14 changes: 7 additions & 7 deletions py3dtilers/Common/tileset_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class FromGeometryTreeToTileset():
nb_nodes = 0

@staticmethod
def convert_to_tileset(geometry_tree: 'GeometryTree', user_arguments=None, extension_name=None, output_dir=None):
def convert_to_tileset(geometry_tree: 'GeometryTree', user_arguments=None, extension_name=None, output_dir=None, with_normals=True):
"""
Recursively creates a tileset from the nodes of a GeometryTree
:param geometry_tree: an instance of GeometryTree to transform into 3DTiles.
Expand All @@ -39,7 +39,7 @@ def convert_to_tileset(geometry_tree: 'GeometryTree', user_arguments=None, exten
root_node = geometry_tree.root_nodes[0]
root_node.set_node_features_geometry(user_arguments)
offset = FromGeometryTreeToTileset.__transform_node(root_node, user_arguments, tree_centroid, obj_writer=obj_writer)
tileset.add_tile(FromGeometryTreeToTileset.__create_tile(root_node, offset, extension_name, output_dir))
tileset.add_tile(FromGeometryTreeToTileset.__create_tile(root_node, offset, extension_name, output_dir, with_normals))
geometry_tree.root_nodes.remove(root_node)

if user_arguments.obj is not None:
Expand Down Expand Up @@ -84,7 +84,7 @@ def __transform_node(node: 'GeometryNode', user_args, tree_centroid=np.array([0,
return distance if user_args.offset[0] == 'centroid' else offset

@staticmethod
def __create_tile(node: 'GeometryNode', transform_offset, extension_name=None, output_dir=None):
def __create_tile(node: 'GeometryNode', transform_offset, extension_name=None, output_dir=None, with_normals=True):
"""
Create a tile from a node. Recursively create tiles from the children of the node.
:param node: the GeometryNode.
Expand All @@ -98,7 +98,7 @@ def __create_tile(node: 'GeometryNode', transform_offset, extension_name=None, o
tile = Tile()
tile.set_geometric_error(node.geometric_error)

content_b3dm = FromGeometryTreeToTileset.__create_tile_content(feature_list, extension_name, node.has_texture(), node.downsample_factor)
content_b3dm = FromGeometryTreeToTileset.__create_tile_content(feature_list, extension_name, node.has_texture(), node.downsample_factor, with_normals)
tile.set_content(content_b3dm)
tile.set_content_uri('tiles/' + f'{FromGeometryTreeToTileset.tile_index}.b3dm')
tile.write_content(output_dir)
Expand All @@ -125,12 +125,12 @@ def __create_tile(node: 'GeometryNode', transform_offset, extension_name=None, o

FromGeometryTreeToTileset.tile_index += 1
for child_node in node.child_nodes:
tile.add_child(FromGeometryTreeToTileset.__create_tile(child_node, [0., 0., 0.], extension_name, output_dir))
tile.add_child(FromGeometryTreeToTileset.__create_tile(child_node, [0., 0., 0.], extension_name, output_dir, with_normals))

return tile

@staticmethod
def __create_tile_content(feature_list: 'FeatureList', extension_name=None, with_texture=False, downsample_factor=1):
def __create_tile_content(feature_list: 'FeatureList', extension_name=None, with_texture=False, downsample_factor=1, with_normals=True):
"""
:param pre_tile: an array containing features of a single tile
Expand Down Expand Up @@ -174,7 +174,7 @@ def __create_tile_content(feature_list: 'FeatureList', extension_name=None, with
0, 1, 0, 0,
0, 0, 0, 1])

gltf = GlTF.from_binary_arrays(arrays, transform, materials=materials)
gltf = GlTF.from_binary_arrays(arrays, transform, materials=materials, withNormals=with_normals)

# Create a batch table and add the ID of each feature to it
ids = [feature.get_id() for feature in feature_list]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cityTemporalTiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def get_default_namespace():
crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1,
output_dir=None, geometric_error=[None, None, None],
split_surfaces=False, add_color=False, kd_tree_max=None, texture_lods=0,
keep_ids=[], exclude_ids=[])
keep_ids=[], exclude_ids=[], no_normals=False)


class Test_Tile(unittest.TestCase):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cityTiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def get_default_namespace():
crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1,
output_dir=None, geometric_error=[None, None, None],
split_surfaces=False, add_color=False, kd_tree_max=None, ids=[], texture_lods=0,
keep_ids=[], exclude_ids=[])
keep_ids=[], exclude_ids=[], no_normals=False)


class Test_Tile(unittest.TestCase):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_geojsonTiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def get_default_namespace():
return Namespace(obj=None, loa=None, lod1=False, crs_in='EPSG:3946',
crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1,
output_dir=None, geometric_error=[None, None, None], kd_tree_max=None,
texture_lods=0, keep_ids=[], exclude_ids=[])
texture_lods=0, keep_ids=[], exclude_ids=[], no_normals=False)


class Test_Tile(unittest.TestCase):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ifcTiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
def get_default_namespace():
return Namespace(obj=None, loa=None, lod1=False, crs_in='EPSG:3946',
crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, grouped_by='IfcTypeObject', scale=1,
output_dir=None, geometric_error=[None, None, None], kd_tree_max=None, texture_lods=0, keep_ids=[], exclude_ids=[])
output_dir=None, geometric_error=[None, None, None], kd_tree_max=None, texture_lods=0, keep_ids=[], exclude_ids=[], no_normals=False)


class Test_Tile(unittest.TestCase):
Expand Down
13 changes: 12 additions & 1 deletion tests/test_objTiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def get_default_namespace():
return Namespace(obj=None, loa=None, lod1=False, crs_in='EPSG:3946',
crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1,
output_dir=None, geometric_error=[None, None, None], kd_tree_max=None,
texture_lods=0, keep_ids=[], exclude_ids=[])
texture_lods=0, keep_ids=[], exclude_ids=[], no_normals=False)


class Test_Tile(unittest.TestCase):
Expand All @@ -24,6 +24,17 @@ def test_basic_case(self):
if tileset is not None:
tileset.write_as_json(Path(obj_tiler.args.output_dir))

def test_basic_case_no_normals(self):
obj_tiler = ObjTiler()
obj_tiler.files = [Path('tests/obj_tiler_data/Cube/cube_1.obj'), Path('tests/obj_tiler_data/Cube/cube_2.obj')]
obj_tiler.args = get_default_namespace()
obj_tiler.args.output_dir = Path("tests/obj_tiler_data/generated_tilesets/basic_case_no_normals")
obj_tiler.args.no_normals = True

tileset = obj_tiler.from_obj_directory()
if tileset is not None:
tileset.write_as_json(Path(obj_tiler.args.output_dir))

def test_texture(self):
obj_tiler = ObjTiler()
obj_tiler.files = [Path('tests/obj_tiler_data/TexturedCube/cube.obj')]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def get_default_namespace():
return Namespace(obj=None, loa=None, lod1=False, crs_in='EPSG:3946',
crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1,
output_dir=None, geometric_error=[None, None, None], kd_tree_max=None,
texture_lods=0, keep_ids=[], exclude_ids=[])
texture_lods=0, keep_ids=[], exclude_ids=[], no_normals=False)


triangles = [[np.array([1843366, 5174473, 200]),
Expand Down

0 comments on commit 87b06c2

Please sign in to comment.