mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-06-20 18:24:43 +00:00
Use libtess2 instead of boost graph and earcut
This commit is contained in:
1 parent
5ef9c9be73
commit
7617da6aff
53 files changed
+6647
-35
No files matched your search
@@ -377,10 +377,12 @@ official_apple_pay_merchants = [
|
||||
"merchant.privatbank.test.telergramios",
|
||||
"merchant.privatbank.prod.telergram",
|
||||
"merchant.paymaster.test.telegramios",
|
||||
#"merchant.paymaster.prod.telegramios",
|
||||
"merchant.smartglocal.prod.telegramios",
|
||||
"merchant.smartglocal.test.telegramios",
|
||||
"merchant.yoomoney.test.telegramios",
|
||||
#"merchant.org.telegram.Best2pay.test",
|
||||
#"merchant.psbank.test.telegramios",
|
||||
]
|
||||
|
||||
official_bundle_ids = [
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Whitespace-only changes.
@@ -0,0 +1,16 @@
|
||||
|
||||
filegroup(
|
||||
name = "LaunchScreen",
|
||||
srcs = [
|
||||
"Base.lproj/LaunchScreen.xib",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "Main",
|
||||
srcs = [
|
||||
"Main/main.m"
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" interfaceStyle="light" id="O8c-13-3vw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<point key="canvasLocation" x="139" y="117"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -0,0 +1,7 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, @"Application", @"AppDelegate");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
load("@build_bazel_rules_apple//apple:ios.bzl",
|
||||
"ios_application",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl",
|
||||
"swift_library",
|
||||
)
|
||||
|
||||
load("//build-system/bazel-utils:plist_fragment.bzl",
|
||||
"plist_fragment",
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "AppResources",
|
||||
srcs = glob([
|
||||
"Resources/**/*",
|
||||
], exclude = ["Resources/**/.*"]),
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "Lib",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
data = [
|
||||
":AppResources",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/LottieMeshSwift:LottieMeshSwift",
|
||||
],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "BuildNumberInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
"""
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "VersionInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
"""
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "AppNameInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Test</string>
|
||||
"""
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "AppInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Test</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.telegram.LottieMesh</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Telegram</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<false/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UIViewEdgeAntialiasing</key>
|
||||
<false/>
|
||||
<key>UIViewGroupOpacity</key>
|
||||
<false/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
"""
|
||||
)
|
||||
|
||||
ios_application(
|
||||
name = "LottieMesh",
|
||||
bundle_id = "org.telegram.LottieMesh",
|
||||
families = ["iphone", "ipad"],
|
||||
minimum_os_version = "9.0",
|
||||
provisioning_profile = "@build_configuration//provisioning:Wildcard.mobileprovision",
|
||||
infoplists = [
|
||||
":AppInfoPlist",
|
||||
":BuildNumberInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
],
|
||||
resources = [
|
||||
"//Tests/Common:LaunchScreen",
|
||||
],
|
||||
frameworks = [
|
||||
],
|
||||
deps = [
|
||||
"//Tests/Common:Main",
|
||||
":Lib",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":120,"w":512,"h":512,"nm":"Cat Kissing heart","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 1","parent":28,"sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[144.75,81.342,0]},"a":{"a":0,"k":[50,50,0]}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"f1","parent":4,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[16.902,56.403,0],"to":[-10.812,-9.286,0],"ti":[-4.15,9.939,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[7.578,18.211,0],"to":[-3.682,8.922,0],"ti":[-11.474,-9.33,0]},{"t":100,"s":[16.902,56.403,0]}]},"a":{"a":0,"k":[41.268,20.785,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0.651,-0.673],[8.733,-1.767],[-19.861,3.406],[-2.625,-8.662],[-1,0.306],[12.5,-2.186],[-32.67,3.532],[-9.545,2.662]],"o":[[-7.894,8.108],[-18.845,2.227],[9.22,-1.644],[0.979,3.228],[-9.903,3.028],[-27.269,4.551],[11.584,-0.098],[1.044,-0.292]],"v":[[22.717,-18.677],[-4.571,-15.821],[6.137,12.114],[33.53,0.445],[31.529,4.186],[7.089,18.038],[-1.84,-22.491],[21.727,-20.236]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0.793,-0.267],[8.733,-1.767],[-21.574,0.524],[-8.505,-5.453],[1.254,0.321],[7.891,-1.169],[-32.67,3.532],[-9.454,0.339]],"o":[[-10.484,3.106],[-19.463,2.918],[10.711,-0.207],[2.701,1.732],[-6.741,1.29],[-27.325,3.017],[11.584,-0.098],[1.054,-0.034]],"v":[[26.863,-13.654],[-3.731,-17.014],[2.892,8.287],[24.973,7.108],[20.51,8.322],[3.334,13.632],[-1.853,-22.754],[27.297,-16.351]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0.929,0.119],[8.733,-1.767],[-23.207,-2.223],[-1.817,-2.005],[3.403,0.335],[3.497,-0.198],[-32.67,3.532],[-9.368,-1.877]],"o":[[-12.954,-1.662],[-20.051,3.577],[12.132,1.162],[2.265,2.499],[-3.727,-0.367],[-27.379,1.554],[11.584,-0.098],[1.063,0.213]],"v":[[30.815,-8.865],[-2.93,-18.152],[-0.201,4.64],[15.987,10.187],[9.335,9.402],[-0.247,9.432],[-1.866,-23.005],[32.608,-12.648]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":54,"s":[{"i":[[0.871,-0.045],[8.733,-1.767],[-22.513,-1.055],[-4.661,-3.471],[2.489,0.329],[5.366,-0.611],[-32.67,3.532],[-9.405,-0.935]],"o":[[-11.903,0.366],[-19.801,3.296],[11.528,0.58],[2.451,2.173],[-5.009,0.337],[-27.356,2.176],[11.584,-0.098],[1.059,0.108]],"v":[[29.134,-10.902],[-3.271,-17.668],[1.115,6.191],[19.808,8.877],[14.087,8.943],[1.276,11.218],[-1.861,-22.898],[36.162,-12.839]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[{"i":[[0.793,-0.267],[8.733,-1.767],[-21.574,0.524],[-8.505,-5.453],[1.254,0.321],[7.891,-1.169],[-32.67,3.532],[-9.454,0.339]],"o":[[-10.484,3.106],[-19.463,2.918],[10.711,-0.207],[2.701,1.732],[-6.741,1.29],[-27.325,3.017],[11.584,-0.098],[1.054,-0.034]],"v":[[26.863,-13.654],[-3.731,-17.014],[2.892,8.287],[24.973,7.108],[20.51,8.322],[3.334,13.632],[-1.853,-22.754],[27.297,-16.351]],"c":true}]},{"t":100,"s":[{"i":[[0.651,-0.673],[8.733,-1.767],[-19.861,3.406],[-2.625,-8.662],[-1,0.306],[12.5,-2.186],[-32.67,3.532],[-9.545,2.662]],"o":[[-7.894,8.108],[-18.845,2.227],[9.22,-1.644],[0.979,3.228],[-9.903,3.028],[-27.269,4.551],[11.584,-0.098],[1.044,-0.292]],"v":[[22.717,-18.677],[-4.571,-15.821],[6.137,12.114],[33.53,0.445],[31.529,4.186],[7.089,18.038],[-1.84,-22.491],[21.727,-20.236]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149000010771,0.090000002992,0.176000004189,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[34.759,22.839]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[-9.375,0.938],[6.562,4.312]],"o":[[-12.188,3.938],[9.375,-0.937],[-6.562,-4.313]],"v":[[-6.437,-5],[-0.687,8],[12.063,-4.625]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[0,0],[-9.375,0.938],[6.562,4.312]],"o":[[-12.188,3.938],[9.375,-0.937],[-6.562,-4.313]],"v":[[-7.605,-7.323],[-3.029,-8.211],[10.895,-6.948]],"c":true}]},{"t":100,"s":[{"i":[[0,0],[-9.375,0.938],[6.562,4.312]],"o":[[-12.188,3.938],[9.375,-0.937],[-6.562,-4.313]],"v":[[-6.437,-5],[-0.687,8],[12.063,-4.625]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002394,0.39199999641,0.611999990426,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[35.293,11.008]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[5,-3],[-7,-9],Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"512_fireworks dop","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":0,"nm":"fireworks !!yellow 4 vspish","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":76,"s":[100]},{"t":86,"s":[0]}]},"r":{"a":0,"k":-10.694},"p":{"a":0,"k":[977.5,379,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[80,80,100]}},"ao":0,"w":486,"h":486,"ip":36,"op":86,"st":6,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"fireworks pink 5","refId":"comp_3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":83,"s":[100]},{"t":94,"s":[0]}]},"r":{"a":0,"k":-0.087},"p":{"a":0,"k":[1279.5,464,0]},"a":{"a":0,"k":[243,243,0]}},"ao":0,"w":486,"h":486,"ip":48,"op":94,"st":18,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"fireworks !!yellow 4 vspish","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":105,"s":[100]},{"t":115,"s":[0]}]},"r":{"a":0,"k":23.994},"p":{"a":0,"k":[305.5,923,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[78.011,78.011,100]}},"ao":0,"w":486,"h":486,"ip":65,"op":115,"st":35,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"fireworks pink 5","refId":"comp_3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":71,"s":[100]},{"t":81,"s":[0]}]},"p":{"a":0,"k":[441.5,398,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[157.86,157.86,100]}},"ao":0,"w":486,"h":486,"ip":31,"op":81,"st":1,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"Trace Shape Layer 1: Path 6 [1.1]","cl":"1","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[-120.256]},{"t":29,"s":[-176.947]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[1286,792,0],"to":[-172.306,-276.404,0],"ti":[305.23,23.654,0]},{"t":29,"s":[468.343,405.178,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":10,"s":[40,40,100]},{"t":30,"s":[150,150,100]}]}},"ao":0,"ip":0,"op":30,"st":-60,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"!!!rocket pink 2","parent":5,"refId":"comp_4","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"a":0,"k":[256.94,395.855,0]}},"ao":0,"w":512,"h":512,"ip":1,"op":36,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"fireworks !!yellow 4 vspish","refId":"comp_1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":89,"s":[100]},{"t":99,"s":[0]}]},"r":{"a":0,"k":-10.694},"p":{"a":0,"k":[633.5,1163,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[128.178,128.178,100]}},"ao":0,"w":486,"h":486,"ip":49,"op":99,"st":19,"bm":0},{"ddd":0,"ind":8,"ty":3,"nm":"Trace Shape Layer 1: Path 5 [1.2]","cl":"2","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[98.009]},{"t":46,"s":[185.502]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":16,"s":[1236,866,0],"to":[-48.547,286.872,0],"ti":[243.966,25.033,0]},{"t":46,"s":[667.754,1167.601,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":16,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,3.222]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":26,"s":[40,40,100]},{"t":46,"s":[130,130,100]}]}},"ao":0,"ip":16,"op":47,"st":-44,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"!!!rocket yellow 2","parent":8,"refId":"comp_5","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"a":0,"k":[256.94,395.855,0]}},"ao":0,"w":512,"h":512,"ip":16,"op":52,"st":16,"bm":0},{"ddd":0,"ind":10,"ty":0,"nm":"fireworks !!blue 4 vspish","refId":"comp_6","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":89,"s":[100]},{"t":100,"s":[0]}]},"r":{"a":0,"k":-0.087},"p":{"a":0,"k":[695.5,832,0]},"a":{"a":0,"k":[243,243,0]},"s":{"a":0,"k":[201.159,201.159,100]}},"ao":0,"w":486,"h":486,"ip":54,"op":100,"st":24,"bm":0},{"ddd":0,"ind":11,"ty":3,"nm":"Trace Shape Layer 1: Path 8 [1.8]","cl":"8","sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[112.759]},{"t":51,"s":[219.154]}]},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.82,"y":0},"t":22,"s":[1352,810,0],"to":[-138.369,305.192,0],"ti":[207.549,143.516,0]},{"t":51,"s":[705.012,835.922,0]}]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":22,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,-2.333]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":32,"s":[40,40,100]},{"t":52,"s":[180,180,100]}]}},"ao":0,"ip":22,"op":52,"st":-38,"bm":0},{"ddd":0,"ind":12,"ty":0,"nm":"!!!rocket blue 2","parent":11,"refId":"comp_7","sr":1,"ks":{"r":{"a":0,"k":90},"p":{"a":0,"k":[-167.855,0.94,0]},"a":{"Line truncated
|
||||
@@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
@objc(Application)
|
||||
public final class Application: UIApplication {
|
||||
}
|
||||
|
||||
@objc(AppDelegate)
|
||||
public final class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
public var window: UIWindow?
|
||||
|
||||
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
let window = UIWindow()
|
||||
self.window = window
|
||||
|
||||
window.rootViewController = ViewController()
|
||||
window.makeKeyAndVisible()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
import LottieMeshSwift
|
||||
|
||||
public final class ViewController: UIViewController {
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.backgroundColor = .black
|
||||
|
||||
let path = Bundle.main.path(forResource: "Fireworks", ofType: "json")!
|
||||
//let path = Bundle.main.path(forResource: "Cat", ofType: "json")!
|
||||
/*for _ in 0 ..< 100 {
|
||||
let _ = generateMeshAnimation(data: try! Data(contentsOf: URL(fileURLWithPath: path)))!
|
||||
}*/
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
let animation = generateMeshAnimation(data: try! Data(contentsOf: URL(fileURLWithPath: path)))!
|
||||
let renderer = MeshRenderer(wireframe: true)!
|
||||
|
||||
renderer.frame = CGRect(origin: CGPoint(x: 0.0, y: 50.0), size: CGSize(width: 300.0, height: 300.0))
|
||||
self.view.addSubview(renderer)
|
||||
|
||||
renderer.add(mesh: animation, offset: CGPoint(), loop: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ swift_library(
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
|
||||
#"//submodules/MeshAnimationCache:MeshAnimationCache"
|
||||
"//submodules/MeshAnimationCache:MeshAnimationCache"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@@ -10,7 +10,7 @@ import AsyncDisplayKit
|
||||
import Display
|
||||
import DeviceLocationManager
|
||||
import TemporaryCachedPeerDataManager
|
||||
//import MeshAnimationCache
|
||||
import MeshAnimationCache
|
||||
|
||||
public final class TelegramApplicationOpenUrlCompletion {
|
||||
public let completion: (Bool) -> Void
|
||||
@@ -737,7 +737,7 @@ public protocol AccountContext: AnyObject {
|
||||
var currentAppConfiguration: Atomic<AppConfiguration> { get }
|
||||
|
||||
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
|
||||
//var meshAnimationCache: MeshAnimationCache { get }
|
||||
var meshAnimationCache: MeshAnimationCache { get }
|
||||
|
||||
func storeSecureIdPassword(password: String)
|
||||
func getStoredSecureIdPassword() -> String?
|
||||
|
||||
@@ -120,6 +120,7 @@ cc_library(
|
||||
]),
|
||||
copts = [
|
||||
"-Isubmodules/LottieMeshSwift/LottieMesh/boost",
|
||||
"-Isubmodules/LottieMeshSwift/libtess2/Include",
|
||||
] + optimization_flags,
|
||||
hdrs = glob([
|
||||
"LottieMesh/PublicHeaders/**/*.h",
|
||||
@@ -128,6 +129,27 @@ cc_library(
|
||||
includes = [
|
||||
"LottieMesh/PublicHeaders",
|
||||
],
|
||||
deps = [
|
||||
":libtess2",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "libtess2",
|
||||
srcs = glob([
|
||||
"libtess2/Sources/**/*.c",
|
||||
"libtess2/Sources/**/*.h",
|
||||
"libtess2/Include/**/*.h",
|
||||
]),
|
||||
copts = [
|
||||
"-Isubmodules/LottieMeshSwift/libtess2/Include",
|
||||
] + optimization_flags,
|
||||
hdrs = glob([
|
||||
"libtess2/Include/**/*.h",
|
||||
]),
|
||||
deps = [
|
||||
],
|
||||
visibility = [
|
||||
|
||||
@@ -10,9 +10,12 @@ struct Point {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
|
||||
explicit Point(float x_, float y_) :
|
||||
Point(float x_, float y_) :
|
||||
x(x_), y(y_) {
|
||||
}
|
||||
|
||||
Point() : Point(0.0f, 0.0f) {
|
||||
}
|
||||
|
||||
bool isEqual(Point const &other, float epsilon = 0.0001f) const {
|
||||
return std::abs(x - other.x) <= epsilon && std::abs(y - other.y) <= epsilon;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include "Vec2.h"
|
||||
#include <optional>
|
||||
|
||||
namespace crushedpixel {
|
||||
|
||||
template<typename Vec2>
|
||||
struct LineSegment {
|
||||
LineSegment(const Vec2 &a, const Vec2 &b) :
|
||||
a(a), b(b) {}
|
||||
|
||||
Vec2 a, b;
|
||||
|
||||
/**
|
||||
* @return A copy of the line segment, offset by the given vector.
|
||||
*/
|
||||
LineSegment operator+(const Vec2 &toAdd) const {
|
||||
return {Vec2Maths::add(a, toAdd), Vec2Maths::add(b, toAdd)};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A copy of the line segment, offset by the given vector.
|
||||
*/
|
||||
LineSegment operator-(const Vec2 &toRemove) const {
|
||||
return {Vec2Maths::subtract(a, toRemove), Vec2Maths::subtract(b, toRemove)};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The line segment's normal vector.
|
||||
*/
|
||||
Vec2 normal() const {
|
||||
auto dir = direction();
|
||||
|
||||
// return the direction vector
|
||||
// rotated by 90 degrees counter-clockwise
|
||||
return {-dir.y, dir.x};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The line segment's direction vector.
|
||||
*/
|
||||
Vec2 direction(bool normalized = true) const {
|
||||
auto vec = Vec2Maths::subtract(b, a);
|
||||
|
||||
return normalized
|
||||
? Vec2Maths::normalized(vec)
|
||||
: vec;
|
||||
}
|
||||
|
||||
static Vec2 intersection(const LineSegment &a, const LineSegment &b, bool infiniteLines, bool &success) {
|
||||
success = true;
|
||||
|
||||
// calculate un-normalized direction vectors
|
||||
auto r = a.direction(false);
|
||||
auto s = b.direction(false);
|
||||
|
||||
auto originDist = Vec2Maths::subtract(b.a, a.a);
|
||||
|
||||
auto uNumerator = Vec2Maths::cross(originDist, r);
|
||||
auto denominator = Vec2Maths::cross(r, s);
|
||||
|
||||
if (std::abs(denominator) < 0.0001f) {
|
||||
// The lines are parallel
|
||||
success = false;
|
||||
return Vec2();
|
||||
}
|
||||
|
||||
// solve the intersection positions
|
||||
auto u = uNumerator / denominator;
|
||||
auto t = Vec2Maths::cross(originDist, s) / denominator;
|
||||
|
||||
if (!infiniteLines && (t < 0 || t > 1 || u < 0 || u > 1)) {
|
||||
// the intersection lies outside of the line segments
|
||||
success = false;
|
||||
return Vec2();
|
||||
}
|
||||
|
||||
// calculate the intersection point
|
||||
// a.a + r * t;
|
||||
return Vec2Maths::add(a.a, Vec2Maths::multiply(r, t));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace crushedpixel
|
||||
@@ -4,9 +4,85 @@
|
||||
#include <LottieMesh/Point.h>
|
||||
#include "Triangulation.h"
|
||||
|
||||
#include "tesselator.h"
|
||||
#include "Polyline2D.h"
|
||||
|
||||
namespace {
|
||||
};
|
||||
|
||||
namespace MeshGenerator {
|
||||
|
||||
std::unique_ptr<Mesh> generateMesh(std::vector<Path> const &paths, std::unique_ptr<Fill> fill, std::unique_ptr<Stroke> stroke) {
|
||||
if (stroke) {
|
||||
std::unique_ptr<Mesh> mesh = std::make_unique<Mesh>();
|
||||
|
||||
for (const auto &path : paths) {
|
||||
crushedpixel::Polyline2D::JointStyle jointStyle = crushedpixel::Polyline2D::JointStyle::ROUND;
|
||||
crushedpixel::Polyline2D::EndCapStyle endCapStyle = crushedpixel::Polyline2D::EndCapStyle::SQUARE;
|
||||
switch (stroke->lineJoin) {
|
||||
case Stroke::LineJoin::Miter:
|
||||
jointStyle = crushedpixel::Polyline2D::JointStyle::MITER;
|
||||
break;
|
||||
case Stroke::LineJoin::Round:
|
||||
jointStyle = crushedpixel::Polyline2D::JointStyle::ROUND;
|
||||
break;
|
||||
case Stroke::LineJoin::Bevel:
|
||||
jointStyle = crushedpixel::Polyline2D::JointStyle::BEVEL;
|
||||
break;
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (stroke->lineCap) {
|
||||
case Stroke::LineCap::Round: {
|
||||
endCapStyle = crushedpixel::Polyline2D::EndCapStyle::ROUND;
|
||||
break;
|
||||
}
|
||||
case Stroke::LineCap::Square: {
|
||||
endCapStyle = crushedpixel::Polyline2D::EndCapStyle::SQUARE;
|
||||
break;
|
||||
}
|
||||
case Stroke::LineCap::Butt: {
|
||||
endCapStyle = crushedpixel::Polyline2D::EndCapStyle::BUTT;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto vertices = crushedpixel::Polyline2D::create(path.points, stroke->lineWidth, jointStyle, endCapStyle);
|
||||
for (const auto &vertex : vertices) {
|
||||
mesh->triangles.push_back((int)mesh->vertices.size());
|
||||
mesh->vertices.push_back(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
assert(mesh->triangles.size() % 3 == 0);
|
||||
return mesh;
|
||||
} else {
|
||||
TESStesselator *tessellator = tessNewTess(NULL);
|
||||
tessSetOption(tessellator, TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, 1);
|
||||
for (const auto &path : paths) {
|
||||
tessAddContour(tessellator, 2, path.points.data(), sizeof(Point), (int)path.points.size());
|
||||
}
|
||||
tessTesselate(tessellator, TESS_WINDING_ODD, TESS_POLYGONS, 3, 2, NULL);
|
||||
|
||||
int vertexCount = tessGetVertexCount(tessellator);
|
||||
const TESSreal *vertices = tessGetVertices(tessellator);
|
||||
int indexCount = tessGetElementCount(tessellator) * 3;
|
||||
const TESSindex *indices = tessGetElements(tessellator);
|
||||
|
||||
std::unique_ptr<Mesh> mesh = std::make_unique<Mesh>();
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
mesh->vertices.push_back(Point(vertices[i * 2 + 0], vertices[i * 2 + 1]));
|
||||
}
|
||||
for (int i = 0; i < indexCount; i++) {
|
||||
mesh->triangles.push_back(indices[i]);
|
||||
}
|
||||
return mesh;
|
||||
}
|
||||
|
||||
std::unique_ptr<PlanarStraightLineGraph> graph;
|
||||
std::unique_ptr<std::vector<Path>> updatedPaths;
|
||||
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
#pragma once
|
||||
|
||||
#include "LineSegment.h"
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
#include <cassert>
|
||||
|
||||
namespace crushedpixel {
|
||||
|
||||
class Polyline2D {
|
||||
public:
|
||||
enum class JointStyle {
|
||||
/**
|
||||
* Corners are drawn with sharp joints.
|
||||
* If the joint's outer angle is too large,
|
||||
* the joint is drawn as beveled instead,
|
||||
* to avoid the miter extending too far out.
|
||||
*/
|
||||
MITER,
|
||||
/**
|
||||
* Corners are flattened.
|
||||
*/
|
||||
BEVEL,
|
||||
/**
|
||||
* Corners are rounded off.
|
||||
*/
|
||||
ROUND
|
||||
};
|
||||
|
||||
enum class EndCapStyle {
|
||||
/**
|
||||
* Path ends are drawn flat,
|
||||
* and don't exceed the actual end point.
|
||||
*/
|
||||
BUTT, // lol
|
||||
/**
|
||||
* Path ends are drawn flat,
|
||||
* but extended beyond the end point
|
||||
* by half the line thickness.
|
||||
*/
|
||||
SQUARE,
|
||||
/**
|
||||
* Path ends are rounded off.
|
||||
*/
|
||||
ROUND,
|
||||
/**
|
||||
* Path ends are connected according to the JointStyle.
|
||||
* When using this EndCapStyle, don't specify the common start/end point twice,
|
||||
* as Polyline2D connects the first and last input point itself.
|
||||
*/
|
||||
JOINT
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a vector of vertices describing a solid path through the input points.
|
||||
* @param points The points of the path.
|
||||
* @param thickness The path's thickness.
|
||||
* @param jointStyle The path's joint style.
|
||||
* @param endCapStyle The path's end cap style.
|
||||
* @param allowOverlap Whether to allow overlapping vertices.
|
||||
* This yields better results when dealing with paths
|
||||
* whose points have a distance smaller than the thickness,
|
||||
* but may introduce overlapping vertices,
|
||||
* which is undesirable when rendering transparent paths.
|
||||
* @return The vertices describing the path.
|
||||
* @tparam Vec2 The vector type to use for the vertices.
|
||||
* Must have public non-const float fields "x" and "y".
|
||||
* Must have a two-args constructor taking x and y values.
|
||||
* See crushedpixel::Vec2 for a type that satisfies these requirements.
|
||||
* @tparam InputCollection The collection type of the input points.
|
||||
* Must contain elements of type Vec2.
|
||||
* Must expose size() and operator[] functions.
|
||||
*/
|
||||
template<typename Vec2, typename InputCollection>
|
||||
static std::vector<Vec2> create(const InputCollection &points, float thickness,
|
||||
JointStyle jointStyle = JointStyle::MITER,
|
||||
EndCapStyle endCapStyle = EndCapStyle::BUTT,
|
||||
bool allowOverlap = false) {
|
||||
std::vector<Vec2> vertices;
|
||||
create(vertices, points, thickness, jointStyle, endCapStyle, allowOverlap);
|
||||
return vertices;
|
||||
}
|
||||
|
||||
template<typename Vec2>
|
||||
static std::vector<Vec2> create(const std::vector<Vec2> &points, float thickness,
|
||||
JointStyle jointStyle = JointStyle::MITER,
|
||||
EndCapStyle endCapStyle = EndCapStyle::BUTT,
|
||||
bool allowOverlap = false) {
|
||||
std::vector<Vec2> vertices;
|
||||
create<Vec2, std::vector<Vec2>>(vertices, points, thickness, jointStyle, endCapStyle, allowOverlap);
|
||||
return vertices;
|
||||
}
|
||||
|
||||
template<typename Vec2, typename InputCollection>
|
||||
static size_t create(std::vector<Vec2> &vertices, const InputCollection &points, float thickness,
|
||||
JointStyle jointStyle = JointStyle::MITER,
|
||||
EndCapStyle endCapStyle = EndCapStyle::BUTT,
|
||||
bool allowOverlap = false) {
|
||||
auto numVerticesBefore = vertices.size();
|
||||
|
||||
create<Vec2, InputCollection>(std::back_inserter(vertices), points, thickness,
|
||||
jointStyle, endCapStyle, allowOverlap);
|
||||
|
||||
return vertices.size() - numVerticesBefore;
|
||||
}
|
||||
|
||||
template<typename Vec2, typename InputCollection, typename OutputIterator>
|
||||
static OutputIterator create(OutputIterator vertices, const InputCollection &points, float thickness,
|
||||
JointStyle jointStyle = JointStyle::MITER,
|
||||
EndCapStyle endCapStyle = EndCapStyle::BUTT,
|
||||
bool allowOverlap = false) {
|
||||
// operate on half the thickness to make our lives easier
|
||||
thickness /= 2;
|
||||
|
||||
// create poly segments from the points
|
||||
std::vector<PolySegment<Vec2>> segments;
|
||||
for (size_t i = 0; i + 1 < points.size(); i++) {
|
||||
auto &point1 = points[i];
|
||||
auto &point2 = points[i + 1];
|
||||
|
||||
// to avoid division-by-zero errors,
|
||||
// only create a line segment for non-identical points
|
||||
if (!Vec2Maths::equal(point1, point2)) {
|
||||
segments.emplace_back(LineSegment<Vec2>(point1, point2), thickness);
|
||||
}
|
||||
}
|
||||
|
||||
if (endCapStyle == EndCapStyle::JOINT) {
|
||||
// create a connecting segment from the last to the first point
|
||||
|
||||
auto &point1 = points[points.size() - 1];
|
||||
auto &point2 = points[0];
|
||||
|
||||
// to avoid division-by-zero errors,
|
||||
// only create a line segment for non-identical points
|
||||
if (!Vec2Maths::equal(point1, point2)) {
|
||||
segments.emplace_back(LineSegment<Vec2>(point1, point2), thickness);
|
||||
}
|
||||
}
|
||||
|
||||
if (segments.empty()) {
|
||||
// handle the case of insufficient input points
|
||||
return vertices;
|
||||
}
|
||||
|
||||
Vec2 nextStart1{0, 0};
|
||||
Vec2 nextStart2{0, 0};
|
||||
Vec2 start1{0, 0};
|
||||
Vec2 start2{0, 0};
|
||||
Vec2 end1{0, 0};
|
||||
Vec2 end2{0, 0};
|
||||
|
||||
// calculate the path's global start and end points
|
||||
auto &firstSegment = segments[0];
|
||||
auto &lastSegment = segments[segments.size() - 1];
|
||||
|
||||
auto pathStart1 = firstSegment.edge1.a;
|
||||
auto pathStart2 = firstSegment.edge2.a;
|
||||
auto pathEnd1 = lastSegment.edge1.b;
|
||||
auto pathEnd2 = lastSegment.edge2.b;
|
||||
|
||||
// handle different end cap styles
|
||||
if (endCapStyle == EndCapStyle::SQUARE) {
|
||||
// extend the start/end points by half the thickness
|
||||
pathStart1 = Vec2Maths::subtract(pathStart1, Vec2Maths::multiply(firstSegment.edge1.direction(), thickness));
|
||||
pathStart2 = Vec2Maths::subtract(pathStart2, Vec2Maths::multiply(firstSegment.edge2.direction(), thickness));
|
||||
pathEnd1 = Vec2Maths::add(pathEnd1, Vec2Maths::multiply(lastSegment.edge1.direction(), thickness));
|
||||
pathEnd2 = Vec2Maths::add(pathEnd2, Vec2Maths::multiply(lastSegment.edge2.direction(), thickness));
|
||||
|
||||
} else if (endCapStyle == EndCapStyle::ROUND) {
|
||||
// draw half circle end caps
|
||||
createTriangleFan(vertices, firstSegment.center.a, firstSegment.center.a,
|
||||
firstSegment.edge1.a, firstSegment.edge2.a, false);
|
||||
createTriangleFan(vertices, lastSegment.center.b, lastSegment.center.b,
|
||||
lastSegment.edge1.b, lastSegment.edge2.b, true);
|
||||
|
||||
} else if (endCapStyle == EndCapStyle::JOINT) {
|
||||
// join the last (connecting) segment and the first segment
|
||||
createJoint(vertices, lastSegment, firstSegment, jointStyle,
|
||||
pathEnd1, pathEnd2, pathStart1, pathStart2, allowOverlap);
|
||||
}
|
||||
|
||||
// generate mesh data for path segments
|
||||
for (size_t i = 0; i < segments.size(); i++) {
|
||||
auto &segment = segments[i];
|
||||
|
||||
// calculate start
|
||||
if (i == 0) {
|
||||
// this is the first segment
|
||||
start1 = pathStart1;
|
||||
start2 = pathStart2;
|
||||
}
|
||||
|
||||
if (i + 1 == segments.size()) {
|
||||
// this is the last segment
|
||||
end1 = pathEnd1;
|
||||
end2 = pathEnd2;
|
||||
|
||||
} else {
|
||||
createJoint(vertices, segment, segments[i + 1], jointStyle,
|
||||
end1, end2, nextStart1, nextStart2, allowOverlap);
|
||||
}
|
||||
|
||||
// emit vertices
|
||||
*vertices++ = start1;
|
||||
*vertices++ = start2;
|
||||
*vertices++ = end1;
|
||||
|
||||
*vertices++ = end1;
|
||||
*vertices++ = start2;
|
||||
*vertices++ = end2;
|
||||
|
||||
start1 = nextStart1;
|
||||
start2 = nextStart2;
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr float pi = 3.14159265358979323846f;
|
||||
|
||||
/**
|
||||
* The threshold for mitered joints.
|
||||
* If the joint's angle is smaller than this angle,
|
||||
* the joint will be drawn beveled instead.
|
||||
*/
|
||||
static constexpr float miterMinAngle = 0.349066; // ~20 degrees
|
||||
|
||||
/**
|
||||
* The minimum angle of a round joint's triangles.
|
||||
*/
|
||||
static constexpr float roundMinAngle = 0.174533; // ~10 degrees
|
||||
|
||||
template<typename Vec2>
|
||||
struct PolySegment {
|
||||
PolySegment(const LineSegment<Vec2> ¢er, float thickness) :
|
||||
center(center),
|
||||
// calculate the segment's outer edges by offsetting
|
||||
// the central line by the normal vector
|
||||
// multiplied with the thickness
|
||||
|
||||
// center + center.normal() * thickness
|
||||
edge1(center + Vec2Maths::multiply(center.normal(), thickness)),
|
||||
edge2(center - Vec2Maths::multiply(center.normal(), thickness)) {}
|
||||
|
||||
LineSegment<Vec2> center, edge1, edge2;
|
||||
};
|
||||
|
||||
template<typename Vec2, typename OutputIterator>
|
||||
static OutputIterator createJoint(OutputIterator vertices,
|
||||
const PolySegment<Vec2> &segment1, const PolySegment<Vec2> &segment2,
|
||||
JointStyle jointStyle, Vec2 &end1, Vec2 &end2,
|
||||
Vec2 &nextStart1, Vec2 &nextStart2,
|
||||
bool allowOverlap) {
|
||||
// calculate the angle between the two line segments
|
||||
auto dir1 = segment1.center.direction();
|
||||
auto dir2 = segment2.center.direction();
|
||||
|
||||
auto angle = Vec2Maths::angle(dir1, dir2);
|
||||
|
||||
// wrap the angle around the 180° mark if it exceeds 90°
|
||||
// for minimum angle detection
|
||||
auto wrappedAngle = angle;
|
||||
if (wrappedAngle > pi / 2) {
|
||||
wrappedAngle = pi - wrappedAngle;
|
||||
}
|
||||
|
||||
if (jointStyle == JointStyle::MITER && wrappedAngle < miterMinAngle) {
|
||||
// the minimum angle for mitered joints wasn't exceeded.
|
||||
// to avoid the intersection point being extremely far out,
|
||||
// thus producing an enormous joint like a rasta on 4/20,
|
||||
// we render the joint beveled instead.
|
||||
jointStyle = JointStyle::BEVEL;
|
||||
}
|
||||
|
||||
if (jointStyle == JointStyle::MITER) {
|
||||
// calculate each edge's intersection point
|
||||
// with the next segment's central line
|
||||
bool sec1Success = true;
|
||||
bool sec2Success = true;
|
||||
auto sec1 = LineSegment<Vec2>::intersection(segment1.edge1, segment2.edge1, true, sec1Success);
|
||||
auto sec2 = LineSegment<Vec2>::intersection(segment1.edge2, segment2.edge2, true, sec2Success);
|
||||
|
||||
end1 = sec1Success ? sec1 : segment1.edge1.b;
|
||||
end2 = sec2Success ? sec2 : segment1.edge2.b;
|
||||
|
||||
nextStart1 = end1;
|
||||
nextStart2 = end2;
|
||||
|
||||
} else {
|
||||
// joint style is either BEVEL or ROUND
|
||||
|
||||
// find out which are the inner edges for this joint
|
||||
auto x1 = dir1.x;
|
||||
auto x2 = dir2.x;
|
||||
auto y1 = dir1.y;
|
||||
auto y2 = dir2.y;
|
||||
|
||||
auto clockwise = x1 * y2 - x2 * y1 < 0;
|
||||
|
||||
const LineSegment<Vec2> *inner1, *inner2, *outer1, *outer2;
|
||||
|
||||
// as the normal vector is rotated counter-clockwise,
|
||||
// the first edge lies to the left
|
||||
// from the central line's perspective,
|
||||
// and the second one to the right.
|
||||
if (clockwise) {
|
||||
outer1 = &segment1.edge1;
|
||||
outer2 = &segment2.edge1;
|
||||
inner1 = &segment1.edge2;
|
||||
inner2 = &segment2.edge2;
|
||||
} else {
|
||||
outer1 = &segment1.edge2;
|
||||
outer2 = &segment2.edge2;
|
||||
inner1 = &segment1.edge1;
|
||||
inner2 = &segment2.edge1;
|
||||
}
|
||||
|
||||
// calculate the intersection point of the inner edges
|
||||
bool innerSecOptSuccess = true;
|
||||
auto innerSecOpt = LineSegment<Vec2>::intersection(*inner1, *inner2, allowOverlap, innerSecOptSuccess);
|
||||
|
||||
auto innerSec = innerSecOptSuccess
|
||||
? innerSecOpt
|
||||
// for parallel lines, simply connect them directly
|
||||
: inner1->b;
|
||||
|
||||
// if there's no inner intersection, flip
|
||||
// the next start position for near-180° turns
|
||||
Vec2 innerStart;
|
||||
if (innerSecOptSuccess) {
|
||||
innerStart = innerSec;
|
||||
} else if (angle > pi / 2) {
|
||||
innerStart = outer1->b;
|
||||
} else {
|
||||
innerStart = inner1->b;
|
||||
}
|
||||
|
||||
if (clockwise) {
|
||||
end1 = outer1->b;
|
||||
end2 = innerSec;
|
||||
|
||||
nextStart1 = outer2->a;
|
||||
nextStart2 = innerStart;
|
||||
|
||||
} else {
|
||||
end1 = innerSec;
|
||||
end2 = outer1->b;
|
||||
|
||||
nextStart1 = innerStart;
|
||||
nextStart2 = outer2->a;
|
||||
}
|
||||
|
||||
// connect the intersection points according to the joint style
|
||||
|
||||
if (jointStyle == JointStyle::BEVEL) {
|
||||
// simply connect the intersection points
|
||||
*vertices++ = outer1->b;
|
||||
*vertices++ = outer2->a;
|
||||
*vertices++ = innerSec;
|
||||
|
||||
} else if (jointStyle == JointStyle::ROUND) {
|
||||
// draw a circle between the ends of the outer edges,
|
||||
// centered at the actual point
|
||||
// with half the line thickness as the radius
|
||||
createTriangleFan(vertices, innerSec, segment1.center.b, outer1->b, outer2->a, clockwise);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a partial circle between two points.
|
||||
* The points must be equally far away from the origin.
|
||||
* @param vertices The vector to add vertices to.
|
||||
* @param connectTo The position to connect the triangles to.
|
||||
* @param origin The circle's origin.
|
||||
* @param start The circle's starting point.
|
||||
* @param end The circle's ending point.
|
||||
* @param clockwise Whether the circle's rotation is clockwise.
|
||||
*/
|
||||
template<typename Vec2, typename OutputIterator>
|
||||
static OutputIterator createTriangleFan(OutputIterator vertices, Vec2 connectTo, Vec2 origin,
|
||||
Vec2 start, Vec2 end, bool clockwise) {
|
||||
|
||||
auto point1 = Vec2Maths::subtract(start, origin);
|
||||
auto point2 = Vec2Maths::subtract(end, origin);
|
||||
|
||||
// calculate the angle between the two points
|
||||
auto angle1 = atan2(point1.y, point1.x);
|
||||
auto angle2 = atan2(point2.y, point2.x);
|
||||
|
||||
// ensure the outer angle is calculated
|
||||
if (clockwise) {
|
||||
if (angle2 > angle1) {
|
||||
angle2 = angle2 - 2 * pi;
|
||||
}
|
||||
} else {
|
||||
if (angle1 > angle2) {
|
||||
angle1 = angle1 - 2 * pi;
|
||||
}
|
||||
}
|
||||
|
||||
auto jointAngle = angle2 - angle1;
|
||||
|
||||
// calculate the amount of triangles to use for the joint
|
||||
auto numTriangles = std::max(1, (int) std::floor(std::abs(jointAngle) / roundMinAngle));
|
||||
|
||||
// calculate the angle of each triangle
|
||||
auto triAngle = jointAngle / numTriangles;
|
||||
|
||||
Vec2 startPoint = start;
|
||||
Vec2 endPoint;
|
||||
for (int t = 0; t < numTriangles; t++) {
|
||||
if (t + 1 == numTriangles) {
|
||||
// it's the last triangle - ensure it perfectly
|
||||
// connects to the next line
|
||||
endPoint = end;
|
||||
} else {
|
||||
auto rot = (t + 1) * triAngle;
|
||||
|
||||
// rotate the original point around the origin
|
||||
endPoint.x = std::cos(rot) * point1.x - std::sin(rot) * point1.y;
|
||||
endPoint.y = std::sin(rot) * point1.x + std::cos(rot) * point1.y;
|
||||
|
||||
// re-add the rotation origin to the target point
|
||||
endPoint = Vec2Maths::add(endPoint, origin);
|
||||
}
|
||||
|
||||
// emit the triangle
|
||||
*vertices++ = startPoint;
|
||||
*vertices++ = endPoint;
|
||||
*vertices++ = connectTo;
|
||||
|
||||
startPoint = endPoint;
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace crushedpixel
|
||||
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace crushedpixel {
|
||||
|
||||
/**
|
||||
* A two-dimensional float vector.
|
||||
* It exposes the x and y fields
|
||||
* as required by the Polyline2D functions.
|
||||
*/
|
||||
struct Vec2 {
|
||||
Vec2() :
|
||||
Vec2(0, 0) {}
|
||||
|
||||
Vec2(float x, float y) :
|
||||
x(x), y(y) {}
|
||||
|
||||
virtual ~Vec2() = default;
|
||||
|
||||
float x, y;
|
||||
};
|
||||
|
||||
namespace Vec2Maths {
|
||||
|
||||
template<typename Vec2>
|
||||
static bool equal(const Vec2 &a, const Vec2 &b) {
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
template<typename Vec2>
|
||||
static Vec2 multiply(const Vec2 &a, const Vec2 &b) {
|
||||
return {a.x * b.x, a.y * b.y};
|
||||
}
|
||||
|
||||
template<typename Vec2>
|
||||
static Vec2 multiply(const Vec2 &vec, float factor) {
|
||||
return {vec.x * factor, vec.y * factor};
|
||||
}
|
||||
|
||||
template<typename Vec2>
|
||||
static Vec2 divide(const Vec2 &vec, float factor) {
|
||||
return {vec.x / factor, vec.y / factor};
|
||||
}
|
||||
|
||||
template<typename Vec2>
|
||||
static Vec2 add(const Vec2 &a, const Vec2 &b) {
|
||||
return {a.x + b.x, a.y + b.y};
|
||||
}
|
||||
|
||||
template<typename Vec2>
|
||||
static Vec2 subtract(const Vec2 &a, const Vec2 &b) {
|
||||
return {a.x - b.x, a.y - b.y};
|
||||
}
|
||||
|
||||
template<typename Vec2>
|
||||
static float magnitude(const Vec2 &vec) {
|
||||
return std::sqrt(vec.x * vec.x + vec.y * vec.y);
|
||||
}
|
||||
|
||||
template<typename Vec2>
|
||||
static Vec2 withLength(const Vec2 &vec, float len) {
|
||||
auto mag = magnitude(vec);
|
||||
auto factor = mag / len;
|
||||
return divide(vec, factor);
|
||||
}
|
||||
|
||||
template<typename Vec2>
|
||||
static Vec2 normalized(const Vec2 &vec) {
|
||||
return withLength(vec, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the dot product of two vectors.
|
||||
*/
|
||||
template<typename Vec2>
|
||||
static float dot(const Vec2 &a, const Vec2 &b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the cross product of two vectors.
|
||||
*/
|
||||
template<typename Vec2>
|
||||
static float cross(const Vec2 &a, const Vec2 &b) {
|
||||
return a.x * b.y - a.y * b.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the angle between two vectors.
|
||||
*/
|
||||
template<typename Vec2>
|
||||
static float angle(const Vec2 &a, const Vec2 &b) {
|
||||
return std::acos(dot(a, b) / (magnitude(a) * magnitude(b)));
|
||||
}
|
||||
|
||||
} // namespace Vec2Maths
|
||||
|
||||
}
|
||||
@@ -94,7 +94,7 @@ float approximateBezierQubicLength(MeshGenerator::Point const &p0, MeshGenerator
|
||||
|
||||
+ (LottieMeshData * _Nullable)generateWithPath:(UIBezierPath * _Nonnull)path fill: (LottieMeshFill * _Nullable)fill stroke:(LottieMeshStroke * _Nullable)stroke {
|
||||
float scale = 1.0f;
|
||||
float flatness = 0.5;
|
||||
float flatness = 1.0;
|
||||
__block MeshGenerator::Point startingPoint(0.0f, 0.0f);
|
||||
__block bool hasStartingPoint = false;
|
||||
__block std::vector<MeshGenerator::Path> paths;
|
||||
|
||||
@@ -126,13 +126,13 @@ public final class MeshAnimation {
|
||||
|
||||
static func read(buffer: MeshReadBuffer) -> Segment {
|
||||
let vertCount = Int(buffer.readInt32())
|
||||
var vertices = Data(count: 4 * vertCount * 2)
|
||||
var vertices = Data(count: vertCount)
|
||||
vertices.withUnsafeMutableBytes { bytes in
|
||||
buffer.read(bytes.baseAddress!, length: bytes.count)
|
||||
}
|
||||
|
||||
let triCount = Int(buffer.readInt32())
|
||||
var triangles = Data(count: 3 * triCount * 4)
|
||||
var triangles = Data(count: triCount)
|
||||
triangles.withUnsafeMutableBytes { bytes in
|
||||
buffer.read(bytes.baseAddress!, length: bytes.count)
|
||||
}
|
||||
@@ -207,6 +207,7 @@ public final class MeshRenderer: MTKView {
|
||||
private final class RenderingMesh {
|
||||
let mesh: MeshAnimation
|
||||
let offset: CGPoint
|
||||
let loop: Bool
|
||||
var currentFrame: Int = 0
|
||||
let vertexBuffer: MTLBuffer
|
||||
let indexBuffer: MTLBuffer
|
||||
@@ -214,9 +215,10 @@ public final class MeshRenderer: MTKView {
|
||||
let maxVertices: Int
|
||||
let maxTriangles: Int
|
||||
|
||||
init(device: MTLDevice, mesh: MeshAnimation, offset: CGPoint) {
|
||||
init(device: MTLDevice, mesh: MeshAnimation, offset: CGPoint, loop: Bool) {
|
||||
self.mesh = mesh
|
||||
self.offset = offset
|
||||
self.loop = loop
|
||||
|
||||
var maxTriangles = 0
|
||||
var maxVertices = 0
|
||||
@@ -254,6 +256,7 @@ public final class MeshRenderer: MTKView {
|
||||
}
|
||||
}
|
||||
|
||||
private let wireframe: Bool
|
||||
private let commandQueue: MTLCommandQueue
|
||||
private let drawPassthroughPipelineState: MTLRenderPipelineState
|
||||
private let drawRadialGradientPipelineStates: [Int: MTLRenderPipelineState]
|
||||
@@ -272,6 +275,8 @@ public final class MeshRenderer: MTKView {
|
||||
public var allAnimationsCompleted: (() -> Void)?
|
||||
|
||||
public init?(wireframe: Bool = false) {
|
||||
self.wireframe = wireframe
|
||||
|
||||
let mainBundle = Bundle(for: MeshRenderer.self)
|
||||
|
||||
guard let path = mainBundle.path(forResource: "LottieMeshSwiftBundle", ofType: "bundle") else {
|
||||
@@ -357,6 +362,7 @@ public final class MeshRenderer: MTKView {
|
||||
self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent))
|
||||
if #available(iOS 15.0, *) {
|
||||
self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 60.0, preferred: 60.0)
|
||||
//self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 10.0, maximum: 60.0, preferred: 10.0)
|
||||
}
|
||||
self.displayLink?.add(to: .main, forMode: .common)
|
||||
self.displayLink?.isPaused = false
|
||||
@@ -372,8 +378,8 @@ public final class MeshRenderer: MTKView {
|
||||
self.displayLink?.invalidate()
|
||||
}
|
||||
|
||||
public func add(mesh: MeshAnimation, offset: CGPoint) {
|
||||
self.meshes.append(RenderingMesh(device: self.device!, mesh: mesh, offset: offset))
|
||||
public func add(mesh: MeshAnimation, offset: CGPoint, loop: Bool = false) {
|
||||
self.meshes.append(RenderingMesh(device: self.device!, mesh: mesh, offset: offset, loop: loop))
|
||||
}
|
||||
|
||||
@objc private func displayLinkEvent() {
|
||||
@@ -400,9 +406,9 @@ public final class MeshRenderer: MTKView {
|
||||
}
|
||||
|
||||
renderEncoder.setCullMode(.none)
|
||||
/*if displayDebug {
|
||||
if self.wireframe {
|
||||
renderEncoder.setTriangleFillMode(.lines)
|
||||
}*/
|
||||
}
|
||||
|
||||
func addTriangle(vertexData: UnsafeMutablePointer<Float>, maxVertices: Int, nextVertexIndex: inout Int, vertices: Data, triangles: Data, triangleIndex: Int) {
|
||||
assert(nextVertexIndex + 3 <= maxVertices)
|
||||
@@ -534,7 +540,11 @@ public final class MeshRenderer: MTKView {
|
||||
|
||||
let nextFrame = mesh.currentFrame + 1
|
||||
if nextFrame >= mesh.mesh.frames.count {
|
||||
removeMeshes.append(i)
|
||||
if mesh.loop {
|
||||
mesh.currentFrame = 0
|
||||
} else {
|
||||
removeMeshes.append(i)
|
||||
}
|
||||
} else {
|
||||
mesh.currentFrame = nextFrame
|
||||
}
|
||||
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Mikko Mononen, July 2009.
|
||||
*/
|
||||
|
||||
#ifndef TESSELATOR_H
|
||||
#define TESSELATOR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// See OpenGL Red Book for description of the winding rules
|
||||
// http://www.glprogramming.com/red/chapter11.html
|
||||
enum TessWindingRule
|
||||
{
|
||||
TESS_WINDING_ODD,
|
||||
TESS_WINDING_NONZERO,
|
||||
TESS_WINDING_POSITIVE,
|
||||
TESS_WINDING_NEGATIVE,
|
||||
TESS_WINDING_ABS_GEQ_TWO,
|
||||
};
|
||||
|
||||
// The contents of the tessGetElements() depends on element type being passed to tessTesselate().
|
||||
// Tesselation result element types:
|
||||
// TESS_POLYGONS
|
||||
// Each element in the element array is polygon defined as 'polySize' number of vertex indices.
|
||||
// If a polygon has than 'polySize' vertices, the remaining indices are stored as TESS_UNDEF.
|
||||
// Example, drawing a polygon:
|
||||
// const int nelems = tessGetElementCount(tess);
|
||||
// const TESSindex* elems = tessGetElements(tess);
|
||||
// for (int i = 0; i < nelems; i++) {
|
||||
// const TESSindex* poly = &elems[i * polySize];
|
||||
// glBegin(GL_POLYGON);
|
||||
// for (int j = 0; j < polySize; j++) {
|
||||
// if (poly[j] == TESS_UNDEF) break;
|
||||
// glVertex2fv(&verts[poly[j]*vertexSize]);
|
||||
// }
|
||||
// glEnd();
|
||||
// }
|
||||
//
|
||||
// TESS_CONNECTED_POLYGONS
|
||||
// Each element in the element array is polygon defined as 'polySize' number of vertex indices,
|
||||
// followed by 'polySize' indices to neighour polygons, that is each element is 'polySize' * 2 indices.
|
||||
// If a polygon has than 'polySize' vertices, the remaining indices are stored as TESS_UNDEF.
|
||||
// If a polygon edge is a boundary, that is, not connected to another polygon, the neighbour index is TESS_UNDEF.
|
||||
// Example, flood fill based on seed polygon:
|
||||
// const int nelems = tessGetElementCount(tess);
|
||||
// const TESSindex* elems = tessGetElements(tess);
|
||||
// unsigned char* visited = (unsigned char*)calloc(nelems);
|
||||
// TESSindex stack[50];
|
||||
// int nstack = 0;
|
||||
// stack[nstack++] = seedPoly;
|
||||
// visited[startPoly] = 1;
|
||||
// while (nstack > 0) {
|
||||
// TESSindex idx = stack[--nstack];
|
||||
// const TESSindex* poly = &elems[idx * polySize * 2];
|
||||
// const TESSindex* nei = &poly[polySize];
|
||||
// for (int i = 0; i < polySize; i++) {
|
||||
// if (poly[i] == TESS_UNDEF) break;
|
||||
// if (nei[i] != TESS_UNDEF && !visited[nei[i]])
|
||||
// stack[nstack++] = nei[i];
|
||||
// visited[nei[i]] = 1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// TESS_BOUNDARY_CONTOURS
|
||||
// Each element in the element array is [base index, count] pair defining a range of vertices for a contour.
|
||||
// The first value is index to first vertex in contour and the second value is number of vertices in the contour.
|
||||
// Example, drawing contours:
|
||||
// const int nelems = tessGetElementCount(tess);
|
||||
// const TESSindex* elems = tessGetElements(tess);
|
||||
// for (int i = 0; i < nelems; i++) {
|
||||
// const TESSindex base = elems[i * 2];
|
||||
// const TESSindex count = elems[i * 2 + 1];
|
||||
// glBegin(GL_LINE_LOOP);
|
||||
// for (int j = 0; j < count; j++) {
|
||||
// glVertex2fv(&verts[(base+j) * vertexSize]);
|
||||
// }
|
||||
// glEnd();
|
||||
// }
|
||||
|
||||
enum TessElementType
|
||||
{
|
||||
TESS_POLYGONS,
|
||||
TESS_CONNECTED_POLYGONS,
|
||||
TESS_BOUNDARY_CONTOURS,
|
||||
};
|
||||
|
||||
|
||||
// TESS_CONSTRAINED_DELAUNAY_TRIANGULATION
|
||||
// If enabled, the initial triagulation is improved with non-robust Constrained Delayney triangulation.
|
||||
// Disable by default.
|
||||
//
|
||||
// TESS_REVERSE_CONTOURS
|
||||
// If enabled, tessAddContour() will treat CW contours as CCW and vice versa
|
||||
// Disabled by default.
|
||||
|
||||
enum TessOption
|
||||
{
|
||||
TESS_CONSTRAINED_DELAUNAY_TRIANGULATION,
|
||||
TESS_REVERSE_CONTOURS
|
||||
};
|
||||
|
||||
typedef float TESSreal;
|
||||
typedef int TESSindex;
|
||||
typedef struct TESStesselator TESStesselator;
|
||||
typedef struct TESSalloc TESSalloc;
|
||||
|
||||
#define TESS_UNDEF (~(TESSindex)0)
|
||||
|
||||
#define TESS_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0)
|
||||
|
||||
// Custom memory allocator interface.
|
||||
// The internal memory allocator allocates mesh edges, vertices and faces
|
||||
// as well as dictionary nodes and active regions in buckets and uses simple
|
||||
// freelist to speed up the allocation. The bucket size should roughly match your
|
||||
// expected input data. For example if you process only hundreds of vertices,
|
||||
// a bucket size of 128 might be ok, where as when processing thousands of vertices
|
||||
// bucket size of 1024 might be approproate. The bucket size is a compromise between
|
||||
// how often to allocate memory from the system versus how much extra space the system
|
||||
// should allocate. Reasonable defaults are show in commects below, they will be used if
|
||||
// the bucket sizes are zero.
|
||||
//
|
||||
// The use may left the memrealloc to be null. In that case, the tesselator will not try to
|
||||
// dynamically grow int's internal arrays. The tesselator only needs the reallocation when it
|
||||
// has found intersecting segments and needs to add new vertex. This defency can be cured by
|
||||
// allocating some extra vertices beforehand. The 'extraVertices' variable allows to specify
|
||||
// number of expected extra vertices.
|
||||
struct TESSalloc
|
||||
{
|
||||
void *(*memalloc)( void *userData, unsigned int size );
|
||||
void *(*memrealloc)( void *userData, void* ptr, unsigned int size );
|
||||
void (*memfree)( void *userData, void *ptr );
|
||||
void* userData; // User data passed to the allocator functions.
|
||||
int meshEdgeBucketSize; // 512
|
||||
int meshVertexBucketSize; // 512
|
||||
int meshFaceBucketSize; // 256
|
||||
int dictNodeBucketSize; // 512
|
||||
int regionBucketSize; // 256
|
||||
int extraVertices; // Number of extra vertices allocated for the priority queue.
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Example use:
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
// tessNewTess() - Creates a new tesselator.
|
||||
// Use tessDeleteTess() to delete the tesselator.
|
||||
// Parameters:
|
||||
// alloc - pointer to a filled TESSalloc struct or NULL to use default malloc based allocator.
|
||||
// Returns:
|
||||
// new tesselator object.
|
||||
TESStesselator* tessNewTess( TESSalloc* alloc );
|
||||
|
||||
// tessDeleteTess() - Deletes a tesselator.
|
||||
// Parameters:
|
||||
// tess - pointer to tesselator object to be deleted.
|
||||
void tessDeleteTess( TESStesselator *tess );
|
||||
|
||||
// tessAddContour() - Adds a contour to be tesselated.
|
||||
// The type of the vertex coordinates is assumed to be TESSreal.
|
||||
// Parameters:
|
||||
// tess - pointer to tesselator object.
|
||||
// size - number of coordinates per vertex. Must be 2 or 3.
|
||||
// pointer - pointer to the first coordinate of the first vertex in the array.
|
||||
// stride - defines offset in bytes between consecutive vertices.
|
||||
// count - number of vertices in contour.
|
||||
void tessAddContour( TESStesselator *tess, int size, const void* pointer, int stride, int count );
|
||||
|
||||
// tessSetOption() - Toggles optional tessellation parameters
|
||||
// Parameters:
|
||||
// option - one of TessOption
|
||||
// value - 1 if enabled, 0 if disabled.
|
||||
void tessSetOption( TESStesselator *tess, int option, int value );
|
||||
|
||||
// tessTesselate() - tesselate contours.
|
||||
// Parameters:
|
||||
// tess - pointer to tesselator object.
|
||||
// windingRule - winding rules used for tesselation, must be one of TessWindingRule.
|
||||
// elementType - defines the tesselation result element type, must be one of TessElementType.
|
||||
// polySize - defines maximum vertices per polygons if output is polygons.
|
||||
// vertexSize - defines the number of coordinates in tesselation result vertex, must be 2 or 3.
|
||||
// normal - defines the normal of the input contours, of null the normal is calculated automatically.
|
||||
// Returns:
|
||||
// 1 if succeed, 0 if failed.
|
||||
int tessTesselate( TESStesselator *tess, int windingRule, int elementType, int polySize, int vertexSize, const TESSreal* normal );
|
||||
|
||||
// tessGetVertexCount() - Returns number of vertices in the tesselated output.
|
||||
int tessGetVertexCount( TESStesselator *tess );
|
||||
|
||||
// tessGetVertices() - Returns pointer to first coordinate of first vertex.
|
||||
const TESSreal* tessGetVertices( TESStesselator *tess );
|
||||
|
||||
// tessGetVertexIndices() - Returns pointer to first vertex index.
|
||||
// Vertex indices can be used to map the generated vertices to the original vertices.
|
||||
// Every point added using tessAddContour() will get a new index starting at 0.
|
||||
// New vertices generated at the intersections of segments are assigned value TESS_UNDEF.
|
||||
const TESSindex* tessGetVertexIndices( TESStesselator *tess );
|
||||
|
||||
// tessGetElementCount() - Returns number of elements in the the tesselated output.
|
||||
int tessGetElementCount( TESStesselator *tess );
|
||||
|
||||
// tessGetElements() - Returns pointer to the first element.
|
||||
const TESSindex* tessGetElements( TESStesselator *tess );
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // TESSELATOR_H
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Mikko Mononen, July 2009.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "../Include/tesselator.h"
|
||||
|
||||
//#define CHECK_BOUNDS
|
||||
|
||||
typedef struct BucketAlloc BucketAlloc;
|
||||
typedef struct Bucket Bucket;
|
||||
|
||||
struct Bucket
|
||||
{
|
||||
Bucket *next;
|
||||
};
|
||||
|
||||
struct BucketAlloc
|
||||
{
|
||||
void *freelist;
|
||||
Bucket *buckets;
|
||||
unsigned int itemSize;
|
||||
unsigned int bucketSize;
|
||||
const char *name;
|
||||
TESSalloc* alloc;
|
||||
};
|
||||
|
||||
static int CreateBucket( struct BucketAlloc* ba )
|
||||
{
|
||||
size_t size;
|
||||
Bucket* bucket;
|
||||
void* freelist;
|
||||
unsigned char* head;
|
||||
unsigned char* it;
|
||||
|
||||
// Allocate memory for the bucket
|
||||
size = sizeof(Bucket) + ba->itemSize * ba->bucketSize;
|
||||
bucket = (Bucket*)ba->alloc->memalloc( ba->alloc->userData, size );
|
||||
if ( !bucket )
|
||||
return 0;
|
||||
bucket->next = 0;
|
||||
|
||||
// Add the bucket into the list of buckets.
|
||||
bucket->next = ba->buckets;
|
||||
ba->buckets = bucket;
|
||||
|
||||
// Add new items to the free list.
|
||||
freelist = ba->freelist;
|
||||
head = (unsigned char*)bucket + sizeof(Bucket);
|
||||
it = head + ba->itemSize * ba->bucketSize;
|
||||
do
|
||||
{
|
||||
it -= ba->itemSize;
|
||||
// Store pointer to next free item.
|
||||
*((void**)it) = freelist;
|
||||
// Pointer to next location containing a free item.
|
||||
freelist = (void*)it;
|
||||
}
|
||||
while ( it != head );
|
||||
// Update pointer to next location containing a free item.
|
||||
ba->freelist = (void*)it;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void *NextFreeItem( struct BucketAlloc *ba )
|
||||
{
|
||||
return *(void**)ba->freelist;
|
||||
}
|
||||
|
||||
struct BucketAlloc* createBucketAlloc( TESSalloc* alloc, const char* name,
|
||||
unsigned int itemSize, unsigned int bucketSize )
|
||||
{
|
||||
BucketAlloc* ba = (BucketAlloc*)alloc->memalloc( alloc->userData, sizeof(BucketAlloc) );
|
||||
|
||||
ba->alloc = alloc;
|
||||
ba->name = name;
|
||||
ba->itemSize = itemSize;
|
||||
if ( ba->itemSize < sizeof(void*) )
|
||||
ba->itemSize = sizeof(void*);
|
||||
ba->bucketSize = bucketSize;
|
||||
ba->freelist = 0;
|
||||
ba->buckets = 0;
|
||||
|
||||
if ( !CreateBucket( ba ) )
|
||||
{
|
||||
alloc->memfree( alloc->userData, ba );
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
void* bucketAlloc( struct BucketAlloc *ba )
|
||||
{
|
||||
void *it;
|
||||
|
||||
// If running out of memory, allocate new bucket and update the freelist.
|
||||
if ( !ba->freelist || !NextFreeItem( ba ) )
|
||||
{
|
||||
if ( !CreateBucket( ba ) )
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Pop item from in front of the free list.
|
||||
it = ba->freelist;
|
||||
ba->freelist = NextFreeItem( ba );
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
void bucketFree( struct BucketAlloc *ba, void *ptr )
|
||||
{
|
||||
#ifdef CHECK_BOUNDS
|
||||
int inBounds = 0;
|
||||
Bucket *bucket;
|
||||
|
||||
// Check that the pointer is allocated with this allocator.
|
||||
bucket = ba->buckets;
|
||||
while ( bucket )
|
||||
{
|
||||
void *bucketMin = (void*)((unsigned char*)bucket + sizeof(Bucket));
|
||||
void *bucketMax = (void*)((unsigned char*)bucket + sizeof(Bucket) + ba->itemSize * ba->bucketSize);
|
||||
if ( ptr >= bucketMin && ptr < bucketMax )
|
||||
{
|
||||
inBounds = 1;
|
||||
break;
|
||||
}
|
||||
bucket = bucket->next;
|
||||
}
|
||||
|
||||
if ( inBounds )
|
||||
{
|
||||
// Add the node in front of the free list.
|
||||
*(void**)ptr = ba->freelist;
|
||||
ba->freelist = ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("ERROR! pointer 0x%p does not belong to allocator '%s'\n", ba->name);
|
||||
}
|
||||
#else
|
||||
// Add the node in front of the free list.
|
||||
*(void**)ptr = ba->freelist;
|
||||
ba->freelist = ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void deleteBucketAlloc( struct BucketAlloc *ba )
|
||||
{
|
||||
TESSalloc* alloc = ba->alloc;
|
||||
Bucket *bucket = ba->buckets;
|
||||
Bucket *next;
|
||||
while ( bucket )
|
||||
{
|
||||
next = bucket->next;
|
||||
alloc->memfree( alloc->userData, bucket );
|
||||
bucket = next;
|
||||
}
|
||||
ba->freelist = 0;
|
||||
ba->buckets = 0;
|
||||
alloc->memfree( alloc->userData, ba );
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Mikko Mononen, July 2009.
|
||||
*/
|
||||
|
||||
#ifndef MEMALLOC_H
|
||||
#define MEMALLOC_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "tesselator.h"
|
||||
|
||||
struct BucketAlloc *createBucketAlloc( TESSalloc* alloc, const char *name,
|
||||
unsigned int itemSize, unsigned int bucketSize );
|
||||
void *bucketAlloc( struct BucketAlloc *ba);
|
||||
void bucketFree( struct BucketAlloc *ba, void *ptr );
|
||||
void deleteBucketAlloc( struct BucketAlloc *ba );
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include "../Include/tesselator.h"
|
||||
#include "bucketalloc.h"
|
||||
#include "dict.h"
|
||||
|
||||
/* really tessDictListNewDict */
|
||||
Dict *dictNewDict( TESSalloc* alloc, void *frame, int (*leq)(void *frame, DictKey key1, DictKey key2) )
|
||||
{
|
||||
Dict *dict = (Dict *)alloc->memalloc( alloc->userData, sizeof( Dict ));
|
||||
DictNode *head;
|
||||
|
||||
if (dict == NULL) return NULL;
|
||||
|
||||
head = &dict->head;
|
||||
|
||||
head->key = NULL;
|
||||
head->next = head;
|
||||
head->prev = head;
|
||||
|
||||
dict->frame = frame;
|
||||
dict->leq = leq;
|
||||
|
||||
if (alloc->dictNodeBucketSize < 16)
|
||||
alloc->dictNodeBucketSize = 16;
|
||||
if (alloc->dictNodeBucketSize > 4096)
|
||||
alloc->dictNodeBucketSize = 4096;
|
||||
dict->nodePool = createBucketAlloc( alloc, "Dict", sizeof(DictNode), alloc->dictNodeBucketSize );
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
/* really tessDictListDeleteDict */
|
||||
void dictDeleteDict( TESSalloc* alloc, Dict *dict )
|
||||
{
|
||||
deleteBucketAlloc( dict->nodePool );
|
||||
alloc->memfree( alloc->userData, dict );
|
||||
}
|
||||
|
||||
/* really tessDictListInsertBefore */
|
||||
DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key )
|
||||
{
|
||||
DictNode *newNode;
|
||||
|
||||
do {
|
||||
node = node->prev;
|
||||
} while( node->key != NULL && ! (*dict->leq)(dict->frame, node->key, key));
|
||||
|
||||
newNode = (DictNode *)bucketAlloc( dict->nodePool );
|
||||
if (newNode == NULL) return NULL;
|
||||
|
||||
newNode->key = key;
|
||||
newNode->next = node->next;
|
||||
node->next->prev = newNode;
|
||||
newNode->prev = node;
|
||||
node->next = newNode;
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/* really tessDictListDelete */
|
||||
void dictDelete( Dict *dict, DictNode *node ) /*ARGSUSED*/
|
||||
{
|
||||
node->next->prev = node->prev;
|
||||
node->prev->next = node->next;
|
||||
bucketFree( dict->nodePool, node );
|
||||
}
|
||||
|
||||
/* really tessDictListSearch */
|
||||
DictNode *dictSearch( Dict *dict, DictKey key )
|
||||
{
|
||||
DictNode *node = &dict->head;
|
||||
|
||||
do {
|
||||
node = node->next;
|
||||
} while( node->key != NULL && ! (*dict->leq)(dict->frame, key, node->key));
|
||||
|
||||
return node;
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
#ifndef DICT_LIST_H
|
||||
#define DICT_LIST_H
|
||||
|
||||
typedef void *DictKey;
|
||||
typedef struct Dict Dict;
|
||||
typedef struct DictNode DictNode;
|
||||
|
||||
Dict *dictNewDict( TESSalloc* alloc, void *frame, int (*leq)(void *frame, DictKey key1, DictKey key2) );
|
||||
|
||||
void dictDeleteDict( TESSalloc* alloc, Dict *dict );
|
||||
|
||||
/* Search returns the node with the smallest key greater than or equal
|
||||
* to the given key. If there is no such key, returns a node whose
|
||||
* key is NULL. Similarly, Succ(Max(d)) has a NULL key, etc.
|
||||
*/
|
||||
DictNode *dictSearch( Dict *dict, DictKey key );
|
||||
DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key );
|
||||
void dictDelete( Dict *dict, DictNode *node );
|
||||
|
||||
#define dictKey(n) ((n)->key)
|
||||
#define dictSucc(n) ((n)->next)
|
||||
#define dictPred(n) ((n)->prev)
|
||||
#define dictMin(d) ((d)->head.next)
|
||||
#define dictMax(d) ((d)->head.prev)
|
||||
#define dictInsert(d,k) (dictInsertBefore((d),&(d)->head,(k)))
|
||||
|
||||
|
||||
/*** Private data structures ***/
|
||||
|
||||
struct DictNode {
|
||||
DictKey key;
|
||||
DictNode *next;
|
||||
DictNode *prev;
|
||||
};
|
||||
|
||||
struct Dict {
|
||||
DictNode head;
|
||||
void *frame;
|
||||
struct BucketAlloc *nodePool;
|
||||
int (*leq)(void *frame, DictKey key1, DictKey key2);
|
||||
};
|
||||
|
||||
#endif
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
//#include "tesos.h"
|
||||
#include <assert.h>
|
||||
#include "mesh.h"
|
||||
#include "geom.h"
|
||||
#include <math.h>
|
||||
|
||||
int tesvertLeq( TESSvertex *u, TESSvertex *v )
|
||||
{
|
||||
/* Returns TRUE if u is lexicographically <= v. */
|
||||
|
||||
return VertLeq( u, v );
|
||||
}
|
||||
|
||||
TESSreal tesedgeEval( TESSvertex *u, TESSvertex *v, TESSvertex *w )
|
||||
{
|
||||
/* Given three vertices u,v,w such that VertLeq(u,v) && VertLeq(v,w),
|
||||
* evaluates the t-coord of the edge uw at the s-coord of the vertex v.
|
||||
* Returns v->t - (uw)(v->s), ie. the signed distance from uw to v.
|
||||
* If uw is vertical (and thus passes thru v), the result is zero.
|
||||
*
|
||||
* The calculation is extremely accurate and stable, even when v
|
||||
* is very close to u or w. In particular if we set v->t = 0 and
|
||||
* let r be the negated result (this evaluates (uw)(v->s)), then
|
||||
* r is guaranteed to satisfy MIN(u->t,w->t) <= r <= MAX(u->t,w->t).
|
||||
*/
|
||||
TESSreal gapL, gapR;
|
||||
|
||||
assert( VertLeq( u, v ) && VertLeq( v, w ));
|
||||
|
||||
gapL = v->s - u->s;
|
||||
gapR = w->s - v->s;
|
||||
|
||||
if( gapL + gapR > 0 ) {
|
||||
if( gapL < gapR ) {
|
||||
return (v->t - u->t) + (u->t - w->t) * (gapL / (gapL + gapR));
|
||||
} else {
|
||||
return (v->t - w->t) + (w->t - u->t) * (gapR / (gapL + gapR));
|
||||
}
|
||||
}
|
||||
/* vertical line */
|
||||
return 0;
|
||||
}
|
||||
|
||||
TESSreal tesedgeSign( TESSvertex *u, TESSvertex *v, TESSvertex *w )
|
||||
{
|
||||
/* Returns a number whose sign matches EdgeEval(u,v,w) but which
|
||||
* is cheaper to evaluate. Returns > 0, == 0 , or < 0
|
||||
* as v is above, on, or below the edge uw.
|
||||
*/
|
||||
TESSreal gapL, gapR;
|
||||
|
||||
assert( VertLeq( u, v ) && VertLeq( v, w ));
|
||||
|
||||
gapL = v->s - u->s;
|
||||
gapR = w->s - v->s;
|
||||
|
||||
if( gapL + gapR > 0 ) {
|
||||
return (v->t - w->t) * gapL + (v->t - u->t) * gapR;
|
||||
}
|
||||
/* vertical line */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************
|
||||
* Define versions of EdgeSign, EdgeEval with s and t transposed.
|
||||
*/
|
||||
|
||||
TESSreal testransEval( TESSvertex *u, TESSvertex *v, TESSvertex *w )
|
||||
{
|
||||
/* Given three vertices u,v,w such that TransLeq(u,v) && TransLeq(v,w),
|
||||
* evaluates the t-coord of the edge uw at the s-coord of the vertex v.
|
||||
* Returns v->s - (uw)(v->t), ie. the signed distance from uw to v.
|
||||
* If uw is vertical (and thus passes thru v), the result is zero.
|
||||
*
|
||||
* The calculation is extremely accurate and stable, even when v
|
||||
* is very close to u or w. In particular if we set v->s = 0 and
|
||||
* let r be the negated result (this evaluates (uw)(v->t)), then
|
||||
* r is guaranteed to satisfy MIN(u->s,w->s) <= r <= MAX(u->s,w->s).
|
||||
*/
|
||||
TESSreal gapL, gapR;
|
||||
|
||||
assert( TransLeq( u, v ) && TransLeq( v, w ));
|
||||
|
||||
gapL = v->t - u->t;
|
||||
gapR = w->t - v->t;
|
||||
|
||||
if( gapL + gapR > 0 ) {
|
||||
if( gapL < gapR ) {
|
||||
return (v->s - u->s) + (u->s - w->s) * (gapL / (gapL + gapR));
|
||||
} else {
|
||||
return (v->s - w->s) + (w->s - u->s) * (gapR / (gapL + gapR));
|
||||
}
|
||||
}
|
||||
/* vertical line */
|
||||
return 0;
|
||||
}
|
||||
|
||||
TESSreal testransSign( TESSvertex *u, TESSvertex *v, TESSvertex *w )
|
||||
{
|
||||
/* Returns a number whose sign matches TransEval(u,v,w) but which
|
||||
* is cheaper to evaluate. Returns > 0, == 0 , or < 0
|
||||
* as v is above, on, or below the edge uw.
|
||||
*/
|
||||
TESSreal gapL, gapR;
|
||||
|
||||
assert( TransLeq( u, v ) && TransLeq( v, w ));
|
||||
|
||||
gapL = v->t - u->t;
|
||||
gapR = w->t - v->t;
|
||||
|
||||
if( gapL + gapR > 0 ) {
|
||||
return (v->s - w->s) * gapL + (v->s - u->s) * gapR;
|
||||
}
|
||||
/* vertical line */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int tesvertCCW( TESSvertex *u, TESSvertex *v, TESSvertex *w )
|
||||
{
|
||||
/* For almost-degenerate situations, the results are not reliable.
|
||||
* Unless the floating-point arithmetic can be performed without
|
||||
* rounding errors, *any* implementation will give incorrect results
|
||||
* on some degenerate inputs, so the client must have some way to
|
||||
* handle this situation.
|
||||
*/
|
||||
return (u->s*(v->t - w->t) + v->s*(w->t - u->t) + w->s*(u->t - v->t)) >= 0;
|
||||
}
|
||||
|
||||
/* Given parameters a,x,b,y returns the value (b*x+a*y)/(a+b),
|
||||
* or (x+y)/2 if a==b==0. It requires that a,b >= 0, and enforces
|
||||
* this in the rare case that one argument is slightly negative.
|
||||
* The implementation is extremely stable numerically.
|
||||
* In particular it guarantees that the result r satisfies
|
||||
* MIN(x,y) <= r <= MAX(x,y), and the results are very accurate
|
||||
* even when a and b differ greatly in magnitude.
|
||||
*/
|
||||
#define RealInterpolate(a,x,b,y) \
|
||||
(a = (a < 0) ? 0 : a, b = (b < 0) ? 0 : b, \
|
||||
((a <= b) ? ((b == 0) ? ((x+y) / 2) \
|
||||
: (x + (y-x) * (a/(a+b)))) \
|
||||
: (y + (x-y) * (b/(a+b)))))
|
||||
|
||||
#ifndef FOR_TRITE_TEST_PROGRAM
|
||||
#define Interpolate(a,x,b,y) RealInterpolate(a,x,b,y)
|
||||
#else
|
||||
|
||||
/* Claim: the ONLY property the sweep algorithm relies on is that
|
||||
* MIN(x,y) <= r <= MAX(x,y). This is a nasty way to test that.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
extern int RandomInterpolate;
|
||||
|
||||
double Interpolate( double a, double x, double b, double y)
|
||||
{
|
||||
printf("*********************%d\n",RandomInterpolate);
|
||||
if( RandomInterpolate ) {
|
||||
a = 1.2 * drand48() - 0.1;
|
||||
a = (a < 0) ? 0 : ((a > 1) ? 1 : a);
|
||||
b = 1.0 - a;
|
||||
}
|
||||
return RealInterpolate(a,x,b,y);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#define Swap(a,b) if (1) { TESSvertex *t = a; a = b; b = t; } else
|
||||
|
||||
void tesedgeIntersect( TESSvertex *o1, TESSvertex *d1,
|
||||
TESSvertex *o2, TESSvertex *d2,
|
||||
TESSvertex *v )
|
||||
/* Given edges (o1,d1) and (o2,d2), compute their point of intersection.
|
||||
* The computed point is guaranteed to lie in the intersection of the
|
||||
* bounding rectangles defined by each edge.
|
||||
*/
|
||||
{
|
||||
TESSreal z1, z2;
|
||||
|
||||
/* This is certainly not the most efficient way to find the intersection
|
||||
* of two line segments, but it is very numerically stable.
|
||||
*
|
||||
* Strategy: find the two middle vertices in the VertLeq ordering,
|
||||
* and interpolate the intersection s-value from these. Then repeat
|
||||
* using the TransLeq ordering to find the intersection t-value.
|
||||
*/
|
||||
|
||||
if( ! VertLeq( o1, d1 )) { Swap( o1, d1 ); }
|
||||
if( ! VertLeq( o2, d2 )) { Swap( o2, d2 ); }
|
||||
if( ! VertLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); }
|
||||
|
||||
if( ! VertLeq( o2, d1 )) {
|
||||
/* Technically, no intersection -- do our best */
|
||||
v->s = (o2->s + d1->s) / 2;
|
||||
} else if( VertLeq( d1, d2 )) {
|
||||
/* Interpolate between o2 and d1 */
|
||||
z1 = EdgeEval( o1, o2, d1 );
|
||||
z2 = EdgeEval( o2, d1, d2 );
|
||||
if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
|
||||
v->s = Interpolate( z1, o2->s, z2, d1->s );
|
||||
} else {
|
||||
/* Interpolate between o2 and d2 */
|
||||
z1 = EdgeSign( o1, o2, d1 );
|
||||
z2 = -EdgeSign( o1, d2, d1 );
|
||||
if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
|
||||
v->s = Interpolate( z1, o2->s, z2, d2->s );
|
||||
}
|
||||
|
||||
/* Now repeat the process for t */
|
||||
|
||||
if( ! TransLeq( o1, d1 )) { Swap( o1, d1 ); }
|
||||
if( ! TransLeq( o2, d2 )) { Swap( o2, d2 ); }
|
||||
if( ! TransLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); }
|
||||
|
||||
if( ! TransLeq( o2, d1 )) {
|
||||
/* Technically, no intersection -- do our best */
|
||||
v->t = (o2->t + d1->t) / 2;
|
||||
} else if( TransLeq( d1, d2 )) {
|
||||
/* Interpolate between o2 and d1 */
|
||||
z1 = TransEval( o1, o2, d1 );
|
||||
z2 = TransEval( o2, d1, d2 );
|
||||
if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
|
||||
v->t = Interpolate( z1, o2->t, z2, d1->t );
|
||||
} else {
|
||||
/* Interpolate between o2 and d2 */
|
||||
z1 = TransSign( o1, o2, d1 );
|
||||
z2 = -TransSign( o1, d2, d1 );
|
||||
if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; }
|
||||
v->t = Interpolate( z1, o2->t, z2, d2->t );
|
||||
}
|
||||
}
|
||||
|
||||
TESSreal inCircle( TESSvertex *v, TESSvertex *v0, TESSvertex *v1, TESSvertex *v2 ) {
|
||||
TESSreal adx, ady, bdx, bdy, cdx, cdy;
|
||||
TESSreal abdet, bcdet, cadet;
|
||||
TESSreal alift, blift, clift;
|
||||
|
||||
adx = v0->s - v->s;
|
||||
ady = v0->t - v->t;
|
||||
bdx = v1->s - v->s;
|
||||
bdy = v1->t - v->t;
|
||||
cdx = v2->s - v->s;
|
||||
cdy = v2->t - v->t;
|
||||
|
||||
abdet = adx * bdy - bdx * ady;
|
||||
bcdet = bdx * cdy - cdx * bdy;
|
||||
cadet = cdx * ady - adx * cdy;
|
||||
|
||||
alift = adx * adx + ady * ady;
|
||||
blift = bdx * bdx + bdy * bdy;
|
||||
clift = cdx * cdx + cdy * cdy;
|
||||
|
||||
return alift * bcdet + blift * cadet + clift * abdet;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns 1 is edge is locally delaunay
|
||||
*/
|
||||
int tesedgeIsLocallyDelaunay( TESShalfEdge *e )
|
||||
{
|
||||
return inCircle(e->Sym->Lnext->Lnext->Org, e->Lnext->Org, e->Lnext->Lnext->Org, e->Org) < 0;
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
#ifndef GEOM_H
|
||||
#define GEOM_H
|
||||
|
||||
#include "mesh.h"
|
||||
|
||||
#ifdef NO_BRANCH_CONDITIONS
|
||||
/* MIPS architecture has special instructions to evaluate boolean
|
||||
* conditions -- more efficient than branching, IF you can get the
|
||||
* compiler to generate the right instructions (SGI compiler doesn't)
|
||||
*/
|
||||
#define VertEq(u,v) (((u)->s == (v)->s) & ((u)->t == (v)->t))
|
||||
#define VertLeq(u,v) (((u)->s < (v)->s) | \
|
||||
((u)->s == (v)->s & (u)->t <= (v)->t))
|
||||
#else
|
||||
#define VertEq(u,v) ((u)->s == (v)->s && (u)->t == (v)->t)
|
||||
#define VertLeq(u,v) (((u)->s < (v)->s) || ((u)->s == (v)->s && (u)->t <= (v)->t))
|
||||
#endif
|
||||
|
||||
#define EdgeEval(u,v,w) tesedgeEval(u,v,w)
|
||||
#define EdgeSign(u,v,w) tesedgeSign(u,v,w)
|
||||
|
||||
/* Versions of VertLeq, EdgeSign, EdgeEval with s and t transposed. */
|
||||
|
||||
#define TransLeq(u,v) (((u)->t < (v)->t) || ((u)->t == (v)->t && (u)->s <= (v)->s))
|
||||
#define TransEval(u,v,w) testransEval(u,v,w)
|
||||
#define TransSign(u,v,w) testransSign(u,v,w)
|
||||
|
||||
|
||||
#define EdgeGoesLeft(e) VertLeq( (e)->Dst, (e)->Org )
|
||||
#define EdgeGoesRight(e) VertLeq( (e)->Org, (e)->Dst )
|
||||
#define EdgeIsInternal(e) e->Rface && e->Rface->inside
|
||||
|
||||
#define ABS(x) ((x) < 0 ? -(x) : (x))
|
||||
#define VertL1dist(u,v) (ABS(u->s - v->s) + ABS(u->t - v->t))
|
||||
|
||||
#define VertCCW(u,v,w) tesvertCCW(u,v,w)
|
||||
|
||||
int tesvertLeq( TESSvertex *u, TESSvertex *v );
|
||||
TESSreal tesedgeEval( TESSvertex *u, TESSvertex *v, TESSvertex *w );
|
||||
TESSreal tesedgeSign( TESSvertex *u, TESSvertex *v, TESSvertex *w );
|
||||
TESSreal testransEval( TESSvertex *u, TESSvertex *v, TESSvertex *w );
|
||||
TESSreal testransSign( TESSvertex *u, TESSvertex *v, TESSvertex *w );
|
||||
int tesvertCCW( TESSvertex *u, TESSvertex *v, TESSvertex *w );
|
||||
void tesedgeIntersect( TESSvertex *o1, TESSvertex *d1, TESSvertex *o2, TESSvertex *d2, TESSvertex *v );
|
||||
int tesedgeIsLocallyDelaunay( TESShalfEdge *e );
|
||||
|
||||
#endif
|
||||
+917
@@ -0,0 +1,917 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
//#include "tesos.h"
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include "mesh.h"
|
||||
#include "geom.h"
|
||||
#include "bucketalloc.h"
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
/************************ Utility Routines ************************/
|
||||
|
||||
/* Allocate and free half-edges in pairs for efficiency.
|
||||
* The *only* place that should use this fact is allocation/free.
|
||||
*/
|
||||
typedef struct { TESShalfEdge e, eSym; } EdgePair;
|
||||
|
||||
/* MakeEdge creates a new pair of half-edges which form their own loop.
|
||||
* No vertex or face structures are allocated, but these must be assigned
|
||||
* before the current edge operation is completed.
|
||||
*/
|
||||
static TESShalfEdge *MakeEdge( TESSmesh* mesh, TESShalfEdge *eNext )
|
||||
{
|
||||
TESShalfEdge *e;
|
||||
TESShalfEdge *eSym;
|
||||
TESShalfEdge *ePrev;
|
||||
EdgePair *pair = (EdgePair *)bucketAlloc( mesh->edgeBucket );
|
||||
if (pair == NULL) return NULL;
|
||||
|
||||
e = &pair->e;
|
||||
eSym = &pair->eSym;
|
||||
|
||||
/* Make sure eNext points to the first edge of the edge pair */
|
||||
if( eNext->Sym < eNext ) { eNext = eNext->Sym; }
|
||||
|
||||
/* Insert in circular doubly-linked list before eNext.
|
||||
* Note that the prev pointer is stored in Sym->next.
|
||||
*/
|
||||
ePrev = eNext->Sym->next;
|
||||
eSym->next = ePrev;
|
||||
ePrev->Sym->next = e;
|
||||
e->next = eNext;
|
||||
eNext->Sym->next = eSym;
|
||||
|
||||
e->Sym = eSym;
|
||||
e->Onext = e;
|
||||
e->Lnext = eSym;
|
||||
e->Org = NULL;
|
||||
e->Lface = NULL;
|
||||
e->winding = 0;
|
||||
e->activeRegion = NULL;
|
||||
e->mark = 0;
|
||||
|
||||
eSym->Sym = e;
|
||||
eSym->Onext = eSym;
|
||||
eSym->Lnext = e;
|
||||
eSym->Org = NULL;
|
||||
eSym->Lface = NULL;
|
||||
eSym->winding = 0;
|
||||
eSym->activeRegion = NULL;
|
||||
eSym->mark = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
/* Splice( a, b ) is best described by the Guibas/Stolfi paper or the
|
||||
* CS348a notes (see mesh.h). Basically it modifies the mesh so that
|
||||
* a->Onext and b->Onext are exchanged. This can have various effects
|
||||
* depending on whether a and b belong to different face or vertex rings.
|
||||
* For more explanation see tessMeshSplice() below.
|
||||
*/
|
||||
static void Splice( TESShalfEdge *a, TESShalfEdge *b )
|
||||
{
|
||||
TESShalfEdge *aOnext = a->Onext;
|
||||
TESShalfEdge *bOnext = b->Onext;
|
||||
|
||||
aOnext->Sym->Lnext = b;
|
||||
bOnext->Sym->Lnext = a;
|
||||
a->Onext = bOnext;
|
||||
b->Onext = aOnext;
|
||||
}
|
||||
|
||||
/* MakeVertex( newVertex, eOrig, vNext ) attaches a new vertex and makes it the
|
||||
* origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives
|
||||
* a place to insert the new vertex in the global vertex list. We insert
|
||||
* the new vertex *before* vNext so that algorithms which walk the vertex
|
||||
* list will not see the newly created vertices.
|
||||
*/
|
||||
static void MakeVertex( TESSvertex *newVertex,
|
||||
TESShalfEdge *eOrig, TESSvertex *vNext )
|
||||
{
|
||||
TESShalfEdge *e;
|
||||
TESSvertex *vPrev;
|
||||
TESSvertex *vNew = newVertex;
|
||||
|
||||
assert(vNew != NULL);
|
||||
|
||||
/* insert in circular doubly-linked list before vNext */
|
||||
vPrev = vNext->prev;
|
||||
vNew->prev = vPrev;
|
||||
vPrev->next = vNew;
|
||||
vNew->next = vNext;
|
||||
vNext->prev = vNew;
|
||||
|
||||
vNew->anEdge = eOrig;
|
||||
/* leave coords, s, t undefined */
|
||||
|
||||
/* fix other edges on this vertex loop */
|
||||
e = eOrig;
|
||||
do {
|
||||
e->Org = vNew;
|
||||
e = e->Onext;
|
||||
} while( e != eOrig );
|
||||
}
|
||||
|
||||
/* MakeFace( newFace, eOrig, fNext ) attaches a new face and makes it the left
|
||||
* face of all edges in the face loop to which eOrig belongs. "fNext" gives
|
||||
* a place to insert the new face in the global face list. We insert
|
||||
* the new face *before* fNext so that algorithms which walk the face
|
||||
* list will not see the newly created faces.
|
||||
*/
|
||||
static void MakeFace( TESSface *newFace, TESShalfEdge *eOrig, TESSface *fNext )
|
||||
{
|
||||
TESShalfEdge *e;
|
||||
TESSface *fPrev;
|
||||
TESSface *fNew = newFace;
|
||||
|
||||
assert(fNew != NULL);
|
||||
|
||||
/* insert in circular doubly-linked list before fNext */
|
||||
fPrev = fNext->prev;
|
||||
fNew->prev = fPrev;
|
||||
fPrev->next = fNew;
|
||||
fNew->next = fNext;
|
||||
fNext->prev = fNew;
|
||||
|
||||
fNew->anEdge = eOrig;
|
||||
fNew->trail = NULL;
|
||||
fNew->marked = FALSE;
|
||||
|
||||
/* The new face is marked "inside" if the old one was. This is a
|
||||
* convenience for the common case where a face has been split in two.
|
||||
*/
|
||||
fNew->inside = fNext->inside;
|
||||
|
||||
/* fix other edges on this face loop */
|
||||
e = eOrig;
|
||||
do {
|
||||
e->Lface = fNew;
|
||||
e = e->Lnext;
|
||||
} while( e != eOrig );
|
||||
}
|
||||
|
||||
/* KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel->Sym),
|
||||
* and removes from the global edge list.
|
||||
*/
|
||||
static void KillEdge( TESSmesh *mesh, TESShalfEdge *eDel )
|
||||
{
|
||||
TESShalfEdge *ePrev, *eNext;
|
||||
|
||||
/* Half-edges are allocated in pairs, see EdgePair above */
|
||||
if( eDel->Sym < eDel ) { eDel = eDel->Sym; }
|
||||
|
||||
/* delete from circular doubly-linked list */
|
||||
eNext = eDel->next;
|
||||
ePrev = eDel->Sym->next;
|
||||
eNext->Sym->next = ePrev;
|
||||
ePrev->Sym->next = eNext;
|
||||
|
||||
bucketFree( mesh->edgeBucket, eDel );
|
||||
}
|
||||
|
||||
|
||||
/* KillVertex( vDel ) destroys a vertex and removes it from the global
|
||||
* vertex list. It updates the vertex loop to point to a given new vertex.
|
||||
*/
|
||||
static void KillVertex( TESSmesh *mesh, TESSvertex *vDel, TESSvertex *newOrg )
|
||||
{
|
||||
TESShalfEdge *e, *eStart = vDel->anEdge;
|
||||
TESSvertex *vPrev, *vNext;
|
||||
|
||||
/* change the origin of all affected edges */
|
||||
e = eStart;
|
||||
do {
|
||||
e->Org = newOrg;
|
||||
e = e->Onext;
|
||||
} while( e != eStart );
|
||||
|
||||
/* delete from circular doubly-linked list */
|
||||
vPrev = vDel->prev;
|
||||
vNext = vDel->next;
|
||||
vNext->prev = vPrev;
|
||||
vPrev->next = vNext;
|
||||
|
||||
bucketFree( mesh->vertexBucket, vDel );
|
||||
}
|
||||
|
||||
/* KillFace( fDel ) destroys a face and removes it from the global face
|
||||
* list. It updates the face loop to point to a given new face.
|
||||
*/
|
||||
static void KillFace( TESSmesh *mesh, TESSface *fDel, TESSface *newLface )
|
||||
{
|
||||
TESShalfEdge *e, *eStart = fDel->anEdge;
|
||||
TESSface *fPrev, *fNext;
|
||||
|
||||
/* change the left face of all affected edges */
|
||||
e = eStart;
|
||||
do {
|
||||
e->Lface = newLface;
|
||||
e = e->Lnext;
|
||||
} while( e != eStart );
|
||||
|
||||
/* delete from circular doubly-linked list */
|
||||
fPrev = fDel->prev;
|
||||
fNext = fDel->next;
|
||||
fNext->prev = fPrev;
|
||||
fPrev->next = fNext;
|
||||
|
||||
bucketFree( mesh->faceBucket, fDel );
|
||||
}
|
||||
|
||||
|
||||
/****************** Basic Edge Operations **********************/
|
||||
|
||||
/* tessMeshMakeEdge creates one edge, two vertices, and a loop (face).
|
||||
* The loop consists of the two new half-edges.
|
||||
*/
|
||||
TESShalfEdge *tessMeshMakeEdge( TESSmesh *mesh )
|
||||
{
|
||||
TESSvertex *newVertex1 = (TESSvertex*)bucketAlloc(mesh->vertexBucket);
|
||||
TESSvertex *newVertex2 = (TESSvertex*)bucketAlloc(mesh->vertexBucket);
|
||||
TESSface *newFace = (TESSface*)bucketAlloc(mesh->faceBucket);
|
||||
TESShalfEdge *e;
|
||||
|
||||
/* if any one is null then all get freed */
|
||||
if (newVertex1 == NULL || newVertex2 == NULL || newFace == NULL) {
|
||||
if (newVertex1 != NULL) bucketFree( mesh->vertexBucket, newVertex1 );
|
||||
if (newVertex2 != NULL) bucketFree( mesh->vertexBucket, newVertex2 );
|
||||
if (newFace != NULL) bucketFree( mesh->faceBucket, newFace );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
e = MakeEdge( mesh, &mesh->eHead );
|
||||
if (e == NULL) return NULL;
|
||||
|
||||
MakeVertex( newVertex1, e, &mesh->vHead );
|
||||
MakeVertex( newVertex2, e->Sym, &mesh->vHead );
|
||||
MakeFace( newFace, e, &mesh->fHead );
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
/* tessMeshSplice( eOrg, eDst ) is the basic operation for changing the
|
||||
* mesh connectivity and topology. It changes the mesh so that
|
||||
* eOrg->Onext <- OLD( eDst->Onext )
|
||||
* eDst->Onext <- OLD( eOrg->Onext )
|
||||
* where OLD(...) means the value before the meshSplice operation.
|
||||
*
|
||||
* This can have two effects on the vertex structure:
|
||||
* - if eOrg->Org != eDst->Org, the two vertices are merged together
|
||||
* - if eOrg->Org == eDst->Org, the origin is split into two vertices
|
||||
* In both cases, eDst->Org is changed and eOrg->Org is untouched.
|
||||
*
|
||||
* Similarly (and independently) for the face structure,
|
||||
* - if eOrg->Lface == eDst->Lface, one loop is split into two
|
||||
* - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one
|
||||
* In both cases, eDst->Lface is changed and eOrg->Lface is unaffected.
|
||||
*
|
||||
* Some special cases:
|
||||
* If eDst == eOrg, the operation has no effect.
|
||||
* If eDst == eOrg->Lnext, the new face will have a single edge.
|
||||
* If eDst == eOrg->Lprev, the old face will have a single edge.
|
||||
* If eDst == eOrg->Onext, the new vertex will have a single edge.
|
||||
* If eDst == eOrg->Oprev, the old vertex will have a single edge.
|
||||
*/
|
||||
int tessMeshSplice( TESSmesh* mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst )
|
||||
{
|
||||
int joiningLoops = FALSE;
|
||||
int joiningVertices = FALSE;
|
||||
|
||||
if( eOrg == eDst ) return 1;
|
||||
|
||||
if( eDst->Org != eOrg->Org ) {
|
||||
/* We are merging two disjoint vertices -- destroy eDst->Org */
|
||||
joiningVertices = TRUE;
|
||||
KillVertex( mesh, eDst->Org, eOrg->Org );
|
||||
}
|
||||
if( eDst->Lface != eOrg->Lface ) {
|
||||
/* We are connecting two disjoint loops -- destroy eDst->Lface */
|
||||
joiningLoops = TRUE;
|
||||
KillFace( mesh, eDst->Lface, eOrg->Lface );
|
||||
}
|
||||
|
||||
/* Change the edge structure */
|
||||
Splice( eDst, eOrg );
|
||||
|
||||
if( ! joiningVertices ) {
|
||||
TESSvertex *newVertex = (TESSvertex*)bucketAlloc( mesh->vertexBucket );
|
||||
if (newVertex == NULL) return 0;
|
||||
|
||||
/* We split one vertex into two -- the new vertex is eDst->Org.
|
||||
* Make sure the old vertex points to a valid half-edge.
|
||||
*/
|
||||
MakeVertex( newVertex, eDst, eOrg->Org );
|
||||
eOrg->Org->anEdge = eOrg;
|
||||
}
|
||||
if( ! joiningLoops ) {
|
||||
TESSface *newFace = (TESSface*)bucketAlloc( mesh->faceBucket );
|
||||
if (newFace == NULL) return 0;
|
||||
|
||||
/* We split one loop into two -- the new loop is eDst->Lface.
|
||||
* Make sure the old face points to a valid half-edge.
|
||||
*/
|
||||
MakeFace( newFace, eDst, eOrg->Lface );
|
||||
eOrg->Lface->anEdge = eOrg;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* tessMeshDelete( eDel ) removes the edge eDel. There are several cases:
|
||||
* if (eDel->Lface != eDel->Rface), we join two loops into one; the loop
|
||||
* eDel->Lface is deleted. Otherwise, we are splitting one loop into two;
|
||||
* the newly created loop will contain eDel->Dst. If the deletion of eDel
|
||||
* would create isolated vertices, those are deleted as well.
|
||||
*
|
||||
* This function could be implemented as two calls to tessMeshSplice
|
||||
* plus a few calls to memFree, but this would allocate and delete
|
||||
* unnecessary vertices and faces.
|
||||
*/
|
||||
int tessMeshDelete( TESSmesh *mesh, TESShalfEdge *eDel )
|
||||
{
|
||||
TESShalfEdge *eDelSym = eDel->Sym;
|
||||
int joiningLoops = FALSE;
|
||||
|
||||
/* First step: disconnect the origin vertex eDel->Org. We make all
|
||||
* changes to get a consistent mesh in this "intermediate" state.
|
||||
*/
|
||||
if( eDel->Lface != eDel->Rface ) {
|
||||
/* We are joining two loops into one -- remove the left face */
|
||||
joiningLoops = TRUE;
|
||||
KillFace( mesh, eDel->Lface, eDel->Rface );
|
||||
}
|
||||
|
||||
if( eDel->Onext == eDel ) {
|
||||
KillVertex( mesh, eDel->Org, NULL );
|
||||
} else {
|
||||
/* Make sure that eDel->Org and eDel->Rface point to valid half-edges */
|
||||
eDel->Rface->anEdge = eDel->Oprev;
|
||||
eDel->Org->anEdge = eDel->Onext;
|
||||
|
||||
Splice( eDel, eDel->Oprev );
|
||||
if( ! joiningLoops ) {
|
||||
TESSface *newFace= (TESSface*)bucketAlloc( mesh->faceBucket );
|
||||
if (newFace == NULL) return 0;
|
||||
|
||||
/* We are splitting one loop into two -- create a new loop for eDel. */
|
||||
MakeFace( newFace, eDel, eDel->Lface );
|
||||
}
|
||||
}
|
||||
|
||||
/* Claim: the mesh is now in a consistent state, except that eDel->Org
|
||||
* may have been deleted. Now we disconnect eDel->Dst.
|
||||
*/
|
||||
if( eDelSym->Onext == eDelSym ) {
|
||||
KillVertex( mesh, eDelSym->Org, NULL );
|
||||
KillFace( mesh, eDelSym->Lface, NULL );
|
||||
} else {
|
||||
/* Make sure that eDel->Dst and eDel->Lface point to valid half-edges */
|
||||
eDel->Lface->anEdge = eDelSym->Oprev;
|
||||
eDelSym->Org->anEdge = eDelSym->Onext;
|
||||
Splice( eDelSym, eDelSym->Oprev );
|
||||
}
|
||||
|
||||
/* Any isolated vertices or faces have already been freed. */
|
||||
KillEdge( mesh, eDel );
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/******************** Other Edge Operations **********************/
|
||||
|
||||
/* All these routines can be implemented with the basic edge
|
||||
* operations above. They are provided for convenience and efficiency.
|
||||
*/
|
||||
|
||||
|
||||
/* tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that
|
||||
* eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex.
|
||||
* eOrg and eNew will have the same left face.
|
||||
*/
|
||||
TESShalfEdge *tessMeshAddEdgeVertex( TESSmesh *mesh, TESShalfEdge *eOrg )
|
||||
{
|
||||
TESShalfEdge *eNewSym;
|
||||
TESShalfEdge *eNew = MakeEdge( mesh, eOrg );
|
||||
if (eNew == NULL) return NULL;
|
||||
|
||||
eNewSym = eNew->Sym;
|
||||
|
||||
/* Connect the new edge appropriately */
|
||||
Splice( eNew, eOrg->Lnext );
|
||||
|
||||
/* Set the vertex and face information */
|
||||
eNew->Org = eOrg->Dst;
|
||||
{
|
||||
TESSvertex *newVertex= (TESSvertex*)bucketAlloc( mesh->vertexBucket );
|
||||
if (newVertex == NULL) return NULL;
|
||||
|
||||
MakeVertex( newVertex, eNewSym, eNew->Org );
|
||||
}
|
||||
eNew->Lface = eNewSym->Lface = eOrg->Lface;
|
||||
|
||||
return eNew;
|
||||
}
|
||||
|
||||
|
||||
/* tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew,
|
||||
* such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org.
|
||||
* eOrg and eNew will have the same left face.
|
||||
*/
|
||||
TESShalfEdge *tessMeshSplitEdge( TESSmesh *mesh, TESShalfEdge *eOrg )
|
||||
{
|
||||
TESShalfEdge *eNew;
|
||||
TESShalfEdge *tempHalfEdge= tessMeshAddEdgeVertex( mesh, eOrg );
|
||||
if (tempHalfEdge == NULL) return NULL;
|
||||
|
||||
eNew = tempHalfEdge->Sym;
|
||||
|
||||
/* Disconnect eOrg from eOrg->Dst and connect it to eNew->Org */
|
||||
Splice( eOrg->Sym, eOrg->Sym->Oprev );
|
||||
Splice( eOrg->Sym, eNew );
|
||||
|
||||
/* Set the vertex and face information */
|
||||
eOrg->Dst = eNew->Org;
|
||||
eNew->Dst->anEdge = eNew->Sym; /* may have pointed to eOrg->Sym */
|
||||
eNew->Rface = eOrg->Rface;
|
||||
eNew->winding = eOrg->winding; /* copy old winding information */
|
||||
eNew->Sym->winding = eOrg->Sym->winding;
|
||||
|
||||
return eNew;
|
||||
}
|
||||
|
||||
|
||||
/* tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst
|
||||
* to eDst->Org, and returns the corresponding half-edge eNew.
|
||||
* If eOrg->Lface == eDst->Lface, this splits one loop into two,
|
||||
* and the newly created loop is eNew->Lface. Otherwise, two disjoint
|
||||
* loops are merged into one, and the loop eDst->Lface is destroyed.
|
||||
*
|
||||
* If (eOrg == eDst), the new face will have only two edges.
|
||||
* If (eOrg->Lnext == eDst), the old face is reduced to a single edge.
|
||||
* If (eOrg->Lnext->Lnext == eDst), the old face is reduced to two edges.
|
||||
*/
|
||||
TESShalfEdge *tessMeshConnect( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst )
|
||||
{
|
||||
TESShalfEdge *eNewSym;
|
||||
int joiningLoops = FALSE;
|
||||
TESShalfEdge *eNew = MakeEdge( mesh, eOrg );
|
||||
if (eNew == NULL) return NULL;
|
||||
|
||||
eNewSym = eNew->Sym;
|
||||
|
||||
if( eDst->Lface != eOrg->Lface ) {
|
||||
/* We are connecting two disjoint loops -- destroy eDst->Lface */
|
||||
joiningLoops = TRUE;
|
||||
KillFace( mesh, eDst->Lface, eOrg->Lface );
|
||||
}
|
||||
|
||||
/* Connect the new edge appropriately */
|
||||
Splice( eNew, eOrg->Lnext );
|
||||
Splice( eNewSym, eDst );
|
||||
|
||||
/* Set the vertex and face information */
|
||||
eNew->Org = eOrg->Dst;
|
||||
eNewSym->Org = eDst->Org;
|
||||
eNew->Lface = eNewSym->Lface = eOrg->Lface;
|
||||
|
||||
/* Make sure the old face points to a valid half-edge */
|
||||
eOrg->Lface->anEdge = eNewSym;
|
||||
|
||||
if( ! joiningLoops ) {
|
||||
TESSface *newFace= (TESSface*)bucketAlloc( mesh->faceBucket );
|
||||
if (newFace == NULL) return NULL;
|
||||
|
||||
/* We split one loop into two -- the new loop is eNew->Lface */
|
||||
MakeFace( newFace, eNew, eOrg->Lface );
|
||||
}
|
||||
return eNew;
|
||||
}
|
||||
|
||||
|
||||
/******************** Other Operations **********************/
|
||||
|
||||
/* tessMeshZapFace( fZap ) destroys a face and removes it from the
|
||||
* global face list. All edges of fZap will have a NULL pointer as their
|
||||
* left face. Any edges which also have a NULL pointer as their right face
|
||||
* are deleted entirely (along with any isolated vertices this produces).
|
||||
* An entire mesh can be deleted by zapping its faces, one at a time,
|
||||
* in any order. Zapped faces cannot be used in further mesh operations!
|
||||
*/
|
||||
void tessMeshZapFace( TESSmesh *mesh, TESSface *fZap )
|
||||
{
|
||||
TESShalfEdge *eStart = fZap->anEdge;
|
||||
TESShalfEdge *e, *eNext, *eSym;
|
||||
TESSface *fPrev, *fNext;
|
||||
|
||||
/* walk around face, deleting edges whose right face is also NULL */
|
||||
eNext = eStart->Lnext;
|
||||
do {
|
||||
e = eNext;
|
||||
eNext = e->Lnext;
|
||||
|
||||
e->Lface = NULL;
|
||||
if( e->Rface == NULL ) {
|
||||
/* delete the edge -- see TESSmeshDelete above */
|
||||
|
||||
if( e->Onext == e ) {
|
||||
KillVertex( mesh, e->Org, NULL );
|
||||
} else {
|
||||
/* Make sure that e->Org points to a valid half-edge */
|
||||
e->Org->anEdge = e->Onext;
|
||||
Splice( e, e->Oprev );
|
||||
}
|
||||
eSym = e->Sym;
|
||||
if( eSym->Onext == eSym ) {
|
||||
KillVertex( mesh, eSym->Org, NULL );
|
||||
} else {
|
||||
/* Make sure that eSym->Org points to a valid half-edge */
|
||||
eSym->Org->anEdge = eSym->Onext;
|
||||
Splice( eSym, eSym->Oprev );
|
||||
}
|
||||
KillEdge( mesh, e );
|
||||
}
|
||||
} while( e != eStart );
|
||||
|
||||
/* delete from circular doubly-linked list */
|
||||
fPrev = fZap->prev;
|
||||
fNext = fZap->next;
|
||||
fNext->prev = fPrev;
|
||||
fPrev->next = fNext;
|
||||
|
||||
bucketFree( mesh->faceBucket, fZap );
|
||||
}
|
||||
|
||||
|
||||
/* tessMeshNewMesh() creates a new mesh with no edges, no vertices,
|
||||
* and no loops (what we usually call a "face").
|
||||
*/
|
||||
TESSmesh *tessMeshNewMesh( TESSalloc* alloc )
|
||||
{
|
||||
TESSvertex *v;
|
||||
TESSface *f;
|
||||
TESShalfEdge *e;
|
||||
TESShalfEdge *eSym;
|
||||
TESSmesh *mesh = (TESSmesh *)alloc->memalloc( alloc->userData, sizeof( TESSmesh ));
|
||||
if (mesh == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (alloc->meshEdgeBucketSize < 16)
|
||||
alloc->meshEdgeBucketSize = 16;
|
||||
if (alloc->meshEdgeBucketSize > 4096)
|
||||
alloc->meshEdgeBucketSize = 4096;
|
||||
|
||||
if (alloc->meshVertexBucketSize < 16)
|
||||
alloc->meshVertexBucketSize = 16;
|
||||
if (alloc->meshVertexBucketSize > 4096)
|
||||
alloc->meshVertexBucketSize = 4096;
|
||||
|
||||
if (alloc->meshFaceBucketSize < 16)
|
||||
alloc->meshFaceBucketSize = 16;
|
||||
if (alloc->meshFaceBucketSize > 4096)
|
||||
alloc->meshFaceBucketSize = 4096;
|
||||
|
||||
mesh->edgeBucket = createBucketAlloc( alloc, "Mesh Edges", sizeof(EdgePair), alloc->meshEdgeBucketSize );
|
||||
mesh->vertexBucket = createBucketAlloc( alloc, "Mesh Vertices", sizeof(TESSvertex), alloc->meshVertexBucketSize );
|
||||
mesh->faceBucket = createBucketAlloc( alloc, "Mesh Faces", sizeof(TESSface), alloc->meshFaceBucketSize );
|
||||
|
||||
v = &mesh->vHead;
|
||||
f = &mesh->fHead;
|
||||
e = &mesh->eHead;
|
||||
eSym = &mesh->eHeadSym;
|
||||
|
||||
v->next = v->prev = v;
|
||||
v->anEdge = NULL;
|
||||
|
||||
f->next = f->prev = f;
|
||||
f->anEdge = NULL;
|
||||
f->trail = NULL;
|
||||
f->marked = FALSE;
|
||||
f->inside = FALSE;
|
||||
|
||||
e->next = e;
|
||||
e->Sym = eSym;
|
||||
e->Onext = NULL;
|
||||
e->Lnext = NULL;
|
||||
e->Org = NULL;
|
||||
e->Lface = NULL;
|
||||
e->winding = 0;
|
||||
e->activeRegion = NULL;
|
||||
|
||||
eSym->next = eSym;
|
||||
eSym->Sym = e;
|
||||
eSym->Onext = NULL;
|
||||
eSym->Lnext = NULL;
|
||||
eSym->Org = NULL;
|
||||
eSym->Lface = NULL;
|
||||
eSym->winding = 0;
|
||||
eSym->activeRegion = NULL;
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
||||
/* tessMeshUnion( mesh1, mesh2 ) forms the union of all structures in
|
||||
* both meshes, and returns the new mesh (the old meshes are destroyed).
|
||||
*/
|
||||
TESSmesh *tessMeshUnion( TESSalloc* alloc, TESSmesh *mesh1, TESSmesh *mesh2 )
|
||||
{
|
||||
TESSface *f1 = &mesh1->fHead;
|
||||
TESSvertex *v1 = &mesh1->vHead;
|
||||
TESShalfEdge *e1 = &mesh1->eHead;
|
||||
TESSface *f2 = &mesh2->fHead;
|
||||
TESSvertex *v2 = &mesh2->vHead;
|
||||
TESShalfEdge *e2 = &mesh2->eHead;
|
||||
|
||||
/* Add the faces, vertices, and edges of mesh2 to those of mesh1 */
|
||||
if( f2->next != f2 ) {
|
||||
f1->prev->next = f2->next;
|
||||
f2->next->prev = f1->prev;
|
||||
f2->prev->next = f1;
|
||||
f1->prev = f2->prev;
|
||||
}
|
||||
|
||||
if( v2->next != v2 ) {
|
||||
v1->prev->next = v2->next;
|
||||
v2->next->prev = v1->prev;
|
||||
v2->prev->next = v1;
|
||||
v1->prev = v2->prev;
|
||||
}
|
||||
|
||||
if( e2->next != e2 ) {
|
||||
e1->Sym->next->Sym->next = e2->next;
|
||||
e2->next->Sym->next = e1->Sym->next;
|
||||
e2->Sym->next->Sym->next = e1;
|
||||
e1->Sym->next = e2->Sym->next;
|
||||
}
|
||||
|
||||
alloc->memfree( alloc->userData, mesh2 );
|
||||
return mesh1;
|
||||
}
|
||||
|
||||
|
||||
static int CountFaceVerts( TESSface *f )
|
||||
{
|
||||
TESShalfEdge *eCur = f->anEdge;
|
||||
int n = 0;
|
||||
do
|
||||
{
|
||||
n++;
|
||||
eCur = eCur->Lnext;
|
||||
}
|
||||
while (eCur != f->anEdge);
|
||||
return n;
|
||||
}
|
||||
|
||||
int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace )
|
||||
{
|
||||
TESShalfEdge *e, *eNext, *eSym;
|
||||
TESShalfEdge *eHead = &mesh->eHead;
|
||||
TESSvertex *va, *vb, *vc, *vd, *ve, *vf;
|
||||
int leftNv, rightNv;
|
||||
|
||||
for( e = eHead->next; e != eHead; e = eNext )
|
||||
{
|
||||
eNext = e->next;
|
||||
eSym = e->Sym;
|
||||
if( !eSym )
|
||||
continue;
|
||||
|
||||
// Both faces must be inside
|
||||
if( !e->Lface || !e->Lface->inside )
|
||||
continue;
|
||||
if( !eSym->Lface || !eSym->Lface->inside )
|
||||
continue;
|
||||
|
||||
leftNv = CountFaceVerts( e->Lface );
|
||||
rightNv = CountFaceVerts( eSym->Lface );
|
||||
if( (leftNv+rightNv-2) > maxVertsPerFace )
|
||||
continue;
|
||||
|
||||
// Merge if the resulting poly is convex.
|
||||
//
|
||||
// vf--ve--vd
|
||||
// ^|
|
||||
// left e || right
|
||||
// |v
|
||||
// va--vb--vc
|
||||
|
||||
va = e->Lprev->Org;
|
||||
vb = e->Org;
|
||||
vc = e->Sym->Lnext->Dst;
|
||||
|
||||
vd = e->Sym->Lprev->Org;
|
||||
ve = e->Sym->Org;
|
||||
vf = e->Lnext->Dst;
|
||||
|
||||
if( VertCCW( va, vb, vc ) && VertCCW( vd, ve, vf ) ) {
|
||||
if( e == eNext || e == eNext->Sym ) { eNext = eNext->next; }
|
||||
if( !tessMeshDelete( mesh, e ) )
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void tessMeshFlipEdge( TESSmesh *mesh, TESShalfEdge *edge )
|
||||
{
|
||||
TESShalfEdge *a0 = edge;
|
||||
TESShalfEdge *a1 = a0->Lnext;
|
||||
TESShalfEdge *a2 = a1->Lnext;
|
||||
TESShalfEdge *b0 = edge->Sym;
|
||||
TESShalfEdge *b1 = b0->Lnext;
|
||||
TESShalfEdge *b2 = b1->Lnext;
|
||||
|
||||
TESSvertex *aOrg = a0->Org;
|
||||
TESSvertex *aOpp = a2->Org;
|
||||
TESSvertex *bOrg = b0->Org;
|
||||
TESSvertex *bOpp = b2->Org;
|
||||
|
||||
TESSface *fa = a0->Lface;
|
||||
TESSface *fb = b0->Lface;
|
||||
|
||||
assert(EdgeIsInternal(edge));
|
||||
assert(a2->Lnext == a0);
|
||||
assert(b2->Lnext == b0);
|
||||
|
||||
a0->Org = bOpp;
|
||||
a0->Onext = b1->Sym;
|
||||
b0->Org = aOpp;
|
||||
b0->Onext = a1->Sym;
|
||||
a2->Onext = b0;
|
||||
b2->Onext = a0;
|
||||
b1->Onext = a2->Sym;
|
||||
a1->Onext = b2->Sym;
|
||||
|
||||
a0->Lnext = a2;
|
||||
a2->Lnext = b1;
|
||||
b1->Lnext = a0;
|
||||
|
||||
b0->Lnext = b2;
|
||||
b2->Lnext = a1;
|
||||
a1->Lnext = b0;
|
||||
|
||||
a1->Lface = fb;
|
||||
b1->Lface = fa;
|
||||
|
||||
fa->anEdge = a0;
|
||||
fb->anEdge = b0;
|
||||
|
||||
if (aOrg->anEdge == a0) aOrg->anEdge = b1;
|
||||
if (bOrg->anEdge == b0) bOrg->anEdge = a1;
|
||||
|
||||
assert( a0->Lnext->Onext->Sym == a0 );
|
||||
assert( a0->Onext->Sym->Lnext == a0 );
|
||||
assert( a0->Org->anEdge->Org == a0->Org );
|
||||
|
||||
|
||||
assert( a1->Lnext->Onext->Sym == a1 );
|
||||
assert( a1->Onext->Sym->Lnext == a1 );
|
||||
assert( a1->Org->anEdge->Org == a1->Org );
|
||||
|
||||
assert( a2->Lnext->Onext->Sym == a2 );
|
||||
assert( a2->Onext->Sym->Lnext == a2 );
|
||||
assert( a2->Org->anEdge->Org == a2->Org );
|
||||
|
||||
assert( b0->Lnext->Onext->Sym == b0 );
|
||||
assert( b0->Onext->Sym->Lnext == b0 );
|
||||
assert( b0->Org->anEdge->Org == b0->Org );
|
||||
|
||||
assert( b1->Lnext->Onext->Sym == b1 );
|
||||
assert( b1->Onext->Sym->Lnext == b1 );
|
||||
assert( b1->Org->anEdge->Org == b1->Org );
|
||||
|
||||
assert( b2->Lnext->Onext->Sym == b2 );
|
||||
assert( b2->Onext->Sym->Lnext == b2 );
|
||||
assert( b2->Org->anEdge->Org == b2->Org );
|
||||
|
||||
assert(aOrg->anEdge->Org == aOrg);
|
||||
assert(bOrg->anEdge->Org == bOrg);
|
||||
|
||||
assert(a0->Oprev->Onext->Org == a0->Org);
|
||||
}
|
||||
|
||||
#ifdef DELETE_BY_ZAPPING
|
||||
|
||||
/* tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh.
|
||||
*/
|
||||
void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh )
|
||||
{
|
||||
TESSface *fHead = &mesh->fHead;
|
||||
|
||||
while( fHead->next != fHead ) {
|
||||
tessMeshZapFace( fHead->next );
|
||||
}
|
||||
assert( mesh->vHead.next == &mesh->vHead );
|
||||
|
||||
alloc->memfree( alloc->userData, mesh );
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh.
|
||||
*/
|
||||
void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh )
|
||||
{
|
||||
deleteBucketAlloc(mesh->edgeBucket);
|
||||
deleteBucketAlloc(mesh->vertexBucket);
|
||||
deleteBucketAlloc(mesh->faceBucket);
|
||||
|
||||
alloc->memfree( alloc->userData, mesh );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
/* tessMeshCheckMesh( mesh ) checks a mesh for self-consistency.
|
||||
*/
|
||||
void tessMeshCheckMesh( TESSmesh *mesh )
|
||||
{
|
||||
TESSface *fHead = &mesh->fHead;
|
||||
TESSvertex *vHead = &mesh->vHead;
|
||||
TESShalfEdge *eHead = &mesh->eHead;
|
||||
TESSface *f, *fPrev;
|
||||
TESSvertex *v, *vPrev;
|
||||
TESShalfEdge *e, *ePrev;
|
||||
|
||||
for( fPrev = fHead ; (f = fPrev->next) != fHead; fPrev = f) {
|
||||
assert( f->prev == fPrev );
|
||||
e = f->anEdge;
|
||||
do {
|
||||
assert( e->Sym != e );
|
||||
assert( e->Sym->Sym == e );
|
||||
assert( e->Lnext->Onext->Sym == e );
|
||||
assert( e->Onext->Sym->Lnext == e );
|
||||
assert( e->Lface == f );
|
||||
e = e->Lnext;
|
||||
} while( e != f->anEdge );
|
||||
}
|
||||
assert( f->prev == fPrev && f->anEdge == NULL );
|
||||
|
||||
for( vPrev = vHead ; (v = vPrev->next) != vHead; vPrev = v) {
|
||||
assert( v->prev == vPrev );
|
||||
e = v->anEdge;
|
||||
do {
|
||||
assert( e->Sym != e );
|
||||
assert( e->Sym->Sym == e );
|
||||
assert( e->Lnext->Onext->Sym == e );
|
||||
assert( e->Onext->Sym->Lnext == e );
|
||||
assert( e->Org == v );
|
||||
e = e->Onext;
|
||||
} while( e != v->anEdge );
|
||||
}
|
||||
assert( v->prev == vPrev && v->anEdge == NULL );
|
||||
|
||||
for( ePrev = eHead ; (e = ePrev->next) != eHead; ePrev = e) {
|
||||
assert( e->Sym->next == ePrev->Sym );
|
||||
assert( e->Sym != e );
|
||||
assert( e->Sym->Sym == e );
|
||||
assert( e->Org != NULL );
|
||||
assert( e->Dst != NULL );
|
||||
assert( e->Lnext->Onext->Sym == e );
|
||||
assert( e->Onext->Sym->Lnext == e );
|
||||
}
|
||||
assert( e->Sym->next == ePrev->Sym
|
||||
&& e->Sym == &mesh->eHeadSym
|
||||
&& e->Sym->Sym == e
|
||||
&& e->Org == NULL && e->Dst == NULL
|
||||
&& e->Lface == NULL && e->Rface == NULL );
|
||||
}
|
||||
|
||||
#endif
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
#ifndef MESH_H
|
||||
#define MESH_H
|
||||
|
||||
#include "../Include/tesselator.h"
|
||||
|
||||
typedef struct TESSmesh TESSmesh;
|
||||
typedef struct TESSvertex TESSvertex;
|
||||
typedef struct TESSface TESSface;
|
||||
typedef struct TESShalfEdge TESShalfEdge;
|
||||
typedef struct ActiveRegion ActiveRegion;
|
||||
|
||||
/* The mesh structure is similar in spirit, notation, and operations
|
||||
* to the "quad-edge" structure (see L. Guibas and J. Stolfi, Primitives
|
||||
* for the manipulation of general subdivisions and the computation of
|
||||
* Voronoi diagrams, ACM Transactions on Graphics, 4(2):74-123, April 1985).
|
||||
* For a simplified description, see the course notes for CS348a,
|
||||
* "Mathematical Foundations of Computer Graphics", available at the
|
||||
* Stanford bookstore (and taught during the fall quarter).
|
||||
* The implementation also borrows a tiny subset of the graph-based approach
|
||||
* use in Mantyla's Geometric Work Bench (see M. Mantyla, An Introduction
|
||||
* to Sold Modeling, Computer Science Press, Rockville, Maryland, 1988).
|
||||
*
|
||||
* The fundamental data structure is the "half-edge". Two half-edges
|
||||
* go together to make an edge, but they point in opposite directions.
|
||||
* Each half-edge has a pointer to its mate (the "symmetric" half-edge Sym),
|
||||
* its origin vertex (Org), the face on its left side (Lface), and the
|
||||
* adjacent half-edges in the CCW direction around the origin vertex
|
||||
* (Onext) and around the left face (Lnext). There is also a "next"
|
||||
* pointer for the global edge list (see below).
|
||||
*
|
||||
* The notation used for mesh navigation:
|
||||
* Sym = the mate of a half-edge (same edge, but opposite direction)
|
||||
* Onext = edge CCW around origin vertex (keep same origin)
|
||||
* Dnext = edge CCW around destination vertex (keep same dest)
|
||||
* Lnext = edge CCW around left face (dest becomes new origin)
|
||||
* Rnext = edge CCW around right face (origin becomes new dest)
|
||||
*
|
||||
* "prev" means to substitute CW for CCW in the definitions above.
|
||||
*
|
||||
* The mesh keeps global lists of all vertices, faces, and edges,
|
||||
* stored as doubly-linked circular lists with a dummy header node.
|
||||
* The mesh stores pointers to these dummy headers (vHead, fHead, eHead).
|
||||
*
|
||||
* The circular edge list is special; since half-edges always occur
|
||||
* in pairs (e and e->Sym), each half-edge stores a pointer in only
|
||||
* one direction. Starting at eHead and following the e->next pointers
|
||||
* will visit each *edge* once (ie. e or e->Sym, but not both).
|
||||
* e->Sym stores a pointer in the opposite direction, thus it is
|
||||
* always true that e->Sym->next->Sym->next == e.
|
||||
*
|
||||
* Each vertex has a pointer to next and previous vertices in the
|
||||
* circular list, and a pointer to a half-edge with this vertex as
|
||||
* the origin (NULL if this is the dummy header). There is also a
|
||||
* field "data" for client data.
|
||||
*
|
||||
* Each face has a pointer to the next and previous faces in the
|
||||
* circular list, and a pointer to a half-edge with this face as
|
||||
* the left face (NULL if this is the dummy header). There is also
|
||||
* a field "data" for client data.
|
||||
*
|
||||
* Note that what we call a "face" is really a loop; faces may consist
|
||||
* of more than one loop (ie. not simply connected), but there is no
|
||||
* record of this in the data structure. The mesh may consist of
|
||||
* several disconnected regions, so it may not be possible to visit
|
||||
* the entire mesh by starting at a half-edge and traversing the edge
|
||||
* structure.
|
||||
*
|
||||
* The mesh does NOT support isolated vertices; a vertex is deleted along
|
||||
* with its last edge. Similarly when two faces are merged, one of the
|
||||
* faces is deleted (see tessMeshDelete below). For mesh operations,
|
||||
* all face (loop) and vertex pointers must not be NULL. However, once
|
||||
* mesh manipulation is finished, TESSmeshZapFace can be used to delete
|
||||
* faces of the mesh, one at a time. All external faces can be "zapped"
|
||||
* before the mesh is returned to the client; then a NULL face indicates
|
||||
* a region which is not part of the output polygon.
|
||||
*/
|
||||
|
||||
struct TESSvertex {
|
||||
TESSvertex *next; /* next vertex (never NULL) */
|
||||
TESSvertex *prev; /* previous vertex (never NULL) */
|
||||
TESShalfEdge *anEdge; /* a half-edge with this origin */
|
||||
|
||||
/* Internal data (keep hidden) */
|
||||
TESSreal coords[3]; /* vertex location in 3D */
|
||||
TESSreal s, t; /* projection onto the sweep plane */
|
||||
int pqHandle; /* to allow deletion from priority queue */
|
||||
TESSindex n; /* to allow identify unique vertices */
|
||||
TESSindex idx; /* to allow map result to original verts */
|
||||
};
|
||||
|
||||
struct TESSface {
|
||||
TESSface *next; /* next face (never NULL) */
|
||||
TESSface *prev; /* previous face (never NULL) */
|
||||
TESShalfEdge *anEdge; /* a half edge with this left face */
|
||||
|
||||
/* Internal data (keep hidden) */
|
||||
TESSface *trail; /* "stack" for conversion to strips */
|
||||
TESSindex n; /* to allow identiy unique faces */
|
||||
char marked; /* flag for conversion to strips */
|
||||
char inside; /* this face is in the polygon interior */
|
||||
};
|
||||
|
||||
struct TESShalfEdge {
|
||||
TESShalfEdge *next; /* doubly-linked list (prev==Sym->next) */
|
||||
TESShalfEdge *Sym; /* same edge, opposite direction */
|
||||
TESShalfEdge *Onext; /* next edge CCW around origin */
|
||||
TESShalfEdge *Lnext; /* next edge CCW around left face */
|
||||
TESSvertex *Org; /* origin vertex (Overtex too long) */
|
||||
TESSface *Lface; /* left face */
|
||||
|
||||
/* Internal data (keep hidden) */
|
||||
ActiveRegion *activeRegion; /* a region with this upper edge (sweep.c) */
|
||||
int winding; /* change in winding number when crossing
|
||||
from the right face to the left face */
|
||||
int mark; /* Used by the Edge Flip algorithm */
|
||||
};
|
||||
|
||||
#define Rface Sym->Lface
|
||||
#define Dst Sym->Org
|
||||
|
||||
#define Oprev Sym->Lnext
|
||||
#define Lprev Onext->Sym
|
||||
#define Dprev Lnext->Sym
|
||||
#define Rprev Sym->Onext
|
||||
#define Dnext Rprev->Sym /* 3 pointers */
|
||||
#define Rnext Oprev->Sym /* 3 pointers */
|
||||
|
||||
struct TESSmesh {
|
||||
TESSvertex vHead; /* dummy header for vertex list */
|
||||
TESSface fHead; /* dummy header for face list */
|
||||
TESShalfEdge eHead; /* dummy header for edge list */
|
||||
TESShalfEdge eHeadSym; /* and its symmetric counterpart */
|
||||
|
||||
struct BucketAlloc* edgeBucket;
|
||||
struct BucketAlloc* vertexBucket;
|
||||
struct BucketAlloc* faceBucket;
|
||||
};
|
||||
|
||||
/* The mesh operations below have three motivations: completeness,
|
||||
* convenience, and efficiency. The basic mesh operations are MakeEdge,
|
||||
* Splice, and Delete. All the other edge operations can be implemented
|
||||
* in terms of these. The other operations are provided for convenience
|
||||
* and/or efficiency.
|
||||
*
|
||||
* When a face is split or a vertex is added, they are inserted into the
|
||||
* global list *before* the existing vertex or face (ie. e->Org or e->Lface).
|
||||
* This makes it easier to process all vertices or faces in the global lists
|
||||
* without worrying about processing the same data twice. As a convenience,
|
||||
* when a face is split, the "inside" flag is copied from the old face.
|
||||
* Other internal data (v->data, v->activeRegion, f->data, f->marked,
|
||||
* f->trail, e->winding) is set to zero.
|
||||
*
|
||||
* ********************** Basic Edge Operations **************************
|
||||
*
|
||||
* tessMeshMakeEdge( mesh ) creates one edge, two vertices, and a loop.
|
||||
* The loop (face) consists of the two new half-edges.
|
||||
*
|
||||
* tessMeshSplice( eOrg, eDst ) is the basic operation for changing the
|
||||
* mesh connectivity and topology. It changes the mesh so that
|
||||
* eOrg->Onext <- OLD( eDst->Onext )
|
||||
* eDst->Onext <- OLD( eOrg->Onext )
|
||||
* where OLD(...) means the value before the meshSplice operation.
|
||||
*
|
||||
* This can have two effects on the vertex structure:
|
||||
* - if eOrg->Org != eDst->Org, the two vertices are merged together
|
||||
* - if eOrg->Org == eDst->Org, the origin is split into two vertices
|
||||
* In both cases, eDst->Org is changed and eOrg->Org is untouched.
|
||||
*
|
||||
* Similarly (and independently) for the face structure,
|
||||
* - if eOrg->Lface == eDst->Lface, one loop is split into two
|
||||
* - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one
|
||||
* In both cases, eDst->Lface is changed and eOrg->Lface is unaffected.
|
||||
*
|
||||
* tessMeshDelete( eDel ) removes the edge eDel. There are several cases:
|
||||
* if (eDel->Lface != eDel->Rface), we join two loops into one; the loop
|
||||
* eDel->Lface is deleted. Otherwise, we are splitting one loop into two;
|
||||
* the newly created loop will contain eDel->Dst. If the deletion of eDel
|
||||
* would create isolated vertices, those are deleted as well.
|
||||
*
|
||||
* ********************** Other Edge Operations **************************
|
||||
*
|
||||
* tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that
|
||||
* eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex.
|
||||
* eOrg and eNew will have the same left face.
|
||||
*
|
||||
* tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew,
|
||||
* such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org.
|
||||
* eOrg and eNew will have the same left face.
|
||||
*
|
||||
* tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst
|
||||
* to eDst->Org, and returns the corresponding half-edge eNew.
|
||||
* If eOrg->Lface == eDst->Lface, this splits one loop into two,
|
||||
* and the newly created loop is eNew->Lface. Otherwise, two disjoint
|
||||
* loops are merged into one, and the loop eDst->Lface is destroyed.
|
||||
*
|
||||
* ************************ Other Operations *****************************
|
||||
*
|
||||
* tessMeshNewMesh() creates a new mesh with no edges, no vertices,
|
||||
* and no loops (what we usually call a "face").
|
||||
*
|
||||
* tessMeshUnion( mesh1, mesh2 ) forms the union of all structures in
|
||||
* both meshes, and returns the new mesh (the old meshes are destroyed).
|
||||
*
|
||||
* tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh.
|
||||
*
|
||||
* tessMeshZapFace( fZap ) destroys a face and removes it from the
|
||||
* global face list. All edges of fZap will have a NULL pointer as their
|
||||
* left face. Any edges which also have a NULL pointer as their right face
|
||||
* are deleted entirely (along with any isolated vertices this produces).
|
||||
* An entire mesh can be deleted by zapping its faces, one at a time,
|
||||
* in any order. Zapped faces cannot be used in further mesh operations!
|
||||
*
|
||||
* tessMeshCheckMesh( mesh ) checks a mesh for self-consistency.
|
||||
*/
|
||||
|
||||
TESShalfEdge *tessMeshMakeEdge( TESSmesh *mesh );
|
||||
int tessMeshSplice( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst );
|
||||
int tessMeshDelete( TESSmesh *mesh, TESShalfEdge *eDel );
|
||||
|
||||
TESShalfEdge *tessMeshAddEdgeVertex( TESSmesh *mesh, TESShalfEdge *eOrg );
|
||||
TESShalfEdge *tessMeshSplitEdge( TESSmesh *mesh, TESShalfEdge *eOrg );
|
||||
TESShalfEdge *tessMeshConnect( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst );
|
||||
|
||||
TESSmesh *tessMeshNewMesh( TESSalloc* alloc );
|
||||
TESSmesh *tessMeshUnion( TESSalloc* alloc, TESSmesh *mesh1, TESSmesh *mesh2 );
|
||||
int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace );
|
||||
void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh );
|
||||
void tessMeshZapFace( TESSmesh *mesh, TESSface *fZap );
|
||||
|
||||
void tessMeshFlipEdge( TESSmesh *mesh, TESShalfEdge *edge );
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define tessMeshCheckMesh( mesh )
|
||||
#else
|
||||
void tessMeshCheckMesh( TESSmesh *mesh );
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+514
@@ -0,0 +1,514 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
//#include "tesos.h"
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include "../Include/tesselator.h"
|
||||
#include "priorityq.h"
|
||||
|
||||
|
||||
#define INIT_SIZE 32
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
#ifdef FOR_TRITE_TEST_PROGRAM
|
||||
#define LEQ(x,y) (*pq->leq)(x,y)
|
||||
#else
|
||||
/* Violates modularity, but a little faster */
|
||||
#include "geom.h"
|
||||
#define LEQ(x,y) VertLeq((TESSvertex *)x, (TESSvertex *)y)
|
||||
#endif
|
||||
|
||||
|
||||
/* Include all the code for the regular heap-based queue here. */
|
||||
|
||||
/* The basic operations are insertion of a new key (pqInsert),
|
||||
* and examination/extraction of a key whose value is minimum
|
||||
* (pqMinimum/pqExtractMin). Deletion is also allowed (pqDelete);
|
||||
* for this purpose pqInsert returns a "handle" which is supplied
|
||||
* as the argument.
|
||||
*
|
||||
* An initial heap may be created efficiently by calling pqInsert
|
||||
* repeatedly, then calling pqInit. In any case pqInit must be called
|
||||
* before any operations other than pqInsert are used.
|
||||
*
|
||||
* If the heap is empty, pqMinimum/pqExtractMin will return a NULL key.
|
||||
* This may also be tested with pqIsEmpty.
|
||||
*/
|
||||
|
||||
|
||||
/* Since we support deletion the data structure is a little more
|
||||
* complicated than an ordinary heap. "nodes" is the heap itself;
|
||||
* active nodes are stored in the range 1..pq->size. When the
|
||||
* heap exceeds its allocated size (pq->max), its size doubles.
|
||||
* The children of node i are nodes 2i and 2i+1.
|
||||
*
|
||||
* Each node stores an index into an array "handles". Each handle
|
||||
* stores a key, plus a pointer back to the node which currently
|
||||
* represents that key (ie. nodes[handles[i].node].handle == i).
|
||||
*/
|
||||
|
||||
|
||||
#define pqHeapMinimum(pq) ((pq)->handles[(pq)->nodes[1].handle].key)
|
||||
#define pqHeapIsEmpty(pq) ((pq)->size == 0)
|
||||
|
||||
|
||||
|
||||
/* really pqHeapNewPriorityQHeap */
|
||||
PriorityQHeap *pqHeapNewPriorityQ( TESSalloc* alloc, int size, int (*leq)(PQkey key1, PQkey key2) )
|
||||
{
|
||||
PriorityQHeap *pq = (PriorityQHeap *)alloc->memalloc( alloc->userData, sizeof( PriorityQHeap ));
|
||||
if (pq == NULL) return NULL;
|
||||
|
||||
pq->size = 0;
|
||||
pq->max = size;
|
||||
pq->nodes = (PQnode *)alloc->memalloc( alloc->userData, (size + 1) * sizeof(pq->nodes[0]) );
|
||||
if (pq->nodes == NULL) {
|
||||
alloc->memfree( alloc->userData, pq );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pq->handles = (PQhandleElem *)alloc->memalloc( alloc->userData, (size + 1) * sizeof(pq->handles[0]) );
|
||||
if (pq->handles == NULL) {
|
||||
alloc->memfree( alloc->userData, pq->nodes );
|
||||
alloc->memfree( alloc->userData, pq );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pq->initialized = FALSE;
|
||||
pq->freeList = 0;
|
||||
pq->leq = leq;
|
||||
|
||||
pq->nodes[1].handle = 1; /* so that Minimum() returns NULL */
|
||||
pq->handles[1].key = NULL;
|
||||
return pq;
|
||||
}
|
||||
|
||||
/* really pqHeapDeletePriorityQHeap */
|
||||
void pqHeapDeletePriorityQ( TESSalloc* alloc, PriorityQHeap *pq )
|
||||
{
|
||||
alloc->memfree( alloc->userData, pq->handles );
|
||||
alloc->memfree( alloc->userData, pq->nodes );
|
||||
alloc->memfree( alloc->userData, pq );
|
||||
}
|
||||
|
||||
|
||||
static void FloatDown( PriorityQHeap *pq, int curr )
|
||||
{
|
||||
PQnode *n = pq->nodes;
|
||||
PQhandleElem *h = pq->handles;
|
||||
PQhandle hCurr, hChild;
|
||||
int child;
|
||||
|
||||
hCurr = n[curr].handle;
|
||||
for( ;; ) {
|
||||
child = curr << 1;
|
||||
if( child < pq->size && LEQ( h[n[child+1].handle].key,
|
||||
h[n[child].handle].key )) {
|
||||
++child;
|
||||
}
|
||||
|
||||
assert(child <= pq->max);
|
||||
|
||||
hChild = n[child].handle;
|
||||
if( child > pq->size || LEQ( h[hCurr].key, h[hChild].key )) {
|
||||
n[curr].handle = hCurr;
|
||||
h[hCurr].node = curr;
|
||||
break;
|
||||
}
|
||||
n[curr].handle = hChild;
|
||||
h[hChild].node = curr;
|
||||
curr = child;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void FloatUp( PriorityQHeap *pq, int curr )
|
||||
{
|
||||
PQnode *n = pq->nodes;
|
||||
PQhandleElem *h = pq->handles;
|
||||
PQhandle hCurr, hParent;
|
||||
int parent;
|
||||
|
||||
hCurr = n[curr].handle;
|
||||
for( ;; ) {
|
||||
parent = curr >> 1;
|
||||
hParent = n[parent].handle;
|
||||
if( parent == 0 || LEQ( h[hParent].key, h[hCurr].key )) {
|
||||
n[curr].handle = hCurr;
|
||||
h[hCurr].node = curr;
|
||||
break;
|
||||
}
|
||||
n[curr].handle = hParent;
|
||||
h[hParent].node = curr;
|
||||
curr = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/* really pqHeapInit */
|
||||
void pqHeapInit( PriorityQHeap *pq )
|
||||
{
|
||||
int i;
|
||||
|
||||
/* This method of building a heap is O(n), rather than O(n lg n). */
|
||||
|
||||
for( i = pq->size; i >= 1; --i ) {
|
||||
FloatDown( pq, i );
|
||||
}
|
||||
pq->initialized = TRUE;
|
||||
}
|
||||
|
||||
/* really pqHeapInsert */
|
||||
/* returns INV_HANDLE iff out of memory */
|
||||
PQhandle pqHeapInsert( TESSalloc* alloc, PriorityQHeap *pq, PQkey keyNew )
|
||||
{
|
||||
int curr;
|
||||
PQhandle free;
|
||||
|
||||
curr = ++ pq->size;
|
||||
if( (curr*2) > pq->max ) {
|
||||
if (!alloc->memrealloc)
|
||||
{
|
||||
return INV_HANDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
PQnode *saveNodes= pq->nodes;
|
||||
PQhandleElem *saveHandles= pq->handles;
|
||||
|
||||
// If the heap overflows, double its size.
|
||||
pq->max <<= 1;
|
||||
pq->nodes = (PQnode *)alloc->memrealloc( alloc->userData, pq->nodes,
|
||||
(size_t)((pq->max + 1) * sizeof( pq->nodes[0] )));
|
||||
if (pq->nodes == NULL) {
|
||||
pq->nodes = saveNodes; // restore ptr to free upon return
|
||||
return INV_HANDLE;
|
||||
}
|
||||
pq->handles = (PQhandleElem *)alloc->memrealloc( alloc->userData, pq->handles,
|
||||
(size_t) ((pq->max + 1) * sizeof( pq->handles[0] )));
|
||||
if (pq->handles == NULL) {
|
||||
pq->handles = saveHandles; // restore ptr to free upon return
|
||||
return INV_HANDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( pq->freeList == 0 ) {
|
||||
free = curr;
|
||||
} else {
|
||||
free = pq->freeList;
|
||||
pq->freeList = pq->handles[free].node;
|
||||
}
|
||||
|
||||
pq->nodes[curr].handle = free;
|
||||
pq->handles[free].node = curr;
|
||||
pq->handles[free].key = keyNew;
|
||||
|
||||
if( pq->initialized ) {
|
||||
FloatUp( pq, curr );
|
||||
}
|
||||
assert(free != INV_HANDLE);
|
||||
return free;
|
||||
}
|
||||
|
||||
/* really pqHeapExtractMin */
|
||||
PQkey pqHeapExtractMin( PriorityQHeap *pq )
|
||||
{
|
||||
PQnode *n = pq->nodes;
|
||||
PQhandleElem *h = pq->handles;
|
||||
PQhandle hMin = n[1].handle;
|
||||
PQkey min = h[hMin].key;
|
||||
|
||||
if( pq->size > 0 ) {
|
||||
n[1].handle = n[pq->size].handle;
|
||||
h[n[1].handle].node = 1;
|
||||
|
||||
h[hMin].key = NULL;
|
||||
h[hMin].node = pq->freeList;
|
||||
pq->freeList = hMin;
|
||||
|
||||
if( -- pq->size > 0 ) {
|
||||
FloatDown( pq, 1 );
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
/* really pqHeapDelete */
|
||||
void pqHeapDelete( PriorityQHeap *pq, PQhandle hCurr )
|
||||
{
|
||||
PQnode *n = pq->nodes;
|
||||
PQhandleElem *h = pq->handles;
|
||||
int curr;
|
||||
|
||||
assert( hCurr >= 1 && hCurr <= pq->max && h[hCurr].key != NULL );
|
||||
|
||||
curr = h[hCurr].node;
|
||||
n[curr].handle = n[pq->size].handle;
|
||||
h[n[curr].handle].node = curr;
|
||||
|
||||
if( curr <= -- pq->size ) {
|
||||
if( curr <= 1 || LEQ( h[n[curr>>1].handle].key, h[n[curr].handle].key )) {
|
||||
FloatDown( pq, curr );
|
||||
} else {
|
||||
FloatUp( pq, curr );
|
||||
}
|
||||
}
|
||||
h[hCurr].key = NULL;
|
||||
h[hCurr].node = pq->freeList;
|
||||
pq->freeList = hCurr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Now redefine all the function names to map to their "Sort" versions. */
|
||||
|
||||
/* really tessPqSortNewPriorityQ */
|
||||
PriorityQ *pqNewPriorityQ( TESSalloc* alloc, int size, int (*leq)(PQkey key1, PQkey key2) )
|
||||
{
|
||||
PriorityQ *pq = (PriorityQ *)alloc->memalloc( alloc->userData, sizeof( PriorityQ ));
|
||||
if (pq == NULL) return NULL;
|
||||
|
||||
pq->heap = pqHeapNewPriorityQ( alloc, size, leq );
|
||||
if (pq->heap == NULL) {
|
||||
alloc->memfree( alloc->userData, pq );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// pq->keys = (PQkey *)memAlloc( INIT_SIZE * sizeof(pq->keys[0]) );
|
||||
pq->keys = (PQkey *)alloc->memalloc( alloc->userData, size * sizeof(pq->keys[0]) );
|
||||
if (pq->keys == NULL) {
|
||||
pqHeapDeletePriorityQ( alloc, pq->heap );
|
||||
alloc->memfree( alloc->userData, pq );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pq->size = 0;
|
||||
pq->max = size; //INIT_SIZE;
|
||||
pq->initialized = FALSE;
|
||||
pq->leq = leq;
|
||||
|
||||
return pq;
|
||||
}
|
||||
|
||||
/* really tessPqSortDeletePriorityQ */
|
||||
void pqDeletePriorityQ( TESSalloc* alloc, PriorityQ *pq )
|
||||
{
|
||||
assert(pq != NULL);
|
||||
if (pq->heap != NULL) pqHeapDeletePriorityQ( alloc, pq->heap );
|
||||
if (pq->order != NULL) alloc->memfree( alloc->userData, pq->order );
|
||||
if (pq->keys != NULL) alloc->memfree( alloc->userData, pq->keys );
|
||||
alloc->memfree( alloc->userData, pq );
|
||||
}
|
||||
|
||||
|
||||
#define LT(x,y) (! LEQ(y,x))
|
||||
#define GT(x,y) (! LEQ(x,y))
|
||||
#define Swap(a,b) if(1){PQkey *tmp = *a; *a = *b; *b = tmp;}else
|
||||
|
||||
/* really tessPqSortInit */
|
||||
int pqInit( TESSalloc* alloc, PriorityQ *pq )
|
||||
{
|
||||
PQkey **p, **r, **i, **j, *piv;
|
||||
struct { PQkey **p, **r; } Stack[50], *top = Stack;
|
||||
unsigned int seed = 2016473283;
|
||||
|
||||
/* Create an array of indirect pointers to the keys, so that we
|
||||
* the handles we have returned are still valid.
|
||||
*/
|
||||
/*
|
||||
pq->order = (PQkey **)memAlloc( (size_t)
|
||||
(pq->size * sizeof(pq->order[0])) );
|
||||
*/
|
||||
pq->order = (PQkey **)alloc->memalloc( alloc->userData,
|
||||
(size_t)((pq->size+1) * sizeof(pq->order[0])) );
|
||||
/* the previous line is a patch to compensate for the fact that IBM */
|
||||
/* machines return a null on a malloc of zero bytes (unlike SGI), */
|
||||
/* so we have to put in this defense to guard against a memory */
|
||||
/* fault four lines down. from fossum@austin.ibm.com. */
|
||||
if (pq->order == NULL) return 0;
|
||||
|
||||
p = pq->order;
|
||||
r = p + pq->size - 1;
|
||||
for( piv = pq->keys, i = p; i <= r; ++piv, ++i ) {
|
||||
*i = piv;
|
||||
}
|
||||
|
||||
/* Sort the indirect pointers in descending order,
|
||||
* using randomized Quicksort
|
||||
*/
|
||||
top->p = p; top->r = r; ++top;
|
||||
while( --top >= Stack ) {
|
||||
p = top->p;
|
||||
r = top->r;
|
||||
while( r > p + 10 ) {
|
||||
seed = seed * 1539415821 + 1;
|
||||
i = p + seed % (r - p + 1);
|
||||
piv = *i;
|
||||
*i = *p;
|
||||
*p = piv;
|
||||
i = p - 1;
|
||||
j = r + 1;
|
||||
do {
|
||||
do { ++i; } while( GT( **i, *piv ));
|
||||
do { --j; } while( LT( **j, *piv ));
|
||||
Swap( i, j );
|
||||
} while( i < j );
|
||||
Swap( i, j ); /* Undo last swap */
|
||||
if( i - p < r - j ) {
|
||||
top->p = j+1; top->r = r; ++top;
|
||||
r = i-1;
|
||||
} else {
|
||||
top->p = p; top->r = i-1; ++top;
|
||||
p = j+1;
|
||||
}
|
||||
}
|
||||
/* Insertion sort small lists */
|
||||
for( i = p+1; i <= r; ++i ) {
|
||||
piv = *i;
|
||||
for( j = i; j > p && LT( **(j-1), *piv ); --j ) {
|
||||
*j = *(j-1);
|
||||
}
|
||||
*j = piv;
|
||||
}
|
||||
}
|
||||
pq->max = pq->size;
|
||||
pq->initialized = TRUE;
|
||||
pqHeapInit( pq->heap ); /* always succeeds */
|
||||
|
||||
#ifndef NDEBUG
|
||||
p = pq->order;
|
||||
r = p + pq->size - 1;
|
||||
for( i = p; i < r; ++i ) {
|
||||
assert( LEQ( **(i+1), **i ));
|
||||
}
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* really tessPqSortInsert */
|
||||
/* returns INV_HANDLE iff out of memory */
|
||||
PQhandle pqInsert( TESSalloc* alloc, PriorityQ *pq, PQkey keyNew )
|
||||
{
|
||||
int curr;
|
||||
|
||||
if( pq->initialized ) {
|
||||
return pqHeapInsert( alloc, pq->heap, keyNew );
|
||||
}
|
||||
curr = pq->size;
|
||||
if( ++ pq->size >= pq->max ) {
|
||||
if (!alloc->memrealloc)
|
||||
{
|
||||
return INV_HANDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
PQkey *saveKey= pq->keys;
|
||||
// If the heap overflows, double its size.
|
||||
pq->max <<= 1;
|
||||
pq->keys = (PQkey *)alloc->memrealloc( alloc->userData, pq->keys,
|
||||
(size_t)(pq->max * sizeof( pq->keys[0] )));
|
||||
if (pq->keys == NULL) {
|
||||
pq->keys = saveKey; // restore ptr to free upon return
|
||||
return INV_HANDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(curr != INV_HANDLE);
|
||||
pq->keys[curr] = keyNew;
|
||||
|
||||
/* Negative handles index the sorted array. */
|
||||
return -(curr+1);
|
||||
}
|
||||
|
||||
/* really tessPqSortExtractMin */
|
||||
PQkey pqExtractMin( PriorityQ *pq )
|
||||
{
|
||||
PQkey sortMin, heapMin;
|
||||
|
||||
if( pq->size == 0 ) {
|
||||
return pqHeapExtractMin( pq->heap );
|
||||
}
|
||||
sortMin = *(pq->order[pq->size-1]);
|
||||
if( ! pqHeapIsEmpty( pq->heap )) {
|
||||
heapMin = pqHeapMinimum( pq->heap );
|
||||
if( LEQ( heapMin, sortMin )) {
|
||||
return pqHeapExtractMin( pq->heap );
|
||||
}
|
||||
}
|
||||
do {
|
||||
-- pq->size;
|
||||
} while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL );
|
||||
return sortMin;
|
||||
}
|
||||
|
||||
/* really tessPqSortMinimum */
|
||||
PQkey pqMinimum( PriorityQ *pq )
|
||||
{
|
||||
PQkey sortMin, heapMin;
|
||||
|
||||
if( pq->size == 0 ) {
|
||||
return pqHeapMinimum( pq->heap );
|
||||
}
|
||||
sortMin = *(pq->order[pq->size-1]);
|
||||
if( ! pqHeapIsEmpty( pq->heap )) {
|
||||
heapMin = pqHeapMinimum( pq->heap );
|
||||
if( LEQ( heapMin, sortMin )) {
|
||||
return heapMin;
|
||||
}
|
||||
}
|
||||
return sortMin;
|
||||
}
|
||||
|
||||
/* really tessPqSortIsEmpty */
|
||||
int pqIsEmpty( PriorityQ *pq )
|
||||
{
|
||||
return (pq->size == 0) && pqHeapIsEmpty( pq->heap );
|
||||
}
|
||||
|
||||
/* really tessPqSortDelete */
|
||||
void pqDelete( PriorityQ *pq, PQhandle curr )
|
||||
{
|
||||
if( curr >= 0 ) {
|
||||
pqHeapDelete( pq->heap, curr );
|
||||
return;
|
||||
}
|
||||
curr = -(curr+1);
|
||||
assert( curr < pq->max && pq->keys[curr] != NULL );
|
||||
|
||||
pq->keys[curr] = NULL;
|
||||
while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL ) {
|
||||
-- pq->size;
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
#ifndef PRIORITYQ_H
|
||||
#define PRIORITYQ_H
|
||||
|
||||
/* The basic operations are insertion of a new key (pqInsert),
|
||||
* and examination/extraction of a key whose value is minimum
|
||||
* (pqMinimum/pqExtractMin). Deletion is also allowed (pqDelete);
|
||||
* for this purpose pqInsert returns a "handle" which is supplied
|
||||
* as the argument.
|
||||
*
|
||||
* An initial heap may be created efficiently by calling pqInsert
|
||||
* repeatedly, then calling pqInit. In any case pqInit must be called
|
||||
* before any operations other than pqInsert are used.
|
||||
*
|
||||
* If the heap is empty, pqMinimum/pqExtractMin will return a NULL key.
|
||||
* This may also be tested with pqIsEmpty.
|
||||
*/
|
||||
|
||||
/* Since we support deletion the data structure is a little more
|
||||
* complicated than an ordinary heap. "nodes" is the heap itself;
|
||||
* active nodes are stored in the range 1..pq->size. When the
|
||||
* heap exceeds its allocated size (pq->max), its size doubles.
|
||||
* The children of node i are nodes 2i and 2i+1.
|
||||
*
|
||||
* Each node stores an index into an array "handles". Each handle
|
||||
* stores a key, plus a pointer back to the node which currently
|
||||
* represents that key (ie. nodes[handles[i].node].handle == i).
|
||||
*/
|
||||
|
||||
typedef void *PQkey;
|
||||
typedef int PQhandle;
|
||||
typedef struct PriorityQHeap PriorityQHeap;
|
||||
|
||||
#define INV_HANDLE 0x0fffffff
|
||||
|
||||
typedef struct { PQhandle handle; } PQnode;
|
||||
typedef struct { PQkey key; PQhandle node; } PQhandleElem;
|
||||
|
||||
struct PriorityQHeap {
|
||||
|
||||
PQnode *nodes;
|
||||
PQhandleElem *handles;
|
||||
int size, max;
|
||||
PQhandle freeList;
|
||||
int initialized;
|
||||
|
||||
int (*leq)(PQkey key1, PQkey key2);
|
||||
};
|
||||
|
||||
typedef struct PriorityQ PriorityQ;
|
||||
|
||||
struct PriorityQ {
|
||||
PriorityQHeap *heap;
|
||||
|
||||
PQkey *keys;
|
||||
PQkey **order;
|
||||
PQhandle size, max;
|
||||
int initialized;
|
||||
|
||||
int (*leq)(PQkey key1, PQkey key2);
|
||||
};
|
||||
|
||||
PriorityQ *pqNewPriorityQ( TESSalloc* alloc, int size, int (*leq)(PQkey key1, PQkey key2) );
|
||||
void pqDeletePriorityQ( TESSalloc* alloc, PriorityQ *pq );
|
||||
|
||||
int pqInit( TESSalloc* alloc, PriorityQ *pq );
|
||||
PQhandle pqInsert( TESSalloc* alloc, PriorityQ *pq, PQkey key );
|
||||
PQkey pqExtractMin( PriorityQ *pq );
|
||||
void pqDelete( PriorityQ *pq, PQhandle handle );
|
||||
|
||||
PQkey pqMinimum( PriorityQ *pq );
|
||||
int pqIsEmpty( PriorityQ *pq );
|
||||
|
||||
#endif
|
||||
+1324
File diff suppressed because it is too large.
Load diff
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
#ifndef SWEEP_H
|
||||
#define SWEEP_H
|
||||
|
||||
#include "mesh.h"
|
||||
|
||||
/* tessComputeInterior( tess ) computes the planar arrangement specified
|
||||
* by the given contours, and further subdivides this arrangement
|
||||
* into regions. Each region is marked "inside" if it belongs
|
||||
* to the polygon, according to the rule given by tess->windingRule.
|
||||
* Each interior region is guaranteed be monotone.
|
||||
*/
|
||||
int tessComputeInterior( TESStesselator *tess );
|
||||
|
||||
|
||||
/* The following is here *only* for access by debugging routines */
|
||||
|
||||
#include "dict.h"
|
||||
|
||||
/* For each pair of adjacent edges crossing the sweep line, there is
|
||||
* an ActiveRegion to represent the region between them. The active
|
||||
* regions are kept in sorted order in a dynamic dictionary. As the
|
||||
* sweep line crosses each vertex, we update the affected regions.
|
||||
*/
|
||||
|
||||
struct ActiveRegion {
|
||||
TESShalfEdge *eUp; /* upper edge, directed right to left */
|
||||
DictNode *nodeUp; /* dictionary node corresponding to eUp */
|
||||
int windingNumber; /* used to determine which regions are
|
||||
* inside the polygon */
|
||||
int inside; /* is this region inside the polygon? */
|
||||
int sentinel; /* marks fake edges at t = +/-infinity */
|
||||
int dirty; /* marks regions where the upper or lower
|
||||
* edge has changed, but we haven't checked
|
||||
* whether they intersect yet */
|
||||
int fixUpperEdge; /* marks temporary edges introduced when
|
||||
* we process a "right vertex" (one without
|
||||
* any edges leaving to the right) */
|
||||
};
|
||||
|
||||
#define RegionBelow(r) ((ActiveRegion *) dictKey(dictPred((r)->nodeUp)))
|
||||
#define RegionAbove(r) ((ActiveRegion *) dictKey(dictSucc((r)->nodeUp)))
|
||||
|
||||
#endif
|
||||
+1114
File diff suppressed because it is too large.
Load diff
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) [dates of first publication] Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** 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 including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ 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 SILICON GRAPHICS, INC.
|
||||
** 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.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Author: Eric Veach, July 1994.
|
||||
*/
|
||||
|
||||
#ifndef TESS_H
|
||||
#define TESS_H
|
||||
|
||||
#include <setjmp.h>
|
||||
#include "bucketalloc.h"
|
||||
#include "mesh.h"
|
||||
#include "dict.h"
|
||||
#include "priorityq.h"
|
||||
#include "../Include/tesselator.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//typedef struct TESStesselator TESStesselator;
|
||||
|
||||
struct TESStesselator {
|
||||
|
||||
/*** state needed for collecting the input data ***/
|
||||
TESSmesh *mesh; /* stores the input contours, and eventually
|
||||
the tessellation itself */
|
||||
int outOfMemory;
|
||||
|
||||
/*** state needed for projecting onto the sweep plane ***/
|
||||
|
||||
TESSreal normal[3]; /* user-specified normal (if provided) */
|
||||
TESSreal sUnit[3]; /* unit vector in s-direction (debugging) */
|
||||
TESSreal tUnit[3]; /* unit vector in t-direction (debugging) */
|
||||
|
||||
TESSreal bmin[2];
|
||||
TESSreal bmax[2];
|
||||
|
||||
int processCDT; /* option to run Constrained Delayney pass. */
|
||||
int reverseContours; /* tessAddContour() will treat CCW contours as CW and vice versa */
|
||||
|
||||
/*** state needed for the line sweep ***/
|
||||
int windingRule; /* rule for determining polygon interior */
|
||||
|
||||
Dict *dict; /* edge dictionary for sweep line */
|
||||
PriorityQ *pq; /* priority queue of vertex events */
|
||||
TESSvertex *event; /* current sweep event being processed */
|
||||
|
||||
struct BucketAlloc* regionPool;
|
||||
|
||||
TESSindex vertexIndexCounter;
|
||||
|
||||
TESSreal *vertices;
|
||||
TESSindex *vertexIndices;
|
||||
int vertexCount;
|
||||
TESSindex *elements;
|
||||
int elementCount;
|
||||
|
||||
TESSalloc alloc;
|
||||
|
||||
jmp_buf env; /* place to jump to when memAllocs fail */
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -14,6 +14,7 @@ swift_library(
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/GZip:GZip",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@@ -3,8 +3,7 @@ import LottieMeshSwift
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import GZip
|
||||
|
||||
#error("Exclude")
|
||||
import AppBundle
|
||||
|
||||
public final class MeshAnimationCache {
|
||||
private final class Item {
|
||||
@@ -16,14 +15,14 @@ public final class MeshAnimationCache {
|
||||
|
||||
private let mediaBox: MediaBox
|
||||
|
||||
private var items: [MediaResourceId: Item] = [:]
|
||||
private var items: [String: Item] = [:]
|
||||
|
||||
public init(mediaBox: MediaBox) {
|
||||
self.mediaBox = mediaBox
|
||||
}
|
||||
|
||||
public func get(resource: MediaResource) -> MeshAnimation? {
|
||||
if let item = self.items[resource.id] {
|
||||
if let item = self.items[resource.id.stringRepresentation] {
|
||||
if let animation = item.animation {
|
||||
return animation
|
||||
} else if let readyPath = item.readyPath, let data = try? Data(contentsOf: URL(fileURLWithPath: readyPath)) {
|
||||
@@ -36,7 +35,7 @@ public final class MeshAnimationCache {
|
||||
}
|
||||
} else {
|
||||
let item = Item()
|
||||
self.items[resource.id] = item
|
||||
self.items[resource.id.stringRepresentation] = item
|
||||
|
||||
let path = self.mediaBox.cachedRepresentationPathForId(resource.id.stringRepresentation, representationId: "mesh-animation", keepDuration: .general)
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
|
||||
@@ -50,6 +49,35 @@ public final class MeshAnimationCache {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func get(bundleName: String) -> MeshAnimation? {
|
||||
if let item = self.items[bundleName] {
|
||||
if let animation = item.animation {
|
||||
return animation
|
||||
} else if let readyPath = item.readyPath, let data = try? Data(contentsOf: URL(fileURLWithPath: readyPath)) {
|
||||
let buffer = MeshReadBuffer(data: data)
|
||||
let animation = MeshAnimation.read(buffer: buffer)
|
||||
item.animation = animation
|
||||
return animation
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
let item = Item()
|
||||
self.items[bundleName] = item
|
||||
|
||||
let path = self.mediaBox.cachedRepresentationPathForId(bundleName, representationId: "mesh-animation", keepDuration: .general)
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
|
||||
let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data))
|
||||
item.readyPath = path
|
||||
item.animation = animation
|
||||
return animation
|
||||
} else {
|
||||
self.cache(item: item, bundleName: bundleName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func cache(item: Item, resource: MediaResource) {
|
||||
let mediaBox = self.mediaBox
|
||||
@@ -89,7 +117,53 @@ public final class MeshAnimationCache {
|
||||
return
|
||||
}
|
||||
if let animationAndPath = animationAndPath {
|
||||
if let item = strongSelf.items[resource.id] {
|
||||
if let item = strongSelf.items[resource.id.stringRepresentation] {
|
||||
item.isPending = false
|
||||
item.animation = animationAndPath.0
|
||||
item.readyPath = animationAndPath.1
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
private func cache(item: Item, bundleName: String) {
|
||||
let mediaBox = self.mediaBox
|
||||
item.isPending = true
|
||||
item.disposable.set((Signal<(MeshAnimation, String)?, NoError> { subscriber in
|
||||
guard let path = getAppBundle().path(forResource: bundleName, ofType: "tgs") else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
|
||||
guard let zippedData = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
let jsonData = TGGUnzipData(zippedData, 1 * 1024 * 1024) ?? zippedData
|
||||
if let animation = generateMeshAnimation(data: jsonData) {
|
||||
let buffer = MeshWriteBuffer()
|
||||
animation.write(buffer: buffer)
|
||||
mediaBox.storeCachedResourceRepresentation(bundleName, representationId: "mesh-animation", keepDuration: .general, data: buffer.makeData(), completion: { path in
|
||||
subscriber.putNext((animation, path))
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
} else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
|> runOn(Queue.concurrentDefaultQueue())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] animationAndPath in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let animationAndPath = animationAndPath {
|
||||
if let item = strongSelf.items[bundleName] {
|
||||
item.isPending = false
|
||||
item.animation = animationAndPath.0
|
||||
item.readyPath = animationAndPath.1
|
||||
|
||||
@@ -213,7 +213,7 @@ public final class MediaBox {
|
||||
return ResourceStorePaths(partial: "\(fileNameForId(id))_partial", complete: "\(fileNameForId(id))")
|
||||
}
|
||||
|
||||
private func cachedRepresentationPathsForId(_ id: MediaResourceId, representationId: String, keepDuration: CachedMediaRepresentationKeepDuration) -> ResourceStorePaths {
|
||||
private func cachedRepresentationPathsForId(_ id: String, representationId: String, keepDuration: CachedMediaRepresentationKeepDuration) -> ResourceStorePaths {
|
||||
let cacheString: String
|
||||
switch keepDuration {
|
||||
case .general:
|
||||
@@ -784,14 +784,22 @@ public final class MediaBox {
|
||||
|
||||
public func storeCachedResourceRepresentation(_ resource: MediaResource, representation: CachedMediaResourceRepresentation, data: Data) {
|
||||
self.dataQueue.async {
|
||||
let path = self.cachedRepresentationPathsForId(resource.id, representationId: representation.uniqueId, keepDuration: representation.keepDuration).complete
|
||||
let path = self.cachedRepresentationPathsForId(resource.id.stringRepresentation, representationId: representation.uniqueId, keepDuration: representation.keepDuration).complete
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: path))
|
||||
}
|
||||
}
|
||||
|
||||
public func storeCachedResourceRepresentation(_ resource: MediaResource, representationId: String, keepDuration: CachedMediaRepresentationKeepDuration, data: Data, completion: @escaping (String) -> Void = { _ in }) {
|
||||
self.dataQueue.async {
|
||||
let path = self.cachedRepresentationPathsForId(resource.id, representationId: representationId, keepDuration: keepDuration).complete
|
||||
let path = self.cachedRepresentationPathsForId(resource.id.stringRepresentation, representationId: representationId, keepDuration: keepDuration).complete
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: path))
|
||||
completion(path)
|
||||
}
|
||||
}
|
||||
|
||||
public func storeCachedResourceRepresentation(_ resourceId: String, representationId: String, keepDuration: CachedMediaRepresentationKeepDuration, data: Data, completion: @escaping (String) -> Void = { _ in }) {
|
||||
self.dataQueue.async {
|
||||
let path = self.cachedRepresentationPathsForId(resourceId, representationId: representationId, keepDuration: keepDuration).complete
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: path))
|
||||
completion(path)
|
||||
}
|
||||
@@ -802,7 +810,7 @@ public final class MediaBox {
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
let begin: () -> Void = {
|
||||
let paths = self.cachedRepresentationPathsForId(resource.id, representationId: representation.uniqueId, keepDuration: representation.keepDuration)
|
||||
let paths = self.cachedRepresentationPathsForId(resource.id.stringRepresentation, representationId: representation.uniqueId, keepDuration: representation.keepDuration)
|
||||
if let size = fileSize(paths.complete) {
|
||||
self.timeBasedCleanup.touch(paths: [
|
||||
paths.complete
|
||||
@@ -971,7 +979,7 @@ public final class MediaBox {
|
||||
let begin: () -> Void = {
|
||||
let paths: ResourceStorePaths
|
||||
if let baseResourceId = baseResourceId {
|
||||
paths = self.cachedRepresentationPathsForId(MediaResourceId(baseResourceId), representationId: id, keepDuration: keepDuration)
|
||||
paths = self.cachedRepresentationPathsForId(MediaResourceId(baseResourceId).stringRepresentation, representationId: id, keepDuration: keepDuration)
|
||||
} else {
|
||||
paths = self.storePathsForId(MediaResourceId(id))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
final class ReactionStrip: ASDisplayNode {
|
||||
private var labelValues: [String] = []
|
||||
private var labelNodes: [ImmediateTextNode] = []
|
||||
|
||||
var selected: ((String) -> Void)?
|
||||
|
||||
override init() {
|
||||
self.labelValues = ["🧡", "🎆", "🎈", "🎉", "👍", "👎", "💩", "💸", "😂"]
|
||||
|
||||
super.init()
|
||||
|
||||
for labelValue in self.labelValues {
|
||||
let labelNode = ImmediateTextNode()
|
||||
labelNode.attributedText = NSAttributedString(string: labelValue, font: Font.regular(20.0), textColor: .black)
|
||||
self.labelNodes.append(labelNode)
|
||||
self.addSubnode(labelNode)
|
||||
labelNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.labelTapGesture(_:))))
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func labelTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
for i in 0 ..< self.labelNodes.count {
|
||||
if self.labelNodes[i].view === recognizer.view {
|
||||
self.selected?(self.labelValues[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
var labelOrigin = CGPoint(x: 0.0, y: 0.0)
|
||||
for labelNode in self.labelNodes {
|
||||
let labelSize = labelNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
labelNode.frame = CGRect(origin: labelOrigin, size: labelSize)
|
||||
labelOrigin.x += labelSize.width + 10.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import MapResourceToAvatarSizes
|
||||
import SolidRoundedButtonNode
|
||||
import AudioBlob
|
||||
import DeviceAccess
|
||||
import LottieMeshSwift
|
||||
|
||||
let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||
let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
||||
@@ -39,8 +40,8 @@ let fullscreenBackgroundColor = UIColor(rgb: 0x000000)
|
||||
private let smallButtonSize = CGSize(width: 36.0, height: 36.0)
|
||||
private let sideButtonSize = CGSize(width: 56.0, height: 56.0)
|
||||
private let topPanelHeight: CGFloat = 63.0
|
||||
let bottomAreaHeight: CGFloat = 206.0
|
||||
private let fullscreenBottomAreaHeight: CGFloat = 80.0
|
||||
let bottomAreaHeight: CGFloat = 206.0 + 32.0
|
||||
private let fullscreenBottomAreaHeight: CGFloat = 80.0 + 32.0
|
||||
private let bottomGradientHeight: CGFloat = 70.0
|
||||
|
||||
func decorationCornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
|
||||
@@ -780,11 +781,14 @@ public final class VoiceChatController: ViewController {
|
||||
fileprivate let switchCameraButton: CallControllerButtonItemNode
|
||||
fileprivate let leaveButton: CallControllerButtonItemNode
|
||||
fileprivate let actionButton: VoiceChatActionButton
|
||||
fileprivate let reactionStrip: ReactionStrip
|
||||
private let leftBorderNode: ASDisplayNode
|
||||
private let rightBorderNode: ASDisplayNode
|
||||
private let mainStageContainerNode: ASDisplayNode
|
||||
private let mainStageBackgroundNode: ASDisplayNode
|
||||
private let mainStageNode: VoiceChatMainStageNode
|
||||
|
||||
private var meshAnimationView: UIView?
|
||||
|
||||
private let transitionMaskView: UIView
|
||||
private let transitionMaskTopFillLayer: CALayer
|
||||
@@ -1056,6 +1060,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.switchCameraButton.isUserInteractionEnabled = false
|
||||
self.leaveButton = CallControllerButtonItemNode()
|
||||
self.actionButton = VoiceChatActionButton()
|
||||
self.reactionStrip = ReactionStrip()
|
||||
|
||||
if self.isScheduling {
|
||||
self.cameraButton.alpha = 0.0
|
||||
@@ -1136,8 +1141,38 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.participantsNode = VoiceChatTimerNode(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
self.meshAnimationView = MeshRenderer()
|
||||
self.meshAnimationView?.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.reactionStrip.selected = { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let mapping: [String: String] = [
|
||||
"🧡": "Hearts",
|
||||
"🎆": "Fireworks",
|
||||
"🎈": "Balloon",
|
||||
"🎉": "Party",
|
||||
"👍": "SuperThumbsUp1",
|
||||
"👎": "SuperThumbsDown",
|
||||
"💩": "Poo",
|
||||
"💸": "Money",
|
||||
"😂": "Joy"
|
||||
]
|
||||
|
||||
if let name = mapping[value] {
|
||||
if #available(iOS 13.0, *) {
|
||||
if let animation = call.accountContext.meshAnimationCache.get(bundleName: name), let meshAnimationView = strongSelf.meshAnimationView as? MeshRenderer {
|
||||
meshAnimationView.add(mesh: animation, offset: CGPoint())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(context.account.peerId)
|
||||
|> map { peer in
|
||||
@@ -1840,6 +1875,11 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.addSubnode(self.dimNode)
|
||||
self.addSubnode(self.contentContainer)
|
||||
|
||||
if let meshAnimationView = self.meshAnimationView {
|
||||
self.view.addSubview(meshAnimationView)
|
||||
}
|
||||
|
||||
self.contentContainer.addSubnode(self.backgroundNode)
|
||||
|
||||
self.contentContainer.addSubnode(self.listContainer)
|
||||
@@ -1856,6 +1896,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.contentContainer.addSubnode(self.mainStageContainerNode)
|
||||
self.contentContainer.addSubnode(self.transitionContainerNode)
|
||||
self.contentContainer.addSubnode(self.bottomPanelNode)
|
||||
self.contentContainer.addSubnode(self.reactionStrip)
|
||||
self.contentContainer.addSubnode(self.timerNode)
|
||||
self.contentContainer.addSubnode(self.scheduleTextNode)
|
||||
self.contentContainer.addSubnode(self.fullscreenListContainer)
|
||||
@@ -4277,6 +4318,12 @@ public final class VoiceChatController: ViewController {
|
||||
let previousLayout = self.validLayout?.0
|
||||
self.validLayout = (layout, navigationHeight)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
if let meshAnimationView = self.meshAnimationView as? MeshRenderer {
|
||||
meshAnimationView.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.size.width), size: CGSize(width: layout.size.width, height: layout.size.width))
|
||||
}
|
||||
}
|
||||
|
||||
let size = layout.size
|
||||
let contentWidth: CGFloat
|
||||
let headerWidth: CGFloat
|
||||
@@ -4734,6 +4781,9 @@ public final class VoiceChatController: ViewController {
|
||||
} : nil)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.reactionStrip, frame: CGRect(origin: CGPoint(x: 12.0, y: size.height - 44.0 - 10.0), size: CGSize(width: size.width - 12.0 * 2.0, height: 44.0)))
|
||||
self.reactionStrip.update(size: self.reactionStrip.bounds.size)
|
||||
|
||||
self.cameraButton.isUserInteractionEnabled = hasCameraButton
|
||||
|
||||
var buttonsTransition: ContainedViewLayoutTransition = .immediate
|
||||
|
||||
@@ -243,8 +243,8 @@ swift_library(
|
||||
"//submodules/AdUI:AdUI",
|
||||
"//submodules/SparseItemGrid:SparseItemGrid",
|
||||
"//submodules/CalendarMessageScreen:CalendarMessageScreen",
|
||||
#"//submodules/LottieMeshSwift:LottieMeshSwift",
|
||||
#"//submodules/MeshAnimationCache:MeshAnimationCache",
|
||||
"//submodules/LottieMeshSwift:LottieMeshSwift",
|
||||
"//submodules/MeshAnimationCache:MeshAnimationCache",
|
||||
"//submodules/DirectMediaImageCache:DirectMediaImageCache",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||
|
||||
@@ -16,7 +16,7 @@ import TelegramCallsUI
|
||||
import TelegramBaseController
|
||||
import AsyncDisplayKit
|
||||
import PresentationDataUtils
|
||||
//import MeshAnimationCache
|
||||
import MeshAnimationCache
|
||||
|
||||
private final class DeviceSpecificContactImportContext {
|
||||
let disposable = MetaDisposable()
|
||||
@@ -156,7 +156,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
private var experimentalUISettingsDisposable: Disposable?
|
||||
|
||||
public let cachedGroupCallContexts: AccountGroupCallContextCache
|
||||
//public let meshAnimationCache: MeshAnimationCache
|
||||
public let meshAnimationCache: MeshAnimationCache
|
||||
|
||||
public init(sharedContext: SharedAccountContextImpl, account: Account, limitsConfiguration: LimitsConfiguration, contentSettings: ContentSettings, appConfiguration: AppConfiguration, temp: Bool = false)
|
||||
{
|
||||
@@ -189,7 +189,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
}
|
||||
|
||||
self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl()
|
||||
//self.meshAnimationCache = MeshAnimationCache(mediaBox: account.postbox.mediaBox)
|
||||
self.meshAnimationCache = MeshAnimationCache(mediaBox: account.postbox.mediaBox)
|
||||
|
||||
let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration])
|
||||
|> map { preferences -> LimitsConfiguration in
|
||||
|
||||
@@ -24,7 +24,7 @@ import ShimmerEffect
|
||||
import WallpaperBackgroundNode
|
||||
import LocalMediaResources
|
||||
import AppBundle
|
||||
//import LottieMeshSwift
|
||||
import LottieMeshSwift
|
||||
|
||||
private let nameFont = Font.medium(14.0)
|
||||
private let inlineBotPrefixFont = Font.regular(14.0)
|
||||
@@ -1435,7 +1435,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId)
|
||||
|
||||
/*if #available(iOS 13.0, *), item.context.sharedContext.immediateExperimentalUISettings.acceleratedStickers, let meshAnimation = item.context.meshAnimationCache.get(resource: file.resource) {
|
||||
if #available(iOS 13.0, *), item.context.sharedContext.immediateExperimentalUISettings.acceleratedStickers, let meshAnimation = item.context.meshAnimationCache.get(resource: file.resource) {
|
||||
var overlayMeshAnimationNode: ChatMessageTransitionNode.DecorationItemNode?
|
||||
if let current = self.overlayMeshAnimationNode {
|
||||
overlayMeshAnimationNode = current
|
||||
@@ -1466,7 +1466,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
if let meshRenderer = overlayMeshAnimationNode?.contentView as? MeshRenderer {
|
||||
meshRenderer.add(mesh: meshAnimation, offset: CGPoint(x: CGFloat.random(in: -30.0 ... 30.0), y: CGFloat.random(in: -30.0 ... 30.0)))
|
||||
}
|
||||
} else*/ do {
|
||||
} else {
|
||||
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||
let additionalAnimationNode = AnimatedStickerNode()
|
||||
additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 2.0), height: Int(animationSize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix))
|
||||
|
||||
@@ -803,7 +803,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
[list addObject:@"2.7.7"];
|
||||
[list addObject:@"3.0.0"];
|
||||
if (includeReference) {
|
||||
//[list addObject:@"4.0.0"];
|
||||
[list addObject:@"4.0.0"];
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user