From 0f1bd27678fb542f8601bbaf493baa1c22732fe2 Mon Sep 17 00:00:00 2001 From: Roozbeh Pournader Date: Sat, 26 Jul 2014 11:20:29 -0700 Subject: Add Spiro version 0.01. --- third_party/spiro/LICENSE | 339 +++++++ third_party/spiro/README | 24 + third_party/spiro/README.third_party | 10 + third_party/spiro/curves/band.py | 85 ++ third_party/spiro/curves/bezfigs.py | 183 ++++ third_party/spiro/curves/bigmat.py | 662 ++++++++++++++ third_party/spiro/curves/cloth_off.py | 2 + third_party/spiro/curves/clothoid.py | 66 ++ third_party/spiro/curves/cornu.py | 140 +++ third_party/spiro/curves/euler-elastica.py | 29 + third_party/spiro/curves/fromcubic.py | 208 +++++ third_party/spiro/curves/mecsolve.py | 859 ++++++++++++++++++ third_party/spiro/curves/mvc.py | 208 +++++ third_party/spiro/curves/numintsynth.py | 176 ++++ third_party/spiro/curves/offset.py | 21 + third_party/spiro/curves/pcorn.py | 160 ++++ third_party/spiro/curves/plot_solve_clothoid.py | 164 ++++ third_party/spiro/curves/poly3.py | 148 ++++ third_party/spiro/curves/polymat-bad.py | 64 ++ third_party/spiro/curves/polymat.py | 399 +++++++++ third_party/spiro/curves/tocubic.py | 461 ++++++++++ third_party/spiro/font/Makefile | 3 + third_party/spiro/font/blend.c | 372 ++++++++ third_party/spiro/font/cut.py | 52 ++ third_party/spiro/font/mkblends.py | 16 + third_party/spiro/font/replace_class.py | 14 + third_party/spiro/font/segment.c | 194 ++++ third_party/spiro/ppedit/Makefile | 25 + third_party/spiro/ppedit/Makefile_gtk1 | 5 + third_party/spiro/ppedit/README | 112 +++ third_party/spiro/ppedit/bezctx.c | 48 + third_party/spiro/ppedit/bezctx.h | 10 + third_party/spiro/ppedit/bezctx_hittest.c | 261 ++++++ third_party/spiro/ppedit/bezctx_hittest.h | 5 + third_party/spiro/ppedit/bezctx_intf.h | 20 + third_party/spiro/ppedit/bezctx_libart.c | 131 +++ third_party/spiro/ppedit/bezctx_libart.h | 5 + third_party/spiro/ppedit/bezctx_ps.c | 116 +++ third_party/spiro/ppedit/bezctx_ps.h | 8 + third_party/spiro/ppedit/bezctx_quartz.c | 78 ++ third_party/spiro/ppedit/bezctx_quartz.h | 4 + third_party/spiro/ppedit/bezctx_x3.c | 96 ++ third_party/spiro/ppedit/bezctx_x3.h | 3 + third_party/spiro/ppedit/carbon_main.c | 182 ++++ third_party/spiro/ppedit/cornu.c | 615 +++++++++++++ third_party/spiro/ppedit/cornu.h | 13 + third_party/spiro/ppedit/gpl.txt | 339 +++++++ third_party/spiro/ppedit/image.c | 141 +++ third_party/spiro/ppedit/image.h | 11 + third_party/spiro/ppedit/pe_view.c | 548 ++++++++++++ third_party/spiro/ppedit/pe_view.h | 11 + third_party/spiro/ppedit/plate.c | 526 +++++++++++ third_party/spiro/ppedit/plate.h | 100 +++ third_party/spiro/ppedit/ppedit.c | 603 +++++++++++++ third_party/spiro/ppedit/ppedit_gtk1.c | 930 ++++++++++++++++++++ third_party/spiro/ppedit/sexp.c | 127 +++ third_party/spiro/ppedit/sexp.h | 10 + third_party/spiro/ppedit/spiro.c | 1070 +++++++++++++++++++++++ third_party/spiro/ppedit/spiro.h | 18 + third_party/spiro/ppedit/zmisc.h | 12 + third_party/spiro/x3/Makefile | 25 + third_party/spiro/x3/pyrex/Makefile | 39 + third_party/spiro/x3/pyrex/beztest.py | 44 + third_party/spiro/x3/pyrex/main.py | 40 + third_party/spiro/x3/pyrex/x3.pyx | 259 ++++++ third_party/spiro/x3/test.c | 158 ++++ third_party/spiro/x3/x3.h | 293 +++++++ third_party/spiro/x3/x3carbon.c | 1002 +++++++++++++++++++++ third_party/spiro/x3/x3common.c | 359 ++++++++ third_party/spiro/x3/x3common.h | 16 + third_party/spiro/x3/x3gtk.c | 778 ++++++++++++++++ third_party/spiro/x3/x3win32.c | 174 ++++ 72 files changed, 14429 insertions(+) create mode 100644 third_party/spiro/LICENSE create mode 100644 third_party/spiro/README create mode 100644 third_party/spiro/README.third_party create mode 100644 third_party/spiro/curves/band.py create mode 100644 third_party/spiro/curves/bezfigs.py create mode 100644 third_party/spiro/curves/bigmat.py create mode 100644 third_party/spiro/curves/cloth_off.py create mode 100644 third_party/spiro/curves/clothoid.py create mode 100644 third_party/spiro/curves/cornu.py create mode 100644 third_party/spiro/curves/euler-elastica.py create mode 100644 third_party/spiro/curves/fromcubic.py create mode 100644 third_party/spiro/curves/mecsolve.py create mode 100644 third_party/spiro/curves/mvc.py create mode 100644 third_party/spiro/curves/numintsynth.py create mode 100644 third_party/spiro/curves/offset.py create mode 100644 third_party/spiro/curves/pcorn.py create mode 100644 third_party/spiro/curves/plot_solve_clothoid.py create mode 100644 third_party/spiro/curves/poly3.py create mode 100644 third_party/spiro/curves/polymat-bad.py create mode 100644 third_party/spiro/curves/polymat.py create mode 100644 third_party/spiro/curves/tocubic.py create mode 100644 third_party/spiro/font/Makefile create mode 100644 third_party/spiro/font/blend.c create mode 100644 third_party/spiro/font/cut.py create mode 100644 third_party/spiro/font/mkblends.py create mode 100644 third_party/spiro/font/replace_class.py create mode 100644 third_party/spiro/font/segment.c create mode 100644 third_party/spiro/ppedit/Makefile create mode 100644 third_party/spiro/ppedit/Makefile_gtk1 create mode 100644 third_party/spiro/ppedit/README create mode 100644 third_party/spiro/ppedit/bezctx.c create mode 100644 third_party/spiro/ppedit/bezctx.h create mode 100644 third_party/spiro/ppedit/bezctx_hittest.c create mode 100644 third_party/spiro/ppedit/bezctx_hittest.h create mode 100644 third_party/spiro/ppedit/bezctx_intf.h create mode 100644 third_party/spiro/ppedit/bezctx_libart.c create mode 100644 third_party/spiro/ppedit/bezctx_libart.h create mode 100644 third_party/spiro/ppedit/bezctx_ps.c create mode 100644 third_party/spiro/ppedit/bezctx_ps.h create mode 100644 third_party/spiro/ppedit/bezctx_quartz.c create mode 100644 third_party/spiro/ppedit/bezctx_quartz.h create mode 100644 third_party/spiro/ppedit/bezctx_x3.c create mode 100644 third_party/spiro/ppedit/bezctx_x3.h create mode 100644 third_party/spiro/ppedit/carbon_main.c create mode 100644 third_party/spiro/ppedit/cornu.c create mode 100644 third_party/spiro/ppedit/cornu.h create mode 100644 third_party/spiro/ppedit/gpl.txt create mode 100644 third_party/spiro/ppedit/image.c create mode 100644 third_party/spiro/ppedit/image.h create mode 100644 third_party/spiro/ppedit/pe_view.c create mode 100644 third_party/spiro/ppedit/pe_view.h create mode 100644 third_party/spiro/ppedit/plate.c create mode 100644 third_party/spiro/ppedit/plate.h create mode 100644 third_party/spiro/ppedit/ppedit.c create mode 100644 third_party/spiro/ppedit/ppedit_gtk1.c create mode 100644 third_party/spiro/ppedit/sexp.c create mode 100644 third_party/spiro/ppedit/sexp.h create mode 100644 third_party/spiro/ppedit/spiro.c create mode 100644 third_party/spiro/ppedit/spiro.h create mode 100644 third_party/spiro/ppedit/zmisc.h create mode 100644 third_party/spiro/x3/Makefile create mode 100644 third_party/spiro/x3/pyrex/Makefile create mode 100644 third_party/spiro/x3/pyrex/beztest.py create mode 100644 third_party/spiro/x3/pyrex/main.py create mode 100644 third_party/spiro/x3/pyrex/x3.pyx create mode 100644 third_party/spiro/x3/test.c create mode 100644 third_party/spiro/x3/x3.h create mode 100644 third_party/spiro/x3/x3carbon.c create mode 100644 third_party/spiro/x3/x3common.c create mode 100644 third_party/spiro/x3/x3common.h create mode 100644 third_party/spiro/x3/x3gtk.c create mode 100644 third_party/spiro/x3/x3win32.c diff --git a/third_party/spiro/LICENSE b/third_party/spiro/LICENSE new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/third_party/spiro/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/third_party/spiro/README b/third_party/spiro/README new file mode 100644 index 0000000..e6a2d4b --- /dev/null +++ b/third_party/spiro/README @@ -0,0 +1,24 @@ +Spiro release 0.01 +4 May 2007 +Raph Levien + +This is a very rough release of the Spiro toolset that I've been using +to create my fonts. The main program is ppedit, and there's a more +detailed README in that directory. + +To build ppedit, do: + +cd ppedit +make + +It requires Gtk2 and cairo. + +The curves/ subdirectory contains python utilities for manipulating +curves, including a Bezier optimizer. + +The font/ subdirectory contains tools for segmenting scans and +compositing them into classes, suitable for tracing over. (Note, +however, that the gtk2 build of ppedit doesn't yet load background +images for tracing). + +Always see http://levien.com/spiro/ for more updates. diff --git a/third_party/spiro/README.third_party b/third_party/spiro/README.third_party new file mode 100644 index 0000000..6c59b1a --- /dev/null +++ b/third_party/spiro/README.third_party @@ -0,0 +1,10 @@ +URL: http://www.levien.com/spiro/spiro-0.01.tar.gz +Version: 0.01 +License: GPL v2 or later +License File: LICENSE + +Description: +Spiro is a toolkit for curve design, especially font design. + +Local Modifications: +No modifications. \ No newline at end of file diff --git a/third_party/spiro/curves/band.py b/third_party/spiro/curves/band.py new file mode 100644 index 0000000..7e61d35 --- /dev/null +++ b/third_party/spiro/curves/band.py @@ -0,0 +1,85 @@ +# A little solver for band-diagonal matrices. Based on NR Ch 2.4. + +from math import * + +from Numeric import * + +do_pivot = True + +def bandec(a, m1, m2): + n, m = a.shape + mm = m1 + m2 + 1 + if m != mm: + raise ValueError('Array has width %d expected %d' % (m, mm)) + al = zeros((n, m1), Float) + indx = zeros(n, Int) + + for i in range(m1): + l = m1 - i + for j in range(l, mm): a[i, j - l] = a[i, j] + for j in range(mm - l, mm): a[i, j] = 0 + + d = 1. + + l = m1 + for k in range(n): + dum = a[k, 0] + pivot = k + if l < n: l += 1 + if do_pivot: + for j in range(k + 1, l): + if abs(a[j, 0]) > abs(dum): + dum = a[j, 0] + pivot = j + indx[k] = pivot + if dum == 0.: a[k, 0] = 1e-20 + if pivot != k: + d = -d + for j in range(mm): + tmp = a[k, j] + a[k, j] = a[pivot, j] + a[pivot, j] = tmp + for i in range(k + 1, l): + dum = a[i, 0] / a[k, 0] + al[k, i - k - 1] = dum + for j in range(1, mm): + a[i, j - 1] = a[i, j] - dum * a[k, j] + a[i, mm - 1] = 0. + return al, indx, d + +def banbks(a, m1, m2, al, indx, b): + n, m = a.shape + mm = m1 + m2 + 1 + l = m1 + for k in range(n): + i = indx[k] + if i != k: + tmp = b[k] + b[k] = b[i] + b[i] = tmp + if l < n: l += 1 + for i in range(k + 1, l): + b[i] -= al[k, i - k - 1] * b[k] + l = 1 + for i in range(n - 1, -1, -1): + dum = b[i] + for k in range(1, l): + dum -= a[i, k] * b[k + i] + b[i] = dum / a[i, 0] + if l < mm: l += 1 + +if __name__ == '__main__': + a = zeros((10, 3), Float) + for i in range(10): + a[i, 0] = 1 + a[i, 1] = 2 + a[i, 2] = 1 + print a + al, indx, d = bandec(a, 1, 1) + print a + print al + print indx + b = zeros(10, Float) + b[5] = 1 + banbks(a, 1, 1, al, indx, b) + print b diff --git a/third_party/spiro/curves/bezfigs.py b/third_party/spiro/curves/bezfigs.py new file mode 100644 index 0000000..b7e408b --- /dev/null +++ b/third_party/spiro/curves/bezfigs.py @@ -0,0 +1,183 @@ +import sys +from math import * + +import fromcubic +import tocubic + +import cornu + +def eps_prologue(x0, y0, x1, y1, draw_box = False): + print '%!PS-Adobe-3.0 EPSF' + print '%%BoundingBox:', x0, y0, x1, y1 + print '%%EndComments' + print '%%EndProlog' + print '%%Page: 1 1' + if draw_box: + print x0, y0, 'moveto', x0, y1, 'lineto', x1, y1, 'lineto', x1, y0, 'lineto closepath stroke' + +def eps_trailer(): + print '%%EOF' + +def fit_cubic_superfast(z0, z1, arclen, th0, th1, aab): + chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) + cth0, sth0 = cos(th0), sin(th0) + cth1, sth1 = -cos(th1), -sin(th1) + armlen = .66667 * arclen + a = armlen * aab + b = armlen - a + bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), + (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] + return bz + +def fit_cubic(z0, z1, arclen, th_fn, fast, aabmin = 0, aabmax = 1.): + chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) + if (arclen < 1.000001 * chord): + return [z0, z1], 0 + th0 = th_fn(0) + th1 = th_fn(arclen) + imax = 4 + jmax = 10 + if fast: + imax = 1 + jmax = 0 + for i in range(imax): + for j in range(jmax + 1): + if jmax == 0: + aab = 0.5 * (aabmin + aabmax) + else: + aab = aabmin + (aabmax - aabmin) * j / jmax + if fast == 2: + bz = fit_cubic_superfast(z0, z1, arclen, th0, th1, aab) + else: + bz = tocubic.fit_cubic_arclen(z0, z1, arclen, th0, th1, aab) + score = tocubic.measure_bz_rk4(bz, arclen, th_fn) + print '% aab =', aab, 'score =', score + sys.stdout.flush() + if j == 0 or score < best_score: + best_score = score + best_aab = aab + best_bz = bz + daab = .06 * (aabmax - aabmin) + aabmin = max(0, best_aab - daab) + aabmax = min(1, best_aab + daab) + print '%--- best_aab =', best_aab + return best_bz, best_score + +def cornu_to_cubic(t0, t1, figno): + if figno == 1: + aabmin = 0 + aabmax = 0.4 + elif figno == 2: + aabmin = 0.5 + aabmax = 1. + else: + aabmin = 0 + aabmax = 1. + fast = 0 + if figno == 3: + fast = 1 + elif figno == 4: + fast = 2 + def th_fn(s): + return (s + t0) ** 2 + y0, x0 = cornu.eval_cornu(t0) + y1, x1 = cornu.eval_cornu(t1) + bz, score = fit_cubic((x0, y0), (x1, y1), t1 - t0, th_fn, fast, aabmin, aabmax) + return bz, score + +def plot_k_of_bz(bz): + dbz = tocubic.bz_deriv(bz) + ddbz = tocubic.bz_deriv(dbz) + cmd = 'moveto' + ss = [0] + def arclength_deriv(x, ss): + dx, dy = tocubic.bz_eval(dbz, x) + return [hypot(dx, dy)] + dt = 0.01 + t = 0 + for i in range(101): + dx, dy = tocubic.bz_eval(dbz, t) + ddx, ddy = tocubic.bz_eval(ddbz, t) + k = (ddy * dx - dy * ddx) / (dx * dx + dy * dy) ** 1.5 + print 100 + 500 * ss[0], 100 + 200 * k, cmd + cmd = 'lineto' + + dsdx = arclength_deriv(t, ss) + tocubic.rk4(ss, dsdx, t, .01, arclength_deriv) + t += dt + print 'stroke' + +def plot_k_nominal(s0, s1): + k0 = 2 * s0 + k1 = 2 * s1 + print 'gsave 0.5 setlinewidth' + print 100, 100 + 200 * k0, 'moveto' + print 100 + 500 * (s1 - s0), 100 + 200 * k1, 'lineto' + print 'stroke grestore' + +def simple_bez(): + eps_prologue(95, 126, 552, 508, 0) + tocubic.plot_prolog() + print '/ss 1.5 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + bz, score = cornu_to_cubic(.5, 1.1, 2) + fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True) + print 'stroke' + print '/Times-Roman 12 selectfont' + print '95 130 moveto ((x0, y0)) show' + print '360 200 moveto ((x1, y1)) show' + print '480 340 moveto ((x2, y2)) show' + print '505 495 moveto ((x3, y3)) show' + print 'showpage' + eps_trailer() + +def fast_bez(figno): + if figno == 3: + y1 = 520 + else: + y1 = 550 + eps_prologue(95, 140, 552, y1, 0) + tocubic.plot_prolog() + print '/ss 1.5 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + bz, score = cornu_to_cubic(.5, 1.1, figno) + fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True) + print 'stroke' + plot_k_nominal(.5, 1.1) + plot_k_of_bz(bz) + print 'showpage' + eps_trailer() + +def bezfig(s1): + eps_prologue(95, 38, 510, 550, 0) + #print '0.5 0.5 scale 500 100 translate' + tocubic.plot_prolog() + print '/ss 1.5 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + bz, score = cornu_to_cubic(.5, 0.85, 1) + fromcubic.plot_bzs([[bz]], (-400, 0), 1000, True) + print 'stroke' + plot_k_nominal(.5, 0.85) + plot_k_of_bz(bz) + bz, score = cornu_to_cubic(.5, 0.85, 2) + fromcubic.plot_bzs([[bz]], (-400, 100), 1000, True) + print 'stroke' + print 'gsave 0 50 translate' + plot_k_nominal(.5, .85) + plot_k_of_bz(bz) + print 'grestore' + print 'showpage' + +import sys + +if __name__ == '__main__': + figno = int(sys.argv[1]) + if figno == 0: + simple_bez() + elif figno == 1: + bezfig(1.0) + elif figno == 2: + bezfig(0.85) + else: + fast_bez(figno) + #fast_bez(4) diff --git a/third_party/spiro/curves/bigmat.py b/third_party/spiro/curves/bigmat.py new file mode 100644 index 0000000..c1fac0c --- /dev/null +++ b/third_party/spiro/curves/bigmat.py @@ -0,0 +1,662 @@ +# Solver based on direct Newton solving of 4 parameters for each curve +# segment + +import sys +from math import * + +from Numeric import * +import LinearAlgebra as la + +import poly3 +import band + +class Seg: + def __init__(self, chord, th): + self.ks = [0., 0., 0., 0.] + self.chord = chord + self.th = th + def compute_ends(self, ks): + chord, ch_th = poly3.integ_chord(ks) + l = chord / self.chord + thl = ch_th - (-.5 * ks[0] + .125 * ks[1] - 1./48 * ks[2] + 1./384 * ks[3]) + thr = (.5 * ks[0] + .125 * ks[1] + 1./48 * ks[2] + 1./384 * ks[3]) - ch_th + k0l = l * (ks[0] - .5 * ks[1] + .125 * ks[2] - 1./48 * ks[3]) + k0r = l * (ks[0] + .5 * ks[1] + .125 * ks[2] + 1./48 * ks[3]) + l2 = l * l + k1l = l2 * (ks[1] - .5 * ks[2] + .125 * ks[3]) + k1r = l2 * (ks[1] + .5 * ks[2] + .125 * ks[3]) + l3 = l2 * l + k2l = l3 * (ks[2] - .5 * ks[3]) + k2r = l3 * (ks[2] + .5 * ks[3]) + return (thl, k0l, k1l, k2l), (thr, k0r, k1r, k2r), l + def set_ends_from_ks(self): + self.endl, self.endr, self.l = self.compute_ends(self.ks) + def fast_pderivs(self): + l = self.l + l2 = l * l + l3 = l2 * l + return [((.5, l, 0, 0), (.5, l, 0, 0)), + ((-1./12, -l/2, l2, 0), (1./12, l/2, l2, 0)), + ((1./48, l/8, -l2/2, l3), (1./48, l/8, l2/2, l3)), + ((-1./480, -l/48, l2/8, -l3/2), (1./480, l/48, l2/8, l3/2))] + def compute_pderivs(self): + rd = 2e6 + delta = 1./rd + base_ks = self.ks + base_endl, base_endr, dummy = self.compute_ends(base_ks) + result = [] + for i in range(4): + try_ks = base_ks[:] + try_ks[i] += delta + try_endl, try_endr, dummy = self.compute_ends(try_ks) + deriv_l = (rd * (try_endl[0] - base_endl[0]), + rd * (try_endl[1] - base_endl[1]), + rd * (try_endl[2] - base_endl[2]), + rd * (try_endl[3] - base_endl[3])) + deriv_r = (rd * (try_endr[0] - base_endr[0]), + rd * (try_endr[1] - base_endr[1]), + rd * (try_endr[2] - base_endr[2]), + rd * (try_endr[3] - base_endr[3])) + result.append((deriv_l, deriv_r)) + return result + +class Node: + def __init__(self, x, y, ty, th): + self.x = x + self.y = y + self.ty = ty + self.th = th + def continuity(self): + if self.ty == 'o': + return 4 + elif self.ty in ('c', '[', ']'): + return 2 + else: + return 0 + +def mod_2pi(th): + u = th / (2 * pi) + return 2 * pi * (u - floor(u + 0.5)) + +def setup_path(path): + segs = [] + nodes = [] + nsegs = len(path) + if path[0][2] == '{': + nsegs -= 1 + for i in range(nsegs): + i1 = (i + 1) % len(path) + x0, y0, t0 = path[i] + x1, y1, t1 = path[i1] + s = Seg(hypot(y1 - y0, x1 - x0), atan2(y1 - y0, x1 - x0)) + segs.append(s) + for i in range(len(path)): + x0, y0, t0 = path[i] + + if t0 in ('{', '}', 'v'): + th = 0 + else: + s0 = segs[(i + len(path) - 1) % len(path)] + s1 = segs[i] + th = mod_2pi(s1.th - s0.th) + + n = Node(x0, y0, t0, th) + nodes.append(n) + return segs, nodes + +def count_vec(nodes): + jincs = [] + n = 0 + for i in range(len(nodes)): + i1 = (i + 1) % len(nodes) + t0 = nodes[i].ty + t1 = nodes[i1].ty + if t0 in ('{', '}', 'v', '[') and t1 in ('{', '}', 'v', ']'): + jinc = 0 + elif t0 in ('{', '}', 'v', '[') and t1 == 'c': + jinc = 1 + elif t0 == 'c' and t1 in ('{', '}', 'v', ']'): + jinc = 1 + elif t0 == 'c' and t1 == 'c': + jinc = 2 + else: + jinc = 4 + jincs.append(jinc) + n += jinc + return n, jincs + +thscale, k0scale, k1scale, k2scale = 1, 1, 1, 1 + +def inversedot_woodbury(m, v): + a = zeros((n, 11), Float) + for i in range(n): + for j in range(max(-7, -i), min(4, n - i)): + a[i, j + 7] = m[i, i + j] + print a + al, indx, d = band.bandec(a, 7, 3) + VtZ = identity(4, Float) + Z = zeros((n, 4), Float) + for i in range(4): + u = zeros(n, Float) + for j in range(4): + u[j] = m[j, n - 4 + i] + band.banbks(a, 7, 3, al, indx, u) + for k in range(n): + Z[k, i] = u[k] + #Z[:,i] = u + for j in range(4): + VtZ[j, i] += u[n - 4 + j] + print Z + print VtZ + H = la.inverse(VtZ) + print H + band.banbks(a, 7, 3, al, indx, v) + return(v - dot(Z, dot(H, v[n - 4:]))) + +def inversedot(m, v): + return dot(la.inverse(m), v) + n, nn = m.shape + if 1: + for i in range(n): + sys.stdout.write('% ') + for j in range(n): + if m[i, j] > 0: sys.stdout.write('+ ') + elif m[i, j] < 0: sys.stdout.write('- ') + else: sys.stdout.write(' ') + sys.stdout.write('\n') + + cyclic = False + for i in range(4): + for j in range(n - 4, n): + if m[i, j] != 0: + cyclic = True + print '% cyclic:', cyclic + if not cyclic: + a = zeros((n, 11), Float) + for i in range(n): + for j in range(max(-5, -i), min(6, n - i)): + a[i, j + 5] = m[i, i + j] + for i in range(n): + sys.stdout.write('% ') + for j in range(11): + if a[i, j] > 0: sys.stdout.write('+ ') + elif a[i, j] < 0: sys.stdout.write('- ') + else: sys.stdout.write(' ') + sys.stdout.write('\n') + al, indx, d = band.bandec(a, 5, 5) + print a + band.banbks(a, 5, 5, al, indx, v) + return v + else: + #return inversedot_woodbury(m, v) + bign = 3 * n + a = zeros((bign, 11), Float) + u = zeros(bign, Float) + for i in range(bign): + u[i] = v[i % n] + for j in range(-7, 4): + a[i, j + 7] = m[i % n, (i + j + 7 * n) % n] + #print a + if 1: + for i in range(bign): + sys.stdout.write('% ') + for j in range(11): + if a[i, j] > 0: sys.stdout.write('+ ') + elif a[i, j] < 0: sys.stdout.write('- ') + else: sys.stdout.write(' ') + sys.stdout.write('\n') + #print u + al, indx, d = band.bandec(a, 5, 5) + band.banbks(a, 5, 5, al, indx, u) + #print u + return u[n + 2: 2 * n + 2] + +def iter(segs, nodes): + n, jincs = count_vec(nodes) + print '%', jincs + v = zeros(n, Float) + m = zeros((n, n), Float) + for i in range(len(segs)): + segs[i].set_ends_from_ks() + j = 0 + j0 = 0 + for i in range(len(segs)): + i1 = (i + 1) % len(nodes) + t0 = nodes[i].ty + t1 = nodes[i1].ty + seg = segs[i] + + derivs = seg.compute_pderivs() + print '%derivs:', derivs + + jinc = jincs[i] # the number of params on this seg + print '%', t0, t1, jinc, j0 + + # The constraints are laid out as follows: + # constraints that cross the node on the left + # constraints on the left side + # constraints on the right side + # constraints that cross the node on the right + + jj = j0 # the index into the constraint row we're writing + jthl, jk0l, jk1l, jk2l = -1, -1, -1, -1 + jthr, jk0r, jk1r, jk2r = -1, -1, -1, -1 + + # constraints crossing left + + if t0 == 'o': + jthl = jj + 0 + jk0l = jj + 1 + jk1l = jj + 2 + jk2l = jj + 3 + jj += 4 + elif t0 in ('c', '[', ']'): + jthl = jj + 0 + jk0l = jj + 1 + jj += 2 + + # constraints on left + + if t0 in ('[', 'v', '{') and jinc == 4: + jk1l = jj + jj += 1 + if t0 in ('[', 'v', '{', 'c') and jinc == 4: + jk2l = jj + jj += 1 + + # constraints on right + + if t1 in (']', 'v', '}') and jinc == 4: + jk1r = jj + jj += 1 + if t1 in (']', 'v', '}', 'c') and jinc == 4: + jk2r = jj + jj += 1 + + # constraints crossing right + + jj %= n + j1 = jj + + if t1 == 'o': + jthr = jj + 0 + jk0r = jj + 1 + jk1r = jj + 2 + jk2r = jj + 3 + jj += 4 + elif t1 in ('c', '[', ']'): + jthr = jj + 0 + jk0r = jj + 1 + jj += 2 + + print '%', jthl, jk0l, jk1l, jk2l, jthr, jk0r, jk1r, jk2r + + if jthl >= 0: + v[jthl] += thscale * (nodes[i].th - seg.endl[0]) + if jinc == 1: + m[jthl][j] += derivs[0][0][0] + elif jinc == 2: + m[jthl][j + 1] += derivs[0][0][0] + m[jthl][j] += derivs[1][0][0] + elif jinc == 4: + m[jthl][j + 2] += derivs[0][0][0] + m[jthl][j + 3] += derivs[1][0][0] + m[jthl][j + 0] += derivs[2][0][0] + m[jthl][j + 1] += derivs[3][0][0] + if jk0l >= 0: + v[jk0l] += k0scale * seg.endl[1] + if jinc == 1: + m[jk0l][j] -= derivs[0][0][1] + elif jinc == 2: + m[jk0l][j + 1] -= derivs[0][0][1] + m[jk0l][j] -= derivs[1][0][1] + elif jinc == 4: + m[jk0l][j + 2] -= derivs[0][0][1] + m[jk0l][j + 3] -= derivs[1][0][1] + m[jk0l][j + 0] -= derivs[2][0][1] + m[jk0l][j + 1] -= derivs[3][0][1] + if jk1l >= 0: + v[jk1l] += k1scale * seg.endl[2] + m[jk1l][j + 2] -= derivs[0][0][2] + m[jk1l][j + 3] -= derivs[1][0][2] + m[jk1l][j + 0] -= derivs[2][0][2] + m[jk1l][j + 1] -= derivs[3][0][2] + if jk2l >= 0: + v[jk2l] += k2scale * seg.endl[3] + m[jk2l][j + 2] -= derivs[0][0][3] + m[jk2l][j + 3] -= derivs[1][0][3] + m[jk2l][j + 0] -= derivs[2][0][3] + m[jk2l][j + 1] -= derivs[3][0][3] + + if jthr >= 0: + v[jthr] -= thscale * seg.endr[0] + if jinc == 1: + m[jthr][j] += derivs[0][1][0] + elif jinc == 2: + m[jthr][j + 1] += derivs[0][1][0] + m[jthr][j + 0] += derivs[1][1][0] + elif jinc == 4: + m[jthr][j + 2] += derivs[0][1][0] + m[jthr][j + 3] += derivs[1][1][0] + m[jthr][j + 0] += derivs[2][1][0] + m[jthr][j + 1] += derivs[3][1][0] + if jk0r >= 0: + v[jk0r] -= k0scale * seg.endr[1] + if jinc == 1: + m[jk0r][j] += derivs[0][1][1] + elif jinc == 2: + m[jk0r][j + 1] += derivs[0][1][1] + m[jk0r][j + 0] += derivs[1][1][1] + elif jinc == 4: + m[jk0r][j + 2] += derivs[0][1][1] + m[jk0r][j + 3] += derivs[1][1][1] + m[jk0r][j + 0] += derivs[2][1][1] + m[jk0r][j + 1] += derivs[3][1][1] + if jk1r >= 0: + v[jk1r] -= k1scale * seg.endr[2] + m[jk1r][j + 2] += derivs[0][1][2] + m[jk1r][j + 3] += derivs[1][1][2] + m[jk1r][j + 0] += derivs[2][1][2] + m[jk1r][j + 1] += derivs[3][1][2] + if jk2r >= 0: + v[jk2r] -= k2scale * seg.endr[3] + m[jk2r][j + 2] += derivs[0][1][3] + m[jk2r][j + 3] += derivs[1][1][3] + m[jk2r][j + 0] += derivs[2][1][3] + m[jk2r][j + 1] += derivs[3][1][3] + + j += jinc + j0 = j1 + #print m + dk = inversedot(m, v) + dkmul = 1 + j = 0 + for i in range(len(segs)): + jinc = jincs[i] + if jinc == 1: + segs[i].ks[0] += dkmul * dk[j] + elif jinc == 2: + segs[i].ks[0] += dkmul * dk[j + 1] + segs[i].ks[1] += dkmul * dk[j + 0] + elif jinc == 4: + segs[i].ks[0] += dkmul * dk[j + 2] + segs[i].ks[1] += dkmul * dk[j + 3] + segs[i].ks[2] += dkmul * dk[j + 0] + segs[i].ks[3] += dkmul * dk[j + 1] + j += jinc + + norm = 0. + for i in range(len(dk)): + norm += dk[i] * dk[i] + return sqrt(norm) + + +def plot_path(segs, nodes, tol = 1.0, show_cpts = False): + if show_cpts: + cpts = [] + j = 0 + cmd = 'moveto' + for i in range(len(segs)): + i1 = (i + 1) % len(nodes) + n0 = nodes[i] + n1 = nodes[i1] + x0, y0, t0 = n0.x, n0.y, n0.ty + x1, y1, t1 = n1.x, n1.y, n1.ty + ks = segs[i].ks + abs_ks = abs(ks[0]) + abs(ks[1] / 2) + abs(ks[2] / 8) + abs(ks[3] / 48) + n_subdiv = int(ceil(.001 + tol * abs_ks)) + n_subhalf = (n_subdiv + 1) / 2 + if n_subdiv > 1: + n_subdiv = n_subhalf * 2 + ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) + pside = poly3.int_3spiro_poly(ksp, n_subhalf) + ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) + mside = poly3.int_3spiro_poly(ksm, n_subhalf) + mside.reverse() + for j in range(len(mside)): + mside[j] = (-mside[j][0], -mside[j][1]) + if n_subdiv > 1: + pts = mside + pside[1:] + else: + pts = mside[:1] + pside[1:] + chord_th = atan2(y1 - y0, x1 - x0) + chord_len = hypot(y1 - y0, x1 - x0) + rot = chord_th - atan2(pts[-1][1] - pts[0][1], pts[-1][0] - pts[0][0]) + scale = chord_len / hypot(pts[-1][1] - pts[0][1], pts[-1][0] - pts[0][0]) + u, v = scale * cos(rot), scale * sin(rot) + xt = x0 - u * pts[0][0] + v * pts[0][1] + yt = y0 - u * pts[0][1] - v * pts[0][0] + s = -.5 + for x, y in pts: + xp, yp = xt + u * x - v * y, yt + u * y + v * x + thp = (((ks[3]/24 * s + ks[2]/6) * s + ks[1] / 2) * s + ks[0]) * s + rot + up, vp = scale / (1.5 * n_subdiv) * cos(thp), scale / (1.5 * n_subdiv) * sin(thp) + if s == -.5: + if cmd == 'moveto': + print xp, yp, 'moveto' + cmd = 'curveto' + else: + if show_cpts: + cpts.append((xlast + ulast, ylast + vlast)) + cpts.append((xp - up, yp - vp)) + print xlast + ulast, ylast + vlast, xp - up, yp - vp, xp, yp, 'curveto' + xlast, ylast, ulast, vlast = xp, yp, up, vp + s += 1. / n_subdiv + if t1 == 'v': + j += 2 + else: + j += 1 + print 'stroke' + if show_cpts: + for x, y in cpts: + print 'gsave 0 0 1 setrgbcolor', x, y, 'translate circle fill grestore' + +def plot_ks(segs, nodes, xo, yo, xscale, yscale): + j = 0 + cmd = 'moveto' + x = xo + for i in range(len(segs)): + i1 = (i + 1) % len(nodes) + n0 = nodes[i] + n1 = nodes[i1] + x0, y0, t0 = n0.x, n0.y, n0.ty + x1, y1, t1 = n1.x, n1.y, n1.ty + ks = segs[i].ks + chord, ch_th = poly3.integ_chord(ks) + l = chord/segs[i].chord + k0 = l * poly3.eval_cubic(ks[0], ks[1], .5 * ks[2], 1./6 * ks[3], -.5) + print x, yo + yscale * k0, cmd + cmd = 'lineto' + k3 = l * poly3.eval_cubic(ks[0], ks[1], .5 * ks[2], 1./6 * ks[3], .5) + k1 = k0 + l/3 * (ks[1] - 0.5 * ks[2] + .125 * ks[3]) + k2 = k3 - l/3 * (ks[1] + 0.5 * ks[2] + .125 * ks[3]) + print x + xscale / l / 3., yo + yscale * k1 + print x + 2 * xscale / l / 3., yo + yscale * k2 + print x + xscale / l, yo + yscale * k3, 'curveto' + x += xscale / l + if t1 == 'v': + j += 2 + else: + j += 1 + print 'stroke' + print xo, yo, 'moveto', x, yo, 'lineto stroke' + +def plot_nodes(nodes, segs): + for i in range(len(nodes)): + n = nodes[i] + print 'gsave', n.x, n.y, 'translate' + if n.ty in ('c', '[', ']'): + th = segs[i].th - segs[i].endl[0] + if n.ty == ']': th += pi + print 180 * th / pi, 'rotate' + if n.ty == 'o': + print 'circle fill' + elif n.ty == 'c': + print '3 4 poly fill' + elif n.ty in ('v', '{', '}'): + print '45 rotate 3 4 poly fill' + elif n.ty in ('[', ']'): + print '0 -3 moveto 0 0 3 90 270 arc fill' + else: + print '5 3 poly fill' + print 'grestore' + +def prologue(): + print '/ss 2 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + print '/poly {' + print ' dup false exch {' + print ' 0 3 index 2 index { lineto } { moveto } ifelse pop true' + print ' 360.0 2 index div rotate' + print ' } repeat pop pop pop' + print '} bind def' + +def run_path(path, show_iter = False, n = 10, xo = 36, yo = 550, xscale = .25, yscale = 2000, pl_nodes = True): + segs, nodes = setup_path(path) + print '.5 setlinewidth' + for i in range(n): + if i == n - 1: + print '0 0 0 setrgbcolor 1 setlinewidth' + elif i == 0: + print '1 0 0 setrgbcolor' + elif i == 1: + print '0 0.5 0 setrgbcolor' + elif i == 2: + print '0.3 0.3 0.3 setrgbcolor' + norm = iter(segs, nodes) + print '% norm =', norm + if show_iter or i == n - 1: + #print '1 0 0 setrgbcolor' + #plot_path(segs, nodes, tol = 5) + #print '0 0 0 setrgbcolor' + plot_path(segs, nodes, tol = 1.0) + plot_ks(segs, nodes, xo, yo, xscale, yscale) + if pl_nodes: + plot_nodes(nodes, segs) + +if __name__ == '__main__': + if 1: + path = [(100, 350, 'o'), (225, 350, 'o'), (350, 450, 'o'), + (450, 400, 'o'), (315, 205, 'o'), (300, 200, 'o'), + (285, 205, 'o')] + + if 1: + path = [(100, 350, 'o'), (175, 375, '['), (250, 375, ']'), (325, 425, '['), + (325, 450, ']'), + (400, 475, 'o'), (350, 200, 'c')] + + if 0: + ecc, ty, ty1 = 0.56199, 'c', 'c' + ecc, ty, ty1 = 0.49076, 'o', 'o', + ecc, ty, ty1 = 0.42637, 'o', 'c' + path = [(300 - 200 * ecc, 300, ty), (300, 100, ty1), + (300 + 200 * ecc, 300, ty), (300, 500, ty1)] + + # difficult path #3 + if 0: + path = [(100, 300, '{'), (225, 350, 'o'), (350, 500, 'o'), + (450, 500, 'o'), (450, 450, 'o'), (300, 200, '}')] + + if 0: + path = [(100, 100, '{'), (200, 180, 'v'), (250, 215, 'o'), + (300, 200, 'o'), (400, 100, '}')] + + if 0: + praw = [ + (134, 90, 'o'), + (192, 68, 'o'), + (246, 66, 'o'), + (307, 107, 'o'), + (314, 154, '['), + (317, 323, ']'), + (347, 389, 'o'), + (373, 379, 'v'), + (386, 391, 'v'), + (365, 409, 'o'), + (335, 419, 'o'), + (273, 390, 'v'), + (251, 405, 'o'), + (203, 423, 'o'), + (102, 387, 'o'), + (94, 321, 'o'), + (143, 276, 'o'), + (230, 251, 'o'), + (260, 250, 'v'), + (260, 220, '['), + (258, 157, ']'), + (243, 110, 'o'), + (159, 99, 'o'), + (141, 121, 'o'), + (156, 158, 'o'), + (121, 184, 'o'), + (106, 117, 'o')] + if 0: + praw = [ + (275, 56, 'o'), + (291, 75, 'v'), + (312, 61, 'o'), + (344, 50, 'o'), + (373, 72, 'o'), + (356, 91, 'o'), + (334, 81, 'o'), + (297, 85, 'v'), + (306, 116, 'o'), + (287, 180, 'o'), + (236, 213, 'o'), + (182, 212, 'o'), + (157, 201, 'v'), + (149, 209, 'o'), + (143, 230, 'o'), + (162, 246, 'c'), + (202, 252, 'o'), + (299, 260, 'o'), + (331, 282, 'o'), + (341, 341, 'o'), + (308, 390, 'o'), + (258, 417, 'o'), + (185, 422, 'o'), + (106, 377, 'o'), + (110, 325, 'o'), + (133, 296, 'o'), + (147, 283, 'v'), + (117, 238, 'o'), + (133, 205, 'o'), + (147, 191, 'v'), + (126, 159, 'o'), + (128, 94, 'o'), + (167, 50, 'o'), + (237, 39, 'o')] + + path = [] + for x, y, ty in praw: + #if ty == 'o': ty = 'c' + path.append((x, 550 - y, ty)) + + if 0: + path = [(100, 300, 'o'), (300, 100, 'o'), (300, 500, 'o')] + + if 0: + # Woodford data set + ty = 'o' + praw = [(0, 0, '{'), (1, 1.9, ty), (2, 2.7, ty), (3, 2.6, ty), + (4, 1.6, ty), (5, .89, ty), (6, 1.2, '}')] + path = [] + for x, y, t in praw: + path.append((100 + 80 * x, 100 + 80 * y, t)) + + if 0: + ycen = 32 + yrise = 0 + path = [] + ty = '{' + for i in range(10): + path.append((50 + i * 30, 250 + (10 - i) * yrise, ty)) + ty = 'o' + path.append((350, 250 + ycen, ty)) + for i in range(1, 10): + path.append((350 + i * 30, 250 + i * yrise, ty)) + path.append((650, 250 + 10 * yrise, '}')) + + prologue() + + run_path(path, show_iter = True, n=5) diff --git a/third_party/spiro/curves/cloth_off.py b/third_party/spiro/curves/cloth_off.py new file mode 100644 index 0000000..0ebaf4d --- /dev/null +++ b/third_party/spiro/curves/cloth_off.py @@ -0,0 +1,2 @@ +# Fancy new algorithms for computing the offset of a clothoid. + diff --git a/third_party/spiro/curves/clothoid.py b/third_party/spiro/curves/clothoid.py new file mode 100644 index 0000000..b3ae6af --- /dev/null +++ b/third_party/spiro/curves/clothoid.py @@ -0,0 +1,66 @@ +from math import * +import cornu + +def mod_2pi(th): + u = th / (2 * pi) + return 2 * pi * (u - floor(u + 0.5)) + +# Given clothoid k(s) = k0 + k1 s, compute th1 - th0 of chord from s = -.5 +# to .5. +def compute_dth(k0, k1): + if k1 < 0: + return -compute_dth(k0, -k1) + elif k1 == 0: + return 0 + sqrk1 = sqrt(2 * k1) + t0 = (k0 - .5 * k1) / sqrk1 + t1 = (k0 + .5 * k1) / sqrk1 + (y0, x0) = cornu.eval_cornu(t0) + (y1, x1) = cornu.eval_cornu(t1) + chord_th = atan2(y1 - y0, x1 - x0) + return mod_2pi(t1 * t1 - chord_th) - mod_2pi(chord_th - t0 * t0) + +def compute_chord(k0, k1): + if k1 == 0: + if k0 == 0: + return 1 + else: + return sin(k0 * .5) / (k0 * .5) + sqrk1 = sqrt(2 * abs(k1)) + t0 = (k0 - .5 * k1) / sqrk1 + t1 = (k0 + .5 * k1) / sqrk1 + (y0, x0) = cornu.eval_cornu(t0) + (y1, x1) = cornu.eval_cornu(t1) + return hypot(y1 - y0, x1 - x0) / abs(t1 - t0) + +# Given th0 and th1 at endpoints (measured from chord), return k0 +# and k1 such that the clothoid k(s) = k0 + k1 s, evaluated from +# s = -.5 to .5, has the tangents given +def solve_clothoid(th0, th1, verbose = False): + k0 = th0 + th1 + + # initial guess + k1 = 6 * (th1 - th0) + error = (th1 - th0) - compute_dth(k0, k1) + if verbose: + print k0, k1, error + + k1_old, error_old = k1, error + # second guess based on d(dth)/dk1 ~ 1/6 + k1 += 6 * error + error = (th1 - th0) - compute_dth(k0, k1) + if verbose: + print k0, k1, error + + # secant method + for i in range(10): + if abs(error) < 1e-9: break + k1_old, error_old, k1 = k1, error, k1 + (k1_old - k1) * error / (error - error_old) + error = (th1 - th0) - compute_dth(k0, k1) + if verbose: + print k0, k1, error + + return k0, k1 + +if __name__ == '__main__': + print solve_clothoid(.06, .05, True) diff --git a/third_party/spiro/curves/cornu.py b/third_party/spiro/curves/cornu.py new file mode 100644 index 0000000..26f68e9 --- /dev/null +++ b/third_party/spiro/curves/cornu.py @@ -0,0 +1,140 @@ +from math import * + +# implementation adapted from cephes + +def polevl(x, coef): + ans = coef[-1] + for i in range(len(coef) - 2, -1, -1): + ans = ans * x + coef[i] + return ans + +sn = [ +-2.99181919401019853726E3, + 7.08840045257738576863E5, +-6.29741486205862506537E7, + 2.54890880573376359104E9, +-4.42979518059697779103E10, + 3.18016297876567817986E11 +] +sn.reverse() +sd = [ + 1.00000000000000000000E0, + 2.81376268889994315696E2, + 4.55847810806532581675E4, + 5.17343888770096400730E6, + 4.19320245898111231129E8, + 2.24411795645340920940E10, + 6.07366389490084639049E11 +] +sd.reverse() +cn = [ +-4.98843114573573548651E-8, + 9.50428062829859605134E-6, +-6.45191435683965050962E-4, + 1.88843319396703850064E-2, +-2.05525900955013891793E-1, + 9.99999999999999998822E-1 +] +cn.reverse() +cd = [ + 3.99982968972495980367E-12, + 9.15439215774657478799E-10, + 1.25001862479598821474E-7, + 1.22262789024179030997E-5, + 8.68029542941784300606E-4, + 4.12142090722199792936E-2, + 1.00000000000000000118E0 +] +cd.reverse() + +fn = [ + 4.21543555043677546506E-1, + 1.43407919780758885261E-1, + 1.15220955073585758835E-2, + 3.45017939782574027900E-4, + 4.63613749287867322088E-6, + 3.05568983790257605827E-8, + 1.02304514164907233465E-10, + 1.72010743268161828879E-13, + 1.34283276233062758925E-16, + 3.76329711269987889006E-20 +] +fn.reverse() +fd = [ + 1.00000000000000000000E0, + 7.51586398353378947175E-1, + 1.16888925859191382142E-1, + 6.44051526508858611005E-3, + 1.55934409164153020873E-4, + 1.84627567348930545870E-6, + 1.12699224763999035261E-8, + 3.60140029589371370404E-11, + 5.88754533621578410010E-14, + 4.52001434074129701496E-17, + 1.25443237090011264384E-20 +] +fd.reverse() +gn = [ + 5.04442073643383265887E-1, + 1.97102833525523411709E-1, + 1.87648584092575249293E-2, + 6.84079380915393090172E-4, + 1.15138826111884280931E-5, + 9.82852443688422223854E-8, + 4.45344415861750144738E-10, + 1.08268041139020870318E-12, + 1.37555460633261799868E-15, + 8.36354435630677421531E-19, + 1.86958710162783235106E-22 +] +gn.reverse() +gd = [ + 1.00000000000000000000E0, + 1.47495759925128324529E0, + 3.37748989120019970451E-1, + 2.53603741420338795122E-2, + 8.14679107184306179049E-4, + 1.27545075667729118702E-5, + 1.04314589657571990585E-7, + 4.60680728146520428211E-10, + 1.10273215066240270757E-12, + 1.38796531259578871258E-15, + 8.39158816283118707363E-19, + 1.86958710162783236342E-22 +] +gd.reverse() + + +def fresnel(xxa): + x = abs(xxa) + x2 = x * x + if x2 < 2.5625: + t = x2 * x2 + ss = x * x2 * polevl(t, sn) / polevl(t, sd) + cc = x * polevl(t, cn) / polevl(t, cd) + elif x > 36974.0: + ss = 0.5 + cc = 0.5 + else: + t = pi * x2 + u = 1.0 / (t * t) + t = 1.0 / t + f = 1.0 - u * polevl(u, fn) / polevl(u, fd) + g = t * polevl(u, gn) / polevl(u, gd) + t = pi * .5 * x2 + c = cos(t) + s = sin(t) + t = pi * x + cc = 0.5 + (f * s - g * c) / t + ss = 0.5 - (f * c + g * s) / t + if xxa < 0: + cc = -cc + ss = -ss + return ss, cc + +def eval_cornu(t): + spio2 = sqrt(pi * .5) + s, c = fresnel(t / spio2) + s *= spio2 + c *= spio2 + return s, c diff --git a/third_party/spiro/curves/euler-elastica.py b/third_party/spiro/curves/euler-elastica.py new file mode 100644 index 0000000..a721d1e --- /dev/null +++ b/third_party/spiro/curves/euler-elastica.py @@ -0,0 +1,29 @@ +from math import * + +def plot_elastica(a, c): + s = 500 + cmd = 'moveto' + dx = .001 + x, y = 0, 0 + if c * c > 2 * a * a: + g = sqrt(c * c - 2 * a * a) + x = g + .01 + if c == 0: + x = .001 + try: + for i in range(1000): + print 6 + s * x, 200 + s * y, cmd + cmd = 'lineto' + x += dx + if 1 and c * c > 2 * a * a: + print (c * c - x * x) * (x * x - g * g) + dy = dx * (x * x - .5 * c * c - .5 * g * g) / sqrt((c * c - x * x) * (x * x - g * g)) + else: + dy = dx * (a * a - c * c + x * x)/sqrt((c * c - x * x) * (2 * a * a - c * c + x * x)) + y += dy + except ValueError, e: + pass + print 'stroke' + +plot_elastica(1, 0) +print 'showpage' diff --git a/third_party/spiro/curves/fromcubic.py b/third_party/spiro/curves/fromcubic.py new file mode 100644 index 0000000..210f9d4 --- /dev/null +++ b/third_party/spiro/curves/fromcubic.py @@ -0,0 +1,208 @@ +# Convert piecewise cubic into piecewise clothoid representation. + +from math import * + +import clothoid +import pcorn +import tocubic + +import offset + +def read_bz(f): + result = [] + for l in f.xreadlines(): + s = l.split() + if len(s) > 0: + cmd = s[-1] + #print s[:-1], cmd + if cmd == 'm': + sp = [] + result.append(sp) + curpt = [float(x) for x in s[0:2]] + startpt = curpt + elif cmd == 'l': + newpt = [float(x) for x in s[0:2]] + sp.append((curpt, newpt)) + curpt = newpt + elif cmd == 'c': + c1 = [float(x) for x in s[0:2]] + c2 = [float(x) for x in s[2:4]] + newpt = [float(x) for x in s[4:6]] + sp.append((curpt, c1, c2, newpt)) + curpt = newpt + return result + +def plot_bzs(bzs, z0, scale, fancy = False): + for sp in bzs: + for i in range(len(sp)): + bz = sp[i] + tocubic.plot_bz(bz, z0, scale, i == 0) + print 'stroke' + if fancy: + for i in range(len(sp)): + bz = sp[i] + + x0, y0 = z0[0] + scale * bz[0][0], z0[1] + scale * bz[0][1] + print 'gsave', x0, y0, 'translate circle fill grestore' + if len(bz) == 4: + x1, y1 = z0[0] + scale * bz[1][0], z0[1] + scale * bz[1][1] + x2, y2 = z0[0] + scale * bz[2][0], z0[1] + scale * bz[2][1] + x3, y3 = z0[0] + scale * bz[3][0], z0[1] + scale * bz[3][1] + print 'gsave 0.5 setlinewidth', x0, y0, 'moveto' + print x1, y1, 'lineto stroke' + print x2, y2, 'moveto' + print x3, y3, 'lineto stroke grestore' + print 'gsave', x1, y1, 'translate 0.75 dup scale circle fill grestore' + print 'gsave', x2, y2, 'translate 0.75 dup scale circle fill grestore' + print 'gsave', x3, y3, 'translate 0.75 dup scale circle fill grestore' + + + +def measure_bz_cloth(seg, bz, n = 100): + bz_arclen = tocubic.bz_arclength_rk4(bz) + arclen_ratio = seg.arclen / bz_arclen + dbz = tocubic.bz_deriv(bz) + + def measure_derivs(x, ys): + dx, dy = tocubic.bz_eval(dbz, x) + ds = hypot(dx, dy) + s = ys[0] * arclen_ratio + dscore = ds * (tocubic.mod_2pi(atan2(dy, dx) - seg.th(s)) ** 2) + #print s, atan2(dy, dx), seg.th(s) + return [ds, dscore] + dt = 1./n + t = 0 + ys = [0, 0] + for i in range(n): + dydx = measure_derivs(t, ys) + tocubic.rk4(ys, dydx, t, dt, measure_derivs) + t += dt + return ys[1] + +def cubic_bz_to_pcorn(bz, thresh): + dx = bz[3][0] - bz[0][0] + dy = bz[3][1] - bz[0][1] + dx1 = bz[1][0] - bz[0][0] + dy1 = bz[1][1] - bz[0][1] + dx2 = bz[3][0] - bz[2][0] + dy2 = bz[3][1] - bz[2][1] + chth = atan2(dy, dx) + th0 = tocubic.mod_2pi(chth - atan2(dy1, dx1)) + th1 = tocubic.mod_2pi(atan2(dy2, dx2) - chth) + seg = pcorn.Segment(bz[0], bz[3], th0, th1) + err = measure_bz_cloth(seg, bz) + if err < thresh: + return [seg] + else: + # de Casteljau + x01, y01 = 0.5 * (bz[0][0] + bz[1][0]), 0.5 * (bz[0][1] + bz[1][1]) + x12, y12 = 0.5 * (bz[1][0] + bz[2][0]), 0.5 * (bz[1][1] + bz[2][1]) + x23, y23 = 0.5 * (bz[2][0] + bz[3][0]), 0.5 * (bz[2][1] + bz[3][1]) + xl2, yl2 = 0.5 * (x01 + x12), 0.5 * (y01 + y12) + xr1, yr1 = 0.5 * (x12 + x23), 0.5 * (y12 + y23) + xm, ym = 0.5 * (xl2 + xr1), 0.5 * (yl2 + yr1) + bzl = [bz[0], (x01, y01), (xl2, yl2), (xm, ym)] + bzr = [(xm, ym), (xr1, yr1), (x23, y23), bz[3]] + segs = cubic_bz_to_pcorn(bzl, 0.5 * thresh) + segs.extend(cubic_bz_to_pcorn(bzr, 0.5 * thresh)) + return segs + +def bzs_to_pcorn(bzs, thresh = 1e-9): + result = [] + for sp in bzs: + rsp = [] + for bz in sp: + if len(bz) == 2: + dx = bz[1][0] - bz[0][0] + dy = bz[1][1] - bz[0][1] + th = atan2(dy, dx) + rsp.append(pcorn.Segment(bz[0], bz[1], 0, 0)) + else: + rsp.extend(cubic_bz_to_pcorn(bz, thresh)) + result.append(rsp) + return result + +def plot_segs(segs): + for i in range(len(segs)): + seg = segs[i] + if i == 0: + print seg.z0[0], seg.z0[1], 'moveto' + print seg.z1[0], seg.z1[1], 'lineto' + print 'stroke' + for i in range(len(segs)): + seg = segs[i] + if i == 0: + print 'gsave', seg.z0[0], seg.z0[1], 'translate circle fill grestore' + print 'gsave', seg.z1[0], seg.z1[1], 'translate circle fill grestore' + +import sys + +def test_to_pcorn(): + C1 = 0.55228 + bz = [(100, 100), (100 + 400 * C1, 100), (500, 500 - 400 * C1), (500, 500)] + for i in range(0, 13): + thresh = .1 ** i + segs = cubic_bz_to_pcorn(bz, thresh) + plot_segs(segs) + print >> sys.stderr, thresh, len(segs) + print '0 20 translate' + +if __name__ == '__main__': + f = file(sys.argv[1]) + bzs = read_bz(f) + rsps = bzs_to_pcorn(bzs, 1) + #print rsps + tocubic.plot_prolog() + print 'grestore' + print '1 -1 scale 0 -720 translate' + print '/ss 1.5 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + tot = 0 + for segs in rsps: + curve = pcorn.Curve(segs) + #curve = offset.offset(curve, 10) + print '%', curve.arclen + print '%', curve.sstarts + if 0: + print 'gsave 1 0 0 setrgbcolor' + cmd = 'moveto' + for i in range(100): + s = i * .01 * curve.arclen + x, y = curve.xy(s) + th = curve.th(s) + sth = 5 * sin(th) + cth = 5 * cos(th) + print x, y, cmd + cmd = 'lineto' + print 'closepath stroke grestore' + for i in range(100): + s = i * .01 * curve.arclen + x, y = curve.xy(s) + th = curve.th(s) + sth = 5 * sin(th) + cth = 5 * cos(th) + if 0: + print x - cth, y - sth, 'moveto' + print x + cth, y + sth, 'lineto stroke' + if 1: + for s in curve.find_breaks(): + print 'gsave 0 1 0 setrgbcolor' + x, y = curve.xy(s) + print x, y, 'translate 2 dup scale circle fill' + print 'grestore' + #plot_segs(segs) + + print 'gsave 0 0 0 setrgbcolor' + optim = 3 + thresh = 1e-2 + new_bzs = tocubic.pcorn_curve_to_bzs(curve, optim, thresh) + tot += len(new_bzs) + plot_bzs([new_bzs], (0, 0), 1, True) + print 'grestore' + print 'grestore' + print '/Helvetica 12 selectfont' + print '36 720 moveto (thresh=%g optim=%d) show' % (thresh, optim) + print '( tot segs=%d) show' % tot + print 'showpage' + + #plot_bzs(bzs, (100, 100), 1) diff --git a/third_party/spiro/curves/mecsolve.py b/third_party/spiro/curves/mecsolve.py new file mode 100644 index 0000000..3840cea --- /dev/null +++ b/third_party/spiro/curves/mecsolve.py @@ -0,0 +1,859 @@ +from math import * +import array +import sys +import random + +import numarray as N +import numarray.linear_algebra as la + +def eps_prologue(x0, y0, x1, y1, draw_box = False): + print '%!PS-Adobe-3.0 EPSF' + print '%%BoundingBox:', x0, y0, x1, y1 + print '%%EndComments' + print '%%EndProlog' + print '%%Page: 1 1' + if draw_box: + print x0, y0, 'moveto', x0, y1, 'lineto', x1, y1, 'lineto', x1, y0, 'lineto closepath stroke' + +def eps_trailer(): + print '%%EOF' + +# One step of 4th-order Runge-Kutta numerical integration - update y in place +def rk4(y, dydx, x, h, derivs): + hh = h * .5 + h6 = h * (1./6) + xh = x + hh + yt = [] + for i in range(len(y)): + yt.append(y[i] + hh * dydx[i]) + dyt = derivs(xh, yt) + for i in range(len(y)): + yt[i] = y[i] + hh * dyt[i] + dym = derivs(xh, yt) + for i in range(len(y)): + yt[i] = y[i] + h * dym[i] + dym[i] += dyt[i] + dyt = derivs(x + h, yt) + for i in range(len(y)): + y[i] += h6 * (dydx[i] + dyt[i] + 2 * dym[i]) + +def run_elastica_half(sp, k0, lam1, lam2, n): + def mec_derivs(x, ys): + th, k = ys[2], ys[3] + dx, dy = cos(th), sin(th) + return [dx, dy, k, lam1 * dx + lam2 * dy, k * k] + ys = [0, 0, 0, k0, 0] + xyk = [(ys[0], ys[1], ys[3])] + n = max(1, int(sp * n)) + h = float(sp) / n + s = 0 + for i in range(n): + dydx = mec_derivs(s, ys) + rk4(ys, dydx, s, h, mec_derivs) + xyk.append((ys[0], ys[1], ys[3])) + return xyk, ys[2], ys[4] + +def run_elastica(sm, sp, k0, lam1, lam2, n = 100): + xykm, thm, costm = run_elastica_half(-sm, k0, -lam1, lam2, n) + xykp, thp, costp = run_elastica_half(sp, k0, lam1, lam2, n) + xyk = [] + for i in range(1, len(xykm)): + x, y, k = xykm[i] + xyk.append((-x, y, k)) + xyk.reverse() + xyk.extend(xykp) + x = xyk[-1][0] - xyk[0][0] + y = xyk[-1][1] - xyk[0][1] + th = thm + thp + sth, cth = sin(thm), cos(thm) + x, y = x * cth - y * sth, x * sth + y * cth + result = [] + maxk = 0 + for xt, yt, kt in xyk: + maxk = max(maxk, kt) + result.append((xt, yt, kt)) + cost = costm + costp + return result, cost, x, y, th + +def run_mec_cos_pos(k, lam1, lam2, n = 1000): + cost = 0 + th = 0 + x = 0 + y = 0 + dt = 1.0 / n + result = [(0, 0)] + for i in range(n): + k1 = lam1 * cos(th) + lam2 * sin(th) + + cost += dt * k * k + x += dt * cos(th) + y += dt * sin(th) + th += dt * k + + k += dt * k1 + result.append((x, y)) + return result, cost, x, y, th + +def run_mec_cos(k, lam1, lam2, n = 1000): + resp, costp, xp, yp, thp = run_mec_cos_pos(k*.5, lam1*.25, lam2*.25, n) + resm, costm, xm, ym, thm = run_mec_cos_pos(k*.5, lam1*-.25, lam2*.25, n) + cost = (costp + costm) * .5 + x, y = xp + xm, yp - ym + th = thp + thm + sth, cth = .5 * sin(thm), .5 * cos(thm) + x, y = x * cth - y * sth, x * sth + y * cth + result = [] + for i in range(1, n): + xt, yt = resm[n - i - 1] + result.append((-xt * cth - yt * sth, -xt * sth + yt * cth)) + for i in range(n): + xt, yt = resp[i] + result.append((xt * cth - yt * sth, xt * sth + yt * cth)) + return result, cost, x, y, th + +def cross_prod(a, b): + return [a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0]] + +def solve_mec(constraint_fnl): + delta = 1e-3 + params = [pi, 0, 0] + for i in range(20): + k, lam1, lam2 = params + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2) + #print i * .05, 'setgray' + #plot(xys) + c1c, c2c, costc = constraint_fnl(cost, x, y, th) + print '% constraint_fnl =', c1c, c2c, 'cost =', costc + + dc1s = [] + dc2s = [] + for j in range(len(params)): + params1 = N.array(params) + params1[j] += delta + k, lam1, lam2 = params1 + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2) + c1p, c2p, costp = constraint_fnl(cost, x, y, th) + params1 = N.array(params) + params1[j] -= delta + k, lam1, lam2 = params1 + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2) + c1m, c2m, costm = constraint_fnl(cost, x, y, th) + dc1s.append((c1p - c1m) / (2 * delta)) + dc2s.append((c2p - c2m) / (2 * delta)) + xp = cross_prod(dc1s, dc2s) + xp = N.divide(xp, sqrt(N.dot(xp, xp))) # Normalize to unit length + + print '% dc1s =', dc1s + print '% dc2s =', dc2s + print '% xp =', xp + + # Compute second derivative wrt orthogonal vec + params1 = N.array(params) + for j in range(len(params)): + params1[j] += delta * xp[j] + k, lam1, lam2 = params1 + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2) + c1p, c2p, costp = constraint_fnl(cost, x, y, th) + print '% constraint_fnl+ =', c1p, c2p, 'cost =', costp + params1 = N.array(params) + for j in range(len(params)): + params1[j] -= delta * xp[j] + k, lam1, lam2 = params1 + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2) + c1m, c2m, costm = constraint_fnl(cost, x, y, th) + print '% constraint_fnl- =', c1m, c2m, 'cost =', costm + d2cost = (costp + costm - 2 * costc) / (delta * delta) + dcost = (costp - costm) / (2 * delta) + + print '% dcost =', dcost, 'd2cost =', d2cost + if d2cost < 0: d2cost = .1 + # Make Jacobian matrix to invert + jm = N.array([dc1s, dc2s, [x * d2cost for x in xp]]) + #print jm + ji = la.inverse(jm) + #print ji + + dp = N.dot(ji, [c1c, c2c, dcost]) + print '% dp =', dp + print '% [right]=', [c1c, c2c, dcost] + params -= dp * .1 + print '%', params + sys.stdout.flush() + return params + +def solve_mec_3constr(constraint_fnl, n = 30, initparams = None): + delta = 1e-3 + if initparams: + params = N.array(initparams) + else: + params = [3.14, 0, 0] + for i in range(n): + k, lam1, lam2 = params + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2) + c1c, c2c, c3c = constraint_fnl(cost, x, y, th) + print '% constraint_fnl =', c1c, c2c, c3c + + dc1s = [] + dc2s = [] + dc3s = [] + for j in range(len(params)): + params1 = N.array(params) + params1[j] += delta + k, lam1, lam2 = params1 + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2) + c1p, c2p, c3p = constraint_fnl(cost, x, y, th) + params1 = N.array(params) + params1[j] -= delta + k, lam1, lam2 = params1 + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2) + c1m, c2m, c3m = constraint_fnl(cost, x, y, th) + dc1s.append((c1p - c1m) / (2 * delta)) + dc2s.append((c2p - c2m) / (2 * delta)) + dc3s.append((c3p - c3m) / (2 * delta)) + + # Make Jacobian matrix to invert + jm = N.array([dc1s, dc2s, dc3s]) + #print jm + ji = la.inverse(jm) + + dp = N.dot(ji, [c1c, c2c, c3c]) + if i < n/2: scale = .25 + else: scale = 1 + params -= scale * dp + print '%', params + return params + +def mk_ths_fnl(th0, th1): + def fnl(cost, x, y, th): + actual_th0 = atan2(y, x) + actual_th1 = th - actual_th0 + print '% x =', x, 'y =', y, 'hypot =', hypot(x, y) + return th0 - actual_th0, th1 - actual_th1, cost + return fnl + +def mk_y_fnl(th0, th1, ytarg): + def fnl(cost, x, y, th): + actual_th0 = atan2(y, x) + actual_th1 = th - actual_th0 + return th0 - actual_th0, th1 - actual_th1, ytarg - hypot(x, y) + return fnl + +def solve_mec_nested_inner(th0, th1, y, fnl): + innerfnl = mk_y_fnl(th0, th1, y) + params = [th0 + th1, 1e-6, 1e-6] + params = solve_mec_3constr(innerfnl, 10, params) + k, lam1, lam2 = params + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2, 100) + c1, c2, c3 = fnl(cost, x, y, th) + return c3, params + +# Solve (th0, th1) mec to minimize fnl; use solve_mec_3constr as inner loop +# Use golden section search +def solve_mec_nested(th0, th1, fnl): + R = .61803399 + C = 1 - R + ax, cx = .6, .85 + bx = .5 * (ax + cx) + + x0, x3 = ax, cx + if abs(cx - bx) > abs(bx - ax): + x1, x2 = bx, bx + C * (cx - bx) + else: + x1, x2 = bx - C * (bx - ax), bx + f1, p = solve_mec_nested_inner(th0, th1, x1, fnl) + f2, p = solve_mec_nested_inner(th0, th1, x2, fnl) + for i in range(10): + print '%', x0, x1, x2, x3, ':', f1, f2 + if f2 < f1: + x0, x1, x2 = x1, x2, R * x2 + C * x3 + f1 = f2 + f2, p = solve_mec_nested_inner(th0, th1, x2, fnl) + else: + x1, x2, x3 = R * x1 + C * x0, x1, x2 + f2 = f1 + f1, p = solve_mec_nested_inner(th0, th1, x1, fnl) + if f1 < f2: + x = x1 + else: + x = x2 + cc, p = solve_mec_nested_inner(th0, th1, x, fnl) + return p + +def plot(xys): + cmd = 'moveto' + for xy in xys: + print 306 + 200 * xy[0], 396 - 200 * xy[1], cmd + cmd = 'lineto' + print 'stroke' + +def mec_test(): + th0, th1 = 0, pi / 4 + fnl = mk_ths_fnl(th0, th1) + params = solve_mec_nested(th0, th1, fnl) + k, lam1, lam2 = params + xys, cost, x, y, th = run_mec_cos(k, lam1, lam2, 1000) + plot(xys) + print '% run_mec_cos:', cost, x, y, th + print '1 0 0 setrgbcolor' + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2) + print '%', xys + plot(xys) + print '% run_elastica:', cost, x, y, th + print 'showpage' + print '%', params + +def lenfig(): + print '306 720 translate' + th0, th1 = pi / 2, pi / 2 + for i in range(1, 10): + y = .1 * i + .003 + fnl = mk_y_fnl(th0, th1, y) + params = solve_mec_3constr(fnl) + k, lam1, lam2 = params + print 'gsave 0.5 dup scale -306 -396 translate' + xys, cost, x, y, th = run_elastica(-2, 2, k, lam1, lam2, 100) + print 'gsave [2] 0 setdash' + plot(xys) + print 'grestore' + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2, 100) + plot(xys) + print 'grestore' + print '% y =', y, 'params =', params + if lam2 < 0: + mymaxk = k + else: + mymaxk = sqrt(k * k + 4 * lam2) + lam = abs(lam2) / (mymaxk * mymaxk) + print '-200 0 moveto /Symbol 12 selectfont (l) show' + print '/Times-Roman 12 selectfont ( = %.4g) show' % lam + print '0 -70 translate' + print 'showpage' + +def lenplot(figno, th0, th1): + result = [] + if figno in (1, 2): + imax = 181 + elif figno == 3: + imax = 191 + for i in range(1, imax): + yy = .005 * i + if figno in (1, 2) and i == 127: + yy = 2 / pi + if figno == 3 and i == 165: + yy = .8270 + fnl = mk_y_fnl(th0, th1, yy) + params = solve_mec_3constr(fnl) + k, lam1, lam2 = params + xys, cost, x0, y0, th = run_elastica(-.5, .5, k, lam1, lam2, 100) + if lam2 < 0: + mymaxk = k + else: + mymaxk = sqrt(k * k + 4 * lam2) + lam = abs(lam2) / (mymaxk * mymaxk) + result.append((yy, lam, cost)) + return result + +def lengraph(figno): + if figno in (1, 2): + eps_prologue(15, 47, 585, 543) + th0, th1 = pi / 2, pi / 2 + yscale = 900 + ytic = .05 + ymax = 10 + elif figno == 3: + eps_prologue(15, 47, 585, 592) + th0, th1 = pi / 3, pi / 3 + yscale = 500 + ytic = .1 + ymax = 10 + x0 = 42 + xscale = 7.5 * 72 + y0 = 72 + print '/Times-Roman 12 selectfont' + print '/ss 1.5 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + print '.5 setlinewidth' + print x0, y0, 'moveto', xscale, '0 rlineto 0', yscale * ytic * ymax, 'rlineto', -xscale, '0 rlineto closepath stroke' + for i in range(0, 11): + print x0 + .1 * i * xscale, y0, 'moveto 0 6 rlineto stroke' + print x0 + .1 * i * xscale, y0 + ytic * ymax * yscale, 'moveto 0 -6 rlineto stroke' + print x0 + .1 * i * xscale, y0 - 12, 'moveto' + print '(%.1g) dup stringwidth exch -.5 mul exch rmoveto show' % (.1 * i) + for i in range(0, 11): + print x0, y0 + ytic * i * yscale, 'moveto 6 0 rlineto stroke' + print x0 + xscale, y0 + ytic * i * yscale, 'moveto -6 0 rlineto stroke' + print x0 - 3, y0 + ytic * i * yscale - 3.5, 'moveto' + print '(%.2g) dup stringwidth exch neg exch rmoveto show' % (ytic * i) + print x0 + .25 * xscale, y0 - 24, 'moveto (chordlen / arclen) show' + print x0 - 14, y0 + ytic * ymax * yscale + 10, 'moveto /Symbol 12 selectfont (l) show' + if figno in (1, 2): + print x0 + 2 / pi * xscale, y0 - 18, 'moveto' + print '(2/p) dup stringwidth exch -.5 mul exch rmoveto show' + print 'gsave [3] 0 setdash' + print x0, y0 + .25 * yscale, 'moveto', xscale, '0 rlineto stroke' + if figno == 3: + print x0, y0 + .5 * yscale, 'moveto', xscale, '0 rlineto stroke' + xinterest = .81153 + print x0 + xinterest * xscale, y0, 'moveto 0', yscale * .5, 'rlineto stroke' + print 'grestore' + print '.75 setlinewidth' + if 1: + if figno in (1, 2): + costscale = .01 * yscale + elif figno == 3: + costscale = .0187 * yscale + lenresult = lenplot(figno, th0, th1) + cmd = 'moveto' + for x, y, cost in lenresult: + print x0 + xscale * x, y0 + yscale * y, cmd + cmd = 'lineto' + print 'stroke' + if figno in (2, 3): + cmd = 'moveto' + for x, y, cost in lenresult: + print x0 + xscale * x, y0 + costscale * cost, cmd + cmd = 'lineto' + print 'stroke' + cmd = 'moveto' + for x, y, cost in lenresult: + print x0 + xscale * x, y0 + costscale * x * cost, cmd + cmd = 'lineto' + print 'stroke' + print '/Times-Roman 12 selectfont' + if figno == 2: + xlm, ylm = .75, 7 + xls, yls = .42, 15 + elif figno == 3: + xlm, ylm = .6, 3 + xls, yls = .37, 15 + print x0 + xscale * xlm, y0 + costscale * ylm, 'moveto' + print '(MEC cost) show' + print x0 + xscale * xls, y0 + costscale * yls, 'moveto' + print '(SIMEC cost) show' + if figno == 1: + minis = [(.05, 5, -5), + (.26, -40, 10), + (.4569466, -5, -10), + (.55, 35, 12), + + (.6026, -60, 45), + (.6046, -60, 15), + (.6066, -60, -15), + + (.6213, -22, 10), + (.6366198, 15, 22), + (.66, 20, 10), + (.9, 0, -10)] + elif figno == 2: + minis = [(.4569466, -5, -10), + (.6366198, 15, 22)] + elif figno == 3: + minis = [(.741, 55, -10), + (.81153, -30, 20), + (.8270, 20, 30)] + + for yy, xo, yo in minis: + fnl = mk_y_fnl(th0, th1, yy) + params = solve_mec_3constr(fnl) + k, lam1, lam2 = params + if lam2 < 0: + mymaxk = k + else: + mymaxk = sqrt(k * k + 4 * lam2) + lam = abs(lam2) / (mymaxk * mymaxk) + x = x0 + xscale * yy + y = y0 + yscale * lam + print 'gsave %g %g translate circle fill' % (x, y) + print '%g %g translate 0.15 dup scale' % (xo, yo) + print '-306 -396 translate' + print '2 setlinewidth' + if yy < .6 or yy > .61: + s = 2 + elif yy == .6046: + s = 2.8 + else: + s = 5 + xys, cost, x, y, th = run_elastica(-s, s, k, lam1, lam2, 100) + print 'gsave [10] 0 setdash' + plot(xys) + print 'grestore 6 setlinewidth' + xys, cost, x, y, th = run_elastica(-.5, .5, k, lam1, lam2, 100) + plot(xys) + print 'grestore' + + print 'showpage' + eps_trailer() + +def draw_axes(x0, y0, xscale, yscale, xmax, ymax, nx, ny): + print '.5 setlinewidth' + print '/Times-Roman 12 selectfont' + print x0, y0, 'moveto', xscale * xmax, '0 rlineto 0', yscale * ymax, 'rlineto', -xscale * xmax, '0 rlineto closepath stroke' + xinc = (xmax * xscale * 1.) / nx + yinc = (ymax * yscale * 1.) / ny + for i in range(0, nx + 1): + if i > 0 and i < nx + 1: + print x0 + xinc * i, y0, 'moveto 0 6 rlineto stroke' + print x0 + xinc * i, y0 + ymax * yscale, 'moveto 0 -6 rlineto stroke' + print x0 + xinc * i, y0 - 12, 'moveto' + print '(%.2g) dup stringwidth exch -.5 mul exch rmoveto show' % ((i * xmax * 1.) / nx) + for i in range(0, ny + 1): + if i > 0 and i < ny + 1: + print x0, y0 + yinc * i, 'moveto 6 0 rlineto stroke' + print x0 + xmax * xscale, y0 + yinc * i, 'moveto -6 0 rlineto stroke' + print x0 - 3, y0 + yinc * i - 3.5, 'moveto' + print '(%.2g) dup stringwidth exch neg exch rmoveto show' % ((i * ymax * 1.) / ny) + + +def mecchord(): + x0 = 72 + y0 = 72 + thscale = 150 + chscale = 400 + print '.5 setlinewidth' + print '/ss 1.5 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + draw_axes(x0, y0, thscale, chscale, 3.2, 1, 16, 10) + print x0 + 1 * thscale, y0 - 28, 'moveto (angle) show' + print 'gsave', x0 - 32, y0 + .25 * chscale, 'translate' + print '90 rotate 0 0 moveto (chordlen / arclen) show' + print 'grestore' + + cmd = 'moveto' + for i in range(0, 67): + s = i * .01 + xys, cost, x, y, th = run_elastica(-s, s, 4, 0, -8, 100) + #plot(xys) + if s > 0: + ch = hypot(x, y) / (2 * s) + else: + ch = 1 + print '%', s, x, y, th, ch + print x0 + thscale * th / 2, y0 + ch * chscale, cmd + cmd = 'lineto' + print 'stroke' + + print 'gsave %g %g translate circle fill' % (x0 + thscale * th / 2, y0 + ch * chscale) + print '-30 15 translate 0.25 dup scale' + print '-306 -396 translate' + print '2 setlinewidth' + plot(xys) + print 'grestore' + + print 'gsave [2] 0 setdash' + cmd = 'moveto' + for i in range(0, 151): + th = pi * i / 150. + if th > 0: + ch = sin(th) / th + else: + ch = 1 + print x0 + thscale * th, y0 + ch * chscale, cmd + cmd = 'lineto' + print 'stroke' + print 'grestore' + + k0 = 4 * .4536 / (2 / pi) + s = pi / (2 * k0) + xys, cost, x, y, th = run_elastica(-s, s, k0, 0, 0, 100) + th = pi + ch = 2 / pi + print 'gsave %g %g translate circle fill' % (x0 + thscale * th / 2, y0 + ch * chscale) + print '30 15 translate 0.25 dup scale' + print '-306 -396 translate' + print '2 setlinewidth' + plot(xys) + print 'grestore' + + print x0 + 1.25 * thscale, y0 + .55 * chscale, 'moveto (MEC) show' + print x0 + 2.3 * thscale, y0 + .35 * chscale, 'moveto (SIMEC) show' + + print 'showpage' + +def trymec(sm, sp): + xys, thm, cost = run_elastica_half(abs(sm), 0, 1, 0, 10) + xm, ym, km = xys[-1] + if sm < 0: + xm = -xm + ym = -ym + thm = -thm + xys, thp, cost = run_elastica_half(abs(sp), 0, 1, 0, 10) + xp, yp, kp = xys[-1] + if sp < 0: + xp = -xp + yp = -yp + thp = -thp + chth = atan2(yp - ym, xp - xm) + #print xm, ym, xp, yp, chth, thm, thp + actual_th0 = chth - thm + actual_th1 = thp - chth + return actual_th0, actual_th1 + +def findmec_old(th0, th1): + guess_ds = sqrt(6 * (th1 - th0)) + guess_savg = 2 * (th1 + th0) / guess_ds + sm = .5 * (guess_savg - guess_ds) + sp = .5 * (guess_savg + guess_ds) + #sm, sp = th0, th1 + for i in range(1): + actual_th0, actual_th1 = trymec(sm, sp) + guess_dth = 1./6 * (sp - sm) ** 2 + guess_avth = .5 * (sp + sm) * (sp - sm) + guess_th0 = .5 * (guess_avth - guess_dth) + guess_th1 = .5 * (guess_avth + guess_dth) + print sm, sp, actual_th0, actual_th1, guess_th0, guess_th1 + +def mecplots(): + print '2 dup scale' + print '-153 -296 translate' + scale = 45 + print '0.25 setlinewidth' + print 306 - scale * pi/2, 396, 'moveto', 306 + scale * pi/2, 396, 'lineto stroke' + print 306, 396 - scale * pi/2, 'moveto', 306, 396 + scale * pi/2, 'lineto stroke' + print '/ss .5 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + + tic = .1 + maj = 5 + r0, r1 = 0, 81 + + for i in range(r0, r1, maj): + sm = i * tic + cmd = 'moveto' + for j in range(i, r1): + sp = j * tic + 1e-3 + th0, th1 = trymec(sm, sp) + print '%', sm, sp, th0, th1 + print 306 + scale * th0, 396 + scale * th1, cmd + cmd = 'lineto' + print 'stroke' + for j in range(r0, r1, maj): + sp = j * tic + 1e-3 + cmd = 'moveto' + for i in range(0, j + 1): + sm = i * tic + th0, th1 = trymec(sm, sp) + print '%', sm, sp, th0, th1 + print 306 + scale * th0, 396 + scale * th1, cmd + cmd = 'lineto' + print 'stroke' + + for i in range(r0, r1, maj): + sm = i * tic + for j in range(i, r1, maj): + sp = j * tic + 1e-3 + th0, th1 = trymec(sm, sp) + print 'gsave' + print 306 + scale * th0, 596 + scale * th1, 'translate' + print 'circle fill' + uscale = 3 + xys, thm, cost = run_elastica_half(abs(sm), 0, 1, 0, 100) + x0, y0 = xys[-1][0], xys[-1][1] + if sm < 0: + x0, y0 = -x0, -y0 + xys, thm, cost = run_elastica_half(abs(sp), 0, 1, 0, 100) + x1, y1 = xys[-1][0], xys[-1][1] + if sp < 0: + x1, y1 = -x1, -y1 + cmd = 'moveto' + for xy in xys: + print xy[0] * uscale, xy[1] * uscale, cmd + cmd = 'lineto' + print 'stroke' + print '1 0 0 setrgbcolor' + print x0 * uscale, y0 * uscale, 'moveto' + print x1 * uscale, y1 * uscale, 'lineto stroke' + print 'grestore' + + print 'showpage' + +def findmec(th0, th1): + delta = 1e-3 + if th0 < 0 and th0 - th1 < .1: + sm = 5. + sp = 6. + else: + sm = 3. + sp = 5. + params = [sm, sp] + lasterr = 1e6 + for i in range(25): + sm, sp = params + ath0, ath1 = trymec(sm, sp) + c1c, c2c = th0 - ath0, th1 - ath1 + + err = c1c * c1c + c2c * c2c + if 0: + print '%findmec', sm, sp, ath0, ath1, err + sys.stdout.flush() + + if err < 1e-9: + return params + if err > lasterr: + return None + lasterr = err + + + dc1s = [] + dc2s = [] + for j in range(len(params)): + params1 = N.array(params) + params1[j] += delta + sm, sp = params1 + ath0, ath1 = trymec(sm, sp) + c1p, c2p = th0 - ath0, th1 - ath1 + + params1 = N.array(params) + params1[j] -= delta + sm, sp = params1 + ath0, ath1 = trymec(sm, sp) + c1m, c2m = th0 - ath0, th1 - ath1 + + dc1s.append((c1p - c1m) / (2 * delta)) + dc2s.append((c2p - c2m) / (2 * delta)) + + jm = N.array([dc1s, dc2s]) + ji = la.inverse(jm) + dp = N.dot(ji, [c1c, c2c]) + + if i < 4: + scale = .5 + else: + scale = 1 + params -= scale * dp + if params[0] < 0: params[0] = 0. + return params + +def mecrange(figtype): + scale = 130 + eps_prologue(50, 110, 570, 630) + print -50, 0, 'translate' + print '0.5 setlinewidth' + thlmin, thlmax = -pi/2, 2.4 + thrmin, thrmax = -2.2, pi / 2 + .2 + print 306 + scale * thlmin, 396, 'moveto', 306 + scale * thlmax, 396, 'lineto stroke' + print 306, 396 + scale * thrmin, 'moveto', 306, 396 + scale * thrmax, 'lineto stroke' + + print 'gsave [2] 0 setdash' + print 306, 396 + scale * pi / 2, 'moveto' + print 306 + scale * thlmax, 396 + scale * pi / 2, 'lineto stroke' + print 306 + scale * thlmin, 396 - scale * pi / 2, 'moveto' + print 306 + scale * thlmax, 396 - scale * pi / 2, 'lineto stroke' + print 306 + scale * pi / 2, 396 + scale * thrmin, 'moveto' + print 306 + scale * pi / 2, 396 + scale * thrmax, 'lineto stroke' + print 'grestore' + + print 306 + 3, 396 + scale * thrmax - 10, 'moveto' + print '/Symbol 12 selectfont (q) show' + print 0, -2, 'rmoveto' + print '/Times-Italic 9 selectfont (right) show' + + print 306 - 18, 396 + scale * pi / 2 - 4, 'moveto' + print '/Symbol 12 selectfont (p/2) show' + print 306 + scale * 2.2, 396 - scale * pi / 2 + 2, 'moveto' + print '/Symbol 12 selectfont (-p/2) show' + + print 306 + scale * pi/2 + 2, 396 + scale * thrmax - 10, 'moveto' + print '/Symbol 12 selectfont (p/2) show' + + print 306 + scale * 2.2, 396 + 6, 'moveto' + print '/Symbol 12 selectfont (q) show' + print 0, -2, 'rmoveto' + print '/Times-Italic 9 selectfont (left) show' + + print '/ss 0.8 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + cmd = 'moveto' + for i in range(0, 201): + th = (i * .005 - .75 )* pi + rmin = 1.5 + rmax = 2.5 + for j in range(20): + r = (rmin + rmax) * .5 + th0 = r * cos(th) + th1 = r * sin(th) + if findmec(th0, th1) == None: + rmax = r + else: + rmin = r + r = (rmin + rmax) * .5 + th0 = r * cos(th) + th1 = r * sin(th) + print '%', r, th, th0, th1 + print 306 + scale * th0, 396 + scale * th1, cmd + cmd = 'lineto' + sys.stdout.flush() + print 'stroke' + sys.stdout.flush() + + for i in range(-11, 12): + for j in range(-11, i + 1): + th0, th1 = i * .196, j * .196 + print '%', th0, th1 + params = findmec(th0, th1) + if params != None: + sm, sp = params + print 'gsave' + print 306 + scale * th0, 396 + scale * th1, 'translate' + uscale = 22 + k0, lam1, lam2 = justify_mec(sm, sp) + xys, cost, x, y, th = run_elastica(-.5, .5, k0, lam1, lam2) + cmdm = 'moveto' + dx = xys[-1][0] - xys[0][0] + dy = xys[-1][1] - xys[0][1] + ch = hypot(dx, dy) + chth = atan2(dy, dx) + if figtype == 'mecrange': + print 'circle fill' + s = uscale * sin(chth) / ch + c = uscale * cos(chth) / ch + h = -xys[0][0] * s + xys[0][1] * c + for xy in xys: + print xy[0] * c + xy[1] * s, h + xy[0] * s - xy[1] * c, cmdm + cmdm = 'lineto' + elif figtype == 'mecrangek': + ds = 1. / (len(xys) - 1) + sscale = 13. / ch + kscale = 3 * ch + print 'gsave .25 setlinewidth' + print sscale * -.5, 0, 'moveto', sscale, 0, 'rlineto stroke' + print 'grestore' + for l in range(len(xys)): + print sscale * (ds * l - 0.5), kscale * xys[l][2], cmdm + cmdm = 'lineto' + print 'stroke' + print 'grestore' + sys.stdout.flush() + print 'showpage' + eps_trailer() + +# given sm, sp in [0, 1, 0] mec, return params for -0.5..0.5 segment +def justify_mec(sm, sp): + sc = (sm + sp) * 0.5 + xys, thc, cost = run_elastica_half(sc, 0, 1, 0, 100) + print '% sc =', sc, 'thc =', thc + k0, lam1, lam2 = xys[-1][2], cos(thc), -sin(thc) + ds = sp - sm + k0 *= ds + lam1 *= ds * ds + lam2 *= ds * ds + return [k0, lam1, lam2] + +import sys +figname = sys.argv[1] +if len(figname) > 4 and figname[-4:] == '.pdf': figname = figname[:-4] +if figname in ('lengraph1', 'lengraph2', 'lengraph3'): + lengraph(int(figname[-1])) + #mec_test() + #lenfig() +elif figname == 'mecchord': + mecchord() +elif figname in ('mecrange', 'mecrangek'): + mecrange(figname) +elif figname == 'mecplots': + mecplots() +elif figname == 'findmec': + findmec(-.1, -.1) + findmec(-.2, -.2) + diff --git a/third_party/spiro/curves/mvc.py b/third_party/spiro/curves/mvc.py new file mode 100644 index 0000000..19ff927 --- /dev/null +++ b/third_party/spiro/curves/mvc.py @@ -0,0 +1,208 @@ +from math import * +import array +import sys +import random + +def run_mvc(k, k1, k2, k3, C, n = 100, do_print = False): + cmd = 'moveto' + result = array.array('d') + cost = 0 + th = 0 + x = 0 + y = 0 + dt = 1.0 / n + for i in range(n): + k4 = -k * (k * k2 - .5 * k1 * k1 + C) + + cost += dt * k1 * k1 + x += dt * cos(th) + y += dt * sin(th) + th += dt * k + + k += dt * k1 + k1 += dt * k2 + k2 += dt * k3 + k3 += dt * k4 + result.append(k) + if do_print: print 400 + 400 * x, 500 + 400 * y, cmd + cmd = 'lineto' + return result, cost, x, y, th + +def run_mec_cos(k, lam1, lam2, n = 100, do_print = False): + cmd = 'moveto' + result = array.array('d') + cost = 0 + th = 0 + x = 0 + y = 0 + dt = 1.0 / n + for i in range(n): + k1 = lam1 * cos(th) + lam2 * sin(th) + + cost += dt * k * k + x += dt * cos(th) + y += dt * sin(th) + th += dt * k + + k += dt * k1 + result.append(k) + if do_print: print 400 + 400 * x, 500 + 400 * y, cmd + cmd = 'lineto' + return result, cost, x, y, th + +def descend(params, fnl): + delta = 1 + for i in range(100): + best = fnl(params, i, True) + bestparams = params + for j in range(2 * len(params)): + ix = j / 2 + sign = 1 - 2 * (ix & 1) + newparams = params[:] + newparams[ix] += delta * sign + new = fnl(newparams, i) + if (new < best): + bestparams = newparams + best = new + if (bestparams == params): + delta *= .5 + print '%', params, delta + sys.stdout.flush() + params = bestparams + return bestparams + +def descend2(params, fnl): + delta = 20 + for i in range(5): + best = fnl(params, i, True) + bestparams = params + for j in range(100000): + newparams = params[:] + for ix in range(len(params)): + newparams[ix] += delta * (2 * random.random() - 1) + new = fnl(newparams, i) + if (new < best): + bestparams = newparams + best = new + if (bestparams == params): + delta *= .5 + params = bestparams + print '%', params, best, delta + sys.stdout.flush() + return bestparams + +def desc_eval(params, dfdp, fnl, i, x): + newparams = params[:] + for ix in range(len(params)): + newparams[ix] += x * dfdp[ix] + return fnl(newparams, i) + +def descend3(params, fnl): + dp = 1e-6 + for i in range(1000): + base = fnl(params, i, True) + dfdp = [] + for ix in range(len(params)): + newparams = params[:] + newparams[ix] += dp + new = fnl(newparams, i) + dfdp.append((new - base) / dp) + print '% dfdp = ', dfdp + xr = 0. + yr = base + xm = -1e-3 + ym = desc_eval(params, dfdp, fnl, i, xm) + if ym > yr: + while ym > yr: + xl, yl = xm, ym + xm = .618034 * xl + ym = desc_eval(params, dfdp, fnl, i, xm) + else: + xl = 1.618034 * xm + yl = desc_eval(params, dfdp, fnl, i, xl) + while ym > yl: + xm, ym = xl, yl + xl = 1.618034 * xm + yl = desc_eval(params, dfdp, fnl, i, xl) + + # We have initial bracket; ym < yl and ym < yr + + x0, x3 = xl, xr + if abs(xr - xm) > abs(xm - xl): + x1, y1 = xm, ym + x2 = xm + .381966 * (xr - xm) + y2 = desc_eval(params, dfdp, fnl, i, x2) + else: + x2, y2 = xm, ym + x1 = xm + .381966 * (xl - xm) + y1 = desc_eval(params, dfdp, fnl, i, x1) + for j in range(30): + if y2 < y1: + x0, x1, x2 = x1, x2, x2 + .381966 * (x3 - x2) + y0, y1 = y1, y2 + y2 = desc_eval(params, dfdp, fnl, i, x2) + else: + x1, x2, x3 = x1 + .381966 * (x0 - x1), x1, x2 + y1 = desc_eval(params, dfdp, fnl, i, x1) + if y1 < y2: + xbest = x1 + ybest = y1 + else: + xbest = x2 + ybest = y2 + for ix in range(len(params)): + params[ix] += xbest * dfdp[ix] + print '%', params, xbest, ybest + sys.stdout.flush() + return params + +def mk_mvc_fnl(th0, th1): + def fnl(params, i, do_print = False): + k, k1, k2, k3, C = params + ks, cost, x, y, th = run_mvc(k, k1, k2, k3, C, 100) + cost *= hypot(y, x) ** 3 + actual_th0 = atan2(y, x) + actual_th1 = th - actual_th0 + if do_print: print '%', x, y, actual_th0, actual_th1, cost + err = (th0 - actual_th0) ** 2 + (th1 - actual_th1) ** 2 + multiplier = 1000 + return cost + err * multiplier + return fnl + +def mk_mec_fnl(th0, th1): + def fnl(params, i, do_print = False): + k, lam1, lam2 = params + ks, cost, x, y, th = run_mec_cos(k, lam1, lam2) + cost *= hypot(y, x) + actual_th0 = atan2(y, x) + actual_th1 = th - actual_th0 + if do_print: print '%', x, y, actual_th0, actual_th1, cost + err = (th0 - actual_th0) ** 2 + (th1 - actual_th1) ** 2 + multiplier = 10 + return cost + err * multiplier + return fnl + +#ks, cost, x, y, th = run_mvc(0, 10, -10, 10, 200) +#print '%', cost, x, y +#print 'stroke showpage' + +def mvc_test(): + fnl = mk_mvc_fnl(-pi, pi/4) + params = [0, 0, 0, 0, 0] + params = descend3(params, fnl) + k, k1, k2, k3, C = params + run_mvc(k, k1, k2, k3, C, 100, True) + print 'stroke showpage' + print '%', params + +def mec_test(): + th0, th1 = pi/2, pi/2 + fnl = mk_mec_fnl(th0, th1) + params = [0, 0, 0] + params = descend2(params, fnl) + k, lam1, lam2 = params + run_mec_cos(k, lam1, lam2, 1000, True) + print 'stroke showpage' + print '%', params + +mvc_test() diff --git a/third_party/spiro/curves/numintsynth.py b/third_party/spiro/curves/numintsynth.py new file mode 100644 index 0000000..9a51b66 --- /dev/null +++ b/third_party/spiro/curves/numintsynth.py @@ -0,0 +1,176 @@ +# Synthesize a procedure to numerically integrate the 3rd order poly spiral + +tex = False + +if tex: + mulsym = ' ' +else: + mulsym = ' * ' + +class Poly: + def __init__(self, p0, coeffs): + self.p0 = p0 + self.coeffs = coeffs + def eval(self, x): + y = x ** self.p0 + z = 0 + for c in coeffs: + z += y * c + y *= x + return z + +def add(poly0, poly1, nmax): + lp0 = len(poly0.coeffs) + lp1 = len(poly1.coeffs) + p0 = min(poly0.p0, poly1.p0) + n = min(max(poly0.p0 + lp0, poly1.p1 + lp1), nmax) - p0 + if n <= 0: return Poly(0, []) + coeffs = [] + for i in range(n): + c = 0 + if i >= poly0.p0 - p0 and i < lp0 + poly0.p0 - p0: + c += poly0.coeffs[i + p0 - poly0.p0] + if i >= poly1.p0 - p0 and i < lp1 + poly1.p0 - p0: + c += poly1.coeffs[i + p0 - poly1.p0] + coeffs.append(c) + return Poly(p0, coeffs) + +def pr(str): + if tex: + print str, '\\\\' + else: + print '\t' + str + ';' + +def prd(str): + if tex: + print str, '\\\\' + else: + print '\tdouble ' + str + ';' + +def polymul(p0, p1, degree, basename, suppress_odd = False): + result = [] + for i in range(min(degree, len(p0) + len(p1) - 1)): + terms = [] + for j in range(i + 1): + if j < len(p0) and i - j < len(p1): + t0 = p0[j] + t1 = p1[i - j] + if t0 != None and t1 != None: + terms.append(t0 + mulsym + t1) + if terms == []: + result.append(None) + else: + var = basename % i + if (j % 2 == 0) or not suppress_odd: + prd(var + ' = ' + ' + '.join(terms)) + result.append(var) + return result + +def polysquare(p0, degree, basename): + result = [] + for i in range(min(degree, 2 * len(p0) - 1)): + terms = [] + for j in range((i + 1)/ 2): + if i - j < len(p0): + t0 = p0[j] + t1 = p0[i - j] + if t0 != None and t1 != None: + terms.append(t0 + mulsym + t1) + if len(terms) >= 1: + if tex and len(terms) == 1: + terms = ['2 ' + terms[0]] + else: + terms = ['2' + mulsym + '(' + ' + '.join(terms) + ')'] + if (i % 2) == 0: + t = p0[i / 2] + if t != None: + if tex: + terms.append(t + '^2') + else: + terms.append(t + mulsym + t) + if terms == []: + result.append(None) + else: + var = basename % i + prd(var + ' = ' + ' + '.join(terms)) + result.append(var) + return result + +def mkspiro(degree): + if tex: + us = ['u = 1'] + vs = ['v ='] + else: + us = ['u = 1'] + vs = ['v = 0'] + if tex: + tp = [None, 't_{11}', 't_{12}', 't_{13}', 't_{14}'] + else: + tp = [None, 't1_1', 't1_2', 't1_3', 't1_4'] + if tex: + prd(tp[1] + ' = k_0') + prd(tp[2] + ' = \\frac{k_1}{2}') + prd(tp[3] + ' = \\frac{k_2}{6}') + prd(tp[4] + ' = \\frac{k_3}{24}') + else: + prd(tp[1] + ' = km0') + prd(tp[2] + ' = .5 * km1') + prd(tp[3] + ' = (1./6) * km2') + prd(tp[4] + ' = (1./24) * km3') + tlast = tp + coef = 1. + for i in range(1, degree - 1): + tmp = [] + tcoef = coef + #print tlast + for j in range(len(tlast)): + c = tcoef / (j + 1) + if (j % 2) == 0 and tlast[j] != None: + if tex: + tmp.append('\\frac{%s}{%.0f}' % (tlast[j], 1./c)) + else: + if c < 1e-9: + cstr = '%.16e' % c + else: + cstr = '(1./%d)' % int(.5 + (1./c)) + tmp.append(cstr + ' * ' + tlast[j]) + tcoef *= .5 + if tmp != []: + sign = ('+', '-')[(i / 2) % 2] + var = ('u', 'v')[i % 2] + if tex: + if i == 1: pref = '' + else: pref = sign + ' ' + str = pref + (' ' + sign + ' ').join(tmp) + else: + str = var + ' ' + sign + '= ' + ' + '.join(tmp) + if var == 'u': us.append(str) + else: vs.append(str) + if i < degree - 1: + if tex: + basename = 't_{%d%%d}' % (i + 1) + else: + basename = 't%d_%%d' % (i + 1) + if i == 1: + tnext = polysquare(tp, degree - 1, basename) + t2 = tnext + elif i == 3: + tnext = polysquare(t2l, degree - 1, basename) + elif (i % 2) == 0: + tnext = polymul(tlast, tp, degree - 1, basename, True) + else: + tnext = polymul(t2l, t2, degree - 1, basename) + t2l = tlast + tlast = tnext + coef /= (i + 1) + if tex: + pr(' '.join(us)) + pr(' '.join(vs)) + else: + for u in us: + pr(u) + for v in vs: + pr(v) + +if __name__ == '__main__': + mkspiro(12) diff --git a/third_party/spiro/curves/offset.py b/third_party/spiro/curves/offset.py new file mode 100644 index 0000000..c528aec --- /dev/null +++ b/third_party/spiro/curves/offset.py @@ -0,0 +1,21 @@ +# offset curve of piecewise cornu curves + +from math import * + +import pcorn +from clothoid import mod_2pi + +def seg_offset(seg, d): + th0 = seg.th(0) + th1 = seg.th(seg.arclen) + z0 = [seg.z0[0] + d * sin(th0), seg.z0[1] - d * cos(th0)] + z1 = [seg.z1[0] + d * sin(th1), seg.z1[1] - d * cos(th1)] + chth = atan2(z1[1] - z0[1], z1[0] - z0[0]) + return [pcorn.Segment(z0, z1, mod_2pi(chth - th0), mod_2pi(th1 - chth))] + + +def offset(curve, d): + segs = [] + for seg in curve.segs: + segs.extend(seg_offset(seg, d)) + return pcorn.Curve(segs) diff --git a/third_party/spiro/curves/pcorn.py b/third_party/spiro/curves/pcorn.py new file mode 100644 index 0000000..723a82f --- /dev/null +++ b/third_party/spiro/curves/pcorn.py @@ -0,0 +1,160 @@ +# Utilities for piecewise cornu representation of curves + +from math import * + +import clothoid +import cornu + +class Segment: + def __init__(self, z0, z1, th0, th1): + self.z0 = z0 + self.z1 = z1 + self.th0 = th0 + self.th1 = th1 + self.compute() + def __repr__(self): + return '[' + `self.z0` + `self.z1` + ' ' + `self.th0` + ' ' + `self.th1` + ']' + def compute(self): + dx = self.z1[0] - self.z0[0] + dy = self.z1[1] - self.z0[1] + chord = hypot(dy, dx) + chth = atan2(dy, dx) + k0, k1 = clothoid.solve_clothoid(self.th0, self.th1) + charc = clothoid.compute_chord(k0, k1) + + self.chord = chord + self.chth = chth + self.k0, self.k1 = k0, k1 + self.charc = charc + self.arclen = chord / charc + self.thmid = self.chth - self.th0 + 0.5 * self.k0 - 0.125 * self.k1 + + self.setup_xy_fresnel() + + def setup_xy_fresnel(self): + k0, k1 = self.k0, self.k1 + if k1 == 0: k1 = 1e-6 # hack + if k1 != 0: + sqrk1 = sqrt(2 * abs(k1)) + t0 = (k0 - .5 * k1) / sqrk1 + t1 = (k0 + .5 * k1) / sqrk1 + (y0, x0) = cornu.eval_cornu(t0) + (y1, x1) = cornu.eval_cornu(t1) + chord_th = atan2(y1 - y0, x1 - x0) + chord = hypot(y1 - y0, x1 - x0) + scale = self.chord / chord + if k1 >= 0: + th = self.chth - chord_th + self.mxx = scale * cos(th) + self.myx = scale * sin(th) + self.mxy = -self.myx + self.myy = self.mxx + else: + th = self.chth + chord_th + self.mxx = scale * cos(th) + self.myx = scale * sin(th) + self.mxy = self.myx + self.myy = -self.mxx + # rotate -chord_th, flip top/bottom, rotate self.chth + self.x0 = self.z0[0] - (self.mxx * x0 + self.mxy * y0) + self.y0 = self.z0[1] - (self.myx * x0 + self.myy * y0) + + def th(self, s): + u = s / self.arclen - 0.5 + return self.thmid + (0.5 * self.k1 * u + self.k0) * u + + def xy(self, s): + # using fresnel integrals; polynomial approx might be better + u = s / self.arclen - 0.5 + k0, k1 = self.k0, self.k1 + if k1 == 0: k1 = 1e-6 # hack + if k1 != 0: + sqrk1 = sqrt(2 * abs(k1)) + t = (k0 + u * k1) / sqrk1 + (y, x) = cornu.eval_cornu(t) + return [self.x0 + self.mxx * x + self.mxy * y, + self.y0 + self.myx * x + self.myy * y] + + def find_extrema(self): + # find solutions of th(s) = 0 mod pi/2 + # todo: find extra solutions when there's an inflection + th0 = self.thmid + 0.125 * self.k1 - 0.5 * self.k0 + th1 = self.thmid + 0.125 * self.k1 + 0.5 * self.k0 + twooverpi = 2 / pi + n0 = int(floor(th0 * twooverpi)) + n1 = int(floor(th1 * twooverpi)) + if th1 > th0: signum = 1 + else: signum = -1 + result = [] + for i in range(n0, n1, signum): + th = pi/2 * (i + 0.5 * (signum + 1)) + a = .5 * self.k1 + b = self.k0 + c = self.thmid - th + if a == 0: + u1 = -c/b + u2 = 1000 + else: + sqrtdiscrim = sqrt(b * b - 4 * a * c) + u1 = (-b - sqrtdiscrim) / (2 * a) + u2 = (-b + sqrtdiscrim) / (2 * a) + if u1 >= -0.5 and u1 < 0.5: + result.append(self.arclen * (u1 + 0.5)) + if u2 >= -0.5 and u2 < 0.5: + result.append(self.arclen * (u2 + 0.5)) + return result + +class Curve: + def __init__(self, segs): + self.segs = segs + self.compute() + def compute(self): + arclen = 0 + sstarts = [] + for seg in self.segs: + sstarts.append(arclen) + arclen += seg.arclen + + self.arclen = arclen + self.sstarts = sstarts + def th(self, s, deltas = False): + u = s / self.arclen + s = self.arclen * (u - floor(u)) + if s == 0 and not deltas: s = self.arclen + i = 0 + while i < len(self.segs) - 1: + # binary search would make a lot of sense here + snext = self.sstarts[i + 1] + if s < snext or (not deltas and s == snext): + break + i += 1 + return self.segs[i].th(s - self.sstarts[i]) + def xy(self, s): + u = s / self.arclen + s = self.arclen * (u - floor(u)) + i = 0 + while i < len(self.segs) - 1: + # binary search would make a lot of sense here + if s <= self.sstarts[i + 1]: + break + i += 1 + return self.segs[i].xy(s - self.sstarts[i]) + def find_extrema(self): + result = [] + for i in range(len(self.segs)): + seg = self.segs[i] + for s in seg.find_extrema(): + result.append(s + self.sstarts[i]) + return result + def find_breaks(self): + result = [] + for i in range(len(self.segs)): + pseg = self.segs[(i + len(self.segs) - 1) % len(self.segs)] + seg = self.segs[i] + th = clothoid.mod_2pi(pseg.chth + pseg.th1 - (seg.chth - seg.th0)) + print '% pseg', pseg.chth + pseg.th1, 'seg', seg.chth - seg.th0 + pisline = pseg.k0 == 0 and pseg.k1 == 0 + sisline = seg.k0 == 0 and seg.k1 == 0 + if fabs(th) > 1e-3 or (pisline and not sisline) or (sisline and not pisline): + result.append(self.sstarts[i]) + return result diff --git a/third_party/spiro/curves/plot_solve_clothoid.py b/third_party/spiro/curves/plot_solve_clothoid.py new file mode 100644 index 0000000..c611069 --- /dev/null +++ b/third_party/spiro/curves/plot_solve_clothoid.py @@ -0,0 +1,164 @@ +import clothoid +from math import * + +print '%!PS-Adobe' + +def integ_spiro(k0, k1, k2, k3, n = 4): + th1 = k0 + th2 = .5 * k1 + th3 = (1./6) * k2 + th4 = (1./24) * k3 + ds = 1. / n + ds2 = ds * ds + ds3 = ds2 * ds + s = .5 * ds - .5 + + k0 *= ds + k1 *= ds + k2 *= ds + k3 *= ds + + x = 0 + y = 0 + + for i in range(n): + if n == 1: + km0 = k0 + km1 = k1 * ds + km2 = k2 * ds2 + else: + km0 = (((1./6) * k3 * s + .5 * k2) * s + k1) * s + k0 + km1 = ((.5 * k3 * s + k2) * s + k1) * ds + km2 = (k3 * s + k2) * ds2 + km3 = k3 * ds3 + + t1_1 = km0 + t1_2 = .5 * km1 + t1_3 = (1./6) * km2 + t1_4 = (1./24) * km3 + t2_2 = t1_1 * t1_1 + t2_3 = 2 * (t1_1 * t1_2) + t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2 + t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3) + t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3 + t2_7 = 2 * (t1_3 * t1_4) + t2_8 = t1_4 * t1_4 + t3_4 = t2_2 * t1_2 + t2_3 * t1_1 + t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1 + t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1 + t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2 + t4_4 = t2_2 * t2_2 + t4_5 = 2 * (t2_2 * t2_3) + t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3 + t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4) + t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4 + t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5) + t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5 + t5_6 = t4_4 * t1_2 + t4_5 * t1_1 + t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1 + t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1 + t6_6 = t4_4 * t2_2 + t6_7 = t4_4 * t2_3 + t4_5 * t2_2 + t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2 + t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2 + t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2 + t7_8 = t6_6 * t1_2 + t6_7 * t1_1 + t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1 + t8_8 = t6_6 * t2_2 + t8_9 = t6_6 * t2_3 + t6_7 * t2_2 + t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2 + t9_10 = t8_8 * t1_2 + t8_9 * t1_1 + t10_10 = t8_8 * t2_2 + u = 1 + u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8 + u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8 + (1./270336) * t4_10 + u -= (1./322560) * t6_6 + (1./1658880) * t6_8 + (1./8110080) * t6_10 + u += (1./92897280) * t8_8 + (1./454164480) * t8_10 + u -= 2.4464949595157930e-11 * t10_10 + v = (1./12) * t1_2 + (1./80) * t1_4 + v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8 + (1./67584) * t3_10 + v += (1./53760) * t5_6 + (1./276480) * t5_8 + (1./1351680) * t5_10 + v -= (1./11612160) * t7_8 + (1./56770560) * t7_10 + v += 2.4464949595157932e-10 * t9_10 + if n == 1: + x = u + y = v + else: + th = (((th4 * s + th3) * s + th2) * s + th1) * s + cth = cos(th) + sth = sin(th) + + x += cth * u - sth * v + y += cth * v + sth * u + s += ds + return [x * ds, y * ds] + +count_iter = 0 + +# Given th0 and th1 at endpoints (measured from chord), return k0 +# and k1 such that the clothoid k(s) = k0 + k1 s, evaluated from +# s = -.5 to .5, has the tangents given +def solve_clothoid(th0, th1, verbose = False): + global count_iter + + k1_old = 0 + e_old = th1 - th0 + k0 = th0 + th1 + k1 = 6 * (1 - ((.5 / pi) * k0) ** 3) * e_old + + # secant method + for i in range(10): + x, y = integ_spiro(k0, k1, 0, 0) + e = (th1 - th0) + 2 * atan2(y, x) - .25 * k1 + count_iter += 1 + if verbose: + print k0, k1, e + if abs(e) < 1e-9: break + k1_old, e_old, k1 = k1, e, k1 + (k1_old - k1) * e / (e - e_old) + + return k0, k1 + +def plot_by_thp(): + count = 0 + for i in range(11): + thp = i * .1 + print .5 + .05 * i, .5, .5, 'setrgbcolor' + print '.75 setlinewidth' + cmd = 'moveto' + for j in range(-40, 41): + thm = j * .02 + k0, k1 = solve_clothoid(thp - thm, thp + thm, True) + count += 1 + k1 = min(40, max(-40, k1)) + print 306 + 75 * thm, 396 - 10 * k1, cmd + cmd = 'lineto' + print 'stroke' + print '% count_iter = ', count_iter, 'for', count + +def plot_by_thm(): + print '.75 setlinewidth' + print 36, 396 - 350, 'moveto' + print 0, 700, 'rlineto stroke' + for i in range(-10, 10): + if i == 0: wid = 636 + else: wid = 5 + print 36, 396 - 10 * i, 'moveto', wid, '0 rlineto stroke' + cmd = 'moveto' + thm = -.1 + for i in range(41): + thp = i * .1 + k0, k1 = solve_clothoid(thp - thm, thp + thm) + print 36 + 150 * thp, 396 - 100 * k1, cmd + cmd = 'lineto' + print 'stroke' + print '0 0 1 setrgbcolor' + cmd = 'moveto' + for i in range(41): + thp = i * .1 + k1 = 12 * thm * cos(.5 * thp) + k1 = 12 * thm * (1 - (thp / pi) ** 3) + print 36 + 150 * thp, 396 - 100 * k1, cmd + cmd = 'lineto' + print 'stroke' + +plot_by_thp() diff --git a/third_party/spiro/curves/poly3.py b/third_party/spiro/curves/poly3.py new file mode 100644 index 0000000..daf6f87 --- /dev/null +++ b/third_party/spiro/curves/poly3.py @@ -0,0 +1,148 @@ +# Numerical techniques for solving 3rd order polynomial spline systems + +# The standard representation is the vector of derivatives at s=0, +# with -.5 <= s <= 5. +# +# Thus, \kappa(s) = k0 + k1 s + 1/2 k2 s^2 + 1/6 k3 s^3 + +from math import * + +def eval_cubic(a, b, c, d, x): + return ((d * x + c) * x + b) * x + a + +# integrate over s = [0, 1] +def int_3spiro_poly(ks, n): + x, y = 0, 0 + th = 0 + ds = 1.0 / n + th1, th2, th3, th4 = ks[0], .5 * ks[1], (1./6) * ks[2], (1./24) * ks[3] + k0, k1, k2, k3 = ks[0] * ds, ks[1] * ds, ks[2] * ds, ks[3] * ds + s = 0 + result = [(x, y)] + for i in range(n): + sm = s + 0.5 * ds + th = sm * eval_cubic(th1, th2, th3, th4, sm) + cth = cos(th) + sth = sin(th) + + km0 = ((1./6 * k3 * sm + .5 * k2) * sm + k1) * sm + k0 + km1 = ((.5 * k3 * sm + k2) * sm + k1) * ds + km2 = (k3 * sm + k2) * ds * ds + km3 = k3 * ds * ds * ds + #print km0, km1, km2, km3 + u = 1 - km0 * km0 / 24 + v = km1 / 24 + + u = 1 - km0 * km0 / 24 + (km0 ** 4 - 4 * km0 * km2 - 3 * km1 * km1) / 1920 + v = km1 / 24 + (km3 - 6 * km0 * km0 * km1) / 1920 + + x += cth * u - sth * v + y += cth * v + sth * u + result.append((ds * x, ds * y)) + + s += ds + + return result + +def integ_chord(k, n = 64): + ks = (k[0] * .5, k[1] * .25, k[2] * .125, k[3] * .0625) + xp, yp = int_3spiro_poly(ks, n)[-1] + ks = (k[0] * -.5, k[1] * .25, k[2] * -.125, k[3] * .0625) + xm, ym = int_3spiro_poly(ks, n)[-1] + dx, dy = .5 * (xp + xm), .5 * (yp + ym) + return hypot(dx, dy), atan2(dy, dx) + +# Return th0, th1, k0, k1 for given params +def calc_thk(ks): + chord, ch_th = integ_chord(ks) + th0 = ch_th - (-.5 * ks[0] + .125 * ks[1] - 1./48 * ks[2] + 1./384 * ks[3]) + th1 = (.5 * ks[0] + .125 * ks[1] + 1./48 * ks[2] + 1./384 * ks[3]) - ch_th + k0 = chord * (ks[0] - .5 * ks[1] + .125 * ks[2] - 1./48 * ks[3]) + k1 = chord * (ks[0] + .5 * ks[1] + .125 * ks[2] + 1./48 * ks[3]) + #print '%', (-.5 * ks[0] + .125 * ks[1] - 1./48 * ks[2] + 1./384 * ks[3]), (.5 * ks[0] + .125 * ks[1] + 1./48 * ks[2] + 1./384 * ks[3]), ch_th + return th0, th1, k0, k1 + +def calc_k1k2(ks): + chord, ch_th = integ_chord(ks) + k1l = chord * chord * (ks[1] - .5 * ks[2] + .125 * ks[3]) + k1r = chord * chord * (ks[1] + .5 * ks[2] + .125 * ks[3]) + k2l = chord * chord * chord * (ks[2] - .5 * ks[3]) + k2r = chord * chord * chord * (ks[2] + .5 * ks[3]) + return k1l, k1r, k2l, k2r + +def plot(ks): + ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) + pside = int_3spiro_poly(ksp, 64) + ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) + mside = int_3spiro_poly(ksm, 64) + mside.reverse() + for i in range(len(mside)): + mside[i] = (-mside[i][0], -mside[i][1]) + pts = mside + pside[1:] + cmd = "moveto" + for j in range(len(pts)): + x, y = pts[j] + print 306 + 300 * x, 400 + 300 * y, cmd + cmd = "lineto" + print "stroke" + x, y = pts[0] + print 306 + 300 * x, 400 + 300 * y, "moveto" + x, y = pts[-1] + print 306 + 300 * x, 400 + 300 * y, "lineto .5 setlinewidth stroke" + print "showpage" + +def solve_3spiro(th0, th1, k0, k1): + ks = [0, 0, 0, 0] + for i in range(5): + th0_a, th1_a, k0_a, k1_a = calc_thk(ks) + dth0 = th0 - th0_a + dth1 = th1 - th1_a + dk0 = k0 - k0_a + dk1 = k1 - k1_a + ks[0] += (dth0 + dth1) * 1.5 + (dk0 + dk1) * -.25 + ks[1] += (dth1 - dth0) * 15 + (dk0 - dk1) * 1.5 + ks[2] += (dth0 + dth1) * -12 + (dk0 + dk1) * 6 + ks[3] += (dth0 - dth1) * 360 + (dk1 - dk0) * 60 + #print '% ks =', ks + return ks + +def iter_spline(pts, ths, ks): + pass + +def solve_vee(): + kss = [] + for i in range(10): + kss.append([0, 0, 0, 0]) + thl = [0] * len(kss) + thr = [0] * len(kss) + k0l = [0] * len(kss) + k0r = [0] * len(kss) + k1l = [0] * len(kss) + k1r = [0] * len(kss) + k2l = [0] * len(kss) + k2r = [0] * len(kss) + for i in range(10): + for j in range(len(kss)): + thl[j], thr[j], k0l[j], k0r[j] = calc_thk(kss[j]) + k0l[j], k1r[j], k2l[j], k2r[j] = calc_k1k2(kss[j]) + for j in range(len(kss) - 1): + dth = thl[j + 1] + thr[j] + if j == 5: dth += .1 + dk0 = k0l[j + 1] - k0r[j] + dk1 = k1l[j + 1] - k1r[j] + dk2 = k2l[j + 1] - k2r[j] + + +if __name__ == '__main__': + k0 = pi * 3 + ks = [0, k0, -2 * k0, 0] + ks = [0, 0, 0, 0.01] + #plot(ks) + thk = calc_thk(ks) + print '%', thk + + ks = solve_3spiro(0, 0, 0, 0.001) + print '% thk =', calc_thk(ks) + #plot(ks) + print '%', ks + print calc_k1k2(ks) diff --git a/third_party/spiro/curves/polymat-bad.py b/third_party/spiro/curves/polymat-bad.py new file mode 100644 index 0000000..493fcd8 --- /dev/null +++ b/third_party/spiro/curves/polymat-bad.py @@ -0,0 +1,64 @@ +from Numeric import * +import LinearAlgebra as la +import sys + +n = 15 +m = zeros(((n + 1) * 4, (n + 1) * 4), Float) +for i in range(n): + m[4 * i + 2][4 * i + 0] = .5 + m[4 * i + 2][4 * i + 1] = -1./12 + m[4 * i + 2][4 * i + 2] = 1./48 + m[4 * i + 2][4 * i + 3] = -1./480 + m[4 * i + 2][4 * i + 4] = .5 + m[4 * i + 2][4 * i + 5] = 1./12 + m[4 * i + 2][4 * i + 6] = 1./48 + m[4 * i + 2][4 * i + 7] = 1./480 + + m[4 * i + 3][4 * i + 0] = 1 + m[4 * i + 3][4 * i + 1] = .5 + m[4 * i + 3][4 * i + 2] = .125 + m[4 * i + 3][4 * i + 3] = 1./48 + m[4 * i + 3][4 * i + 4] = -1 + m[4 * i + 3][4 * i + 5] = .5 + m[4 * i + 3][4 * i + 6] = -.125 + m[4 * i + 3][4 * i + 7] = 1./48 + + m[4 * i + 4][4 * i + 0] = 0 + m[4 * i + 4][4 * i + 1] = 1 + m[4 * i + 4][4 * i + 2] = .5 + m[4 * i + 4][4 * i + 3] = .125 + m[4 * i + 4][4 * i + 4] = 0 + m[4 * i + 4][4 * i + 5] = -1 + m[4 * i + 4][4 * i + 6] = .5 + m[4 * i + 4][4 * i + 7] = -.125 + + m[4 * i + 5][4 * i + 0] = 0 + m[4 * i + 5][4 * i + 1] = 0 + m[4 * i + 5][4 * i + 2] = 1 + m[4 * i + 5][4 * i + 3] = .5 + m[4 * i + 5][4 * i + 4] = 0 + m[4 * i + 5][4 * i + 5] = 0 + m[4 * i + 5][4 * i + 6] = -1 + m[4 * i + 5][4 * i + 7] = .5 + +m[n * 4 + 2][2] = 1 +m[n * 4 + 3][3] = 1 + +m[0][n * 4 + 2] = 1 +m[1][n * 4 + 3] = 1 + +def printarr(m): + for j in range(n * 4 + 4): + for i in range(n * 4 + 4): + print '%6.1f' % m[j][i], + print '' + +sys.output_line_width = 160 +#print array2string(m, precision = 3) +mi = la.inverse(m) +#printarr(mi) +print '' +for j in range(n + 1): + for k in range(4): + print '%7.2f' % mi[j * 4 + k][(n / 2) * 4 + 2], + print '' diff --git a/third_party/spiro/curves/polymat.py b/third_party/spiro/curves/polymat.py new file mode 100644 index 0000000..a05e2bf --- /dev/null +++ b/third_party/spiro/curves/polymat.py @@ -0,0 +1,399 @@ +import sys +from math import * + +from Numeric import * +import LinearAlgebra as la + +import poly3 + +n = 15 +m = zeros(((n + 1) * 4, (n + 1) * 4), Float) +for i in range(n): + m[4 * i + 2][4 * i + 0] = .5 + m[4 * i + 2][4 * i + 1] = -1./12 + m[4 * i + 2][4 * i + 2] = 1./48 + m[4 * i + 2][4 * i + 3] = -1./480 + m[4 * i + 2][4 * i + 4] = .5 + m[4 * i + 2][4 * i + 5] = 1./12 + m[4 * i + 2][4 * i + 6] = 1./48 + m[4 * i + 2][4 * i + 7] = 1./480 + + m[4 * i + 3][4 * i + 0] = 1 + m[4 * i + 3][4 * i + 1] = .5 + m[4 * i + 3][4 * i + 2] = .125 + m[4 * i + 3][4 * i + 3] = 1./48 + m[4 * i + 3][4 * i + 4] = -1 + m[4 * i + 3][4 * i + 5] = .5 + m[4 * i + 3][4 * i + 6] = -.125 + m[4 * i + 3][4 * i + 7] = 1./48 + + m[4 * i + 4][4 * i + 0] = 0 + m[4 * i + 4][4 * i + 1] = 1 + m[4 * i + 4][4 * i + 2] = .5 + m[4 * i + 4][4 * i + 3] = .125 + m[4 * i + 4][4 * i + 4] = 0 + m[4 * i + 4][4 * i + 5] = -1 + m[4 * i + 4][4 * i + 6] = .5 + m[4 * i + 4][4 * i + 7] = -.125 + + m[4 * i + 5][4 * i + 0] = 0 + m[4 * i + 5][4 * i + 1] = 0 + m[4 * i + 5][4 * i + 2] = 1 + m[4 * i + 5][4 * i + 3] = .5 + m[4 * i + 5][4 * i + 4] = 0 + m[4 * i + 5][4 * i + 5] = 0 + m[4 * i + 5][4 * i + 6] = -1 + m[4 * i + 5][4 * i + 7] = .5 + +m[n * 4 + 2][2] = 1 +m[n * 4 + 3][3] = 1 + +m[0][n * 4 + 2] = 1 +m[1][n * 4 + 3] = 1 + +def printarr(m): + for j in range(n * 4 + 4): + for i in range(n * 4 + 4): + print '%6.1f' % m[j][i], + print '' + +sys.output_line_width = 160 +#print array2string(m, precision = 3) +mi = la.inverse(m) +#printarr(mi) + +# fit arc to pts (0, 0), (x, y), and (1, 0), return th tangent to +# arc at (x, y) +def fit_arc(x, y): + th = atan2(y - 2 * x * y, y * y + x - x * x) + return th + +def mod_2pi(th): + u = th / (2 * pi) + return 2 * pi * (u - floor(u + 0.5)) + +def plot_path(path, th, k): + if path[0][2] == '{': closed = 0 + else: closed = 1 + j = 0 + cmd = 'moveto' + for i in range(len(path) + closed - 1): + i1 = (i + 1) % len(path) + x0, y0, t0 = path[i] + x1, y1, t1 = path[i1] + j1 = (j + 1) % len(th) + th0 = th[j] + k0 = k[j] + th1 = th[j1] + k1 = k[j1] + chord_th = atan2(y1 - y0, x1 - x0) + chord_len = hypot(y1 - y0, x1 - x0) + ks = poly3.solve_3spiro(mod_2pi(chord_th - th0), + mod_2pi(th1 - chord_th), + k0 * chord_len, k1 * chord_len) + ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) + pside = poly3.int_3spiro_poly(ksp, 64) + ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) + mside = poly3.int_3spiro_poly(ksm, 64) + mside.reverse() + for i in range(len(mside)): + mside[i] = (-mside[i][0], -mside[i][1]) + pts = mside + pside[1:] + rot = chord_th - atan2(pts[-1][1] - pts[0][1], pts[-1][0] - pts[0][0]) + scale = hypot(x1 - x0, y1 - y0) / hypot(pts[-1][1] - pts[0][1], pts[-1][0] - pts[0][0]) + u, v = scale * cos(rot), scale * sin(rot) + xt = x0 - u * pts[0][0] + v * pts[0][1] + yt = y0 - u * pts[0][1] - v * pts[0][0] + for x, y in pts: + print xt + u * x - v * y, yt + u * y + v * x, cmd + cmd = 'lineto' + if t1 == 'v': + j += 2 + else: + j += 1 + print 'stroke' + if 0: + j = 0 + for i in range(len(path)): + x0, y0, t0 = path[i] + print 'gsave', x0, y0, 'translate' + print ' ', th[j] * 180 / pi, 'rotate' + print ' -50 0 moveto 50 0 lineto stroke' + print 'grestore' + j += 1 + +def plot_ks(path, th, k): + if path[0][2] == '{': closed = 0 + else: closed = 1 + j = 0 + cmd = 'moveto' + xo = 36 + yo = 550 + xscale = .5 + yscale = 2000 + x = xo + for i in range(len(path) + closed - 1): + i1 = (i + 1) % len(path) + x0, y0, t0 = path[i] + x1, y1, t1 = path[i1] + j1 = (j + 1) % len(th) + th0 = th[j] + k0 = k[j] + th1 = th[j1] + k1 = k[j1] + chord_th = atan2(y1 - y0, x1 - x0) + chord_len = hypot(y1 - y0, x1 - x0) + ks = poly3.solve_3spiro(mod_2pi(chord_th - th0), + mod_2pi(th1 - chord_th), + k0 * chord_len, k1 * chord_len) + ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) + pside = poly3.int_3spiro_poly(ksp, 64) + ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) + mside = poly3.int_3spiro_poly(ksm, 64) + print '%', x0, y0, k0, k1 + print "% ks = ", ks + l = 2 * chord_len / hypot(pside[-1][0] + mside[-1][0], pside[-1][1] + mside[-1][1]) + for i in range(100): + s = .01 * i - 0.5 + kp = poly3.eval_cubic(ks[0], ks[1], .5 * ks[2], 1./6 * ks[3], s) + kp /= l + print x + xscale * l * (s + .5), yo + yscale * kp, cmd + cmd = 'lineto' + x += xscale * l + if t1 == 'v': + j += 2 + else: + j += 1 + print 'stroke' + print xo, yo, 'moveto', x, yo, 'lineto stroke' + +def make_error_vec(path, th, k): + if path[0][2] == '{': closed = 0 + else: closed = 1 + n = len(path) + v = zeros(n * 2, Float) + j = 0 + for i in range(len(path) + closed - 1): + i1 = (i + 1) % len(path) + x0, y0, t0 = path[i] + x1, y1, t1 = path[i1] + j1 = (j + 1) % len(th) + th0 = th[j] + k0 = k[j] + th1 = th[j1] + k1 = k[j1] + chord_th = atan2(y1 - y0, x1 - x0) + chord_len = hypot(y1 - y0, x1 - x0) + ks = poly3.solve_3spiro(mod_2pi(chord_th - th0), + mod_2pi(th1 - chord_th), + k0 * chord_len, k1 * chord_len) + ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) + pside = poly3.int_3spiro_poly(ksp, 64) + ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) + mside = poly3.int_3spiro_poly(ksm, 64) + l = chord_len / hypot(pside[-1][0] + mside[-1][0], pside[-1][1] + mside[-1][1]) + k1l = (ks[1] - .5 * ks[2] + .125 * ks[3]) / (l * l) + k1r = (ks[1] + .5 * ks[2] + .125 * ks[3]) / (l * l) + k2l = (ks[2] - .5 * ks[3]) / (l * l * l) + k2r = (ks[2] + .5 * ks[3]) / (l * l * l) + + if t0 == 'o' or t0 == '[' or t0 == 'v': + v[2 * j] -= k1l + v[2 * j + 1] -= k2l + elif t0 == 'c': + v[2 * j + 1] += k2l + + if t1 == 'o' or t1 == ']' or t1 == 'v': + v[2 * j1] += k1r + v[2 * j1 + 1] += k2r + elif t1 == 'c': + v[2 * j1] += k2r + + print "% left k'", k1l, "k''", k2l, "right k'", k1r, "k''", k2r + if t1 == 'v': + j += 2 + else: + j += 1 + print '% error vector:' + for i in range(n): + print '% ', v[2 * i], v[2 * i + 1] + return v + +def add_k1l(m, row, col, col1, l, s): + l2 = l * l + m[row][2 * col] += s * 36 / l2 + m[row][2 * col + 1] -= s * 9 / l + m[row][2 * col1] += s * 24 / l2 + m[row][2 * col1 + 1] += s * 3 / l + +def add_k1r(m, row, col, col1, l, s): + l2 = l * l + m[row][2 * col] += s * 24 / l2 + m[row][2 * col + 1] -= s * 3 / l + m[row][2 * col1] += s * 36 / l2 + m[row][2 * col1 + 1] += s * 9 / l + +def add_k2l(m, row, col, col1, l, s): + l2 = l * l + l3 = l2 * l + m[row][2 * col] -= s * 192 / l3 + m[row][2 * col + 1] += s * 36 / l2 + m[row][2 * col1] -= s * 168 / l3 + m[row][2 * col1 + 1] -= s * 24 / l2 + +def add_k2r(m, row, col, col1, l, s): + l2 = l * l + l3 = l2 * l + m[row][2 * col] += s * 168 / l3 + m[row][2 * col + 1] -= s * 24 / l2 + m[row][2 * col1] += s * 192 / l3 + m[row][2 * col1 + 1] += s * 36 / l2 + +def make_matrix(path, th, k): + if path[0][2] == '{': closed = 0 + else: closed = 1 + n = len(path) + m = zeros((n * 2, n * 2), Float) + j = 0 + for i in range(len(path) + closed - 1): + i1 = (i + 1) % len(path) + x0, y0, t0 = path[i] + x1, y1, t1 = path[i1] + j1 = (j + 1) % len(th) + th0 = th[j] + k0 = k[j] + th1 = th[j1] + k1 = k[j1] + chord_th = atan2(y1 - y0, x1 - x0) + chord_len = hypot(y1 - y0, x1 - x0) + ks = poly3.solve_3spiro(mod_2pi(chord_th - th0), + mod_2pi(th1 - chord_th), + k0 * chord_len, k1 * chord_len) + ksp = (ks[0] * .5, ks[1] * .25, ks[2] * .125, ks[3] * .0625) + pside = poly3.int_3spiro_poly(ksp, 64) + ksm = (ks[0] * -.5, ks[1] * .25, ks[2] * -.125, ks[3] * .0625) + mside = poly3.int_3spiro_poly(ksm, 64) + l = chord_len / hypot(pside[-1][0] + mside[-1][0], pside[-1][1] + mside[-1][1]) + + if t0 == 'o' or t0 == '[' or t0 == 'v': + add_k1l(m, 2 * j, j, j1, l, -1) + add_k2l(m, 2 * j + 1, j, j1, l, -1) + elif t0 == 'c': + add_k2l(m, 2 * j + 1, j, j1, l, 1) + + if t1 == 'o' or t1 == ']' or t1 == 'v': + add_k1r(m, 2 * j1, j, j1, l, 1) + add_k2r(m, 2 * j1 + 1, j, j1, l, 1) + elif t1 == 'c': + add_k2r(m, 2 * j1, j, j1, l, 1) + + if t1 == 'v': + j += 2 + else: + j += 1 + return m + +def solve(path): + if path[0][2] == '{': closed = 0 + else: closed = 1 + dxs = [] + dys = [] + chords = [] + for i in range(len(path) - 1): + dxs.append(path[i + 1][0] - path[i][0]) + dys.append(path[i + 1][1] - path[i][1]) + chords.append(hypot(dxs[-1], dys[-1])) + nominal_th = [] + nominal_k = [] + if not closed: + nominal_th.append(atan2(dys[0], dxs[0])) + nominal_k.append(0) + for i in range(1 - closed, len(path) - 1 + closed): + x0, y0, t0 = path[(i + len(path) - 1) % len(path)] + x1, y1, t1 = path[i] + x2, y2, t2 = path[(i + 1) % len(path)] + dx = float(x2 - x0) + dy = float(y2 - y0) + ir2 = dx * dx + dy * dy + x = ((x1 - x0) * dx + (y1 - y0) * dy) / ir2 + y = ((y1 - y0) * dx - (x1 - x0) * dy) / ir2 + th = fit_arc(x, y) + atan2(dy, dx) + bend_angle = mod_2pi(atan2(y2 - y1, x2 - x1) - atan2(y1 - y0, x1 - x0)) + k = 2 * bend_angle/(hypot(y2 - y1, x2 - x1) + hypot(y1 - y0, x1 - x0)) + print '% bend angle', bend_angle, 'k', k + if t1 == ']': + th = atan2(y1 - y0, x1 - x0) + k = 0 + elif t1 == '[': + th = atan2(y2 - y1, x2 - x1) + k = 0 + nominal_th.append(th) + nominal_k.append(k) + if not closed: + nominal_th.append(atan2(dys[-1], dxs[-1])) + nominal_k.append(0) + print '%', nominal_th + print '0 0 1 setrgbcolor .5 setlinewidth' + plot_path(path, nominal_th, nominal_k) + plot_ks(path, nominal_th, nominal_k) + th = nominal_th[:] + k = nominal_k[:] + n = 8 + for i in range(n): + ev = make_error_vec(path, th, k) + m = make_matrix(path, th, k) + #print m + #print 'inverse:' + #print la.inverse(m) + v = dot(la.inverse(m), ev) + #print v + for j in range(len(path)): + th[j] += 1. * v[2 * j] + k[j] -= 1. * .5 * v[2 * j + 1] + if i == n - 1: + print '0 0 0 setrgbcolor 1 setlinewidth' + elif i == 0: + print '1 0 0 setrgbcolor' + elif i == 1: + print '0 0.5 0 setrgbcolor' + elif i == 2: + print '0.3 0.3 0.3 setrgbcolor' + plot_path(path, th, k) + plot_ks(path, th, k) + print '% th:', th + print '% k:', k + +path = [(100, 100, 'o'), (200, 250, 'o'), (350, 225, 'o'), + (450, 350, 'c'), (450, 200, 'o'), (300, 100, 'o')] +if 0: + path = [(100, 480, '['), (100, 300, ']'), (300, 100, 'o'), + (500, 300, '['), (500, 480, ']'), (480, 500, '['), + (120, 500, ']')] + + +path = [(100, 480, ']'), (100, 120, '['), + (120, 100, ']'), (480, 100, '['), + (500, 120, ']'), (500, 480, '['), + (480, 500, ']'), (120, 500, '[')] + +path = [(100, 120, '['), (120, 100, ']'), + (140, 100, 'o'), (160, 100, 'o'), (180, 100, 'o'), (200, 100, 'o'), + (250, 250, 'o'), + (100, 200, 'o'), (100, 180, 'o'), (100, 160, 'o'), (100, 140, 'o')] + +path = [(100, 350, 'o'), (225, 350, 'o'), (350, 450, 'o'), + (450, 400, 'o'), (315, 205, 'o'), (300, 200, 'o'), + (285, 205, 'o')] + +if 0: + path = [] + path.append((350, 600, 'c')) + path.append((50, 450, 'c')) + for i in range(10): + path.append((50 + i * 30, 300 - i * 5, 'c')) + for i in range(11): + path.append((350 + i * 30, 250 + i * 5, 'c')) + path.append((650, 450, 'c')) + +solve(path) +print 'showpage' diff --git a/third_party/spiro/curves/tocubic.py b/third_party/spiro/curves/tocubic.py new file mode 100644 index 0000000..d70b9a2 --- /dev/null +++ b/third_party/spiro/curves/tocubic.py @@ -0,0 +1,461 @@ +# Some code to convert arbitrary curves to high quality cubics. + +# Some conventions: points are (x, y) pairs. Cubic Bezier segments are +# lists of four points. + +import sys + +from math import * + +import pcorn + +def pt_wsum(points, wts): + x, y = 0, 0 + for i in range(len(points)): + x += points[i][0] * wts[i] + y += points[i][1] * wts[i] + return x, y + +# Very basic spline primitives +def bz_eval(bz, t): + degree = len(bz) - 1 + mt = 1 - t + if degree == 3: + return pt_wsum(bz, [mt * mt * mt, 3 * mt * mt * t, 3 * mt * t * t, t * t * t]) + elif degree == 2: + return pt_wsum(bz, [mt * mt, 2 * mt * t, t * t]) + elif degree == 1: + return pt_wsum(bz, [mt, t]) + +def bz_deriv(bz): + degree = len(bz) - 1 + return [(degree * (bz[i + 1][0] - bz[i][0]), degree * (bz[i + 1][1] - bz[i][1])) for i in range(degree)] + +def bz_arclength(bz, n = 10): + # We're just going to integrate |z'| over the parameter [0..1]. + # The integration algorithm here is eqn 4.1.14 from NRC2, and is + # chosen for simplicity. Likely adaptive and/or higher-order + # algorithms would be better, but this should be good enough. + # Convergence should be quartic in n. + wtarr = (3./8, 7./6, 23./24) + dt = 1./n + s = 0 + dbz = bz_deriv(bz) + for i in range(0, n + 1): + if i < 3: + wt = wtarr[i] + elif i > n - 3: + wt = wtarr[n - i] + else: + wt = 1. + dx, dy = bz_eval(dbz, i * dt) + ds = hypot(dx, dy) + s += wt * ds + return s * dt + +# One step of 4th-order Runge-Kutta numerical integration - update y in place +def rk4(y, dydx, x, h, derivs): + hh = h * .5 + h6 = h * (1./6) + xh = x + hh + yt = [] + for i in range(len(y)): + yt.append(y[i] + hh * dydx[i]) + dyt = derivs(xh, yt) + for i in range(len(y)): + yt[i] = y[i] + hh * dyt[i] + dym = derivs(xh, yt) + for i in range(len(y)): + yt[i] = y[i] + h * dym[i] + dym[i] += dyt[i] + dyt = derivs(x + h, yt) + for i in range(len(y)): + y[i] += h6 * (dydx[i] + dyt[i] + 2 * dym[i]) + +def bz_arclength_rk4(bz, n = 10): + dbz = bz_deriv(bz) + def arclength_deriv(x, ys): + dx, dy = bz_eval(dbz, x) + return [hypot(dx, dy)] + dt = 1./n + t = 0 + ys = [0] + for i in range(n): + dydx = arclength_deriv(t, ys) + rk4(ys, dydx, t, dt, arclength_deriv) + t += dt + return ys[0] + +# z0 and z1 are start and end points, resp. +# th0 and th1 are the initial and final tangents, measured in the +# direction of the curve. +# aab is a/(a + b), where a and b are the lengths of the bezier "arms" +def fit_cubic_arclen(z0, z1, arclen, th0, th1, aab): + chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) + cth0, sth0 = cos(th0), sin(th0) + cth1, sth1 = -cos(th1), -sin(th1) + armlen = .66667 * chord + darmlen = 1e-6 * armlen + for i in range(10): + a = armlen * aab + b = armlen - a + bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), + (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] + actual_s = bz_arclength_rk4(bz) + if (abs(arclen - actual_s) < 1e-12): + break + a = (armlen + darmlen) * aab + b = (armlen + darmlen) - a + bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), + (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] + actual_s2 = bz_arclength_rk4(bz) + ds = (actual_s2 - actual_s) / darmlen + #print '% armlen = ', armlen + if ds == 0: + break + armlen += (arclen - actual_s) / ds + a = armlen * aab + b = armlen - a + bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), + (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] + return bz + +def mod_2pi(th): + u = th / (2 * pi) + return 2 * pi * (u - floor(u + 0.5)) + +def measure_bz(bz, arclen, th_fn, n = 1000): + dt = 1./n + dbz = bz_deriv(bz) + s = 0 + score = 0 + for i in range(n): + dx, dy = bz_eval(dbz, (i + .5) * dt) + ds = dt * hypot(dx, dy) + s += ds + score += ds * (mod_2pi(atan2(dy, dx) - th_fn(s)) ** 2) + return score + +def measure_bz_rk4(bz, arclen, th_fn, n = 10): + dbz = bz_deriv(bz) + def measure_derivs(x, ys): + dx, dy = bz_eval(dbz, x) + ds = hypot(dx, dy) + s = ys[0] + dscore = ds * (mod_2pi(atan2(dy, dx) - th_fn(s)) ** 2) + return [ds, dscore] + dt = 1./n + t = 0 + ys = [0, 0] + for i in range(n): + dydx = measure_derivs(t, ys) + rk4(ys, dydx, t, dt, measure_derivs) + t += dt + return ys[1] + +# th_fn() is a function that takes an arclength from the start point, and +# returns an angle - thus th_fn(0) and th_fn(arclen) are the initial and +# final tangents. +# z0, z1, and arclen are as fit_cubic_arclen +def fit_cubic(z0, z1, arclen, th_fn, fast = 1): + chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) + if (arclen < 1.000001 * chord): + return [z0, z1], 0 + th0 = th_fn(0) + th1 = th_fn(arclen) + imax = 4 + jmax = 10 + aabmin = 0 + aabmax = 1. + if fast: + imax = 1 + jmax = 0 + for i in range(imax): + for j in range(jmax + 1): + if jmax == 0: + aab = 0.5 * (aabmin + aabmax) + else: + aab = aabmin + (aabmax - aabmin) * j / jmax + bz = fit_cubic_arclen(z0, z1, arclen, th0, th1, aab) + score = measure_bz_rk4(bz, arclen, th_fn) + print '% aab =', aab, 'score =', score + sys.stdout.flush() + if j == 0 or score < best_score: + best_score = score + best_aab = aab + best_bz = bz + daab = .06 * (aabmax - aabmin) + aabmin = max(0, best_aab - daab) + aabmax = min(1, best_aab + daab) + print '%--- best_aab =', best_aab + return best_bz, best_score + +def plot_prolog(): + print '%!PS' + print '/m { moveto } bind def' + print '/l { lineto } bind def' + print '/c { curveto } bind def' + print '/z { closepath } bind def' + +def plot_bz(bz, z0, scale, do_moveto = True): + x0, y0 = z0 + if do_moveto: + print bz[0][0] * scale + x0, bz[0][1] * scale + y0, 'm' + if len(bz) == 4: + x1, y1 = bz[1][0] * scale + x0, bz[1][1] * scale + y0 + x2, y2 = bz[2][0] * scale + x0, bz[2][1] * scale + y0 + x3, y3 = bz[3][0] * scale + x0, bz[3][1] * scale + y0 + print x1, y1, x2, y2, x3, y3, 'c' + elif len(bz) == 2: + print bz[1][0] * scale + x0, bz[1][1] * scale + y0, 'l' + +def test_bz_arclength(): + bz = [(0, 0), (.5, 0), (1, 0.5), (1, 1)] + ans = bz_arclength_rk4(bz, 2048) + last = 1 + lastrk = 1 + for i in range(3, 11): + n = 1 << i + err = bz_arclength(bz, n) - ans + err_rk = bz_arclength_rk4(bz, n) - ans + print n, err, last / err, err_rk, lastrk / err_rk + last = err + lastrk = err_rk + +def test_fit_cubic_arclen(): + th = pi / 4 + arclen = th / sin(th) + bz = fit_cubic_arclen((0, 0), (1, 0), arclen, th, th, .5) + print '%', bz + plot_bz(bz, (100, 400), 500) + print 'stroke' + print 'showpage' + +# -- cornu fitting + +import cornu + +def cornu_to_cubic(t0, t1): + def th_fn(s): + return (s + t0) ** 2 + y0, x0 = cornu.eval_cornu(t0) + y1, x1 = cornu.eval_cornu(t1) + bz, score = fit_cubic((x0, y0), (x1, y1), t1 - t0, th_fn, 0) + return bz, score + +def test_draw_cornu(): + plot_prolog() + thresh = 1e-6 + print '/ss 1.5 def' + print '/circle { ss 0 moveto currentpoint exch ss sub exch ss 0 360 arc } bind def' + s0 = 0 + imax = 200 + x0, y0, scale = 36, 100, 500 + bzs = [] + for i in range(1, imax): + s = sqrt(i * .1) + bz, score = cornu_to_cubic(s0, s) + if score > (s - s0) * thresh or i == imax - 1: + plot_bz(bz, (x0, y0), scale, s0 == 0) + bzs.append(bz) + s0 = s + print 'stroke' + for i in range(len(bzs)): + bz = bzs[i] + bx0, by0 = x0 + bz[0][0] * scale, y0 + bz[0][1] * scale + bx1, by1 = x0 + bz[1][0] * scale, y0 + bz[1][1] * scale + bx2, by2 = x0 + bz[2][0] * scale, y0 + bz[2][1] * scale + bx3, by3 = x0 + bz[3][0] * scale, y0 + bz[3][1] * scale + print 'gsave 0 0 1 setrgbcolor .5 setlinewidth' + print bx0, by0, 'moveto', bx1, by1, 'lineto stroke' + print bx2, by2, 'moveto', bx3, by3, 'lineto stroke' + print 'grestore' + print 'gsave', bx0, by0, 'translate circle fill grestore' + print 'gsave', bx1, by1, 'translate .5 dup scale circle fill grestore' + print 'gsave', bx2, by2, 'translate .5 dup scale circle fill grestore' + print 'gsave', bx3, by3, 'translate circle fill grestore' + +# -- fitting of piecewise cornu curves + +def pcorn_segment_to_bzs_optim_inner(curve, s0, s1, thresh, nmax = None): + result = [] + if s0 == s1: return [], 0 + while s0 < s1: + def th_fn_inner(s): + if s > s1: s = s1 + return curve.th(s0 + s, s == 0) + z0 = curve.xy(s0) + z1 = curve.xy(s1) + bz, score = fit_cubic(z0, z1, s1 - s0, th_fn_inner, 0) + if score < thresh or nmax != None and len(result) == nmax - 1: + result.append(bz) + break + r = s1 + l = s0 + .001 * (s1 - s0) + for i in range(10): + smid = 0.5 * (l + r) + zmid = curve.xy(smid) + bz, score = fit_cubic(z0, zmid, smid - s0, th_fn_inner, 0) + if score > thresh: + r = smid + else: + l = smid + print '% s0=', s0, 'smid=', smid, 'actual score =', score + result.append(bz) + s0 = smid + print '% last actual score=', score + return result, score + +def pcorn_segment_to_bzs_optim(curve, s0, s1, thresh, optim): + result, score = pcorn_segment_to_bzs_optim_inner(curve, s0, s1, thresh) + bresult, bscore = result, score + if len(result) > 1 and optim > 2: + nmax = len(result) + gamma = 1./6 + l = score + r = thresh + for i in range(5): + tmid = (0.5 * (l ** gamma + r ** gamma)) ** (1/gamma) + result, score = pcorn_segment_to_bzs_optim_inner(curve, s0, s1, tmid, nmax) + if score < tmid: + l = max(l, score) + r = tmid + else: + l = tmid + r = min(r, score) + if max(score, tmid) < bscore: + bresult, bscore = result, max(score, tmid) + return result + +def pcorn_segment_to_bzs(curve, s0, s1, optim = 0, thresh = 1e-3): + if optim >= 2: + return pcorn_segment_to_bzs_optim(curve, s0, s1, thresh, optim) + z0 = curve.xy(s0) + z1 = curve.xy(s1) + fast = (optim == 0) + def th_fn(s): + return curve.th(s0 + s, s == 0) + bz, score = fit_cubic(z0, z1, s1 - s0, th_fn, fast) + if score < thresh: + return [bz] + else: + smid = 0.5 * (s0 + s1) + result = pcorn_segment_to_bzs(curve, s0, smid, optim, thresh) + result.extend(pcorn_segment_to_bzs(curve, smid, s1, optim, thresh)) + return result + +def pcorn_curve_to_bzs(curve, optim = 3, thresh = 1e-3): + result = [] + extrema = curve.find_extrema() + extrema.extend(curve.find_breaks()) + extrema.sort() + print '%', extrema + for i in range(len(extrema)): + s0 = extrema[i] + if i == len(extrema) - 1: + s1 = extrema[0] + curve.arclen + else: + s1 = extrema[i + 1] + result.extend(pcorn_segment_to_bzs(curve, s0, s1, optim, thresh)) + return result + +import struct + +def fit_cubic_arclen_forplot(z0, z1, arclen, th0, th1, aab): + chord = hypot(z1[0] - z0[0], z1[1] - z0[1]) + cth0, sth0 = cos(th0), sin(th0) + cth1, sth1 = -cos(th1), -sin(th1) + armlen = .66667 * chord + darmlen = 1e-6 * armlen + for i in range(10): + a = armlen * aab + b = armlen - a + bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), + (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] + actual_s = bz_arclength_rk4(bz) + if (abs(arclen - actual_s) < 1e-12): + break + a = (armlen + darmlen) * aab + b = (armlen + darmlen) - a + bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), + (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] + actual_s2 = bz_arclength_rk4(bz) + ds = (actual_s2 - actual_s) / darmlen + #print '% armlen = ', armlen + armlen += (arclen - actual_s) / ds + a = armlen * aab + b = armlen - a + bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), + (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] + return bz, a, b + +def plot_errors_2d(t0, t1, as_ppm): + xs = 1024 + ys = 1024 + if as_ppm: + print 'P6' + print xs, ys + print 255 + def th_fn(s): + return (s + t0) ** 2 + y0, x0 = cornu.eval_cornu(t0) + y1, x1 = cornu.eval_cornu(t1) + z0 = (x0, y0) + z1 = (x1, y1) + chord = hypot(y1 - y0, x1 - x0) + arclen = t1 - t0 + th0 = th_fn(0) + th1 = th_fn(arclen) + cth0, sth0 = cos(th0), sin(th0) + cth1, sth1 = -cos(th1), -sin(th1) + + for y in range(ys): + b = .8 * chord * (ys - y - 1) / ys + for x in range(xs): + a = .8 * chord * x / xs + bz = [z0, (z0[0] + cth0 * a, z0[1] + sth0 * a), + (z1[0] + cth1 * b, z1[1] + sth1 * b), z1] + s_bz = bz_arclength(bz, 10) + def th_fn_scaled(s): + return (s * arclen / s_bz + t0) ** 2 + score = measure_bz_rk4(bz, arclen, th_fn_scaled, 10) + if as_ppm: + ls = -log(score) + color_th = ls + darkstep = 0 + if s_bz > arclen: + g0 = 128 - darkstep + else: + g0 = 128 + darkstep + sc = 127 - darkstep + rr = g0 + sc * cos(color_th) + gg = g0 + sc * cos(color_th + 2 * pi / 3) + bb = g0 + sc * cos(color_th - 2 * pi / 3) + sys.stdout.write(struct.pack('3B', rr, gg, bb)) + else: + print a, b, score + if not as_ppm: + print + +def plot_arclen(t0, t1): + def th_fn(s): + return (s + t0) ** 2 + y0, x0 = cornu.eval_cornu(t0) + y1, x1 = cornu.eval_cornu(t1) + z0 = (x0, y0) + z1 = (x1, y1) + chord = hypot(y1 - y0, x1 - x0) + arclen = t1 - t0 + th0 = th_fn(0) + th1 = th_fn(arclen) + for i in range(101): + aab = i * .01 + bz, a, b = fit_cubic_arclen_forplot(z0, z1, arclen, th0, th1, aab) + print a, b + +if __name__ == '__main__': + #test_bz_arclength() + test_draw_cornu() + #run_one_cornu_seg() + #plot_errors_2d(.5, 1.0, False) + #plot_arclen(.5, 1.0) diff --git a/third_party/spiro/font/Makefile b/third_party/spiro/font/Makefile new file mode 100644 index 0000000..f6096a6 --- /dev/null +++ b/third_party/spiro/font/Makefile @@ -0,0 +1,3 @@ +CFLAGS = -Wall -O2 -g +LDLIBS = -lm +all: blend segment diff --git a/third_party/spiro/font/blend.c b/third_party/spiro/font/blend.c new file mode 100644 index 0000000..87a540f --- /dev/null +++ b/third_party/spiro/font/blend.c @@ -0,0 +1,372 @@ +#include +#include +#include +#include + +typedef struct { + const char *fn; + int xs; + int ys; + unsigned char *buf; +} pgm; + +typedef struct { + int xs; + int ys; + int *sum; + int *count; +} blendbuf; + +static void +die (char *why) +{ + fprintf (stderr, "%s\n", why); + exit (1); +} + +#define MAX_SIZE 65536 + +pgm *load_pgm(const char *fn) +{ + FILE *fi = fopen(fn, "rb"); + pgm *result; + char buf[256]; + int xs, ys; + int depth; + + if (fi == NULL) + return NULL; + + fgets (buf, sizeof(buf), fi); + if (buf[0] != 'P' || buf[1] != '5') + die ("Need pgmraw image on input"); + + xs = ys = 0; + do + fgets (buf, sizeof(buf), fi); + while (buf[0] == '#'); + sscanf (buf, "%d %d", &xs, &ys); + if (xs <= 0 || ys <= 0 || xs > MAX_SIZE || ys > MAX_SIZE) + die ("Input image size out of range"); + + do + fgets (buf, sizeof(buf), fi); + while (buf[0] == '#'); + sscanf (buf, "%d", &depth); + if (depth != 255) + die ("Only works with depth=255 images"); + + result = (pgm *)malloc(sizeof(pgm)); + + result->fn = fn; + result->xs = xs; + result->ys = ys; + result->buf = (unsigned char *)malloc(xs * ys); + + fread(result->buf, 1, xs * ys, fi); + fprintf(stderr, "loaded file %s %dx%d\n", fn, xs, ys); + fclose(fi); + return result; +} + +int +align_pgms(const pgm *p1, const pgm *p2, int *px, int *py) +{ + int xo, yo; + int xa, ya; + int best = 0x7fffffff; + + xa = (p1->xs < p2->xs ? p1->xs : p2->xs) - 20; + ya = (p1->ys < p2->ys ? p1->ys : p2->ys) - 20; + + for (yo = -10; yo <= 10; yo++) + for (xo = -10; xo <= 10; xo++) { + int sum = 0; + int i, j; + + for (j = 0; j < ya; j++) + for (i = 0; i < xa; i++) { + int g1 = p1->buf[(j + 10) * p1->xs + i + 10]; + int g2 = p2->buf[(j + 10 - yo) * p2->xs + i - xo + 10]; + sum += (g1 - g2) * (g1 - g2); + } + if (sum < best) { + best = sum; + *px = xo; + *py = yo; + } + } + return best; +} + +blendbuf * +new_blendbuf(int xs, int ys) +{ + blendbuf *result = (blendbuf *)malloc(sizeof(blendbuf)); + int i; + + result->xs = xs; + result->ys = ys; + result->sum = (int *)malloc(sizeof(int) * xs * ys); + result->count = (int *)malloc(sizeof(int) * xs * ys); + for (i = 0; i < xs * ys; i++) { + result->sum[i] = 0; + result->count[i] = 0; + } + + return result; +} + +void +add_pgm(blendbuf *bb, pgm *p, int xo, int yo) +{ + int i, j; + + for (j = 0; j < p->ys; j++) { + if (j + yo >= 0 && j + yo < bb->ys) { + for (i = 0; i < p->xs; i++) { + if (i + xo >= 0 && i + xo < bb->xs) { + int ix = (j + yo) * bb->xs + i + xo; + bb->sum[ix] += p->buf[j * p->xs + i]; + bb->count[ix]++; + } + } + } + } +} + +pgm * +pgm_from_blendbuf(blendbuf *bb) +{ + int xs = bb->xs; + int ys = bb->ys; + pgm *result = (pgm *)malloc(sizeof(pgm)); + int i, j; + + result->xs = xs; + result->ys = ys; + result->buf = (unsigned char *)malloc(xs * ys); + + for (j = 0; j < ys; j++) { + for (i = 0; i < xs; i++) { + int ix = j * xs + i; + unsigned char g; + if (bb->count[ix]) + g = (bb->sum[ix] + (bb->count[ix] >> 1)) / bb->count[ix]; + else + g = 255; + result->buf[ix] = g; + } + } + return result; +} + +pgm * +pgm_from_blendbuf_enhanced(blendbuf *bb, double sharp, double gamma) +{ + int xs = bb->xs; + int ys = bb->ys; + pgm *result = (pgm *)malloc(sizeof(pgm)); + int i, j; + double ming = 255, maxg = 0; + double *tmpbuf; + + result->xs = xs; + result->ys = ys; + result->buf = (unsigned char *)malloc(xs * ys); + + tmpbuf = (double *)malloc(xs * ys * sizeof(double)); + + for (j = 0; j < ys; j++) { + for (i = 0; i < xs; i++) { + int ix = j * xs + i; + double g; + if (bb->count[ix]) { + g = ((double)bb->sum[ix]) / bb->count[ix]; + if (g < ming) ming = g; + if (g > maxg) maxg = g; + } else + g = 255.0; + tmpbuf[ix] = g; + } + } + for (j = 0; j < ys; j++) { + for (i = 0; i < xs; i++) { + int ix = j * xs + i; + double g; + if (bb->count[ix]) { + int u, v; + int cnt = 0; + double sum = 0; + + for (v = -1; v <= 1; v++) { + for (u = -1; u <= 1; u++) { + if (i + u >= 0 && i + u < xs && + j + v >= 0 && j + v < ys && + bb->count[(j + v) * xs + i + u]) { + sum += tmpbuf[(j + v) * xs + i + u]; + cnt += 1; + } + } + } + + g = (1 + sharp) * tmpbuf[ix] - sharp * sum / cnt; + g = (g - ming) / (maxg - ming); + if (g < 0) g = 0; + if (g > 1) g = 1; + g = pow(g, gamma); + } else + g = 1; + result->buf[ix] = (int)(255 * g + 0.5); + } + } + free(tmpbuf); + return result; +} + +void +print_pgm(pgm *p, FILE *f) +{ + fprintf(f, "P5\n%d %d\n255\n", p->xs, p->ys); + fwrite(p->buf, 1, p->xs * p->ys, f); +} + +void +threshold(pgm *p) +{ + int i; + + for (i = 0; i < p->xs * p->ys; i++) + p->buf[i] = p->buf[i] > 128 ? 1 : 0; +} + +int +classify(pgm **pgmlist, int n_pgm) +{ + int *class = (int *)malloc(sizeof(int) * n_pgm); + int n_class = 0; + int i, j; + int tshift = 4; + + for (i = 0; i < n_pgm; i++) + class[i] = -1; + + for (i = 0; i < n_pgm; i++) { + pgm *pi = pgmlist[i]; + + if (class[i] == -1) { + class[i] = n_class++; + for (j = i + 1; j < n_pgm; j++) { + pgm *pj = pgmlist[j]; + int xo, yo; + int score; + + if (abs(pi->xs - pj->xs) < 10 && + abs(pi->ys - pj->ys) < 10) { + score = align_pgms(pi, pj, &xo, &yo); + if (score < ((pi->xs - 20) * (pi->ys - 20)) >> tshift) { + class[j] = class[i]; + } + } + } + } + printf("%s: class%d\n", pi->fn, class[i]); + fflush(stdout); + } + free(class); + return 0; +} + + +static int +intcompar(const void *a, const void *b) { + return *((int *)a) - *((int *)b); +} + +int +do_blend(pgm **pgmlist, int n_pgm, int n_passes, float thresh) +{ + blendbuf *bb; + int pass; + int i; + pgm *base = pgmlist[0]; + int *scores, *scores2, *xos, *yos; + + scores = (int *)malloc(n_pgm * sizeof(int)); + scores2 = (int *)malloc(n_pgm * sizeof(int)); + xos = (int *)malloc(n_pgm * sizeof(int)); + yos = (int *)malloc(n_pgm * sizeof(int)); + + for (pass = 0; pass < n_passes; pass++) { + int scorethresh; + + bb = new_blendbuf(base->xs, base->ys); + for (i = 0; i < n_pgm; i++) { + int xo, yo; + int score = align_pgms(base, pgmlist[i], &xo, &yo); + fprintf(stderr, "%s: score = %d, offset = %d, %d\n", + pgmlist[i]->fn, score, xo, yo); + scores[i] = score; + xos[i] = xo; + yos[i] = yo; + } + + if (pass == 0) { + scorethresh = 0x7fffffff; + } else { + memcpy(scores2, scores, n_pgm * sizeof(int)); + qsort(scores2, n_pgm, sizeof(int), intcompar); + scorethresh = scores2[(int)(thresh * n_pgm)]; + } + + for (i = 0; i < n_pgm; i++) { + if (pass > 0) + fprintf(stderr, "%s: score = %d %s\n", + pgmlist[i]->fn, scores[i], + scores[i] <= scorethresh ? "ok" : "-"); + if (scores[i] <= scorethresh) + add_pgm(bb, pgmlist[i], xos[i], yos[i]); + } + + if (pass == n_passes - 1) + base = pgm_from_blendbuf_enhanced(bb, 5, 0.5); + else + base = pgm_from_blendbuf(bb); + if (pass != n_passes - 1) + fprintf(stderr, "\n"); + } + + free(scores); + free(xos); + free(yos); + print_pgm(base, stdout); + return 0; +} + +int +main(int argc, char **argv) +{ + int i; + int n_pgm = 0; + pgm **pgmlist = (pgm **)malloc(sizeof(pgm *) * argc - 1); + int do_class = 0; + int n_pass = 2; + float thresh = 0.90; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-c")) + do_class = 1; + else + pgmlist[n_pgm++] = load_pgm(argv[i]); + } + + if (do_class) { + for (i = 0; i < n_pgm; i++) + threshold(pgmlist[i]); + return classify(pgmlist, n_pgm); + } else { + return do_blend(pgmlist, n_pgm, n_pass, thresh); + } + + return 0; +} diff --git a/third_party/spiro/font/cut.py b/third_party/spiro/font/cut.py new file mode 100644 index 0000000..541efa8 --- /dev/null +++ b/third_party/spiro/font/cut.py @@ -0,0 +1,52 @@ +import sys + +athresh = 100 +border = 20 + +segf = sys.argv[1] +if len(sys.argv) > 2: + pref = sys.argv[2] +else: + pref = '/tmp/cut' +rects = [] +starts = {} +for l in file(segf).xreadlines(): + ls = l.split() + if len(ls) == 6 and ls[-1] == 'rect': + r = map(int, ls[:4]) + area = (r[2] - r[0]) * (r[3] - r[1]) + if area > athresh: + rpad = [r[0] - border, r[1] - border, r[2] + border, r[3] + border] + if not starts.has_key(rpad[1]): + starts[rpad[1]] = [] + starts[rpad[1]].append(len(rects)) + rects.append(rpad) +inf = sys.stdin +l = inf.readline() +if l != 'P5\n': + raise 'expected pgm file' +while 1: + l = inf.readline() + if l[0] != '#': break +x, y = map(int, l.split()) +l = inf.readline() + +active = {} +for j in range(y): + if starts.has_key(j): + for ix in starts[j]: + r = rects[ix] + ofn = pref + '%04d.pgm' % ix + of = file(ofn, 'w') + active[ix] = of + print >> of, 'P5' + print >> of, r[2] - r[0], r[3] - r[1] + print >> of, '255' + buf = inf.read(x) + for ix, of in active.items(): + r = rects[ix] + of.write(buf[r[0]:r[2]]) + if j == r[3] - 1: + of.close() + del active[ix] + diff --git a/third_party/spiro/font/mkblends.py b/third_party/spiro/font/mkblends.py new file mode 100644 index 0000000..09fe882 --- /dev/null +++ b/third_party/spiro/font/mkblends.py @@ -0,0 +1,16 @@ +import os, sys + +glyphmap = {} +for ln in file(sys.argv[1]).xreadlines(): + fnglyph = ln.strip().split(': ') + if len(fnglyph) == 2: + fn, name = fnglyph + pgmf = fn[:-4] + '.pgm' + if not glyphmap.has_key(name): + glyphmap[name] = [] + glyphmap[name].append(pgmf) +for name in glyphmap.iterkeys(): + cmd = '~/garden/font/blend ' + ' '.join(glyphmap[name]) + ' | pnmtopng > ' + name + '.png' + print cmd + os.system(cmd) + diff --git a/third_party/spiro/font/replace_class.py b/third_party/spiro/font/replace_class.py new file mode 100644 index 0000000..9a1da51 --- /dev/null +++ b/third_party/spiro/font/replace_class.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +import sys +srfile = file(sys.argv[1]) +table = {} +for line in srfile.xreadlines(): + clas, repl = line.split() + if repl[-1] not in '-?': + table['class' + clas] = repl + +for line in sys.stdin.xreadlines(): + fn, clas = line.split() + if table.has_key(clas): + print fn, table[clas] diff --git a/third_party/spiro/font/segment.c b/third_party/spiro/font/segment.c new file mode 100644 index 0000000..8eb6b06 --- /dev/null +++ b/third_party/spiro/font/segment.c @@ -0,0 +1,194 @@ +#include +#include +#include + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +typedef struct { + int x0; + int x1; + int y0; + int y1; + double xmom; + double ymom; + int area; +} seg; + +typedef struct { + int x0; + int x1; +} run; + +void +find_runs(run *result, const unsigned char *buf, unsigned char thresh, int xs) +{ + int x; + int j = 0; + + for (x = 0; x < xs;) { + int x0, x1; + for (x0 = x; x0 < xs; x0++) + if (buf[x0] < thresh) + break; + if (x0 == xs) + break; + for (x1 = x0 + 1; x1 < xs; x1++) + if (buf[x1] >= thresh) + break; + result[j].x0 = x0; + result[j].x1 = x1; + j++; + x = x1 + 1; + } + result[j].x0 = 0; + result[j].x1 = 0; +} + +void +print_rect(const seg *r) +{ + printf("%d %d %d %d %g rect\n", r->x0, r->y0, r->x1, r->y1, + r->area / (1.0 * (r->x1 - r->x0) * (r->y1 - r->y0))); + printf("%g %g ci\n", r->xmom / r->area, r->ymom / r->area); +} + +void +merge_runs(seg *buf, const run *new_runs, int y) +{ + int bi, ni; + int bs; + int bi0; + int flag; + + for (bs = 0; buf[bs].x1; bs++); + + bi = 0; + flag = 1; + for (ni = 0; new_runs[ni].x1; ni++) { + int run_len = new_runs[ni].x1 - new_runs[ni].x0; + + bi0 = bi; + for (; bi < bs && buf[bi].x1 <= new_runs[ni].x0; bi++) { + if (flag) { + buf[bi].y1 = y; + print_rect(&buf[bi]); + } else { + bi0 = bi + 1; + flag = 1; + } + } + if (bi > bi0) { + memmove(&buf[bi0], &buf[bi], (bs - bi) * sizeof(seg)); + bs += bi0 - bi; + } + bi = bi0; + if (bi < bs && buf[bi].x0 < new_runs[ni].x1) { + double xmom = buf[bi].xmom; + double ymom = buf[bi].ymom; + int area = buf[bi].area; + int y0 = buf[bi].y0; + + for (bi = bi + 1; bi < bs && buf[bi].x0 < new_runs[ni].x1; bi++) { + y0 = MIN(y0, buf[bi].y0); + xmom += buf[bi].xmom; + ymom += buf[bi].ymom; + area += buf[bi].area; + } + buf[bi0].x0 = MIN(buf[bi0].x0, new_runs[ni].x0); + buf[bi0].x1 = MAX(buf[bi - 1].x1, new_runs[ni].x1); + buf[bi0].y0 = y0; + buf[bi0].xmom = xmom + run_len * .5 * (new_runs[ni].x0 + new_runs[ni].x1); + buf[bi0].ymom = ymom + run_len * y; + buf[bi0].area = area + run_len; + if (bi > bi0 + 1) { + memmove(&buf[bi0 + 1], &buf[bi], (bs - bi) * sizeof(seg)); + bs += bi0 + 1 - bi; + } + bi = bi0; + flag = 0; + } else { + memmove(&buf[bi + 1], &buf[bi], (bs - bi) * sizeof(seg)); + bs++; + buf[bi].x0 = new_runs[ni].x0; + buf[bi].x1 = new_runs[ni].x1; + buf[bi].y0 = y; + buf[bi].xmom = run_len * .5 * (new_runs[ni].x0 + new_runs[ni].x1); + buf[bi].ymom = run_len * y; + buf[bi].area = run_len; + bi++; + flag = 1; + } + } + bi0 = bi; + for (; bi < bs; bi++) { + if (flag) { + buf[bi].y1 = y; + print_rect(&buf[bi]); + } else { + bi0 = bi + 1; + flag = 1; + } + } + buf[bi0].x0 = 0; + buf[bi0].x1 = 0; +} + +static void +die (char *why) +{ + fprintf (stderr, "%s\n", why); + exit (1); +} + +#define MAX_SIZE 65536 + +int +main (int argc, char **argv) +{ + FILE *fi = stdin; + FILE *fo = stdout; + char buf[256]; + int xs, ys; + int depth; + unsigned char *imgbuf; + seg *segs; + run *runs; + int y; + + fgets (buf, sizeof(buf), fi); + if (buf[0] != 'P' || buf[1] != '5') + die ("Need pgmraw image on input"); + + xs = ys = 0; + do + fgets (buf, sizeof(buf), fi); + while (buf[0] == '#'); + sscanf (buf, "%d %d", &xs, &ys); + if (xs <= 0 || ys <= 0 || xs > MAX_SIZE || ys > MAX_SIZE) + die ("Input image size out of range"); + + do + fgets (buf, sizeof(buf), fi); + while (buf[0] == '#'); + sscanf (buf, "%d", &depth); + if (depth != 255) + die ("Only works with depth=255 images"); + + runs = (run *)malloc(((xs + 3) >> 1) * sizeof(run)); + segs = (seg *)malloc(((xs + 3) >> 1) * sizeof(seg)); + imgbuf = (unsigned char *)malloc (xs); + segs[0].x0 = 0; + segs[0].x1 = 0; + for (y = 0; y < ys; y++) { + fread (imgbuf, 1, xs, fi); + find_runs(runs, imgbuf, 160, xs); + merge_runs(segs, runs, y); + } + + free(imgbuf); + free(runs); + free(segs); + + return 0; +} diff --git a/third_party/spiro/ppedit/Makefile b/third_party/spiro/ppedit/Makefile new file mode 100644 index 0000000..589fdc1 --- /dev/null +++ b/third_party/spiro/ppedit/Makefile @@ -0,0 +1,25 @@ +TARGET = gtk + +ifeq ($(TARGET),gtk) + X3_PLAT = X3_GTK + X3_INCL = `pkg-config --cflags gtk+-2.0` + X3_LIBS = `pkg-config --libs gtk+-2.0` +endif + +ifeq ($(TARGET),carbon) + X3_PLAT = X3_CARBON + X3_LIBS = -framework Carbon +endif + +ifeq ($(TARGET),win32) + X3_PLAT = X3_WIN32 + X3_LIBS = -lgdi32 +endif + +CFLAGS = -g -Wall -D$(X3_PLAT) $(X3_INCL) -I../x3/ +LDFLAGS = -g +LDLIBS = $(X3_LIBS) + +all: ppedit + +ppedit: ppedit.o cornu.o bezctx.o bezctx_x3.o bezctx_hittest.o plate.o sexp.o image.o bezctx_ps.o spiro.o ../x3/x3$(TARGET).o ../x3/x3common.o diff --git a/third_party/spiro/ppedit/Makefile_gtk1 b/third_party/spiro/ppedit/Makefile_gtk1 new file mode 100644 index 0000000..9523375 --- /dev/null +++ b/third_party/spiro/ppedit/Makefile_gtk1 @@ -0,0 +1,5 @@ +CFLAGS = -Wall -g `gtk-config --cflags` `libart2-config --cflags` +LDLIBS = `gtk-config --libs` `libart2-config --libs` +all: ppedit_gtk1 + +ppedit_gtk1: ppedit_gtk1.o cornu.o bezctx.o bezctx_libart.o bezctx_hittest.o plate.o sexp.o image.o bezctx_ps.o spiro.o diff --git a/third_party/spiro/ppedit/README b/third_party/spiro/ppedit/README new file mode 100644 index 0000000..c99037d --- /dev/null +++ b/third_party/spiro/ppedit/README @@ -0,0 +1,112 @@ +README for ppedit + +Raph Levien +4 May 2007 + +ppedit is my prototype application for editing curves using my +curvature-continuous spirals. While I have used this code to draw many +font outlines, it is very rough around the edges, and is far from a +polished tool. + + +== License and patent grant == + +All code in this package is released under the terms of the GNU GPL, +version 2 or later, at your choice. + +Further, there is a provisional patent application filed for the +underlying curve technology. The following patent grant applies to any +patent which may be issued as a result of that application: + +Whereas, Raph Levien (hereinafter "Inventor") has obtained patent +protection for related technology (hereinafter "Patented Technology"), +Inventor wishes to aid the the GNU free software project in achieving +its goals, and Inventor also wishes to increase public awareness of +Patented Technology, Inventor hereby grants a fully paid up, +nonexclusive, irrevocable, royalty free license to practice the +patents listed below ("the Patents") if and only if practiced in +conjunction with software distributed under the terms of any version +of the GNU General Public License as published by the Free Software +Foundation, 59 Temple Place, Suite 330, Boston, MA 02111. Inventor +reserves all other rights, including without limitation, licensing for +software not distributed under the GNU General Public License. + +== Building == + +The main build supported right now is the Gtk2/cairo one. There's also +a Mac build and a Gtk1 one, but those aren't guaranteed to work. + +1. Make sure you've got ../x3/ in a directory parallel to ppedit. If + you've unpacked from a tarball, this should be the case already. + From darcs, use: darcs get http://levien.com/garden/x3 + +2. make + +3. The binary is ppedit + +== Using == + +The numeric keys 1-6 select the mode. 1 is selection, 2-6 select +different point modes: + +2: Add G4-continuous curve point +3: Add corner point +4: Add left-facing one-way point +5: Add right-facing one-way point +6: Add G2-continuous curve point + +Note: Dave Crossland has a set of alternate keybindings which are +probably faster. + +== Plate files == + +Ctrl-S saves a plate file in a file of the name 'plate'. Additionally, +a plate file can be given as a command line argument. The file uses +simple S-expressions, with a one-character code for each point, then +the X and Y coordinates - 0,0 is top left. + +Here's the cap U from Inconsolata, for example: + +(plate + (v 68 78) + (v 159 78) + (o 158 92) + ([ 148 115) + (] 148 552) + (o 298 744) + ([ 459 549) + (v 459 78) + (v 536 78) + (] 536 547) + (o 295 813) + ([ 68 551) + (z) +) + +v: corner +o: g4 +c: g2 +[: left-facing one-way +]: right-facing one-way + +== Conversion to PostScript == + +Ctrl-P converts to PostScript, saving '/tmp/foo.ps'. Other utilities +can convert that representation into FontForge, and also optimize the +Beziers. + +== Stability == + +The spline solver in this release is _not_ numerically robust. When +you start drawing random points, you'll quickly run into divergence. +However, "sensible" plates based on real fonts usually converge. Some +tips: + +1. Huge changes of angle are likely to diverge. + +2. For the first two or three points, G4 points are likelier to + converge than G2's. For longer segments, G2 is more likely. + +3. Start on a curve point. + +A more numerically robust approach is in the works. diff --git a/third_party/spiro/ppedit/bezctx.c b/third_party/spiro/ppedit/bezctx.c new file mode 100644 index 0000000..722f5db --- /dev/null +++ b/third_party/spiro/ppedit/bezctx.c @@ -0,0 +1,48 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include "bezctx.h" + +void bezctx_moveto(bezctx *bc, double x, double y, int is_open) +{ + bc->moveto(bc, x, y, is_open); +} + +void bezctx_lineto(bezctx *bc, double x, double y) +{ + bc->lineto(bc, x, y); +} + +void bezctx_quadto(bezctx *bc, double x1, double y1, double x2, double y2) +{ + bc->quadto(bc, x1, y1, x2, y2); +} + +void bezctx_curveto(bezctx *bc, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bc->curveto(bc, x1, y1, x2, y2, x3, y3); +} + +void bezctx_mark_knot(bezctx *bc, int knot_idx) +{ + if (bc->mark_knot) + bc->mark_knot(bc, knot_idx); +} diff --git a/third_party/spiro/ppedit/bezctx.h b/third_party/spiro/ppedit/bezctx.h new file mode 100644 index 0000000..057312e --- /dev/null +++ b/third_party/spiro/ppedit/bezctx.h @@ -0,0 +1,10 @@ +#include "bezctx_intf.h" + +struct _bezctx { + void (*moveto)(bezctx *bc, double x, double y, int is_open); + void (*lineto)(bezctx *bc, double x, double y); + void (*quadto)(bezctx *bc, double x1, double y1, double x2, double y2); + void (*curveto)(bezctx *bc, double x1, double y1, double x2, double y2, + double x3, double y3); + void (*mark_knot)(bezctx *bc, int knot_idx); +}; diff --git a/third_party/spiro/ppedit/bezctx_hittest.c b/third_party/spiro/ppedit/bezctx_hittest.c new file mode 100644 index 0000000..5a3d549 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_hittest.c @@ -0,0 +1,261 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_hittest.h" +#include + +typedef struct { + bezctx base; + double x, y; + + double x0, y0; + int knot_idx; + + int knot_idx_min; + double r_min; +} bezctx_hittest; + +static void +bezctx_hittest_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_hittest *bc = (bezctx_hittest *)z; + + bc->x0 = x; + bc->y0 = y; +} + +static void +bezctx_hittest_lineto(bezctx *z, double x, double y) { + bezctx_hittest *bc = (bezctx_hittest *)z; + double x0 = bc->x0; + double y0 = bc->y0; + double dx = x - x0; + double dy = y - y0; + double dotp = (bc->x - x0) * dx + (bc->y - y0) * dy; + double lin_dotp = dx * dx + dy * dy; + double r_min, r; + + r = hypot(bc->x - x0, bc->y - y0); + r_min = r; + r = hypot(bc->x - x, bc->y - y); + if (r < r_min) r_min = r; + + if (dotp >= 0 && dotp <= lin_dotp) { + double norm = (bc->x - x0) * dy - (bc->y - y0) * dx; + r = fabs(norm / sqrt(lin_dotp)); + if (r < r_min) r_min = r; + } + + if (r_min < bc->r_min) { + bc->r_min = r_min; + bc->knot_idx_min = bc->knot_idx; + } + + bc->x0 = x; + bc->y0 = y; +} + +#define cube(x) ((x) * (x) * (x)) + +static double +my_cbrt(double x) +{ + if (x >= 0) + return pow(x, 1.0 / 3.0); + else + return -pow(-x, 1.0 / 3.0); +} + +/** + * Give real roots to eqn c0 + c1 * x + c2 * x^2 + c3 * x^3 == 0. + * Return value is number of roots found. + **/ +static int +solve_cubic(double c0, double c1, double c2, double c3, double root[3]) +{ + double p, q, r, a, b, Q, x0; + + p = c2 / c3; + q = c1 / c3; + r = c0 / c3; + a = (3 * q - p * p) / 3; + b = (2 * cube(p) - 9 * p * q + 27 * r) / 27; + Q = b * b / 4 + cube(a) / 27; + x0 = p / 3; + if (Q > 0) { + double sQ = sqrt(Q); + double t1 = my_cbrt(-b/2 + sQ) + my_cbrt(-b/2 - sQ); + root[0] = t1 - x0; + return 1; + } else if (Q == 0) { + double t1 = my_cbrt(b / 2); + double x1 = t1 - x0; + root[0] = x1; + root[1] = x1; + root[2] = -2 * t1 - x0; + return 3; + } else { + double sQ = sqrt(-Q); + double rho = hypot(-b/2, sQ); + double th = atan2(sQ, -b/2); + double cbrho = my_cbrt(rho); + double c = cos(th / 3); + double s = sin(th / 3); + double sqr3 = sqrt(3); + root[0] = 2 * cbrho * c - x0; + root[1] = -cbrho * (c + sqr3 * s) - x0; + root[2] = -cbrho * (c - sqr3 * s) - x0; + return 3; + } +} + + +static double +dist_to_quadratic(double x, double y, + double x0, double y0, + double x1, double y1, + double x2, double y2) +{ + double u0, u1, t0, t1, t2, c0, c1, c2, c3; + double roots[3]; + int n_roots; + double ts[4]; + int n_ts; + int i; + double minerr = 0; + + u0 = x1 - x0; + u1 = x0 - 2 * x1 + x2; + t0 = x0 - x; + t1 = 2 * u0; + t2 = u1; + c0 = t0 * u0; + c1 = t1 * u0 + t0 * u1; + c2 = t2 * u0 + t1 * u1; + c3 = t2 * u1; + + u0 = y1 - y0; + u1 = y0 - 2 * y1 + y2; + t0 = y0 - y; + t1 = 2 * u0; + t2 = u1; + c0 += t0 * u0; + c1 += t1 * u0 + t0 * u1; + c2 += t2 * u0 + t1 * u1; + c3 += t2 * u1; + + n_roots = solve_cubic(c0, c1, c2, c3, roots); + n_ts = 0; + for (i = 0; i < n_roots; i++) { + double t = roots[i]; + if (t > 0 && t < 1) + ts[n_ts++] = t; + } + if (n_ts < n_roots) { + ts[n_ts++] = 0; + ts[n_ts++] = 1; + } + for (i = 0; i < n_ts; i++) { + double t = ts[i]; + double xa = x0 * (1 - t) * (1 - t) + 2 * x1 * (1 - t) * t + x2 * t * t; + double ya = y0 * (1 - t) * (1 - t) + 2 * y1 * (1 - t) * t + y2 * t * t; + double err = hypot(xa - x, ya - y); + if (i == 0 || err < minerr) { + minerr = err; + } + } + return minerr; +} + +static void +bezctx_hittest_quadto(bezctx *z, double x1, double y1, double x2, double y2) +{ + bezctx_hittest *bc = (bezctx_hittest *)z; + double r = dist_to_quadratic(bc->x, bc->y, + bc->x0, bc->y0, x1, y1, x2, y2); + + if (r < bc->r_min) { + bc->r_min = r; + bc->knot_idx_min = bc->knot_idx; + } + bc->x0 = x2; + bc->y0 = y2; +} + +static void +bezctx_hittest_curveto(bezctx *z, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bezctx_hittest *bc = (bezctx_hittest *)z; + double x0 = bc->x0; + double y0 = bc->y0; + int n_subdiv = 32; + int i; + double xq2, yq2; + + /* todo: subdivide to quadratics rather than lines */ + for (i = 0; i < n_subdiv; i++) { + double t = (1. / n_subdiv) * (i + 1); + double mt = 1 - t; + + xq2 = x0 * mt * mt * mt + 3 * x1 * mt * t * t + 3 * x2 * mt * mt * t + + x3 * t * t * t; + yq2 = y0 * mt * mt * mt + 3 * y1 * mt * t * t + 3 * y2 * mt * mt * t + + y3 * t * t * t; + bezctx_hittest_lineto(z, xq2, yq2); + } +} + +static void +bezctx_hittest_mark_knot(bezctx *z, int knot_idx) { + bezctx_hittest *bc = (bezctx_hittest *)z; + + bc->knot_idx = knot_idx; +} + +bezctx * +new_bezctx_hittest(double x, double y) { + bezctx_hittest *result = znew(bezctx_hittest, 1); + + result->base.moveto = bezctx_hittest_moveto; + result->base.lineto = bezctx_hittest_lineto; + result->base.quadto = bezctx_hittest_quadto; + result->base.curveto = bezctx_hittest_curveto; + result->base.mark_knot = bezctx_hittest_mark_knot; + result->x = x; + result->y = y; + result->knot_idx_min = -1; + result->r_min = 1e12; + return &result->base; +} + +double +bezctx_hittest_report(bezctx *z, int *p_knot_idx) +{ + bezctx_hittest *bc = (bezctx_hittest *)z; + double r_min = bc->r_min; + + if (p_knot_idx) + *p_knot_idx = bc->knot_idx_min; + + zfree(z); + return r_min; +} diff --git a/third_party/spiro/ppedit/bezctx_hittest.h b/third_party/spiro/ppedit/bezctx_hittest.h new file mode 100644 index 0000000..79949ae --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_hittest.h @@ -0,0 +1,5 @@ +bezctx * +new_bezctx_hittest(double x, double y); + +double +bezctx_hittest_report(bezctx *z, int *p_knot_idx); diff --git a/third_party/spiro/ppedit/bezctx_intf.h b/third_party/spiro/ppedit/bezctx_intf.h new file mode 100644 index 0000000..a47b8ef --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_intf.h @@ -0,0 +1,20 @@ +typedef struct _bezctx bezctx; + +bezctx * +new_bezctx(void); + +void +bezctx_moveto(bezctx *bc, double x, double y, int is_open); + +void +bezctx_lineto(bezctx *bc, double x, double y); + +void +bezctx_quadto(bezctx *bc, double x1, double y1, double x2, double y2); + +void +bezctx_curveto(bezctx *bc, double x1, double y1, double x2, double y2, + double x3, double y3); + +void +bezctx_mark_knot(bezctx *bc, int knot_idx); diff --git a/third_party/spiro/ppedit/bezctx_libart.c b/third_party/spiro/ppedit/bezctx_libart.c new file mode 100644 index 0000000..c6b268f --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_libart.c @@ -0,0 +1,131 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_libart.h" + +typedef struct { + bezctx base; + int n_bez; + int n_bez_max; + ArtBpath *bez; +} bezctx_libart; + +static void +bezctx_libart_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *bp; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bp = &bc->bez[bc->n_bez++]; + bp->code = is_open ? ART_MOVETO_OPEN : ART_MOVETO; + bp->x3 = x; + bp->y3 = y; +} + +void +bezctx_libart_lineto(bezctx *z, double x, double y) { + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *bp; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bp = &bc->bez[bc->n_bez++]; + bp->code = ART_LINETO; + bp->x3 = x; + bp->y3 = y; +} + +void +bezctx_libart_quadto(bezctx *z, double x1, double y1, double x2, double y2) +{ + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *bp; + double x0, y0; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bp = &bc->bez[bc->n_bez++]; + x0 = bp[-1].x3; + y0 = bp[-1].y3; + bp->code = ART_CURVETO; + bp->x1 = x1 + (1./3) * (x0 - x1); + bp->y1 = y1 + (1./3) * (y0 - y1); + bp->x2 = x1 + (1./3) * (x2 - x1); + bp->y2 = y1 + (1./3) * (y2 - y1); + bp->x3 = x2; + bp->y3 = y2; +} + +void +bezctx_libart_curveto(bezctx *z, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *bp; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bp = &bc->bez[bc->n_bez++]; + bp->code = ART_CURVETO; + bp->x1 = x1; + bp->y1 = y1; + bp->x2 = x2; + bp->y2 = y2; + bp->x3 = x3; + bp->y3 = y3; +} + +ArtBpath * +bezctx_to_bpath(bezctx *z) { + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *result; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bc->bez[bc->n_bez].code = ART_END; + result = bc->bez; + zfree(bc); + return result; +} + +bezctx * +new_bezctx_libart(void) { + bezctx_libart *result = znew(bezctx_libart, 1); + + result->base.moveto = bezctx_libart_moveto; + result->base.lineto = bezctx_libart_lineto; + result->base.quadto = bezctx_libart_quadto; + result->base.curveto = bezctx_libart_curveto; + result->base.mark_knot = NULL; + result->n_bez = 0; + result->n_bez_max = 4; + result->bez = znew(ArtBpath, result->n_bez_max); + return &result->base; +} + diff --git a/third_party/spiro/ppedit/bezctx_libart.h b/third_party/spiro/ppedit/bezctx_libart.h new file mode 100644 index 0000000..c831b98 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_libart.h @@ -0,0 +1,5 @@ + +bezctx *new_bezctx_libart(void); + +ArtBpath * +bezctx_to_bpath(bezctx *bc); diff --git a/third_party/spiro/ppedit/bezctx_ps.c b/third_party/spiro/ppedit/bezctx_ps.c new file mode 100644 index 0000000..05a38ae --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_ps.c @@ -0,0 +1,116 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include + +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_ps.h" + +typedef struct { + bezctx base; + int is_open; + double x, y; + FILE *f; +} bezctx_ps; + +const char *ps_prolog = "%!PS\n" +"/m { moveto } bind def\n" +"/l { lineto } bind def\n" +"/c { curveto } bind def\n" +"/z { closepath } bind def\n" +"1 -1 scale\n" +"0 -792 translate\n"; + +const char *ps_postlog = "stroke\n" +"showpage\n"; + +static void +bezctx_ps_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_ps *bc = (bezctx_ps *)z; + + if (!bc->is_open) fprintf(bc->f, "z\n"); + fprintf(bc->f, "%g %g m\n", x, y); + bc->is_open = is_open; + bc->x = x; + bc->y = y; +} + +void +bezctx_ps_lineto(bezctx *z, double x, double y) { + bezctx_ps *bc = (bezctx_ps *)z; + + fprintf(bc->f, "%g %g l\n", x, y); + bc->x = x; + bc->y = y; +} + +void +bezctx_ps_quadto(bezctx *z, double xm, double ym, double x3, double y3) +{ + bezctx_ps *bc = (bezctx_ps *)z; + double x0, y0; + double x1, y1; + double x2, y2; + + x0 = bc->x; + y0 = bc->y; + x1 = xm + (1./3) * (x0 - xm); + y1 = ym + (1./3) * (y0 - ym); + x2 = xm + (1./3) * (x3 - xm); + y2 = ym + (1./3) * (y3 - ym); + fprintf(bc->f, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3); + bc->x = x3; + bc->y = y3; +} + +void +bezctx_ps_curveto(bezctx *z, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bezctx_ps *bc = (bezctx_ps *)z; + + fprintf(bc->f, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3); + bc->x = x3; + bc->y = y3; +} + +bezctx * +new_bezctx_ps(FILE *f) { + bezctx_ps *result = znew(bezctx_ps, 1); + + result->base.moveto = bezctx_ps_moveto; + result->base.lineto = bezctx_ps_lineto; + result->base.quadto = bezctx_ps_quadto; + result->base.curveto = bezctx_ps_curveto; + result->base.mark_knot = NULL; + result->is_open = 1; + result->f = f; + return &result->base; +} + +void +bezctx_ps_close(bezctx *z) +{ + bezctx_ps *bc = (bezctx_ps *)z; + + if (!bc->is_open) fprintf(bc->f, "z\n"); + zfree(bc); +} diff --git a/third_party/spiro/ppedit/bezctx_ps.h b/third_party/spiro/ppedit/bezctx_ps.h new file mode 100644 index 0000000..5190fd7 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_ps.h @@ -0,0 +1,8 @@ + +const char *ps_prolog; +const char *ps_postlog; + +bezctx *new_bezctx_ps(FILE *f); + +void +bezctx_ps_close(bezctx *bc); diff --git a/third_party/spiro/ppedit/bezctx_quartz.c b/third_party/spiro/ppedit/bezctx_quartz.c new file mode 100644 index 0000000..f04e0bc --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_quartz.c @@ -0,0 +1,78 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include + +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_quartz.h" + + +typedef struct { + bezctx base; + CGMutablePathRef pathref; + int is_open; +} bezctx_quartz; + +static void +bezctx_quartz_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_quartz *bc = (bezctx_quartz *)z; + if (!bc->is_open) CGPathCloseSubpath(bc->pathref); + CGPathMoveToPoint(bc->pathref, NULL, x, y); + bc->is_open = is_open; +} + +static void +bezctx_quartz_lineto(bezctx *z, double x, double y) { + bezctx_quartz *bc = (bezctx_quartz *)z; + CGPathAddLineToPoint(bc->pathref, NULL, x, y); +} + +static void +bezctx_quartz_quadto(bezctx *z, double x1, double y1, double x2, double y2) +{ + bezctx_quartz *bc = (bezctx_quartz *)z; + CGPathAddQuadCurveToPoint(bc->pathref, NULL, x1, y1, x2, y2); +} + +bezctx * +new_bezctx_quartz(void) { + bezctx_quartz *result = znew(bezctx_quartz, 1); + + result->base.moveto = bezctx_quartz_moveto; + result->base.lineto = bezctx_quartz_lineto; + result->base.quadto = bezctx_quartz_quadto; + result->base.mark_knot = NULL; + result->pathref = CGPathCreateMutable(); + result->is_open = 1; + return &result->base; +} + + +CGMutablePathRef +bezctx_to_quartz(bezctx *z) +{ + bezctx_quartz *bc = (bezctx_quartz *)z; + CGMutablePathRef result = bc->pathref; + + if (!bc->is_open) CGPathCloseSubpath(result); + zfree(bc); + return result; +} diff --git a/third_party/spiro/ppedit/bezctx_quartz.h b/third_party/spiro/ppedit/bezctx_quartz.h new file mode 100644 index 0000000..e234c08 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_quartz.h @@ -0,0 +1,4 @@ +bezctx *new_bezctx_quartz(void); + +CGMutablePathRef +bezctx_to_quartz(bezctx *bc); diff --git a/third_party/spiro/ppedit/bezctx_x3.c b/third_party/spiro/ppedit/bezctx_x3.c new file mode 100644 index 0000000..9278910 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_x3.c @@ -0,0 +1,96 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_x3.h" + +typedef struct { + bezctx base; + x3dc *dc; + int is_open; +} bezctx_x3; + +static void +bezctx_x3_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_x3 *bc = (bezctx_x3 *)z; + + if (!bc->is_open) x3closepath(bc->dc); + x3moveto(bc->dc, x, y); + bc->is_open = is_open; +} + +void +bezctx_x3_lineto(bezctx *z, double x, double y) { + bezctx_x3 *bc = (bezctx_x3 *)z; + + x3lineto(bc->dc, x, y); +} + +void +bezctx_x3_quadto(bezctx *z, double x1, double y1, double x2, double y2) +{ + bezctx_x3 *bc = (bezctx_x3 *)z; + double x0, y0; + + x3getcurrentpoint(bc->dc, &x0, &y0); + x3curveto(bc->dc, + x1 + (1./3) * (x0 - x1), + y1 + (1./3) * (y0 - y1), + x1 + (1./3) * (x2 - x1), + y1 + (1./3) * (y2 - y1), + x2, + y2); +} + +void +bezctx_x3_curveto(bezctx *z, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bezctx_x3 *bc = (bezctx_x3 *)z; + + x3curveto(bc->dc, x1, y1, x2, y2, x3, y3); +} + +void +bezctx_x3_finish(bezctx *z) +{ + bezctx_x3 *bc = (bezctx_x3 *)z; + + if (!bc->is_open) + x3closepath(bc->dc); + + zfree(bc); +} + +bezctx * +new_bezctx_x3(x3dc *dc) { + bezctx_x3 *result = znew(bezctx_x3, 1); + + result->base.moveto = bezctx_x3_moveto; + result->base.lineto = bezctx_x3_lineto; + result->base.quadto = bezctx_x3_quadto; + result->base.curveto = bezctx_x3_curveto; + result->base.mark_knot = NULL; + result->dc = dc; + result->is_open = 1; + return &result->base; +} diff --git a/third_party/spiro/ppedit/bezctx_x3.h b/third_party/spiro/ppedit/bezctx_x3.h new file mode 100644 index 0000000..c7b1bd4 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_x3.h @@ -0,0 +1,3 @@ + +bezctx *new_bezctx_x3(x3dc *dc); +void bezctx_x3_finish(bezctx *z); diff --git a/third_party/spiro/ppedit/carbon_main.c b/third_party/spiro/ppedit/carbon_main.c new file mode 100644 index 0000000..5fbd83f --- /dev/null +++ b/third_party/spiro/ppedit/carbon_main.c @@ -0,0 +1,182 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include + +#include "bezctx.h" +#include "bezctx_quartz.h" +#include "plate.h" +#include "pe_view.h" + +#define kCommandToggleCorner 'togC' + +typedef struct { + WindowRef window_ref; + plate *p; + + HIViewRef view; +} plate_edit; + +int n_iter = 10; + +pascal OSStatus my_handler(EventHandlerCallRef nextHandler, EventRef theEvent, void *data) +{ + plate_edit *pe = (plate_edit *)data; + WindowRef window = pe->window_ref; + UInt32 klass = GetEventClass(theEvent); + UInt32 kind = GetEventKind(theEvent); + OSStatus result = eventNotHandledErr; + OSStatus err; + Point where; + Rect bounds; + + switch (klass) { + case kEventClassMouse: + switch (kind) { + case kEventMouseDown: + err = GetEventParameter(theEvent, kEventParamMouseLocation, + typeQDPoint, NULL, sizeof(where), NULL, &where); + printf("mouse down %d %d\n", where.h, where.v); + break; + } + break; + case kEventClassWindow: + switch (kind) { + case kEventWindowDrawContent: + printf("draw content\n"); + result = noErr; + break; + case kEventWindowClickContentRgn: + printf("click_content region\n"); + break; + case kEventWindowHandleContentClick: + err = GetEventParameter(theEvent, kEventParamMouseLocation, + typeQDPoint, NULL, sizeof(where), NULL, &where); + GetWindowBounds(window, kWindowContentRgn, &bounds); + printf("content click %d, %d; %d, %d\n", where.h, where.v, + where.h - bounds.left, where.v - bounds.top); + break; + } + } + return result; +} + +pascal OSStatus app_handler(EventHandlerCallRef nextHandler, EventRef theEvent, void *data) +{ + plate_edit *pe = (plate_edit *)data; + HICommand hiCommand; + OSStatus result = eventNotHandledErr; + GetEventParameter(theEvent, kEventParamDirectObject, typeHICommand,NULL, + sizeof(HICommand),NULL,&hiCommand); + unsigned int c = hiCommand.commandID; + MenuRef menu; + MenuItemIndex ix; + + printf("app_handler %c%c%c%c\n", + (c >> 24) & 255, (c >> 16) & 255, (c >> 8) & 255, c & 255); + switch (c) { + case kHICommandUndo: + GetIndMenuItemWithCommandID(NULL, kHICommandUndo, 1, &menu, &ix); + SetMenuItemTextWithCFString(menu, ix, CFSTR("Undo disabled")); + DisableMenuItem(menu, ix); + break; + case kCommandToggleCorner: + pe_view_toggle_corner(pe->view); + break; + } + return result; +} + +void +add_pe_view(WindowRef window, plate_edit *pe, plate *p) +{ + HIRect rect = {{0, 0}, {32767, 32767}}; + HIViewRef view; + + pe_view_create(window, &rect, &view); + pe_view_set_plate(view, p); + HIViewSetVisible(view, true); + pe->view = view; +} + +void +init_window(WindowRef window, plate_edit *pe) +{ + EventTypeSpec app_event_types[] = { + { kEventClassCommand, kEventProcessCommand } + }; + EventTypeSpec event_types[] = { + { kEventClassWindow, kEventWindowDrawContent }, + { kEventClassWindow, kEventWindowHandleContentClick }, + { kEventClassMouse, kEventMouseDown } + }; + + InstallApplicationEventHandler(NewEventHandlerUPP(app_handler), + GetEventTypeCount(app_event_types), + app_event_types, (void *)pe, NULL); + InstallWindowEventHandler(window, NewEventHandlerUPP(my_handler), + GetEventTypeCount(event_types), + event_types, (void *)pe, NULL); + + add_pe_view(window, pe, pe->p); +} + +int main(int argc, char* argv[]) +{ + IBNibRef nibRef; + WindowRef window; + plate_edit pe; + + OSStatus err; + + // Create a Nib reference passing the name of the nib file (without the .nib extension) + // CreateNibReference only searches into the application bundle. + err = CreateNibReference(CFSTR("main"), &nibRef); + require_noerr( err, CantGetNibRef ); + + // Once the nib reference is created, set the menu bar. "MainMenu" is the name of the menu bar + // object. This name is set in InterfaceBuilder when the nib is created. + err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar")); + require_noerr( err, CantSetMenuBar ); + + // Then create a window. "MainWindow" is the name of the window object. This name is set in + // InterfaceBuilder when the nib is created. + err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &window); + require_noerr( err, CantCreateWindow ); + + // We don't need the nib reference anymore. + DisposeNibReference(nibRef); + + pe.window_ref = window; + pe.p = file_read_plate("/Users/raph/golf/ppedit/g.plate"); + if (pe.p == NULL) + pe.p = new_plate(); + init_window(window, &pe); + // The window was created hidden so show it. + ShowWindow( window ); + + // Call the event loop + RunApplicationEventLoop(); + +CantCreateWindow: +CantSetMenuBar: +CantGetNibRef: + return err; +} diff --git a/third_party/spiro/ppedit/cornu.c b/third_party/spiro/ppedit/cornu.c new file mode 100644 index 0000000..b0eac68 --- /dev/null +++ b/third_party/spiro/ppedit/cornu.c @@ -0,0 +1,615 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include +#include +#include +#include "bezctx_intf.h" + +/* The computation of fresnel integrals is adapted from: */ + +/* +Cephes Math Library Release 2.1: December, 1988 +Copyright 1984, 1987, 1988 by Stephen L. Moshier +Direct inquiries to 30 Frost Street, Cambridge, MA 02140 +*/ + +double polevl( double x, double coef[], int N ) +{ +double ans; +int i; +double *p; + +p = coef; +ans = *p++; +i = N; + +do + ans = ans * x + *p++; +while( --i ); + +return( ans ); +} + +/* p1evl() */ +/* N + * Evaluate polynomial when coefficient of x is 1.0. + * Otherwise same as polevl. + */ + +double p1evl( double x, double coef[], int N ) +{ +double ans; +double *p; +int i; + +p = coef; +ans = x + *p++; +i = N-1; + +do + ans = ans * x + *p++; +while( --i ); + +return( ans ); +} + +static double sn[6] = { +-2.99181919401019853726E3, + 7.08840045257738576863E5, +-6.29741486205862506537E7, + 2.54890880573376359104E9, +-4.42979518059697779103E10, + 3.18016297876567817986E11, +}; +static double sd[6] = { +/* 1.00000000000000000000E0,*/ + 2.81376268889994315696E2, + 4.55847810806532581675E4, + 5.17343888770096400730E6, + 4.19320245898111231129E8, + 2.24411795645340920940E10, + 6.07366389490084639049E11, +}; + +static double cn[6] = { +-4.98843114573573548651E-8, + 9.50428062829859605134E-6, +-6.45191435683965050962E-4, + 1.88843319396703850064E-2, +-2.05525900955013891793E-1, + 9.99999999999999998822E-1, +}; +static double cd[7] = { + 3.99982968972495980367E-12, + 9.15439215774657478799E-10, + 1.25001862479598821474E-7, + 1.22262789024179030997E-5, + 8.68029542941784300606E-4, + 4.12142090722199792936E-2, + 1.00000000000000000118E0, +}; +static double fn[10] = { + 4.21543555043677546506E-1, + 1.43407919780758885261E-1, + 1.15220955073585758835E-2, + 3.45017939782574027900E-4, + 4.63613749287867322088E-6, + 3.05568983790257605827E-8, + 1.02304514164907233465E-10, + 1.72010743268161828879E-13, + 1.34283276233062758925E-16, + 3.76329711269987889006E-20, +}; +static double fd[10] = { +/* 1.00000000000000000000E0,*/ + 7.51586398353378947175E-1, + 1.16888925859191382142E-1, + 6.44051526508858611005E-3, + 1.55934409164153020873E-4, + 1.84627567348930545870E-6, + 1.12699224763999035261E-8, + 3.60140029589371370404E-11, + 5.88754533621578410010E-14, + 4.52001434074129701496E-17, + 1.25443237090011264384E-20, +}; +static double gn[11] = { + 5.04442073643383265887E-1, + 1.97102833525523411709E-1, + 1.87648584092575249293E-2, + 6.84079380915393090172E-4, + 1.15138826111884280931E-5, + 9.82852443688422223854E-8, + 4.45344415861750144738E-10, + 1.08268041139020870318E-12, + 1.37555460633261799868E-15, + 8.36354435630677421531E-19, + 1.86958710162783235106E-22, +}; +static double gd[11] = { +/* 1.00000000000000000000E0,*/ + 1.47495759925128324529E0, + 3.37748989120019970451E-1, + 2.53603741420338795122E-2, + 8.14679107184306179049E-4, + 1.27545075667729118702E-5, + 1.04314589657571990585E-7, + 4.60680728146520428211E-10, + 1.10273215066240270757E-12, + 1.38796531259578871258E-15, + 8.39158816283118707363E-19, + 1.86958710162783236342E-22, +}; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif + +int fresnl( xxa, ssa, cca ) +double xxa, *ssa, *cca; +{ +double f, g, cc, ss, c, s, t, u; +double x, x2; + +x = fabs(xxa); +x2 = x * x; +if( x2 < 2.5625 ) + { + t = x2 * x2; + ss = x * x2 * polevl( t, sn, 5)/p1evl( t, sd, 6 ); + cc = x * polevl( t, cn, 5)/polevl(t, cd, 6 ); + goto done; + } + + + + + +#if 0 +/* Note by RLL: the cutoff here seems low to me; perhaps it should be + eliminated altogether. */ +if( x > 36974.0 ) + { + cc = 0.5; + ss = 0.5; + goto done; + } +#endif + +/* Asymptotic power series auxiliary functions + * for large argument + */ + x2 = x * x; + t = M_PI * x2; + u = 1.0/(t * t); + t = 1.0/t; + f = 1.0 - u * polevl( u, fn, 9)/p1evl(u, fd, 10); + g = t * polevl( u, gn, 10)/p1evl(u, gd, 11); + + t = M_PI_2 * x2; + c = cos(t); + s = sin(t); + t = M_PI * x; + cc = 0.5 + (f * s - g * c)/t; + ss = 0.5 - (f * c + g * s)/t; + +done: +if( xxa < 0.0 ) + { + cc = -cc; + ss = -ss; + } + +*cca = cc; +*ssa = ss; +return(0); +} + +/* + End section adapted from Cephes math library. The following code is + by Raph Levien. +*/ + +void eval_cornu(double t, double *ps, double *pc) +{ + double s, c; + double rspio2 = 0.7978845608028653; /* 1 / sqrt(pi/2) */ + double spio2 = 1.2533141373155; /* sqrt(pi/2) */ + + fresnl(t * rspio2, &s, &c); + *ps = s * spio2; + *pc = c * spio2; +} + +double mod_2pi(double th) { + double u = th * (1 / (2 * M_PI)); + return 2 * M_PI * (u - floor(u + 0.5)); +} + +void fit_cornu_half(double th0, double th1, + double *pt0, double *pt1, + double *pk0, double *pk1) +{ + int i; + const int max_iter = 21; + double t0, t1, t_m; + double tl, tr, t_est; + double s0, c0, s1, c1; + /* This implementation uses bisection, which is simple but almost + certainly not the fastest converging. If time is of the essence, + use something like Newton-Raphson. */ + + if (fabs(th0 + th1) < 1e-6) { + th0 += 1e-6; + th1 += 1e-6; + } + t_est = 0.29112 * (th1 + th0) / sqrt(th1 - th0); + tl = t_est * .9; + tr = t_est * 2; + for (i = 0; i < max_iter; i++) { + double dt; + double chord_th; + + t_m = .5 * (tl + tr); + dt = (th0 + th1) / (4 * t_m); + t0 = t_m - dt; + t1 = t_m + dt; + eval_cornu(t0, &s0, &c0); + eval_cornu(t1, &s1, &c1); + chord_th = atan2(s1 - s0, c1 - c0); + if (mod_2pi(chord_th - t0 * t0 - th0) < 0) + tl = t_m; + else + tr = t_m; + } + *pt0 = t0; + *pt1 = t1; + if (pk0 || pk1) { + double chordlen = hypot(s1 - s0, c1 - c0); + if (pk0) *pk0 = t0 * chordlen; + if (pk1) *pk1 = t1 * chordlen; + } +} + +/* Most of the time, this should give a fairly tight, yet conservative, + (meaning it won't underestimate) approximation of the maximum error + between a Cornu spiral segment and its quadratic Bezier fit. +*/ +double +est_cornu_error(double t0, double t1) +{ + double t, u, est; + + if (t0 < 0 || t1 < 0) { + t0 = -t0; + t1 = -t1; + } + if (t1 < 0) { + fprintf(stderr, "unexpected t1 sign\n"); + } + if (t1 < t0) { + double tmp = t0; + t0 = t1; + t1 = tmp; + } + if (fabs(t0) < 1e-9) { + est = t1 * t1 * t1; + est *= .017256 - .0059 - est * t1; + } else { + t = t1 - t0; + t *= t; + t *= t; + est = t * fabs(t0 + t1 - 1.22084) / (t0 + t1); + u = t0 + t1 + .6; + u = u * u * u; + est *= .014 * (.6 * u + 1); + est += t * (t1 - t0) * .004; + } + return est; +} + +void +affine_pt(const double aff[6], double x, double y, double *px, double *py) { + *px = x * aff[0] + y * aff[2] + aff[4]; + *py = x * aff[1] + y * aff[3] + aff[5]; +} + +void +affine_multiply(double dst[6], const double src1[6], const double src2[6]) +{ + double d0, d1, d2, d3, d4, d5; + d0 = src1[0] * src2[0] + src1[1] * src2[2]; + d1 = src1[0] * src2[1] + src1[1] * src2[3]; + d2 = src1[2] * src2[0] + src1[3] * src2[2]; + d3 = src1[2] * src2[1] + src1[3] * src2[3]; + d4 = src1[4] * src2[0] + src1[5] * src2[2] + src2[4]; + d5 = src1[4] * src2[1] + src1[5] * src2[3] + src2[5]; + dst[0] = d0; + dst[1] = d1; + dst[2] = d2; + dst[3] = d3; + dst[4] = d4; + dst[5] = d5; +} + +void +fit_quadratic(double x0, double y0, double th0, + double x1, double y1, double th1, + double quad[6]) { + double th; + double s0, c0, s1, c1; + double det, s, c; + + th = atan2(y1 - y0, x1 - x0); + s0 = sin(th0 - th); + c0 = cos(th0 - th); + s1 = sin(th - th1); + c1 = cos(th - th1); + det = 1 / (s0 * c1 + s1 * c0); + s = s0 * s1 * det; + c = c0 * s1 * det; + quad[0] = x0; + quad[1] = y0; + quad[2] = x0 + (x1 - x0) * c - (y1 - y0) * s; + quad[3] = y0 + (y1 - y0) * c + (x1 - x0) * s; + quad[4] = x1; + quad[5] = y1; +} + +void +cornu_seg_to_quad(double t0, double t1, const double aff[6], bezctx *bc) +{ + double x0, y0; + double x1, y1; + double th0 = t0 * t0; + double th1 = t1 * t1; + double quad[6]; + double qx1, qy1, qx2, qy2; + + eval_cornu(t0, &y0, &x0); + eval_cornu(t1, &y1, &x1); + fit_quadratic(x0, y0, th0, x1, y1, th1, quad); + affine_pt(aff, quad[2], quad[3], &qx1, &qy1); + affine_pt(aff, quad[4], quad[5], &qx2, &qy2); + bezctx_quadto(bc, qx1, qy1, qx2, qy2); +} + +void +cornu_seg_to_bpath(double t0, double t1, const double aff[6], + bezctx *bc, double tol) +{ + double tm; + + if ((t0 < 0 && t1 > 0) || (t1 < 0 && t0 > 0)) + tm = 0; + else { + if (fabs(t0 * t0 - t1 * t1) < 1.5 && + est_cornu_error(t0, t1) < tol) { + cornu_seg_to_quad(t0, t1, aff, bc); + return; + } +#if 0 + if (fabs(t0 - t1) < 1e-6) { + printf("DIVERGENCE!\007\n"); + return; + + } +#endif + tm = (t0 + t1) * .5; + } + + cornu_seg_to_bpath(t0, tm, aff, bc, tol); + cornu_seg_to_bpath(tm, t1, aff, bc, tol); +} + +void +cornu_to_bpath(const double xs[], const double ys[], const double ths[], int n, + bezctx *bc, double tol, int closed, int kt0, int n_kt) +{ + int i; + + for (i = 0; i < n - 1 + closed; i++) { + double x0 = xs[i], y0 = ys[i]; + int ip1 = (i + 1) % n; + double x1 = xs[ip1], y1 = ys[ip1]; + double th = atan2(y1 - y0, x1 - x0); + double th0 = mod_2pi(ths[i] - th); + double th1 = mod_2pi(th - ths[ip1]); + double t0, t1; + double s0, c0, s1, c1; + double chord_th, chordlen, rot, scale; + double aff[6], aff2[6]; + double flip = -1; + + th1 += 1e-6; + if (th1 < th0) { + double tmp = th0; + th0 = th1; + th1 = tmp; + flip = 1; + } + fit_cornu_half(th0, th1, &t0, &t1, NULL, NULL); + if (flip == 1) { + double tmp = t0; + t0 = t1; + t1 = tmp; + } + eval_cornu(t0, &s0, &c0); + s0 *= flip; + eval_cornu(t1, &s1, &c1); + s1 *= flip; + chord_th = atan2(s1 - s0, c1 - c0); + chordlen = hypot(s1 - s0, c1 - c0); + rot = th - chord_th; + scale = hypot(y1 - y0, x1 - x0) / chordlen; + aff[0] = 1; + aff[1] = 0; + aff[2] = 0; + aff[3] = flip; + aff[4] = -c0; + aff[5] = -s0; + aff2[0] = scale * cos(rot); + aff2[1] = scale * sin(rot); + aff2[2] = -aff2[1]; + aff2[3] = aff2[0]; + aff2[4] = x0; + aff2[5] = y0; + affine_multiply(aff, aff, aff2); + bezctx_mark_knot(bc, (kt0 + i) % n_kt); + cornu_seg_to_bpath(t0, t1, aff, bc, tol / scale); + } +} + +/* fit arc to pts (0, 0), (x, y), and (1, 0), return th tangent to + arc at (x, y) */ +double fit_arc(double x, double y) { + return atan2(y - 2 * x * y, y * y + x - x * x); +} + +void +local_ths(const double xs[], const double ys[], double ths[], int n, + int closed) +{ + int i; + + for (i = 1 - closed; i < n - 1 + closed; i++) { + int im1 = (i + n - 1) % n; + double x0 = xs[im1]; + double y0 = ys[im1]; + double x1 = xs[i]; + double y1 = ys[i]; + int ip1 = (i + 1) % n; + double x2 = xs[ip1]; + double y2 = ys[ip1]; + double dx = x2 - x0; + double dy = y2 - y0; + double ir2 = dx * dx + dy * dy; + double x = ((x1 - x0) * dx + (y1 - y0) * dy) / ir2; + double y = ((y1 - y0) * dx - (x1 - x0) * dy) / ir2; + if (dx == 0.0 && dy == 0.0) + ths[i] = 0.0; + else + ths[i] = fit_arc(x, y) + atan2(dy, dx); + } +} + +void +endpoint_ths(const double xs[], const double ys[], double ths[], int n) +{ + ths[0] = 2 * atan2(ys[1] - ys[0], xs[1] - xs[0]) - ths[1]; + ths[n - 1] = 2 * atan2(ys[n - 1] - ys[n - 2], xs[n - 1] - xs[n-2]) - ths[n - 2]; +} + +void +tweak_ths(const double xs[], const double ys[], double ths[], int n, + double delt, int closed) +{ + double *dks = (double *)malloc(sizeof(double) * n); + int i; + double first_k0, last_k1; + + for (i = 0; i < n - 1 + closed; i++) { + double x0 = xs[i]; + double y0 = ys[i]; + int ip1 = (i + 1) % n; + double x1 = xs[ip1]; + double y1 = ys[ip1]; + double th, th0, th1; + double t0, t1, k0, k1; + double s0, c0, s1, c1; + double scale; + double flip = -1; + + if (x0 == x1 && y0 == y1) { +#ifdef VERBOSE + printf("Overlapping points (i=%d)\n", i); +#endif + /* Very naughty, casting off the constness like this. */ + ((double*) xs)[i] = x1 = x1 + 1e-6; + } + + th = atan2(y1 - y0, x1 - x0); + th0 = mod_2pi(ths[i] - th); + th1 = mod_2pi(th - ths[ip1]); + + th1 += 1e-6; + if (th1 < th0) { + double tmp = th0; + th0 = th1; + th1 = tmp; + flip = 1; + } + fit_cornu_half(th0, th1, &t0, &t1, &k0, &k1); + if (flip == 1) { + double tmp = t0; + t0 = t1; + t1 = tmp; + + tmp = k0; + k0 = k1; + k1 = tmp; + } + eval_cornu(t0, &s0, &c0); + eval_cornu(t1, &s1, &c1); + scale = 1 / hypot(y1 - y0, x1 - x0); + k0 *= scale; + k1 *= scale; + if (i > 0) dks[i] = k0 - last_k1; + else first_k0 = k0; + last_k1 = k1; + } + if (closed) + dks[0] = first_k0 - last_k1; + for (i = 1 - closed; i < n - 1 + closed; i++) { + int im1 = (i + n - 1) % n; + double x0 = xs[im1]; + double y0 = ys[im1]; + double x1 = xs[i]; + double y1 = ys[i]; + int ip1 = (i + 1) % n; + double x2 = xs[ip1]; + double y2 = ys[ip1]; + double chord1 = hypot(x1 - x0, y1 - y0); + double chord2 = hypot(x2 - x1, y2 - y1); + ths[i] -= delt * dks[i] * chord1 * chord2 / (chord1 + chord2); + } + free(dks); +} + +void test_cornu(void) +{ +#if 0 + int i; + for (i = -10; i < 100; i++) { + double t = 36974 * 1.2533141373155 + i * .1; + double s, c; + eval_cornu(t, &s, &c); + printf("%g %g %g\n", t, s, c); + } +#else + double t0, t1; + fit_cornu_half(0, 1, &t0, &t1, 0, 0); + printf("%g %g\n", t0, t1); +#endif +} diff --git a/third_party/spiro/ppedit/cornu.h b/third_party/spiro/ppedit/cornu.h new file mode 100644 index 0000000..f4a955a --- /dev/null +++ b/third_party/spiro/ppedit/cornu.h @@ -0,0 +1,13 @@ +void +cornu_to_bpath(const double xs[], const double ys[], const double ths[], int n, + bezctx *bc, double tol, int closed, int kt0, int n_kt); + +void +local_ths(const double xs[], const double ys[], double ths[], int n, int closed); + +void +endpoint_ths(const double xs[], const double ys[], double ths[], int n); + +void +tweak_ths(const double xs[], const double ys[], double ths[], int n, + double delt, int closed); diff --git a/third_party/spiro/ppedit/gpl.txt b/third_party/spiro/ppedit/gpl.txt new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/third_party/spiro/ppedit/gpl.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/third_party/spiro/ppedit/image.c b/third_party/spiro/ppedit/image.c new file mode 100644 index 0000000..215d27d --- /dev/null +++ b/third_party/spiro/ppedit/image.c @@ -0,0 +1,141 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include +#include +#include "zmisc.h" +#include "image.h" + +/* An image loaded into memory. */ +struct _image { + unsigned char *buf; + int width; + int height; + int rowstride; +}; + +static image * +load_ppm_file(FILE *f, char **reason) +{ + image *result; + char line[256]; + int xs, ys; + int depth; + int n; + + fseek(f, 0, SEEK_SET); + fgets(line, sizeof(line), f); + do { + fgets(line, sizeof(line), f); + } while (line[0] == '#'); + n = sscanf(line, "%d %d", &xs, &ys); + if (n != 2) { + *reason = "Error reading ppmraw size line"; + fclose(f); + return NULL; + } + do { + fgets(line, sizeof(line), f); + } while (line[0] == '#'); + n = sscanf(line, "%d", &depth); + if (n != 1) { + *reason = "Error reading ppmraw depth line"; + fclose(f); + return NULL; + } + result = znew(image, 1); + result->rowstride = 3 * xs; + result->buf = zalloc(ys * result->rowstride); + result->width = xs; + result->height = ys; + fread(result->buf, 1, ys * result->rowstride, f); + fclose(f); + return result; +} + +image * +load_image_file(const char *fn, char **reason) +{ + FILE *f = fopen(fn, "rb"); + unsigned char buf[256]; + int n; + + if (f == NULL) { + *reason = "Error opening file"; + return NULL; + } + n = fread(buf, 1, sizeof(buf), f); + if (n < 4) { + *reason = "Short file"; + fclose(f); + return NULL; + } + if (buf[0] != 'P' || buf[1] != '6') { + *reason = "Unrecognized magic"; + fclose(f); + return NULL; + } + return load_ppm_file(f, reason); +} + +void +free_image(image *im) +{ + zfree(im->buf); + zfree(im); +} + +void +render_image(image *im, const double affine[6], + unsigned char *buf, int rowstride, int x0, int y0, int x1, int y1) +{ + int y; + unsigned char *dest_line = buf; + int src_x0 = x0; + + for (y = y0; y < y1; y++) { + int src_y = y; + + if (src_y >= 0 && src_y < im->height) { + unsigned char *img_line = im->buf + src_y * im->rowstride; + int left_pad = -src_x0; + int img_run, img_off, right_pad; + + if (left_pad > x1 - x0) left_pad = x1 - x0; + if (left_pad > 0) { + memset(dest_line, 255, 3 * left_pad); + } else left_pad = 0; + img_off = src_x0; + if (img_off < 0) img_off = 0; + img_run = x1 - x0 - left_pad; + if (img_run > im->width - img_off) img_run = im->width - img_off; + if (img_run > 0) { + memcpy(dest_line + 3 * left_pad, img_line + 3 * img_off, 3 * img_run); + } else img_run = 0; + right_pad = x1 - x0 - left_pad - img_run; + if (right_pad > 0) { + memset(dest_line + 3 * (left_pad + img_run), 255, 3 * right_pad); + } + } else { + memset(dest_line, 255, rowstride); + } + dest_line += rowstride; + } +} diff --git a/third_party/spiro/ppedit/image.h b/third_party/spiro/ppedit/image.h new file mode 100644 index 0000000..a472405 --- /dev/null +++ b/third_party/spiro/ppedit/image.h @@ -0,0 +1,11 @@ +typedef struct _image image; + +image * +load_image_file(const char *fn, char **reason); + +void +free_image(image *im); + +void +render_image(image *im, const double affine[6], + unsigned char *buf, int rowstride, int x0, int y0, int x1, int y1); diff --git a/third_party/spiro/ppedit/pe_view.c b/third_party/spiro/ppedit/pe_view.c new file mode 100644 index 0000000..9909680 --- /dev/null +++ b/third_party/spiro/ppedit/pe_view.c @@ -0,0 +1,548 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +/* This module implements a Carbon HIView object for a pattern plate + editor. */ + +#include + +#include "bezctx.h" +#include "bezctx_quartz.h" +#include "plate.h" +#include "pe_view.h" + +#define kPEViewClassID CFSTR("com.levien.ppedit.PEView") +#define kPEViewPrivate 'PE_v' + +typedef struct +{ + HIViewRef view; + int show_knots; + plate *p; +} pe_view_data; + +static OSStatus +pe_view_construct(EventRef inEvent) +{ + OSStatus err; + pe_view_data *data; + + data = (pe_view_data *)malloc(sizeof(pe_view_data)); + require_action(data != NULL, CantMalloc, err = memFullErr); + err = GetEventParameter(inEvent, kEventParamHIObjectInstance, + typeHIObjectRef, NULL, sizeof(HIObjectRef), NULL, + (HIObjectRef *)&data->view); + require_noerr(err, ParameterMissing); + err = SetEventParameter(inEvent, kEventParamHIObjectInstance, + typeVoidPtr, sizeof(pe_view_data *), &data); + + data->p = NULL; + data->show_knots = 1; + + ParameterMissing: + if (err != noErr) + free(data); + + CantMalloc: + return err; +} + +static OSStatus +pe_view_destruct(EventRef inEvent, pe_view_data *inData) +{ + free(inData); + return noErr; +} + +static OSStatus +pe_view_initialize(EventHandlerCallRef inCallRef, EventRef inEvent, + pe_view_data *inData) +{ + OSStatus err; + HIRect bounds; + + err = CallNextEventHandler(inCallRef, inEvent); + require_noerr(err, TroubleInSuperClass); + + err = GetEventParameter(inEvent, 'Boun', typeHIRect, + NULL, sizeof(HIRect), NULL, &bounds); + require_noerr(err, ParameterMissing); + + HIViewSetFrame(inData->view, &bounds); + + ParameterMissing: + TroubleInSuperClass: + return err; +} + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +static void +cgcontext_set_rgba(CGContextRef ctx, unsigned int rgba) +{ + const double norm = 1.0 / 255; + CGContextSetRGBFillColor(ctx, + ((rgba >> 24) & 0xff) * norm, + ((rgba >> 16) & 0xff) * norm, + ((rgba >> 8) & 0xff) * norm, + (rgba & 0xff) * norm); +} + +static void +draw_dot(CGContextRef ctx, double x, double y, double r, + unsigned int rgba) +{ + cgcontext_set_rgba(ctx, rgba); + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddArc(path, NULL, x, y, r, 0, 2 * M_PI, false); + CGContextAddPath(ctx, path); + CGPathRelease(path); + CGContextFillPath(ctx); +} + +static void +draw_raw_rect(CGContextRef ctx, double x0, double y0, double x1, double y1, + unsigned int rgba) +{ + HIRect rect; + + cgcontext_set_rgba(ctx, rgba); + rect.origin.x = x0; + rect.origin.y = y0; + rect.size.width = x1 - x0; + rect.size.height = y1 - y0; + CGContextFillRect(ctx, rect); +} + +static void +draw_rect(CGContextRef ctx, double x, double y, double r, + unsigned int rgba) +{ + draw_raw_rect(ctx, x - r, y - r, x + r, y + r, rgba); +} + +static void +draw_plate(CGContextRef ctx, pe_view_data *pe) +{ + plate *p = pe->p; + int i, j; + bezctx *bc; + subpath *sp; + spiro_seg **ss = znew(spiro_seg *, p->n_sp); + + for (i = 0; i < p->n_sp; i++) { + bc = new_bezctx_quartz(); + ss[i] = draw_subpath(&p->sp[i], bc); + CGMutablePathRef path = bezctx_to_quartz(bc); + CGContextAddPath(ctx, path); + CGPathRelease(path); + CGContextStrokePath(ctx); + } + + for (i = 0; i < p->n_sp; i++) { + if (pe->show_knots) { + sp = &p->sp[i]; + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + kt_flags kf = kt->flags; + if ((kf & KT_SELECTED) && (kf & KT_OPEN)) { + draw_dot(ctx, kt->x, kt->y, + 3, 0x000000ff); + draw_dot(ctx, kt->x, kt->y, + 1.5, 0xffffffff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(ctx, kt->x, kt->y, + 3, 0x000000ff); + draw_rect(ctx, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(ctx, kt->x, kt->y, + 2.5, 0x000080ff); + } else { + draw_dot(ctx, kt->x, kt->y, + 2, 0x000080ff); + } + } + } + spiro_free(ss[i]); + } + zfree(ss); +} + +static void +draw_selection(CGContextRef ctx, pe_view_data *pe) +{ + plate *p = pe->p; + + if (p->motmode == MOTION_MODE_SELECT) { + double rx0 = p->sel_x0; + double ry0 = p->sel_y0; + double rx1 = p->x0; + double ry1 = p->y0; + if (rx0 > rx1) { + double tmp = rx1; + rx1 = rx0; + rx0 = tmp; + } + if (ry0 > ry1) { + double tmp = ry1; + ry1 = ry0; + ry0 = tmp; + } + if (rx1 > rx0 && ry1 > ry0) + draw_raw_rect(ctx, rx0, ry0, rx1, ry1, 0x0000ff20); + } +} + +static OSStatus +pe_view_draw(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + CGContextRef ctx; + + err = GetEventParameter(inEvent, kEventParamCGContextRef, typeCGContextRef, + NULL, sizeof(CGContextRef), NULL, &ctx); + require_noerr(err, ParameterMissing); + + if (inData->p) { + draw_plate(ctx, inData); + draw_selection(ctx, inData); + } + + ParameterMissing: + return err; +} + +static OSStatus +pe_view_get_data(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + OSType tag; + Ptr ptr; + Size outSize; + + /* Probably could use a bit more error checking here, for type + and size match. Also, just returning a pe_view_data seems a + little hacky. */ + err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration, + NULL, sizeof(OSType), NULL, &tag); + require_noerr(err, ParameterMissing); + + err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr, + NULL, sizeof(Ptr), NULL, &ptr); + + if (tag == kPEViewPrivate) { + *((pe_view_data **)ptr) = inData; + outSize = sizeof(pe_view_data *); + } else + err = errDataNotSupported; + + if (err == noErr) + err = SetEventParameter(inEvent, kEventParamControlDataBufferSize, typeLongInteger, + sizeof(Size), &outSize); + + ParameterMissing: + return err; +} + +static OSStatus +pe_view_set_data(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + Ptr ptr; + OSType tag; + + err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration, + NULL, sizeof(OSType), NULL, &tag); + require_noerr(err, ParameterMissing); + + err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr, + NULL, sizeof(Ptr), NULL, &ptr); + require_noerr(err, ParameterMissing); + + if (tag == 'Plat') { + inData->p = *(plate **)ptr; + } else + err = errDataNotSupported; + + ParameterMissing: + return err; +} + +static OSStatus +pe_view_hittest(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + HIPoint where; + HIRect bounds; + ControlPartCode part; + + err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint, + NULL, sizeof(HIPoint), NULL, &where); + require_noerr(err, ParameterMissing); + + err = HIViewGetBounds(inData->view, &bounds); + require_noerr(err, ParameterMissing); + + if (CGRectContainsPoint(bounds, where)) + part = 1; + else + part = kControlNoPart; + err = SetEventParameter(inEvent, kEventParamControlPart, + typeControlPartCode, sizeof(ControlPartCode), + &part); + printf("hittest %g, %g!\n", where.x, where.y); + + ParameterMissing: + return err; +} + +static void +pe_view_queue_draw(pe_view_data *pe) +{ + HIViewSetNeedsDisplay(pe->view, true); +} + +static int +pe_view_button_press(pe_view_data *pe, double x, double y, press_mod mods) +{ + pe->p->description = NULL; + plate_press(pe->p, x, y, mods); + pe_view_queue_draw(pe); + return 1; +} + +static int +pe_view_motion(pe_view_data *pe, double x, double y) +{ + if (pe->p->motmode == MOTION_MODE_MOVE) + plate_motion_move(pe->p, x, y); + else if (pe->p->motmode == MOTION_MODE_SELECT) + plate_motion_select(pe->p, x, y); + pe_view_queue_draw(pe); + return 1; +} + +static int +pe_view_button_release(pe_view_data *pe) +{ + int need_redraw; + + need_redraw = (pe->p->motmode == MOTION_MODE_SELECT); + + plate_unpress(pe->p); + + if (need_redraw) + pe_view_queue_draw(pe); + return 1; +} + +static OSStatus +pe_view_track(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + HIPoint where; + MouseTrackingResult mouseStatus; + HIRect bounds; + Rect windBounds; + Point theQDPoint; + + err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint, + NULL, sizeof(HIPoint), NULL, &where); + require_noerr(err, ParameterMissing); + + err = HIViewGetBounds(inData->view, &bounds); + require_noerr(err, ParameterMissing); + + GetWindowBounds(GetControlOwner(inData->view), kWindowStructureRgn, &windBounds); + +#ifdef VERBOSE + printf("press: %g, %g!\n", where.x, where.y); +#endif + pe_view_button_press(inData, where.x, where.y, 0); + + mouseStatus = kMouseTrackingMouseDown; + while (mouseStatus != kMouseTrackingMouseUp) { + TrackMouseLocation(NULL, &theQDPoint, &mouseStatus); + where.x = theQDPoint.h - windBounds.left; + where.y = theQDPoint.v - windBounds.top; + HIViewConvertPoint(&where, NULL, inData->view); +#ifdef VERBOSE + printf("track %d: %g, %g!\n", mouseStatus, where.x, where.y); +#endif + if (mouseStatus == kMouseTrackingMouseUp) { + pe_view_button_release(inData); + } else { + pe_view_motion(inData, where.x, where.y); + } + } + + ParameterMissing: + return err; +} + +pascal OSStatus +pe_view_handler(EventHandlerCallRef inCallRef, + EventRef inEvent, + void* inUserData ) +{ + OSStatus err = eventNotHandledErr; + UInt32 eventClass = GetEventClass(inEvent); + UInt32 eventKind = GetEventKind(inEvent); + pe_view_data *data = (pe_view_data *)inUserData; + + switch (eventClass) { + case kEventClassHIObject: + switch (eventKind) { + case kEventHIObjectConstruct: + err = pe_view_construct(inEvent); + break; + case kEventHIObjectInitialize: + err = pe_view_initialize(inCallRef, inEvent, data); + break; + case kEventHIObjectDestruct: + err = pe_view_destruct(inEvent, data); + break; + } + break; + case kEventClassControl: + switch (eventKind) { + case kEventControlInitialize: + err = noErr; + break; + case kEventControlDraw: + err = pe_view_draw(inEvent, data); + break; + case kEventControlGetData: + err = pe_view_get_data(inEvent, data); + break; + case kEventControlSetData: + err = pe_view_set_data(inEvent, data); + break; + case kEventControlTrack: + err = pe_view_track(inEvent, data); + break; + case kEventControlHitTest: + err = pe_view_hittest(inEvent, data); + break; + /*...*/ + } + break; + } + return err; +} + +static OSStatus +pe_view_register(void) +{ + OSStatus err = noErr; + static HIObjectClassRef pe_view_ClassRef = NULL; + + if (pe_view_ClassRef == NULL) { + EventTypeSpec eventList[] = { + { kEventClassHIObject, kEventHIObjectConstruct }, + { kEventClassHIObject, kEventHIObjectInitialize }, + { kEventClassHIObject, kEventHIObjectDestruct }, + + { kEventClassControl, kEventControlActivate }, + { kEventClassControl, kEventControlDeactivate }, + { kEventClassControl, kEventControlDraw }, + { kEventClassControl, kEventControlHiliteChanged }, + { kEventClassControl, kEventControlHitTest }, + { kEventClassControl, kEventControlInitialize }, + { kEventClassControl, kEventControlGetData }, + { kEventClassControl, kEventControlSetData }, + { kEventClassControl, kEventControlTrack } + }; + err = HIObjectRegisterSubclass(kPEViewClassID, + kHIViewClassID, + NULL, + pe_view_handler, + GetEventTypeCount(eventList), + eventList, + NULL, + &pe_view_ClassRef); + } + return err; +} + +OSStatus pe_view_create( + WindowRef inWindow, + const HIRect* inBounds, + HIViewRef* outView) +{ + OSStatus err; + EventRef event; + + err = pe_view_register(); + require_noerr(err, CantRegister); + + err = CreateEvent(NULL, kEventClassHIObject, kEventHIObjectInitialize, + GetCurrentEventTime(), 0, &event); + require_noerr(err, CantCreateEvent); + + if (inBounds != NULL) { + err = SetEventParameter(event, 'Boun', typeHIRect, sizeof(HIRect), + inBounds); + require_noerr(err, CantSetParameter); + } + + err = HIObjectCreate(kPEViewClassID, event, (HIObjectRef*)outView); + require_noerr(err, CantCreate); + + if (inWindow != NULL) { + HIViewRef root; + err = GetRootControl(inWindow, &root); + require_noerr(err, CantGetRootView); + err = HIViewAddSubview(root, *outView); + } + CantCreate: + CantGetRootView: + CantSetParameter: + CantCreateEvent: + ReleaseEvent(event); + CantRegister: + return err; +} + +void +pe_view_set_plate(HIViewRef view, plate *p) +{ + OSStatus err; + + err = SetControlData(view, 1, 'Plat', 4, &p); +} + +void +pe_view_toggle_corner(HIViewRef view) +{ + OSStatus err; + pe_view_data *pe; + + err = GetControlData(view, 1, kPEViewPrivate, 4, &pe, NULL); + require_noerr(err, CantGetPrivate); + + plate_toggle_corner(pe->p); + pe_view_queue_draw(pe); + + CantGetPrivate: +} diff --git a/third_party/spiro/ppedit/pe_view.h b/third_party/spiro/ppedit/pe_view.h new file mode 100644 index 0000000..8db4fff --- /dev/null +++ b/third_party/spiro/ppedit/pe_view.h @@ -0,0 +1,11 @@ +OSStatus pe_view_create( + WindowRef inWindow, + const HIRect* inBounds, + HIViewRef* outView); + +void +pe_view_set_plate(HIViewRef view, plate *p); + +void +pe_view_toggle_corner(HIViewRef view); + diff --git a/third_party/spiro/ppedit/plate.c b/third_party/spiro/ppedit/plate.c new file mode 100644 index 0000000..023f95f --- /dev/null +++ b/third_party/spiro/ppedit/plate.c @@ -0,0 +1,526 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include +#include +#include + +#include "zmisc.h" +#include "sexp.h" +#include "bezctx_intf.h" +#include "bezctx_hittest.h" +#include "cornu.h" +#include "spiro.h" +#include "plate.h" + +/* This is a global while we're playing with the tangent solver. Once we get that + nailed down, it will go away. */ +extern int n_iter; + +/** + * These are functions for editing a Cornu spline ("plate"), intended + * to be somewhat independent of the UI toolkit specifics. + **/ + +plate * +new_plate(void) +{ + plate *p = znew(plate, 1); + + p->n_sp = 0; + p->n_sp_max = 4; + p->sp = znew(subpath, p->n_sp_max); + p->mmode = MOUSE_MODE_ADD_CURVE; + p->last_curve_mmode = p->mmode; + return p; +} + +void +free_plate(plate *p) +{ + int i; + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + zfree(sp->kt); + } + zfree(p->sp); + zfree(p); +} + +plate * +copy_plate(const plate *p) +{ + int i; + plate *n = znew(plate, 1); + + n->n_sp = p->n_sp; + n->n_sp_max = p->n_sp_max; + n->sp = znew(subpath, n->n_sp_max); + for (i = 0; i < n->n_sp; i++) { + subpath *sp = &p->sp[i]; + subpath *nsp = &n->sp[i]; + + nsp->n_kt = sp->n_kt; + nsp->n_kt_max = sp->n_kt_max; + nsp->kt = znew(knot, nsp->n_kt_max); + memcpy(nsp->kt, sp->kt, nsp->n_kt * sizeof(knot)); + nsp->closed = sp->closed; + } + n->mmode = p->mmode; + return n; +} + +void +plate_select_all(plate *p, int selected) +{ + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + kt->flags &= ~KT_SELECTED; + if (selected) + kt->flags |= KT_SELECTED; + } + } +} + +subpath * +plate_find_selected_sp(plate *p) +{ + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + if (kt->flags & KT_SELECTED) + return sp; + } + } + return NULL; +} + +subpath * +plate_new_sp(plate *p) +{ + subpath *sp; + if (p->n_sp == p->n_sp_max) + p->sp = zrenew(subpath, p->sp, p->n_sp_max <<= 1); + sp = &p->sp[p->n_sp++]; + sp->n_kt = 0; + sp->n_kt_max = 4; + sp->kt = znew(knot, sp->n_kt_max); + sp->closed = 0; + return sp; +} + +static int +try_close_sp(subpath *sp, int ix, int force) +{ + int n_kt = sp->n_kt; + + if (sp->closed) return 0; + if (n_kt < 3) return 0; + if (!force) { + if (ix != 0 && ix != n_kt - 1) return 0; + if (!(sp->kt[n_kt - 1 - ix].flags & KT_SELECTED)) return 0; + } + sp->closed = 1; + return 1; +} + +void +plate_press(plate *p, double x, double y, press_mod mods) +{ + int i, j; + subpath *sp; + knot *kt; + const double srad = 5; + kt_flags new_kt_flags = KT_SELECTED; + + if (p->mmode == MOUSE_MODE_ADD_CORNER) + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_OPEN : KT_CORNER; + else if (p->mmode == MOUSE_MODE_ADD_CORNU) + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_CORNU; + else if (p->mmode == MOUSE_MODE_ADD_LEFT) + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_LEFT; + else if (p->mmode == MOUSE_MODE_ADD_RIGHT) + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_RIGHT; + else + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_OPEN; + + p->x0 = x; + p->y0 = y; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + kt = &sp->kt[j]; + if (hypot(kt->x - x, kt->y - y) < srad) { + int was_closed = try_close_sp(sp, j, mods & PRESS_MOD_DOUBLE); + if (mods & PRESS_MOD_SHIFT) { + kt->flags ^= KT_SELECTED; + } else if (!(kt->flags & KT_SELECTED)) { + plate_select_all(p, 0); + kt->flags |= KT_SELECTED; + } + p->description = was_closed ? "Close Path" : NULL; + p->motmode = MOTION_MODE_MOVE; + return; + } + } + } + + if (p->mmode == MOUSE_MODE_ADD_RIGHT || p->mmode == MOUSE_MODE_ADD_LEFT) + p->mmode = p->last_curve_mmode; + + +#if 1 + /* test whether the button press was on a curve; if so, insert point */ + for (i = 0; i < p->n_sp; i++) { + bezctx *bc = new_bezctx_hittest(x, y); + int knot_idx; + + sp = &p->sp[i]; + free_spiro(draw_subpath(sp, bc)); + if (bezctx_hittest_report(bc, &knot_idx) < srad) { + knot *kt; + + if (sp->n_kt == sp->n_kt_max) + sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1); + plate_select_all(p, 0); + kt = &sp->kt[knot_idx + 1]; + memmove(&kt[1], kt, (sp->n_kt - knot_idx - 1) * sizeof(knot)); + sp->n_kt++; + kt->x = x; + kt->y = y; + kt->flags = new_kt_flags; + p->description = "Insert Point"; + p->motmode = MOTION_MODE_MOVE; + return; + } + } +#endif + + if (p->mmode == MOUSE_MODE_SELECT) { + plate_select_all(p, 0); + p->sel_x0 = x; + p->sel_y0 = y; + p->motmode = MOTION_MODE_SELECT; + return; + } + + sp = plate_find_selected_sp(p); + if (sp == NULL || sp->closed) { + sp = plate_new_sp(p); + p->description = p->n_sp > 1 ? "New Subpath" : "New Path"; + } + + if (sp->n_kt == sp->n_kt_max) + sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1); + plate_select_all(p, 0); + kt = &sp->kt[sp->n_kt++]; + kt->x = x; + kt->y = y; + kt->flags = new_kt_flags; + if (p->description == NULL) + p->description = "Add Point"; + p->motmode = MOTION_MODE_MOVE; +} + +void +plate_motion_move(plate *p, double x, double y) +{ + int i, j, n = 0; + double dx, dy; + + dx = x - p->x0; + dy = y - p->y0; + p->x0 = x; + p->y0 = y; + + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + if (kt->flags & KT_SELECTED) { + kt->x += dx; + kt->y += dy; + n++; + } + } + } + p->description = n == 1 ? "Move Point" : "Move Points"; +} + +void +plate_motion_select(plate *p, double x1, double y1) +{ + int i, j; + double x0 = p->sel_x0; + double y0 = p->sel_y0; + +#ifdef VERBOSE + printf("plate_motion_select %g %g\n", x1, y1); +#endif + p->x0 = x1; + p->y0 = y1; + + if (x0 > x1) { + double tmp = x1; + x1 = x0; + x0 = tmp; + } + if (y0 > y1) { + double tmp = y1; + y1 = y0; + y0 = tmp; + } + + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + kt->flags &= ~KT_SELECTED; + if (kt->x >= x0 && kt->x <= x1 && + kt->y >= y0 && kt->y <= y1) + kt->flags |= KT_SELECTED; + } + } +} + +void plate_unpress(plate *p) +{ + p->motmode = MOTION_MODE_IDLE; +} + +void +plate_toggle_corner(plate *p) +{ + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + if (kt->flags & KT_SELECTED) { + if (kt->flags & KT_CORNER) { + kt->flags |= KT_OPEN; + kt->flags &= ~KT_CORNER; + } else { + kt->flags &= ~KT_OPEN; + kt->flags |= KT_CORNER; + } + } + } + } +} + +void +plate_delete_pt(plate *p) +{ + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + if (kt->flags & KT_SELECTED) { + memmove(kt, &kt[1], (sp->n_kt - j - 1) * sizeof(knot)); + sp->n_kt--; + if (sp->n_kt < 3) sp->closed = 0; + j--; + } + } + } +} + +/* Note: caller is responsible for freeing returned spiro_seg. */ +spiro_seg * +draw_subpath(const subpath *sp, bezctx *bc) +{ + int n = sp->n_kt; + int i; + spiro_cp *path; + spiro_seg *s = NULL; + + if (n > 1) { + path = znew(spiro_cp, n); + + for (i = 0; i < n; i++) { + kt_flags flags = sp->kt[i].flags; + path[i].x = sp->kt[i].x; + path[i].y = sp->kt[i].y; + path[i].ty = !sp->closed && i == 0 ? '{' : + !sp->closed && i == n - 1 ? '}' : + (flags & KT_OPEN) ? 'o' : + (flags & KT_LEFT) ? '[' : + (flags & KT_RIGHT) ? ']' : + (flags & KT_CORNU) ? 'c' : + 'v'; + } + + s = run_spiro(path, n); + spiro_to_bpath(s, n, bc); + + zfree(path); + } + return s; +} + +int +file_write_plate(const char *fn, const plate *p) +{ + FILE *f = fopen(fn, "w"); + int i, j; + int st; + + if (f == NULL) + return -1; + st = fprintf(f, "(plate\n"); + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + for (j = 0; j < sp->n_kt; j++) { + kt_flags kf = sp->kt[j].flags; + const char *cmd; + + if (kf & KT_OPEN) cmd = "o"; + else if (kf & KT_CORNER) cmd = "v"; + else if (kf & KT_CORNU) cmd = "c"; + else if (kf & KT_LEFT) cmd = "["; + else if (kf & KT_RIGHT) cmd = "]"; + st = fprintf(f, " (%s %g %g)\n", cmd, sp->kt[j].x, sp->kt[j].y); + if (st < 0) break; + } + if (st < 0) break; + if (sp->closed) { + st = fprintf(f, " (z)\n"); + } + if (st < 0) break; + } + if (st >= 0) + st = fprintf(f, ")\n"); + if (st >= 0) + st = fclose(f); + return st < 0 ? -1 : 0; +} + +static int +file_read_plate_inner(sexp_reader *sr, plate *p) +{ + subpath *sp = NULL; + + sexp_token(sr); + if (sr->singlechar != '(') return -1; + sexp_token(sr); + if (strcmp(sr->tokbuf, "plate")) return -1; + for (;;) { + sexp_token(sr); + if (sr->singlechar == ')') break; + else if (sr->singlechar == '(') { + int cmd; + + sexp_token(sr); + cmd = sr->singlechar; + if (cmd == 'o' || cmd == 'v' || cmd == '[' || cmd == ']' || + cmd == 'c') { + double x, y; + knot *kt; + + sexp_token(sr); + if (!sr->is_double) return -1; + x = sr->d; + sexp_token(sr); + if (!sr->is_double) return -1; + y = sr->d; + sexp_token(sr); + if (sr->singlechar != ')') return -1; + + if (sp == NULL || sp->closed) + sp = plate_new_sp(p); + + if (sp->n_kt == sp->n_kt_max) + sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1); + kt = &sp->kt[sp->n_kt++]; + kt->x = x; + kt->y = y; + switch (cmd) { + case 'o': + kt->flags = KT_OPEN; + break; + case '[': + kt->flags = KT_LEFT; + break; + case ']': + kt->flags = KT_RIGHT; + break; + case 'c': + kt->flags = KT_CORNU; + break; + default: + kt->flags = KT_CORNER; + break; + } + } else if (cmd == 'z') { + if (sp == NULL) return -1; + sp->closed = 1; + sexp_token(sr); + if (sr->singlechar != ')') return -1; + } else + return -1; + } else return -1; + } + return 0; +} + +plate * +file_read_plate(const char *fn) +{ + FILE *f = fopen(fn, "r"); + plate *p; + sexp_reader sr; + + if (f == NULL) + return NULL; + sr.f = f; + p = new_plate(); + if (file_read_plate_inner(&sr, p)) { + free_plate(p); + p = NULL; + } + fclose(f); + p->mmode = MOUSE_MODE_SELECT; + p->motmode = MOTION_MODE_IDLE; + return p; +} diff --git a/third_party/spiro/ppedit/plate.h b/third_party/spiro/ppedit/plate.h new file mode 100644 index 0000000..a4a1d69 --- /dev/null +++ b/third_party/spiro/ppedit/plate.h @@ -0,0 +1,100 @@ +typedef enum { + KT_OPEN = 1, + KT_CORNER = 2, + KT_LEFT = 4, + KT_RIGHT = 8, + KT_CORNU = 16, + KT_SELECTED = 256 +} kt_flags; + +typedef struct { + double x; + double y; + kt_flags flags; +} knot; + +typedef struct { + int n_kt; + int n_kt_max; + knot *kt; + int closed; +} subpath; + +typedef enum { + MOUSE_MODE_SELECT, + MOUSE_MODE_ADD_CURVE, + MOUSE_MODE_ADD_CORNER, + MOUSE_MODE_ADD_CORNU, + MOUSE_MODE_ADD_LEFT, + MOUSE_MODE_ADD_RIGHT +} mouse_mode; + +typedef enum { + MOTION_MODE_IDLE, + MOTION_MODE_SELECT, + MOTION_MODE_MOVE +} motion_mode; + +typedef struct { + double x0, y0; + const char *description; + + int n_sp; + int n_sp_max; + subpath *sp; + mouse_mode mmode; + mouse_mode last_curve_mmode; + motion_mode motmode; + double sel_x0, sel_y0; +} plate; + +typedef enum { + PRESS_MOD_SHIFT = 1, + PRESS_MOD_CTRL = 2, + PRESS_MOD_DOUBLE = 4, + PRESS_MOD_TRIPLE = 8 +} press_mod; + +plate * +new_plate(void); + +void +free_plate(plate *p); + +plate * +copy_plate(const plate *p); + +void +plate_select_all(plate *p, int selected); + +subpath * +plate_find_selected_sp(plate *p); + +subpath * +plate_new_sp(plate *p); + +void +plate_press(plate *p, double x, double y, press_mod mods); + +void +plate_motion_move(plate *p, double x, double y); + +void +plate_motion_select(plate *p, double x, double y); + +void plate_unpress(plate *p); + +void +plate_toggle_corner(plate *p); + +void +plate_delete_pt(plate *p); + +spiro_seg * +draw_subpath(const subpath *sp, bezctx *bc); + +int +file_write_plate(const char *fn, const plate *p); + +plate * +file_read_plate(const char *fn); diff --git a/third_party/spiro/ppedit/ppedit.c b/third_party/spiro/ppedit/ppedit.c new file mode 100644 index 0000000..6583930 --- /dev/null +++ b/third_party/spiro/ppedit/ppedit.c @@ -0,0 +1,603 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ + +#include "x3.h" + +#include +#include +#include + +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_x3.h" +#include "bezctx_ps.h" +#include "cornu.h" +#include "spiro.h" +#include "plate.h" +#include "image.h" + +int n_iter = 10; + +typedef struct { + const char *description; + plate *p; +} undo_record; + +typedef struct { + x3widget *view; + const char *description; + plate *p; + int undo_n; + int undo_i; + undo_record undo_buf[16]; + int undo_xn_state; + + x3widget *undo_me; + x3widget *redo_me; + + x3widget *show_knots_me; + x3widget *show_bg_me; + int show_knots; + int show_bg; + + image *bg_image; +} plate_edit; + +#define C1 0.55228 +static void +draw_dot(x3dc *dc, + double x, double y, double r, guint32 rgba) +{ + x3setrgba(dc, rgba); + x3moveto(dc, x + r, y); + x3curveto(dc, x + r, y - C1 * r, x + C1 * r, y - r, x, y - r); + x3curveto(dc, x - C1 * r, y - r, x - r, y - C1 * r, x - r, y); + x3curveto(dc, x - r, y + C1 * r, x - C1 * r, y + r, x, y + r); + x3curveto(dc, x + C1 * r, y + r, x + r, y + C1 * r, x + r, y); + x3fill(dc); +} + +static void +draw_raw_rect(x3dc *dc, + double rx0, double ry0, double rx1, double ry1, guint32 rgba) +{ + x3setrgba(dc, rgba); + x3rectangle(dc, rx0, ry0, rx1 - rx0, ry1 - ry0); + x3fill(dc); +} + +static void +draw_rect(x3dc *dc, + double x, double y, double r, guint32 rgba) +{ + draw_raw_rect(dc, + x - r, y - r, x + r, y + r, rgba); +} + +static void +draw_half(x3dc *dc, + double x, double y, double r, double th, guint32 rgba) +{ + double c = cos(th); + double s = sin(th); + + x3setrgba(dc, rgba); + x3moveto(dc, x + c * r, y + s * r); + x3curveto(dc, + x + c * r + C1 * s * r, + y + s * r - C1 * c * r, + x + s * r + C1 * c * r, + y - c * r + C1 * s * r, + x + s * r, + y - c * r); + x3curveto(dc, + x + s * r - C1 * c * r, + y - c * r - C1 * s * r, + x - c * r + C1 * s * r, + y - s * r - C1 * c * r, + x - c * r, + y - s * r); + x3closepath(dc); + x3fill(dc); +} + +static void +draw_plate(x3dc *dc, + plate_edit *pe) +{ + plate *p = pe->p; + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + bezctx *bc = new_bezctx_x3(dc); + subpath *sp = &p->sp[i]; + spiro_seg *s = draw_subpath(sp, bc); + + bezctx_x3_finish(bc); + x3setrgba(dc, 0x000000ff); + x3setlinewidth(dc, 1.5); + x3stroke(dc); + + for (j = 0; j < sp->n_kt; j++) { + if (pe->show_knots) { + knot *kt = &sp->kt[j]; + kt_flags kf = kt->flags; + if ((kf & KT_SELECTED) && (kf & KT_OPEN)) { + draw_dot(dc, kt->x, kt->y, + 3, 0x000000ff); + draw_dot(dc, kt->x, kt->y, + 1.5, 0xffffffff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(dc, kt->x, kt->y, + 3, 0x000000ff); + draw_rect(dc, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(dc, kt->x, kt->y, + 2.5, 0x000080ff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNU)) { + draw_rect(dc, kt->x, kt->y, + 3, 0xc000c0ff); + draw_rect(dc, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNU)) { + draw_rect(dc, kt->x, kt->y, + 2.5, 0x800080ff); + } else if ((kf & KT_LEFT) || (kf & KT_RIGHT)) { + double th = 1.5708 + (s ? get_knot_th(s, j) : 0); + if (kf & KT_LEFT) + th += 3.1415926; + if (kf & KT_SELECTED) { + draw_half(dc, kt->x, kt->y, + 4, th, 0x000000ff); + draw_half(dc, + kt->x + sin(th), kt->y - cos(th), + 2, th, 0xffffffff); + } else { + draw_half(dc, kt->x, kt->y, + 3, th, 0x000080ff); + } + } else { + draw_dot(dc, kt->x, kt->y, + 2, 0x000080ff); + } + } + } + free_spiro(s); + } +} + +static void +draw_selection(x3dc *dc, + plate_edit *pe) +{ + plate *p = pe->p; + + if (p->motmode == MOTION_MODE_SELECT) { + double rx0 = p->sel_x0; + double ry0 = p->sel_y0; + double rx1 = p->x0; + double ry1 = p->y0; + if (rx0 > rx1) { + double tmp = rx1; + rx1 = rx0; + rx0 = tmp; + } + if (ry0 > ry1) { + double tmp = ry1; + ry1 = ry0; + ry0 = tmp; + } + if (rx1 > rx0 && ry1 > ry0) + draw_raw_rect(dc, + rx0, ry0, rx1, ry1, 0x0000ff20); + } +} + +/* Make sure there's room for at least one more undo record. */ +static void +makeroom_undo(plate_edit *pe) +{ + const int undo_max = sizeof(pe->undo_buf) / sizeof(undo_record); + + if (pe->undo_n == undo_max) { + free_plate(pe->undo_buf[0].p); + memmove(pe->undo_buf, pe->undo_buf + 1, (undo_max - 1) * sizeof(undo_record)); + pe->undo_i--; + pe->undo_n--; + } +} + +static void +set_undo_menuitem(x3widget *me, const char *name, const char *desc) +{ + char str[256]; + + if (desc) { + sprintf(str, "%s %s", name, desc); + } else { + strcpy(str, name); + } +#if 0 + gtk_container_foreach(GTK_CONTAINER(me), + (GtkCallback)gtk_label_set_text, + str); +#endif + x3setactive(me, desc != NULL); +} + +static void +set_undo_state(plate_edit *pe, const char *undo_desc, const char *redo_desc) +{ + set_undo_menuitem(pe->undo_me, "Undo", undo_desc); + set_undo_menuitem(pe->redo_me, "Redo", redo_desc); +} + +static void +begin_undo_xn(plate_edit *pe) +{ + int i; + + if (pe->undo_xn_state != 1) { + for (i = pe->undo_i; i < pe->undo_n; i++) + free_plate(pe->undo_buf[i].p); + pe->undo_n = pe->undo_i; + makeroom_undo(pe); + i = pe->undo_i; + pe->undo_buf[i].description = pe->description; + pe->undo_buf[i].p = copy_plate(pe->p); + pe->undo_n = i + 1; + pe->undo_xn_state = 1; + } +} + +static void +dirty_undo_xn(plate_edit *pe, const char *description) +{ + if (pe->undo_xn_state == 0) { + g_warning("dirty_undo_xn: not in begin_undo_xn state"); + begin_undo_xn(pe); + } + if (description == NULL) + description = pe->p->description; + if (pe->undo_xn_state == 1) { + pe->undo_i++; + pe->undo_xn_state = 2; + set_undo_state(pe, description, NULL); + } + pe->description = description; +} + +static void +begindirty_undo_xn(plate_edit *pe, const char *description) +{ + begin_undo_xn(pe); + dirty_undo_xn(pe, description); +} + +static void +end_undo_xn(plate_edit *pe) +{ + if (pe->undo_xn_state == 0) { + g_warning("end_undo_xn: not in undo xn"); + } + pe->undo_xn_state = 0; +} + +static int +undo(plate_edit *pe) +{ + if (pe->undo_i == 0) + return 0; + + if (pe->undo_i == pe->undo_n) { + makeroom_undo(pe); + pe->undo_buf[pe->undo_i].description = pe->description; + pe->undo_buf[pe->undo_i].p = pe->p; + pe->undo_n++; + } else { + free_plate(pe->p); + } + pe->undo_i--; + pe->description = pe->undo_buf[pe->undo_i].description; + set_undo_state(pe, + pe->undo_i > 0 ? pe->description : NULL, + pe->undo_buf[pe->undo_i + 1].description); + g_print("undo: %d of %d\n", pe->undo_i, pe->undo_n); + pe->p = copy_plate(pe->undo_buf[pe->undo_i].p); + return 1; +} + +static int +redo(plate_edit *pe) +{ + if (pe->undo_i >= pe->undo_n - 1) + return 0; + free_plate(pe->p); + pe->undo_i++; + set_undo_state(pe, + pe->undo_buf[pe->undo_i].description, + pe->undo_i < pe->undo_n - 1 ? + pe->undo_buf[pe->undo_i + 1].description : NULL); + pe->description = pe->undo_buf[pe->undo_i].description; + pe->p = copy_plate(pe->undo_buf[pe->undo_i].p); + g_print("redo: %d of %d\n", pe->undo_i, pe->undo_n); + return 1; +} + +typedef struct { + x3viewclient base; + + plate_edit *pe; +} x3vc_ppe; + +static void +ppedit_viewclient_draw(x3viewclient *self, x3dc *dc) +{ + plate_edit *pe = ((x3vc_ppe *)self)->pe; + +#ifdef VERBOSE + printf("ppedit draw\n"); +#endif +#if 1 + x3setrgba(dc, 0xffffffff); +#else + x3setrgba(dc, rand() << 8 | 0xff); +#endif + x3rectangle(dc, dc->x, dc->y, dc->width, dc->height); + x3fill(dc); + + draw_plate(dc, pe); + draw_selection(dc, pe); +} + +static void +ppedit_viewclient_mouse(x3viewclient *self, + int button, int mods, + double x, double y) +{ + x3vc_ppe *z = (x3vc_ppe *)self; + plate_edit *pe = z->pe; + plate *p = pe->p; + +#ifdef VERBOSE + printf("ppedit mouse: %d %d %g %g\n", button, mods, x, y); +#endif + if (button == 1) { + press_mod pm = 0; + + if (mods & GDK_SHIFT_MASK) pm |= PRESS_MOD_SHIFT; + if (mods & GDK_CONTROL_MASK) pm |= PRESS_MOD_CTRL; + if (mods == GDK_2BUTTON_PRESS) pm |= PRESS_MOD_DOUBLE; + if (mods == GDK_3BUTTON_PRESS) pm |= PRESS_MOD_TRIPLE; + + begin_undo_xn(pe); + p->description = NULL; + plate_press(p, x, y, pm); + if (p->description) dirty_undo_xn(pe, NULL); + x3view_dirty(pe->view); + } else if (button == -1) { + int need_redraw = (pe->p->motmode == MOTION_MODE_SELECT); + + plate_unpress(pe->p); + + if (need_redraw) x3view_dirty(pe->view); + } else if (button == 0 && (mods & GDK_BUTTON1_MASK)) { + if (p->motmode == MOTION_MODE_MOVE) { + plate_motion_move(p, x, y); + dirty_undo_xn(pe, NULL); + } else if (p->motmode == MOTION_MODE_SELECT) { + plate_motion_select(p, x, y); + } + x3view_dirty(pe->view); + } +} + +static int +ppedit_viewclient_key(x3viewclient *self, char *keyname, int mods, int key) +{ + x3vc_ppe *z = (x3vc_ppe *)self; + plate_edit *pe = z->pe; + double dx = 0, dy = 0; + int did_something = 0; + + if (!strcmp(keyname, "Left")) + dx = -1; + else if (!strcmp(keyname, "Right")) + dx = 1; + else if (!strcmp(keyname, "Up")) + dy = -1; + else if (!strcmp(keyname, "Down")) + dy = 1; + if (mods & X3_SHIFT_MASK) { + dx *= 10; + dy *= 10; + } else if (mods & X3_CONTROL_MASK) { + dx *= .1; + dy *= .1; + } + if (dx != 0 || dy != 0) { + begindirty_undo_xn(pe, "Keyboard move"); + plate_motion_move(pe->p, pe->p->x0 + dx, pe->p->y0 + dy); + end_undo_xn(pe); + did_something = TRUE; + } + if (did_something) { + x3view_dirty(pe->view); + return 1; + } else + return 0; +} + +x3viewclient *ppedit_viewclient(plate_edit *pe) +{ + x3vc_ppe *result = (x3vc_ppe *)malloc(sizeof(x3vc_ppe)); + + x3viewclient_init(&result->base); + result->base.draw = ppedit_viewclient_draw; + result->base.mouse = ppedit_viewclient_mouse; + result->base.key = ppedit_viewclient_key; + result->pe = pe; + + return &result->base; +} + +int +print_func(plate_edit *pe) +{ + plate *p = pe->p; + int i; + FILE *f = fopen("/tmp/foo.ps", "w"); + bezctx *bc = new_bezctx_ps(f); + + fputs(ps_prolog, f); + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + free_spiro(draw_subpath(sp, bc)); + } + bezctx_ps_close(bc); + fputs(ps_postlog, f); + fclose(f); + return TRUE; +} + +int +ppedit_callback(x3widget *w, void *data, + char *cmd, char *what, char *arg, void *more) +{ + plate_edit *pe = (plate_edit *)data; + printf("my callback: cmd=\"%s\", what=\"%s\", arg=\"%s\"\n", + cmd, what ? what : "(null)", arg ? arg : "(null)"); + + if (!strcmp(cmd, "quit")) { + gtk_main_quit(); + } else if (!strcmp(cmd, "save")) { + file_write_plate("plate", pe->p); + } else if (!strcmp(cmd, "khid")) { + pe->show_knots = !pe->show_knots; + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "bghd")) { + pe->show_bg = !pe->show_bg; + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "prin")) { + return print_func(pe); + } else if (!strcmp(cmd, "undo")) { + undo(pe); + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "redo")) { + redo(pe); + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "togc")) { + begindirty_undo_xn(pe, "Toggle Corner"); + plate_toggle_corner(pe->p); + end_undo_xn(pe); + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "delp")) { + begindirty_undo_xn(pe, "Delete Point"); + plate_delete_pt(pe->p); + end_undo_xn(pe); + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "mods")) { + pe->p->mmode = MOUSE_MODE_SELECT; + } else if (!strcmp(cmd, "modo")) { + pe->p->mmode = MOUSE_MODE_ADD_CURVE; + } else if (!strcmp(cmd, "modv")) { + pe->p->mmode = MOUSE_MODE_ADD_CORNER; + } else if (!strcmp(cmd, "modl")) { + pe->p->mmode = MOUSE_MODE_ADD_LEFT; + } else if (!strcmp(cmd, "modr")) { + pe->p->mmode = MOUSE_MODE_ADD_RIGHT; + } else if (!strcmp(cmd, "modc")) { + pe->p->mmode = MOUSE_MODE_ADD_CORNU; + } + return 1; +} + +static void +create_mainwin(plate_edit *pe) +{ + x3widget *mainwin; + x3widget *view; + x3widget *menu; + void *data = pe; + x3viewflags viewflags = x3view_click | x3view_hover | x3view_key | + x3view_2d | x3view_scroll; + + mainwin = x3window(x3window_main, "ppedit", ppedit_callback, data); + + menu = x3menu(mainwin, "File"); + + x3menuitem(menu, "Save", "save", "s"); + x3menuitem(menu, "Print", "prin", "p"); + x3menuitem(menu, "Quit", "quit", "q"); + + menu = x3menu(mainwin, "Edit"); + + pe->undo_me = x3menuitem(menu, "Undo", "undo", "z"); + pe->redo_me = x3menuitem(menu, "Redo", "redo", "y"); + + //set_undo_state(p, NULL, NULL); + x3menuitem(menu, "Toggle Corner", "togc", "t"); + x3menuitem(menu, "Delete Point", "delp", "d"); + x3menuitem(menu, "Selection Mode", "mods", "1"); + x3menuitem(menu, "Add Curve Mode", "modo", "2"); + x3menuitem(menu, "Add Corner Mode", "modv", "3"); + x3menuitem(menu, "Add Left Mode", "modl", "4"); + x3menuitem(menu, "Add Right Mode", "modr", "5"); + x3menuitem(menu, "Add G2 point Mode", "modc", "6"); + + menu = x3menu(mainwin, "View"); + + pe->show_knots_me = x3menuitem(menu, "Hide Knots", "khid", "k"); + pe->show_bg_me = x3menuitem(menu, "Hide BG", "bghd", "b"); + + pe->view = x3view(mainwin, viewflags, ppedit_viewclient(pe)); + +#ifdef X3_GTK + gtk_window_set_default_size(GTK_WINDOW(mainwin->widget), 512, 512); +#endif + + x3_window_show(mainwin); +} + +int main(int argc, char **argv) +{ + plate_edit pe; + plate *p = NULL; + x3init(&argc, &argv); + char *reason; + + if (argc > 1) + p = file_read_plate(argv[1]); + if (p == NULL) + p = new_plate(); + pe.p = p; + pe.undo_n = 0; + pe.undo_i = 0; + pe.undo_xn_state = 0; + pe.show_knots = 1; + pe.show_bg = 1; + pe.bg_image = load_image_file("/tmp/foo.ppm", &reason); + create_mainwin(&pe); + x3main(); + return 0; +} diff --git a/third_party/spiro/ppedit/ppedit_gtk1.c b/third_party/spiro/ppedit/ppedit_gtk1.c new file mode 100644 index 0000000..b81299e --- /dev/null +++ b/third_party/spiro/ppedit/ppedit_gtk1.c @@ -0,0 +1,930 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include +#include +#include +#include +#include +#include + +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_libart.h" +#include "bezctx_ps.h" +#include "cornu.h" +#include "spiro.h" +#include "plate.h" +#include "image.h" + +int n_iter = 10; + +typedef struct { + const char *description; + plate *p; +} undo_record; + +typedef struct { + GtkWidget *da; + const char *description; + plate *p; + int undo_n; + int undo_i; + undo_record undo_buf[16]; + int undo_xn_state; + + GtkWidget *undo_me; + GtkWidget *redo_me; + + GtkWidget *show_knots_me; + GtkWidget *show_bg_me; + int show_knots; + int show_bg; + + image *bg_image; +} plate_edit; + +int +quit_func(GtkWidget *widget, gpointer dummy) +{ + gtk_main_quit(); + return TRUE; +} + +#define C1 0.55228 +static void +draw_dot(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + double x, double y, double r, guint32 rgba) +{ + ArtBpath bp[6]; + ArtVpath *vp; + ArtSVP *svp; + + bp[0].code = ART_MOVETO; + bp[0].x3 = x + r; + bp[0].y3 = y; + bp[1].code = ART_CURVETO; + bp[1].x1 = x + r; + bp[1].y1 = y - C1 * r; + bp[1].x2 = x + C1 * r; + bp[1].y2 = y - r; + bp[1].x3 = x; + bp[1].y3 = y - r; + bp[2].code = ART_CURVETO; + bp[2].x1 = x - C1 * r; + bp[2].y1 = y - r; + bp[2].x2 = x - r; + bp[2].y2 = y - C1 * r; + bp[2].x3 = x - r; + bp[2].y3 = y; + bp[3].code = ART_CURVETO; + bp[3].x1 = x - r; + bp[3].y1 = y + C1 * r; + bp[3].x2 = x - C1 * r; + bp[3].y2 = y + r; + bp[3].x3 = x; + bp[3].y3 = y + r; + bp[4].code = ART_CURVETO; + bp[4].x1 = x + C1 * r; + bp[4].y1 = y + r; + bp[4].x2 = x + r; + bp[4].y2 = y + C1 * r; + bp[4].x3 = x + r; + bp[4].y3 = y; + bp[5].code = ART_END; + + vp = art_bez_path_to_vec(bp, 0.25); + svp = art_svp_from_vpath(vp); + art_free(vp); + + art_rgb_svp_alpha(svp, x0, y0, x1, y1, rgba, buf, rowstride, NULL); + art_svp_free(svp); +} + +static void +draw_raw_rect(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + double rx0, double ry0, double rx1, double ry1, guint32 rgba) +{ + ArtVpath vp[6]; + ArtSVP *svp; + + vp[0].code = ART_MOVETO; + vp[0].x = rx0; + vp[0].y = ry1; + vp[1].code = ART_LINETO; + vp[1].x = rx1; + vp[1].y = ry1; + vp[2].code = ART_LINETO; + vp[2].x = rx1; + vp[2].y = ry0; + vp[3].code = ART_LINETO; + vp[3].x = rx0; + vp[3].y = ry0; + vp[4].code = ART_LINETO; + vp[4].x = rx0; + vp[4].y = ry1; + vp[5].code = ART_END; + + svp = art_svp_from_vpath(vp); + + art_rgb_svp_alpha(svp, x0, y0, x1, y1, rgba, buf, rowstride, NULL); + art_svp_free(svp); +} + +static void +draw_rect(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + double x, double y, double r, guint32 rgba) +{ + draw_raw_rect(buf, x0, y0, x1, y1, rowstride, + x - r, y - r, x + r, y + r, rgba); +} + +static void +draw_half(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + double x, double y, double r, double th, guint32 rgba) +{ + ArtBpath bp[6]; + ArtVpath *vp; + ArtSVP *svp; + double c = cos(th); + double s = sin(th); + + bp[0].code = ART_MOVETO; + bp[0].x3 = x + c * r; + bp[0].y3 = y + s * r; + bp[1].code = ART_CURVETO; + bp[1].x1 = x + c * r + C1 * s * r; + bp[1].y1 = y + s * r - C1 * c * r; + bp[1].x2 = x + s * r + C1 * c * r; + bp[1].y2 = y - c * r + C1 * s * r; + bp[1].x3 = x + s * r; + bp[1].y3 = y - c * r; + bp[2].code = ART_CURVETO; + bp[2].x1 = x + s * r - C1 * c * r; + bp[2].y1 = y - c * r - C1 * s * r; + bp[2].x2 = x - c * r + C1 * s * r; + bp[2].y2 = y - s * r - C1 * c * r; + bp[2].x3 = x - c * r; + bp[2].y3 = y - s * r; + bp[3].code = ART_LINETO; + bp[3].x3 = x + c * r; + bp[3].y3 = y + s * r; + bp[4].code = ART_END; + + vp = art_bez_path_to_vec(bp, 0.25); + svp = art_svp_from_vpath(vp); + art_free(vp); + + art_rgb_svp_alpha(svp, x0, y0, x1, y1, rgba, buf, rowstride, NULL); + art_svp_free(svp); +} + +static ArtVpath * +bezctx_to_vpath(bezctx *bc) +{ + ArtBpath *bp = bezctx_to_bpath(bc); + ArtVpath *vp = art_bez_path_to_vec(bp, .25); + + g_free(bp); + if (vp[0].code == ART_END || vp[1].code == ART_END) { + g_free(vp); + vp = NULL; + } + return vp; +} + +static void +draw_plate(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + plate_edit *pe) +{ + plate *p = pe->p; + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + bezctx *bc = new_bezctx_libart(); + subpath *sp = &p->sp[i]; + spiro_seg *s = draw_subpath(sp, bc); + ArtVpath *vp = bezctx_to_vpath(bc); + + if (vp != NULL) { + ArtSVP *svp = art_svp_vpath_stroke(vp, ART_PATH_STROKE_JOIN_MITER, + ART_PATH_STROKE_CAP_BUTT, + 1.5, 4.0, 0.25); + + art_free(vp); + art_rgb_svp_alpha(svp, x0, y0, x1, y1, 0x000000ff, buf, rowstride, + NULL); + art_svp_free(svp); + } + + for (j = 0; j < sp->n_kt; j++) { + if (pe->show_knots) { + knot *kt = &sp->kt[j]; + kt_flags kf = kt->flags; + if ((kf & KT_SELECTED) && (kf & KT_OPEN)) { + draw_dot(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 3, 0x000000ff); + draw_dot(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 1.5, 0xffffffff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 3, 0x000000ff); + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 2.5, 0x000080ff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNU)) { + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 3, 0xc000c0ff); + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNU)) { + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 2.5, 0x800080ff); + } else if ((kf & KT_LEFT) || (kf & KT_RIGHT)) { + double th = 1.5708 + (s ? get_knot_th(s, j) : 0); + if (kf & KT_LEFT) + th += 3.1415926; + if (kf & KT_SELECTED) { + draw_half(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 4, th, 0x000000ff); + draw_half(buf, x0, y0, x1, y1, rowstride, + kt->x + sin(th), kt->y - cos(th), + 2, th, 0xffffffff); + } else { + draw_half(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 3, th, 0x000080ff); + } + } else { + draw_dot(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 2, 0x000080ff); + } + } + } + free_spiro(s); + } +} + +static void +draw_selection(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + plate_edit *pe) +{ + plate *p = pe->p; + + if (p->motmode == MOTION_MODE_SELECT) { + double rx0 = p->sel_x0; + double ry0 = p->sel_y0; + double rx1 = p->x0; + double ry1 = p->y0; + if (rx0 > rx1) { + double tmp = rx1; + rx1 = rx0; + rx0 = tmp; + } + if (ry0 > ry1) { + double tmp = ry1; + ry1 = ry0; + ry0 = tmp; + } + if (rx1 > rx0 && ry1 > ry0) + draw_raw_rect(buf, x0, y0, x1, y1, rowstride, + rx0, ry0, rx1, ry1, 0x0000ff20); + } +} + +static void +render_bg_layer(guchar *buf, int rowstride, int x0, int y0, int x1, int y1, + plate_edit *pe) +{ + const double affine[6] = { 1, 0, 0, 1, 0, 0 }; + + if (pe->show_bg && pe->bg_image) + render_image(pe->bg_image, affine, + buf, rowstride, x0, y0, x1, y1); + else + memset(buf, 255, (y1 - y0) * rowstride); +} + +static gint +data_expose (GtkWidget *widget, GdkEventExpose *event, void *data) +{ + plate_edit *pe = (plate_edit *)data; + int x0 = event->area.x; + int y0 = event->area.y; + int width = event->area.width; + int height = event->area.height; + guchar *rgb; + int rowstride = (width * 3 + 3) & -4; + + rgb = g_new (guchar, event->area.height * rowstride); + + render_bg_layer(rgb, rowstride, x0, y0, x0 + width, y0 + height, pe); + + draw_plate(rgb, x0, y0, x0 + width, y0 + height, rowstride, pe); + + draw_selection(rgb, x0, y0, x0 + width, y0 + height, rowstride, pe); + + gdk_draw_rgb_image(widget->window, + widget->style->black_gc, + x0, y0, width, height, + GDK_RGB_DITHER_NONE, rgb, + rowstride); + g_free(rgb); + return FALSE; +} + +/* Make sure there's room for at least one more undo record. */ +static void +makeroom_undo(plate_edit *pe) +{ + const int undo_max = sizeof(pe->undo_buf) / sizeof(undo_record); + + if (pe->undo_n == undo_max) { + free_plate(pe->undo_buf[0].p); + memmove(pe->undo_buf, pe->undo_buf + 1, (undo_max - 1) * sizeof(undo_record)); + pe->undo_i--; + pe->undo_n--; + } +} + +static void +set_undo_menuitem(GtkWidget *me, const char *name, const char *desc) +{ + char str[256]; + + if (desc) { + sprintf(str, "%s %s", name, desc); + } else { + strcpy(str, name); + } + gtk_container_foreach(GTK_CONTAINER(me), + (GtkCallback)gtk_label_set_text, + str); + gtk_widget_set_sensitive(me, desc != NULL); +} + +static void +set_undo_state(plate_edit *pe, const char *undo_desc, const char *redo_desc) +{ + set_undo_menuitem(pe->undo_me, "Undo", undo_desc); + set_undo_menuitem(pe->redo_me, "Redo", redo_desc); +} + +static void +begin_undo_xn(plate_edit *pe) +{ + int i; + + if (pe->undo_xn_state != 1) { + for (i = pe->undo_i; i < pe->undo_n; i++) + free_plate(pe->undo_buf[i].p); + pe->undo_n = pe->undo_i; + makeroom_undo(pe); + i = pe->undo_i; + pe->undo_buf[i].description = pe->description; + pe->undo_buf[i].p = copy_plate(pe->p); + pe->undo_n = i + 1; + pe->undo_xn_state = 1; + } +} + +static void +dirty_undo_xn(plate_edit *pe, const char *description) +{ + if (pe->undo_xn_state == 0) { + g_warning("dirty_undo_xn: not in begin_undo_xn state"); + begin_undo_xn(pe); + } + if (description == NULL) + description = pe->p->description; + if (pe->undo_xn_state == 1) { + pe->undo_i++; + pe->undo_xn_state = 2; + set_undo_state(pe, description, NULL); + } + pe->description = description; +} + +static void +begindirty_undo_xn(plate_edit *pe, const char *description) +{ + begin_undo_xn(pe); + dirty_undo_xn(pe, description); +} + +static void +end_undo_xn(plate_edit *pe) +{ + if (pe->undo_xn_state == 0) { + g_warning("end_undo_xn: not in undo xn"); + } + pe->undo_xn_state = 0; +} + +static int +undo(plate_edit *pe) +{ + if (pe->undo_i == 0) + return 0; + + if (pe->undo_i == pe->undo_n) { + makeroom_undo(pe); + pe->undo_buf[pe->undo_i].description = pe->description; + pe->undo_buf[pe->undo_i].p = pe->p; + pe->undo_n++; + } else { + free_plate(pe->p); + } + pe->undo_i--; + pe->description = pe->undo_buf[pe->undo_i].description; + set_undo_state(pe, + pe->undo_i > 0 ? pe->description : NULL, + pe->undo_buf[pe->undo_i + 1].description); + g_print("undo: %d of %d\n", pe->undo_i, pe->undo_n); + pe->p = copy_plate(pe->undo_buf[pe->undo_i].p); + return 1; +} + +static int +redo(plate_edit *pe) +{ + if (pe->undo_i >= pe->undo_n - 1) + return 0; + free_plate(pe->p); + pe->undo_i++; + set_undo_state(pe, + pe->undo_buf[pe->undo_i].description, + pe->undo_i < pe->undo_n - 1 ? + pe->undo_buf[pe->undo_i + 1].description : NULL); + pe->description = pe->undo_buf[pe->undo_i].description; + pe->p = copy_plate(pe->undo_buf[pe->undo_i].p); + g_print("redo: %d of %d\n", pe->undo_i, pe->undo_n); + return 1; +} + +static gint +data_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + plate *p = pe->p; + double x, y; + press_mod mods = 0; + +#define noVERBOSE +#ifdef VERBOSE + g_print ("button press %f %f %f %d\n", + event->x, event->y, event->pressure, event->type); + +#endif + x = event->x; + y = event->y; + if (event->state & GDK_SHIFT_MASK) mods |= PRESS_MOD_SHIFT; + if (event->state & GDK_CONTROL_MASK) mods |= PRESS_MOD_CTRL; + if (event->type == GDK_2BUTTON_PRESS) mods |= PRESS_MOD_DOUBLE; + if (event->type == GDK_3BUTTON_PRESS) mods |= PRESS_MOD_TRIPLE; + + begin_undo_xn(pe); + p->description = NULL; + plate_press(p, x, y, mods); + if (p->description) dirty_undo_xn(pe, NULL); + gtk_widget_queue_draw(widget); + + return TRUE; +} + +static gint +data_motion_move (GtkWidget *widget, GdkEventMotion *event, plate_edit *pe) +{ + double x, y; + x = event->x; + y = event->y; + + plate_motion_move(pe->p, x, y); + dirty_undo_xn(pe, NULL); + + gtk_widget_queue_draw(widget); + + return TRUE; +} + +static gint +data_motion_select (GtkWidget *widget, GdkEventMotion *event, plate_edit *pe) +{ + double x, y; + + x = event->x; + y = event->y; + + plate_motion_select(pe->p, x, y); + gtk_widget_queue_draw(widget); + + return TRUE; +} + +static gint +data_motion (GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + +#ifdef VERBOSE + g_print ("motion %f %f %f\n", event->x, event->y, event->pressure); + +#endif + if (pe->p->motmode == MOTION_MODE_MOVE) + return data_motion_move(widget, event, pe); + else if (pe->p->motmode == MOTION_MODE_SELECT) + return data_motion_select(widget, event, pe); + return TRUE; +} + +static gint +data_button_release (GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + int need_redraw; + + need_redraw = (pe->p->motmode == MOTION_MODE_SELECT); + + plate_unpress(pe->p); + + gtk_widget_queue_draw(widget); + + return TRUE; +} + +static gboolean +key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + int delta = event->state & 4 ? 10 : 1; + int old_n_iter = n_iter; + double dx = 0, dy = 0; + gboolean did_something = FALSE; + + g_print("key press %d %s %d\n", event->keyval, event->string, event->state); + + if (event->keyval == '<') { + did_something = TRUE; + n_iter -= delta; + } else if (event->keyval == '>') { + n_iter += delta; + } + if (n_iter < 0) n_iter = 0; + if (n_iter != old_n_iter) + g_print("n_iter = %d\n", n_iter); + + if (event->keyval == GDK_Left) + dx = -1; + else if (event->keyval == GDK_Right) + dx = 1; + else if (event->keyval == GDK_Up) + dy = -1; + else if (event->keyval == GDK_Down) + dy = 1; + if (event->state & GDK_SHIFT_MASK) { + dx *= 10; + dy *= 10; + } else if (event->state & GDK_CONTROL_MASK) { + dx *= .1; + dy *= .1; + } + if (dx != 0 || dy != 0) { + begindirty_undo_xn(pe, "Keyboard move"); + plate_motion_move(pe->p, pe->p->x0 + dx, pe->p->y0 + dy); + end_undo_xn(pe); + did_something = TRUE; + } + + if (did_something) { + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key-press-event"); + gtk_widget_queue_draw(widget); + } + + return did_something; +} + +static gint +toggle_corner_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + begindirty_undo_xn(pe, "Toggle Corner"); + plate_toggle_corner(pe->p); + end_undo_xn(pe); + gtk_widget_queue_draw(pe->da); + + return TRUE; +} + +static gint +delete_pt_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + begindirty_undo_xn(pe, "Delete Point"); + plate_delete_pt(pe->p); + end_undo_xn(pe); + gtk_widget_queue_draw(pe->da); + + return TRUE; +} + +static gint +set_select_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_SELECT; + return TRUE; +} + +static gint +set_curve_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_CURVE; + pe->p->last_curve_mmode = pe->p->mmode; + return TRUE; +} + +static gint +set_corner_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_CORNER; + return TRUE; +} + +static gint +set_left_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_LEFT; + return TRUE; +} + +static gint +set_right_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_RIGHT; + return TRUE; +} + +static gint +set_cornu_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_CORNU; + pe->p->last_curve_mmode = pe->p->mmode; + return TRUE; +} + +static gint +undo_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + undo(pe); + gtk_widget_queue_draw(pe->da); + + return TRUE; +} + +static gint +redo_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + redo(pe); + gtk_widget_queue_draw(pe->da); + + return TRUE; +} + +static gint +save_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + file_write_plate("plate", pe->p); + + return TRUE; +} + +static gint +toggle_show_knots_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->show_knots = !pe->show_knots; + gtk_container_foreach(GTK_CONTAINER(pe->show_knots_me), + (GtkCallback)gtk_label_set_text, + pe->show_knots ? "Hide Knots" : "Show Knots"); + gtk_widget_queue_draw(pe->da); + return TRUE; +} + +static gint +toggle_show_bg_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->show_bg = !pe->show_bg; + gtk_container_foreach(GTK_CONTAINER(pe->show_bg_me), + (GtkCallback)gtk_label_set_text, + pe->show_bg ? "Hide BG" : "Show BG"); + gtk_widget_queue_draw(pe->da); + return TRUE; +} + +static gint +print_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + plate *p = pe->p; + int i; + FILE *f = fopen("/tmp/foo.ps", "w"); + bezctx *bc = new_bezctx_ps(f); + + fputs(ps_prolog, f); + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + free_spiro(draw_subpath(sp, bc)); + } + bezctx_ps_close(bc); + fputs(ps_postlog, f); + fclose(f); + return TRUE; +} + +static GtkWidget * +add_menuitem(GtkWidget *menu, const char *name, GtkSignalFunc callback, + gpointer callback_data, GtkAccelGroup *ag, const char *accel) +{ + GtkWidget *menuitem; + + menuitem = gtk_menu_item_new_with_label(name); + gtk_menu_append(GTK_MENU(menu), menuitem); + gtk_widget_show(menuitem); + if (accel != NULL) { + guint accel_key, accel_mods; + + gtk_accelerator_parse(accel, &accel_key, &accel_mods); + gtk_widget_add_accelerator(menuitem, "activate", ag, + accel_key, accel_mods, GTK_ACCEL_VISIBLE); + } + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + (GtkSignalFunc)callback, callback_data); + return (menuitem); +} + +static void +create_mainwin(plate_edit *p) +{ + GtkWidget *mainwin; + GtkWidget *eb; + GtkWidget *da; + GtkWidget *vbox; + GtkWidget *menubar; + GtkWidget *menu; + GtkWidget *menuitem; + GtkAccelGroup *ag; + void *data = p; + + mainwin = gtk_widget_new(gtk_window_get_type(), + "GtkWindow::type", GTK_WINDOW_TOPLEVEL, + "GtkWindow::title", "pattern plate editor", + NULL); + gtk_signal_connect(GTK_OBJECT(mainwin), "destroy", + (GtkSignalFunc)quit_func, NULL); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(mainwin), vbox); + + menubar = gtk_menu_bar_new(); + ag = gtk_accel_group_new(); + gtk_window_add_accel_group(GTK_WINDOW(mainwin), ag); + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); + + menu = gtk_menu_new(); + menuitem = gtk_menu_item_new_with_label("File"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); + gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem); + gtk_widget_show(menuitem); + gtk_menu_set_accel_group(GTK_MENU(menu), ag); + add_menuitem(menu, "Save", (GtkSignalFunc)save_func, data, ag, "S"); + add_menuitem(menu, "Quit", (GtkSignalFunc)quit_func, data, ag, "Q"); + add_menuitem(menu, "Print", (GtkSignalFunc)print_func, data, ag, "P"); + + menu = gtk_menu_new(); + menuitem = gtk_menu_item_new_with_label("Edit"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); + gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem); + gtk_widget_show(menuitem); + gtk_menu_set_accel_group(GTK_MENU(menu), ag); + p->undo_me = add_menuitem(menu, "Undo", (GtkSignalFunc)undo_func, data, ag, "Z"); + p->redo_me = add_menuitem(menu, "Redo", (GtkSignalFunc)redo_func, data, ag, "Y"); + set_undo_state(p, NULL, NULL); + add_menuitem(menu, "Toggle Corner", (GtkSignalFunc)toggle_corner_func, data, ag, "T"); + add_menuitem(menu, "Delete Point", (GtkSignalFunc)delete_pt_func, data, ag, "D"); + add_menuitem(menu, "Selection Mode", (GtkSignalFunc)set_select_mode_func, data, ag, "1"); + add_menuitem(menu, "Add Curve Mode", (GtkSignalFunc)set_curve_mode_func, data, ag, "2"); + add_menuitem(menu, "Add Corner Mode", (GtkSignalFunc)set_corner_mode_func, data, ag, "3"); + add_menuitem(menu, "Add Left Mode", (GtkSignalFunc)set_left_mode_func, data, ag, "4"); + add_menuitem(menu, "Add Right Mode", (GtkSignalFunc)set_right_mode_func, data, ag, "5"); + add_menuitem(menu, "Add Cornu Mode", (GtkSignalFunc)set_cornu_mode_func, data, ag, "6"); + + + menu = gtk_menu_new(); + menuitem = gtk_menu_item_new_with_label("View"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); + gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem); + gtk_widget_show(menuitem); + gtk_menu_set_accel_group(GTK_MENU(menu), ag); + p->show_knots_me = add_menuitem(menu, "Hide Knots", + (GtkSignalFunc)toggle_show_knots_func, + data, ag, "K"); + p->show_bg_me = add_menuitem(menu, "Hide BG", + (GtkSignalFunc)toggle_show_bg_func, + data, ag, "B"); + + eb = gtk_event_box_new (); + GTK_WIDGET_SET_FLAGS(eb, GTK_CAN_FOCUS); + gtk_box_pack_start(GTK_BOX(vbox), eb, TRUE, TRUE, 0); + gtk_widget_set_extension_events (eb, GDK_EXTENSION_EVENTS_ALL); + gtk_signal_connect(GTK_OBJECT (eb), "button-press-event", + (GtkSignalFunc) data_button_press, data); + gtk_signal_connect(GTK_OBJECT (eb), "motion-notify-event", + (GtkSignalFunc) data_motion, data); + gtk_signal_connect(GTK_OBJECT (eb), "button-release-event", + (GtkSignalFunc) data_button_release, data); + gtk_signal_connect(GTK_OBJECT(eb), "key-press-event", + (GtkSignalFunc)key_press, data); + + da = gtk_drawing_area_new(); + p->da = da; + gtk_window_set_default_size(GTK_WINDOW(mainwin), 512, 512); + gtk_container_add(GTK_CONTAINER(eb), da); + gtk_signal_connect(GTK_OBJECT (da), "expose-event", + (GtkSignalFunc) data_expose, data); +#if 0 + gtk_widget_set_double_buffered(da, FALSE); +#endif + + gtk_widget_grab_focus(eb); + gtk_widget_show(da); + gtk_widget_show(eb); + gtk_widget_show(menubar); + gtk_widget_show(vbox); + gtk_widget_show(mainwin); +} + +int main(int argc, char **argv) +{ + plate_edit pe; + plate *p = NULL; + gtk_init(&argc, &argv); + gtk_widget_set_default_colormap(gdk_rgb_get_cmap()); + gtk_widget_set_default_visual(gdk_rgb_get_visual()); + char *reason; + + if (argc > 1) + p = file_read_plate(argv[1]); + if (p == NULL) + p = new_plate(); + pe.p = p; + pe.undo_n = 0; + pe.undo_i = 0; + pe.undo_xn_state = 0; + pe.show_knots = 1; + pe.show_bg = 1; + pe.bg_image = load_image_file("/tmp/foo.ppm", &reason); + create_mainwin(&pe); + gtk_main(); + return 0; +} diff --git a/third_party/spiro/ppedit/sexp.c b/third_party/spiro/ppedit/sexp.c new file mode 100644 index 0000000..b4f0de4 --- /dev/null +++ b/third_party/spiro/ppedit/sexp.c @@ -0,0 +1,127 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include +#include + +#include "sexp.h" + +/* This is handcoded to avoid locale problems. */ +static int +parse_double(sexp_reader *sr) +{ + double sign = 1.0, val = 0.0; + int i; + int numstart; + const char * const b = sr->tokbuf; + int is_double = 1; + + i = 0; + if (b[i] == '+') { + i++; + } else if (b[i] == '-') { + sign = -1.0; + i++; + } + numstart = i; + while (b[i] >= '0' && b[i] <= '9') + val = val * 10.0 + b[i++] - '0'; + if (b[i] == '.') { + double frac = 1.0; + + for (i++; b[i] >= '0' && b[i] <= '9'; i++) { + frac *= 0.1; + val += (b[i] - '0') * frac; + } + + /* A '.' without any digits on either side isn't valid. */ + if (i == numstart + 1) + is_double = 0; + } + if (b[i] == 'e' || b[i] == 'E') { + int expsign = 1, exp = 0; + int expstart; + + if (b[i] == '+') { + i++; + } else if (b[i] == '-') { + expsign = -1; + i++; + } + expstart = i; + while (b[i] >= '0' && b[i] <= '9') + exp = exp * 10 + b[i++] - '0'; + + if (i == expstart) + is_double = 0; + val *= pow(10.0, expsign * exp); + } + val *= sign; + sr->d = val; + if (b[i] != 0) is_double = 0; + sr->is_double = is_double; + return is_double; +} + +/* Return values: 0 = EOF, 1 = token but not double, 2 = valid double */ +int +sexp_token(sexp_reader *sr) +{ + int c; + int i; + + sr->singlechar = -1; + if (sr->f == NULL) + return 0; + + for (;;) { + c = getc(sr->f); + if (c == EOF) { + sr->f = NULL; + return 0; + } else if (c == '#') { + do { + c = getc(sr->f); + } while (c != EOF && c != '\r' && c != '\n'); + } else if (c != ' ' && c != '\r' && c != '\n' && c != '\t') + break; + } + sr->tokbuf[0] = c; + i = 1; + if (c != '(' && c != ')') { + for (;;) { + c = getc(sr->f); + if (c == EOF) { + sr->f = NULL; + break; + } else if (c == ' ' || c == '\r' || c == '\n' || c == '\t') { + break; + } else if (c == '(' || c == ')' || c == '#') { + ungetc(c, sr->f); + break; + } else if (i < sizeof(sr->tokbuf) - 1) + sr->tokbuf[i++] = c; + } + } + sr->tokbuf[i] = 0; + if (i == 1) + sr->singlechar = sr->tokbuf[0]; + return 1 + parse_double(sr); +} diff --git a/third_party/spiro/ppedit/sexp.h b/third_party/spiro/ppedit/sexp.h new file mode 100644 index 0000000..162152d --- /dev/null +++ b/third_party/spiro/ppedit/sexp.h @@ -0,0 +1,10 @@ +typedef struct { + FILE *f; + char tokbuf[256]; + int singlechar; + int is_double; + double d; +} sexp_reader; + +int +sexp_token(sexp_reader *sr); diff --git a/third_party/spiro/ppedit/spiro.c b/third_party/spiro/ppedit/spiro.c new file mode 100644 index 0000000..551697c --- /dev/null +++ b/third_party/spiro/ppedit/spiro.c @@ -0,0 +1,1070 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +/* C implementation of third-order polynomial spirals. */ + +#include +#include +#include + +#include "bezctx_intf.h" +#include "spiro.h" + +struct spiro_seg_s { + double x; + double y; + char ty; + double bend_th; + double ks[4]; + double seg_ch; + double seg_th; + double l; +}; + +typedef struct { + double a[11]; /* band-diagonal matrix */ + double al[5]; /* lower part of band-diagonal decomposition */ +} bandmat; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +int n = 4; + +#ifndef ORDER +#define ORDER 12 +#endif + +/* Integrate polynomial spiral curve over range -.5 .. .5. */ +void +integrate_spiro(const double ks[4], double xy[2]) +{ +#if 0 + int n = 1024; +#endif + double th1 = ks[0]; + double th2 = .5 * ks[1]; + double th3 = (1./6) * ks[2]; + double th4 = (1./24) * ks[3]; + double x, y; + double ds = 1. / n; + double ds2 = ds * ds; + double ds3 = ds2 * ds; + double k0 = ks[0] * ds; + double k1 = ks[1] * ds; + double k2 = ks[2] * ds; + double k3 = ks[3] * ds; + int i; + double s = .5 * ds - .5; + + x = 0; + y = 0; + + for (i = 0; i < n; i++) { + +#if ORDER > 2 + double u, v; + double km0, km1, km2, km3; + + if (n == 1) { + km0 = k0; + km1 = k1 * ds; + km2 = k2 * ds2; + } else { + km0 = (((1./6) * k3 * s + .5 * k2) * s + k1) * s + k0; + km1 = ((.5 * k3 * s + k2) * s + k1) * ds; + km2 = (k3 * s + k2) * ds2; + } + km3 = k3 * ds3; +#endif + + { + +#if ORDER == 4 + double km0_2 = km0 * km0; + u = 24 - km0_2; + v = km1; +#endif + +#if ORDER == 6 + double km0_2 = km0 * km0; + double km0_4 = km0_2 * km0_2; + u = 24 - km0_2 + (km0_4 - 4 * km0 * km2 - 3 * km1 * km1) * (1./80); + v = km1 + (km3 - 6 * km0_2 * km1) * (1./80); +#endif + +#if ORDER == 8 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t6_6 = t4_4 * t2_2; + u = 1; + v = 0; + v += (1./12) * t1_2 + (1./80) * t1_4; + u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6; + v -= (1./480) * t3_4 + (1./2688) * t3_6; + u += (1./1920) * t4_4 + (1./10752) * t4_6; + v += (1./53760) * t5_6; + u -= (1./322560) * t6_6; +#endif + +#if ORDER == 10 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t2_7 = 2 * (t1_3 * t1_4); + double t2_8 = t1_4 * t1_4; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4); + double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1; + double t6_6 = t4_4 * t2_2; + double t6_7 = t4_4 * t2_3 + t4_5 * t2_2; + double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2; + double t7_8 = t6_6 * t1_2 + t6_7 * t1_1; + double t8_8 = t6_6 * t2_2; + u = 1; + v = 0; + v += (1./12) * t1_2 + (1./80) * t1_4; + u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8; + v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8; + u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8; + v += (1./53760) * t5_6 + (1./276480) * t5_8; + u -= (1./322560) * t6_6 + (1./1.65888e+06) * t6_8; + v -= (1./1.16122e+07) * t7_8; + u += (1./9.28973e+07) * t8_8; +#endif + +#if ORDER == 12 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t2_7 = 2 * (t1_3 * t1_4); + double t2_8 = t1_4 * t1_4; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1; + double t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4); + double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4; + double t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5); + double t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1; + double t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1; + double t6_6 = t4_4 * t2_2; + double t6_7 = t4_4 * t2_3 + t4_5 * t2_2; + double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2; + double t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2; + double t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2; + double t7_8 = t6_6 * t1_2 + t6_7 * t1_1; + double t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1; + double t8_8 = t6_6 * t2_2; + double t8_9 = t6_6 * t2_3 + t6_7 * t2_2; + double t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2; + double t9_10 = t8_8 * t1_2 + t8_9 * t1_1; + double t10_10 = t8_8 * t2_2; + u = 1; + v = 0; + v += (1./12) * t1_2 + (1./80) * t1_4; + u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8; + v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8 + (1./67584) * t3_10; + u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8 + (1./270336) * t4_10; + v += (1./53760) * t5_6 + (1./276480) * t5_8 + (1./1.35168e+06) * t5_10; + u -= (1./322560) * t6_6 + (1./1.65888e+06) * t6_8 + (1./8.11008e+06) * t6_10; + v -= (1./1.16122e+07) * t7_8 + (1./5.67706e+07) * t7_10; + u += (1./9.28973e+07) * t8_8 + (1./4.54164e+08) * t8_10; + v += (1./4.08748e+09) * t9_10; + u -= (1./4.08748e+10) * t10_10; +#endif + +#if ORDER == 14 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t2_7 = 2 * (t1_3 * t1_4); + double t2_8 = t1_4 * t1_4; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1; + double t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2; + double t3_12 = t2_8 * t1_4; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4); + double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4; + double t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5); + double t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5; + double t4_11 = 2 * (t2_3 * t2_8 + t2_4 * t2_7 + t2_5 * t2_6); + double t4_12 = 2 * (t2_4 * t2_8 + t2_5 * t2_7) + t2_6 * t2_6; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1; + double t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1; + double t5_12 = t4_8 * t1_4 + t4_9 * t1_3 + t4_10 * t1_2 + t4_11 * t1_1; + double t6_6 = t4_4 * t2_2; + double t6_7 = t4_4 * t2_3 + t4_5 * t2_2; + double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2; + double t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2; + double t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2; + double t6_11 = t4_4 * t2_7 + t4_5 * t2_6 + t4_6 * t2_5 + t4_7 * t2_4 + t4_8 * t2_3 + t4_9 * t2_2; + double t6_12 = t4_4 * t2_8 + t4_5 * t2_7 + t4_6 * t2_6 + t4_7 * t2_5 + t4_8 * t2_4 + t4_9 * t2_3 + t4_10 * t2_2; + double t7_8 = t6_6 * t1_2 + t6_7 * t1_1; + double t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1; + double t7_12 = t6_8 * t1_4 + t6_9 * t1_3 + t6_10 * t1_2 + t6_11 * t1_1; + double t8_8 = t6_6 * t2_2; + double t8_9 = t6_6 * t2_3 + t6_7 * t2_2; + double t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2; + double t8_11 = t6_6 * t2_5 + t6_7 * t2_4 + t6_8 * t2_3 + t6_9 * t2_2; + double t8_12 = t6_6 * t2_6 + t6_7 * t2_5 + t6_8 * t2_4 + t6_9 * t2_3 + t6_10 * t2_2; + double t9_10 = t8_8 * t1_2 + t8_9 * t1_1; + double t9_12 = t8_8 * t1_4 + t8_9 * t1_3 + t8_10 * t1_2 + t8_11 * t1_1; + double t10_10 = t8_8 * t2_2; + double t10_11 = t8_8 * t2_3 + t8_9 * t2_2; + double t10_12 = t8_8 * t2_4 + t8_9 * t2_3 + t8_10 * t2_2; + double t11_12 = t10_10 * t1_2 + t10_11 * t1_1; + double t12_12 = t10_10 * t2_2; + u = 1; + v = 0; + v += (1./12) * t1_2 + (1./80) * t1_4; + u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8; + v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8 + (1./67584) * t3_10 + (1./319488) * t3_12; + u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8 + (1./270336) * t4_10 + (1./1.27795e+06) * t4_12; + v += (1./53760) * t5_6 + (1./276480) * t5_8 + (1./1.35168e+06) * t5_10 + (1./6.38976e+06) * t5_12; + u -= (1./322560) * t6_6 + (1./1.65888e+06) * t6_8 + (1./8.11008e+06) * t6_10 + (1./3.83386e+07) * t6_12; + v -= (1./1.16122e+07) * t7_8 + (1./5.67706e+07) * t7_10 + (1./2.6837e+08) * t7_12; + u += (1./9.28973e+07) * t8_8 + (1./4.54164e+08) * t8_10 + (1./2.14696e+09) * t8_12; + v += (1./4.08748e+09) * t9_10 + (1./1.93226e+10) * t9_12; + u -= (1./4.08748e+10) * t10_10 + (1./1.93226e+11) * t10_12; + v -= (1./2.12549e+12) * t11_12; + u += (1./2.55059e+13) * t12_12; +#endif + +#if ORDER == 16 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t2_7 = 2 * (t1_3 * t1_4); + double t2_8 = t1_4 * t1_4; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1; + double t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2; + double t3_12 = t2_8 * t1_4; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4); + double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4; + double t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5); + double t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5; + double t4_11 = 2 * (t2_3 * t2_8 + t2_4 * t2_7 + t2_5 * t2_6); + double t4_12 = 2 * (t2_4 * t2_8 + t2_5 * t2_7) + t2_6 * t2_6; + double t4_13 = 2 * (t2_5 * t2_8 + t2_6 * t2_7); + double t4_14 = 2 * (t2_6 * t2_8) + t2_7 * t2_7; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1; + double t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1; + double t5_12 = t4_8 * t1_4 + t4_9 * t1_3 + t4_10 * t1_2 + t4_11 * t1_1; + double t5_14 = t4_10 * t1_4 + t4_11 * t1_3 + t4_12 * t1_2 + t4_13 * t1_1; + double t6_6 = t4_4 * t2_2; + double t6_7 = t4_4 * t2_3 + t4_5 * t2_2; + double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2; + double t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2; + double t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2; + double t6_11 = t4_4 * t2_7 + t4_5 * t2_6 + t4_6 * t2_5 + t4_7 * t2_4 + t4_8 * t2_3 + t4_9 * t2_2; + double t6_12 = t4_4 * t2_8 + t4_5 * t2_7 + t4_6 * t2_6 + t4_7 * t2_5 + t4_8 * t2_4 + t4_9 * t2_3 + t4_10 * t2_2; + double t6_13 = t4_5 * t2_8 + t4_6 * t2_7 + t4_7 * t2_6 + t4_8 * t2_5 + t4_9 * t2_4 + t4_10 * t2_3 + t4_11 * t2_2; + double t6_14 = t4_6 * t2_8 + t4_7 * t2_7 + t4_8 * t2_6 + t4_9 * t2_5 + t4_10 * t2_4 + t4_11 * t2_3 + t4_12 * t2_2; + double t7_8 = t6_6 * t1_2 + t6_7 * t1_1; + double t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1; + double t7_12 = t6_8 * t1_4 + t6_9 * t1_3 + t6_10 * t1_2 + t6_11 * t1_1; + double t7_14 = t6_10 * t1_4 + t6_11 * t1_3 + t6_12 * t1_2 + t6_13 * t1_1; + double t8_8 = t6_6 * t2_2; + double t8_9 = t6_6 * t2_3 + t6_7 * t2_2; + double t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2; + double t8_11 = t6_6 * t2_5 + t6_7 * t2_4 + t6_8 * t2_3 + t6_9 * t2_2; + double t8_12 = t6_6 * t2_6 + t6_7 * t2_5 + t6_8 * t2_4 + t6_9 * t2_3 + t6_10 * t2_2; + double t8_13 = t6_6 * t2_7 + t6_7 * t2_6 + t6_8 * t2_5 + t6_9 * t2_4 + t6_10 * t2_3 + t6_11 * t2_2; + double t8_14 = t6_6 * t2_8 + t6_7 * t2_7 + t6_8 * t2_6 + t6_9 * t2_5 + t6_10 * t2_4 + t6_11 * t2_3 + t6_12 * t2_2; + double t9_10 = t8_8 * t1_2 + t8_9 * t1_1; + double t9_12 = t8_8 * t1_4 + t8_9 * t1_3 + t8_10 * t1_2 + t8_11 * t1_1; + double t9_14 = t8_10 * t1_4 + t8_11 * t1_3 + t8_12 * t1_2 + t8_13 * t1_1; + double t10_10 = t8_8 * t2_2; + double t10_11 = t8_8 * t2_3 + t8_9 * t2_2; + double t10_12 = t8_8 * t2_4 + t8_9 * t2_3 + t8_10 * t2_2; + double t10_13 = t8_8 * t2_5 + t8_9 * t2_4 + t8_10 * t2_3 + t8_11 * t2_2; + double t10_14 = t8_8 * t2_6 + t8_9 * t2_5 + t8_10 * t2_4 + t8_11 * t2_3 + t8_12 * t2_2; + double t11_12 = t10_10 * t1_2 + t10_11 * t1_1; + double t11_14 = t10_10 * t1_4 + t10_11 * t1_3 + t10_12 * t1_2 + t10_13 * t1_1; + double t12_12 = t10_10 * t2_2; + double t12_13 = t10_10 * t2_3 + t10_11 * t2_2; + double t12_14 = t10_10 * t2_4 + t10_11 * t2_3 + t10_12 * t2_2; + double t13_14 = t12_12 * t1_2 + t12_13 * t1_1; + double t14_14 = t12_12 * t2_2; + u = 1; + u -= 1./24 * t2_2 + 1./160 * t2_4 + 1./896 * t2_6 + 1./4608 * t2_8; + u += 1./1920 * t4_4 + 1./10752 * t4_6 + 1./55296 * t4_8 + 1./270336 * t4_10 + 1./1277952 * t4_12 + 1./5898240 * t4_14; + u -= 1./322560 * t6_6 + 1./1658880 * t6_8 + 1./8110080 * t6_10 + 1./38338560 * t6_12 + 1./176947200 * t6_14; + u += 1./92897280 * t8_8 + 1./454164480 * t8_10 + 4.6577500191e-10 * t8_12 + 1.0091791708e-10 * t8_14; + u -= 2.4464949595e-11 * t10_10 + 5.1752777990e-12 * t10_12 + 1.1213101898e-12 * t10_14; + u += 3.9206649992e-14 * t12_12 + 8.4947741650e-15 * t12_14; + u -= 4.6674583324e-17 * t14_14; + v = 0; + v += 1./12 * t1_2 + 1./80 * t1_4; + v -= 1./480 * t3_4 + 1./2688 * t3_6 + 1./13824 * t3_8 + 1./67584 * t3_10 + 1./319488 * t3_12; + v += 1./53760 * t5_6 + 1./276480 * t5_8 + 1./1351680 * t5_10 + 1./6389760 * t5_12 + 1./29491200 * t5_14; + v -= 1./11612160 * t7_8 + 1./56770560 * t7_10 + 1./268369920 * t7_12 + 8.0734333664e-10 * t7_14; + v += 2.4464949595e-10 * t9_10 + 5.1752777990e-11 * t9_12 + 1.1213101898e-11 * t9_14; + v -= 4.7047979991e-13 * t11_12 + 1.0193728998e-13 * t11_14; + v += 6.5344416654e-16 * t13_14; +#endif + + } + + if (n == 1) { +#if ORDER == 2 + x = 1; + y = 0; +#else + x = u; + y = v; +#endif + } else { + double th = (((th4 * s + th3) * s + th2) * s + th1) * s; + double cth = cos(th); + double sth = sin(th); + +#if ORDER == 2 + x += cth; + y += sth; +#else + x += cth * u - sth * v; + y += cth * v + sth * u; +#endif + s += ds; + } + } + +#if ORDER == 4 || ORDER == 6 + xy[0] = x * (1./24 * ds); + xy[1] = y * (1./24 * ds); +#else + xy[0] = x * ds; + xy[1] = y * ds; +#endif +} + +static double +compute_ends(const double ks[4], double ends[2][4], double seg_ch) +{ + double xy[2]; + double ch, th; + double l, l2, l3; + double th_even, th_odd; + double k0_even, k0_odd; + double k1_even, k1_odd; + double k2_even, k2_odd; + + integrate_spiro(ks, xy); + ch = hypot(xy[0], xy[1]); + th = atan2(xy[1], xy[0]); + l = ch / seg_ch; + + th_even = .5 * ks[0] + (1./48) * ks[2]; + th_odd = .125 * ks[1] + (1./384) * ks[3] - th; + ends[0][0] = th_even - th_odd; + ends[1][0] = th_even + th_odd; + k0_even = l * (ks[0] + .125 * ks[2]); + k0_odd = l * (.5 * ks[1] + (1./48) * ks[3]); + ends[0][1] = k0_even - k0_odd; + ends[1][1] = k0_even + k0_odd; + l2 = l * l; + k1_even = l2 * (ks[1] + .125 * ks[3]); + k1_odd = l2 * .5 * ks[2]; + ends[0][2] = k1_even - k1_odd; + ends[1][2] = k1_even + k1_odd; + l3 = l2 * l; + k2_even = l3 * ks[2]; + k2_odd = l3 * .5 * ks[3]; + ends[0][3] = k2_even - k2_odd; + ends[1][3] = k2_even + k2_odd; + + return l; +} + +static void +compute_pderivs(const spiro_seg *s, double ends[2][4], double derivs[4][2][4], + int jinc) +{ + double recip_d = 2e6; + double delta = 1./ recip_d; + double try_ks[4]; + double try_ends[2][4]; + int i, j, k; + + compute_ends(s->ks, ends, s->seg_ch); + for (i = 0; i < jinc; i++) { + for (j = 0; j < 4; j++) + try_ks[j] = s->ks[j]; + try_ks[i] += delta; + compute_ends(try_ks, try_ends, s->seg_ch); + for (k = 0; k < 2; k++) + for (j = 0; j < 4; j++) + derivs[j][k][i] = recip_d * (try_ends[k][j] - ends[k][j]); + } +} + +static double +mod_2pi(double th) +{ + double u = th / (2 * M_PI); + return 2 * M_PI * (u - floor(u + 0.5)); +} + +static spiro_seg * +setup_path(const spiro_cp *src, int n) +{ + int n_seg = src[0].ty == '{' ? n - 1 : n; + spiro_seg *r = (spiro_seg *)malloc((n_seg + 1) * sizeof(spiro_seg)); + int i; + int ilast; + + for (i = 0; i < n_seg; i++) { + r[i].x = src[i].x; + r[i].y = src[i].y; + r[i].ty = src[i].ty; + r[i].ks[0] = 0.; + r[i].ks[1] = 0.; + r[i].ks[2] = 0.; + r[i].ks[3] = 0.; + } + r[n_seg].x = src[n_seg % n].x; + r[n_seg].y = src[n_seg % n].y; + r[n_seg].ty = src[n_seg % n].ty; + + for (i = 0; i < n_seg; i++) { + double dx = r[i + 1].x - r[i].x; + double dy = r[i + 1].y - r[i].y; + r[i].seg_ch = hypot(dx, dy); + r[i].seg_th = atan2(dy, dx); + } + + ilast = n_seg - 1; + for (i = 0; i < n_seg; i++) { + if (r[i].ty == '{' || r[i].ty == '}' || r[i].ty == 'v') + r[i].bend_th = 0.; + else + r[i].bend_th = mod_2pi(r[i].seg_th - r[ilast].seg_th); + ilast = i; + } + return r; +} + +static void +bandec11(bandmat *m, int *perm, int n) +{ + int i, j, k; + int l; + + /* pack top triangle to the left. */ + for (i = 0; i < 5; i++) { + for (j = 0; j < i + 6; j++) + m[i].a[j] = m[i].a[j + 5 - i]; + for (; j < 11; j++) + m[i].a[j] = 0.; + } + l = 5; + for (k = 0; k < n; k++) { + int pivot = k; + double pivot_val = m[k].a[0]; + double pivot_scale; + + if (l < n) l++; + + for (j = k + 1; j < l; j++) + if (fabs(m[j].a[0]) > fabs(pivot_val)) { + pivot_val = m[j].a[0]; + pivot = j; + } + + perm[k] = pivot; + if (pivot != k) { + for (j = 0; j < 11; j++) { + double tmp = m[k].a[j]; + m[k].a[j] = m[pivot].a[j]; + m[pivot].a[j] = tmp; + } + } + + if (fabs(pivot_val) < 1e-12) pivot_val = 1e-12; + pivot_scale = 1. / pivot_val; + for (i = k + 1; i < l; i++) { + double x = m[i].a[0] * pivot_scale; + m[k].al[i - k - 1] = x; + for (j = 1; j < 11; j++) + m[i].a[j - 1] = m[i].a[j] - x * m[k].a[j]; + m[i].a[10] = 0.; + } + } +} + +static void +banbks11(const bandmat *m, const int *perm, double *v, int n) +{ + int i, k, l; + + /* forward substitution */ + l = 5; + for (k = 0; k < n; k++) { + i = perm[k]; + if (i != k) { + double tmp = v[k]; + v[k] = v[i]; + v[i] = tmp; + } + if (l < n) l++; + for (i = k + 1; i < l; i++) + v[i] -= m[k].al[i - k - 1] * v[k]; + } + + /* back substitution */ + l = 1; + for (i = n - 1; i >= 0; i--) { + double x = v[i]; + for (k = 1; k < l; k++) + x -= m[i].a[k] * v[k + i]; + v[i] = x / m[i].a[0]; + if (l < 11) l++; + } +} + +int compute_jinc(char ty0, char ty1) +{ + if (ty0 == 'o' || ty1 == 'o' || + ty0 == ']' || ty1 == '[') + return 4; + else if (ty0 == 'c' && ty1 == 'c') + return 2; + else if (((ty0 == '{' || ty0 == 'v' || ty0 == '[') && ty1 == 'c') || + (ty0 == 'c' && (ty1 == '}' || ty1 == 'v' || ty1 == ']'))) + return 1; + else + return 0; +} + +int count_vec(const spiro_seg *s, int nseg) +{ + int i; + int n = 0; + + for (i = 0; i < nseg; i++) + n += compute_jinc(s[i].ty, s[i + 1].ty); + return n; +} + +static void +add_mat_line(bandmat *m, double *v, + double derivs[4], double x, double y, int j, int jj, int jinc, + int nmat) +{ + int k; + + if (jj >= 0) { + int joff = (j + 5 - jj + nmat) % nmat; + v[jj] += x; + for (k = 0; k < jinc; k++) + m[jj].a[joff + k] += y * derivs[k]; + } +} + +static double +spiro_iter(spiro_seg *s, bandmat *m, int *perm, double *v, int n) +{ + int cyclic = s[0].ty != '{' && s[0].ty != 'v'; + int i, j, jj; + int nmat = count_vec(s, n); + double norm; + int n_invert; + + for (i = 0; i < nmat; i++) { + v[i] = 0.; + for (j = 0; j < 11; j++) + m[i].a[j] = 0.; + for (j = 0; j < 5; j++) + m[i].al[j] = 0.; + } + + j = 0; + if (s[0].ty == 'o') + jj = nmat - 2; + else if (s[0].ty == 'c' || s[0].ty == '[' || s[0].ty == ']') + jj = nmat - 1; + else + jj = 0; + for (i = 0; i < n; i++) { + char ty0 = s[i].ty; + char ty1 = s[i + 1].ty; + int jinc = compute_jinc(ty0, ty1); + double th = s[i].bend_th; + double ends[2][4]; + double derivs[4][2][4]; + int jthl = -1, jk0l = -1, jk1l = -1, jk2l = -1; + int jthr = -1, jk0r = -1, jk1r = -1, jk2r = -1; + + compute_pderivs(&s[i], ends, derivs, jinc); + + /* constraints crossing left */ + if (ty0 == 'o' || ty0 == 'c' || ty0 == '[' || ty0 == ']') { + jthl = jj++; + jj %= nmat; + jk0l = jj++; + } + if (ty0 == 'o') { + jj %= nmat; + jk1l = jj++; + jk2l = jj++; + } + + /* constraints on left */ + if ((ty0 == '[' || ty0 == 'v' || ty0 == '{' || ty0 == 'c') && + jinc == 4) { + if (ty0 != 'c') + jk1l = jj++; + jk2l = jj++; + } + + /* constraints on right */ + if ((ty1 == ']' || ty1 == 'v' || ty1 == '}' || ty1 == 'c') && + jinc == 4) { + if (ty1 != 'c') + jk1r = jj++; + jk2r = jj++; + } + + /* constraints crossing right */ + if (ty1 == 'o' || ty1 == 'c' || ty1 == '[' || ty1 == ']') { + jthr = jj; + jk0r = (jj + 1) % nmat; + } + if (ty1 == 'o') { + jk1r = (jj + 2) % nmat; + jk2r = (jj + 3) % nmat; + } + + add_mat_line(m, v, derivs[0][0], th - ends[0][0], 1, j, jthl, jinc, nmat); + add_mat_line(m, v, derivs[1][0], ends[0][1], -1, j, jk0l, jinc, nmat); + add_mat_line(m, v, derivs[2][0], ends[0][2], -1, j, jk1l, jinc, nmat); + add_mat_line(m, v, derivs[3][0], ends[0][3], -1, j, jk2l, jinc, nmat); + add_mat_line(m, v, derivs[0][1], -ends[1][0], 1, j, jthr, jinc, nmat); + add_mat_line(m, v, derivs[1][1], -ends[1][1], 1, j, jk0r, jinc, nmat); + add_mat_line(m, v, derivs[2][1], -ends[1][2], 1, j, jk1r, jinc, nmat); + add_mat_line(m, v, derivs[3][1], -ends[1][3], 1, j, jk2r, jinc, nmat); + j += jinc; + } + if (cyclic) { + memcpy(m + nmat, m, sizeof(bandmat) * nmat); + memcpy(m + 2 * nmat, m, sizeof(bandmat) * nmat); + memcpy(v + nmat, v, sizeof(double) * nmat); + memcpy(v + 2 * nmat, v, sizeof(double) * nmat); + n_invert = 3 * nmat; + j = nmat; + } else { + n_invert = nmat; + j = 0; + } + bandec11(m, perm, n_invert); + banbks11(m, perm, v, n_invert); + norm = 0.; + for (i = 0; i < n; i++) { + char ty0 = s[i].ty; + char ty1 = s[i + 1].ty; + int jinc = compute_jinc(ty0, ty1); + int k; + + for (k = 0; k < jinc; k++) { + double dk = v[j++]; + + s[i].ks[k] += dk; + norm += dk * dk; + } + } + return norm; +} + +int +solve_spiro(spiro_seg *s, int nseg) +{ + bandmat *m; + double *v; + int *perm; + int nmat = count_vec(s, nseg); + int n_alloc = nmat; + double norm; + int i; + + if (nmat == 0) + return 0; + if (s[0].ty != '{' && s[0].ty != 'v') + n_alloc *= 3; + if (n_alloc < 5) + n_alloc = 5; + m = (bandmat *)malloc(sizeof(bandmat) * n_alloc); + v = (double *)malloc(sizeof(double) * n_alloc); + perm = (int *)malloc(sizeof(int) * n_alloc); + + for (i = 0; i < 10; i++) { + norm = spiro_iter(s, m, perm, v, nseg); +#ifdef VERBOSE + printf("%% norm = %g\n", norm); +#endif + if (norm < 1e-12) break; + } + + free(m); + free(v); + free(perm); + return 0; +} + +static void +spiro_seg_to_bpath(const double ks[4], + double x0, double y0, double x1, double y1, + bezctx *bc, int depth) +{ + double bend = fabs(ks[0]) + fabs(.5 * ks[1]) + fabs(.125 * ks[2]) + + fabs((1./48) * ks[3]); + + if (!bend > 1e-8) { + bezctx_lineto(bc, x1, y1); + } else { + double seg_ch = hypot(x1 - x0, y1 - y0); + double seg_th = atan2(y1 - y0, x1 - x0); + double xy[2]; + double ch, th; + double scale, rot; + double th_even, th_odd; + double ul, vl; + double ur, vr; + + integrate_spiro(ks, xy); + ch = hypot(xy[0], xy[1]); + th = atan2(xy[1], xy[0]); + scale = seg_ch / ch; + rot = seg_th - th; + if (depth > 5 || bend < 1.) { + th_even = (1./384) * ks[3] + (1./8) * ks[1] + rot; + th_odd = (1./48) * ks[2] + .5 * ks[0]; + ul = (scale * (1./3)) * cos(th_even - th_odd); + vl = (scale * (1./3)) * sin(th_even - th_odd); + ur = (scale * (1./3)) * cos(th_even + th_odd); + vr = (scale * (1./3)) * sin(th_even + th_odd); + bezctx_curveto(bc, x0 + ul, y0 + vl, x1 - ur, y1 - vr, x1, y1); + } else { + /* subdivide */ + double ksub[4]; + double thsub; + double xysub[2]; + double xmid, ymid; + double cth, sth; + + ksub[0] = .5 * ks[0] - .125 * ks[1] + (1./64) * ks[2] - (1./768) * ks[3]; + ksub[1] = .25 * ks[1] - (1./16) * ks[2] + (1./128) * ks[3]; + ksub[2] = .125 * ks[2] - (1./32) * ks[3]; + ksub[3] = (1./16) * ks[3]; + thsub = rot - .25 * ks[0] + (1./32) * ks[1] - (1./384) * ks[2] + (1./6144) * ks[3]; + cth = .5 * scale * cos(thsub); + sth = .5 * scale * sin(thsub); + integrate_spiro(ksub, xysub); + xmid = x0 + cth * xysub[0] - sth * xysub[1]; + ymid = y0 + cth * xysub[1] + sth * xysub[0]; + spiro_seg_to_bpath(ksub, x0, y0, xmid, ymid, bc, depth + 1); + ksub[0] += .25 * ks[1] + (1./384) * ks[3]; + ksub[1] += .125 * ks[2]; + ksub[2] += (1./16) * ks[3]; + spiro_seg_to_bpath(ksub, xmid, ymid, x1, y1, bc, depth + 1); + } + } +} + +spiro_seg * +run_spiro(const spiro_cp *src, int n) +{ + int nseg = src[0].ty == '{' ? n - 1 : n; + spiro_seg *s = setup_path(src, n); + if (nseg > 1) + solve_spiro(s, nseg); + return s; +} + +void +free_spiro(spiro_seg *s) +{ + free(s); +} + +void +spiro_to_bpath(const spiro_seg *s, int n, bezctx *bc) +{ + int i; + int nsegs = s[n - 1].ty == '}' ? n - 1 : n; + + for (i = 0; i < nsegs; i++) { + double x0 = s[i].x; + double y0 = s[i].y; + double x1 = s[i + 1].x; + double y1 = s[i + 1].y; + + if (i == 0) + bezctx_moveto(bc, x0, y0, s[0].ty == '{'); + bezctx_mark_knot(bc, i); + spiro_seg_to_bpath(s[i].ks, x0, y0, x1, y1, bc, 0); + } +} + +double +get_knot_th(const spiro_seg *s, int i) +{ + double ends[2][4]; + + if (i == 0) { + compute_ends(s[i].ks, ends, s[i].seg_ch); + return s[i].seg_th - ends[0][0]; + } else { + compute_ends(s[i - 1].ks, ends, s[i - 1].seg_ch); + return s[i - 1].seg_th + ends[1][0]; + } +} + +#ifdef UNIT_TEST +#include +#include /* for gettimeofday */ + +static double +get_time (void) +{ + struct timeval tv; + struct timezone tz; + + gettimeofday (&tv, &tz); + + return tv.tv_sec + 1e-6 * tv.tv_usec; +} + +int +test_integ(void) { + double ks[] = {1, 2, 3, 4}; + double xy[2]; + double xynom[2]; + double ch, th; + int i, j; + int nsubdiv; + + n = ORDER < 6 ? 4096 : 1024; + integrate_spiro(ks, xynom); + nsubdiv = ORDER < 12 ? 8 : 7; + for (i = 0; i < nsubdiv; i++) { + double st, en; + double err; + int n_iter = (1 << (20 - i)); + + n = 1 << i; + st = get_time(); + for (j = 0; j < n_iter; j++) + integrate_spiro(ks, xy); + en = get_time(); + err = hypot(xy[0] - xynom[0], xy[1] - xynom[1]); + printf("%d %d %g %g\n", ORDER, n, (en - st) / n_iter, err); + ch = hypot(xy[0], xy[1]); + th = atan2(xy[1], xy[0]); +#if 0 + printf("n = %d: integ(%g %g %g %g) = %g %g, ch = %g, th = %g\n", n, + ks[0], ks[1], ks[2], ks[3], xy[0], xy[1], ch, th); + printf("%d: %g %g\n", n, xy[0] - xynom[0], xy[1] - xynom[1]); +#endif + } + return 0; +} + +void +print_seg(const double ks[4], double x0, double y0, double x1, double y1) +{ + double bend = fabs(ks[0]) + fabs(.5 * ks[1]) + fabs(.125 * ks[2]) + + fabs((1./48) * ks[3]); + + if (bend < 1e-8) { + printf("%g %g lineto\n", x1, y1); + } else { + double seg_ch = hypot(x1 - x0, y1 - y0); + double seg_th = atan2(y1 - y0, x1 - x0); + double xy[2]; + double ch, th; + double scale, rot; + double th_even, th_odd; + double ul, vl; + double ur, vr; + + integrate_spiro(ks, xy); + ch = hypot(xy[0], xy[1]); + th = atan2(xy[1], xy[0]); + scale = seg_ch / ch; + rot = seg_th - th; + if (bend < 1.) { + th_even = (1./384) * ks[3] + (1./8) * ks[1] + rot; + th_odd = (1./48) * ks[2] + .5 * ks[0]; + ul = (scale * (1./3)) * cos(th_even - th_odd); + vl = (scale * (1./3)) * sin(th_even - th_odd); + ur = (scale * (1./3)) * cos(th_even + th_odd); + vr = (scale * (1./3)) * sin(th_even + th_odd); + printf("%g %g %g %g %g %g curveto\n", + x0 + ul, y0 + vl, x1 - ur, y1 - vr, x1, y1); + + } else { + /* subdivide */ + double ksub[4]; + double thsub; + double xysub[2]; + double xmid, ymid; + double cth, sth; + + ksub[0] = .5 * ks[0] - .125 * ks[1] + (1./64) * ks[2] - (1./768) * ks[3]; + ksub[1] = .25 * ks[1] - (1./16) * ks[2] + (1./128) * ks[3]; + ksub[2] = .125 * ks[2] - (1./32) * ks[3]; + ksub[3] = (1./16) * ks[3]; + thsub = rot - .25 * ks[0] + (1./32) * ks[1] - (1./384) * ks[2] + (1./6144) * ks[3]; + cth = .5 * scale * cos(thsub); + sth = .5 * scale * sin(thsub); + integrate_spiro(ksub, xysub); + xmid = x0 + cth * xysub[0] - sth * xysub[1]; + ymid = y0 + cth * xysub[1] + sth * xysub[0]; + print_seg(ksub, x0, y0, xmid, ymid); + ksub[0] += .25 * ks[1] + (1./384) * ks[3]; + ksub[1] += .125 * ks[2]; + ksub[2] += (1./16) * ks[3]; + print_seg(ksub, xmid, ymid, x1, y1); + } + } +} + +void +print_segs(const spiro_seg *segs, int nsegs) +{ + int i; + + for (i = 0; i < nsegs; i++) { + double x0 = segs[i].x; + double y0 = segs[i].y; + double x1 = segs[i + 1].x; + double y1 = segs[i + 1].y; + + if (i == 0) + printf("%g %g moveto\n", x0, y0); + printf("%% ks = [ %g %g %g %g ]\n", + segs[i].ks[0], segs[i].ks[1], segs[i].ks[2], segs[i].ks[3]); + print_seg(segs[i].ks, x0, y0, x1, y1); + } + printf("stroke\n"); +} + +int +test_curve(void) +{ + spiro_cp path[] = { + {334, 117, 'v'}, + {305, 176, 'v'}, + {212, 142, 'c'}, + {159, 171, 'c'}, + {224, 237, 'c'}, + {347, 335, 'c'}, + {202, 467, 'c'}, + {81, 429, 'v'}, + {114, 368, 'v'}, + {201, 402, 'c'}, + {276, 369, 'c'}, + {218, 308, 'c'}, + {91, 211, 'c'}, + {124, 111, 'c'}, + {229, 82, 'c'} + }; + spiro_seg *segs; + int i; + + n = 1; + for (i = 0; i < 1000; i++) { + segs = setup_path(path, 15); + solve_spiro(segs, 15); + } + printf("100 800 translate 1 -1 scale 1 setlinewidth\n"); + print_segs(segs, 15); + printf("showpage\n"); + return 0; +} + +int main(int argc, char **argv) +{ + return test_curve(); +} +#endif diff --git a/third_party/spiro/ppedit/spiro.h b/third_party/spiro/ppedit/spiro.h new file mode 100644 index 0000000..54de404 --- /dev/null +++ b/third_party/spiro/ppedit/spiro.h @@ -0,0 +1,18 @@ +typedef struct { + double x; + double y; + char ty; +} spiro_cp; + +typedef struct spiro_seg_s spiro_seg; + +spiro_seg * +run_spiro(const spiro_cp *src, int n); + +void +free_spiro(spiro_seg *s); + +void +spiro_to_bpath(const spiro_seg *s, int n, bezctx *bc); + +double get_knot_th(const spiro_seg *s, int i); diff --git a/third_party/spiro/ppedit/zmisc.h b/third_party/spiro/ppedit/zmisc.h new file mode 100644 index 0000000..ce3d4d3 --- /dev/null +++ b/third_party/spiro/ppedit/zmisc.h @@ -0,0 +1,12 @@ +/** + * Misc portability and convenience macros. + **/ + +#include + +#define zalloc malloc +#define zrealloc realloc +#define zfree free + +#define znew(type, n) (type *)zalloc(sizeof(type) * (n)) +#define zrenew(type, p, n) (type *)zrealloc((p), sizeof(type) * (n)) diff --git a/third_party/spiro/x3/Makefile b/third_party/spiro/x3/Makefile new file mode 100644 index 0000000..392417b --- /dev/null +++ b/third_party/spiro/x3/Makefile @@ -0,0 +1,25 @@ +TARGET = gtk + +ifeq ($(TARGET),gtk) + X3_PLAT = X3_GTK + X3_INCL = `pkg-config --cflags gtk+-2.0` + X3_LIBS = `pkg-config --libs gtk+-2.0` +endif + +ifeq ($(TARGET),carbon) + X3_PLAT = X3_CARBON + X3_LIBS = -framework Carbon +endif + +ifeq ($(TARGET),win32) + X3_PLAT = X3_WIN32 + X3_LIBS = -lgdi32 +endif + +CFLAGS = -g -Wall -D$(X3_PLAT) $(X3_INCL) +LDFLAGS = -g +LDLIBS = $(X3_LIBS) + +test: test.o x3$(TARGET).o x3common.o + +x3lisp.o: x3lisp.c diff --git a/third_party/spiro/x3/pyrex/Makefile b/third_party/spiro/x3/pyrex/Makefile new file mode 100644 index 0000000..d0324f3 --- /dev/null +++ b/third_party/spiro/x3/pyrex/Makefile @@ -0,0 +1,39 @@ +TARGET = gtk + +ifeq ($(TARGET),gtk) + X3_PLAT = X3_GTK + X3_INCL = `pkg-config --cflags gtk+-2.0` + X3_LIBS = `pkg-config --libs gtk+-2.0` + SHARED_FLAG = -shared +endif + +ifeq ($(TARGET),carbon) + X3_PLAT = X3_CARBON + X3_LIBS = -framework Carbon + SHARED_FLAG = -dynamiclib -flat_namespace -undefined suppress +endif + +ifeq ($(TARGET),win32) + X3_PLAT = X3_WIN32 + X3_LIBS = -lgdi32 +endif + +PY_INCL := -I$(shell python -c "import distutils.sysconfig; print distutils.sysconfig.get_python_inc()") + +CFLAGS = -g -Wall -fPIC -I.. $(PY_INCL) -D$(X3_PLAT) $(X3_INCL) +LDFLAGS = -g +LDLIBS = $(X3_LIBS) + +x3.so: x3.o x3$(TARGET).o x3common.o + gcc $(SHARED_FLAG) $^ $(X3_LIBS) -o $@ + +x3.c: x3.pyx + python2.4-pyrexc $< + +x3$(TARGET).o: ../x3$(TARGET).c + $(CC) -c $(CFLAGS) -o $@ $< + +x3common.o: ../x3common.c + $(CC) -c $(CFLAGS) -o $@ $< + + diff --git a/third_party/spiro/x3/pyrex/beztest.py b/third_party/spiro/x3/pyrex/beztest.py new file mode 100644 index 0000000..effd0d5 --- /dev/null +++ b/third_party/spiro/x3/pyrex/beztest.py @@ -0,0 +1,44 @@ +import x3 +from math import * + +def my_callback(cmd, what, arg, more): + print cmd, what, arg + +class bez: + def __init__(self): + self.coords = [(10, 10), (200, 10), (300, 200), (400, 100)] + self.hit = None + def draw(self, dc): + coords = self.coords + dc.setrgba(0, 0, 0.5, 1) + dc.moveto(coords[0][0], coords[0][1]) + dc.curveto(coords[1][0], coords[1][1], coords[2][0], coords[2][1], + coords[3][0], coords[3][1]) + dc.stroke() + dc.setrgba(0, 0.5, 0, 0.5) + dc.moveto(coords[0][0], coords[0][1]) + dc.lineto(coords[1][0], coords[1][1]) + dc.stroke() + dc.moveto(coords[2][0], coords[2][1]) + dc.lineto(coords[3][0], coords[3][1]) + dc.stroke() + dc.setrgba(1, 0, 0, .5) + for x, y in coords: + dc.rectangle(x - 3, y - 3, 6, 6) + dc.fill() + def mouse(self, button, mods, x, y): + if button == 1: + for i in range(4): + if hypot(x - self.coords[i][0], y - self.coords[i][1]) < 4: + self.hit = i + elif button == -1: + self.hit = None + elif self.hit != None: + self.coords[self.hit] = (x, y) + self.view.dirty() + +win = x3.window(0, "beztest", my_callback) + +x3.view(win, 259, bez()) + +x3.main() diff --git a/third_party/spiro/x3/pyrex/main.py b/third_party/spiro/x3/pyrex/main.py new file mode 100644 index 0000000..e784f46 --- /dev/null +++ b/third_party/spiro/x3/pyrex/main.py @@ -0,0 +1,40 @@ +import x3 + +def my_callback(cmd, what, arg, more): + print cmd, what, arg + +class my_viewclient: + def key(self, name, mods, code): + print name, mods, code + return 1 + def mouse(self, buttons, mods, x, y): + print buttons, mods, x, y + def draw(self, dc): + print 'rect:', dc.rect + dc.moveto(0, 0) + dc.lineto(100, 100) + print dc.currentpoint() + dc.stroke() + dc.selectfont("Nimbus Sans L", 0, 0) + dc.setfontsize(12) + dc.moveto(50, 10) + dc.showtext(u"\u00a1hello, world!") + print dc.textextents(u"\u00a1hello, world!") + +win = x3.window(0, "foo", my_callback) + +m = x3.menu(win, "bar") + +x3.menuitem(m, "baz", "bazz", "b") +x3.menusep(m) +x3.menuitem(m, "Quit", "quit", "q") + +v = x3.vbox(win, 0, 12) + +v.setpacking(True, False, 0) +x3.button(v, "butt", u"\u00a1hello!") +x3.edittext(v, "quux") +v.setpacking(True, True, 0) +x3.view(v, 263, my_viewclient()) + +x3.main() diff --git a/third_party/spiro/x3/pyrex/x3.pyx b/third_party/spiro/x3/pyrex/x3.pyx new file mode 100644 index 0000000..917df94 --- /dev/null +++ b/third_party/spiro/x3/pyrex/x3.pyx @@ -0,0 +1,259 @@ +cdef extern from "Python.h": + void *PyMem_Malloc(int size) except NULL + void Py_INCREF(object) + int PyUnicode_Check(object str) + object PyUnicode_AsUTF8String(object str) + +cdef extern from "x3.h": + ctypedef struct x3widget + ctypedef struct x3dc: + int x + int y + int width + int height + cdef enum x3windowflags: + x3window_main = 1 + x3window_dialog = 2 + ctypedef struct x3viewclient: + void (*destroy)(x3viewclient *self) + void (*mouse)(x3viewclient *self, int buttons, int mods, + double x, double y) + int (*key)(x3viewclient *self, char *keyname, int mods, int key) + void (*draw)(x3viewclient *self, x3dc *dc) + + # client extension + void *py_client + ctypedef struct x3extents: + double x_bearing + double y_bearing + double width + double height + double x_advance + double y_advance + void x3init(int argc, char **argv) + void x3main() + x3widget *x3window(x3windowflags flags, char *name, + int (callback)(x3widget *w, void *data, char *cmd, char *what, char *arg, void *more), + void *data) + x3widget *x3menu(x3widget *parent, char *name) + x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut) + x3widget *x3menusep(x3widget *parent) + x3widget *x3align(x3widget *parent, int alignment) + x3widget *x3pad(x3widget *parent, int t, int b, int l, int r) + x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing) + x3widget *x3hpane(x3widget *parent) + x3widget *x3vpane(x3widget *parent) + x3widget *x3button(x3widget *parent, char *name, char *label) + x3widget *x3label(x3widget *parent, char *text) + x3widget *x3edittext(x3widget *parent, char *cmd) + x3widget *x3view(x3widget *parent, int flags, x3viewclient *vc) + void x3viewclient_init(x3viewclient *vc) + void x3view_dirty(x3widget *w) + void x3view_scrollto(x3widget *w, int x, int y, int width, int height) + + void x3window_setdefaultsize(x3widget *w, int x, int y) + void x3setactive(x3widget *w, int active) + void x3setpacking(x3widget *w, int fill, int expand, int padding) + void x3pane_setsizing(x3widget *w, int c1r, int c1s, int c2r, int c2s) + int x3hasfocus(x3widget *w) + + void x3moveto(x3dc *dc, double x, double y) + void x3lineto(x3dc *dc, double x, double y) + void x3curveto(x3dc *dc, + double x1, double y1, + double x2, double y2, + double x3, double y3) + void x3closepath(x3dc *dc) + void x3rectangle(x3dc *dc, double x, double y, double width, double height) + void x3getcurrentpoint(x3dc *dc, double *px, double *py) + void x3setrgba(x3dc *dc, unsigned int rgba) + void x3setlinewidth(x3dc *dc, double w) + void x3fill(x3dc *dc) + void x3stroke(x3dc *dc) + void x3selectfont(x3dc *dc, char *fontname, int slant, int weight) + void x3setfontsize(x3dc *dc, double size) + void x3showtext(x3dc *dc, char *text) + void x3textextents(x3dc *dc, char *text, x3extents *extents) + X3_GMW(g, m, w) + +cdef object strmaybenull(char *str): + if str == NULL: + return None + else: + return str + +cdef object utf8(object ustr): + if PyUnicode_Check(ustr): + return PyUnicode_AsUTF8String(ustr) + else: + return ustr + +ctypedef struct x3viewclient_py: + x3viewclient base + # client extension + void *py_client + +cdef class widget: + cdef x3widget *w + def hasfocus(self): + return x3hasfocus(self.w) + +cdef int x3py_callback(x3widget *window, void *data, char *cmd, char *what, + char *arg, void *more): + callback = data + callback(cmd, strmaybenull(what), strmaybenull(arg), None) + +cdef class window(widget): + def __new__(self, int flags, char *name, callback): + self.w = x3window(flags, name, x3py_callback, callback) + def setdefaultsize(self, int width, int height): + x3window_setdefaultsize(self.w, width, height) + +cdef class menu(widget): + def __new__(self, widget parent, name): + uname = utf8(name) + self.w = x3menu(parent.w, uname) + +cdef class menuitem(widget): + def __new__(self, widget parent, name, char *cmd, char *shortcut = NULL): + uname = utf8(name) + self.w = x3menuitem(parent.w, uname, cmd, shortcut) + +cdef class menusep(widget): + def __new__(self, widget parent): + self.w = x3menusep(parent.w) + +cdef class align(widget): + def __new__(self, widget parent, int alignment): + self.w = x3align(parent.w, alignment) + +cdef class pad(widget): + def __new__(self, widget parent, int t, int b, int l, int r): + self.w = x3pad(parent.w, t, b, l, r) + +cdef class vbox(widget): + def __new__(self, widget parent, homogeneous, int spacing): + self.w = x3vbox(parent.w, not not homogeneous, spacing) + def setpacking(self, fill, expand, int padding): + x3setpacking(self.w, not not fill, not not expand, padding) + +cdef class hpane(widget): + def __new__(self, widget parent): + self.w = x3hpane(parent.w) + def setsizing(self, c1r, c1s, c2r, c2s): + x3pane_setsizing(self.w, not not c1r, not not c1s, not not c2r, not not c2s) + +cdef class vpane(widget): + def __new__(self, widget parent): + self.w = x3vpane(parent.w) + def setsizing(self, c1r, c1s, c2r, c2s): + x3pane_setsizing(self.w, not not c1r, not not c1s, not not c2r, not not c2s) + +cdef class button(widget): + def __new__(self, widget parent, char *name, label): + ulabel = utf8(label) + self.w = x3button(parent.w, name, ulabel) + +cdef class label(widget): + def __new__(self, widget parent, text): + ulabel = utf8(text) + self.w = x3label(parent.w, ulabel) + +cdef class edittext(widget): + def __new__(self, widget parent, char *cmd): + self.w = x3edittext(parent.w, cmd) + +cdef int x3py_key(x3viewclient *self, char *keyname, int mods, int key): + py_client = (self).py_client + return py_client.key(keyname, mods, key) + +cdef void x3py_mouse(x3viewclient *self, int button, int mods, double x, double y): + py_client = (self).py_client + py_client.mouse(button, mods, x, y) + +cdef int dbl_to_byte(double x): + if x <= 0: return 0 + if x >= 1: return 255 + return x * 255 + 0.5 + +cdef class x3dc_wrap: + cdef x3dc *dc + + property rect: + def __get__(self): + return (self.dc.x, self.dc.y, self.dc.width, self.dc.height) + + def moveto(self, double x, double y): + x3moveto(self.dc, x, y) + def lineto(self, double x, double y): + x3lineto(self.dc, x, y) + def curveto(self, double x1, double y1, double x2, double y2, double x3, double y3): + x3curveto(self.dc, x1, y1, x2, y2, x3, y3) + def closepath(self): + x3closepath(self.dc) + def rectangle(self, double x, double y, double width, double height): + x3rectangle(self.dc, x, y, width, height) + def currentpoint(self): + cdef double x + cdef double y + x3getcurrentpoint(self.dc, &x, &y) + return (x, y) + def setrgba(self, double r, double g, double b, double a): + x3setrgba(self.dc, (dbl_to_byte(r) << 24) | + (dbl_to_byte(g) << 16) | + (dbl_to_byte(b) << 8) | + dbl_to_byte(a)) + def setlinewidth(self, double w): + x3setlinewidth(self.dc, w) + def fill(self): + x3fill(self.dc) + def stroke(self): + x3stroke(self.dc) + def selectfont(self, char *name, int slant, int weight): + x3selectfont(self.dc, name, slant, weight) + def setfontsize(self, double size): + x3setfontsize(self.dc, size) + def showtext(self, text): + utext = utf8(text) + x3showtext(self.dc, utext) + def textextents(self, text): + cdef x3extents ext + utext = utf8(text) + x3textextents(self.dc, utext, &ext) + return (ext.x_bearing, ext.y_bearing, + ext.width, ext.height, + ext.x_advance, ext.y_advance) + + +cdef void x3py_draw(x3viewclient *self, x3dc *dc): + cdef x3dc_wrap dc_wrap + py_client = (self).py_client + dc_wrap = x3dc_wrap() + dc_wrap.dc = dc + py_client.draw(dc_wrap) + +cdef class view(widget): + def __new__(self, widget parent, int flags, viewclient): + cdef x3viewclient_py *vc + vc = PyMem_Malloc(sizeof(x3viewclient_py)) + x3viewclient_init(&vc.base) + vc.py_client = viewclient + Py_INCREF(viewclient) + vc.base.key = x3py_key + vc.base.mouse = x3py_mouse + vc.base.draw = x3py_draw + self.w = x3view(parent.w, flags, &vc.base) + viewclient.view = self + def dirty(self): + x3view_dirty(self.w) + def scrollto(self, int x, int y, int width, int height): + x3view_scrollto(self.w, x, y, width, height) + +def main(): + x3main() + +def gmw(g, m, w): + return X3_GMW(g, m, w) +platform = gmw("gtk", "mac", "win32") + +x3init(0, NULL) diff --git a/third_party/spiro/x3/test.c b/third_party/spiro/x3/test.c new file mode 100644 index 0000000..53fae33 --- /dev/null +++ b/third_party/spiro/x3/test.c @@ -0,0 +1,158 @@ +/* A test app for X3. */ + +#include "x3.h" +#include + +int +mycallback(x3widget *w, void *data, + char *cmd, char *what, char *arg, void *more) +{ + printf("my callback: cmd=\"%s\", what=\"%s\", arg=\"%s\"\n", + cmd, what ? what : "(null)", arg ? arg : "(null)"); + return 1; +} + +#if defined(X3_GTK) || defined(X3_CARBON) +static void test_viewclient_draw(x3viewclient *self, x3dc *dc) +{ + if (dc->buf) { + int i, j; + + for (i = 0; i < dc->height; i++) { + for (j = 0; j < dc->width; j++) { + dc->buf[i * dc->rowstride + j * 3] = i; + dc->buf[i * dc->rowstride + j * 3 + 1] = 0x80; + dc->buf[i * dc->rowstride + j * 3 + 2] = j; + } + } + } else { + x3extents ext; + + x3moveto(dc, 10, 10); + x3curveto(dc, 200, 10, 100, 100, 490, 100); + x3stroke(dc); + x3selectfont(dc, "Nimbus Roman No9 L", 0, 0); + x3moveto(dc, 10, 50); + x3setfontsize(dc, 16); + x3showtext(dc, "hello, world!"); + x3textextents(dc, "hello, world!", &ext); + printf("text extents: %g %g %g %g %g %g\n", + ext.x_bearing, ext.y_bearing, + ext.width, ext.height, + ext.x_advance, ext.y_advance); + } +} + +static int test_viewclient_key(x3viewclient *self, + char *keyname, int mods, int key) +{ + printf("view key: %s %d %d\n", keyname, mods, key); + return 1; +} + +static void test_viewclient_mouse(x3viewclient *self, + int button, int mods, + double x, double y) +{ + printf("view button: %d %d %g %g\n", button, mods, x, y); +} + +x3viewclient *test_viewclient(void) +{ + x3viewclient *result = (x3viewclient *)malloc(sizeof(x3viewclient)); + x3viewclient_init(result); + result->draw = test_viewclient_draw; + result->key = test_viewclient_key; + result->mouse = test_viewclient_mouse; + + return result; +} + +#ifdef X3_USEMAIN +int +main(int argc, char **argv) +{ + x3widget *mainwin; + x3widget *pane; + x3widget *vbox; + x3widget *m; + x3viewflags viewflags = x3view_click | x3view_hover | x3view_key; + + x3init(&argc, &argv); + mainwin = x3window(x3window_main, "untitled", mycallback, NULL); +#if 1 + m = x3menu(mainwin, "F\xc2\xa1le"); + x3menuitem(m, "Save", "save", "s"); + x3setactive(x3menuitem(m, "Save As...", "sava", "S"), 0); + x3menusep(m); + x3menuitem(m, "Preferences...", "pref", ","); + + m = x3menu(mainwin, "Edit"); + + x3menuitem(m, "ctrl-delete", "cdel", "Delete"); + x3menuitem(m, "ctrl-f1", "cf1", "F1"); + if (0) { + int i, j; + + for (j = 0; j < 3; j++) { + char mname[16]; + mname[0] = '0' + j; + mname[1] = 0; + m = x3menu(mainwin, mname); + for (i = 0x20 + 0x20 * j; i < 0x40 + 0x20 * j; i++) { + char name[16]; + + name[0] = i; + name[1] = 0; + x3menuitem(m, name, name, name); + } + } + } +#endif + +#if 1 + pane = x3vpane(mainwin); + vbox = x3vbox(x3pad(pane, 10, 10, 10, 10), 0, 5); + + x3setpacking(vbox, FALSE, FALSE, 0); + x3button(x3align(vbox, x3center), "foo", "Click me!"); + x3button(vbox, "bar", "Click me too!"); + x3setactive(x3button(vbox, "bar", "But not me"), 0); + x3label(vbox, "I am a label."); + x3edittext(vbox, "etxt"); +#endif + + x3setpacking(vbox, TRUE, TRUE, 0); + viewflags |= x3view_2d; + viewflags |= x3view_scroll; + x3view(pane, viewflags, test_viewclient()); + +#if 0 + x3_window_show(mainwin); +#endif + + x3main(); + return 0; +} +#endif +#endif + +#ifdef X3_USEWINMAIN +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, + int iCmdShow) +{ + //printf("winmain\n"); + x3widget *mainwin; + x3widget *vbox; + + x3init_win32(hInstance); + + mainwin = x3window(x3window_main, "untitled", mycallback, NULL); + x3_window_show(mainwin); + vbox = x3vbox(mainwin, 0, 5); + x3button(vbox, "foo", "Click me!"); + x3button(vbox, "bar", "And me too!"); + x3main(); + return 0; +} +#endif diff --git a/third_party/spiro/x3/x3.h b/third_party/spiro/x3/x3.h new file mode 100644 index 0000000..a056583 --- /dev/null +++ b/third_party/spiro/x3/x3.h @@ -0,0 +1,293 @@ +/* X3 is a lightweight toolset for building cross-platform applications. */ + +typedef struct _x3widget x3widget; + +typedef struct _x3viewclient x3viewclient; + +typedef enum { + x3window_main = 1, + x3window_dialog = 2, +} x3windowflags; + +typedef enum { + x3center = 0, + x3left = 1, + x3right = 2, + x3hfill = 3, + x3top = 4, + x3bottom = 8, + x3vfill = 12, + + x3topleft = 5, + x3topright = 6, + x3bottomleft = 9, + x3bottomright = 10, +} x3alignment; + +typedef enum { + x3view_click = 1, + x3view_hover = 2, + x3view_key = 4, + x3view_2d = 0x100, + x3view_rgb = 0x200, + x3view_scroll = 0x10000 +} x3viewflags; + +typedef struct _x3dc x3dc; + +struct _x3viewclient { + void (*destroy)(x3viewclient *self); + void (*mouse)(x3viewclient *self, int buttons, int mods, + double x, double y); + int (*key)(x3viewclient *self, char *keyname, int mods, int key); + void (*draw)(x3viewclient *self, x3dc *dc); +}; + +/* Windows and Carbon both use left/top/right/bottom nomenclature, + at least for int rects, while gtk+ uses x/y/width/height. */ +typedef struct { + double x0; + double y0; + double x1; + double y1; +} x3rect; + +#ifdef X3_GTK + +#include + +#define X3_GMW(g, m, w) g + +struct _x3dc { + /* move to x3rect structure? */ + int x; + int y; + int width; + int height; + + cairo_t *cr; + + /* for rgb drawing */ + unsigned char *buf; + int rowstride; +}; + +struct _x3widget { + char *name; + x3widget *parent; + GtkWidget *widget; +}; + +typedef cairo_text_extents_t x3extents; + +#define X3_SHIFT_MASK GDK_SHIFT_MASK +#define X3_CONTROL_MASK GDK_CONTROL_MASK +#define X3_ALT_MASK GDK_MOD1_MASK +/* there is no X3_CMD_MASK in gtk */ + +#define X3_USEMAIN + +#endif + +#ifdef X3_CARBON + +#define X3_GMW(g, m, w) m + +#include + +struct _x3dc { + /* move to x3rect structure? */ + int x; + int y; + int width; + int height; + + CGContextRef ctx; + CGMutablePathRef path; + + /* for rgb drawing */ + char *buf; + int rowstride; +}; + +typedef struct _x3type x3type; + +struct _x3type { + void (*sizereq)(x3widget *w); + void (*sizealloc)(x3widget *w, x3rect *r); + void (*add)(x3widget *w, x3widget *child); +}; + +typedef enum { + x3carbonnone, + x3carbonhiview, + x3carbonwindow, + x3carbonmenu, + x3carbonmenuitem, +} x3carbonvar; + +typedef enum { + x3flag_needsizereq = 1, + x3flag_needsizealloc = 2 +} x3widgetflags; + +struct _x3widget { + const x3type *type; + x3widgetflags flags; + x3rect sizerequest; + char *name; + x3widget *parent; + x3carbonvar var; + union { + HIViewRef hiview; + WindowRef window; + MenuRef menu; + int menuitem; + } u; + int n_children; + x3widget **children; +}; + +typedef struct { + double x_bearing; + double y_bearing; + double width; + double height; + double x_advance; + double y_advance; +} x3extents; + +#define X3_SHIFT_MASK shiftKey +#define X3_CONTROL_MASK controlKey +#define X3_CMD_MASK cmdKey +#define X3_ALT_MASK optionKey + +/* todo: figure out how to plumb mouse event */ +#define X3_BUTTON1_MASK 0 +#define X3_2BUTTON_PRESS 0 +#define X3_3BUTTON_PRESS 0 + +#define X3_USEMAIN + +#endif + +#ifdef X3_WIN32 + +#include + +#define X3_GMW(g, m, w) w + +typedef struct _x3type x3type; + +struct _x3type { + void (*sizereq)(x3widget *w); + void (*sizealloc)(x3widget *w, x3rect *r); + void (*add)(x3widget *w, x3widget *child); +}; + +typedef enum { + x3winnone, + x3winhwnd, +} x3winvar; + +typedef enum { + x3flag_needsizereq = 1, + x3flag_needsizealloc = 2 +} x3widgetflags; + +typedef struct { + double x_bearing; + double y_bearing; + double width; + double height; + double x_advance; + double y_advance; +} x3extents; + +struct _x3widget { + const x3type *type; + char *name; + x3widget *parent; + x3widgetflags flags; + x3rect sizerequest; + x3winvar var; // should this be in the type? + union { + HWND hwnd; + } u; + int n_children; + x3widget **children; +}; + +#define X3_USEWINMAIN + +void x3init_win32(HINSTANCE hInstance); + +#endif + +typedef int (*x3window_callback)(x3widget *window, void *data, + char *cmd, char *what, char *arg, + void *more); + +/* Main loop */ +void x3init(int *pargc, char ***pargv); +void x3main(void); + +x3widget *x3window(x3windowflags flags, char *label, + x3window_callback callback, void *callback_data); +x3widget *x3menu(x3widget *parent, char *name); +x3widget *x3menuitem(x3widget *parent, char *name, char *cmd, char *shortcut); +x3widget *x3menusep(x3widget *parent); +x3widget *x3align(x3widget *parent, x3alignment alignment); +x3widget *x3pad(x3widget *parent, int t, int b, int l, int r); +x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing); +x3widget *x3hpane(x3widget *parent); +x3widget *x3vpane(x3widget *parent); +x3widget *x3button(x3widget *parent, char *name, char *label); +x3widget *x3label(x3widget *parent, char *label); +x3widget *x3edittext(x3widget *parent, char *cmd); +x3widget *x3view(x3widget *parent, x3viewflags flags, x3viewclient *vc); +void x3view_dirty(x3widget *w); +void x3view_scrollto(x3widget *w, int x, int y, int width, int height); + +void x3viewclient_init(x3viewclient *vc); + +void x3window_setdefaultsize(x3widget *w, int width, int height); + +void x3pane_setsizing(x3widget *w, int child1_resize, int child1_shrink, + int child2_resize, int child2_shrink); + +void x3setactive(x3widget *w, int active); +int x3hasfocus(x3widget *w); +void x3setpacking(x3widget *w, int fill, int expand, int padding); + +extern int x3n_winopen; + +/* 2d drawing functions */ + +void x3moveto(x3dc *dc, double x, double y); +void x3lineto(x3dc *dc, double x, double y); +void x3curveto(x3dc *dc, + double x1, double y1, + double x2, double y2, + double x3, double y3); +void x3closepath(x3dc *dc); +void x3rectangle(x3dc *dc, double x, double y, double width, double height); +void x3getcurrentpoint(x3dc *dc, double *px, double *py); +void x3setrgba(x3dc *dc, unsigned int rgba); +void x3setlinewidth(x3dc *dc, double w); +void x3fill(x3dc *dc); +void x3stroke(x3dc *dc); + +/* This is an overly simple text api, based on cairo's. It may be phased + out in favor of a more capable one. */ +void x3selectfont(x3dc *dc, char *fontname, int slant, int weight); +void x3setfontsize(x3dc *dc, double size); +void x3showtext(x3dc *dc, char *text); +void x3textextents(x3dc *dc, char *text, x3extents *extents); + +#if defined(X3_CARBON) || defined(X3_WIN32) +/* Internals for carbon/win32 */ +void x3widget_init(x3widget *w, const x3type *type); +void x3add_default(x3widget *parent, x3widget *child); +void x3add(x3widget *parent, x3widget *child); +#endif diff --git a/third_party/spiro/x3/x3carbon.c b/third_party/spiro/x3/x3carbon.c new file mode 100644 index 0000000..7929e47 --- /dev/null +++ b/third_party/spiro/x3/x3carbon.c @@ -0,0 +1,1002 @@ +#include "x3.h" +#include "x3common.h" + +/* Globals for managing sync */ + +#define VERBOSE + +#define kX3ViewClassID CFSTR("com.levien.x3.X3View") +#define kX3ViewPrivate 'X3_v' + +/* Some utility-type functions. */ + +UInt32 x3mkmultichar(const char *s) +{ + int len = strlen(s); + int i; + UInt32 result = 0; + + for (i = 0; i < (len > 4 ? 4 : len); i++) + result = (result << 8) + (unsigned char)s[i]; + for (; i < 4; i++) + result = (result << 8) + ' '; + return result; +} + +char *x3multicharstr(UInt32 mc, char buf[5]) +{ + int i, len; + + for (i = 0; i < 4; i++) + if (((mc >> (8 * i)) & 0xff) != ' ') break; + len = 4 - i; + for (i = 0; i < len; i++) + buf[i] = (mc >> (24 - 8 * i)) & 0xff; + buf[len] = 0; + return buf; +} + +void x3widget_init(x3widget *w, const x3type *type) +{ + w->type = type; + w->name = NULL; + w->parent = NULL; + w->var = x3carbonnone; + w->u.window = NULL; + w->n_children = 0; + w->children = NULL; +} + +static x3widget *x3widget_new_container(x3widget *parent, char *name, + const x3type *type) +{ + x3widget *result = (x3widget *)malloc(sizeof(x3widget)); + x3widget_init(result, type); + result->name = name ? strdup(name) : NULL; + x3add(parent, result); + x3qsizereq(result); + return result; +} + +static x3widget *x3widget_new_hiview(x3widget *parent, char *name, + const x3type *type, + HIViewRef hiview) +{ + x3widget *result = (x3widget *)malloc(sizeof(x3widget)); + x3widget_init(result, type); + result->name = name ? strdup(name) : NULL; + result->var = x3carbonhiview; + result->u.hiview = hiview; + x3add(parent, result); + x3qsizereq(result); + return result; +} + +static x3widget *x3widget_new_menu(x3widget *parent, + const x3type *type, + MenuRef menu) +{ + x3widget *result = (x3widget *)malloc(sizeof(x3widget)); + x3widget_init(result, type); + result->var = x3carbonmenu; + result->u.menu = menu; + x3add(parent, result); + return result; +} + +static x3widget *x3widget_new_menuitem(x3widget *parent, + const x3type *type, int index) +{ + x3widget *result = (x3widget *)malloc(sizeof(x3widget)); + x3widget_init(result, type); + x3add(parent, result); + result->var = x3carbonmenuitem; + result->u.menuitem = index; + return result; +} + +void x3init(int *pargc, char ***pargv) +{ + ProcessSerialNumber psn; + + /* Most apps get this done from loading the menu bar from the NIB. + Prior to 10.3, there was an undocumented call. This one is official + for 10.3 and later. */ + GetCurrentProcess(&psn); + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + SetFrontProcess(&psn); + + x3initqs(); +} + +void x3main(void) +{ + x3sync(); + RunApplicationEventLoop(); +} + +void x3_window_show(x3widget *mainwin) +{ + ControlRef root; +#if 0 + TransitionWindow(mainwin->u.window, kWindowZoomTransitionEffect, + kWindowShowTransitionAction, NULL); +#endif + +#if 0 + CreateRootControl(mainwin->u.window, &root); +#endif + //RepositionWindow(mainwin->u.window, NULL, kWindowCascadeOnMainScreen); + ShowWindow(mainwin->u.window); + SelectWindow(mainwin->u.window); +} + +static WindowRef x3window_of(x3widget *w) +{ + while (w->parent) w = w->parent; + return w->var == x3carbonwindow ? w->u.window : NULL; +} + +typedef struct { + x3widget base; + x3window_callback callback; + void *callback_data; +} x3widget_window; + +pascal OSStatus x3carbonWindowEventHandler(EventHandlerCallRef cr, + EventRef inEvent, void *data) +{ + x3widget_window *z = (x3widget_window *)data; + OSStatus result = noErr; + UInt32 eclass = GetEventClass(inEvent); + UInt32 ekind = GetEventKind(inEvent); + char multicharbuf[5]; + + if (eclass == kEventClassCommand && ekind == kEventCommandProcess) { + HICommand command; + int status; + + GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, + NULL, sizeof(HICommand), NULL, &command); + status = z->callback(&z->base, z->callback_data, + x3multicharstr(command.commandID, multicharbuf), + "command", NULL, NULL); + if (status == 1) + result = eventNotHandledErr; + } else if (eclass = kEventClassWindow && ekind == kEventWindowBoundsChanged) { + /* todo: only queue size request when size changes, not just pos */ + x3qsizereq(&z->base); + x3sync(); + } else { + printf("My handler is getting called %s %d!\n", + x3multicharstr(eclass, multicharbuf), + GetEventKind(inEvent)); + } + result = eventNotHandledErr; + return result; +} + +void x3window_sizereq(x3widget *w) +{ +} + +void x3window_sizealloc(x3widget *w, x3rect *r) +{ + int i; + Rect bounds; + x3rect child_r; + + GetWindowBounds(w->u.window, kWindowContentRgn, &bounds); + child_r.x0 = 0; + child_r.x1 = bounds.right - bounds.left; + child_r.y0 = 0; + child_r.y1 = bounds.bottom - bounds.top; + printf("x3window_sizealloc (%d, %d) - (%d, %d)\n", + bounds.left, bounds.top, bounds.right, bounds.bottom); + for (i = 0; i < w->n_children; i++) { + x3widget *child = w->children[i]; + if (child->type->sizealloc) + child->type->sizealloc(child, &child_r); + child->flags &= ~x3flag_needsizealloc; + } +} + +x3type x3windowtype = { x3window_sizereq, + x3window_sizealloc, + x3add_default }; + +x3widget *x3window(x3windowflags flags, char *label, + x3window_callback callback, void *data) +{ + WindowRef window; + Rect bounds = { 100, 100, 400, 600 }; + EventHandlerRef handlerRef; + WindowAttributes attrs = + kWindowCompositingAttribute | + kWindowLiveResizeAttribute | + kWindowInWindowMenuAttribute | + kWindowFrameworkScaledAttribute | + kWindowStandardHandlerAttribute; + EventTypeSpec windowEvents[] = { + { kEventClassCommand, kEventCommandProcess }, + //{ kEventClassCommand, kEventCommandUpdateStatus }, + + //{ kEventClassMouse, kEventMouseDown }, + + //{ kEventClassWindow, kEventWindowClose }, + //{ kEventClassWindow, kEventWindowGetIdealSize }, + { kEventClassWindow, kEventWindowBoundsChanged }, + //{ kEventClassWindow, kEventWindowGetClickActivation }, + //{ kEventClassWindow, kEventWindowContextualMenuSelect } + }; + CFStringRef cflabel = CFStringCreateWithCString(NULL, label, + kCFStringEncodingUTF8); + x3widget *result = (x3widget *)malloc(sizeof(x3widget_window)); + + if (flags & x3window_main) + attrs |= kWindowStandardDocumentAttributes; + + OSStatus err = CreateNewWindow(kDocumentWindowClass, attrs, + &bounds, &window); + + SetWindowTitleWithCFString(window, cflabel); + CFRelease(cflabel); + x3widget_init(result, &x3windowtype); + result->var = x3carbonwindow; + result->u.window = window; + ((x3widget_window *)result)->callback = callback; + ((x3widget_window *)result)->callback_data = data; + + InstallWindowEventHandler(window, NewEventHandlerUPP(x3carbonWindowEventHandler), + sizeof(windowEvents)/sizeof(EventTypeSpec), + windowEvents, result, &handlerRef); + + x3qshow(result); + return result; +} + +x3type x3menutype = { NULL, NULL, x3add_default }; + +x3widget *x3menu(x3widget *parent, char *name) +{ + static int id = 1; /* Note: menu id should probably be kept per-window */ + MenuRef menu; + CFStringRef cflabel = CFStringCreateWithCString(NULL, name, + kCFStringEncodingUTF8); + + CreateNewMenu(id++, 0, &menu); + SetMenuTitleWithCFString(menu, cflabel); + CFRelease(cflabel); + InsertMenu(menu, 0); + return x3widget_new_menu(parent, &x3menutype, menu); +} + +int x3parseshortcut(const char *shortcut, UInt16 *pkey, UInt8 *pmods) +{ + UInt16 key; + UInt8 mods = kMenuNoCommandModifier; + int i = 0; + + while (shortcut[i] == '<') { + if (!strncmp(shortcut + i, "", 5)) { + mods &= ~kMenuNoCommandModifier; + i += 5; + } else if (!strncmp(shortcut + i, "", 7)) { + mods |= kMenuShiftModifier; + i += 7; + } else if (!strncmp(shortcut + i, "