Skip to content

Commit 13884f3

Browse files
authored
Merge pull request #2 from kylebarron/pybind11
2 parents 0f1463c + bad324d commit 13884f3

19 files changed

+11073
-46
lines changed

pydelatin/__init__.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,2 @@
1-
"""Top-level package for pydelatin."""
21

3-
__author__ = """Kyle Barron"""
4-
__email__ = 'kylebarron2@gmail.com'
5-
__version__ = '0.1.0'
6-
7-
from .delatin import Delatin
2+
from .wrapper import Pydelatin

pydelatin/wrapper.py

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from typing import Optional
2+
import numpy as np
3+
from _pydelatin import PydelatinTriangulator
4+
5+
6+
class Pydelatin:
7+
def __init__(
8+
self,
9+
arr: np.ndarray,
10+
z_scale,
11+
z_exag: float = 1,
12+
max_error: float = 0.001,
13+
max_triangles: Optional[int] = None,
14+
max_points: Optional[int] = None,
15+
base_height: float = 0,
16+
level: bool = False,
17+
invert: bool = False,
18+
blur: int = 0,
19+
gamma: float = 0,
20+
border_size: int = 0,
21+
border_height: float = 1):
22+
"""
23+
24+
Args:
25+
- arr: data array
26+
- z_scale: z scale relative to x & y
27+
- z_exag: z exaggeration
28+
- max_error: maximum triangulation error
29+
- max_triangles: maximum number of triangles
30+
- max_points: maximum number of vertices
31+
- base_height: solid base height
32+
- level: auto level input to full grayscale range
33+
- invert: invert heightmap
34+
- blur: gaussian blur sigma
35+
- gamma: gamma curve exponent
36+
- border_size: border size in pixels
37+
- border_height: border z height
38+
"""
39+
super(Pydelatin, self).__init__()
40+
41+
max_triangles = max_triangles if max_triangles is not None else 0
42+
max_points = max_points if max_points is not None else 0
43+
44+
height, width = arr.shape
45+
self.tri = PydelatinTriangulator(
46+
arr.flatten(), width, height, max_error, z_scale, z_exag,
47+
max_triangles, max_points, level, invert, blur, gamma, border_size,
48+
border_height, base_height)
49+
self.tri.run()
50+
51+
@property
52+
def vertices(self):
53+
return self.tri.getPoints()
54+
55+
@property
56+
def triangles(self):
57+
return self.tri.getTriangles()

setup.py

+113-40
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,124 @@
1-
"""Setup for pydelatin."""
2-
from pathlib import Path
1+
from setuptools import find_packages, setup, Extension
2+
from setuptools.command.build_ext import build_ext
3+
import sys
4+
import setuptools
35

4-
import numpy as np
5-
# setuptools must be before Cython
6-
from setuptools import find_packages, setup
7-
from Cython.Build import cythonize
6+
__version__ = '0.1.0'
87

9-
with open("README.md") as f:
10-
readme = f.read()
118

12-
# Runtime requirements.
13-
inst_reqs = ["numpy"]
9+
class get_pybind_include(object):
10+
"""Helper class to determine the pybind11 include path
1411
15-
extra_reqs = {
16-
"test": ["pytest", "pytest-benchmark", "imageio"], }
12+
The purpose of this class is to postpone importing pybind11
13+
until it is actually installed, so that the ``get_include()``
14+
method can be invoked. """
1715

16+
def __str__(self):
17+
import pybind11
18+
return pybind11.get_include()
1819

19-
# Ref https://suzyahyah.github.io/cython/programming/2018/12/01/Gotchas-in-Cython.html
20-
def find_pyx(path='.'):
21-
return list(map(str, Path(path).glob('**/*.pyx')))
20+
21+
ext_modules = [
22+
Extension(
23+
'_pydelatin',
24+
# Sort input source files to ensure bit-for-bit reproducible builds
25+
# (https://github.com/pybind/python_example/pull/53)
26+
sorted([
27+
'src/base.cpp',
28+
'src/blur.cpp',
29+
'src/heightmap.cpp',
30+
'src/main.cpp',
31+
'src/triangulator.cpp',
32+
]),
33+
include_dirs=[
34+
# Path to pybind11 headers
35+
get_pybind_include(),
36+
],
37+
language='c++'
38+
),
39+
]
40+
41+
42+
# cf http://bugs.python.org/issue26689
43+
def has_flag(compiler, flagname):
44+
"""Return a boolean indicating whether a flag name is supported on
45+
the specified compiler.
46+
"""
47+
import tempfile
48+
import os
49+
with tempfile.NamedTemporaryFile('w', suffix='.cpp', delete=False) as f:
50+
f.write('int main (int argc, char **argv) { return 0; }')
51+
fname = f.name
52+
try:
53+
compiler.compile([fname], extra_postargs=[flagname])
54+
except setuptools.distutils.errors.CompileError:
55+
return False
56+
finally:
57+
try:
58+
os.remove(fname)
59+
except OSError:
60+
pass
61+
return True
62+
63+
64+
def cpp_flag(compiler):
65+
"""Return the -std=c++[11/14/17] compiler flag.
66+
67+
The newer version is prefered over c++11 (when it is available).
68+
"""
69+
flags = ['-std=c++17', '-std=c++14', '-std=c++11']
70+
71+
for flag in flags:
72+
if has_flag(compiler, flag):
73+
return flag
74+
75+
raise RuntimeError('Unsupported compiler -- at least C++11 support '
76+
'is needed!')
77+
78+
79+
class BuildExt(build_ext):
80+
"""A custom build extension for adding compiler-specific options."""
81+
c_opts = {
82+
'msvc': ['/EHsc'],
83+
'unix': [],
84+
}
85+
l_opts = {
86+
'msvc': [],
87+
'unix': [],
88+
}
89+
90+
if sys.platform == 'darwin':
91+
darwin_opts = ['-stdlib=libc++', '-mmacosx-version-min=10.7']
92+
c_opts['unix'] += darwin_opts
93+
l_opts['unix'] += darwin_opts
94+
95+
def build_extensions(self):
96+
ct = self.compiler.compiler_type
97+
opts = self.c_opts.get(ct, [])
98+
link_opts = self.l_opts.get(ct, [])
99+
if ct == 'unix':
100+
opts.append(cpp_flag(self.compiler))
101+
if has_flag(self.compiler, '-fvisibility=hidden'):
102+
opts.append('-fvisibility=hidden')
103+
104+
for ext in self.extensions:
105+
ext.define_macros = [('VERSION_INFO', '"{}"'.format(self.distribution.get_version()))]
106+
ext.extra_compile_args = opts
107+
ext.extra_link_args = link_opts
108+
build_ext.build_extensions(self)
22109

23110

24111
setup(
25-
name="pydelatin",
26-
version="0.1.0",
27-
description="A Python port of Martini for fast terrain mesh generation",
28-
long_description=readme,
29-
long_description_content_type="text/markdown",
30-
classifiers=[
31-
"Intended Audience :: Information Technology",
32-
"Intended Audience :: Science/Research",
33-
"License :: OSI Approved :: MIT License",
34-
"Programming Language :: Python :: 3.6",
35-
"Programming Language :: Python :: 3.7",
36-
"Programming Language :: Python :: 3.8",
37-
"Topic :: Scientific/Engineering :: GIS"],
38-
keywords="mesh heightmap elevation terrain numpy",
39-
author="Kyle Barron",
40-
author_email="kylebarron2@gmail.com",
41-
url="https://github.com/kylebarron/pydelatin",
42-
license="MIT",
43-
packages=find_packages(exclude=["ez_setup", "scripts", "examples", "test"]),
44-
include_package_data=True,
112+
name='pydelatin',
113+
version=__version__,
114+
author='Kyle Barron',
115+
author_email='kylebarron2@gmail.com',
116+
url='https://github.com/kylebarron/pydelatin',
117+
description='A wrapper for hmm',
118+
long_description='',
119+
packages=find_packages(include=['pydelatin', 'pydelatin.*']),
120+
ext_modules=ext_modules,
121+
setup_requires=['pybind11>=2.5.0'],
122+
cmdclass={'build_ext': BuildExt},
45123
zip_safe=False,
46-
install_requires=inst_reqs,
47-
extras_require=extra_reqs,
48-
ext_modules=cythonize(find_pyx(), language_level=3),
49-
# Include Numpy headers
50-
include_dirs=[np.get_include()],
51124
)

src/__init__.py.bak

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Top-level package for pydelatin."""
2+
3+
__author__ = """Kyle Barron"""
4+
__email__ = 'kylebarron2@gmail.com'
5+
__version__ = '0.1.0'
6+
7+
from .main import pydelatin

src/base.cpp

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#include "base.h"
2+
3+
#define GLM_ENABLE_EXPERIMENTAL
4+
5+
#include <glm/gtx/hash.hpp>
6+
#include <map>
7+
#include <unordered_map>
8+
9+
void AddBase(
10+
std::vector<glm::vec3> &points,
11+
std::vector<glm::ivec3> &triangles,
12+
const int w, const int h, const float z)
13+
{
14+
const int w1 = w - 1;
15+
const int h1 = h - 1;
16+
17+
std::map<int, float> x0s;
18+
std::map<int, float> x1s;
19+
std::map<int, float> y0s;
20+
std::map<int, float> y1s;
21+
std::unordered_map<glm::vec3, int> lookup;
22+
23+
// find points along each edge
24+
for (int i = 0; i < points.size(); i++) {
25+
const auto &p = points[i];
26+
bool edge = false;
27+
if (p.x == 0) {
28+
x0s[p.y] = p.z;
29+
edge = true;
30+
} else if (p.x == w1) {
31+
x1s[p.y] = p.z;
32+
edge = true;
33+
}
34+
if (p.y == 0) {
35+
y0s[p.x] = p.z;
36+
edge = true;
37+
} else if (p.y == h1) {
38+
y1s[p.x] = p.z;
39+
edge = true;
40+
}
41+
if (edge) {
42+
lookup[p] = i;
43+
}
44+
}
45+
46+
std::vector<std::pair<int, float>> sx0s(x0s.begin(), x0s.end());
47+
std::vector<std::pair<int, float>> sx1s(x1s.begin(), x1s.end());
48+
std::vector<std::pair<int, float>> sy0s(y0s.begin(), y0s.end());
49+
std::vector<std::pair<int, float>> sy1s(y1s.begin(), y1s.end());
50+
51+
const auto pointIndex = [&lookup, &points](
52+
const float x, const float y, const float z)
53+
{
54+
const glm::vec3 point(x, y, z);
55+
if (lookup.find(point) == lookup.end()) {
56+
lookup[point] = points.size();
57+
points.push_back(point);
58+
}
59+
return lookup[point];
60+
};
61+
62+
// compute base center point
63+
const int center = pointIndex(w * 0.5f, h * 0.5f, z);
64+
65+
// edge x = 0
66+
for (int i = 1; i < sx0s.size(); i++) {
67+
const int y0 = sx0s[i-1].first;
68+
const int y1 = sx0s[i].first;
69+
const float z0 = sx0s[i-1].second;
70+
const float z1 = sx0s[i].second;
71+
const int p00 = pointIndex(0, y0, z);
72+
const int p01 = pointIndex(0, y0, z0);
73+
const int p10 = pointIndex(0, y1, z);
74+
const int p11 = pointIndex(0, y1, z1);
75+
triangles.emplace_back(p01, p10, p00);
76+
triangles.emplace_back(p01, p11, p10);
77+
triangles.emplace_back(center, p00, p10);
78+
}
79+
80+
// edge x = w1
81+
for (int i = 1; i < sx1s.size(); i++) {
82+
const int y0 = sx1s[i-1].first;
83+
const int y1 = sx1s[i].first;
84+
const float z0 = sx1s[i-1].second;
85+
const float z1 = sx1s[i].second;
86+
const int p00 = pointIndex(w1, y0, z);
87+
const int p01 = pointIndex(w1, y0, z0);
88+
const int p10 = pointIndex(w1, y1, z);
89+
const int p11 = pointIndex(w1, y1, z1);
90+
triangles.emplace_back(p00, p10, p01);
91+
triangles.emplace_back(p10, p11, p01);
92+
triangles.emplace_back(center, p10, p00);
93+
}
94+
95+
// edge y = 0
96+
for (int i = 1; i < sy0s.size(); i++) {
97+
const int x0 = sy0s[i-1].first;
98+
const int x1 = sy0s[i].first;
99+
const float z0 = sy0s[i-1].second;
100+
const float z1 = sy0s[i].second;
101+
const int p00 = pointIndex(x0, 0, z);
102+
const int p01 = pointIndex(x0, 0, z0);
103+
const int p10 = pointIndex(x1, 0, z);
104+
const int p11 = pointIndex(x1, 0, z1);
105+
triangles.emplace_back(p00, p10, p01);
106+
triangles.emplace_back(p10, p11, p01);
107+
triangles.emplace_back(center, p10, p00);
108+
}
109+
110+
// edge y = h1
111+
for (int i = 1; i < sy1s.size(); i++) {
112+
const int x0 = sy1s[i-1].first;
113+
const int x1 = sy1s[i].first;
114+
const float z0 = sy1s[i-1].second;
115+
const float z1 = sy1s[i].second;
116+
const int p00 = pointIndex(x0, h1, z);
117+
const int p01 = pointIndex(x0, h1, z0);
118+
const int p10 = pointIndex(x1, h1, z);
119+
const int p11 = pointIndex(x1, h1, z1);
120+
triangles.emplace_back(p01, p10, p00);
121+
triangles.emplace_back(p01, p11, p10);
122+
triangles.emplace_back(center, p00, p10);
123+
}
124+
}

src/base.h

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
#include <glm/glm.hpp>
4+
#include <vector>
5+
6+
void AddBase(
7+
std::vector<glm::vec3> &points,
8+
std::vector<glm::ivec3> &triangles,
9+
const int w, const int h, const float z);

0 commit comments

Comments
 (0)