Initial import.
This commit is contained in:
commit
2ecc14f46c
|
@ -0,0 +1,4 @@
|
||||||
|
/build/
|
||||||
|
/cmake-build-*/
|
||||||
|
/stage/
|
||||||
|
/package/
|
|
@ -0,0 +1,67 @@
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
cmake_policy(SET CMP0048 NEW)
|
||||||
|
|
||||||
|
|
||||||
|
## CONFIG
|
||||||
|
|
||||||
|
project(wrnav VERSION 0.0.1 LANGUAGES CXX)
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
add_compile_options(-Werror -Wall -g -O0)
|
||||||
|
|
||||||
|
if(DEFINED ENV{CMAKE_GCOV})
|
||||||
|
add_compile_options(-fprofile-arcs -ftest-coverage)
|
||||||
|
# Need CMake 3.15+.
|
||||||
|
add_link_options(-fprofile-arcs -ftest-coverage)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
pkg_search_module(GTEST REQUIRED gtest_main)
|
||||||
|
|
||||||
|
include_directories(include)
|
||||||
|
|
||||||
|
|
||||||
|
## BUILD
|
||||||
|
|
||||||
|
# add_library(LIBNAME
|
||||||
|
# SOURCES
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# add_executable(EXE_NAME MAIN)
|
||||||
|
# target_link_libraries(EXE_NAME LIBNAMES)
|
||||||
|
# add_dependencies(EXE_NAME LIBNAMES)
|
||||||
|
|
||||||
|
|
||||||
|
## INSTALL
|
||||||
|
|
||||||
|
install(DIRECTORY include/${PROJECT_NAME}
|
||||||
|
DESTINATION include/
|
||||||
|
FILES_MATCHING PATTERN "*.h")
|
||||||
|
|
||||||
|
|
||||||
|
## TEST
|
||||||
|
|
||||||
|
# From Modern CMake:
|
||||||
|
# https://cliutils.gitlab.io/modern-cmake/chapters/testing/googletest.html
|
||||||
|
include(CTest)
|
||||||
|
set(TEST_EXECS)
|
||||||
|
macro(package_add_gtest TESTNAME)
|
||||||
|
add_executable(${TESTNAME} ${ARGN})
|
||||||
|
target_link_libraries(${TESTNAME} ${GTEST_LDFLAGS})
|
||||||
|
target_compile_options(${TESTNAME} PUBLIC ${GTEST_CFLAGS})
|
||||||
|
add_test(NAME ${TESTNAME} COMMAND ${TESTNAME})
|
||||||
|
set_target_properties(${TESTNAME} PROPERTIES
|
||||||
|
FOLDER tests
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY tests)
|
||||||
|
list(APPEND TEST_EXECS ${TESTNAME})
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
package_add_gtest(vector_test test/vector_test.cc)
|
||||||
|
|
||||||
|
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --verbose DEPENDS ${TEST_EXECS})
|
||||||
|
|
||||||
|
|
||||||
|
## DEPLOY
|
||||||
|
|
||||||
|
include(CMakePack.txt)
|
|
@ -0,0 +1,18 @@
|
||||||
|
# build a CPack driven installer package
|
||||||
|
include(InstallRequiredSystemLibraries)
|
||||||
|
set(CPACK_RESOURCE_FILE_LICENSE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt")
|
||||||
|
set(CPACK_PACKAGE_VERSION_MAJOR "${${PROJECT_NAME}_VERSION_MAJOR}")
|
||||||
|
set(CPACK_PACKAGE_VERSION_MINOR "${${PROJECT_NAME}_VERSION_MINOR}")
|
||||||
|
|
||||||
|
# Debian settings
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "K. Isom")
|
||||||
|
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The Shimmering Clarity C++ library")
|
||||||
|
set(CPACK_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION})
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc++1 (>= 3.7.0-1)")
|
||||||
|
# set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_SECTION devel)
|
||||||
|
|
||||||
|
# actually do the thing
|
||||||
|
set(CPACK_GENERATOR DEB)
|
||||||
|
include (CPack)
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright 2019 Kyle Isom <kyle@imap.cc>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,180 @@
|
||||||
|
#ifndef __WRNAV_GEOM_VECTOR_H
|
||||||
|
#define __WRNAV_GEOM_VECTOR_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <ostream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <wrnav/util/math.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace wr {
|
||||||
|
namespace geom {
|
||||||
|
|
||||||
|
template <typename T, size_t N>
|
||||||
|
class Vector {
|
||||||
|
public:
|
||||||
|
Vector() { wr::util::DefaultEpsilon(this->epsilon); }
|
||||||
|
|
||||||
|
Vector(std::initializer_list<T> ilst)
|
||||||
|
{
|
||||||
|
assert(ilst.size() == N);
|
||||||
|
|
||||||
|
wr::util::DefaultEpsilon(this->epsilon);
|
||||||
|
std::copy(ilst.begin(), ilst.end(), this->arr.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
T magnitude() const {
|
||||||
|
T result = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N; i++) {
|
||||||
|
result += (this->arr[i] * this->arr[i]);
|
||||||
|
}
|
||||||
|
return std::sqrt(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
setEpsilon(T epsilon)
|
||||||
|
{
|
||||||
|
this->epsilon = epsilon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
isZero() const
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < N; i++) {
|
||||||
|
if (!wr::util::WithinTolerance(this->arr[i], 0.0, this->epsilon)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vector
|
||||||
|
unitVector() const
|
||||||
|
{
|
||||||
|
return *this / this->magnitude();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
T
|
||||||
|
angle(const Vector<T, N> &rhs) const
|
||||||
|
{
|
||||||
|
Vector<T, N> unitA = this->unitVector();
|
||||||
|
Vector<T, N> unitB = rhs.unitVector();
|
||||||
|
|
||||||
|
return std::acos(unitA * unitB);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vector operator+(const Vector<T, N> &rhs) const {
|
||||||
|
Vector<T, N> vec;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N; i++) {
|
||||||
|
vec.arr[i] = this->arr[i] + rhs.arr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vector operator-(const Vector<T, N> &rhs) const {
|
||||||
|
Vector<T, N> vec;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N; i++) {
|
||||||
|
vec.arr[i] = this->arr[i] - rhs.arr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Scalar multiplication.
|
||||||
|
Vector operator*(const T k) const {
|
||||||
|
Vector<T, N> vec;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N; i++) {
|
||||||
|
vec.arr[i] = this->arr[i] * k;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Scalar division.
|
||||||
|
Vector operator/(const T k) const {
|
||||||
|
Vector<T, N> vec;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N; i++) {
|
||||||
|
vec.arr[i] = this->arr[i] / k;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Dot product.
|
||||||
|
T operator*(const Vector<T, N> &rhs) const {
|
||||||
|
T result = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < N; i++) {
|
||||||
|
result += (this->arr[i] * rhs.arr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool operator==(const Vector<T, N> &rhs) const {
|
||||||
|
for (size_t i = 0; i<N; i++) {
|
||||||
|
if (!wr::util::WithinTolerance(this->arr[i], rhs.arr[i], this->epsilon)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool operator!=(const Vector<T, N> &rhs) const {
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& outs, const Vector<T, N>& vec) {
|
||||||
|
outs << "<";
|
||||||
|
for (size_t i = 0; i < N; i++) {
|
||||||
|
outs << vec.arr[i];
|
||||||
|
if (i < (N-1)) {
|
||||||
|
outs << ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outs << ">";
|
||||||
|
return outs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const size_t dim = N;
|
||||||
|
T epsilon;
|
||||||
|
std::array<T, N> arr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
typedef Vector<float, 3> Vector3f;
|
||||||
|
typedef Vector<float, 4> Vector4f;
|
||||||
|
typedef Vector<double, 3> Vector3d;
|
||||||
|
typedef Vector<double, 4> Vector4d;
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace geom
|
||||||
|
} // namespace wr
|
||||||
|
|
||||||
|
|
||||||
|
#endif // __WRNAV_GEOM_VECTOR_H
|
|
@ -0,0 +1,39 @@
|
||||||
|
#ifndef __WRNAV_UTIL_MATH_H
|
||||||
|
#define __WRNAV_UTIL_MATH_H
|
||||||
|
|
||||||
|
|
||||||
|
namespace wr {
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
|
||||||
|
const double Epsilon_double = 0.0001;
|
||||||
|
const float Epsilon_float = 0.0001;
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
DefaultEpsilon(double &epsilon)
|
||||||
|
{
|
||||||
|
epsilon = Epsilon_double;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
DefaultEpsilon(float &epsilon)
|
||||||
|
{
|
||||||
|
epsilon = Epsilon_float;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T
|
||||||
|
WithinTolerance(T a, T b, T epsilon)
|
||||||
|
{
|
||||||
|
return std::abs(a - b) < epsilon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
} // namespace wr
|
||||||
|
|
||||||
|
|
||||||
|
#endif // __WRNAV_UTIL_MATH_H
|
|
@ -0,0 +1,196 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <wrnav/geom/vector.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace wr;
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3FloatTests, Magnitude)
|
||||||
|
{
|
||||||
|
geom::Vector3f v3f {1.0, -2.0, 3.0};
|
||||||
|
const float expected = 3.74165738677394;
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(v3f.magnitude(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3FloatTests, Equality)
|
||||||
|
{
|
||||||
|
geom::Vector3f a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3f b {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3f c {1.0, 2.0, 1.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(a, b);
|
||||||
|
EXPECT_EQ(b, a);
|
||||||
|
EXPECT_NE(a, c);
|
||||||
|
EXPECT_NE(b, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3FloatTests, Addition)
|
||||||
|
{
|
||||||
|
geom::Vector3f a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3f b {4.0, 5.0, 6.0};
|
||||||
|
geom::Vector3f expected {5.0, 7.0, 9.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(a+b, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3FloatTests, Subtraction)
|
||||||
|
{
|
||||||
|
geom::Vector3f a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3f b {4.0, 5.0, 6.0};
|
||||||
|
geom::Vector3f c {5.0, 7.0, 9.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(c-b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3FloatTests, ScalarMultiplication)
|
||||||
|
{
|
||||||
|
geom::Vector3f a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3f expected {3.0, 6.0, 9.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(a * 3.0, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3FloatTests, ScalarDivision)
|
||||||
|
{
|
||||||
|
geom::Vector3f a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3f b {3.0, 6.0, 9.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(b / 3.0, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3FloatTests, DotProduct)
|
||||||
|
{
|
||||||
|
geom::Vector3f a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3f b {4.0, 5.0, 6.0};
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(a * b, 32.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3FloatTests, UnitVector)
|
||||||
|
{
|
||||||
|
// Test values randomly generated and calculated with numpy.
|
||||||
|
geom::Vector3f vec3 {5.320264018493507, 5.6541812891273935, 1.9233435162644652};
|
||||||
|
geom::Vector3f unit {0.6651669556972103, 0.7069150218815566, 0.24046636539587804};
|
||||||
|
|
||||||
|
EXPECT_EQ(vec3.unitVector(), unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3FloatTests, Angle)
|
||||||
|
{
|
||||||
|
geom::Vector3f a {0.3977933061361172, 8.053980094436525, 8.1287759943773};
|
||||||
|
geom::Vector3f b {9.817895298608196, 4.034166890407462, 4.37628316513266};
|
||||||
|
geom::Vector3f c {7.35, 0.221, 5.188};
|
||||||
|
geom::Vector3f d {2.751, 8.259, 3.985};
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(a.angle(b), 0.9914540426033251);
|
||||||
|
EXPECT_NEAR(c.angle(d), 1.052, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3DoubleTests, Magnitude)
|
||||||
|
{
|
||||||
|
geom::Vector3d v3d{1.0, -2.0, 3.0};
|
||||||
|
const double expected = 3.74165738677394;
|
||||||
|
|
||||||
|
EXPECT_DOUBLE_EQ(v3d.magnitude(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3DoubleTests, Equality)
|
||||||
|
{
|
||||||
|
geom::Vector3d a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3d b {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3d c {1.0, 2.0, 1.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(a, b);
|
||||||
|
EXPECT_EQ(b, a);
|
||||||
|
EXPECT_NE(a, c);
|
||||||
|
EXPECT_NE(b, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3DoubleTests, Addition)
|
||||||
|
{
|
||||||
|
geom::Vector3d a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3d b {4.0, 5.0, 6.0};
|
||||||
|
geom::Vector3d expected {5.0, 7.0, 9.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(a+b, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3DoubleTests, Subtraction)
|
||||||
|
{
|
||||||
|
geom::Vector3d a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3d b {4.0, 5.0, 6.0};
|
||||||
|
geom::Vector3d c {5.0, 7.0, 9.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(c-b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3DoubleTests, ScalarMultiplication)
|
||||||
|
{
|
||||||
|
geom::Vector3d a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3d expected {3.0, 6.0, 9.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(a * 3.0, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3DoubleTests, ScalarDivision)
|
||||||
|
{
|
||||||
|
geom::Vector3d a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3d b {3.0, 6.0, 9.0};
|
||||||
|
|
||||||
|
EXPECT_EQ(b / 3.0, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3DoubleTests, DotProduct)
|
||||||
|
{
|
||||||
|
geom::Vector3d a {1.0, 2.0, 3.0};
|
||||||
|
geom::Vector3d b {4.0, 5.0, 6.0};
|
||||||
|
|
||||||
|
EXPECT_DOUBLE_EQ(a * b, 32.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3DoubleTests, UnitVector)
|
||||||
|
{
|
||||||
|
// Test values randomly generated and calculated with numpy.
|
||||||
|
geom::Vector3d vec3 {5.320264018493507, 5.6541812891273935, 1.9233435162644652};
|
||||||
|
geom::Vector3d unit {0.6651669556972103, 0.7069150218815566, 0.24046636539587804};
|
||||||
|
|
||||||
|
EXPECT_EQ(vec3.unitVector(), unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(Vector3DoubleTests, Angle)
|
||||||
|
{
|
||||||
|
geom::Vector3d a {0.3977933061361172, 8.053980094436525, 8.1287759943773};
|
||||||
|
geom::Vector3d b {9.817895298608196, 4.034166890407462, 4.37628316513266};
|
||||||
|
geom::Vector3d c {7.35, 0.221, 5.188};
|
||||||
|
geom::Vector3d d {2.751, 8.259, 3.985};
|
||||||
|
|
||||||
|
EXPECT_DOUBLE_EQ(a.angle(b), 0.9914540426033251);
|
||||||
|
EXPECT_NEAR(c.angle(d), 1.052, 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
Loading…
Reference in New Issue