├── .gitignore ├── Assets └── youtube_demo_link_image.jpg ├── Common └── Cpp │ ├── DatagramSocket │ ├── DatagramSocket.cpp │ └── DatagramSocket.h │ └── PonkDefs.h ├── LICENSE ├── README.md ├── Samples └── Cpp │ ├── PonkUDPReceiver │ ├── CMakeLists.txt │ └── main.cpp │ └── PonkUDPSender │ ├── CMakeLists.txt │ └── main.cpp └── Touch Plugin ├── CPlusPlus_Common.h ├── GL_Extensions.h ├── Info.plist ├── PonkOutput ├── PonkOutput.cpp ├── PonkOutput.h ├── PonkOutput.sln ├── PonkOutput.vcxproj ├── PonkOutput.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── PonkOutput.xcscheme ├── SOP_CPlusPlusBase.h ├── Sample Project ├── Plugins │ ├── PonkOutput.dll │ ├── PonkOutput.plugin-notarized.zip │ └── PonkOutput.plugin │ │ └── Contents │ │ ├── Info.plist │ │ ├── MacOS │ │ └── PonkOutput │ │ └── _CodeSignature │ │ └── CodeResources ├── PonkDemo.toe ├── PonkDemoMultiOut.toe ├── PonkDemoTexturedPaths.toe └── ponk_output.tox └── matrix.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | .DS_Store 352 | MadLaserBridge.xcodeproj/project.xcworkspace/xcuserdata 353 | *.xcuserstate 354 | 355 | # Qt build folders 356 | build-* 357 | 358 | # Xcode user data 359 | xcuserdata 360 | 361 | # Touch designer known plugins file 362 | Plugins.json 363 | Touch Plugin/Sample Project/Plugins/Plugins.json 364 | 365 | # CMake 366 | CMakeFiles 367 | CMakeCache.txt 368 | *.cmake 369 | Makefile 370 | -------------------------------------------------------------------------------- /Assets/youtube_demo_link_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madmappersoftware/Ponk/ebfa51cab92b8b1a660f2024b6f8e2ed4d1d58d7/Assets/youtube_demo_link_image.jpg -------------------------------------------------------------------------------- /Common/Cpp/DatagramSocket/DatagramSocket.cpp: -------------------------------------------------------------------------------- 1 | #include "DatagramSocket.h" 2 | #include 3 | #include "errno.h" 4 | #include 5 | 6 | /********************************************************************************* 7 | UNIX version 8 | *********************************************************************************/ 9 | 10 | #if defined(__linux__) || defined (__APPLE__) 11 | 12 | #include 13 | 14 | DatagramSocket::DatagramSocket(unsigned int interfaceIP, unsigned int port): 15 | m_port(port) 16 | { 17 | m_socket = socket(AF_INET,SOCK_DGRAM,0); 18 | if (m_socket==INVALID_SOCKET) { 19 | std::cout << "Error creating datagram socket on port " << m_port << std::endl; 20 | assert(false); 21 | return; 22 | } 23 | 24 | // reuse addr 25 | int yes=1; 26 | if (setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) { 27 | std::cout << "Error setting SO_REUSEADDR option" << std::endl; 28 | assert(false); 29 | } 30 | 31 | #ifdef SO_REUSEPORT 32 | if (setsockopt (m_socket, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(int)) != 0) { 33 | std::cout << "Error setting SO_REUSEPORT option" << std::endl; 34 | assert(false); 35 | } 36 | #endif 37 | 38 | // If port is 0, bind anyway so when sending a packet the OS know on which network to send it 39 | // (in case both networks have the same IP mask (ie 192.168.1.xxx / 255.255.255.0) 40 | struct SOCKADDR_IN addr; 41 | memset(&addr,0,sizeof(addr)); 42 | addr.sin_family = AF_INET; 43 | addr.sin_port = htons(port); 44 | addr.sin_addr.s_addr = htonl(interfaceIP); 45 | bzero(&(addr.sin_zero), 8); /* zero the rest of the struct */ 46 | if(bind(m_socket, (struct sockaddr*)&addr, sizeof(sockaddr)) != 0) { 47 | std::cout << "Failed to bind datagram socket on interface " << ipIntToStr(interfaceIP) << " / port " << port << std::endl; 48 | assert(false); 49 | if (port != 0) { 50 | closeSocket(); 51 | return; 52 | } 53 | } 54 | 55 | // set non blocking 56 | if (fcntl(m_socket,F_SETFL,O_NONBLOCK) < 0) { 57 | std::cout << "Error setting O_NONBLOCK option" << std::endl; 58 | assert(false); 59 | closeSocket(); 60 | return; 61 | } 62 | 63 | // enable broadcast 64 | if (setsockopt(m_socket,SOL_SOCKET,SO_BROADCAST,&yes,sizeof(int)) != 0) 65 | { 66 | std::cout << "Error setting SO_BROADCAST option" << std::endl; 67 | assert(false); 68 | closeSocket(); 69 | return; 70 | } 71 | } 72 | 73 | DatagramSocket::~DatagramSocket() 74 | { 75 | closeSocket(); 76 | } 77 | 78 | void DatagramSocket::closeSocket() 79 | { 80 | if (m_socket != INVALID_SOCKET) { 81 | close(m_socket); 82 | m_socket = INVALID_SOCKET; 83 | } 84 | } 85 | 86 | bool DatagramSocket::isInitialized() 87 | { 88 | return (m_socket != INVALID_SOCKET); 89 | } 90 | 91 | bool DatagramSocket::joinMulticastGroup(unsigned int ip, unsigned int interfaceIP) { 92 | int res = 0; 93 | 94 | // Set TTL (Time To Live) 95 | int ttl = 127; 96 | res = setsockopt(m_socket,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl)); 97 | if (res < 0) { 98 | std::cout << "Error in DatagramSocket: could not set TTL, error: " << strerror(errno) << std::endl; 99 | assert(false); 100 | } 101 | 102 | struct ip_mreq mreq; 103 | mreq.imr_interface.s_addr = htonl(interfaceIP); 104 | mreq.imr_multiaddr.s_addr = htonl(ip); 105 | res = setsockopt(m_socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)); 106 | if (res < 0) { 107 | if (errno == EADDRINUSE) { 108 | // Ignore, it means we already joined this group 109 | } else { 110 | std::cout << "Error in DatagramSocket: IP_ADD_MEMBERSHIP error: " << strerror(errno) << std::endl; 111 | assert(false); 112 | return false; 113 | } 114 | } 115 | 116 | return true; 117 | } 118 | 119 | bool DatagramSocket::leaveMulticastGroup(unsigned int ip, unsigned int interfaceIP) { 120 | struct ip_mreq mreq; 121 | mreq.imr_interface.s_addr = htonl(interfaceIP); 122 | mreq.imr_multiaddr.s_addr = htonl(ip); 123 | auto res = setsockopt(m_socket,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(mreq)); 124 | if (res < 0) { 125 | std::cout << "Error in DatagramSocket: IP_DROP_MEMBERSHIP error: " << strerror(errno) << std::endl; 126 | assert(false); 127 | return false; 128 | } 129 | 130 | return true; 131 | } 132 | 133 | bool DatagramSocket::sendBroadcast(unsigned int port,void * buf,unsigned int buflen) 134 | { 135 | SOCKADDR_IN to; 136 | memset(&to,0,sizeof(to)); 137 | to.sin_family = AF_INET; 138 | to.sin_addr.s_addr= htonl(0xFFFFFFFF); 139 | to.sin_port = htons(port); 140 | bzero(&(to.sin_zero), 8); /* zero the rest of the struct */ 141 | auto ret = sendto(m_socket,(char*)buf,buflen,0,(sockaddr *) &to, sizeof ( SOCKADDR_IN )); 142 | if (ret!=buflen) { 143 | std::cout << "Error in DatagramSocket: sendto error: " << strerror(errno) << std::endl; 144 | } 145 | return ((unsigned int)ret == buflen); 146 | } 147 | 148 | bool DatagramSocket::sendTo(const GenericAddr & addr,const void *buf,unsigned int buflen) 149 | { 150 | if (buflen == 0) { 151 | assert(false); 152 | return false; 153 | } 154 | 155 | SOCKADDR_IN to; 156 | memset(&to,0,sizeof(to)); 157 | to.sin_family = addr.family; 158 | to.sin_addr.s_addr = htonl(addr.ip); 159 | to.sin_port = htons(addr.port); 160 | bzero(&(to.sin_zero), 8); /* zero the rest of the struct */ 161 | auto ret = sendto(m_socket,(void *)buf,buflen,0,(sockaddr *)&to,sizeof(sockaddr)); 162 | if (ret != buflen) { 163 | std::cout << "Error in DatagramSocket: sendto error: " << strerror(errno) << " on interface " << ipIntToStr(addr.ip) << std::endl; 164 | } 165 | return ((unsigned int)ret == buflen); 166 | } 167 | 168 | bool DatagramSocket::recvFrom(GenericAddr & addr,void * buf,unsigned int & buflen) 169 | { 170 | SOCKADDR_IN from; 171 | memset((void*)&from,0,sizeof(from)); 172 | int from_addr_len = sizeof(from); 173 | auto res = recvfrom(m_socket,(void*)buf,buflen,0,(sockaddr*)&from,(socklen_t*)&from_addr_len); 174 | 175 | // we received datas 176 | if (res > 0) { 177 | assert(from_addr_len == sizeof(sockaddr)); 178 | buflen = static_cast(res); 179 | 180 | addr.family = AF_INET; 181 | addr.ip = ntohl(from.sin_addr.s_addr); 182 | addr.port = ntohs(from.sin_port); 183 | 184 | return true; 185 | } else { 186 | if (errno == EWOULDBLOCK) { 187 | // that's normal for non blocking sockets 188 | // when there is no data 189 | buflen = 0; 190 | return true; 191 | } 192 | else 193 | { 194 | // no datas, no error 195 | buflen = 0; 196 | return true; 197 | } 198 | } 199 | } 200 | 201 | #endif 202 | 203 | /********************************************************************************* 204 | WIN32 version 205 | *********************************************************************************/ 206 | 207 | #if defined(_WIN32) 208 | 209 | #include 210 | 211 | DatagramSocket::DatagramSocket(unsigned int interfaceIP, unsigned int port) 212 | { 213 | m_port = port; 214 | 215 | static bool wsaStartedUp = false; 216 | if (wsaStartedUp == false) { 217 | wsaStartedUp = true; 218 | WSADATA wsaData = {0}; 219 | WSAStartup(MAKEWORD(2, 2), &wsaData); 220 | } 221 | 222 | // create socket 223 | m_socket=socket ( AF_INET, SOCK_DGRAM, 0 ); 224 | if (INVALID_SOCKET == m_socket) { 225 | std::cout << "Error: Could not create DatagramSocket" << std::endl; 226 | closeSocket(); 227 | return; 228 | } 229 | 230 | // enable bradcast 231 | BOOL fBroadcast=1; 232 | int err = setsockopt(m_socket,SOL_SOCKET, 233 | SO_BROADCAST,(CHAR *)&fBroadcast, 234 | sizeof(BOOL)); 235 | if ( SOCKET_ERROR == err ) { 236 | std::cout << "Error: Could not set DatagramSocket SO_BROADCAST option" << std::endl; 237 | closeSocket(); 238 | return; 239 | } 240 | 241 | // reuseaddr 242 | BOOL fReUseAddr=1; 243 | err = setsockopt(m_socket,SOL_SOCKET, 244 | SO_REUSEADDR,(CHAR *)&fReUseAddr, 245 | sizeof(BOOL)); 246 | if ( SOCKET_ERROR == err ) { 247 | std::cout << "Error: Could not set DatagramSocket SO_REUSEADDR option" << std::endl; 248 | } 249 | 250 | // non blocking 251 | unsigned long value = 1; 252 | err = ioctlsocket(m_socket, FIONBIO, &value); 253 | if ( SOCKET_ERROR == err ) { 254 | std::cout << "Error: Could not set DatagramSocket FIONBIO value" << std::endl; 255 | closeSocket(); 256 | return; 257 | } 258 | 259 | // increase buffer size 260 | int bufLen = 200000; 261 | err = setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, (CHAR *)&bufLen, sizeof(bufLen)); 262 | if ( SOCKET_ERROR == err ) { 263 | std::cout << "Error: Could not set DatagramSocket SO_RCVBUF option" << std::endl; 264 | } 265 | 266 | // If port is 0, bind anyway so when sending a packet the OS know on which network to send it 267 | // (in case both networks have the same IP mask (ie 192.168.1.xxx / 255.255.255.0) 268 | SOCKADDR_IN own; 269 | own.sin_family = AF_INET; 270 | own.sin_addr.s_addr = htonl ( interfaceIP ); 271 | own.sin_port = htons ( m_port ); 272 | err = bind (m_socket, (SOCKADDR *) &own, sizeof (SOCKADDR_IN) ); 273 | if (err != 0) { 274 | if (port != 0) { 275 | std::cout << "Failed to bind datagram socket" << std::endl; 276 | assert(false); 277 | closeSocket(); 278 | return; 279 | } 280 | } 281 | } 282 | 283 | DatagramSocket::~DatagramSocket() 284 | { 285 | closeSocket(); 286 | } 287 | 288 | void DatagramSocket::closeSocket() 289 | { 290 | if (m_socket != INVALID_SOCKET) { 291 | closesocket(m_socket); 292 | m_socket = INVALID_SOCKET; 293 | } 294 | } 295 | 296 | bool DatagramSocket::isInitialized() 297 | { 298 | return m_socket != INVALID_SOCKET; 299 | } 300 | 301 | bool DatagramSocket::joinMulticastGroup(unsigned int ip, unsigned int interfaceIP) { 302 | struct ip_mreq mreq; 303 | 304 | int ttl = 64; // Limits to same region 305 | if (setsockopt( 306 | m_socket, 307 | IPPROTO_IP, 308 | IP_MULTICAST_TTL, 309 | (char *)&ttl, 310 | sizeof(ttl)) == SOCKET_ERROR) 311 | { 312 | int osErr = WSAGetLastError(); 313 | std::cout << "Error in DatagramSocket: could not set IP_MULTICAST_TTL (error " << std::to_string(osErr) << ")" << std::endl; 314 | assert(false); 315 | } 316 | 317 | #ifndef IP_ADD_SOURCE_MEMBERSHIP 318 | #define IP_ADD_SOURCE_MEMBERSHIP 15 /* join IP group/source */ 319 | #endif 320 | 321 | // Join the multicast group from which to receive datagrams. 322 | mreq.imr_multiaddr.s_addr = htonl(ip); 323 | mreq.imr_interface.s_addr = htonl(interfaceIP); 324 | if (setsockopt (m_socket, 325 | IPPROTO_IP, 326 | IP_ADD_MEMBERSHIP, 327 | (char FAR *)&mreq, 328 | sizeof (mreq)) == SOCKET_ERROR) 329 | { 330 | int osErr = WSAGetLastError(); 331 | 332 | // WSAEADDRNOTAVAIL error means that we already joined this group 333 | if (osErr != WSAEADDRNOTAVAIL) 334 | { 335 | std::cout << "Error in DatagramSocket: could not set IP_ADD_MEMBERSHIP (error " << std::to_string(osErr) << ")" << std::endl; 336 | assert(false); 337 | return false; 338 | } 339 | } 340 | 341 | return true; 342 | } 343 | 344 | bool DatagramSocket::leaveMulticastGroup(unsigned int ip, unsigned int interfaceIP) { 345 | #ifndef IP_DROP_SOURCE_MEMBERSHIP 346 | #define IP_DROP_SOURCE_MEMBERSHIP 16 /* leave IP group/source */ 347 | #endif 348 | 349 | // Join the multicast group from which to receive datagrams. 350 | ip_mreq mreq; 351 | mreq.imr_multiaddr.s_addr = htonl(ip); 352 | mreq.imr_interface.s_addr = htonl(interfaceIP); 353 | if (setsockopt (m_socket, 354 | IPPROTO_IP, 355 | IP_DROP_SOURCE_MEMBERSHIP, 356 | (char FAR *)&mreq, 357 | sizeof (mreq)) == SOCKET_ERROR) 358 | { 359 | int osErr = WSAGetLastError(); 360 | 361 | // WSAEADDRNOTAVAIL error means that we already joined this group 362 | if (osErr != WSAEADDRNOTAVAIL) 363 | { 364 | std::cout << "Error in DatagramSocket: could not set IP_DROP_SOURCE_MEMBERSHIP (error " << std::to_string(osErr) << ")" << std::endl; 365 | assert(false); 366 | return false; 367 | } 368 | } 369 | 370 | return true; 371 | } 372 | 373 | bool DatagramSocket::sendBroadcast(unsigned int port, void * buf, unsigned int buflen) 374 | { 375 | SOCKADDR_IN target; 376 | target.sin_family = AF_INET; 377 | target.sin_addr.s_addr=htonl ( INADDR_BROADCAST ); 378 | target.sin_port = htons ( port ); 379 | int res = sendto(m_socket, 380 | (char*)buf,buflen,0, 381 | (SOCKADDR *) &target, sizeof ( SOCKADDR_IN )); 382 | if (res != buflen) { 383 | assert(false); 384 | return false; 385 | } 386 | return true; 387 | } 388 | 389 | bool DatagramSocket::sendTo(const GenericAddr & addr, const void *buf, unsigned int buflen) 390 | { 391 | SOCKADDR_IN target; 392 | target.sin_family = addr.family; 393 | target.sin_addr.s_addr= htonl(addr.ip); 394 | target.sin_port = htons(addr.port); 395 | int res = sendto(m_socket,(char*) buf,buflen,0,(SOCKADDR *) &target, sizeof ( SOCKADDR_IN )); 396 | if (res != buflen) { 397 | int osErr = WSAGetLastError(); 398 | if (osErr == WSAEWOULDBLOCK) { 399 | // there is nothing on the input 400 | return true; 401 | } else if (osErr == WSAECONNRESET) { 402 | // nothing on the other hand, ignore 403 | return true; 404 | } else { 405 | std::cout << "Error in DatagramSocket: writing failed (error " << std::to_string(osErr) << ")" << std::endl; 406 | } 407 | 408 | return false; 409 | } 410 | return true; 411 | } 412 | 413 | bool DatagramSocket::recvFrom(GenericAddr& addr, void * buf, unsigned int & buflen) 414 | { 415 | SOCKADDR_IN source; 416 | source.sin_family = AF_INET; 417 | source.sin_addr.s_addr = htonl(INADDR_ANY); 418 | source.sin_port = htons(m_port); 419 | int nSize = sizeof ( SOCKADDR_IN ); 420 | buflen = recvfrom (m_socket,(char*)buf,buflen,0,(SOCKADDR FAR *) &source,&nSize); 421 | 422 | if (buflen == SOCKET_ERROR) { 423 | buflen = 0; 424 | 425 | int osErr = WSAGetLastError(); 426 | if (osErr == WSAEWOULDBLOCK) { 427 | // there is nothing on the input 428 | return true; 429 | } else if (osErr == WSAECONNRESET) { 430 | // recfrom documentation froim Windows: WSAECONNRESET - On a UDP-datagram socket this error indicates a previous send operation resulted in an ICMP Port Unreachable message 431 | // Ignore this error or we'll get a connection reset after sending a packet to a non existing target 432 | return true; 433 | } else { 434 | std::cout << "Error in DatagramSocket: reading failed (error " << std::to_string(osErr) << ")" << std::endl; 435 | } 436 | 437 | return false; 438 | } 439 | 440 | addr.family = AF_INET; 441 | addr.ip = ntohl(source.sin_addr.s_addr); 442 | addr.port = ntohs(source.sin_port); 443 | 444 | return true; 445 | } 446 | 447 | #endif 448 | -------------------------------------------------------------------------------- /Common/Cpp/DatagramSocket/DatagramSocket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline std::string ipIntToStr(unsigned int ip) { 6 | return std::to_string((ip >> 24) & 0xFF) + '.' + std::to_string((ip >> 16) & 0xFF) + '.' + 7 | std::to_string((ip >> 8) & 0xFF) + '.' + std::to_string(ip & 0xFF); 8 | } 9 | 10 | struct GenericAddr 11 | { 12 | short family = 0; 13 | unsigned int ip = 0; 14 | unsigned short port = 0; 15 | }; 16 | 17 | /********************************************************************************* 18 | UNIX version 19 | *********************************************************************************/ 20 | 21 | #if defined(__linux__) || defined (__APPLE__) 22 | 23 | #ifndef SOCKET 24 | #define SOCKET int 25 | #endif 26 | 27 | #ifndef SOCKADDR_IN 28 | #define SOCKADDR_IN sockaddr_in 29 | #endif 30 | 31 | #ifndef INVALID_SOCKET 32 | #define INVALID_SOCKET -1 33 | #endif 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include // get host by name 41 | #include 42 | #include 43 | #include 44 | 45 | class DatagramSocket 46 | { 47 | public: 48 | DatagramSocket(unsigned int interfaceIP, unsigned int port); 49 | ~DatagramSocket(); 50 | 51 | bool joinMulticastGroup(unsigned int ip, unsigned int interfaceIP); 52 | bool leaveMulticastGroup(unsigned int ip, unsigned int interfaceIP); 53 | 54 | bool sendBroadcast(unsigned int port,void * buf,unsigned int buflen); 55 | 56 | bool sendTo(const GenericAddr & addr,const void *buf,unsigned int buflen); 57 | bool recvFrom(GenericAddr & addr,void * buf,unsigned int & buflen); 58 | 59 | bool isInitialized(); 60 | 61 | private: 62 | void closeSocket(); 63 | 64 | int m_port=0; 65 | SOCKET m_socket = INVALID_SOCKET; 66 | }; 67 | 68 | #endif 69 | 70 | /********************************************************************************* 71 | WIN32 version 72 | *********************************************************************************/ 73 | 74 | #if defined(_WIN32) 75 | 76 | #include 77 | #include 78 | #include 79 | #pragma comment(lib, "Ws2_32.lib") 80 | 81 | class DatagramSocket 82 | { 83 | public: 84 | DatagramSocket(unsigned int interfaceIP, unsigned int port); 85 | 86 | ~DatagramSocket(); 87 | 88 | bool joinMulticastGroup(unsigned int ip, unsigned int interfaceIP); 89 | bool leaveMulticastGroup(unsigned int ip, unsigned int interfaceIP); 90 | 91 | bool sendBroadcast(unsigned int port, void * buf, unsigned int buflen); 92 | 93 | bool sendTo(const GenericAddr & addr, const void *buf, unsigned int buflen); 94 | bool recvFrom(GenericAddr & addr, void * buf, unsigned int & buflen); 95 | 96 | bool isInitialized(); 97 | 98 | private: 99 | void closeSocket(); 100 | 101 | int m_port = 0; 102 | SOCKET m_socket = INVALID_SOCKET; 103 | }; 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /Common/Cpp/PonkDefs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * PONK (Pathes Over NetworK) is a minimal protocol to transfer 2D colored pathes from a source to 5 | * a receiver. It has been developped to transfer laser path from a software to another over network using UDP. 6 | * 7 | * Requirements are: 8 | * - Transfer over network of frames composed of 2D geometry path with colors along the path 9 | * - Make it simple to implement on both sides 10 | * - Make it work on almost any network - no specific hardware requirement or os settings tricks 11 | * - Make it extensible while not increasing bandwidth requirements in most common cases 12 | * - Avoid using unecessary bandwidth: 13 | * - For laser, having more than 8 bits per component colors is mostly useless (laser projector 14 | * diodes have a very poor definition in low values anyway) 15 | * - Support multiple formats to adapt bandwidth to project requirements 16 | * - If the frame to transmit is too long to fit a single UDP packet, it should be cut in multiple 17 | * chunks. PONK_MAX_CHUNK_SIZE is set to 8192 by default. This value should make 18 | * it work on all popular OS. 19 | * - The sender should be able to attach "meta data" to each path transmitted, that the receiver 20 | * might handle for specific behaviors. When rasterizing a path for laser rendering 21 | * user might give some hints like "should we favor scan speed or render precision ?" 22 | * - Receiver must be able to detect network issue and ignore a frame if something when wrong (CRC) 23 | * 24 | * Transport 25 | * - Data is transmitted over UDP multicast or unicast. By default multicast is preferred because it allows 26 | * multiple softwares running on same computer to receive the packets. With unicast, first socket bound 27 | * on the port will eat the packets and software started after will not receive any data. 28 | * But since some network switches don't handle multicast well, and unicast performances are better, 29 | * there should be an option to switch to unicast. 30 | * So the expected behavior on both side is: 31 | * 32 | * - Sender should be in multicast by default, with an option to switch to unicast and enter the target IP 33 | * - Receiver should subscribe to multicast address (setsockopt / IP_ADD_MEMBERSHIP), it will then receive 34 | * packets coming through multicast or through unicast 35 | * 36 | * Implementation in Sender 37 | * - A software can send instanciate multiple senders. 38 | * - A sender is identified by a 32 bits number which can be a random generated value when instanciating 39 | * the sending component 40 | * - It also transmits a "sender name" string (UTF8) that can be used to display a readable 41 | * source name is the receiving application. 42 | * - The stream identifier should not change when reloading a project file so the receiver can 43 | * reconnect the stream. Changing the stream name should not affect existing connection (the 44 | * receiver must use the integer identifier, the string is used to display a meaningful text only) 45 | * 46 | * Implementation in Receiver 47 | * - The receiver should handle the fact that multiple senders can be sending packets. 48 | * - It should be as reactive as possible to reduce latency and improve synchronization. 49 | * - The receiver should accept any packet size (chunk size can be adjusted on sender side) 50 | * - The receiver should at least support data format "PONK_DATA_FORMAT_XY_F32_RGB_U8" 51 | * - If the receiver doesn't handle sender data type, it should notify it in the user interface in some way 52 | * - Protocol Version field in the packets shouldn't be ignored: if the specified protocol version is not 53 | * handled, the receiver should ignore the packet and notify the user that it is not compatible with sender 54 | * for this reason (protocol version will be increased only if breaking compatibility, not if we decide 55 | * too add a new data format) 56 | * 57 | * Meta Data Format 58 | * - Each path can have a list of meta data attached 59 | * - A meta data is identified by 8 characters (the key) 60 | * - Meta data value is a 32 bits floating point number, 61 | * if value should be an boolean (ie "optimize angles"), any value different of zero is considered true 62 | * if value should be an integer (ie "min ilda points"), value should be rounded to nearest int 63 | * if value should be a floating point, no problem 64 | * 65 | * Possible improvements / extensions: 66 | * Synchronization 67 | * - If the receiver notify sender that it has used the last received frame, the sender could 68 | * adjust to the receiver framerate... (since the sender doesn't know how long it will take 69 | * to the laser to travel the path, but receiver might know) 70 | * New Data Formats: 71 | * - XY_U16_SingleRGB: if you send a path of 2000 points with the same color, 72 | * we could reduce bandwidth a lot by removing color 73 | * - XYRGBU1U2U3: would be useful to control additional diodes (ie yellow, deep blue...) 74 | * - XYZRGB: providing the Z would let the user handle the 3D->2D projection with a 75 | * controllable camera in the receiver 76 | * Laser Rasterization Settings: 77 | * If you use a software for path generation (sender) and a receiver for the display, 78 | * you might want to provide, for each path, some parameters that the receiver could 79 | * use for rasterizing the 2D geometry pathes to an ILDA frame. For instance: 80 | * - Should we skip scanning long black sections ? (parameter "Skip Black Sections" ?) 81 | * - Should we prior scan speed or path render precision ? (parameter "Max Scan Speed" in radians/ms ?) 82 | * - Should we optimize angles for this path ? In some case you might want (ie text display) 83 | * but is some not (ie generative noisy curved shapes) 84 | * Those settings can actually be transmitted in meta data, but uniformizing it is an idea. 85 | * 86 | * Packet Format: 87 | * - Header: 88 | * - Header String - char[8]: "PONK-UDP" 89 | * - Protocol Version - char: 0 90 | * - Sender Indetifier - 32 bits int 91 | * - Sender Name - char[32] 92 | * - Frame Number - unsigned char: incremented on each frame 93 | * - Chunk Count - unsigned char 94 | * - Chunk Number - unsigned char 95 | * - CRC - unsigned int: sum of all data contained in this frame (of all chunks) 96 | * - Data: 97 | * - For each path: 98 | * - Data format - unsigned char (PONK_DATA_FORMAT_XY_F32_RGB_U8...) 99 | * - Meta Data count - unsigned char 100 | * - For each Meta Data: 101 | * - Key - char[8] 102 | * - Value - 32 bits float 103 | * - Point Count - unsigned short (16 bits) 104 | * - For each point 105 | * - Point data, depending on data format, ie X,Y as float 32, R,G,B as unsigned char 106 | * 107 | * List of Meta Data support by: 108 | * 109 | * - MadMapper / MadLaser: most of those parameters can be adjusted at surface level. Adding meta data will override 110 | * settings set at surface level for the path it is attached to. Those parameters are documented in MadLaser 111 | * documentation 112 | * - PATHNUMB: Integer number for identifying the shape (ie if the first shape for previous frame disappeared, 113 | * MadMapper can anyway know this shape corresponds to a shape in previous frame using this identifier, it 114 | * might be used for instance in dispatching algorithms) 115 | * - MAXSPEED: Floating point number defining the maximum laser scan speed for this shape. Normal value is 1.0 116 | * and is a compromise between scan speed and scan accuracy at a reasonable projection angle. A value of 2 will 117 | * tell MadMapper it can scan twice faster when rendering this shape. 118 | * - FIXSPEED: Floating point number defining the speed at which we will travel this path with the laser beam. 119 | * Normal value is 1.0 and is a compromise between scan speed and scan accuracy at a reasonable projection 120 | * angle. A value of 2 will tell MadMapper to scan twice faster when rendering this shape. 121 | * - SKIPBLCK: Boolean value to tell MadMapper it should or not skip scanning "long" black sections of the path 122 | * - PRESRVOR: Boolean value to tell MadMapper it should not looks for the best rendering order for the pathes 123 | * transmitted by this media, but those pathes should be rendered in the order they are received. 124 | * - ANGLEOPT: Boolean value to tell MadMapper we want angle optimization or not 125 | * - ANGLETHR: Floating point value - minimum angle in degrees for activating angle optimization (between 22.5 & 90) 126 | * - ANGLEMXD: Floating point value - maximum time to spend on angle points 127 | * - FRSTPNTR: Integer - First Point Repeat 128 | * - LASTPNTR: Integer - Last Point Repeat 129 | * - POLYFADI: Floating point value - how much we should decrease luminosity when starting scanning a path (0.0-8.0) 130 | * - POLYFADO: Floating point value - how much we should decrease luminosity when ending scanning a path (0.0-0.1) 131 | * - MINIPNTS: Integer - minimum number of ILDA points that should be generated to render this path (allows to make 132 | * a small path very lightened and a long path less, normally luminosity (= scanning time) is dispatched depending 133 | * on the path length (except for single beam) 134 | * - SOFTCLOS: Integer - number of ILDA points we should use for "edge blend" start & end of a closed shape 135 | * - SNGLPTIN: Integer - number of ILDA points we should use for this shape if it's a single beam (a single position or 136 | * at least all points are at the same position) - 0 for automatic 137 | * 138 | * - ... Other software to be added 139 | */ 140 | 141 | // Header String 142 | #define PONK_HEADER_STRING "PONK-UDP" 143 | // Protocol Version 144 | #define PONK_PROTOCOL_VERSION 0 145 | // Data Formats 146 | #define PONK_DATA_FORMAT_XYRGB_U16 0 147 | #define PONK_DATA_FORMAT_XY_F32_RGB_U8 1 148 | // Maximum chunk size 149 | #define PONK_MAX_CHUNK_SIZE 1472 150 | // Ponk Multicast address 151 | #define PONK_MULTICAST_IP ((239<<24) + (255<<16) + (10<<8) + (24<<0)) 152 | // Ponk port = 5583 153 | #define PONK_PORT 5583 154 | 155 | #ifdef _MSC_VER 156 | // ms VC .NET 157 | #pragma pack( push, before_definition ) 158 | #pragma pack(1) 159 | #define ATTRIBUTE_PACKED ; 160 | #else 161 | // gcc 162 | #ifndef ATTRIBUTE_PACKED 163 | #define ATTRIBUTE_PACKED __attribute__( ( packed ) ) 164 | #endif 165 | #endif 166 | 167 | struct GeomUdpHeader { 168 | char headerString[8]; // = "PONK-UDP" 169 | unsigned char protocolVersion; // 0 at the moment 170 | unsigned int senderIdentifier; // 4 bytes - used to identify the source, so when changing name in sender, the receiver can just rename existing stream 171 | char senderName[32]; // 32 bytes UTF8 null terminated string 172 | unsigned char frameNumber; // Increase by one on each frame 173 | unsigned char chunkCount; // Number of chunks in this frame 174 | unsigned char chunkNumber; // Number of this chunk 175 | unsigned int dataCrc; // CRC of all data in the frame (data from chunks, to detect network transmission issues) 176 | } ATTRIBUTE_PACKED; 177 | 178 | struct GeomUdpMetaData { 179 | char name[8]; // EightCC (64 bits / 8 bytes), ie "POLYNUMB" 180 | char value[4]; // 4 bytes for value, must be casted to int / bool / float 181 | } ATTRIBUTE_PACKED; 182 | 183 | struct GeomUdpPathData { 184 | unsigned short pointCount; // Point Count 185 | // Data: XYRGBXYRGB.... 186 | } ATTRIBUTE_PACKED; 187 | 188 | struct GeomUdpPath { 189 | unsigned char metaDataCount; // Number of meta data 190 | // Data: N x GeomUdpMetaData, then N x GeomUdpPathData 191 | } ATTRIBUTE_PACKED; 192 | 193 | struct GeomUdpPacketData { 194 | unsigned char dataFormat; // ie: PONK_DATA_FORMAT_XY_F32_RGB_U8 195 | // Data: N x GeomUdpPath 196 | } ATTRIBUTE_PACKED; 197 | 198 | #if defined(_MSC_VER) 199 | #pragma pack( pop, before_definition ) 200 | #endif 201 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ponk 2 | Open Protocol to transmit **P**athes **O**ver **N**etwor**K** 3 | 4 | PONK (**P**athes **O**ver **N**etwor**K**) is a minimal protocol to transfer 2D colored pathes from a source to a receiver. It has been developped to transfer laser path from a software to another over network using UDP. 5 | 6 | [![Demo](/Assets/youtube_demo_link_image.jpg)](https://www.youtube.com/watch?v=VmzsDqeO2RQ) 7 | 8 | ## Requirements are: 9 | - Transfer over network of frames composed of 2D geometry path with colors along the path 10 | - Make it simple to implement on both sides 11 | - Make it work on almost any network - no specific hardware or OS requirement 12 | - Make it extensible while not increasing bandwidth in most common cases 13 | - Avoid using unecessary bandwidth: 14 | - For laser, having more than 8 bits per component colors is mostly useless (laser projector diodes have a very poor definition in low values anyway) 15 | - Support multiple formats to adapt bandwidth to project requirements 16 | - If the frame to transmit is too long to fit a single UDP packet, it should be cut in multiple chunks. GEOM_UDP_MAX_DATA_BYTES_PER_PACKET is set to 8192 by default. This value should make it work on all popular OS. 17 | - The sender should be able to attach "meta data" to each path transmitted, that the receiver might handle for specific behaviors. When rasterizing a path for laser rendering user might give some hints like "should we favor scan speed or render precision ?" 18 | - Receiver must be able to detect network issue and ignore a frame if something when wrong (CRC) 19 | 20 | ## Transport 21 | - Data is transmitted over UDP multicast or unicast. By default multicast is preferred because it allows multiple softwares running on same computer to receive the packets. With unicast, first socket bound on the port will eat the packets and software started after will not receive any data. 22 | But since some network switches don't handle multicast well, and unicast performances are better, there should be an option to switch to unicast. 23 | So the expected behavior on both side is: 24 | - Sender should be in multicast by default, with an option to switch to unicast and enter the target IP 25 | - Receiver should subscribe to multicast address (setsockopt / IP_ADD_MEMBERSHIP), it will then receive packets coming through multicast or through unicast 26 | 27 | ## Implementation in Sender 28 | - A software can instanciate multiple senders (different streams) 29 | - A sender is identified by a 32 bits number which can be a random generated value when instanciating the sending component 30 | - It also transmits a "sender name" string (UTF8) that can be used to display a readable source name is the receiving application. 31 | - The stream identifier should not change when reloading a project file so the receiver canreconnect the stream. Changing the stream name should not affect existing connection (the receiver must use the integer identifier, the string is used to display a meaningful text only) 32 | 33 | ## Implementation in Receiver 34 | - The receiver should handle the fact that multiple senders can be sending packets. 35 | - It should be as reactive as possible to reduce latency and improve synchronization. 36 | - The receiver should accept any packet size (chunk size can be adjusted on sender side) 37 | - The receiver should at least support data format "GEOM_UDP_DATA_FORMAT_XY_F32_RGB_U8" 38 | - If the receiver doesn't handle sender data type, it should notify it in the user interface in some way 39 | - Protocol Version field in the packets shouldn't be ignored: if the specified protocol version is not handled, the receiver should ignore the packet and notify the user that it is not compatible with sender for this reason (protocol version will be increased only if breaking compatibility, not if we decide too add a new data format) 40 | 41 | ## Meta Data Format 42 | - Each path can have a list of meta data attached 43 | - A meta data is identified by 8 characters (the key) 44 | - Meta data value is a 32 bits floating point number, 45 | if value should be an boolean (ie "optimize angles"), any value different of zero is considered true 46 | if value should be an integer (ie "min ilda points"), value should be rounded to nearest int 47 | if value should be a floating point, no problem 48 | 49 | ## Possible improvements / extensions: 50 | - Synchronization 51 | - If the receiver notify sender that it has used the last received frame, the sender could adjust to the receiver framerate... (since the sender doesn't know how long it will take to the laser to travel the path, but receiver might know) 52 | - New Data Formats: 53 | - XY_U16_SingleRGB: if you send a path of 2000 points with the same color, we could reduce bandwidth a lot by removing color for each point 54 | - XYRGBU1U2U3: would be useful to control additional diodes (ie yellow, deep blue...) 55 | - XYZRGB: providing the Z would let the user handle the 3D->2D projection with a controllable camera in the receiver 56 | 57 | ## Packet Format: 58 | - Header: 59 | - Header String - char[8]: "PONK-UDP" 60 | - Protocol Version - char: 0 61 | - Sender Indetifier - 32 bits int 62 | - Sender Name - char[32] 63 | - Frame Number - unsigned char: incremented on each frame 64 | - Chunk Count - unsigned char 65 | - Chunk Number - unsigned char 66 | - CRC - unsigned int: sum of all data contained in this frame (of all chunks) 67 | - Data: 68 | - For each path: 69 | - Data format - unsigned char (GEOM_UDP_DATA_FORMAT_XY_F32_RGB_U8...) 70 | - Meta Data count - unsigned char 71 | - For each Meta Data: 72 | - Key - char[8] 73 | - Value - 32 bits float 74 | - Point Count - unsigned short (16 bits) 75 | - For each point 76 | - Point data, depending on data format, ie X,Y as float 32, R,G,B as unsigned char 77 | 78 | ## List of Meta Data support by: 79 | 80 | - MadMapper / MadLaser: most of those parameters can be adjusted at surface level. Adding meta data will override settings set at surface level for the path it is attached to. Those parameters are documented in MadLaser documentation 81 | - PATHNUMB: Integer number for identifying the shape (ie if the first shape for previous frame disappeared, MadMapper can anyway know this shape corresponds to a shape in previous frame using this identifier, it might be used for instance in dispatching algorithms) 82 | - MAXSPEED: Floating point number defining the maximum laser scan speed for this shape. Normal value is 1.0 and is a compromise between scan speed and scan accuracy at a reasonable projection angle. A value of 2 will tell MadMapper it can scan twice faster when rendering this shape. 83 | - FIXSPEED: Floating point number defining the speed at which we will travel this path with the laser beam. Normal value is 1.0 and is a compromise between scan speed and scan accuracy at a reasonable projection angle. A value of 2 will tell MadMapper to scan twice faster when rendering this shape. 84 | - SKIPBLCK: Boolean value to tell MadMapper it should or not skip scanning "long" black sections of the path 85 | - PRESRVOR: Boolean value to tell MadMapper it should not looks for the best rendering order for the pathes transmitted by this media, but those pathes should be rendered in the order they are received. 86 | - ANGLEOPT: Boolean value to tell MadMapper we want angle optimization or not 87 | - ANGLETHR: Floating point value - minimum angle in degrees for activating angle optimization (between 22.5 & 90) 88 | - ANGLEMXD: Floating point value - maximum time to spend on angle points 89 | - FRSTPNTR: Integer - First Point Repeat 90 | - LASTPNTR: Integer - Last Point Repeat 91 | - POLYFADI: Floating point value - how much we should decrease luminosity when starting scanning a path (0.0-8.0) 92 | - POLYFADO: Floating point value - how much we should decrease luminosity when ending scanning a path (0.0-0.1) 93 | - MINIPNTS: Integer - minimum number of ILDA points that should be generated to render this path (allows to make a small path very lightened and a long path less, normally luminosity (= scanning time) is dispatched depending on the path length (except for single beam) 94 | - SOFTCLOS: Integer - number of ILDA points we should use for "edge blend" start & end of a closed shape 95 | - SNGLPTIN: Integer - number of ILDA points we should use for this shape if it's a single beam (a single position or at least all points are at the same position) - 0 for automatic 96 | 97 | - ... Other software to be added 98 | -------------------------------------------------------------------------------- /Samples/Cpp/PonkUDPReceiver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(PonkReceiver LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | set(SOURCES 9 | ../../../Common/Cpp/DatagramSocket/DatagramSocket.cpp 10 | main.cpp 11 | ) 12 | set(HEADERS 13 | ../../../Common/Cpp/PonkDefs.h 14 | ../../../Common/Cpp/DatagramSocket/DatagramSocket.h 15 | ) 16 | 17 | add_executable(PonkReceiver ${SOURCES} ${HEADERS}) 18 | target_include_directories(PonkReceiver PRIVATE "../../../Common/Cpp/") 19 | 20 | -------------------------------------------------------------------------------- /Samples/Cpp/PonkUDPReceiver/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "DatagramSocket/DatagramSocket.h" 7 | #include "PonkDefs.h" 8 | 9 | int main() 10 | { 11 | std::cout << "Starting" << std::endl; 12 | 13 | DatagramSocket socket(INADDR_ANY,PONK_PORT); 14 | socket.joinMulticastGroup(PONK_MULTICAST_IP,INADDR_ANY); 15 | 16 | // TODO: let user choose a network interface or join for all active networkinterfaces 17 | // Zero means first active network adapter if I'm not wrong 18 | const int networkInterfaceIp = 0; //((192<<24) + (168<<16) + (1<<8) + 3); 19 | 20 | int currentFrameNumber = -1; 21 | 22 | // We'll keep all chunks data in a vector until we get all chunks 23 | // Protocol supports up to 255 chunks 24 | // We'll accept received data chunks in the wrong order though I doubt it should happen 25 | int currentFrameChunkCount = -1; 26 | int currentFrameDataCrc = -1; 27 | std::vector chunksDataHasBeenReceived; 28 | std::vector> chunksData; 29 | chunksDataHasBeenReceived.resize(255); 30 | chunksData.resize(255); 31 | 32 | while (true) { 33 | unsigned char buffer[65536]; 34 | unsigned int bufferSize = static_cast(sizeof(buffer)); 35 | 36 | GenericAddr sourceAddr; 37 | if (!socket.recvFrom(sourceAddr, buffer, bufferSize)) { 38 | assert(false); // Should never happen 39 | return -1; 40 | } 41 | 42 | if (bufferSize == 0) { 43 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 44 | continue; 45 | } 46 | 47 | //std::cout << "Received packet of " << std::to_string(bufferSize) << " bytes" << std::endl; 48 | 49 | // Parse buffer 50 | if (bufferSize < sizeof(GeomUdpHeader)) { 51 | std::cout << "Error in frame, frame size " << std::to_string(bufferSize) << " is lower than header size" << std::endl; 52 | continue; 53 | } 54 | 55 | const GeomUdpHeader* header = reinterpret_cast(buffer); 56 | 57 | // Check protocol header string 58 | if (strncmp(header->headerString,PONK_HEADER_STRING,8) != 0) { 59 | std::cout << "Error in frame, invalid header" << std::endl; 60 | continue; 61 | } 62 | 63 | // Check protocol version 64 | if (header->protocolVersion > 0) { 65 | std::cout << "Source protocol version is " << std::to_string(header->protocolVersion) 66 | << " but this sample code only support protocol version 0" << std::endl; 67 | continue; 68 | } 69 | 70 | // Read Sender Name string (32 bytes null terminated UTF8 string) 71 | //std::cout << "Sender name " << senderName << std::endl; 72 | 73 | // Read Frame Number 74 | // If we actually received part of a frame, we shouldn't received a different frame number 75 | if (currentFrameNumber != -1 && header->frameNumber != currentFrameNumber) { 76 | std::cout << "Error: frame number has changed to " << std::to_string(header->frameNumber) 77 | << " but we have not received all chunks from frame number " << std::to_string(currentFrameNumber) 78 | << ". Resetting chunks data" << std::endl; 79 | assert(false); 80 | // Reset state. Note that ideally we shouldn't skip all chunks from a frame if we receive the first chunk 81 | // of next frame before last chunk of previous frame, but keep the sample code simple (we should keep received chunks 82 | // for the 256 possible frame number) - a frame will generally fit a single chunk / UDP packet and this case should rarely happen 83 | for (auto& chunkData: chunksData) { 84 | chunkData.clear(); 85 | } 86 | for (int i=0; i<255; i++) { 87 | chunksDataHasBeenReceived[i] = false; 88 | } 89 | currentFrameNumber = -1; 90 | } 91 | 92 | // If we're actually reading a frame, ensure chunkCount doesn't change accross same frame headers (buggy sender) 93 | if (currentFrameNumber != -1 && currentFrameChunkCount != header->chunkCount) { 94 | std::cout << "Error: received a new chunk for a frame with a different chunk count" << std::endl; 95 | assert(false); 96 | } 97 | 98 | // If we're actually reading a frame, ensure dataCrc doesn't change (buggy sender) 99 | if (currentFrameNumber != -1 && currentFrameDataCrc != header->dataCrc) { 100 | std::cout << "Error: received a new chunk for a frame with a different data CRC" << std::endl; 101 | assert(false); 102 | } 103 | 104 | currentFrameNumber = header->frameNumber; 105 | currentFrameChunkCount = header->chunkCount; 106 | currentFrameDataCrc = header->dataCrc; 107 | 108 | // Check Chunk Count 109 | if (header->chunkCount == 0) { 110 | std::cout << "Error in frame, chunk count is zero" << std::endl; 111 | assert(false); 112 | } 113 | 114 | // Check Chunk number 115 | if (header->chunkNumber >= header->chunkCount) { 116 | // Sender is buggy 117 | std::cout << "Error in frame, chunk number (" << std::to_string(header->chunkNumber) << ") is over chunk count (" << std::to_string(header->chunkCount) << ")" << std::endl; 118 | assert(false); 119 | continue; 120 | } 121 | if (!chunksData[header->chunkNumber].empty()) { 122 | // Buggy sender or dying network 123 | std::cout << "Error in frame, we already received data for chunk " << std::to_string(header->chunkNumber) << std::endl; 124 | assert(false); 125 | } 126 | 127 | // Now read data 128 | const auto dataLength = bufferSize - sizeof(GeomUdpHeader); 129 | unsigned int bufferOffset = sizeof(GeomUdpHeader); 130 | for (unsigned int i=0; ichunkNumber].push_back(buffer[bufferOffset++]); 132 | } 133 | chunksDataHasBeenReceived[header->chunkNumber] = true; 134 | 135 | //std::cout << "Received chunk " << std::to_string(chunkNumber) << "/" << std::to_string(chunkCount) << " for frame " << std::to_string(frameNumber) << std::endl; 136 | 137 | // If we received all frame chunks, log the frame 138 | bool receivedAllChunksYet = true; 139 | for (int i=0; ichunkCount; i++) { 140 | if (!chunksDataHasBeenReceived[i]) { 141 | receivedAllChunksYet = false; 142 | break; 143 | } 144 | } 145 | 146 | if (receivedAllChunksYet) { 147 | // Put all frame data together in a single buffer 148 | std::vector allData; 149 | allData.reserve(255*PONK_MAX_CHUNK_SIZE); 150 | for (int i=0; ichunkCount; i++) { 151 | allData.insert(allData.end(),chunksData[i].begin(),chunksData[i].end()); 152 | } 153 | 154 | // Seems we're all good, we know have complete frame data 155 | std::cout << "Received frame " << std::to_string(currentFrameNumber) << std::endl; 156 | 157 | // Reset state 158 | for (int i=0; ichunkCount; i++) { 159 | chunksData[i].clear(); 160 | chunksDataHasBeenReceived[i] = false; 161 | } 162 | currentFrameNumber = -1; 163 | 164 | // Parse Frame Data 165 | const auto dataSize = allData.size(); 166 | if (dataSize < 1) { 167 | std::cout << "Error: frame data is empty" << std::endl; 168 | continue; 169 | } 170 | 171 | // Check Data CRC 172 | int computedCrc = 0; 173 | for (auto v: allData) { 174 | computedCrc += v; 175 | } 176 | if (computedCrc != header->dataCrc) { 177 | std::cout << "Error: invalid data CRC, ignoring frame" << std::endl; 178 | assert(false); 179 | continue; 180 | } 181 | 182 | unsigned int dataOffset = 0; 183 | 184 | // Read Pathes 185 | struct Path { 186 | struct Point { 187 | float x,y,r,g,b; 188 | }; 189 | std::vector points; 190 | }; 191 | 192 | // Loop over pathes until there's no more data to read 193 | std::vector pathes; 194 | while (dataOffset < dataSize) { 195 | // Read data format 196 | const auto dataFormat = allData[dataOffset]; 197 | dataOffset++; 198 | 199 | // Read Meta Data Count 200 | if (dataSize < dataOffset+1) { 201 | std::cout << "Error: not enough data to read path meta data count" << std::endl; 202 | break; 203 | } 204 | const unsigned short metaDataCount = allData[dataOffset]; 205 | dataOffset++; 206 | 207 | // Read Meta Data 208 | if (dataSize < dataOffset+12 * metaDataCount) { 209 | std::cout << "Error: not enough data to read path meta data" << std::endl; 210 | break; 211 | } 212 | for (int idx=0; idx(&allData[dataOffset]); 219 | dataOffset += 4; 220 | std::cout << "Path Meta " << metaName << " = " << floatValue << std::endl; 221 | } 222 | 223 | // Read Point Count 224 | if (dataSize < dataOffset+2) { 225 | std::cout << "Error: not enough data to read path point count" << std::endl; 226 | break; 227 | } 228 | const unsigned short pointCount = allData[dataOffset] + (allData[dataOffset+1]<<8); 229 | std::cout << " -> Path " << std::to_string(pathes.size()) << " / Point Count = " << std::to_string(pointCount) << std::endl; 230 | dataOffset += 2; 231 | 232 | unsigned char bytesPerPoint; 233 | if (dataFormat == PONK_DATA_FORMAT_XYRGB_U16) { 234 | bytesPerPoint = 5 * sizeof(unsigned short); 235 | } else if (dataFormat == PONK_DATA_FORMAT_XY_F32_RGB_U8) { 236 | bytesPerPoint = 2 * sizeof(float) + 3 * sizeof(unsigned char); 237 | } else { 238 | std::cout << "Error: unhandled data format: " << dataFormat << std::endl; 239 | break; 240 | } 241 | 242 | if (dataSize < dataOffset + pointCount * bytesPerPoint) { 243 | std::cout << "Error: not enough data to read path points" << std::endl; 244 | break; 245 | } 246 | 247 | Path path; 248 | for (int i=0; i(&allData[dataOffset]); 270 | dataOffset += sizeof(float); 271 | point.y = *reinterpret_cast(&allData[dataOffset]); 272 | dataOffset += sizeof(float); 273 | point.r = allData[dataOffset]; 274 | dataOffset++; 275 | point.g = allData[dataOffset]; 276 | dataOffset++; 277 | point.b = allData[dataOffset]; 278 | dataOffset++; 279 | } 280 | 281 | path.points.push_back(point); 282 | } 283 | 284 | pathes.push_back(path); // Note that this is very inefficient 285 | } 286 | 287 | assert(dataOffset == dataSize); 288 | } 289 | } 290 | 291 | return 0; 292 | } 293 | -------------------------------------------------------------------------------- /Samples/Cpp/PonkUDPSender/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(PonkSender LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | set(SOURCES 9 | ../../../Common/Cpp/DatagramSocket/DatagramSocket.cpp 10 | main.cpp 11 | ) 12 | set(HEADERS 13 | ../../../Common/Cpp/PonkDefs.h 14 | ../../../Common/Cpp/DatagramSocket/DatagramSocket.h 15 | ) 16 | 17 | add_executable(PonkSender ${SOURCES} ${HEADERS}) 18 | target_include_directories(PonkSender PRIVATE "../../../Common/Cpp/") 19 | 20 | 21 | -------------------------------------------------------------------------------- /Samples/Cpp/PonkUDPSender/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "DatagramSocket/DatagramSocket.h" 7 | #include "PonkDefs.h" 8 | #ifndef M_PI // M_PI not defined on Windows 9 | #define M_PI 3.14159265358979323846 10 | #endif 11 | 12 | void push8bits(std::vector& fullData, unsigned char value) { 13 | fullData.push_back(value); 14 | } 15 | 16 | void push16bits(std::vector& fullData, unsigned short value) { 17 | fullData.push_back(static_cast((value>>0) & 0xFF)); 18 | fullData.push_back(static_cast((value>>8) & 0xFF)); 19 | } 20 | 21 | void push32bits(std::vector& fullData, int value) { 22 | fullData.push_back(static_cast((value>>0) & 0xFF)); 23 | fullData.push_back(static_cast((value>>8) & 0xFF)); 24 | fullData.push_back(static_cast((value>>16) & 0xFF)); 25 | fullData.push_back(static_cast((value>>24) & 0xFF)); 26 | } 27 | 28 | void push32bits(std::vector& fullData, float value) { 29 | push32bits(fullData,*reinterpret_cast(&value)); 30 | } 31 | 32 | void pushMetaData(std::vector& fullData, const char (&eightCC)[9],float value) { 33 | for (int i=0; i<8; i++) { 34 | fullData.push_back(eightCC[i]); 35 | } 36 | push32bits(fullData,*(int*)&value); 37 | } 38 | 39 | int main() 40 | { 41 | std::cout << "Starting" << std::endl; 42 | 43 | DatagramSocket socket(INADDR_ANY,0); 44 | 45 | // send a moving circle and a triangle in loop 46 | double animTime = 0; 47 | auto nextFrametime = std::chrono::system_clock::now(); 48 | unsigned char frameNumber = 0; 49 | while (true) { 50 | std::vector fullData; 51 | fullData.reserve(65536); 52 | 53 | #ifdef USE_PONK_DATA_FORMAT_XYRGB_U16 54 | // Generate circle data with 1024 points 55 | fullData.push_back(PONK_DATA_FORMAT_XYRGB_U16); // Write Format Data 56 | 57 | // Meta Data 58 | fullData.push_back(2); // Write meta data count 59 | pushMetaData(fullData,"PATHNUMB",1.f); 60 | pushMetaData(fullData,"MAXSPEED",0.1f); 61 | 62 | // Write point count - LSB first 63 | #define CIRCLE_POINT_COUNT 1024 64 | #define CIRCLE_MOVE_SIZE 0.2 65 | #define CIRCLE_SIZE 0.5 66 | push16bits(fullData,CIRCLE_POINT_COUNT); 67 | // Write 1024 points 68 | const auto circleCenterX = CIRCLE_MOVE_SIZE * cos(animTime*3); 69 | const auto circleCenterY = CIRCLE_MOVE_SIZE * sin(animTime*3); 70 | for (int i=0; i<1024; i++) { 71 | // Be sure to close circle 72 | const auto normalizedPosInCircle = double(i)/(CIRCLE_POINT_COUNT-1); 73 | const auto x = circleCenterX + CIRCLE_SIZE * cos(normalizedPosInCircle*2*M_PI); 74 | const auto y = circleCenterY + CIRCLE_SIZE * sin(normalizedPosInCircle*2*M_PI); 75 | assert(x>=-1 && x<=1 && y>=-1 && y<=1); 76 | const auto x16Bits = static_cast(((x+1)/2) * 65535); 77 | const auto y16Bits = static_cast(((y+1)/2) * 65535); 78 | // Push X - LSB first 79 | push16bits(fullData,x16Bits); 80 | // Push Y - LSB first 81 | push16bits(fullData,y16Bits); 82 | // Push R - LSB first 83 | push16bits(fullData,0xFFFF); 84 | // Push G - LSB first 85 | push16bits(fullData,0xFFFF); 86 | // Push B - LSB first 87 | push16bits(fullData,0xFFFF); 88 | } 89 | 90 | // Generate a triangle with 4 points (to close it) 91 | fullData.push_back(PONK_DATA_FORMAT_XYRGB_U16); // Write Format Data 92 | 93 | // Meta Data 94 | fullData.push_back(1); // Write meta data count 95 | pushMetaData(fullData,"PATHNUMB",2.f); 96 | 97 | // Write point count - LSB first 98 | #define TRIANGLE_POINT_COUNT 4 99 | #define TRIANGLE_SIZE 0.5 100 | fullData.push_back((TRIANGLE_POINT_COUNT>>0) & 0xFF); 101 | fullData.push_back((TRIANGLE_POINT_COUNT>>8) & 0xFF); 102 | for (int i=0; i=-1 && x<=1 && y>=-1 && y<=1); 107 | const auto x16Bits = static_cast(((x+1)/2) * 65535); 108 | const auto y16Bits = static_cast(((y+1)/2) * 65535); 109 | // Push X - LSB first 110 | push16bits(fullData,x16Bits); 111 | // Push Y - LSB first 112 | push16bits(fullData,y16Bits); 113 | // Push R - LSB first 114 | push16bits(fullData,0xFFFF); 115 | // Push G - LSB first 116 | push16bits(fullData,0); 117 | // Push B - LSB first 118 | push16bits(fullData,0); 119 | } 120 | #else 121 | // Generate circle data with 1024 points 122 | fullData.push_back(PONK_DATA_FORMAT_XY_F32_RGB_U8); // Write Format Data 123 | 124 | // Meta Data 125 | fullData.push_back(2); // Write meta data count 126 | pushMetaData(fullData,"PATHNUMB",1.f); 127 | pushMetaData(fullData,"MAXSPEED",1.0f); 128 | 129 | // Write point count - LSB first 130 | #define CIRCLE_POINT_COUNT 4096 131 | #define CIRCLE_MOVE_SIZE 0.2f 132 | #define CIRCLE_SIZE 0.5f 133 | push16bits(fullData,CIRCLE_POINT_COUNT); 134 | // Write CIRCLE_POINT_COUNT points 135 | const auto circleCenterX = CIRCLE_MOVE_SIZE * cos(animTime*3); 136 | const auto circleCenterY = CIRCLE_MOVE_SIZE * sin(animTime*3); 137 | for (int i=0; i(circleCenterX + CIRCLE_SIZE * cos(normalizedPosInCircle*2*M_PI)); 141 | const auto y = static_cast(circleCenterY + CIRCLE_SIZE * sin(normalizedPosInCircle*2*M_PI)); 142 | assert(x>=-1 && x<=1 && y>=-1 && y<=1); 143 | // Push X - LSB first 144 | push32bits(fullData,x); 145 | // Push Y - LSB first 146 | push32bits(fullData,y); 147 | // Push R - LSB first 148 | push8bits(fullData,0xFF); 149 | // Push G - LSB first 150 | push8bits(fullData,0xFF); 151 | // Push B - LSB first 152 | push8bits(fullData,0xFF); 153 | } 154 | 155 | // Generate a triangle with 4 points (to close it) 156 | // Generate circle data with 1024 points 157 | fullData.push_back(PONK_DATA_FORMAT_XY_F32_RGB_U8); // Write Format Data 158 | 159 | // Meta Data 160 | fullData.push_back(1); // Write meta data count 161 | pushMetaData(fullData,"PATHNUMB",2.f); 162 | 163 | // Write point count - LSB first 164 | #define TRIANGLE_POINT_COUNT 4 165 | #define TRIANGLE_SIZE 0.5f 166 | push16bits(fullData,TRIANGLE_POINT_COUNT); 167 | for (int i=0; i(TRIANGLE_SIZE * cos(normalizedPosInTriangle*2*M_PI)); 170 | const auto y = static_cast(TRIANGLE_SIZE * sin(normalizedPosInTriangle*2*M_PI)); 171 | assert(x>=-1 && x<=1 && y>=-1 && y<=1); 172 | // Push X - LSB first 173 | push32bits(fullData,x); 174 | // Push Y - LSB first 175 | push32bits(fullData,y); 176 | // Push R - LSB first 177 | push8bits(fullData,0xFF); 178 | // Push G - LSB first 179 | push8bits(fullData,0); 180 | // Push B - LSB first 181 | push8bits(fullData,0); 182 | } 183 | #endif 184 | 185 | // Compute necessary chunk count 186 | size_t chunksCount64 = 1 + fullData.size() / (PONK_MAX_CHUNK_SIZE-sizeof(GeomUdpHeader)); 187 | if (chunksCount64 > 255) { 188 | throw std::runtime_error("Protocol doesn't accept sending " 189 | "a packet that would be splitted " 190 | "in more than 255 chunks"); 191 | } 192 | 193 | // Compute data CRC 194 | unsigned int dataCrc = 0; 195 | for (auto v: fullData) { 196 | dataCrc += v; 197 | } 198 | 199 | // Send all chunks to the desired IP address 200 | size_t written = 0; 201 | unsigned char chunkNumber = 0; 202 | unsigned char chunksCount = static_cast(chunksCount64); 203 | while (written < fullData.size()) { 204 | // Write packet header - 8 bytes 205 | GeomUdpHeader header; 206 | strncpy(header.headerString,PONK_HEADER_STRING,sizeof(header.headerString)); 207 | header.protocolVersion = 0; 208 | header.senderIdentifier = 123123; // Unique ID (so when changing name in sender, the receiver can just rename existing stream) 209 | strncpy(header.senderName,"Sample Sender",sizeof(header.senderName)); 210 | header.frameNumber = frameNumber; 211 | header.chunkCount = chunksCount; 212 | header.chunkNumber = chunkNumber; 213 | header.dataCrc = dataCrc; 214 | 215 | // Prepare buffer 216 | std::vector packet; 217 | size_t dataBytesForThisChunk = std::min(fullData.size()-written,PONK_MAX_CHUNK_SIZE-sizeof(GeomUdpHeader)); 218 | packet.resize(sizeof(GeomUdpHeader) + dataBytesForThisChunk); 219 | // Write header 220 | memcpy(&packet[0],&header,sizeof(GeomUdpHeader)); 221 | // Write data 222 | memcpy(&packet[sizeof(GeomUdpHeader)],&fullData[written],dataBytesForThisChunk); 223 | written += dataBytesForThisChunk; 224 | 225 | // Now send chunk packet 226 | GenericAddr destAddr; 227 | destAddr.family = AF_INET; 228 | // Unicast on localhost 127.0.0.1 229 | destAddr.ip = PONK_MULTICAST_IP; 230 | destAddr.port = PONK_PORT; 231 | socket.sendTo(destAddr, &packet.front(), static_cast(packet.size())); 232 | 233 | chunkNumber++; 234 | } 235 | 236 | std::cout << "Sent frame " << std::to_string(frameNumber) << std::endl; 237 | 238 | animTime += 1/60.; 239 | frameNumber++; 240 | 241 | nextFrametime += std::chrono::microseconds(1000000/60); 242 | std::this_thread::sleep_until(nextFrametime); 243 | } 244 | 245 | return 0; 246 | } 247 | -------------------------------------------------------------------------------- /Touch Plugin/GL_Extensions.h: -------------------------------------------------------------------------------- 1 | // Stub file for simpler CHOP usage than an OpenGLTOP 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /Touch Plugin/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Touch Plugin/PonkOutput: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.30503.244 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PonkOutput", "PonkOutput.vcxproj", "{3F5BEECD-FA36-459F-91B8-BB481A67EF44}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|x64 = Debug|x64 10 | Release|x64 = Release|x64 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Debug|x64.ActiveCfg = Debug|x64 14 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Debug|x64.Build.0 = Debug|x64 15 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Release|x64.ActiveCfg = Release|x64 16 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Release|x64.Build.0 = Release|x64 17 | EndGlobalSection 18 | GlobalSection(SolutionProperties) = preSolution 19 | HideSolutionNode = FALSE 20 | EndGlobalSection 21 | GlobalSection(ExtensibilityGlobals) = postSolution 22 | SolutionGuid = {F85A231A-AB15-4BE7-A0DB-37514ADDAC86} 23 | EndGlobalSection 24 | EndGlobal 25 | -------------------------------------------------------------------------------- /Touch Plugin/PonkOutput.cpp: -------------------------------------------------------------------------------- 1 | #include "PonkOutput.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef M_PI // M_PI not defined on Windows 10 | #define M_PI 3.14159265358979323846 11 | #endif 12 | 13 | // These functions are basic C function, which the DLL loader can find 14 | // much easier than finding a C++ Class. 15 | // The DLLEXPORT prefix is needed so the compile exports these functions from the .dll 16 | // you are creating 17 | extern "C" 18 | { 19 | 20 | DLLEXPORT 21 | void 22 | FillSOPPluginInfo(SOP_PluginInfo *info) 23 | { 24 | // Always return SOP_CPLUSPLUS_API_VERSION in this function. 25 | info->apiVersion = SOPCPlusPlusAPIVersion; 26 | 27 | // The opType is the unique name for this TOP. It must start with a 28 | // capital A-Z character, and all the following characters must lower case 29 | // or numbers (a-z, 0-9) 30 | info->customOPInfo.opType->setString("Ponkoutput"); 31 | 32 | // The opLabel is the text that will show up in the OP Create Dialog 33 | info->customOPInfo.opLabel->setString("Ponk Output"); 34 | 35 | // Will be turned into a 3 letter icon on the nodes 36 | info->customOPInfo.opIcon->setString("PNK"); 37 | 38 | // Information about the author of this OP 39 | info->customOPInfo.authorName->setString("Tyrell"); 40 | info->customOPInfo.authorEmail->setString("colas@tyrell.studio"); 41 | 42 | // This SOP works with 0 or 1 inputs 43 | info->customOPInfo.minInputs = 1; 44 | info->customOPInfo.maxInputs = 1; 45 | 46 | } 47 | 48 | DLLEXPORT 49 | SOP_CPlusPlusBase* 50 | CreateSOPInstance(const OP_NodeInfo* info) 51 | { 52 | // Return a new instance of your class every time this is called. 53 | // It will be called once per SOP that is using the .dll 54 | return new PonkOutput(info); 55 | } 56 | 57 | DLLEXPORT 58 | void 59 | DestroySOPInstance(SOP_CPlusPlusBase* instance) 60 | { 61 | // Delete the instance here, this will be called when 62 | // Touch is shutting down, when the SOP using that instance is deleted, or 63 | // if the SOP loads a different DLL 64 | delete (PonkOutput*)instance; 65 | } 66 | 67 | }; 68 | 69 | 70 | PonkOutput::PonkOutput(const OP_NodeInfo* info) : myNodeInfo(info) 71 | { 72 | myExecuteCount = 0; 73 | myOffset = 0.0; 74 | myChop = ""; 75 | 76 | myChopChanName = ""; 77 | myChopChanVal = 0; 78 | 79 | myDat = "N/A"; 80 | 81 | socket = new DatagramSocket (INADDR_ANY, 0);; 82 | } 83 | 84 | PonkOutput::~PonkOutput() 85 | { 86 | delete socket; 87 | } 88 | 89 | void 90 | PonkOutput::getGeneralInfo(SOP_GeneralInfo* ginfo, const OP_Inputs* inputs, void* reserved) 91 | { 92 | // This will cause the node to cook every frame 93 | ginfo->cookEveryFrameIfAsked = false; 94 | 95 | ginfo->cookEveryFrame = true; 96 | 97 | //if direct to GPU loading: 98 | ginfo->directToGPU = false; 99 | 100 | } 101 | 102 | void PonkOutput::push16bits(std::vector& fullData, unsigned short value) { 103 | fullData.push_back(static_cast((value >> 0) & 0xFF)); 104 | fullData.push_back(static_cast((value >> 8) & 0xFF)); 105 | } 106 | 107 | void PonkOutput::push32bits(std::vector& fullData, int value) { 108 | fullData.push_back(static_cast((value >> 0) & 0xFF)); 109 | fullData.push_back(static_cast((value >> 8) & 0xFF)); 110 | fullData.push_back(static_cast((value >> 16) & 0xFF)); 111 | fullData.push_back(static_cast((value >> 24) & 0xFF)); 112 | } 113 | 114 | void PonkOutput::pushFloat32(std::vector& fullData, float value) { 115 | const auto asInt = *reinterpret_cast(&value); 116 | push32bits(fullData,asInt); 117 | } 118 | 119 | void PonkOutput::pushMetaData(std::vector& fullData, const char(&eightCC)[9], int value) { 120 | for (int i = 0; i < 8; i++) { 121 | fullData.push_back(eightCC[i]); 122 | } 123 | push32bits(fullData, value); 124 | } 125 | void PonkOutput::pushMetaData(std::vector& fullData, const char(&eightCC)[9], float value) { 126 | for (int i = 0; i < 8; i++) { 127 | fullData.push_back(eightCC[i]); 128 | } 129 | push32bits(fullData, *(int*)&value); 130 | } 131 | 132 | void PonkOutput::pushPoint_XY_F32_RGB_U8(std::vector& fullData, const Position& pointPosition, const Color& pointColor) { 133 | pushFloat32(fullData, pointPosition.x); 134 | pushFloat32(fullData, pointPosition.y); 135 | #define CLAMP_IN_ZERO_ONE(x) (x<0?0:(x>1?1:x)) 136 | fullData.push_back(static_cast(CLAMP_IN_ZERO_ONE(pointColor.r)*255)); 137 | fullData.push_back(static_cast(CLAMP_IN_ZERO_ONE(pointColor.g)*255)); 138 | fullData.push_back(static_cast(CLAMP_IN_ZERO_ONE(pointColor.b)*255)); 139 | } 140 | 141 | void PonkOutput::pushPoint_XYRGB_U16(std::vector& fullData, const Position& pointPosition, const Color& pointColor) { 142 | if (pointPosition.x < -1 || pointPosition.x > 1 || pointPosition.y < -1 || pointPosition.y > 1) { 143 | // Clamp position and set color = 0 144 | unsigned short x16Bits, y16Bits; 145 | if (pointPosition.x < -1) { 146 | x16Bits = 0; 147 | } else if (pointPosition.x > 1) { 148 | x16Bits = 65535; 149 | } else { 150 | x16Bits = static_cast(((pointPosition.x + 1) / 2) * 65535); 151 | } 152 | if (pointPosition.y < -1) { 153 | y16Bits = 0; 154 | } else if (pointPosition.y > 1) { 155 | y16Bits = 65535; 156 | } else { 157 | y16Bits = static_cast(((pointPosition.y + 1) / 2) * 65535); 158 | } 159 | 160 | // Push X - LSB first 161 | push16bits(fullData, x16Bits); 162 | // Push Y - LSB first 163 | push16bits(fullData, y16Bits); 164 | // Push R - LSB first 165 | push16bits(fullData, 0); 166 | // Push G - LSB first 167 | push16bits(fullData, 0); 168 | // Push B - LSB first 169 | push16bits(fullData, 0); 170 | } else { 171 | const auto x16Bits = static_cast(((pointPosition.x + 1) / 2) * 65535); 172 | const auto y16Bits = static_cast(((pointPosition.y + 1) / 2) * 65535); 173 | 174 | const auto r16Bits = static_cast(((pointColor.r + 1) / 2) * 65535); 175 | const auto g16Bits = static_cast(((pointColor.g + 1) / 2) * 65535); 176 | const auto b16Bits = static_cast(((pointColor.b + 1) / 2) * 65535); 177 | 178 | // Push X - LSB first 179 | push16bits(fullData, x16Bits); 180 | // Push Y - LSB first 181 | push16bits(fullData, y16Bits); 182 | // Push R - LSB first 183 | push16bits(fullData, r16Bits); 184 | // Push G - LSB first 185 | push16bits(fullData, g16Bits); 186 | // Push B - LSB first 187 | push16bits(fullData, b16Bits); 188 | } 189 | } 190 | 191 | std::map PonkOutput::getMetadata(const OP_SOPInput* sinput) { 192 | std::map metadata; 193 | 194 | // check how many metadata attribute the primitive dat contains 195 | int numMetadata = sinput->getNumCustomAttributes(); 196 | 197 | // get the metadata from the dat 198 | for (int i = 0; i < numMetadata; i++) { 199 | const SOP_CustomAttribData* customAttribData = sinput->getCustomAttribute(i); 200 | std::string metadataName = customAttribData->name; 201 | float metadataValue = (float)customAttribData->floatData[0]; 202 | metadata[metadataName] = metadataValue; 203 | } 204 | 205 | return metadata; 206 | } 207 | 208 | Matrix44 209 | PonkOutput::buildCameraTransProjMatrix(const OP_Inputs* inputs) 210 | { 211 | 212 | // get the camera object 213 | const OP_ObjectInput* camera = inputs->getParObject("Camera"); 214 | 215 | Matrix44 cameraWorldTransform; 216 | // check if the parameter is set 217 | if (camera) 218 | { 219 | // it seems that the array returned by OP_ObjectInput->worldTransform 220 | // is not in the correct order therefore we need to reverse 221 | // it before creating our matrix 222 | // TODO: check if that really needed because i had to 223 | // change the order of the Position Matrix multiplication 224 | // in multPositionMatrix member function of the matrix 225 | // class to compensate that 90 degress matrix rotation 226 | cameraWorldTransform = Matrix44(); 227 | for (int x = 0; x < 4; x++) 228 | { 229 | for (int y = 0; y < 4; y++) 230 | { 231 | cameraWorldTransform[x][y] = camera->worldTransform[x][y]; 232 | } 233 | } 234 | cameraWorldTransform.invert(); 235 | } 236 | 237 | // get the camera projection matrix 238 | Matrix44 cameraProjection; 239 | inputs->getParDouble4("Projectionmatrixa", cameraProjection[0][0], 240 | cameraProjection[0][1], 241 | cameraProjection[0][2], 242 | cameraProjection[0][3]); 243 | inputs->getParDouble4("Projectionmatrixb", cameraProjection[1][0], 244 | cameraProjection[1][1], 245 | cameraProjection[2][2], 246 | cameraProjection[3][3]); 247 | inputs->getParDouble4("Projectionmatrixc", cameraProjection[2][0], 248 | cameraProjection[2][1], 249 | cameraProjection[2][2], 250 | cameraProjection[2][3]); 251 | inputs->getParDouble4("Projectionmatrixd", cameraProjection[3][0], 252 | cameraProjection[3][1], 253 | cameraProjection[3][2], 254 | cameraProjection[3][3]); 255 | 256 | return cameraProjection * cameraWorldTransform; 257 | } 258 | 259 | 260 | 261 | void 262 | PonkOutput::execute(SOP_Output* output, const OP_Inputs* inputs, void* reserved) 263 | { 264 | myExecuteCount++; 265 | if (!inputs->getParInt("Active")) { 266 | return; 267 | } 268 | 269 | 270 | // Only run if a SOP is connected on the first input 271 | if (inputs->getNumInputs() > 0) 272 | { 273 | // Get the input sop 274 | const OP_SOPInput *sinput = inputs->getInputSOP(0); 275 | 276 | // build the matrix to do the world space to screen projection 277 | Matrix44 cameraTransProj = buildCameraTransProjMatrix(inputs); 278 | 279 | // Create the vector will store our the data that need to be send to madLaser 280 | std::vector fullData; 281 | fullData.reserve(65536); 282 | 283 | const Position* ptArr = sinput->getPointPositions(); 284 | const Color* colors = nullptr; 285 | 286 | if (sinput->hasColors()) { 287 | colors = sinput->getColors()->colors; 288 | } 289 | 290 | for (int primitiveNumber = 0; primitiveNumber < sinput->getNumPrimitives(); primitiveNumber++) 291 | { 292 | //std::cout << "-------------------- primitive : " << i << std::endl; 293 | 294 | // Write Format Data 295 | fullData.push_back(PONK_DATA_FORMAT_XY_F32_RGB_U8); 296 | 297 | // get the metadata 298 | std::map metadata = getMetadata(sinput); 299 | 300 | // Write meta data count 301 | fullData.push_back(metadata.size()); 302 | 303 | for (const auto& kv : metadata) { 304 | char charMetadata[9]; 305 | std::copy(kv.first.begin(), kv.first.end(), charMetadata); 306 | 307 | pushMetaData(fullData, charMetadata, kv.second); 308 | } 309 | 310 | const SOP_PrimitiveInfo primInfo = sinput->getPrimitive(primitiveNumber); 311 | 312 | const int32_t* primVert = primInfo.pointIndices; 313 | 314 | int numPoints = primInfo.numVertices; 315 | 316 | // check if the primitve is closed 317 | if (primInfo.isClosed) { 318 | // Write point count 319 | push16bits(fullData, numPoints+1); 320 | } else { 321 | push16bits(fullData, numPoints); 322 | } 323 | 324 | static const Color s_white(1.0f, 1.0f, 1.0f, 1.0f); 325 | 326 | for (int pointNumber = 0; pointNumber < numPoints; pointNumber++) { 327 | Position pointPosition = cameraTransProj * ptArr[primVert[pointNumber]]; 328 | pushPoint_XY_F32_RGB_U8(fullData, pointPosition, sinput->hasColors()?colors[primVert[pointNumber]]:s_white); 329 | } 330 | 331 | // If the primitive is close add the first point at the end 332 | if (primInfo.isClosed) { 333 | Position pointPosition = cameraTransProj * ptArr[primVert[0]]; 334 | pushPoint_XY_F32_RGB_U8(fullData, pointPosition, sinput->hasColors()?colors[primVert[0]]:s_white); 335 | 336 | } 337 | } 338 | 339 | 340 | // Check if we don't reach the maximum number of chunck 341 | size_t chunksCount64 = 1 + fullData.size() / (PONK_MAX_CHUNK_SIZE-sizeof(GeomUdpHeader)); 342 | if (chunksCount64 > 255) { 343 | throw std::runtime_error("Protocol doesn't accept sending " 344 | "a packet that would be splitted " 345 | "in more than 255 chunks"); 346 | } 347 | 348 | // Get the ip address from the attribute 349 | int ip[4]; 350 | inputs->getParInt4("Netaddress", ip[0], ip[1], ip[2], ip[3]); 351 | 352 | // Get the Unique identifier from the attribute 353 | int uid = inputs->getParInt("Uid"); 354 | //std::cout << "Uid " << uid << std::endl; 355 | 356 | // Compute packet CRC 357 | unsigned int dataCrc = 0; 358 | for (auto v: fullData) { 359 | dataCrc += v; 360 | } 361 | 362 | size_t written = 0; 363 | unsigned char chunkNumber = 0; 364 | unsigned char chunksCount = static_cast(chunksCount64); 365 | while (written < fullData.size()) { 366 | // Write packet header - 8 bytes 367 | GeomUdpHeader header; 368 | strncpy(header.headerString, PONK_HEADER_STRING, sizeof(header.headerString)); 369 | header.protocolVersion = 0; 370 | header.senderIdentifier = uid; // Unique ID (so when changing name in sender, the receiver can just rename existing stream) 371 | strncpy(header.senderName, "Touch Designer", sizeof(header.senderName)); 372 | header.frameNumber = frameNumber; 373 | header.chunkCount = chunksCount; 374 | header.chunkNumber = chunkNumber; 375 | header.dataCrc = dataCrc; 376 | 377 | // Prepare buffer 378 | std::vector packet; 379 | size_t dataBytesForThisChunk = std::min(fullData.size() - written, PONK_MAX_CHUNK_SIZE-sizeof(GeomUdpHeader)); 380 | packet.resize(sizeof(GeomUdpHeader) + dataBytesForThisChunk); 381 | // Write header 382 | memcpy(&packet[0], &header, sizeof(GeomUdpHeader)); 383 | // Write data 384 | memcpy(&packet[sizeof(GeomUdpHeader)], &fullData[written], dataBytesForThisChunk); 385 | written += dataBytesForThisChunk; 386 | 387 | // Now send chunk packet 388 | GenericAddr destAddr; 389 | destAddr.family = AF_INET; 390 | 391 | // Unicast UDP 392 | destAddr.ip = ((ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3]); 393 | destAddr.port = PONK_PORT; 394 | socket->sendTo(destAddr, &packet[0], static_cast(packet.size())); 395 | 396 | chunkNumber++; 397 | } 398 | 399 | //std::cout << "Sent frame " << std::to_string(frameNumber) << std::endl; 400 | 401 | frameNumber++; 402 | } 403 | 404 | } 405 | 406 | void 407 | PonkOutput::executeVBO(SOP_VBOOutput* output, 408 | const OP_Inputs* inputs, 409 | void* reserved) 410 | { 411 | 412 | } 413 | 414 | //----------------------------------------------------------------------------------------------------- 415 | // CHOP, DAT, and custom parameters 416 | //----------------------------------------------------------------------------------------------------- 417 | 418 | int32_t 419 | PonkOutput::getNumInfoCHOPChans(void* reserved) 420 | { 421 | // We return the number of channel we want to output to any Info CHOP 422 | // connected to the CHOP. In this example we are just going to send 4 channels. 423 | return 4; 424 | } 425 | 426 | void 427 | PonkOutput::getInfoCHOPChan(int32_t index, 428 | OP_InfoCHOPChan* chan, void* reserved) 429 | { 430 | // This function will be called once for each channel we said we'd want to return 431 | // In this example it'll only be called once. 432 | 433 | if (index == 0) 434 | { 435 | chan->name->setString("executeCount"); 436 | chan->value = (float)myExecuteCount; 437 | } 438 | 439 | if (index == 1) 440 | { 441 | chan->name->setString("offset"); 442 | chan->value = (float)myOffset; 443 | } 444 | 445 | if (index == 2) 446 | { 447 | chan->name->setString(myChop.c_str()); 448 | chan->value = (float)myOffset; 449 | } 450 | 451 | if (index == 3) 452 | { 453 | chan->name->setString(myChopChanName.c_str()); 454 | chan->value = myChopChanVal; 455 | } 456 | } 457 | 458 | bool 459 | PonkOutput::getInfoDATSize(OP_InfoDATSize* infoSize, void* reserved) 460 | { 461 | infoSize->rows = 3; 462 | infoSize->cols = 2; 463 | // Setting this to false means we'll be assigning values to the table 464 | // one row at a time. True means we'll do it one column at a time. 465 | infoSize->byColumn = false; 466 | return true; 467 | } 468 | 469 | void 470 | PonkOutput::getInfoDATEntries(int32_t index, 471 | int32_t nEntries, 472 | OP_InfoDATEntries* entries, 473 | void* reserved) 474 | { 475 | char tempBuffer[4096]; 476 | 477 | if (index == 0) 478 | { 479 | // Set the value for the first column 480 | #ifdef _WIN32 481 | strcpy_s(tempBuffer, "executeCount"); 482 | #else // macOS 483 | strlcpy(tempBuffer, "executeCount", sizeof(tempBuffer)); 484 | #endif 485 | entries->values[0]->setString(tempBuffer); 486 | 487 | // Set the value for the second column 488 | #ifdef _WIN32 489 | sprintf_s(tempBuffer, "%d", myExecuteCount); 490 | #else // macOS 491 | snprintf(tempBuffer, sizeof(tempBuffer), "%d", myExecuteCount); 492 | #endif 493 | entries->values[1]->setString(tempBuffer); 494 | } 495 | 496 | if (index == 1) 497 | { 498 | // Set the value for the first column 499 | #ifdef _WIN32 500 | strcpy_s(tempBuffer, "offset"); 501 | #else // macOS 502 | strlcpy(tempBuffer, "offset", sizeof(tempBuffer)); 503 | #endif 504 | entries->values[0]->setString(tempBuffer); 505 | 506 | // Set the value for the second column 507 | #ifdef _WIN32 508 | sprintf_s(tempBuffer, "%g", myOffset); 509 | #else // macOS 510 | snprintf(tempBuffer, sizeof(tempBuffer), "%g", myOffset); 511 | #endif 512 | entries->values[1]->setString(tempBuffer); 513 | } 514 | 515 | if (index == 2) 516 | { 517 | // Set the value for the first column 518 | #ifdef _WIN32 519 | strcpy_s(tempBuffer, "DAT input name"); 520 | #else // macOS 521 | strlcpy(tempBuffer, "offset", sizeof(tempBuffer)); 522 | #endif 523 | entries->values[0]->setString(tempBuffer); 524 | 525 | // Set the value for the second column 526 | #ifdef _WIN32 527 | strcpy_s(tempBuffer, myDat.c_str()); 528 | #else // macOS 529 | snprintf(tempBuffer, sizeof(tempBuffer), "%g", myOffset); 530 | #endif 531 | entries->values[1]->setString(tempBuffer); 532 | } 533 | } 534 | 535 | 536 | 537 | void 538 | PonkOutput::setupParameters(OP_ParameterManager* manager, void* reserved) 539 | { // Active 540 | 541 | { 542 | OP_NumericParameter np; 543 | 544 | np.name = "Active"; 545 | np.label = "Active"; 546 | 547 | OP_ParAppendResult res = manager->appendToggle(np); 548 | assert(res == OP_ParAppendResult::Success); 549 | } 550 | // Ip 551 | { 552 | OP_NumericParameter np; 553 | 554 | np.name = "Netaddress"; 555 | np.label = "Network Address"; 556 | 557 | // Minimum values 558 | np.minValues[0] = 0; 559 | np.minValues[1] = 0; 560 | np.minValues[2] = 0; 561 | np.minValues[3] = 0; 562 | 563 | // Maximum values 564 | np.maxValues[0] = 255; 565 | np.maxValues[1] = 255; 566 | np.maxValues[2] = 255; 567 | np.maxValues[3] = 255; 568 | 569 | // Default value 570 | np.defaultValues[0] = 127; 571 | np.defaultValues[1] = 0; 572 | np.defaultValues[2] = 0; 573 | np.defaultValues[3] = 1; 574 | 575 | OP_ParAppendResult res = manager->appendInt(np, 4); 576 | assert(res == OP_ParAppendResult::Success); 577 | } 578 | 579 | // Unique ID 580 | { 581 | OP_NumericParameter np; 582 | 583 | np.name = "Uid"; 584 | np.label = "Unique ID"; 585 | 586 | OP_ParAppendResult res = manager->appendInt(np, 1); 587 | assert(res == OP_ParAppendResult::Success); 588 | } 589 | 590 | // Camera 591 | 592 | // Camera Object 593 | { 594 | OP_StringParameter sp; 595 | 596 | sp.name = "Camera"; 597 | sp.label = "Camera"; 598 | 599 | OP_ParAppendResult res = manager->appendObject(sp); 600 | assert(res == OP_ParAppendResult::Success); 601 | } 602 | 603 | // projection matrix 604 | { 605 | OP_NumericParameter np; 606 | 607 | np.name = "Projectionmatrixa"; 608 | np.label = "Projection Matrix A"; 609 | 610 | np.defaultValues[0] = 1; 611 | 612 | OP_ParAppendResult res = manager->appendFloat(np, 4); 613 | assert(res == OP_ParAppendResult::Success); 614 | } 615 | 616 | { 617 | OP_NumericParameter np; 618 | 619 | np.defaultValues[1] = 1; 620 | 621 | np.name = "Projectionmatrixb"; 622 | np.label = "Projection Matrix B"; 623 | 624 | OP_ParAppendResult res = manager->appendFloat(np, 4); 625 | assert(res == OP_ParAppendResult::Success); 626 | } 627 | 628 | { 629 | OP_NumericParameter np; 630 | 631 | np.defaultValues[2] = 1; 632 | 633 | np.name = "Projectionmatrixc"; 634 | np.label = "Projection Matrix C"; 635 | 636 | OP_ParAppendResult res = manager->appendFloat(np, 4); 637 | assert(res == OP_ParAppendResult::Success); 638 | } 639 | 640 | { 641 | OP_NumericParameter np; 642 | 643 | np.defaultValues[3] = 1; 644 | 645 | np.name = "Projectionmatrixd"; 646 | np.label = "Projection Matrix D"; 647 | 648 | OP_ParAppendResult res = manager->appendFloat(np, 4); 649 | assert(res == OP_ParAppendResult::Success); 650 | } 651 | } 652 | 653 | void 654 | PonkOutput::pulsePressed(const char* name, void* reserved) 655 | { 656 | if (!strcmp(name, "Reset")) 657 | { 658 | myOffset = 0.0; 659 | } 660 | } 661 | 662 | -------------------------------------------------------------------------------- /Touch Plugin/PonkOutput.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DatagramSocket/DatagramSocket.h" 4 | #include "PonkDefs.h" 5 | 6 | #include "SOP_CPlusPlusBase.h" 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include "matrix.h" 13 | 14 | using namespace TD; 15 | 16 | 17 | // To get more help about these functions, look at SOP_CPlusPlusBase.h 18 | class PonkOutput : public SOP_CPlusPlusBase 19 | { 20 | public: 21 | 22 | PonkOutput(const OP_NodeInfo* info); 23 | 24 | virtual ~PonkOutput(); 25 | 26 | virtual void getGeneralInfo(SOP_GeneralInfo*, const OP_Inputs*, void* reserved1) override; 27 | 28 | virtual void execute(SOP_Output*, const OP_Inputs*, void* reserved) override; 29 | 30 | 31 | virtual void executeVBO(SOP_VBOOutput* output, const OP_Inputs* inputs, 32 | void* reserved) override; 33 | 34 | 35 | virtual int32_t getNumInfoCHOPChans(void* reserved) override; 36 | 37 | virtual void getInfoCHOPChan(int index, OP_InfoCHOPChan* chan, void* reserved) override; 38 | 39 | virtual bool getInfoDATSize(OP_InfoDATSize* infoSize, void* reserved) override; 40 | 41 | virtual void getInfoDATEntries(int32_t index, int32_t nEntries, 42 | OP_InfoDATEntries* entries, 43 | void* reserved) override; 44 | 45 | virtual void setupParameters(OP_ParameterManager* manager, void* reserved) override; 46 | virtual void pulsePressed(const char* name, void* reserved) override; 47 | 48 | private: 49 | void push16bits(std::vector& fullData, unsigned short value); 50 | void push32bits(std::vector& fullData, int value); 51 | void pushFloat32(std::vector& fullData, float value); 52 | void pushMetaData(std::vector& fullData, const char(&eightCC)[9], int value); 53 | void pushMetaData(std::vector& fullData, const char(&eightCC)[9], float value); 54 | void pushPoint_XYRGB_U16(std::vector& fullData, const Position& pointPosition, const Color& pointColor); 55 | void pushPoint_XY_F32_RGB_U8(std::vector& fullData, const Position& pointPosition, const Color& pointColor); 56 | std::map getMetadata(const OP_SOPInput* sinput); 57 | 58 | Matrix44 buildCameraTransProjMatrix(const OP_Inputs* inputs); 59 | 60 | // We don't need to store this pointer, but we do for the example. 61 | // The OP_NodeInfo class store information about the node that's using 62 | // this instance of the class (like its name). 63 | const OP_NodeInfo* myNodeInfo; 64 | 65 | // In this example this value will be incremented each time the execute() 66 | // function is called, then passes back to the SOP 67 | int32_t myExecuteCount; 68 | 69 | 70 | double myOffset; 71 | std::string myChopChanName; 72 | float myChopChanVal; 73 | std::string myChop; 74 | 75 | std::string myDat; 76 | 77 | int myNumVBOTexLayers; 78 | 79 | DatagramSocket* socket; 80 | 81 | double animTime = 0; 82 | unsigned char frameNumber = 0; 83 | }; 84 | -------------------------------------------------------------------------------- /Touch Plugin/PonkOutput.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.32413.511 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PonkOutput", "PonkOutput.vcxproj", "{3F5BEECD-FA36-459F-91B8-BB481A67EF44}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Debug|x64.ActiveCfg = Debug|x64 15 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Debug|x64.Build.0 = Debug|x64 16 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Release|x64.ActiveCfg = Release|x64 17 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3A756144-3B2C-48D6-A356-BA0E2E5F2010} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Touch Plugin/PonkOutput.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44} 15 | SimpleShapes 16 | Win32Proj 17 | 10.0 18 | 19 | 20 | 21 | DynamicLibrary 22 | Unicode 23 | true 24 | v142 25 | 26 | 27 | DynamicLibrary 28 | Unicode 29 | v142 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | <_ProjectFileVersion>10.0.40219.1 43 | $(SolutionDir)$(Configuration)\ 44 | $(Configuration)\ 45 | false 46 | $(SolutionDir)$(Configuration)\ 47 | $(Configuration)\ 48 | false 49 | 50 | 51 | ..\Common\Cpp;$(IncludePath) 52 | 53 | 54 | ..\Common\Cpp;$(IncludePath) 55 | 56 | 57 | 58 | Disabled 59 | WIN32;_DEBUG;_WINDOWS;_USRDLL;SIMPLESHAPES_EXPORTS;%(PreprocessorDefinitions) 60 | EnableFastChecks 61 | MultiThreadedDebugDLL 62 | 63 | 64 | Level3 65 | ProgramDatabase 66 | 67 | 68 | true 69 | Windows 70 | 71 | 72 | 73 | 74 | WIN32;NDEBUG;_WINDOWS;_USRDLL;CPLUSPLUSCHOPEXAMPLE_EXPORTS;%(PreprocessorDefinitions) 75 | MultiThreadedDLL 76 | 77 | 78 | Level3 79 | ProgramDatabase 80 | 81 | 82 | true 83 | Windows 84 | true 85 | true 86 | 87 | 88 | 89 | 90 | 91 | WIN32;_DEBUG;_WINDOWS;_USRDLL;SIMPLESHAPES_EXPORTS;%(PreprocessorDefinitions) 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /Touch Plugin/PonkOutput.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C9939CF7282AE5B700381246 /* PonkOutput.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9939CF5282AE5B700381246 /* PonkOutput.cpp */; }; 11 | C9939CFD282AE79B00381246 /* DatagramSocket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9939CFB282AE79B00381246 /* DatagramSocket.cpp */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXFileReference section */ 15 | C98BE64728C93A5F00BA61C4 /* PonkDefs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PonkDefs.h; path = ../Common/Cpp/PonkDefs.h; sourceTree = ""; }; 16 | C9939CF5282AE5B700381246 /* PonkOutput.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PonkOutput.cpp; sourceTree = ""; }; 17 | C9939CF6282AE5B700381246 /* PonkOutput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PonkOutput.h; sourceTree = ""; }; 18 | C9939CFB282AE79B00381246 /* DatagramSocket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DatagramSocket.cpp; sourceTree = ""; }; 19 | C9939CFC282AE79B00381246 /* DatagramSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DatagramSocket.h; sourceTree = ""; }; 20 | E227272721B6FEB100905532 /* PonkOutput.plugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PonkOutput.plugin; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | E227272A21B6FEB100905532 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 22 | E227273021B6FF1F00905532 /* CPlusPlus_Common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CPlusPlus_Common.h; sourceTree = ""; }; 23 | E227273121B6FF1F00905532 /* GL_Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GL_Extensions.h; sourceTree = ""; }; 24 | E227273221B6FF1F00905532 /* SOP_CPlusPlusBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOP_CPlusPlusBase.h; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXFrameworksBuildPhase section */ 28 | E227272421B6FEB100905532 /* Frameworks */ = { 29 | isa = PBXFrameworksBuildPhase; 30 | buildActionMask = 2147483647; 31 | files = ( 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXFrameworksBuildPhase section */ 36 | 37 | /* Begin PBXGroup section */ 38 | C9939CFA282AE79B00381246 /* DatagramSocket */ = { 39 | isa = PBXGroup; 40 | children = ( 41 | C9939CFB282AE79B00381246 /* DatagramSocket.cpp */, 42 | C9939CFC282AE79B00381246 /* DatagramSocket.h */, 43 | ); 44 | name = DatagramSocket; 45 | path = ../Common/Cpp/DatagramSocket; 46 | sourceTree = ""; 47 | }; 48 | E227271E21B6FEB100905532 = { 49 | isa = PBXGroup; 50 | children = ( 51 | E227272921B6FEB100905532 /* PonkOutput */, 52 | E227272821B6FEB100905532 /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | E227272821B6FEB100905532 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | E227272721B6FEB100905532 /* PonkOutput.plugin */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | E227272921B6FEB100905532 /* PonkOutput */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | C98BE64728C93A5F00BA61C4 /* PonkDefs.h */, 68 | C9939CFA282AE79B00381246 /* DatagramSocket */, 69 | E227273021B6FF1F00905532 /* CPlusPlus_Common.h */, 70 | E227273121B6FF1F00905532 /* GL_Extensions.h */, 71 | C9939CF5282AE5B700381246 /* PonkOutput.cpp */, 72 | C9939CF6282AE5B700381246 /* PonkOutput.h */, 73 | E227273221B6FF1F00905532 /* SOP_CPlusPlusBase.h */, 74 | E227272A21B6FEB100905532 /* Info.plist */, 75 | ); 76 | name = PonkOutput; 77 | sourceTree = ""; 78 | }; 79 | /* End PBXGroup section */ 80 | 81 | /* Begin PBXNativeTarget section */ 82 | E227272621B6FEB100905532 /* PonkOutput */ = { 83 | isa = PBXNativeTarget; 84 | buildConfigurationList = E227272D21B6FEB100905532 /* Build configuration list for PBXNativeTarget "PonkOutput" */; 85 | buildPhases = ( 86 | E227272321B6FEB100905532 /* Sources */, 87 | E227272421B6FEB100905532 /* Frameworks */, 88 | E227272521B6FEB100905532 /* Resources */, 89 | ); 90 | buildRules = ( 91 | ); 92 | dependencies = ( 93 | ); 94 | name = PonkOutput; 95 | productName = SimpleShapes; 96 | productReference = E227272721B6FEB100905532 /* PonkOutput.plugin */; 97 | productType = "com.apple.product-type.bundle"; 98 | }; 99 | /* End PBXNativeTarget section */ 100 | 101 | /* Begin PBXProject section */ 102 | E227271F21B6FEB100905532 /* Project object */ = { 103 | isa = PBXProject; 104 | attributes = { 105 | LastUpgradeCheck = 1330; 106 | ORGANIZATIONNAME = Derivative; 107 | TargetAttributes = { 108 | E227272621B6FEB100905532 = { 109 | CreatedOnToolsVersion = 10.1; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = E227272221B6FEB100905532 /* Build configuration list for PBXProject "PonkOutput" */; 114 | compatibilityVersion = "Xcode 9.3"; 115 | developmentRegion = en; 116 | hasScannedForEncodings = 0; 117 | knownRegions = ( 118 | en, 119 | Base, 120 | ); 121 | mainGroup = E227271E21B6FEB100905532; 122 | productRefGroup = E227272821B6FEB100905532 /* Products */; 123 | projectDirPath = ""; 124 | projectRoot = ""; 125 | targets = ( 126 | E227272621B6FEB100905532 /* PonkOutput */, 127 | ); 128 | }; 129 | /* End PBXProject section */ 130 | 131 | /* Begin PBXResourcesBuildPhase section */ 132 | E227272521B6FEB100905532 /* Resources */ = { 133 | isa = PBXResourcesBuildPhase; 134 | buildActionMask = 2147483647; 135 | files = ( 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | /* End PBXResourcesBuildPhase section */ 140 | 141 | /* Begin PBXSourcesBuildPhase section */ 142 | E227272321B6FEB100905532 /* Sources */ = { 143 | isa = PBXSourcesBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | C9939CF7282AE5B700381246 /* PonkOutput.cpp in Sources */, 147 | C9939CFD282AE79B00381246 /* DatagramSocket.cpp in Sources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXSourcesBuildPhase section */ 152 | 153 | /* Begin XCBuildConfiguration section */ 154 | E227272B21B6FEB100905532 /* Debug */ = { 155 | isa = XCBuildConfiguration; 156 | buildSettings = { 157 | ALWAYS_SEARCH_USER_PATHS = NO; 158 | CLANG_ANALYZER_NONNULL = YES; 159 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 160 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 161 | CLANG_CXX_LIBRARY = "libc++"; 162 | CLANG_ENABLE_MODULES = YES; 163 | CLANG_ENABLE_OBJC_ARC = YES; 164 | CLANG_ENABLE_OBJC_WEAK = YES; 165 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 166 | CLANG_WARN_BOOL_CONVERSION = YES; 167 | CLANG_WARN_COMMA = YES; 168 | CLANG_WARN_CONSTANT_CONVERSION = YES; 169 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 170 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 171 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 172 | CLANG_WARN_EMPTY_BODY = YES; 173 | CLANG_WARN_ENUM_CONVERSION = YES; 174 | CLANG_WARN_INFINITE_RECURSION = YES; 175 | CLANG_WARN_INT_CONVERSION = YES; 176 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 177 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 178 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 179 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 180 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 181 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 182 | CLANG_WARN_STRICT_PROTOTYPES = YES; 183 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 184 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 185 | CLANG_WARN_UNREACHABLE_CODE = YES; 186 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 187 | CODE_SIGN_IDENTITY = "-"; 188 | COPY_PHASE_STRIP = NO; 189 | DEBUG_INFORMATION_FORMAT = dwarf; 190 | ENABLE_STRICT_OBJC_MSGSEND = YES; 191 | ENABLE_TESTABILITY = YES; 192 | GCC_C_LANGUAGE_STANDARD = gnu11; 193 | GCC_DYNAMIC_NO_PIC = NO; 194 | GCC_NO_COMMON_BLOCKS = YES; 195 | GCC_OPTIMIZATION_LEVEL = 0; 196 | GCC_PREPROCESSOR_DEFINITIONS = ( 197 | "DEBUG=1", 198 | "$(inherited)", 199 | ); 200 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 201 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 202 | GCC_WARN_UNDECLARED_SELECTOR = YES; 203 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 204 | GCC_WARN_UNUSED_FUNCTION = YES; 205 | GCC_WARN_UNUSED_VARIABLE = YES; 206 | MACOSX_DEPLOYMENT_TARGET = 10.13; 207 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 208 | MTL_FAST_MATH = YES; 209 | ONLY_ACTIVE_ARCH = YES; 210 | SDKROOT = macosx; 211 | }; 212 | name = Debug; 213 | }; 214 | E227272C21B6FEB100905532 /* Release */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | ALWAYS_SEARCH_USER_PATHS = NO; 218 | CLANG_ANALYZER_NONNULL = YES; 219 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 220 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 221 | CLANG_CXX_LIBRARY = "libc++"; 222 | CLANG_ENABLE_MODULES = YES; 223 | CLANG_ENABLE_OBJC_ARC = YES; 224 | CLANG_ENABLE_OBJC_WEAK = YES; 225 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 226 | CLANG_WARN_BOOL_CONVERSION = YES; 227 | CLANG_WARN_COMMA = YES; 228 | CLANG_WARN_CONSTANT_CONVERSION = YES; 229 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 230 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 231 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 232 | CLANG_WARN_EMPTY_BODY = YES; 233 | CLANG_WARN_ENUM_CONVERSION = YES; 234 | CLANG_WARN_INFINITE_RECURSION = YES; 235 | CLANG_WARN_INT_CONVERSION = YES; 236 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 237 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 238 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 239 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 240 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 241 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 242 | CLANG_WARN_STRICT_PROTOTYPES = YES; 243 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 244 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 245 | CLANG_WARN_UNREACHABLE_CODE = YES; 246 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 247 | CODE_SIGN_IDENTITY = "-"; 248 | COPY_PHASE_STRIP = NO; 249 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 250 | ENABLE_NS_ASSERTIONS = NO; 251 | ENABLE_STRICT_OBJC_MSGSEND = YES; 252 | GCC_C_LANGUAGE_STANDARD = gnu11; 253 | GCC_NO_COMMON_BLOCKS = YES; 254 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 255 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 256 | GCC_WARN_UNDECLARED_SELECTOR = YES; 257 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 258 | GCC_WARN_UNUSED_FUNCTION = YES; 259 | GCC_WARN_UNUSED_VARIABLE = YES; 260 | MACOSX_DEPLOYMENT_TARGET = 10.13; 261 | MTL_ENABLE_DEBUG_INFO = NO; 262 | MTL_FAST_MATH = YES; 263 | SDKROOT = macosx; 264 | }; 265 | name = Release; 266 | }; 267 | E227272E21B6FEB100905532 /* Debug */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | CODE_SIGN_STYLE = Automatic; 271 | COMBINE_HIDPI_IMAGES = YES; 272 | DEVELOPMENT_TEAM = 574S7ZGCZ5; 273 | HEADER_SEARCH_PATHS = ( 274 | ../Common/Cpp, 275 | ../Common/Cpp/DatagramSocket, 276 | ); 277 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 278 | INSTALL_PATH = /; 279 | PRODUCT_BUNDLE_IDENTIFIER = com.Tyrell.PonkOutput; 280 | PRODUCT_NAME = "$(TARGET_NAME)"; 281 | WRAPPER_EXTENSION = plugin; 282 | }; 283 | name = Debug; 284 | }; 285 | E227272F21B6FEB100905532 /* Release */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | CODE_SIGN_STYLE = Automatic; 289 | COMBINE_HIDPI_IMAGES = YES; 290 | DEVELOPMENT_TEAM = 574S7ZGCZ5; 291 | HEADER_SEARCH_PATHS = ( 292 | ../Common/Cpp, 293 | ../Common/Cpp/DatagramSocket, 294 | ); 295 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 296 | INSTALL_PATH = /; 297 | PRODUCT_BUNDLE_IDENTIFIER = com.Tyrell.PonkOutput; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | WRAPPER_EXTENSION = plugin; 300 | }; 301 | name = Release; 302 | }; 303 | /* End XCBuildConfiguration section */ 304 | 305 | /* Begin XCConfigurationList section */ 306 | E227272221B6FEB100905532 /* Build configuration list for PBXProject "PonkOutput" */ = { 307 | isa = XCConfigurationList; 308 | buildConfigurations = ( 309 | E227272B21B6FEB100905532 /* Debug */, 310 | E227272C21B6FEB100905532 /* Release */, 311 | ); 312 | defaultConfigurationIsVisible = 0; 313 | defaultConfigurationName = Release; 314 | }; 315 | E227272D21B6FEB100905532 /* Build configuration list for PBXNativeTarget "PonkOutput" */ = { 316 | isa = XCConfigurationList; 317 | buildConfigurations = ( 318 | E227272E21B6FEB100905532 /* Debug */, 319 | E227272F21B6FEB100905532 /* Release */, 320 | ); 321 | defaultConfigurationIsVisible = 0; 322 | defaultConfigurationName = Release; 323 | }; 324 | /* End XCConfigurationList section */ 325 | }; 326 | rootObject = E227271F21B6FEB100905532 /* Project object */; 327 | } 328 | -------------------------------------------------------------------------------- /Touch Plugin/PonkOutput.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Touch Plugin/PonkOutput.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Touch Plugin/PonkOutput.xcodeproj/xcshareddata/xcschemes/PonkOutput.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Touch Plugin/SOP_CPlusPlusBase.h: -------------------------------------------------------------------------------- 1 | /* Shared Use License: This file is owned by Derivative Inc. (Derivative) 2 | * and can only be used, and/or modified for use, in conjunction with 3 | * Derivative's TouchDesigner software, and only if you are a licensee who has 4 | * accepted Derivative's TouchDesigner license or assignment agreement 5 | * (which also govern the use of this file). You may share or redistribute 6 | * a modified version of this file provided the following conditions are met: 7 | * 8 | * 1. The shared file or redistribution must retain the information set out 9 | * above and this list of conditions. 10 | * 2. Derivative's name (Derivative Inc.) or its trademarks may not be used 11 | * to endorse or promote products derived from this file without specific 12 | * prior written permission from Derivative. 13 | */ 14 | 15 | /* 16 | * Produced by: 17 | * 18 | * Derivative Inc 19 | * 401 Richmond Street West, Unit 386 20 | * Toronto, Ontario 21 | * Canada M5V 3A8 22 | * 416-591-3555 23 | * 24 | * NAME: SOP_CPlusPlusBase.h 25 | * 26 | * 27 | * Do not edit this file directly! 28 | * Make a subclass of SOP_CPlusPlusBase instead, and add your own 29 | * data/functions. 30 | 31 | * Derivative Developers:: Make sure the virtual function order 32 | * stays the same, otherwise changes won't be backwards compatible 33 | */ 34 | //#pragma once 35 | 36 | #ifndef __SOP_CPlusPlusBase__ 37 | #define __SOP_CPlusPlusBase__ 38 | 39 | #include 40 | #include "CPlusPlus_Common.h" 41 | 42 | namespace TD 43 | { 44 | 45 | class SOP_CPlusPlusBase; 46 | 47 | #pragma pack(push, 8) 48 | 49 | // Define for the current API version that this sample code is made for. 50 | // To upgrade to a newer version, replace the files 51 | // SOP_CPlusPlusBase.h 52 | // CPlusPlus_Common.h 53 | // from the samples folder in a newer TouchDesigner installation. 54 | // You may need to upgrade your plugin code in that case, to match 55 | // the new API requirements 56 | const int SOPCPlusPlusAPIVersion = 3; 57 | 58 | class SOP_PluginInfo 59 | { 60 | public: 61 | int32_t apiVersion = 0; 62 | 63 | int32_t reserved[100]; 64 | 65 | // Information used to describe this plugin as a custom OP. 66 | OP_CustomOPInfo customOPInfo; 67 | 68 | int32_t reserved2[20]; 69 | }; 70 | 71 | enum class SOP_Winding : int32_t 72 | { 73 | // Clockwise vertex winding. Don't use, for legacy plugins. 74 | LegacyCW = 0, 75 | 76 | // Counter-clockwise vertex winding. All new plugins should work this way. 77 | CCW, 78 | }; 79 | 80 | class SOP_GeneralInfo 81 | { 82 | public: 83 | // Set this to true if you want the SOP to cook every frame, even 84 | // if none of it's inputs/parameters are changing. 85 | // This is generally useful for cases where the node is outputting to 86 | // something external to TouchDesigner, such as a network socket or device. 87 | // It ensures the node cooks every if nothing inside the network is using/viewing 88 | // the output of this node. 89 | // Important: 90 | // If the node may not be viewed/used by other nodes in the file, 91 | // such as a TCP network output node that isn't viewed in perform mode, 92 | // you should set cookOnStart = true in OP_CustomOPInfo. 93 | // That will ensure cooking is kick-started for this node. 94 | // Note that this fix only works for Custom Operators, not 95 | // cases where the .dll is loaded into CPlusPlus SOP. 96 | // DEFAULT: false 97 | bool cookEveryFrame; 98 | 99 | // Set this to true if you want the SOP to cook every frame, but only 100 | // if someone asks for it to cook. So if nobody is using the output from 101 | // the SOP, it won't cook. This is difereent from 'cookEveryFrame' 102 | // since that will cause it to cook every frame no matter what. 103 | // Defaults to false. 104 | // DEFAULT: false 105 | bool cookEveryFrameIfAsked; 106 | 107 | 108 | // Set this flag to true to load the geometry to the GPU for faster updating 109 | // Note that if you set this flag to 'true', then the function executeVBO() is being called 110 | // instead of execute() function. 111 | // If this flag is set to false, then execute() function is called. 112 | // When directToGPU is true, the data is not available to be used in SOPs, 113 | // except to be rendered with a Render TOP. 114 | // DEFAULT: false. 115 | bool directToGPU; 116 | 117 | 118 | // Legacy code used clockwise vertex winding, but counter-clockwise is more consistent 119 | // with TouchDesigner. Older plugins will have this set to LegacyCW, 120 | // but newer ones should set it to CCW, as the sample code does. 121 | SOP_Winding winding; 122 | 123 | private: 124 | int32_t reserved[19]; 125 | }; 126 | 127 | 128 | 129 | // The buffer object mode. 130 | // This helps the graphics driver determine where to keep the data 131 | // for beter performance. 132 | enum class VBOBufferMode : int32_t 133 | { 134 | // The data will be modified once or rarely and used many times. 135 | Static = 0, 136 | 137 | // The data will be modified repeatedly and used many times. 138 | Dynamic, 139 | }; 140 | 141 | // an enumerator to specify the group type 142 | enum class SOP_GroupType 143 | { 144 | Point = 0, 145 | Primitive, 146 | }; 147 | 148 | 149 | // This class is used to create geometry on the CPU, to be used in SOP networks. 150 | // NOTE: set 'directToGPU' flag from SOP_GeneralInfo class to false. 151 | class SOP_Output 152 | { 153 | public: 154 | 155 | SOP_Output() 156 | { 157 | } 158 | 159 | ~SOP_Output() 160 | { 161 | } 162 | 163 | // Add a single point at the given position. 164 | // Returns the point's index. 165 | virtual int32_t addPoint(const Position& pos) = 0; 166 | 167 | // Add multiple points at specified positions. 168 | // 'numPoints' is the number of points to be added. 169 | virtual bool addPoints(const Position* pos, int32_t numPoints) = 0; 170 | 171 | // Returns the number of added points at the time of query 172 | virtual int32_t getNumPoints() = 0; 173 | 174 | // Set the normal vector for the point with the 'pointIdx'. 175 | // The point must already exist by via calling addPoints() or addPoint(). 176 | virtual bool setNormal(const Vector& n, int32_t pointIdx) = 0; 177 | 178 | // Set the normal vectors for existing points. 179 | // Note that has been the points must be already added by calling addPoints() or addPoint(). 180 | // The startPointIdx indicates the start index of the points to set normals for. 181 | virtual bool setNormals(const Vector* n, int32_t numPoints, int32_t startPointIdx) = 0; 182 | 183 | // Returns true if the normal has been set for this geometry. 184 | virtual bool hasNormal() = 0; 185 | 186 | // Set the color value with Color (i.e. r,g,b,a) for the point with 'pointIdx' index. 187 | // The point must already exist by via calling addPoints() or addPoint(). 188 | virtual bool setColor(const Color& c, int32_t pointIdx) = 0; 189 | 190 | // Set the colors for points that are already added. 191 | // The startPointIdx indicates the start index of the points to set colors for. 192 | virtual bool setColors(const Color* colors, int32_t numPoints, int32_t startPointIdx) = 0; 193 | 194 | // Returns true if the color has been set for this geometry. 195 | virtual bool hasColor() = 0; 196 | 197 | // Set texture coordinate data for existing points. 198 | // The numLayers is the texcoord size and can be from 1 up to 8 for texture layers 199 | // The first numLayers used will be used as the max number of layers for all future calls for this cook. 200 | // The pointIdx specifies the point index with the texture coords 201 | virtual bool setTexCoord(const TexCoord* tex, int32_t numLayers, int32_t pointIdx) = 0; 202 | 203 | // Set texture coordinate data for existing points. 204 | // The numLayers is the texCoord size and can be from 1 up to 8 for texCoord layers. 205 | // The first numLayers used will be used as the max number of layers for all future calls for this cook. 206 | // The startPointIdx indicates the start index of the points to set texCoord for. 207 | virtual bool setTexCoords(const TexCoord* t, int32_t numPoints, int32_t numLayers, int32_t startPointIdx) = 0; 208 | 209 | // Returns true if the texCoord/textures has been set for this geometry. 210 | virtual bool hasTexCoord() = 0; 211 | 212 | // Returns the number of texcoord layers 213 | virtual int32_t getNumTexCoordLayers() = 0; 214 | 215 | // Set the custom attribute with SOP_CustomAttribData (must have set its name, number of components, and its type) 216 | // The data param must hold the data for the custom attribute. 217 | // E.g a custom atrrib with 4 components for each point should holds 4*numPoints values for its data. 218 | virtual bool setCustomAttribute(const SOP_CustomAttribData* cu, int32_t numPoints) = 0; 219 | 220 | // Returns true if the custom attributes has been set for this geometry. 221 | virtual bool hasCustomAttibutes() = 0; 222 | 223 | // Add a triangle using the points at the given 3 indices. 224 | virtual bool addTriangle(int32_t ptIdx1, int32_t ptIdx2, int32_t ptIdx3) = 0; 225 | 226 | // Add multiple triangles using an array of point's indices. 227 | // The size param represents the number of triangles to be added. 228 | // 'indices' must contain at least 3 * size elements. 229 | virtual bool addTriangles(const int32_t *indices, int32_t size) = 0; 230 | 231 | // add particle systems from the points that has been already added. The points can have colors, normals and custom attribs. 232 | // the startIndex param is the staring index of the points from particle system. 233 | virtual bool addParticleSystem(int32_t numParticles, int32_t startIndex) = 0; 234 | 235 | // Add line strip from the points that has been already added. The points can have colors, normals and custom attribs. 236 | // the 'indices' contains the indices of vertices, and 'size' is the number of indices for the line strip 237 | virtual bool addLine(const int32_t *indices, int32_t size) = 0; 238 | 239 | // Add line strips from the points that has been already added.The points can have colors, normals and custom attribs. 240 | // the 'indices' contains the indices of vertices, 'sizeOfEachLine' contains the number of vertices for each line, 241 | // 'numOfLines' specifies the number of lines to be drawn. 242 | // Note that the number of elements in sizeOfEachLine must be equal to numOfLines. 243 | virtual bool addLines(const int32_t *indices, int32_t* sizeOfEachLine, int32_t numOfLines) = 0; 244 | 245 | // Returns the number of added primitives at the time of query. Currently it is either the number of triangles or particles. 246 | virtual int32_t getNumPrimitives() = 0; 247 | 248 | // Set the bounding box for the whole geometry. 249 | // Setting the bounding box helps to have exact homing on the viewer. 250 | // You may set this value at each frame for non static geometries that are translating constantly. 251 | virtual bool setBoundingBox(const BoundingBox& bbox) = 0; 252 | 253 | // Add a group with input type and name. 254 | // Returns false if a group with this name already exists. 255 | virtual bool addGroup(const SOP_GroupType& type, const char* name) = 0; 256 | 257 | // Destroy a group with input type and name. 258 | // Returns false if a group with this name for the specified type does not exists. 259 | virtual bool destroyGroup(const SOP_GroupType& type, const char* name) = 0; 260 | 261 | // Add a point with its index to an already existing group with SOP_GroupType::Point type. 262 | // Returns false if a point group with this name does not exists Or 263 | // if a point with that index does not exists. 264 | virtual bool addPointToGroup(int index, const char* name) = 0; 265 | 266 | // Add a primitive with its index to an already existing group with SOP_GroupType::Primitive type. 267 | // Returns false if a primitive group with this name does not exists Or 268 | // if a pritimive with that index does not exists. 269 | virtual bool addPrimToGroup(int index, const char* name) = 0; 270 | 271 | // Add a point/prim index to an already defined group. 272 | // Returns false if a primitive group with this name does not exists Or 273 | // if a pritimive/point with that index does not exists. 274 | virtual bool addToGroup(int index, const SOP_GroupType& type, const char* name) = 0; 275 | 276 | // Add a point with its index to an already existing group with SOP_GroupType::Point type. 277 | // Returns false if a point group with this name does not exists Or 278 | // if a point with that index does not exists. 279 | virtual bool discardFromPointGroup(int index, const char* name) = 0; 280 | 281 | // Add a primitive with its index to an already existing group with SOP_GroupType::Primitive type. 282 | // Returns false if a primitive group with this name does not exists Or 283 | // if a pritimive with that index does not exists. 284 | virtual bool discardFromPrimGroup(int index, const char* name) = 0; 285 | 286 | // Remove a point/prim index from an already defined group. 287 | // Returns false if a primitive group with this name does not exists Or 288 | // if a pritimive/point with that index does not exists. 289 | virtual bool discardFromGroup(int index, const SOP_GroupType& type, const char* name) = 0; 290 | 291 | private: 292 | 293 | int32_t reserved[20]; 294 | }; 295 | 296 | 297 | // This class is used to load geometry directly onto the GPU. The geometry can be used for rendering, but not SOP networks. 298 | // NOTE: set 'directToGPU' flag from SOP_GeneralInfo class to true. 299 | class SOP_VBOOutput 300 | { 301 | 302 | public: 303 | 304 | SOP_VBOOutput() 305 | { 306 | } 307 | 308 | ~SOP_VBOOutput() 309 | { 310 | } 311 | 312 | // enable/set the normal, color, texcoord, if the geometry contains this information 313 | virtual void enableNormal() = 0; 314 | 315 | virtual void enableColor() = 0; 316 | 317 | virtual void enableTexCoord(int32_t numLayers = 0) = 0; 318 | 319 | // Returns true if the normal, color, texcoord, or custom attributes has been set for this geometry. 320 | virtual bool hasNormal() = 0; 321 | 322 | virtual bool hasColor() = 0; 323 | 324 | virtual bool hasTexCoord() = 0; 325 | 326 | virtual bool hasCustomAttibutes() = 0; 327 | 328 | // Add the custom attribute with SOP_CustomAttribInfo (must have set its name, number of components, and its type) 329 | virtual bool addCustomAttribute(const SOP_CustomAttribInfo& cu) = 0; 330 | 331 | // Allocates and setup VBO buffers. 332 | // Call this fucntion before adding any points, colors or normals, 333 | // but after enableNormal(), enableColor(), addCustomAttribute(). 334 | // 'numVertices' is how much memory to allocate for positions/normals etc. 335 | // 'numIndices' is how much memory to allocate for indices that are used 336 | // to build primitives. 337 | virtual void allocVBO(int32_t numVertices, int32_t numIndices, VBOBufferMode mode) = 0; 338 | 339 | // Returns the start of an array of Positions that should be filled. 340 | // The length of this array is numVertices. 341 | virtual Position* getPos() = 0; 342 | 343 | // Returns the start of an array of Vectors that should be filled. 344 | // The length of this array is numVertices. 345 | virtual Vector* getNormals() = 0; 346 | 347 | // Returns the start of an array of Colors that should be filled. 348 | // The length of this array is numVertices. 349 | virtual Color* getColors() = 0; 350 | 351 | // Returns the start of an array of TexCoords that should be filled. 352 | // The length of this array is numVertices. 353 | virtual TexCoord* getTexCoords() = 0; 354 | 355 | // Returns the number of texcoord layers which has been already set 356 | // by enableTexCoord() call. 357 | virtual int32_t getNumTexCoordLayers() = 0; 358 | 359 | // Returns an array of indices (of vertices) that should be filled. The indices 360 | // for all the triangles must be set as their initial values is undefined. 361 | // It is used to create 'numTriangles' triangles. 362 | // Length of the returned array is numTriangles * 3. 363 | virtual int32_t* addTriangles(int32_t numTriangles) = 0; 364 | 365 | // Returns an array of indices (of vertices) that should be filled. The indices 366 | // for particles must be set as their initial values is undefined. 367 | // It is used to create 'numParticles' particles. 368 | // Length of the returned array is numParticles. 369 | virtual int32_t* addParticleSystem(int32_t numParticles) = 0; 370 | 371 | // Returns an array of indices (of vertices) that should be filled. 372 | // It is used to create line strip with numIndices. 373 | // Length of the returned array is numIndices. 374 | virtual int32_t* addLines(int32_t numIndices) = 0; 375 | 376 | // Fills SOP_CustomAttribData with data for the given attribute 'name'. 377 | // The intData or floatData member contains a pointer to the member that should 378 | // be filled. 379 | // Returns false is case of null arguments, or invalid name. 380 | virtual bool getCustomAttribute(SOP_CustomAttribData* cu, const char* name) = 0; 381 | 382 | // Finish updating the VBO buffers. 383 | // After you are done with the VBO buffers, make sure to call this function 384 | // Note: this function must be the last function to be called 385 | virtual void updateComplete() = 0; 386 | 387 | // Set the bounding box for the whole geometry. 388 | // We recommned to set the bounding box in GPU direct mode for exact homing. 389 | // You may set this value at each frame for non static geometries that are translating constantly. 390 | virtual bool setBoundingBox(const BoundingBox& bbox) = 0; 391 | 392 | private: 393 | 394 | }; 395 | 396 | 397 | 398 | /*** DO NOT EDIT THIS CLASS, MAKE A SUBCLASS OF IT INSTEAD ***/ 399 | class SOP_CPlusPlusBase 400 | { 401 | 402 | protected: 403 | 404 | SOP_CPlusPlusBase() 405 | { 406 | } 407 | 408 | public: 409 | 410 | virtual 411 | ~SOP_CPlusPlusBase() 412 | { 413 | } 414 | 415 | // BEGIN PUBLIC INTERFACE 416 | 417 | // Some general settings can be assigned here (if you ovierride it) 418 | // The OP_Inputs* provides the access to the custom parameters 419 | // before the call to the execute/VBO() functions. 420 | virtual void 421 | getGeneralInfo(SOP_GeneralInfo*, const OP_Inputs*, void* reserved1) 422 | { 423 | } 424 | 425 | 426 | // Add geometry data such as points, normals, colors, and triangles 427 | // or particles and etc. obtained from your desired algorithm or external files. 428 | // If the "directToGPU" flag is set to false, this function is being called 429 | // instead of executeVBO(). 430 | // See the OP_Inputs class definition for more details on it's contents 431 | virtual void execute(SOP_Output*, const OP_Inputs*, void* reserved1) = 0; 432 | 433 | // For direct GPU loading (i.e. "directToGPU" is set to true) this function is being called 434 | // instead of execute(). 435 | // Fill the VBO buffers with the geometry data, obtained from your desired algorithm or files, 436 | // such as points, normals, colors, texcoord, triangles, and etc. 437 | virtual void executeVBO(SOP_VBOOutput*, const OP_Inputs*, void* reserved1) = 0; 438 | 439 | 440 | // Override these methods if you want to output values to the Info CHOP/DAT 441 | // returning 0 means you dont plan to output any Info CHOP channels 442 | virtual int32_t 443 | getNumInfoCHOPChans(void *reserved1) 444 | { 445 | return 0; 446 | } 447 | 448 | // Specify the name and value for CHOP 'index', 449 | // by assigning something to 'name' and 'value' members of the 450 | // OP_InfoCHOPChan class pointer that is passed (it points 451 | // to a valid instance of the class already. 452 | // the 'name' pointer will initially point to nullptr 453 | // you must allocate memory or assign a constant string 454 | // to it. 455 | virtual void 456 | getInfoCHOPChan(int32_t index, OP_InfoCHOPChan* chan, void *reserved1) 457 | { 458 | } 459 | 460 | 461 | // Return false if you arn't returning data for an Info DAT 462 | // Return true if you are. 463 | // Set the members of the CHOP_InfoDATSize class to specify 464 | // the dimensions of the Info DAT 465 | virtual bool 466 | getInfoDATSize(OP_InfoDATSize* infoSize, void* reserved1) 467 | { 468 | return false; 469 | } 470 | 471 | 472 | // You are asked to assign values to the Info DAT 1 row or column at a time 473 | // The 'byColumn' variable in 'getInfoDATSize' is how you specify 474 | // if it is by column or by row. 475 | // 'index' is the row/column index 476 | // 'nEntries' is the number of entries in the row/column 477 | // Strings should be UTF-8 encoded. 478 | virtual void 479 | getInfoDATEntries(int32_t index, int32_t nEntries, 480 | OP_InfoDATEntries* entries, void* reserved1) 481 | { 482 | } 483 | 484 | 485 | // You can use this function to put the node into a warning state 486 | // with the returned string as the message. 487 | virtual void 488 | getWarningString(OP_String *warning, void *reserved1) 489 | { 490 | } 491 | 492 | // You can use this function to put the node into a error state 493 | // with the returned string as the message. 494 | virtual void 495 | getErrorString(OP_String *error, void *reserved1) 496 | { 497 | } 498 | 499 | // Use this function to return some text that will show up in the 500 | // info popup (when you middle click on a node) 501 | virtual void 502 | getInfoPopupString(OP_String *info, void *reserved1) 503 | { 504 | } 505 | 506 | 507 | // Override these methods if you want to define specfic parameters 508 | virtual void 509 | setupParameters(OP_ParameterManager* manager, void* reserved1) 510 | { 511 | } 512 | 513 | 514 | // This is called whenever a pulse parameter is pressed 515 | virtual void 516 | pulsePressed(const char* name, void* reserved1) 517 | { 518 | } 519 | 520 | // This is called whenever a dynamic menu type custom parameter needs to have it's content's 521 | // updated. It may happen often, so this could should be efficient. 522 | virtual void 523 | buildDynamicMenu(const OP_Inputs* inputs, OP_BuildDynamicMenuInfo* info, void* reserved1) 524 | { 525 | } 526 | 527 | // END PUBLIC INTERFACE 528 | 529 | 530 | private: 531 | 532 | // Reserved for future features 533 | virtual int32_t reservedFunc6() { return 0; } 534 | virtual int32_t reservedFunc7() { return 0; } 535 | virtual int32_t reservedFunc8() { return 0; } 536 | virtual int32_t reservedFunc9() { return 0; } 537 | virtual int32_t reservedFunc10() { return 0; } 538 | virtual int32_t reservedFunc11() { return 0; } 539 | virtual int32_t reservedFunc12() { return 0; } 540 | virtual int32_t reservedFunc13() { return 0; } 541 | virtual int32_t reservedFunc14() { return 0; } 542 | virtual int32_t reservedFunc15() { return 0; } 543 | virtual int32_t reservedFunc16() { return 0; } 544 | virtual int32_t reservedFunc17() { return 0; } 545 | virtual int32_t reservedFunc18() { return 0; } 546 | virtual int32_t reservedFunc19() { return 0; } 547 | virtual int32_t reservedFunc20() { return 0; } 548 | 549 | int32_t reserved[400]; 550 | 551 | }; 552 | 553 | #pragma pack(pop) 554 | 555 | static_assert(offsetof(SOP_PluginInfo, apiVersion) == 0, "Incorrect Alignment"); 556 | static_assert(offsetof(SOP_PluginInfo, customOPInfo) == 408, "Incorrect Alignment"); 557 | static_assert(sizeof(SOP_PluginInfo) == 944, "Incorrect Size"); 558 | 559 | static_assert(offsetof(SOP_GeneralInfo, cookEveryFrame) == 0, "Incorrect Alignment"); 560 | static_assert(offsetof(SOP_GeneralInfo, cookEveryFrameIfAsked) == 1, "Incorrect Alignment"); 561 | static_assert(offsetof(SOP_GeneralInfo, directToGPU) == 2, "Incorrect Alignment"); 562 | static_assert(offsetof(SOP_GeneralInfo, winding) == 4, "Incorrect Alignment"); 563 | static_assert(sizeof(SOP_GeneralInfo) == 84, "Incorrect Size"); 564 | 565 | }; // namespace TD 566 | 567 | #endif 568 | -------------------------------------------------------------------------------- /Touch Plugin/Sample Project/Plugins/PonkOutput.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madmappersoftware/Ponk/ebfa51cab92b8b1a660f2024b6f8e2ed4d1d58d7/Touch Plugin/Sample Project/Plugins/PonkOutput.dll -------------------------------------------------------------------------------- /Touch Plugin/Sample Project/Plugins/PonkOutput.plugin-notarized.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madmappersoftware/Ponk/ebfa51cab92b8b1a660f2024b6f8e2ed4d1d58d7/Touch Plugin/Sample Project/Plugins/PonkOutput.plugin-notarized.zip -------------------------------------------------------------------------------- /Touch Plugin/Sample Project/Plugins/PonkOutput.plugin/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 21G217 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | PonkOutput 11 | CFBundleIdentifier 12 | com.Tyrell.PonkOutput 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | PonkOutput 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 13F100 31 | DTPlatformName 32 | macosx 33 | DTPlatformVersion 34 | 12.3 35 | DTSDKBuild 36 | 21E226 37 | DTSDKName 38 | macosx12.3 39 | DTXcode 40 | 1341 41 | DTXcodeBuild 42 | 13F100 43 | LSMinimumSystemVersion 44 | 10.13 45 | 46 | 47 | -------------------------------------------------------------------------------- /Touch Plugin/Sample Project/Plugins/PonkOutput.plugin/Contents/MacOS/PonkOutput: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madmappersoftware/Ponk/ebfa51cab92b8b1a660f2024b6f8e2ed4d1d58d7/Touch Plugin/Sample Project/Plugins/PonkOutput.plugin/Contents/MacOS/PonkOutput -------------------------------------------------------------------------------- /Touch Plugin/Sample Project/Plugins/PonkOutput.plugin/Contents/_CodeSignature/CodeResources: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | files 6 | 7 | files2 8 | 9 | rules 10 | 11 | ^Resources/ 12 | 13 | ^Resources/.*\.lproj/ 14 | 15 | optional 16 | 17 | weight 18 | 1000 19 | 20 | ^Resources/.*\.lproj/locversion.plist$ 21 | 22 | omit 23 | 24 | weight 25 | 1100 26 | 27 | ^Resources/Base\.lproj/ 28 | 29 | weight 30 | 1010 31 | 32 | ^version.plist$ 33 | 34 | 35 | rules2 36 | 37 | .*\.dSYM($|/) 38 | 39 | weight 40 | 11 41 | 42 | ^(.*/)?\.DS_Store$ 43 | 44 | omit 45 | 46 | weight 47 | 2000 48 | 49 | ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ 50 | 51 | nested 52 | 53 | weight 54 | 10 55 | 56 | ^.* 57 | 58 | ^Info\.plist$ 59 | 60 | omit 61 | 62 | weight 63 | 20 64 | 65 | ^PkgInfo$ 66 | 67 | omit 68 | 69 | weight 70 | 20 71 | 72 | ^Resources/ 73 | 74 | weight 75 | 20 76 | 77 | ^Resources/.*\.lproj/ 78 | 79 | optional 80 | 81 | weight 82 | 1000 83 | 84 | ^Resources/.*\.lproj/locversion.plist$ 85 | 86 | omit 87 | 88 | weight 89 | 1100 90 | 91 | ^Resources/Base\.lproj/ 92 | 93 | weight 94 | 1010 95 | 96 | ^[^/]+$ 97 | 98 | nested 99 | 100 | weight 101 | 10 102 | 103 | ^embedded\.provisionprofile$ 104 | 105 | weight 106 | 20 107 | 108 | ^version\.plist$ 109 | 110 | weight 111 | 20 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Touch Plugin/Sample Project/PonkDemo.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madmappersoftware/Ponk/ebfa51cab92b8b1a660f2024b6f8e2ed4d1d58d7/Touch Plugin/Sample Project/PonkDemo.toe -------------------------------------------------------------------------------- /Touch Plugin/Sample Project/PonkDemoMultiOut.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madmappersoftware/Ponk/ebfa51cab92b8b1a660f2024b6f8e2ed4d1d58d7/Touch Plugin/Sample Project/PonkDemoMultiOut.toe -------------------------------------------------------------------------------- /Touch Plugin/Sample Project/PonkDemoTexturedPaths.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madmappersoftware/Ponk/ebfa51cab92b8b1a660f2024b6f8e2ed4d1d58d7/Touch Plugin/Sample Project/PonkDemoTexturedPaths.toe -------------------------------------------------------------------------------- /Touch Plugin/Sample Project/ponk_output.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madmappersoftware/Ponk/ebfa51cab92b8b1a660f2024b6f8e2ed4d1d58d7/Touch Plugin/Sample Project/ponk_output.tox -------------------------------------------------------------------------------- /Touch Plugin/matrix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CPlusPlus_Common.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | template class Matrix44 12 | { 13 | public: 14 | 15 | //------------------- 16 | // Access to elements 17 | //------------------- 18 | 19 | T x[4][4]; 20 | 21 | T * operator [] (int i); 22 | const T * operator [] (int i) const; 23 | 24 | Matrix44(); 25 | // 1 0 0 0 26 | // 0 1 0 0 27 | // 0 0 1 0 28 | // 0 0 0 1 29 | 30 | Matrix44(T a); 31 | // a a a a 32 | // a a a a 33 | // a a a a 34 | // a a a a 35 | 36 | Matrix44(const T a[4][4]); 37 | // a[0][0] a[0][1] a[0][2] a[0][3] 38 | // a[1][0] a[1][1] a[1][2] a[1][3] 39 | // a[2][0] a[2][1] a[2][2] a[2][3] 40 | // a[3][0] a[3][1] a[3][2] a[3][3] 41 | 42 | Matrix44(T a, T b, T c, T d, T e, T f, T g, T h, 43 | T i, T j, T k, T l, T m, T n, T o, T p); 44 | // a b c d 45 | // e f g h 46 | // i j k l 47 | // m n o p 48 | 49 | //-------------------------------- 50 | // Copy constructor and assignment 51 | //-------------------------------- 52 | 53 | Matrix44(const Matrix44 &v); 54 | template explicit Matrix44(const Matrix44 &v); 55 | 56 | const Matrix44 & operator = (const Matrix44 &v); 57 | const Matrix44 & operator = (T a); 58 | 59 | //--------- 60 | // Identity 61 | //--------- 62 | 63 | /*void makeIdentity();*/ 64 | 65 | 66 | //--------- 67 | // Equality 68 | //--------- 69 | 70 | bool operator == (const Matrix44 &v) const; 71 | bool operator != (const Matrix44 &v) const; 72 | 73 | //------------------------ 74 | // Component-wise addition 75 | //------------------------ 76 | 77 | const Matrix44 & operator += (const Matrix44 &v); 78 | const Matrix44 & operator += (T a); 79 | Matrix44 operator + (const Matrix44 &v) const; 80 | 81 | 82 | //--------------------------- 83 | // Component-wise subtraction 84 | //--------------------------- 85 | 86 | const Matrix44 & operator -= (const Matrix44 &v); 87 | const Matrix44 & operator -= (T a); 88 | Matrix44 operator - (const Matrix44 &v) const; 89 | 90 | 91 | //------------------------------------ 92 | // Component-wise multiplication by -1 93 | //------------------------------------ 94 | 95 | Matrix44 operator - () const; 96 | const Matrix44 & negate(); 97 | 98 | 99 | //------------------------------ 100 | // Component-wise multiplication 101 | //------------------------------ 102 | 103 | const Matrix44 & operator *= (T a); 104 | Matrix44 operator * (T a) const; 105 | 106 | 107 | //----------------------------------- 108 | // Matrix-times-matrix multiplication 109 | //----------------------------------- 110 | 111 | const Matrix44 & operator *= (const Matrix44 &v); 112 | Matrix44 operator * (const Matrix44 &v) const; 113 | TD::Position operator * (const TD::Position &v) const; 114 | 115 | static void multiply(const Matrix44 &a, // assumes that 116 | const Matrix44 &b, // &a != &c and 117 | Matrix44 &c); // &b != &c. 118 | 119 | //----------------------------------- 120 | // Position-times-matrix multiplication 121 | //----------------------------------- 122 | 123 | void multPositionMatrix(const TD::Position &src, TD::Position &dst) const; 124 | 125 | //------------------------------------------------------------ 126 | // Inverse matrix: If singExc is false, inverting a singular 127 | // matrix produces an identity matrix. If singExc is true, 128 | // inverting a singular matrix throws a SingMatrixExc. 129 | // 130 | // inverse() and invert() invert matrices using determinants; 131 | // gjInverse() and gjInvert() use the Gauss-Jordan method. 132 | // 133 | // inverse() and invert() are significantly faster than 134 | // gjInverse() and gjInvert(), but the results may be slightly 135 | // less accurate. 136 | // 137 | //------------------------------------------------------------ 138 | 139 | const Matrix44 & invert(bool singExc = false); 140 | 141 | Matrix44 inverse(bool singExc = false) const; 142 | 143 | const Matrix44 & gjInvert(bool singExc = false); 144 | 145 | Matrix44 gjInverse(bool singExc = false) const; 146 | }; 147 | 148 | //-------------- 149 | // Stream output 150 | //-------------- 151 | template 152 | std::ostream & operator << (std::ostream & s, const Matrix44 &m); 153 | 154 | template 155 | inline T 156 | abs(T a); 157 | 158 | 159 | template 160 | inline T * 161 | Matrix44::operator [] (int i) 162 | { 163 | return x[i]; 164 | } 165 | 166 | template 167 | inline const T * 168 | Matrix44::operator [] (int i) const 169 | { 170 | return x[i]; 171 | } 172 | 173 | template 174 | inline 175 | Matrix44::Matrix44() 176 | { 177 | memset(x, 0, sizeof(x)); 178 | x[0][0] = 1; 179 | x[1][1] = 1; 180 | x[2][2] = 1; 181 | x[3][3] = 1; 182 | } 183 | 184 | template 185 | inline 186 | Matrix44::Matrix44(T a) 187 | { 188 | x[0][0] = a; 189 | x[0][1] = a; 190 | x[0][2] = a; 191 | x[0][3] = a; 192 | x[1][0] = a; 193 | x[1][1] = a; 194 | x[1][2] = a; 195 | x[1][3] = a; 196 | x[2][0] = a; 197 | x[2][1] = a; 198 | x[2][2] = a; 199 | x[2][3] = a; 200 | x[3][0] = a; 201 | x[3][1] = a; 202 | x[3][2] = a; 203 | x[3][3] = a; 204 | } 205 | 206 | template 207 | inline 208 | Matrix44::Matrix44(const T a[4][4]) 209 | { 210 | memcpy(x, a, sizeof(x)); 211 | } 212 | 213 | template 214 | inline 215 | Matrix44::Matrix44(T a, T b, T c, T d, T e, T f, T g, T h, 216 | T i, T j, T k, T l, T m, T n, T o, T p) 217 | { 218 | x[0][0] = a; 219 | x[0][1] = b; 220 | x[0][2] = c; 221 | x[0][3] = d; 222 | x[1][0] = e; 223 | x[1][1] = f; 224 | x[1][2] = g; 225 | x[1][3] = h; 226 | x[2][0] = i; 227 | x[2][1] = j; 228 | x[2][2] = k; 229 | x[2][3] = l; 230 | x[3][0] = m; 231 | x[3][1] = n; 232 | x[3][2] = o; 233 | x[3][3] = p; 234 | } 235 | 236 | template 237 | inline 238 | Matrix44::Matrix44(const Matrix44 &v) 239 | { 240 | x[0][0] = v.x[0][0]; 241 | x[0][1] = v.x[0][1]; 242 | x[0][2] = v.x[0][2]; 243 | x[0][3] = v.x[0][3]; 244 | x[1][0] = v.x[1][0]; 245 | x[1][1] = v.x[1][1]; 246 | x[1][2] = v.x[1][2]; 247 | x[1][3] = v.x[1][3]; 248 | x[2][0] = v.x[2][0]; 249 | x[2][1] = v.x[2][1]; 250 | x[2][2] = v.x[2][2]; 251 | x[2][3] = v.x[2][3]; 252 | x[3][0] = v.x[3][0]; 253 | x[3][1] = v.x[3][1]; 254 | x[3][2] = v.x[3][2]; 255 | x[3][3] = v.x[3][3]; 256 | } 257 | 258 | template 259 | template 260 | inline 261 | Matrix44::Matrix44(const Matrix44 &v) 262 | { 263 | x[0][0] = T(v.x[0][0]); 264 | x[0][1] = T(v.x[0][1]); 265 | x[0][2] = T(v.x[0][2]); 266 | x[0][3] = T(v.x[0][3]); 267 | x[1][0] = T(v.x[1][0]); 268 | x[1][1] = T(v.x[1][1]); 269 | x[1][2] = T(v.x[1][2]); 270 | x[1][3] = T(v.x[1][3]); 271 | x[2][0] = T(v.x[2][0]); 272 | x[2][1] = T(v.x[2][1]); 273 | x[2][2] = T(v.x[2][2]); 274 | x[2][3] = T(v.x[2][3]); 275 | x[3][0] = T(v.x[3][0]); 276 | x[3][1] = T(v.x[3][1]); 277 | x[3][2] = T(v.x[3][2]); 278 | x[3][3] = T(v.x[3][3]); 279 | } 280 | 281 | template 282 | inline const Matrix44 & 283 | Matrix44::operator = (const Matrix44 &v) 284 | { 285 | x[0][0] = v.x[0][0]; 286 | x[0][1] = v.x[0][1]; 287 | x[0][2] = v.x[0][2]; 288 | x[0][3] = v.x[0][3]; 289 | x[1][0] = v.x[1][0]; 290 | x[1][1] = v.x[1][1]; 291 | x[1][2] = v.x[1][2]; 292 | x[1][3] = v.x[1][3]; 293 | x[2][0] = v.x[2][0]; 294 | x[2][1] = v.x[2][1]; 295 | x[2][2] = v.x[2][2]; 296 | x[2][3] = v.x[2][3]; 297 | x[3][0] = v.x[3][0]; 298 | x[3][1] = v.x[3][1]; 299 | x[3][2] = v.x[3][2]; 300 | x[3][3] = v.x[3][3]; 301 | return *this; 302 | } 303 | 304 | template 305 | inline const Matrix44 & 306 | Matrix44::operator = (T a) 307 | { 308 | x[0][0] = a; 309 | x[0][1] = a; 310 | x[0][2] = a; 311 | x[0][3] = a; 312 | x[1][0] = a; 313 | x[1][1] = a; 314 | x[1][2] = a; 315 | x[1][3] = a; 316 | x[2][0] = a; 317 | x[2][1] = a; 318 | x[2][2] = a; 319 | x[2][3] = a; 320 | x[3][0] = a; 321 | x[3][1] = a; 322 | x[3][2] = a; 323 | x[3][3] = a; 324 | return *this; 325 | } 326 | 327 | template 328 | bool 329 | Matrix44::operator == (const Matrix44 &v) const 330 | { 331 | return x[0][0] == v.x[0][0] && 332 | x[0][1] == v.x[0][1] && 333 | x[0][2] == v.x[0][2] && 334 | x[0][3] == v.x[0][3] && 335 | x[1][0] == v.x[1][0] && 336 | x[1][1] == v.x[1][1] && 337 | x[1][2] == v.x[1][2] && 338 | x[1][3] == v.x[1][3] && 339 | x[2][0] == v.x[2][0] && 340 | x[2][1] == v.x[2][1] && 341 | x[2][2] == v.x[2][2] && 342 | x[2][3] == v.x[2][3] && 343 | x[3][0] == v.x[3][0] && 344 | x[3][1] == v.x[3][1] && 345 | x[3][2] == v.x[3][2] && 346 | x[3][3] == v.x[3][3]; 347 | } 348 | 349 | template 350 | bool 351 | Matrix44::operator != (const Matrix44 &v) const 352 | { 353 | return x[0][0] != v.x[0][0] || 354 | x[0][1] != v.x[0][1] || 355 | x[0][2] != v.x[0][2] || 356 | x[0][3] != v.x[0][3] || 357 | x[1][0] != v.x[1][0] || 358 | x[1][1] != v.x[1][1] || 359 | x[1][2] != v.x[1][2] || 360 | x[1][3] != v.x[1][3] || 361 | x[2][0] != v.x[2][0] || 362 | x[2][1] != v.x[2][1] || 363 | x[2][2] != v.x[2][2] || 364 | x[2][3] != v.x[2][3] || 365 | x[3][0] != v.x[3][0] || 366 | x[3][1] != v.x[3][1] || 367 | x[3][2] != v.x[3][2] || 368 | x[3][3] != v.x[3][3]; 369 | } 370 | 371 | template 372 | const Matrix44 & 373 | Matrix44::operator += (const Matrix44 &v) 374 | { 375 | x[0][0] += v.x[0][0]; 376 | x[0][1] += v.x[0][1]; 377 | x[0][2] += v.x[0][2]; 378 | x[0][3] += v.x[0][3]; 379 | x[1][0] += v.x[1][0]; 380 | x[1][1] += v.x[1][1]; 381 | x[1][2] += v.x[1][2]; 382 | x[1][3] += v.x[1][3]; 383 | x[2][0] += v.x[2][0]; 384 | x[2][1] += v.x[2][1]; 385 | x[2][2] += v.x[2][2]; 386 | x[2][3] += v.x[2][3]; 387 | x[3][0] += v.x[3][0]; 388 | x[3][1] += v.x[3][1]; 389 | x[3][2] += v.x[3][2]; 390 | x[3][3] += v.x[3][3]; 391 | 392 | return *this; 393 | } 394 | 395 | template 396 | const Matrix44 & 397 | Matrix44::operator += (T a) 398 | { 399 | x[0][0] += a; 400 | x[0][1] += a; 401 | x[0][2] += a; 402 | x[0][3] += a; 403 | x[1][0] += a; 404 | x[1][1] += a; 405 | x[1][2] += a; 406 | x[1][3] += a; 407 | x[2][0] += a; 408 | x[2][1] += a; 409 | x[2][2] += a; 410 | x[2][3] += a; 411 | x[3][0] += a; 412 | x[3][1] += a; 413 | x[3][2] += a; 414 | x[3][3] += a; 415 | 416 | return *this; 417 | } 418 | 419 | template 420 | Matrix44 421 | Matrix44::operator + (const Matrix44 &v) const 422 | { 423 | return Matrix44(x[0][0] + v.x[0][0], 424 | x[0][1] + v.x[0][1], 425 | x[0][2] + v.x[0][2], 426 | x[0][3] + v.x[0][3], 427 | x[1][0] + v.x[1][0], 428 | x[1][1] + v.x[1][1], 429 | x[1][2] + v.x[1][2], 430 | x[1][3] + v.x[1][3], 431 | x[2][0] + v.x[2][0], 432 | x[2][1] + v.x[2][1], 433 | x[2][2] + v.x[2][2], 434 | x[2][3] + v.x[2][3], 435 | x[3][0] + v.x[3][0], 436 | x[3][1] + v.x[3][1], 437 | x[3][2] + v.x[3][2], 438 | x[3][3] + v.x[3][3]); 439 | } 440 | 441 | template 442 | const Matrix44 & 443 | Matrix44::operator -= (const Matrix44 &v) 444 | { 445 | x[0][0] -= v.x[0][0]; 446 | x[0][1] -= v.x[0][1]; 447 | x[0][2] -= v.x[0][2]; 448 | x[0][3] -= v.x[0][3]; 449 | x[1][0] -= v.x[1][0]; 450 | x[1][1] -= v.x[1][1]; 451 | x[1][2] -= v.x[1][2]; 452 | x[1][3] -= v.x[1][3]; 453 | x[2][0] -= v.x[2][0]; 454 | x[2][1] -= v.x[2][1]; 455 | x[2][2] -= v.x[2][2]; 456 | x[2][3] -= v.x[2][3]; 457 | x[3][0] -= v.x[3][0]; 458 | x[3][1] -= v.x[3][1]; 459 | x[3][2] -= v.x[3][2]; 460 | x[3][3] -= v.x[3][3]; 461 | 462 | return *this; 463 | } 464 | 465 | template 466 | const Matrix44 & 467 | Matrix44::operator -= (T a) 468 | { 469 | x[0][0] -= a; 470 | x[0][1] -= a; 471 | x[0][2] -= a; 472 | x[0][3] -= a; 473 | x[1][0] -= a; 474 | x[1][1] -= a; 475 | x[1][2] -= a; 476 | x[1][3] -= a; 477 | x[2][0] -= a; 478 | x[2][1] -= a; 479 | x[2][2] -= a; 480 | x[2][3] -= a; 481 | x[3][0] -= a; 482 | x[3][1] -= a; 483 | x[3][2] -= a; 484 | x[3][3] -= a; 485 | 486 | return *this; 487 | } 488 | 489 | template 490 | Matrix44 491 | Matrix44::operator - (const Matrix44 &v) const 492 | { 493 | return Matrix44(x[0][0] - v.x[0][0], 494 | x[0][1] - v.x[0][1], 495 | x[0][2] - v.x[0][2], 496 | x[0][3] - v.x[0][3], 497 | x[1][0] - v.x[1][0], 498 | x[1][1] - v.x[1][1], 499 | x[1][2] - v.x[1][2], 500 | x[1][3] - v.x[1][3], 501 | x[2][0] - v.x[2][0], 502 | x[2][1] - v.x[2][1], 503 | x[2][2] - v.x[2][2], 504 | x[2][3] - v.x[2][3], 505 | x[3][0] - v.x[3][0], 506 | x[3][1] - v.x[3][1], 507 | x[3][2] - v.x[3][2], 508 | x[3][3] - v.x[3][3]); 509 | } 510 | 511 | template 512 | Matrix44 513 | Matrix44::operator - () const 514 | { 515 | return Matrix44(-x[0][0], 516 | -x[0][1], 517 | -x[0][2], 518 | -x[0][3], 519 | -x[1][0], 520 | -x[1][1], 521 | -x[1][2], 522 | -x[1][3], 523 | -x[2][0], 524 | -x[2][1], 525 | -x[2][2], 526 | -x[2][3], 527 | -x[3][0], 528 | -x[3][1], 529 | -x[3][2], 530 | -x[3][3]); 531 | } 532 | 533 | template 534 | const Matrix44 & 535 | Matrix44::negate() 536 | { 537 | x[0][0] = -x[0][0]; 538 | x[0][1] = -x[0][1]; 539 | x[0][2] = -x[0][2]; 540 | x[0][3] = -x[0][3]; 541 | x[1][0] = -x[1][0]; 542 | x[1][1] = -x[1][1]; 543 | x[1][2] = -x[1][2]; 544 | x[1][3] = -x[1][3]; 545 | x[2][0] = -x[2][0]; 546 | x[2][1] = -x[2][1]; 547 | x[2][2] = -x[2][2]; 548 | x[2][3] = -x[2][3]; 549 | x[3][0] = -x[3][0]; 550 | x[3][1] = -x[3][1]; 551 | x[3][2] = -x[3][2]; 552 | x[3][3] = -x[3][3]; 553 | 554 | return *this; 555 | } 556 | 557 | template 558 | const Matrix44 & 559 | Matrix44::operator *= (T a) 560 | { 561 | x[0][0] *= a; 562 | x[0][1] *= a; 563 | x[0][2] *= a; 564 | x[0][3] *= a; 565 | x[1][0] *= a; 566 | x[1][1] *= a; 567 | x[1][2] *= a; 568 | x[1][3] *= a; 569 | x[2][0] *= a; 570 | x[2][1] *= a; 571 | x[2][2] *= a; 572 | x[2][3] *= a; 573 | x[3][0] *= a; 574 | x[3][1] *= a; 575 | x[3][2] *= a; 576 | x[3][3] *= a; 577 | 578 | return *this; 579 | } 580 | 581 | template 582 | Matrix44 583 | Matrix44::operator * (T a) const 584 | { 585 | return Matrix44(x[0][0] * a, 586 | x[0][1] * a, 587 | x[0][2] * a, 588 | x[0][3] * a, 589 | x[1][0] * a, 590 | x[1][1] * a, 591 | x[1][2] * a, 592 | x[1][3] * a, 593 | x[2][0] * a, 594 | x[2][1] * a, 595 | x[2][2] * a, 596 | x[2][3] * a, 597 | x[3][0] * a, 598 | x[3][1] * a, 599 | x[3][2] * a, 600 | x[3][3] * a); 601 | } 602 | 603 | template 604 | inline Matrix44 605 | operator * (T a, const Matrix44 &v) 606 | { 607 | return v * a; 608 | } 609 | 610 | template 611 | inline const Matrix44 & 612 | Matrix44::operator *= (const Matrix44 &v) 613 | { 614 | Matrix44 tmp(T(0)); 615 | 616 | multiply(*this, v, tmp); 617 | *this = tmp; 618 | return *this; 619 | } 620 | 621 | template 622 | inline TD::Position 623 | Matrix44::operator * (const TD::Position &v) const 624 | { 625 | TD::Position tmp; 626 | 627 | multPositionMatrix(v, tmp); 628 | return tmp; 629 | } 630 | 631 | template 632 | inline Matrix44 633 | Matrix44::operator * (const Matrix44 &v) const 634 | { 635 | Matrix44 tmp(T(0)); 636 | 637 | multiply(*this, v, tmp); 638 | return tmp; 639 | } 640 | 641 | template 642 | void 643 | Matrix44::multiply(const Matrix44 &a, 644 | const Matrix44 &b, 645 | Matrix44 &c) 646 | { 647 | const T *ap = &a.x[0][0]; 648 | const T *bp = &b.x[0][0]; 649 | T *cp = &c.x[0][0]; 650 | 651 | T a0, a1, a2, a3; 652 | 653 | a0 = ap[0]; 654 | a1 = ap[1]; 655 | a2 = ap[2]; 656 | a3 = ap[3]; 657 | 658 | cp[0] = a0 * bp[0] + a1 * bp[4] + a2 * bp[8] + a3 * bp[12]; 659 | cp[1] = a0 * bp[1] + a1 * bp[5] + a2 * bp[9] + a3 * bp[13]; 660 | cp[2] = a0 * bp[2] + a1 * bp[6] + a2 * bp[10] + a3 * bp[14]; 661 | cp[3] = a0 * bp[3] + a1 * bp[7] + a2 * bp[11] + a3 * bp[15]; 662 | 663 | a0 = ap[4]; 664 | a1 = ap[5]; 665 | a2 = ap[6]; 666 | a3 = ap[7]; 667 | 668 | cp[4] = a0 * bp[0] + a1 * bp[4] + a2 * bp[8] + a3 * bp[12]; 669 | cp[5] = a0 * bp[1] + a1 * bp[5] + a2 * bp[9] + a3 * bp[13]; 670 | cp[6] = a0 * bp[2] + a1 * bp[6] + a2 * bp[10] + a3 * bp[14]; 671 | cp[7] = a0 * bp[3] + a1 * bp[7] + a2 * bp[11] + a3 * bp[15]; 672 | 673 | a0 = ap[8]; 674 | a1 = ap[9]; 675 | a2 = ap[10]; 676 | a3 = ap[11]; 677 | 678 | cp[8] = a0 * bp[0] + a1 * bp[4] + a2 * bp[8] + a3 * bp[12]; 679 | cp[9] = a0 * bp[1] + a1 * bp[5] + a2 * bp[9] + a3 * bp[13]; 680 | cp[10] = a0 * bp[2] + a1 * bp[6] + a2 * bp[10] + a3 * bp[14]; 681 | cp[11] = a0 * bp[3] + a1 * bp[7] + a2 * bp[11] + a3 * bp[15]; 682 | 683 | a0 = ap[12]; 684 | a1 = ap[13]; 685 | a2 = ap[14]; 686 | a3 = ap[15]; 687 | 688 | cp[12] = a0 * bp[0] + a1 * bp[4] + a2 * bp[8] + a3 * bp[12]; 689 | cp[13] = a0 * bp[1] + a1 * bp[5] + a2 * bp[9] + a3 * bp[13]; 690 | cp[14] = a0 * bp[2] + a1 * bp[6] + a2 * bp[10] + a3 * bp[14]; 691 | cp[15] = a0 * bp[3] + a1 * bp[7] + a2 * bp[11] + a3 * bp[15]; 692 | } 693 | 694 | template 695 | void 696 | Matrix44::multPositionMatrix(const TD::Position &src, TD::Position &dst) const 697 | { 698 | T a, b, c, w; 699 | 700 | a = src.x * x[0][0] + src.y * x[0][1] + src.z * x[0][2] + x[0][3]; 701 | b = src.x * x[1][0] + src.y * x[1][1] + src.z * x[1][2] + x[1][3]; 702 | c = src.x * x[2][0] + src.y * x[2][1] + src.z * x[2][2] + x[2][3]; 703 | w = src.x * x[3][0] + src.y * x[3][1] + src.z * x[3][2] + x[3][3]; 704 | 705 | dst.x = static_cast(a / w); 706 | dst.y = static_cast(b / w); 707 | dst.z = static_cast(c / w); 708 | } 709 | 710 | //-------------------------------- 711 | // Implementation of stream output 712 | //-------------------------------- 713 | 714 | template 715 | std::ostream & 716 | operator << (std::ostream &s, const Matrix44 &m) 717 | { 718 | std::ios_base::fmtflags oldFlags = s.flags(); 719 | int width; 720 | 721 | if (s.flags() & std::ios_base::fixed) 722 | { 723 | s.setf(std::ios_base::showpoint); 724 | width = static_cast(s.precision()) + 5; 725 | } 726 | else 727 | { 728 | s.setf(std::ios_base::scientific); 729 | s.setf(std::ios_base::showpoint); 730 | width = static_cast(s.precision()) + 8; 731 | } 732 | 733 | s << "(" << std::setw(width) << m[0][0] << 734 | " " << std::setw(width) << m[0][1] << 735 | " " << std::setw(width) << m[0][2] << 736 | " " << std::setw(width) << m[0][3] << "\n" << 737 | 738 | " " << std::setw(width) << m[1][0] << 739 | " " << std::setw(width) << m[1][1] << 740 | " " << std::setw(width) << m[1][2] << 741 | " " << std::setw(width) << m[1][3] << "\n" << 742 | 743 | " " << std::setw(width) << m[2][0] << 744 | " " << std::setw(width) << m[2][1] << 745 | " " << std::setw(width) << m[2][2] << 746 | " " << std::setw(width) << m[2][3] << "\n" << 747 | 748 | " " << std::setw(width) << m[3][0] << 749 | " " << std::setw(width) << m[3][1] << 750 | " " << std::setw(width) << m[3][2] << 751 | " " << std::setw(width) << m[3][3] << ")\n"; 752 | 753 | s.flags(oldFlags); 754 | return s; 755 | } 756 | 757 | template 758 | const Matrix44 & 759 | Matrix44::gjInvert(bool singExc) 760 | { 761 | *this = gjInverse(singExc); 762 | return *this; 763 | } 764 | 765 | template 766 | Matrix44 767 | Matrix44::gjInverse(bool singExc) const 768 | { 769 | int i, j, k; 770 | Matrix44 s; 771 | Matrix44 t(*this); 772 | 773 | // Forward elimination 774 | 775 | for (i = 0; i < 3; i++) 776 | { 777 | int pivot = i; 778 | 779 | T pivotsize = t[i][i]; 780 | 781 | if (pivotsize < 0) 782 | pivotsize = -pivotsize; 783 | 784 | for (j = i + 1; j < 4; j++) 785 | { 786 | T tmp = t[j][i]; 787 | 788 | if (tmp < 0) 789 | tmp = -tmp; 790 | 791 | if (tmp > pivotsize) 792 | { 793 | pivot = j; 794 | pivotsize = tmp; 795 | } 796 | } 797 | 798 | if (pivotsize == 0) 799 | { 800 | if (singExc) 801 | throw std::runtime_error("Cannot invert singular matrix."); 802 | 803 | return Matrix44(); 804 | } 805 | 806 | if (pivot != i) 807 | { 808 | for (j = 0; j < 4; j++) 809 | { 810 | T tmp; 811 | 812 | tmp = t[i][j]; 813 | t[i][j] = t[pivot][j]; 814 | t[pivot][j] = tmp; 815 | 816 | tmp = s[i][j]; 817 | s[i][j] = s[pivot][j]; 818 | s[pivot][j] = tmp; 819 | } 820 | } 821 | 822 | for (j = i + 1; j < 4; j++) 823 | { 824 | T f = t[j][i] / t[i][i]; 825 | 826 | for (k = 0; k < 4; k++) 827 | { 828 | t[j][k] -= f * t[i][k]; 829 | s[j][k] -= f * s[i][k]; 830 | } 831 | } 832 | } 833 | 834 | // Backward substitution 835 | 836 | for (i = 3; i >= 0; --i) 837 | { 838 | T f; 839 | 840 | if ((f = t[i][i]) == 0) 841 | { 842 | if (singExc) 843 | throw std::runtime_error("Cannot invert singular matrix."); 844 | 845 | return Matrix44(); 846 | } 847 | 848 | for (j = 0; j < 4; j++) 849 | { 850 | t[i][j] /= f; 851 | s[i][j] /= f; 852 | } 853 | 854 | for (j = 0; j < i; j++) 855 | { 856 | f = t[j][i]; 857 | 858 | for (k = 0; k < 4; k++) 859 | { 860 | t[j][k] -= f * t[i][k]; 861 | s[j][k] -= f * s[i][k]; 862 | } 863 | } 864 | } 865 | 866 | return s; 867 | } 868 | 869 | template 870 | const Matrix44 & 871 | Matrix44::invert(bool singExc) 872 | { 873 | *this = inverse(singExc); 874 | return *this; 875 | } 876 | 877 | template 878 | Matrix44 879 | Matrix44::inverse(bool singExc) const 880 | { 881 | if (x[0][3] != 0 || x[1][3] != 0 || x[2][3] != 0 || x[3][3] != 1) 882 | return gjInverse(singExc); 883 | 884 | Matrix44 s(x[1][1] * x[2][2] - x[2][1] * x[1][2], 885 | x[2][1] * x[0][2] - x[0][1] * x[2][2], 886 | x[0][1] * x[1][2] - x[1][1] * x[0][2], 887 | 0, 888 | 889 | x[2][0] * x[1][2] - x[1][0] * x[2][2], 890 | x[0][0] * x[2][2] - x[2][0] * x[0][2], 891 | x[1][0] * x[0][2] - x[0][0] * x[1][2], 892 | 0, 893 | 894 | x[1][0] * x[2][1] - x[2][0] * x[1][1], 895 | x[2][0] * x[0][1] - x[0][0] * x[2][1], 896 | x[0][0] * x[1][1] - x[1][0] * x[0][1], 897 | 0, 898 | 899 | 0, 900 | 0, 901 | 0, 902 | 1); 903 | 904 | T r = x[0][0] * s[0][0] + x[0][1] * s[1][0] + x[0][2] * s[2][0]; 905 | 906 | if (abs(r) >= 1) 907 | { 908 | for (int i = 0; i < 3; ++i) 909 | { 910 | for (int j = 0; j < 3; ++j) 911 | { 912 | s[i][j] /= r; 913 | } 914 | } 915 | } 916 | else 917 | { 918 | T mr = abs(r) / (std::numeric_limits::min)(); 919 | 920 | for (int i = 0; i < 3; ++i) 921 | { 922 | for (int j = 0; j < 3; ++j) 923 | { 924 | if (mr > abs(s[i][j])) 925 | { 926 | s[i][j] /= r; 927 | } 928 | else 929 | { 930 | if (singExc) 931 | throw std::runtime_error("Cannot invert singular matrix."); 932 | 933 | return Matrix44(); 934 | } 935 | } 936 | } 937 | } 938 | 939 | s[3][0] = -x[3][0] * s[0][0] - x[3][1] * s[1][0] - x[3][2] * s[2][0]; 940 | s[3][1] = -x[3][0] * s[0][1] - x[3][1] * s[1][1] - x[3][2] * s[2][1]; 941 | s[3][2] = -x[3][0] * s[0][2] - x[3][1] * s[1][2] - x[3][2] * s[2][2]; 942 | 943 | return s; 944 | } 945 | 946 | 947 | template 948 | inline T 949 | abs(T a) 950 | { 951 | return (a > T(0)) ? a : -a; 952 | } 953 | --------------------------------------------------------------------------------