diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..394316f --- /dev/null +++ b/.clang-format @@ -0,0 +1,409 @@ +--- +Language: Cpp + +# 基于的编码规范, 可选: +# - LLVM: https://llvm.org/docs/CodingStandards.html +# - Google: https://google.github.io/styleguide/cppguide.html +# - Chromium: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/styleguide/styleguide.md +# - Mozilla: https://firefox-source-docs.mozilla.org/code-quality/coding-style/index.html +# - WebKit: https://www.webkit.org/coding/coding-style.html +# - Microsoft: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +# - GNU: https://www.gnu.org/prep/standards/standards.html +# - InheritParentConfig: 继承父目录的编码规范, 如果有的话, 不是一个真正的编码规范 +# - None: 不使用, 即自动配置, 也就是本文件中的自定义内容 +# BasedOnStyle: LLVM + +#访问声明符缩进 +AccessModifierOffset: -4 +# 开括号后的对齐(包括小括号/大括号/尖括号), 建议使用Align +# - Align: 对于开括号, 即在换行情况下, 换行的参数跟开括号对齐, 建议使用 +# - DontAlign: 不对于开括号, 即换行时使用配置的空格数 +# - AlwaysBreak: 永远换行, 即第一个参数都不允许粘连括号, 会强制换行, 换行后使用配置空格数对齐 +# - BlockIndent: 同AlwaysBreak, 多了一个操作: 如果参数不固定在同一行, 闭括号将在下一行 +AlignAfterOpenBracket: Align + +# - 结构休数组统一初始化对齐, 建议不配置, 没过多必要, 详见clang-format doc +# - None: 不做处理, 即保留开发者的代码 +# - Left: 左对齐 +# - Right: 右对齐 +AlignArrayOfStructures: None + +# 连续赋值语句的对齐,即多个赋值语句连续出现时的对齐策略配置, 包含多个子配置项 +AlignConsecutiveAssignments: + # 是否启用, 建议不启用 + Enabled: false + # 是否跨过空行, 即多个对齐语句中间有空行时, 是否跨过, 如果要开启连续赋值语句的配置, 建议为false + AcrossEmptyLines: false + # 同AcrossComments: 即是否跨过注释, 建议false + AcrossComments: false + # 是否跨过复合语句(包括空行及注释), 建议False + AlignCompound: false + # 是否(右)对齐赋值操作的操作符, 建议true + PadOperators: true + +# 同AlignConsecutiveAssignments, 表示连续位定义语句出现时, 是否需要对齐:符号, 位变量定义用得少, 可以不开启 +AlignConsecutiveBitFields: + # 是否启用, 建议不启用 + Enabled: false + # 同AlignConsecutiveAssignments + AcrossEmptyLines: false + # 同AlignConsecutiveAssignments + AcrossComments: false + # 只在AlignConsecutiveAssignments配置中有效, 自动生成的clang-format有此项, 忽略 + AlignCompound: false + # 只在AlignConsecutiveAssignments配置中有效, 自动生成的clang-format有此项, 忽略 + PadOperators: false + +# 是否对齐连续声明, 同AlignConsecutiveDeclarations +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + # 只在AlignConsecutiveAssignments配置中有效, 自动生成的clang-format有此项, 忽略 + AlignCompound: false + # 只在AlignConsecutiveAssignments配置中有效, 自动生成的clang-format有此项, 忽略 + PadOperators: false + +# 续行符(\\)对齐: +# - DontAlign: 不做操作 +# - Left: 尽可能向左对齐, 即最长一行代码为准 +# - Right: 跟开发都写的最远的\\对齐(即不会自动缩减你的空格), 建议使用这个 +AlignEscapedNewlines: Right + +# 在二元/一元表达式中的操作数对齐, 可选值: +# - DontAlign: 不做对齐, 在操作数换行后, 将使用ContinuationIndentWidth来对齐 +# - Align: 即换行时, 操作数(or 操作符加操作数)跟上一行的第一个操作数左对齐, 具体操作符要不要换行, 由BreakBeforeBinaryOperators配置决定 +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +# 允许短的函数放在同一行, 可选值: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All +AllowShortFunctionsOnASingleLine: All +# 允许lambda在一行中, 同上, 建议All +AllowShortLambdasOnASingleLine: All +# 是否将简单的if(else/else if)语句中的body跟if(else/else if)放置于同一行,可选值 +# - Never: 永远不, 建议值 +# - WithoutElse: 没有else/else if时, 允许 +# - OnlyFirstIf: 只有第一个if允许 +# - AllIfAndElse: 所有的if/else都允许 +AllowShortIfStatementsOnASingleLine: Never +# 是否允许loop语句体跟loop语句共行, true/false, 建议false +AllowShortLoopsOnASingleLine: false +# Deprecated, 废弃定义, 设置为None即可 +AlwaysBreakAfterDefinitionReturnType: None +# Return类型后是否换行, 诡异的定义, 请设置为None即可 +AlwaysBreakAfterReturnType: None +# 多常量字符串定义是, 是否在第一个字符串常量前换行, true/false, 建议false +AlwaysBreakBeforeMultilineStrings: false +# 模板声明换行风格, 可选值: +# - No: 永远不对开发者的风格作处理 +# - MultiLine: 建议值, 即仅在开发者写的模板声明(包括函数)跨越多行时, 进行换行, 否则维持原样 +# - Yes: 不管如何都进行分行, 不建议 +AlwaysBreakTemplateDeclarations: MultiLine +# 属性宏列表, 自定义, 用于语言扩展或静态分析注解, 可忽略 +AttributeMacros: + - __capability +# 函数调用时的参数(Arguments)是否放置于一行, false不放置, true强制一个调用参数一行, 建议false +BinPackArguments: false +# 函数定义参数(Parameters)是否放置于一行, 同BinPackArguments +BinPackParameters: false + +# 大括号换行 +BraceWrapping: + # 在case后的大括号是否换行 + AfterCaseLabel: true + # class后 + AfterClass: true + # 控制语句(if/for/while/switch/...)后是否换行 + # - Never: 永远不, 即永远将语句体的大括号放置于控制语句同一行 + # - MultiLine: 多行控制语句才进行换行 + # - Always: 永远换行, 建议 + AfterControlStatement: Always + # 下面比较容易理解, 不再作无意义的解释 + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +# 二元操作符前是否换行, 建议为None +BreakBeforeBinaryOperators: None +# 概念声明前是否换行, 建议Always +BreakBeforeConceptDeclarations: Always +# 大括号换行风格,Custom即可, 具体值可参考上方文档 +BreakBeforeBraces: Custom +# 继承列表括号前换行, false即可 +BreakBeforeInheritanceComma: false +# 是否将整个继承列表换行 +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +# 是否在构造函数初始化列表的,前换行 +BreakConstructorInitializersBeforeComma: false +# 继承列表换行风格, 使用BeforeComma适合 +BreakConstructorInitializers: BeforeComma +# Java注解相关, 跳过 +BreakAfterJavaFieldAnnotations: false +# 字面字符串是否换行, true +BreakStringLiterals: true +# 代码列字符上限 +ColumnLimit: 120 +# pragma注释 +CommentPragmas: '^ IWYU pragma:' +# 注释关键字对齐(const/volatile), 建议Leave +# - Leave: - 不改变开发者定义 +# - Left: 位于类型前 +# - Right: 位于类型后 +# - Custom: 自定义 +QualifierAlignment: Leave +# 未在文档中找到 +CompactNamespaces: false +# 构造函数初始化列表缩进, 建议0 +ConstructorInitializerIndentWidth: 0 +# 函数调用续行对齐, 建议4 +ContinuationIndentWidth: 4 +# C++11的统一初始化列表大括号风格, 建议true +Cpp11BracedListStyle: true +# 提取行结束符并标准化, 建议false, 不要进行分析及自动运用, 而是强制使用UseCRLF设定来做 +DeriveLineEnding: true +# 是否开启文件分析, 根据文件中的*/&使用情况更新clang-format设定, 在无法决定时, 使用PointerAlignment代替, 不建议开启 +DerivePointerAlignment: false +DisableFormat: false +# 访问限定后是否添加空行, 建议Never +EmptyLineAfterAccessModifier: Never +# 访问限定前是否要求空行, 建议LogicalBlock +EmptyLineBeforeAccessModifier: LogicalBlock +# 实验性的自动检测同行并进行操作, 建议false +ExperimentalAutoDetectBinPacking: false +# 是否打包构造函数初始化列表, 建议Never, 可选: +# - Never: 永远不做操作, 即一个参数一行 +# - BinPack: 两个参数一行 +# - CurrentLine: 所有参数放置于一行, 如果放不下, 就一个参数一行 +# - NextLine: 同CurrentLine有点像, 唯一不同就是如果放不行, 将剩余参数放置于下一行(即不自动一参一行) +PackConstructorInitializers: Never +BasedOnStyle: '' +# 废弃配置 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +# 废弃配置 +AllowAllConstructorInitializersOnNextLine: true +# 是否强制在namespace结尾增加 // namespace xxx, 建议为true +FixNamespaceComments: true +# 大于多少行namespace内的代码行时才在namespace结尾添加 // namespace xxx, 建议0,即无论如何都添加 +ShortNamespaceLines: 0 +# Macro宏 +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +#If宏 +IfMacros: + - KJ_IF_MAYBE +# include代码块操作, 前提是SortIncludes开启: +# - Preserve: 只对每个代码块排序 +# - Merge: 对所有代码块合并, 并在合并后排序 +# - Regroup: 对所有include块进行分析, 并重新分块, 不建议! +IncludeBlocks: Preserve +# Include Sort选项, 可选: +# - Never: 永远不, 建议 +# - CaseSensitive: 大小写敏感排序 +# - CaseInsensitive: 大小写不敏感排序 +SortIncludes: Never +# Include种类, 默认即可 +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +# 缩进访问控制 +IndentAccessModifiers: false +# 缩进case语句, 建议false +IndentCaseLabels: false +# 缩进case body, 建议true +IndentCaseBlocks: true +# 缩进goto标签 +IndentGotoLabels: true +# 预处理指示(PPD-PreProcessor Directive)缩进, 建议None +# - None: 不缩进 +# - AfterHash: #不缩进, #后面的指示缩进 +# - BeforeHash: #跟前缩进 +IndentPPDirectives: None +# extern "C"缩进, 建议AfterExternBlock +IndentExternBlock: AfterExternBlock +# 模板require是否缩进 +IndentRequiresClause: true +# 缩进宽度 +IndentWidth: 4 +# 函数名换行时, 是否缩进(即返回值跟名字不同行时), 建议false +IndentWrappedFunctionNames: false +# 是否在代码块中(if/else/for/do/while)强制插入大括号, 建议false +InsertBraces: false +# 是否强制插入拖尾的',', 建议为None +InsertTrailingCommas: None +# Java相关, 跳过 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +# 是否block开始前有一个empty line, 诡异, 直接false +KeepEmptyLinesAtTheStartOfBlocks: false +# 未找到定义 +LambdaBodyIndentation: Signature +# 宏开始的正则, 不使用 +MacroBlockBegin: '' +# 宏结束的正则, 不使用 +MacroBlockEnd: '' +# 空行保持, 建议为1 +MaxEmptyLinesToKeep: 1 +# Namespace内的对齐, 直接使用None即可, 即所有namespace内(包括内嵌的)都不indent +NamespaceIndentation: None +# Obj-C语言设置, 跳过 +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +# 罚分设定(根据你的"违规"值选择罚分少的) +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +# 指针对齐, 建议Right +PointerAlignment: Right +# 引用对齐, 可选: +# - Pointer: 使用'PointerAlignment'配置, 建议使用 +# - Left: Left +# - Right: Right +ReferenceAlignment: Pointer +# 预处理对齐宽度 +PPIndentWidth: -1 +# 是否允许clang-format尝试重新粘合注释(true/false), 不建议使用 +ReflowComments: false +# 是否移除多余的{}, 不建议 +RemoveBracesLLVM: false +# 模板中的require语句位置, 建议OwnLine +RequiresClausePosition: OwnLine +# 分隔不同定义块, 建议Always, 可选: +# - Leave - 不处理, 建议, 即由业务决定, 也可以使用Always +# - Always - 永远进行分隔 +# - Never: 永远 不进行, 不建议 +SeparateDefinitionBlocks: Leave +# Java项, 跳过 +SortJavaStaticImport: Before +# 排序using语句(true/false), 不建议开启 +SortUsingDeclarations: false +# C风格cast的类型括号后面是否增加space(true/false), 比较诡异, 建议false +SpaceAfterCStyleCast: false +# 逻辑非操作(!)后面是否加space(true/false), 比较诡异, 建议false +SpaceAfterLogicalNot: false +# template关键字后面是否加space(true/false), 建议true, 即template , 而不是template +SpaceAfterTemplateKeyword: true +# 赋值语句操作符前是否添加space(true/false), 建议true +SpaceBeforeAssignmentOperators: true +# case语句:前是否增加space(true/false), 建议false +SpaceBeforeCaseColon: false +# c++11的统一初始化列表的大括号中是否添加space(true/false), 建议false +SpaceBeforeCpp11BracedList: false +# 构造函数初始化列表:前是否加space(true/false), 建议false +SpaceBeforeCtorInitializerColon: false +# 继承列表的:前是否加space(true/false), 建议true +SpaceBeforeInheritanceColon: true +# 圆括号前是否增加空格: 建议只在控制语句的贺括号前增加, 即配置为ControlStatements即可 +SpaceBeforeParens: ControlStatements +# SpaceBeforeParens为Custom时使用 +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +# 指针修饰的space添加, 建议Default, 即使用PointerAlignment代替 +SpaceAroundPointerQualifiers: Default +# Loop关键字前前是否增加space, 建议true +SpaceBeforeRangeBasedForLoopColon: true +# 空body是否添加space, 建议true +SpaceInEmptyBlock: true +# 圆括号前是否增加space, 建议false, true太多影响代码紧凑 +SpaceInEmptyParentheses: false +# Trailing注释前的空格数, 建议1 +SpacesBeforeTrailingComments: 1 +# <>里面是否增加space, 不建议, 配置成Never即可 +SpacesInAngles: Never +# 条件语句()里面是否增加space, 不建议, 配置成Never即可 +SpacesInConditionalStatement: false +# 容器初始化列表[]/{}里面是否增加space, 不建议(跟C++11风格保持一致) +SpacesInContainerLiterals: false +# C风格的转换()里面是否增加space, 不建议 +SpacesInCStyleCastParentheses: false +# 行注释前的空格范围数量, 建议Maximum关闭, 设置成-1, 即//到你的注释内容前的空格数量至少是1, 至多是无穷 +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +# 贺括号内是否加space, false +SpacesInParentheses: false +# 中括号内是否加space, false +SpacesInSquareBrackets: false +# 大括号内是否加space, false +SpaceBeforeSquareBrackets: false +# 位定义:前后是否增加空格, 可选: +# - Both: 前后都添加 +# - Before: 只在前增加 +# - After: 只在后增加 +# - None: 不增加, 建议, 没有必要因为过多的space(s)影响代码紧凑 +BitFieldColonSpacing: None +# C++标准, Latest即可 +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +# Tab宽度, 建议4 +TabWidth: 4 +# 不使用CRLF, 强制关闭, 如果DeriveLineEnding为true却未自动决策出来, 此项用于fallback策略 +UseCRLF: false +# Tab使用, 没有必要使用, 直接Never +UseTab: Never +# 空格敏感宏列表 +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..83445c6 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,23 @@ +Checks: " + -*, + bugprone-*, + performance-*, + readability-*, + misc-*, + clang-analyzer-*, + cppcoreguidelines-*, + modernize-*, + + performance-unnecessary-value-param, + modernize-pass-by-value, + + -readability-identifier-length, + -modernize-use-trailing-return-type, + -cppcoreguidelines-magic-numbers, + -cppcoreguidelines-init-variables, + " + +WarningsAsErrors: '' +HeaderFilterRegex: '.*' +AnalyzeTemporaryDtors: false +FormatStyle: file \ No newline at end of file diff --git a/include/line.hpp b/include/line.hpp index f98c4dc..7385dfd 100644 --- a/include/line.hpp +++ b/include/line.hpp @@ -24,12 +24,14 @@ // real t; // }; -struct ClosestDescOnSeg { +struct ClosestDescOnSeg +{ real t; real dis; }; -class ILine { +class ILine +{ public: virtual ~ILine() = default; @@ -42,16 +44,26 @@ public: virtual ClosestDescOnSeg getClosestParam(const Vec3 &p) = 0; }; -class Polyline : public ILine { +const real DISC_ARC_ANGLE = std::numbers::pi * 0.125; + +class Polyline : public ILine +{ public: using Point = Vec3; Polyline(Pt3Array points, std::vector bugles, const Vec3 &normal, bool closed = false) - : _points(std::move(points)), _bugles(std::move(bugles)), _closed(closed), _normal(normal.normalize()) { + : _points(std::move(points)) + , _bugles(std::move(bugles)) + , _closed(closed) + , _normal(normal.normalize()) + { assert(_points.size() >= 2); - if (closed) { + if (closed) + { assert(_points.size() == _points.size()); - } else { + } + else + { assert(_points.size() - 1 == _points.size()); } circularArcs.resize(_bugles.size()); @@ -65,15 +77,18 @@ public: [[nodiscard]] bool isClosed() const { return _closed; } - struct CircularArc { + struct CircularArc + { Vec3 center; real radius; real theta; real h; - Vec3 u; // dir of OA - Vec3 v; // u X v = normal + Vec3 u; // dir of OA + Vec3 v; // u X v = normal + Vec3 inCircleDir; - PtBoundaryRelation inCircleCheck(const Vec3 &pt) const { + PtBoundaryRelation inCircleCheck(const Vec3 &pt) const + { real d = (pt - center).norm(); return d < radius ? Inside : d > radius ? Outside : OnBoundary; } @@ -88,25 +103,29 @@ private: std::vector circularArcs; public: - void initSegInfo() { - for (size_t i = 0; i < _bugles.size(); ++i) { + void initSegInfo() + { + for (size_t i = 0; i < _bugles.size(); ++i) + { const Point &A = _points[i]; const Point &B = _points[(i + 1) % _points.size()]; Vec3 ABHalf = (B - A) * 0.5; Vec3 ABNorm = ABHalf.normalize(); - Vec3 QONorm = _normal.cross(ABNorm) * (abs(_bugles[i]) > 1 ? -1 : 1); real theta = std::atan(_bugles[i]) * 4; + circularArcs[i].inCircleDir = _normal.cross(ABNorm) * (abs(_bugles[i]) > 1 ? -1 : 1); circularArcs[i].h = ABHalf.norm() * std::tan(theta * 0.5); - circularArcs[i].center = A + ABHalf + QONorm * circularArcs[i].h; + circularArcs[i].center = A + ABHalf + circularArcs[i].inCircleDir * circularArcs[i].h; circularArcs[i].theta = theta; circularArcs[i].radius = (circularArcs[i].center - A).norm(); circularArcs[i].u = (A - circularArcs[i].center).normalize(); - circularArcs[i].v = ABNorm.cross(circularArcs[i].u); + circularArcs[i].v = _normal.cross(circularArcs[i].u); } } - Vec3 eval(real param) override { - if (circularArcs.empty()) initSegInfo(); + Vec3 eval(real param) override + { + if (circularArcs.empty()) + initSegInfo(); int seg = static_cast(param); real tOnSeg = param - seg; const auto &arc = circularArcs[seg]; @@ -114,8 +133,10 @@ public: return arc.center + arc.radius * (arc.u * std::cos(phi) + arc.v * std::sin(phi)); } - Vec3 der1(real param) override { - if (circularArcs.empty()) initSegInfo(); + Vec3 der1(real param) override + { + if (circularArcs.empty()) + initSegInfo(); int seg = static_cast(param); real tOnSeg = param - seg; const auto &arc = circularArcs[seg]; @@ -123,8 +144,10 @@ public: return arc.radius * (arc.u * -std::sin(phi) + arc.v * std::cos(phi)); } - Vec3 der2(real param) override { - if (circularArcs.empty()) initSegInfo(); + Vec3 der2(real param) override + { + if (circularArcs.empty()) + initSegInfo(); int seg = static_cast(param); real tOnSeg = param - seg; const auto &arc = circularArcs[seg]; @@ -132,29 +155,36 @@ public: return -arc.radius * (arc.u * std::cos(phi) + arc.v * std::cos(phi)); } - ClosestDescOnSeg getClosestParam(const Vec3 &p) override { + ClosestDescOnSeg getClosestParam(const Vec3 &p) override + { real closestDis = std::numeric_limits::max(); real closestParam; - for (int i = 0; i < _bugles.size(); ++i) { + for (int i = 0; i < _bugles.size(); ++i) + { const Vec3 &A = _points[i]; const Vec3 &B = _points[(i + 1) % _points.size()]; const auto &arc = circularArcs[i]; real dis2Seg = segPtDist(p, A, B).dis; - if (dis2Seg - arc.h > closestDis) continue; - if ((A - p).norm() < closestDis) { + if (dis2Seg - arc.h > closestDis) + continue; + if ((A - p).norm() < closestDis) + { closestDis = (A - p).norm(); closestParam = i; } - if ((B - p).norm() < closestDis) { + if ((B - p).norm() < closestDis) + { closestDis = (B - p).norm(); closestParam = i + 1; } int segInsertedCnt = arc.theta / DISC_ARC_ANGLE; - for (int j = 0; j < segInsertedCnt; ++j) { + for (int j = 0; j < segInsertedCnt; ++j) + { real insertParam = i + j * DISC_ARC_ANGLE / arc.theta; const Vec3 insertPt = eval(insertParam); real dis2InsertPt = (p - insertPt).norm(); - if (dis2InsertPt < closestDis) { + if (dis2InsertPt < closestDis) + { closestDis = dis2InsertPt; closestParam = insertParam; } @@ -169,58 +199,77 @@ public: Vec3 qDer2 = der2(closestParam); real lDer1 = (q - p).dot(qDer1); int iter = 0; - while (abs(lDer1) > std::numeric_limits::epsilon() * 1e6) { - closestParam -= lDer1 / (qDer1.dot(qDer1) + (q - p).dot(qDer2)); // -der1 / der2 + while (abs(lDer1) > std::numeric_limits::epsilon() * 1e6) + { + closestParam -= lDer1 / (qDer1.dot(qDer1) + (q - p).dot(qDer2)); // -der1 / der2 q = eval(closestParam); qDer1 = der1(closestParam); qDer2 = der2(closestParam); lDer1 = (q - p).dot(qDer1); printf("After iter %d, dL is %lf\n", iter, lDer1); - if (closestParam < seg - std::numeric_limits::epsilon()) { + if (closestParam < seg - std::numeric_limits::epsilon()) + { closestParam = seg; closestDis = (_points[seg] - p).norm(); break; - } else if (closestParam > seg + 1 + std::numeric_limits::epsilon()) { + } + else if (closestParam > seg + 1 + std::numeric_limits::epsilon()) + { closestParam = seg + 1; closestDis = (_points[(seg + 1) % _points.size()] - p).norm(); break; } + closestDis = (q - p).norm(); + iter++; } return {closestParam, closestDis}; } const std::vector &getCircularArcs() const { return circularArcs; } - void print() const { - if (_closed) printf("Closed Polyline: \n"); - else printf("Open Polyline: \n"); + void print() const + { + if (_closed) + printf("Closed Polyline: \n"); + else + printf("Open Polyline: \n"); printf("Points: {\n"); - for (int i = 0; i < _points.size(); ++i) { + for (int i = 0; i < _points.size(); ++i) + { printf("<%lf, %lf, %lf>", _points[i].x(), _points[i].y(), _points[i].z()); - if (i != _points.size() - 1) printf(", "); + if (i != _points.size() - 1) + printf(", "); } std::cout << "}" << std::endl; printf("Bugles: {\n"); - for (int i = 0; i < _bugles.size(); ++i) { + for (int i = 0; i < _bugles.size(); ++i) + { printf("%lf", _bugles[i]); - if (i != _bugles.size() - 1) printf(", "); + if (i != _bugles.size() - 1) + printf(", "); } std::cout << "}" << std::endl; } -private: - const real DISC_ARC_ANGLE = std::numbers::pi * 0.125; - - static ClosestDescOnSeg segPtDist(const Vec3 &p, const Vec3 &A, const Vec3 &B) { + static ClosestDescOnSeg segPtDist(const Vec3 &p, const Vec3 &A, const Vec3 &B) + { Vec3 AB = B - A; Vec3 AP = p - A; real h = std::clamp(AP.dot(AB) / AB.dot(AB), 0., 1.); return {h, (AP - AB * h).norm()}; } + static ClosestDescOnSeg segPtDist(const Vec2 &p, const Vec2 &A, const Vec2 &B) + { + Vec2 AB = B - A; + Vec2 AP = p - A; + real h = std::clamp(AP.dot(AB) / AB.dot(AB), 0., 1.); + return {h, (AP - AB * h).norm()}; + } }; -class PolynomialLine : public ILine { +class PolynomialLine : public ILine +{ public: Vec3 eval(real param) override { return {}; }; diff --git a/include/solid.hpp b/include/solid.hpp index 260cf12..8c31453 100644 --- a/include/solid.hpp +++ b/include/solid.hpp @@ -5,60 +5,80 @@ #include #include -class ISolid { +class ISolid +{ public: virtual ~ISolid() = default; virtual real sdf(const Vec3 &p) = 0; }; -Vec2 get2DRepOf3DPt(const Vec3 &pt3D, const Vec3 &u, const Vec3 &v, const Vec3 &localO) { +Vec2 get2DRepOf3DPt(const Vec3 &pt3D, const Vec3 &u, const Vec3 &v, const Vec3 &localO) +{ Vec3 OP = pt3D - localO; return {OP.dot(u), OP.dot(v)}; } -class IExtrudedSolid : public ISolid { +Vec2 get2DRepOf3DDir(const Vec3 &dir, const Vec3 &u, const Vec3 &v) { return Vec2{dir.dot(u), dir.dot(v)}.normalize(); } + +class IExtrudedSolid : public ISolid +{ public: - Polyline _profile; + Polyline _profile; // TODO: may be replaced by const ref to profile real _rScale; public: - IExtrudedSolid(Polyline profile, real rScale) : _profile(std::move(profile)), _rScale(rScale) { + IExtrudedSolid(Polyline profile, real rScale) + : _profile(std::move(profile)) + , _rScale(rScale) + { } }; /** * calculate winding number of a point w.r.t. a segment ab */ -real unsignedWindingNumberSegment(const Vec3 &p, const Vec3 &a, const Vec3 &b, const Vec3 &refNormal) { +real unsignedWindingNumberSegment(const Vec3 &p, const Vec3 &a, const Vec3 &b, const Vec3 &refNormal) +{ Vec3 pa = a - p; Vec3 pb = b - p; - return std::acos(std::clamp(pa.dot(pb) / (pa.norm() * pb.norm()), static_cast(-1.), - static_cast(1.))) / (std::numbers::pi * 2); + return std::acos(std::clamp(pa.dot(pb) / (pa.norm() * pb.norm()), static_cast(-1.), static_cast(1.))) / + (std::numbers::pi * 2); } -class ExtrudedSolidPolyline : public IExtrudedSolid { +class ExtrudedSolidPolyline : public IExtrudedSolid +{ private: Polyline _axis; Pt2Array _localProfile2D; Pt2Array _localCircleCenter2D; + Pt2Array _localInCircleDir; public: - ExtrudedSolidPolyline(Polyline profile, Polyline axis, real rScale) : IExtrudedSolid(std::move(profile), rScale), - _axis(std::move(axis)) { + ExtrudedSolidPolyline(Polyline profile, Polyline axis, real rScale) + : IExtrudedSolid(std::move(profile), rScale) + , _axis(std::move(axis)) + { assert(_profile.isClosed()); // TODO: project profile at st point to 2D Vec3 T = _axis.der1(0).normalize(); Vec3 N = _axis.der2(0).normalize(); Vec3 B = T.cross(N); Vec3 Q = _axis.eval(0); - for (int i = 0; i < _profile.getPoints().size(); ++i) { - _localProfile2D.emplace_back(get2DRepOf3DPt(_profile.getPoints()[i] - Q, N, B, Q)); - _localCircleCenter2D.emplace_back(get2DRepOf3DPt(_profile.getCircularArcs()[i].center - Q, N, B, Q)); + int segCount = _profile.getPoints().size(); + _localProfile2D.resize(segCount); + _localInCircleDir.resize(segCount); + _localCircleCenter2D.resize(segCount); + for (int i = 0; i < segCount; ++i) + { + _localProfile2D[i] = get2DRepOf3DPt(_profile.getPoints()[i] - Q, N, B, Q); + _localCircleCenter2D[i] = get2DRepOf3DPt(_profile.getCircularArcs()[i].center - Q, N, B, Q); + _localInCircleDir[i] = get2DRepOf3DDir(_profile.getCircularArcs()[i].inCircleDir, N, B); } } - real sdf(const Vec3 &p) override { + real sdf(const Vec3 &p) override + { ClosestDescOnSeg closestDesc = _axis.getClosestParam(p); // TNB coordinate system auto t = closestDesc.t; @@ -67,14 +87,14 @@ public: Vec3 B = T.cross(N); Vec3 Q = _axis.eval(t); Vec3 QP = p - Q; - auto uv = get2DRepOf3DPt(QP, N, B, Q); - //test it + auto p2D = get2DRepOf3DPt(QP, N, B, Q); + // TODO: to test if p2D is in _localProfile2D + for (auto i = 0; i < _localProfile2D.size(); ++i) { } - // TODO: to test if uv is in _localProfile2D - for (auto i = 0; i < _localProfile2D.size(); ++i) { - } - return 0; + ClosestDescOnSeg closestDescOnProfile = distance2Profile2D(p2D); + // TODO: on的情况 + return closestDescOnProfile.dis * isPtInside2DProfile(p2D) ? 1 : -1; } private: @@ -84,99 +104,252 @@ private: * out + in = in * out + out = out */ - bool isInside2DPolyline(const Polyline& outline, const Vec3& p3D, const Vec2& p2D) { - assert(outline.isClosed()); - int intersectionCount = 0, segCount = outline.getBugles().size(); - int onSegIdx = -1; - constexpr int numRays = 3; // 射线数量 - int majorityIn = 0; // 在多边形内的射线计数 - int majorityOut = 0; // 在多边形外的射线计数 - - for (int rayIdx = 0; rayIdx < numRays && onSegIdx == -1; ++rayIdx) { - double angle = (2.0 * std::numbers::pi * rayIdx) / numRays; - Vec2 rayDir(cos(angle), sin(angle)); - int crossings = 0; - - for (int i = 0; i < segCount; ++i) { - const Vec2 &a = _localProfile2D[i]; - const Vec2 &b = _localProfile2D[(i + 1) % segCount]; - if (isPointOnSegment(p2D, a, b)) { - onSegIdx = i; - break; + bool isPtInside2DProfile(const Vec2 &p2D) + { + assert(_profile.isClosed()); + + int segCount = _profile.getBugles().size(); + // 先判断是否在outline上 + // 顺便判断点-扇的位置关系 + bool inFan = false; + int onLinesegButHasBugle = -1; + for (int i = 0; i < segCount; ++i) + { + const Vec2 &a = _localProfile2D[i]; + const Vec2 &b = _localProfile2D[(i + 1) % segCount]; + if (_profile.getBugles()[i] == 0) + { + //line segment + if (isPointOnSegment(p2D, a, b)) + { + return true; + } + continue; + } + if (isPointOnSegment(p2D, a, b)) + { + onLinesegButHasBugle = i; + break; + } + const auto &arc = _profile.getCircularArcs()[i]; + real po = (p2D - _localCircleCenter2D[i]).norm(); + if (po == arc.radius) + { + return true; + } + if (po < arc.radius && (p2D - a).dot(_localInCircleDir[i]) > 0) + { + inFan = true; + break; + } + } + + // 判断点-直线多边形的关系 + const auto ptInPolygon = [&](const Vec2 &p) { + int intersectionCount = 0; + // int onSegIdx = -1; + constexpr int numRays = 3; // 射线数量 + int majorityIn = 0; // 在多边形内的射线计数 + int majorityOut = 0; // 在多边形外的射线计数 + for (int rayIdx = 0; rayIdx < numRays; ++rayIdx) + { + double angle = (2.0 * std::numbers::pi * rayIdx) / numRays; + Vec2 rayDir(cos(angle), sin(angle)); + int crossings = 0; + + for (int i = 0; i < segCount; ++i) + { + const Vec2 &a = _localProfile2D[i]; + const Vec2 &b = _localProfile2D[(i + 1) % segCount]; + assert(isPointOnSegment(p, a, b)); + // if (isPointOnSegment(p2D, a, b)) + // { + // onSegIdx = i; + // break; + // } + // 使用向量方法计算射线和边的交点 + double dx1 = b[0] - a[0]; + double dy1 = b[1] - a[1]; + double dx2 = rayDir[0]; + double dy2 = rayDir[1]; + double determinant = dx1 * dy2 - dy1 * dx2; + + // 如果determinant为0,则射线和边平行,不计算交点 + if (isEqual(determinant, 0)) + continue; + + double t1 = ((p[0] - a[0]) * dy2 - (p[1] - a[1]) * dx2) / determinant; + double t2 = ((p[0] - a[0]) * dy1 - (p[1] - a[1]) * dx1) / determinant; + + // 检查交点是否在边上(0 <= t1 <= 1)且射线上(t2 >= 0) + if (t1 >= 0 && t1 <= 1 && t2 >= 0) + { + crossings++; + } } - // 使用向量方法计算射线和边的交点 - double dx1 = b[0] - a[0]; - double dy1 = b[1] - a[1]; - double dx2 = rayDir[0]; - double dy2 = rayDir[1]; - double determinant = dx1 * dy2 - dy1 * dx2; - - // 如果determinant为0,则射线和边平行,不计算交点 - if (isEqual(determinant, 0)) continue; - - double t1 = ((p2D[0] - a[0]) * dy2 - (p2D[1] - a[1]) * dx2) / determinant; - double t2 = ((p2D[0] - a[0]) * dy1 - (p2D[1] - a[1]) * dx1) / determinant; - - // 检查交点是否在边上(0 <= t1 <= 1)且射线上(t2 >= 0) - if (t1 >= 0 && t1 <= 1 && t2 >= 0) { - crossings++; + if (crossings % 2 == 0) + { + majorityOut++; + } + else + { + majorityIn++; } } - if (crossings % 2 == 0) { - majorityOut++; - } else { - majorityIn++; + return majorityIn > majorityOut; + }; + + if (onLinesegButHasBugle != -1) + { + // 需要特殊考虑的情况 + // 从p2D向inCircle方向前进一小步 + Vec2 samplePt = + p2D + _localCircleCenter2D[onLinesegButHasBugle] * std::numeric_limits::epsilon() * 1e6; + return !ptInPolygon(samplePt); // 取反 + } + return ptInPolygon(p2D) ^ inFan; + + // TODO: 返回on的情况 + } + + ClosestDescOnSeg distance2Profile2D(const Vec2 &p2D) + { + // TODO: 2D 下点到圆弧的距离应该可以直接算,不用这么迭代! + assert(_profile.isClosed()); + real closestDis = std::numeric_limits::max(); + real closestParam; + int segCount = _profile.getBugles().size(); + for (int i = 0; i < segCount; ++i) + { + const Vec2 &a = _localProfile2D[i]; + const Vec2 &b = _localProfile2D[(i + 1) % segCount]; + const auto &arc = _profile.getCircularArcs()[i]; + real dis2Seg = Polyline::segPtDist(p2D, a, b).dis; + if (dis2Seg - arc.h > closestDis) + continue; + if ((a - p2D).norm() < closestDis) + { + closestDis = (a - p2D).norm(); + closestParam = i; + } + if ((b - p2D).norm() < closestDis) + { + closestDis = (b - p2D).norm(); + closestParam = i + 1; + } + int segInsertedCnt = arc.theta / DISC_ARC_ANGLE; + for (int j = 0; j < segInsertedCnt; ++j) + { + real insertParam = i + j * DISC_ARC_ANGLE / arc.theta; + real dis2InsertPt = (p2D - eval(insertParam)).norm(); + if (dis2InsertPt < closestDis) + { + closestDis = dis2InsertPt; + closestParam = insertParam; + } } } - // 判断是否在扇内 - bool inFan = false; - for (int i = 0; i < segCount; ++i) { - const Vec2& a = _localProfile2D[i]; - const Vec2& b = _localProfile2D[(i + 1) % segCount]; - real po = (p2D - _localCircleCenter2D[i]).norm(); - if (po == _profile.getCircularArcs()[i].radius) { - // TODO + // TODO: 为了鲁棒和精度,应该在每个可能最近的seg上做newton iteration + int seg = static_cast(closestParam); + // Q = arc.center + arc.radius * (arc.u * std::cos(phi) + arc.v * std::sin(phi)) + // d2 = (Q - p)^2 + Vec2 q = eval(closestParam); + Vec2 qDer1 = der1(closestParam); + Vec2 qDer2 = der2(closestParam); + real lDer1 = (q - p2D).dot(qDer1); + int iter = 0; + while (abs(lDer1) > std::numeric_limits::epsilon() * 1e6) + { + closestParam -= lDer1 / (qDer1.dot(qDer1) + (q - p2D).dot(qDer2)); // -der1 / der2 + q = eval(closestParam); + qDer1 = der1(closestParam); + qDer2 = der2(closestParam); + lDer1 = (q - p2D).dot(qDer1); + printf("After iter %d, dL is %lf\n", iter, lDer1); + if (closestParam < seg - std::numeric_limits::epsilon()) + { + closestParam = seg; + closestDis = (_localProfile2D[seg] - p2D).norm(); + break; + } + else if (closestParam > seg + 1 + std::numeric_limits::epsilon()) + { + closestParam = seg + 1; + closestDis = (_localProfile2D[(seg + 1) % segCount] - p2D).norm(); + break; } - if ((po < _profile.getCircularArcs()[i].radius) { - if ((p2D - a).dot(b - a) > 0) { - inFan = true; - break; + closestDis = (q - p2D).norm(); + iter++; + } + return {closestDis, closestParam}; + } + + bool isOn2DPolyline(const Vec2 &p2D) + { + int segCount = _profile.getBugles().size(); + for (int i = 0; i < segCount; ++i) + { + const Vec2 &a = _localProfile2D[i]; + const Vec2 &b = _localProfile2D[(i + 1) % segCount]; + if (_profile.getBugles()[i] == 0) + { + //line segment + if (isPointOnSegment(p2D, a, b)) + { + return true; } + continue; } } } - bool isPointOnSegment(const Vec2 &p, const Vec2 &a, const Vec2 &b) { + bool isPointOnSegment(const Vec2 p, const Vec2 &a, const Vec2 &b) + { // check collinearity double crossProduct = (p[1] - a[1]) * (b[0] - a[0]) - (p[0] - a[0]) * (b[1] - a[1]); - if (!isEqual(crossProduct, 0)) return false; // Not collinear + if (!isEqual(crossProduct, 0)) + return false; // Not collinear // Check if point is within segment bounds - return (p[0] >= std::min(a[0], b[0]) && p[0] <= std::max(a[0], b[0]) && - p[1] >= std::min(a[1], b[1]) && p[1] <= std::max(a[1], b[1])); + return (p[0] >= std::min(a[0], b[0]) && p[0] <= std::max(a[0], b[0]) && p[1] >= std::min(a[1], b[1]) && + p[1] <= std::max(a[1], b[1])); } - real wnCircularArc(const Vec3 &p, const Vec3 &a, const Vec3 &b, const Vec3 &plgNormal, - const Polyline::CircularArc &arc, int dir) { + real wnCircularArc( + const Vec3 &p, const Vec3 &a, const Vec3 &b, const Vec3 &plgNormal, const Polyline::CircularArc &arc, int dir) + { Vec3 pa = a - p; Vec3 pb = b - p; - real wn = std::acos(std::clamp(pa.dot(pb) / (pa.norm() * pb.norm()), static_cast(-1.), - static_cast(1.))) / (std::numbers::pi * 2); + real wn = + std::acos(std::clamp(pa.dot(pb) / (pa.norm() * pb.norm()), static_cast(-1.), static_cast(1.))) / + (std::numbers::pi * 2); auto inOutCircle = arc.inCircleCheck(p); - if (inOutCircle == PtBoundaryRelation::Outside || pa.cross(pb).dot(plgNormal) < 0) { + if (inOutCircle == PtBoundaryRelation::Outside || pa.cross(pb).dot(plgNormal) < 0) + { // outside // pa.cross(pb).dot(plgNormal) 不会 == 0 return -wn * dir; } - if (inOutCircle == PtBoundaryRelation::Inside) { + if (inOutCircle == PtBoundaryRelation::Inside) + { return wn * dir; } return 0; } + + // Vec2 eval2DProfile(real param) + // { + // int seg = static_cast(param); + // real tOnSeg = param - seg; + // const auto &arc = circularArcs[seg]; + // real phi = tOnSeg * arc.theta; + // return arc.center + arc.radius * (arc.u * std::cos(phi) + arc.v * std::sin(phi)); + // } }; -class ExtrudedSolidPolynomialLine : public IExtrudedSolid { +class ExtrudedSolidPolynomialLine : public IExtrudedSolid +{ protected: PolynomialLine _axis; };