commit a33c6c168babf542e42d6238a11155f62a923420 Author: chenzhirong <826531489@qq.com> Date: Sat Oct 11 13:06:32 2025 +0800 init: 初始化仓库 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99ea4cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.vscode + +# Pyre type checker +.pyre/ +.idea/' + +.DS_Store + +# Intellij IDEA Files +.idea/* +!.idea/vcs.xml +!.idea/icon.png +.ideaDataSources/ +*.iml \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Docker/Dockerfile b/Docker/Dockerfile new file mode 100644 index 0000000..639a909 --- /dev/null +++ b/Docker/Dockerfile @@ -0,0 +1,19 @@ +FROM ubuntu:20.04 + +WORKDIR /root + +COPY run.sh . + +RUN apt update && apt install -y python3-pip git wget + +RUN pip install --upgrade pip + +RUN git clone --depth 1 https://github.com/naurril/SUSTechPOINTS.git + +RUN cd SUSTechPOINTS && \ + wget https://github.com/naurril/SUSTechPOINTS/releases/download/0.1/deep_annotation_inference.h5 -P algos/models && \ + python3 -m pip install -r ./requirement.txt -i https://pypi.tuna.tsinghua.edu.cn/simple + +ENTRYPOINT ["/root/run.sh"] + +EXPOSE 8081 diff --git a/Docker/run.sh b/Docker/run.sh new file mode 100755 index 0000000..e1d8088 --- /dev/null +++ b/Docker/run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cd /root/SUSTechPOINTS && python3 ./main.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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. But first, please read +. diff --git a/README_cn.md b/README_cn.md new file mode 100644 index 0000000..44d89a2 --- /dev/null +++ b/README_cn.md @@ -0,0 +1,208 @@ +# SUSTechPOINTS: 3D Point Cloud Annotation Tool + +![screenshot](./doc/screenshot.png) + +## 项目启动 +```shell +uv sync +uv run mian.py +``` +## UI说明 + +### 屏幕左上区域 + +![screenshot](./doc/header.png) + + Scene选择 + Frame选择 + 目标id选择(试验用,会启动batchedit模式,显示该物体的多个实例) + 相机选择 在不同相机间切换,选择3dbox时也会自动切换 + Box信息 + *(表示已更改未保存) 类别-ID |距离| x y z | 长宽高 | roll pitch yaw | 点数 |F:n(follow obj n) + +### 配置菜单(右上角) + +![screenshot](./doc/view-menu.png) + +- point size 增加/减小点的大小 +- point brightness 增强/减弱点的亮度 +- hide box 隐藏3dbox +- theme 暗/亮模式选择 +- color objects 目标着色方案:按id/类别,无色 +- batch mode max box number: 批编辑模式下显示的实例个数 +- data settings: 是否显示雷达数据 +- experimental: 实验,标定用 +- take screenshot 下载屏幕截图(仅3D场景) +- Help + +### 相机图片 + +拖动图片的右下角可以调整大小, 选择不同的相机会显示不同图片. + +### 输出窗口 +右下角窗口会输出运行信息, 可以点击标题栏隐藏/显示. + +![screenshot](./doc/output-window.png) + +### 右键菜单 + +右键点击空白区域 + +![screenshot](./doc/contextmenu.png) + +- New 在鼠标当前位置创建对应的box +- Paste 在鼠标当前位置paste +- goto +- play +- pause/resume +- stop +- +- save 保存 +- save all +- reload 放弃当前修改刷新上一次保存的内容 +- reload all +- frame info +- stat + +右键点击box + +![screenshot](./doc/contextmenu-obj.png) + +- delete 删除该box +- delete other instance 删除其他frame里该object的box +- sync object type 其它frame中该物体的类型设置为当前box的类型 +- sync object size 其它frame中该物体的大小设置为当前box的类型 +- inspect all instances 唤起批量标注界面 +- select as ref 选择当前box为参考box (同copy) +- follows ref 设置当前box为跟随参考box(即相对位置固定) +- sync followers 将所有跟随当前box的物体标注出来. + +(该菜单部分功能处于试验状态,尚不完善.) + + +## 操作 + + +### 调整视角 + +在主窗口里可以通过鼠标左键旋转, 右键移动, 滚轮缩放视角. + + +### 新加Box + +方法1: 鼠标移动到目标物体上, 右键选择new-物体种类, 会自动生成box并尝试自动旋转角度和调整box大小. + +方法2: 按住ctrl键, 鼠标左键拉一个矩形, 会自动生成box并尝试自动旋转角度和调整box大小. +![auto-rotate](./doc/auto-rotate.gif) + +方法3: 按住shift键,鼠标左键拉一个矩形, 会生成一个box, 包含矩形框围住的点, 方向为屏幕向上的方向. 注意该操作不会自动调整box的大小和方向. + +注: +- 画矩形时尽量避免将目标物体之外的点选中,可以少选. +- 上述操作方法是通过矩形投影,将范围内的点进行region grow找到目标物体所有的点. 为了避免选中太多的错误点,建议将视角旋转到接近鸟瞰视角. +- region grow算法会受到地面的影响, 目前采用的方式是将最低的30cm部分先删除再region grow,如果地面非常倾斜,会影响效果, box生成之后需要手工调整. +- region grow算法比较慢(需要优化), 对于超大的物体如bus尽量框选完整,可以加快速度 +- shift+矩形选择不会自动识别方向,为了让初始方向大致正确,建议将主视图旋转到物体的方向是沿屏幕向上或者向下,如果方向反了,按g键旋转180度. + + +### box操作 + +左键点击一个目标,会选中该目标物体. 选择的物体同时会在屏幕左侧显示3个投影窗口,分别是鸟瞰视图,侧视图和后视图. 如果有相机图片的话,还会显示box在图片上的投影.同时在box的旁边还会显示快速工具栏(下图). + +![fast-toolbox](./doc/fast-toolbox.png) + +在快速工具栏上可以修改目标类别和tracking ID. 鼠标悬浮在工具按钮上会有相应的功能提示. + + +点击选中的box会激活主窗口的box调整模式,多次点击会在box大小/角度/位置3中调整模型中切换, 拖动可对box进行调整. 键盘z/x/c可以切换x/y/z轴. 使用v键也可以切换模式. + +点击空白处可以取消box的编辑模式,或者取消box的选择, ESC键有同样的功能. + + +box被选择后, 左边的3个子窗口都可以对box进行调整.鼠标移动到某个子窗口即可在该子窗口进行调整, 调整操作方式相同, 但是各自针对不同的轴. 每个窗口可以调节2个轴的参数. + +子窗口内滚动鼠标可调节显示的大小. 拉动虚线/角落可以调节box的大小和旋转角度. 双击虚线/角落/中心位置可以自动缩小box使其和点贴近. 双击旋转线会将box旋转180度. + +按住Ctrl键拖动虚线, 释放鼠标会让对应的虚线自动向内侧贴近点. +按照Shift键拖动虚线有类似的效果, 但是会保持box的大小不变, 对box进行平移. + +鸟瞰视图里的toolbox提供了几个常用功能的按钮: + +![bird's eye view-toolbox](./doc/bev-toolbox.png) + +分别是自动平移, 自动旋转, 自动旋转加缩放, 重置功能. + +除鼠标和toolbox外, 还支持键盘操作. + + a: 左移 + s: 下移 + d: 右移 + w: 上移动 + q: 逆时针旋转 + e: 顺时针旋转 + r: 逆时针旋转同时自动调整box大小 + f: 顺时针选择同时自动调整box大小 + g: 反向 + t: 重置 + +鸟瞰图的红色圆圈表示lidar(xy平面的原点)的位置所处的方向. + +侧视图和后视图提供和鸟瞰图相同的功能(自动旋转除外). + + +### 其他功能 + + -/=: 调整点的大小 + ctrl+s 保存标注结果(暂不支持自动保存) + del/ctrl+d remove selected box + + 1,2 选择上一个/下一个box + 3,4 切换到上一帧/下一帧 + 5,6,7 显示/隐藏3个子视图的相机参数(调试功能) + + space: 暂停/继续播放 + +## 批量编辑 + +![batch edit ui](./doc/batch-edit.png) + +批量编辑界面可以同时对同一目标物体的多个实例(不同frame)进行编辑.  + +- 激活方式1, 右键点击某box, 选择inspect all instances +- 激活方式2, 屏幕左上角窗口选择obj (试验用,不能自动切换到合适的frame) + +默认一次显示20帧进行编辑. 每个子窗口的操作方式与非批量模式相同. +在配置界面可以选择一次选择的帧数. +第一个实例的图片右下角可以调节每个编辑窗口的大小,可以根据需要调节. + +右上角的功能按钮如下: + + Trajectory 显示轨迹 + + Auto 自动标注 + Auto(no rotation) + Interpolate 仅插值,不进行旋转和位置的调整. + + Reload  放弃本次编辑的内容,重新加载 + Finalize 将所有自动标注的内容标记为已确认(等同于人工标注). + + Save 保存 + Previous 前20帧(有10帧重叠) + Next 后20帧(有10帧重叠) + Exit 退出 + +说明 +- 人工修改过的标注不会受到自动标注和插值的影响.finalize就是将所有的自动标注的box标记为等同人工调整过的. 标注完后需要finalize, save. +- 每个小窗口的标题是帧号,如果有M字母表示是由machine自动标注的,否则表示为人工修改过或者确认过的. +- 鼠标移动到某个小窗口, Ctrl+D可以删除box (或者右键操作) + +## 右键菜单 +![batch edit ui](./doc/batchedit-context-menu.png) + + + + + +## Object type configuration + +如果需要修改模型的目标类型/大小/颜色,可以修改 [obj_cfg.js](src/public/js/../../../public/js/obj_cfg.js)文件. diff --git a/README_guide.md b/README_guide.md new file mode 100644 index 0000000..f348078 --- /dev/null +++ b/README_guide.md @@ -0,0 +1,233 @@ + + + + + +[工具介绍](./README.md) + + +[操作说明](./README_cn.md) + +[快捷键](./doc/shortcuts_cn.md) + +# 什么是3D目标检测/追踪 + +3D目标检测就是给定3D场景(激光点云,或者图像), 把所有感兴趣的物体识别出来, 用3D立方体将物体框起来, 并给出物体的类别. 如果还有追踪任务, 则需要给每个目标物体分配唯一ID. + +对应的数据集可以参考 +[KITTI数据集](http://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d), +[百度Apollo数据集](http://apolloscape.auto/tracking.html) + +## 如何制作 + +通常我们需要专用的数据采集车, 采集各种场景的数据. 数据包括激光雷达点云, 相机图片等. 然后从中挑选有代表性的片段进行标注. + + +# 标注要求 + +下文描述3D目标检测和追踪数据集(点云/图像)的标注. + +## 标注范围 + +### 距离 +不管目标物体距离多远, 只要是可辨认出目标物体的都应该标注.如果被完全遮挡则不用标. + + +### 类别及属性 +所有可移动的物体都要标注. + +部分类别有一些属性,需要根据实际情况选择或者填写 +比如人的属性, 伞,婴儿车,行李,坐着,蹲着.. +rider的属性: 伞,载客1个,载客2个.. +车的属性: 门开着 + + +目前支持的类别如下: + +|Name|中文|可选属性|参考图片| +|----|----|------|------| +|Car|轿车|门开着|| +|Van|面包车||| +|PoliceCar|警车||| +|Pedestrian|行人|伞,行李,坐着,蹲着,弯腰|| +|RoadWorker|工人||![roadworker](./doc/objtype/road-worker.png)| +|Child|小孩||| +|Cone|雪糕筒||| +|FireHydrant|消防栓||| +|ReflectiveTriangle|安全三角||![triangle](./doc/objtype/Triangle.jpeg)| +|PlatformCart|平板车||![platform cart](./doc/objtype/platform-cart.jpg)| +|ConstructionCart|建筑小车||![Construction Cart](./doc/objtype/construction-cart.jpeg)| +|RoadBarrel|安全桶||![road barrel](./doc/objtype/road-barrel.jpg)| +|TrafficBarrier|交通护栏||![traffic barrier](./doc/objtype/TrafficBarrier.jpeg)| +|ScooterRider|骑电动车的人|伞,1个乘客,2个乘客|| +|MotorcyleRider|骑摩托的人|伞,1个乘客,2个乘客|| +|BicycleRider|骑自行车的|伞,1个乘客,2个乘客|| +|Bicycle|自行车|倒在地上| +|Babycart|婴儿车||![babycart](./doc/objtype/babycart.png)| +|LongVehicle|长尾拖车||![longvehicle](./doc/objtype/LongVehicle.png)| +|Motorcycle||| +|Scooter|电动车||![scooter](./doc/objtype/scooter.jpg)| +|BicycleGroup|路边的电动车/自行车堆|| +|Bus||| +|Truck|卡车|| +|ConcreteTruck|水泥车||![Construction Cart](./doc/objtype/concrete-truck.jpeg)| +|Tram|电车|| +|Animal|动物|| +|ForkLift|叉车||![Forklift](./doc/objtype/forklift.jpg)| +|Trimotorcycle|电动三轮车||![Trimotocycle](./doc/objtype/Trimotorcycle.jpeg)| +|FreightTricycle|人力三轮车||![freight tricycle](./doc/objtype/freight-tricycle.png)| +|Crane|吊车||![crane](./doc/objtype/crane.jpeg)| +|Excavator|挖掘机||![excavator](./doc/objtype/excavator.png)| +|Roadroller|压路机||![road roller](./doc/objtype/RoadRoller.jpeg)| +|Bulldozer|推土机||![bull dozer](./doc/objtype/Bulldozer.jpeg)| + + + +## 3D Box要求 + +3d box的大小,方向,类别都需要准确标注.对于旋转方向,需要将3个轴都旋转到正确的方向. + +### 车 +3D box应该力求和真实目标物体大小一样,包括后视镜,车门(如果是开的)等所有附属物.box的方向是车头的方向. + +对于部分遮挡的车,需要在相邻帧中找到同一物体,将box大小COPY过去. +如果找不到相同的物理,根据照片等查看是否有相同车款,如果有的话将box拷贝过去. +如果找不到,可以根据经验估计大小. + +### 人 +box应该包括整个人,包括四肢.人走动时形状发生变化,box应该跟着变化. +人方向为整个主题躯干的方向,或者行进的方向. + +注意如果地面是倾斜的,人仍然会平行于重力方向站立,不会垂直于地面,此处有别于车. + +### 雪糕筒/交通障碍栏等/防撞桶 +这些物体没有明确方向,可以使用任一方向. + + +## 追踪ID要求 + +每个物体需要标注跟踪ID,在场景内唯一,如果有中断,后续应该仍然使用相同的ID. + + +# 如何开始标注 + +建议的标注方法 + +0. 建议按照目标物体标, 不要按帧标. 也就是一个目标物体在整个场景中全部标完后再标下一个物体. +1. 打开一个场景后,可以先浏览一下,然后开始选定目标物体进行标注. 一个场景长20s, 包含40帧原始数据,每秒选取了2帧进行标注.(我们采集的数据是每秒10帧,每帧间隔100ms. 由于间隔时间短,场景变化太小,所以不需要每帧都标注,我们可以根据每秒2帧的标注结果进行线性插值完成对剩余8帧的标注.) +2. 标一个物体时,建议选择场景中视野最好的帧开始,比如距离较近,无遮挡就满足要求,这种情况下可以比较准确的标出物体的大小和方向. 标好后,可以使用复制/粘贴的方式把box迁移到上一帧或者下一帧,两帧都调整好后,可以启动批量标注功能(edit multiple instances),使用自动标注的方式对其他帧进行标注. 先标两帧的目的是给物体一个初始速度,对自动标注时的追踪有帮助. 也可以标完一帧后就开始批量标注,如果有问题再进行调整. +3. 批量标的时候,有时候自动标注算法会失败,比如找不到物体(追踪丢失等),方向不正确等,这时需要手工调整,可以调整一部分后再尝试批量标.最后将不存在物体(被遮挡,或者太远看不见了)的物体box删除.自动算法包含 插值,自动(不旋转),全自动三种, 这三个功能依次更加自动化, 但是在复杂情况下出错的概率也更高。 而且这三个功能运行的条件是已经有一些或者至少一个box是已经标好的,已经标好的box越多,算法效果可能就更好。所以在标注过程中,可以先尝试全自动,如果有部分box已经标的满足要求,可以将这些box finalize(相当于已经人工确认),这样下次再运行算法时,就有更多可参考的输入。如果全自动效果不好,可以尝试自动(无旋转),如果效果仍然不好,可以尝试最简单的插值。不管用那种方法,都应该是运行算法-手工修改一两个box-运行算法-...这样交替的方式操作,整体效率最高。 +4. 建议打开trajectory查看该物体在整个场景里面的轨迹,如果有异常(比如方向变化太大等)可以再次检查确认. +5. 点击finalize, save, exit, 完成一个物体在场景里面的标注. +6. 对于小目标的物体,如人,在标注的过程中可能不太容易分辩方向,可以根据其前进的方向,相对与建筑/路面的方向等进行辅助判断. 在拥挤场景如果不容易进行追踪,可以切换到10hz的数据(goto/10Hz)进行识别和标注. 10hz的数据和2Hz的数据是共享标注结果的.识别完成后建议切换回2hz,因为10HZ下帧间差距太小,标起来比较浪费时间. + + + +# FAQ +- 如何升级版本 + + 标注工具是基于web页面的,服务端升级后就会自动升级,但是本地有时候会使用local cache不更新,此时可以用两种方式强制更新 + - 如果使用chrome, 可以按住ctrl按刷新按钮 + - 清除历史记录,再刷新页面 +- 坐标系 + + 标注系统涉及到2个坐标系, 点云坐标系和世界坐标系. 在设置里面可以选择显示的坐标系(coordinate system) + + - LiDAR: 按点云坐标系显示, 原点为激光雷达的原点, 按车身方向, z轴朝上, y朝后, x朝左. 该配置下看起来世界向后走,采集车(ego car)不动 + + - GPS/UTM: 按大地坐标系显示, z朝上, x朝东, y朝北. 该配置下, 看起来地面不动, 车向前后. (由于定位精度和误差, 地面有时会漂移) + + +- 视图转来转去一段时间后, 就很难操作, 怎么办 + - 在主界面右键选择reset view, 会回到当前frame正中间, 从上向下俯视. + +- 如何确定物体的方向(旋转) + - 对于大型的车, 一般可以使用算法确定的方向, 然后微调. + - 对于人, 有几种方法: + - 可以先确定位置, 最后使用行进方向作为方向. (在multiple instance edit模式下, 右键/fit/moving direction), 然后根据情况微调. 如果人没有移动, 该方法不可使用. + - 按照周围环境, 如路的方向, 借助图片, 确定方向, + - 按照人的身形确定方向. + +- 如何确定遮挡物体的大小 + - 在前后帧中寻找相对完整的场景, 从该帧开始标, 然后将大小迁移到其他帧. + - multiple instance edit模式下, interpolate/auto等功能都是保持物体大小的, 只要有已经确定大小的帧就可以工作. + - 普通模式下, 可以使用copy, paste的方式将box从一帧挪到另一帧. + - 在用鼠标编辑box时, 按住shift, 可以保持box大小不变. + - 没有可参考的其他帧数据,而且被遮挡,怎么确定大小 + - 根据环境:在侧试图或者后视图中,缩放视角,查看物体周围是否有地面线,如果有将box下边缘拉到地面线的位置. 如下图,后边的灰色线为地面线,可以据此确定box的下边界。 + + ![bottom-line](./doc/box-size-bottom.png) + + - 根据物体的对称性,将边线拉到对称的位置 + + - 查看对应的图片,如果是常见车型(如byd的的士),可以找到另外的同类型的车对应的box,复制粘贴,然后修改位置(不修改大小) + - 实在没有任何办法的情况下,根据经验估计大小 + +- 如何修改类别 + + 如果某个object的类别标错了,又不想一个一个去修改,可以在某帧修改好之后,鼠标移到工具框的`...`然后选`Sync object type & attr`. + + ![modify-type](./doc/modify-type-attr.png) + +- multiple instance edit模式下, 有哪些操作方法 + - box选择, 使用鼠标可以选择多个操作对象 + - 单击: 选择/反选 + - Ctrl+单击: 选择/反选 + - shift+单击: 选择连续帧 + - 拖动: 选择多个帧 + - 鼠标右键可以选择当前帧前面的,后面的, 所有的帧 + - 注意鼠标如果在某个box的边线上点击时, 则是对box的编辑,不会进入选择功能 + - box选择后, 使用右键菜单, 可以进行如下功能 + - 删除 + - interploate 按线性移动速度方式插值 + - auto (no rotation) 自动(不旋转) + - auto annotate 全自动 + + - fit + - size: 自动适配大小 + - position: 自动适配位置 + - rotation: 用ai算法调整方向 + - moving direciotn: 使用行进方向调整方向 + - 上述功能对应俯视图的4个按钮 + - finalize: 将所选box标记为人工编辑完成(后续自动算法运行时会作为重要的参考,而且自动算法不再会修改该box) + - reload + - goto this frame: 切换到普通模式,并切换到当前帧, 对应的box会选中 + - 右上角的按钮 + - `trajectory` 显示该物体在世界坐标系下的轨迹, 双击某个位置的box, 会退出并将对应的box选中. + - 其他按钮跟右键菜单一样,但是针对所有的帧. + - 显示屏有点小/大, 如果调整批量编辑的数量 + - 右上角config -> `Batch mode max box number` + - 调整数量后, 如果显示的帧数少于场景总帧数, 请使用右上角按钮`next`/`previous`翻页 +- 点云的点有点暗,看不清怎么办 + - 使用+/-调整点的大小, 或者在config菜单中修改(右上角按钮) + +- box编辑功能 + - 快捷键列表 (俯视图/侧视图/后视图), 鼠标在某个视图上时,按键对该视图有效 + - a: 左移 + - s: 下移 + - d: 右移 + - w: 上移动 + - q: 逆时针旋转 + - e: 顺时针旋转 + - r: 逆时针旋转同时自动调整box大小 + - f: 顺时针选择同时自动调整box大小 + - g: 反向 + + - 鼠标操作 (俯视图/侧视图/后视图) + - 鼠标可以对每个试图对应的矩形边线,角,旋转方向进行拖动/双击, 产生对应编辑效果 + - 拖动 - 移动边线到鼠标位置 + - 双击 - 自动fit到最近的内点 + - shift+拖动 - 移动边线到鼠标位置,但是整个box大小保持不变 + - ctrl+拖动 - 拖动后, 从做后的位置,自动fit到物体最近的内点 + - 按钮 + - scale - 自动调整大小 + - rotate - 自动调整方向,大小不变 + - move - 自动调整位置,大小和方向都不改变 + - I am lucky - 方向/大小/位置都自动调整 + - move direction - 使用物体的移动方向作为朝向, + - 如果是运动物体, 且前后帧至少有一帧已经标注过,位置正确即可计算方向 + - 如果物体没有移动或者移动很缓慢,该功能不可使用 + - 如果是大型车辆, 速度慢且转弯时, 该功能也不可使用 +# 参考资料 + + diff --git a/algos/__init__.py b/algos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/algos/models/deep_annotation_inference.h5 b/algos/models/deep_annotation_inference.h5 new file mode 100644 index 0000000..ea0f79f Binary files /dev/null and b/algos/models/deep_annotation_inference.h5 differ diff --git a/algos/models/deep_annotation_inference.keras b/algos/models/deep_annotation_inference.keras new file mode 100644 index 0000000..10cc7f9 Binary files /dev/null and b/algos/models/deep_annotation_inference.keras differ diff --git a/algos/models/readme.txt b/algos/models/readme.txt new file mode 100644 index 0000000..db7aaeb --- /dev/null +++ b/algos/models/readme.txt @@ -0,0 +1 @@ +This folder is used to store models \ No newline at end of file diff --git a/algos/pre_annotate.py b/algos/pre_annotate.py new file mode 100644 index 0000000..4c0ac98 --- /dev/null +++ b/algos/pre_annotate.py @@ -0,0 +1,299 @@ + +import os +import tensorflow as tf + + + +import numpy as np + +from . import util +import glob +import math +import json + +util.config_gpu() + + +RESAMPLE_NUM = 10 + +model_file = "./algos/models/deep_annotation_inference.h5" + +rotation_model = tf.keras.models.load_model(model_file) +rotation_model.summary() + +NUM_POINT=512 + +def sample_one_obj(points, num): + if points.shape[0] < NUM_POINT: + return np.concatenate([points, np.zeros((NUM_POINT-points.shape[0], 3), dtype=np.float32)], axis=0) + else: + idx = np.arange(points.shape[0]) + np.random.shuffle(idx) + return points[idx[0:num]] + +def predict_yaw(points): + points = np.array(points).reshape((-1,3)) + input_data = np.stack([x for x in map(lambda x: sample_one_obj(points, NUM_POINT), range(RESAMPLE_NUM))], axis=0) + pred_val = rotation_model.predict(input_data) + pred_cls = np.argmax(pred_val, axis=-1) + print(pred_cls) + + ret = (pred_cls[0]*3+1.5)*np.pi/180. + ret =[0,0,ret] + print(ret) + + return ret + +# warmup the model +predict_yaw(np.random.random([1000,3])) + +if False: + # weights_path = "../DeepAnnotate/da_rp_weights.h5" + # #filter_model = tf.keras.filter_models.load_filter_model(filter_model_path) + + # filter_model = M.get_filter_model_rp_discrimination(common.NUM_POINT, 2, False) + + # # filter_model.compile(optimizer=tf.keras.optimizers.Adam(0.0001), + # # loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), + # # metrics=[tf.keras.metrics.sparse_categorical_accuracy, tf.keras.metrics.SparseTopKCategoricalAccuracy(k=2)]) + # #filter_model.summary() + + # filter_model.load_weights(weights_path) + # filter_model.summary() + + use_env = False + if use_env: + filter_model_file = './algos/models/deepannotate_rp_discrimination_env.h5' #"./deepannotate_rp_discrimination.back.h5" + else: + filter_model_file = './algos/models/deepannotate_rp_discrimination_obj_xyzi.h5' #"./deepannotate_rp_discrimination.back.h5" + filter_model = tf.keras.models.load_model(filter_model_file) + filter_model.summary() + + + + # rotation_model_file = "../SUSTechPoints-be/algos/models/deep_annotation_inference.h5" + # rotation_model = tf.keras.models.load_model(rotation_model_file) + # rotation_model.summary() + + # #from rotation import model as rotation_model + + # #NUM_POINT = 512 + + def cluster_points(pcdfile): + def pre_cluster_pcd(file, output_folder): + pre_cluster_exe = "/home/lie/code/pcltest/build/cluster" + cmd = "{} {} {}".format(pre_cluster_exe, file, output_folder) + #print(cmd) + os.system(cmd) + + temp_output_folder = "./temppcd" + if os.path.exists(temp_output_folder): + os.system("rm {}/*".format(temp_output_folder)) + else: + os.mkdir(temp_output_folder) + + pre_cluster_pcd(pcdfile, temp_output_folder) + + obj_files = glob.glob(temp_output_folder+"/*-obj.bin") + env_files = glob.glob(temp_output_folder+"/*-env.bin") + obj_files.sort() + env_files.sort() + + ## note these clusters are already sorted by points number + objs = [np.fromfile(c, dtype=np.float32).reshape(-1, 4) for c in obj_files] + envs = [np.fromfile(c, dtype=np.float32).reshape(-1, 4) for c in env_files] + clusters = list(zip(objs, envs)) + return clusters + + def euler_angle_to_rotate_matrix(eu, t): + theta = eu + #Calculate rotation about x axis + R_x = np.array([ + [1, 0, 0], + [0, math.cos(theta[0]), -math.sin(theta[0])], + [0, math.sin(theta[0]), math.cos(theta[0])] + ]) + + #Calculate rotation about y axis + R_y = np.array([ + [math.cos(theta[1]), 0, math.sin(theta[1])], + [0, 1, 0], + [-math.sin(theta[1]), 0, math.cos(theta[1])] + ]) + + #Calculate rotation about z axis + R_z = np.array([ + [math.cos(theta[2]), -math.sin(theta[2]), 0], + [math.sin(theta[2]), math.cos(theta[2]), 0], + [0, 0, 1]]) + + R = np.matmul(R_x, np.matmul(R_y, R_z)) + + t = t.reshape([-1,1]) + R = np.concatenate([R,t], axis=-1) + R = np.concatenate([R, np.array([0,0,0,1]).reshape([1,-1])], axis=0) + return R + + + def sample_one_obj(points, num): + centroid = list(np.mean(points[:,:3], axis=0)) # intensity + + if points.shape[1] > 3: + centroid = np.append(centroid, np.zeros(points.shape[1]-3)) + print(centroid) + centroid.reshape(1,-1) + points = points - centroid + + # padding or sample + if points.shape[0]>num: + idx = np.arange(points.shape[0]) + np.random.shuffle(idx) + points = points[idx[0:num]] + else: + sample_idx = np.random.randint(0, high = points.shape[0], size=num - points.shape[0]) + padding = points[sample_idx] + points = np.concatenate([points, padding], axis=0) + + print("input shape", points.shape) + return points[:,:3] + + def filter_nearby_objects(clusters): + def nearby(c): # c is a pair + center = np.mean(c,axis=0) + return np.sum((center*center)[0:2]) < 70*70 + + ind = [nearby(c[0]) for c in clusters] + return np.array(clusters)[ind] + + + # # return true/false list + def filter_candidate_objects(clusters): + + ## all clusters stacked into a batch + + objidx = 1 if use_env else 0 + input_cluster_points = np.stack([sample_one_obj(p[objidx], NUM_POINT) for p in clusters], axis=0) # use env to do filtering + + pred_val = filter_model.predict(input_cluster_points) + #print(pred_val) + prob = np.exp(pred_val)/np.sum(np.exp(pred_val),axis=1,keepdims=True) + + #print(prob) + #pred_cls = np.argmax(prob, axis=1) + pred_cls = prob[:,1]>0.5 + return pred_cls + + def decide_obj_rotation(objs): + input_data = np.stack([sample_one_obj(o, NUM_POINT) for o in objs], axis=0) + pred_val = rotation_model.predict(input_data) + pred_cls = np.argmax(pred_val, axis=-1) + + ret = (pred_cls*3+1.5)*np.pi/180.0 #only z-axis rotation is predicted + + return ret + + def calculate_box_dimension(objs, rotation): + + def calc_one_box(obj, rot): + #print(obj.shape, rot) + rot = np.array([0,0,rot]) + centroid = np.mean(obj, axis=0) + obj = obj - centroid + + trans_mat = euler_angle_to_rotate_matrix(rot, np.zeros(3))[:3,:3] + relative_position = np.matmul(obj, trans_mat) + + pmin = np.min(relative_position, axis=0) + pmax = np.max(relative_position, axis=0) + pmin[2] = pmin[2] - 0.2 # remember 0.2 was removed, as ground + + box_dim = pmax-pmin + box_center_delta = box_dim/2 + pmin + #center delta shoulb be translated to global coord + box_center_delta = np.matmul(trans_mat, box_center_delta) + + box_center = box_center_delta + centroid + + return np.stack([box_center, box_dim, rot],axis=0) + + + return [calc_one_box(obj, rot) for obj,rot in zip(objs, rotation)] + + + + ## main func + def pre_annotate(pcdfile): + clusters = cluster_points(pcdfile) + + clusters = filter_nearby_objects(clusters) + cand_ind = filter_candidate_objects(clusters) + + # positive_files = np.array(cluster_files)[cand_ind] + # positive_files = [x for x in map(lambda f: f.replace(".bin",".pcd"), list(positive_files))] + + # outstr = "" + # for f in positive_files: + # outstr = outstr + " " + f + + # print(outstr) + + + # calculate box + cand_clusters = np.array(clusters)[cand_ind] + + # only objs is needed from now on + cand_clusters = [x[0][:,:3] for x in cand_clusters] + cand_rotation = decide_obj_rotation(cand_clusters) + + # print(cand_rotation) + boxes = calculate_box_dimension(cand_clusters, cand_rotation) + #print(boxes) + return boxes + + + def translate_np_to_json(boxes): + def trans_one_box(box): + return { + 'obj_type': 'Unknown', + 'psr': { + 'position': { + 'x': box[0,0], + 'y': box[0,1], + 'z': box[0,2], + }, + 'scale': { + 'x': box[1,0], + 'y': box[1,1], + 'z': box[1,2], + }, + 'rotation': { + 'x': box[2,0], + 'y': box[2,1], + 'z': box[2,2], + } + }, + 'obj_id': '', + } + + # return [trans_one_box(b) for b in boxes] + + def annotate_file(input, output=None): + boxes = pre_annotate(input) + boxes_json = translate_np_to_json(boxes) + + if output: + with open(output, 'w') as f: + json.dump(boxes_json, f) + + return boxes_json + +# if __name__ == "__main__": +# #root_folder = "/home/lie/fast/code/SUSTechPoints-be/data/sustechscapes-mini-dataset-test" +# root_folder = "/home/lie/fast/code/SUSTechPoints-be/data/2020-07-12-15-36-24" +# files = os.listdir(root_folder + "/lidar") +# files.sort() +# for pcdfile in files: +# print(pcdfile) +# jsonfile = pcdfile.replace(".pcd",".json") + +# annotate_file(root_folder + "/lidar/" + pcdfile, root_folder + "/label/" + jsonfile) \ No newline at end of file diff --git a/algos/rotation.py b/algos/rotation.py new file mode 100644 index 0000000..759f57f --- /dev/null +++ b/algos/rotation.py @@ -0,0 +1,43 @@ + + +import numpy as np +import tensorflow as tf +import util +util.config_gpu() + +RESAMPLE_NUM = 10 + +model_file = "./algos/models/deep_annotation_inference.h5" + +model = tf.keras.models.load_model(model_file) +model.summary() + +NUM_POINT=512 + +def sample_one_obj(points, num): + if points.shape[0] < NUM_POINT: + return np.concatenate([points, np.zeros((NUM_POINT-points.shape[0], 3), dtype=np.float32)], axis=0) + else: + idx = np.arange(points.shape[0]) + np.random.shuffle(idx) + return points[idx[0:num]] + +def predict_yaw(points): + points = np.array(points).reshape((-1,3)) + input_data = np.stack([x for x in map(lambda x: sample_one_obj(points, NUM_POINT), range(RESAMPLE_NUM))], axis=0) + pred_val = model.predict(input_data) + pred_cls = np.argmax(pred_val, axis=-1) + print(pred_cls) + + ret = (pred_cls[0]*3+1.5)*np.pi/180. + ret =[0,0,ret] + print(ret) + + return ret + +# warmup the model +predict_yaw(np.random.random([1000,3])) + + +if __name__ == "__main__": + predict_yaw(np.random.random([1000,3])) \ No newline at end of file diff --git a/algos/trajectory.py b/algos/trajectory.py new file mode 100644 index 0000000..305bfd7 --- /dev/null +++ b/algos/trajectory.py @@ -0,0 +1,262 @@ + + +# from filterpy.kalman import KalmanFilter +import numpy as np + +import sys +import os +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +sys.path.append(BASE_DIR) +sys.path.append(os.path.join(BASE_DIR, '..')) +import scene_reader + +class MAFilter: + def __init__(self, init_x): + self.x = init_x + self.v = np.zeros(9) # position, rotation + self.step = 0 + + def update(self, x): + if self.step == 0: + self.v = x-self.x + else: + self.v = self.v*0.5 + (x-self.x)*0.5 + + self.x[0:9] = x + self.step += 1 + + def predict(self): + self.x += self.v + self.step += 1 + return self.x + + + +def get_my_filter(init_x): + return MAFilter(init_x) + + +# def get_kalman_filter(init_x): +# dim_z = 9 +# kf = KalmanFilter(dim_x=12, dim_z=dim_z) +# kf.F = np.array([ [1,0,0,0,0,0,0,0,0,1,0,0], # state transition matrix +# [0,1,0,0,0,0,0,0,0,0,1,0], +# [0,0,1,0,0,0,0,0,0,0,0,0], +# [0,0,0,1,0,0,0,0,0,0,0,0], +# [0,0,0,0,1,0,0,0,0,0,0,0], +# [0,0,0,0,0,1,0,0,0,0,0,0], +# [0,0,0,0,0,0,1,0,0,0,0,0], +# [0,0,0,0,0,0,0,1,0,0,0,0], +# [0,0,0,0,0,0,0,0,1,0,0,0], +# [0,0,0,0,0,0,0,0,0,1,0,0], +# [0,0,0,0,0,0,0,0,1,0,1,0], +# [0,0,0,0,0,0,0,0,0,0,0,1], +# ]) + +# kf.H = np.array([ [1,0,0,0,0,0,0,0,0,0,0,0], # measurement function, +# [0,1,0,0,0,0,0,0,0,0,0,0], +# [0,0,1,0,0,0,0,0,0,0,0,0], +# [0,0,0,1,0,0,0,0,0,0,0,0], +# [0,0,0,0,1,0,0,0,0,0,0,0], +# [0,0,0,0,0,1,0,0,0,0,0,0], +# [0,0,0,0,0,0,1,0,0,0,0,0], +# [0,0,0,0,0,0,0,1,0,0,0,0], +# [0,0,0,0,0,0,0,0,1,0,0,0], +# ]) + +# # self.kf.R[0:,0:] *= 10. # measurement uncertainty +# #kf.P[dim_z:,dim_z:] *= 1000. #state uncertainty, give high uncertainty to the unobservable initial velocities, covariance matrix +# #kf.P *= 10. + +# # self.kf.Q[-1,-1] *= 0.01 # process uncertainty +# #kf.Q[dim_z:,dim_z:] *= 0.01 +# #kf.x = kf.x * 0.0 +# kf.x[:dim_z] = init_x.reshape((dim_z, 1)) + +# print(kf.P) +# return kf + + +def get_obj_ann(scene, frame, id): + ann = scene_reader.read_annotations(scene, frame) + target_ann = list(filter(lambda a: a["obj_id"]==id, ann)) + if len(target_ann) == 1: + return target_ann[0] + elif len(target_ann)> 1: + print("Warning: duplicate object id found!") + return target_ann[0] + else: + return None + +# bbox3D measurement state: x,y,z,theta,l,w,h, vx,vy,vz +def ann_to_kalman_state(ann): + return np.array([ + ann["psr"]["position"]["x"], + ann["psr"]["position"]["y"], + ann["psr"]["position"]["z"], + + ann["psr"]["scale"]["x"], + ann["psr"]["scale"]["y"], + ann["psr"]["scale"]["z"], + + ann["psr"]["rotation"]["x"], + ann["psr"]["rotation"]["y"], + ann["psr"]["rotation"]["z"], + ]) + + +def kalman_state_to_ann(proto, state): + state = np.reshape(state, -1) + return {"psr":{"position":{"x":state[0], + "y":state[1], + "z":state[2] + }, + "scale": {"x":state[3], + "y":state[4], + "z":state[5]}, + "rotation":{"x":state[6], + "y":state[7], + "z":state[8] + }, + }, + "obj_type":proto["obj_type"], + "obj_id":proto["obj_id"], + } + +def interpolate_segment(start_ann, end_ann, insert_number): + end = ann_to_kalman_state(end_ann) + start = ann_to_kalman_state(start_ann) + linear_delta = (end-start)/(insert_number+1) + return list(map(lambda i: kalman_state_to_ann(start_ann, start+linear_delta*(i+1)), range(insert_number))) + +def interpolate(annotations): + + # interpolate + N = len(annotations) + i = 0 + num_interpolate = 0 + while i+1 + + + SUSTech POINTS + + + + + + + + + + + +
+ + + + + + + + + + + \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..829a812 --- /dev/null +++ b/main.py @@ -0,0 +1,238 @@ +import random +import string + +import cherrypy +import os +import json +from jinja2 import Environment, FileSystemLoader +env = Environment(loader=FileSystemLoader('./')) + +import os +import sys +import scene_reader +from tools import check_labels as check + + +# BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +# sys.path.append(BASE_DIR) + +#sys.path.append(os.path.join(BASE_DIR, './algos')) +#import algos.rotation as rotation +from algos import pre_annotate + + +#sys.path.append(os.path.join(BASE_DIR, '../tracking')) +#import algos.trajectory as trajectory + +# extract_object_exe = "~/code/pcltest/build/extract_object" +# registration_exe = "~/code/go_icp_pcl/build/test_go_icp" + +# sys.path.append(os.path.join(BASE_DIR, './tools')) +# import tools.dataset_preprocess.crop_scene as crop_scene + +class Root(object): + @cherrypy.expose + def index(self, scene="", frame=""): + tmpl = env.get_template('index.html') + return tmpl.render() + + @cherrypy.expose + def icon(self): + tmpl = env.get_template('test_icon.html') + return tmpl.render() + + @cherrypy.expose + def ml(self): + tmpl = env.get_template('test_ml.html') + return tmpl.render() + + @cherrypy.expose + def reg(self): + tmpl = env.get_template('registration_demo.html') + return tmpl.render() + + @cherrypy.expose + def view(self, file): + tmpl = env.get_template('view.html') + return tmpl.render() + + # @cherrypy.expose + # def saveworld(self, scene, frame): + + # # cl = cherrypy.request.headers['Content-Length'] + # rawbody = cherrypy.request.body.readline().decode('UTF-8') + + # with open("./data/"+scene +"/label/"+frame+".json",'w') as f: + # f.write(rawbody) + + # return "ok" + + @cherrypy.expose + def saveworldlist(self): + + # cl = cherrypy.request.headers['Content-Length'] + rawbody = cherrypy.request.body.readline().decode('UTF-8') + data = json.loads(rawbody) + + for d in data: + scene = d["scene"] + frame = d["frame"] + ann = d["annotation"] + with open("./data/"+scene +"/label/"+frame+".json",'w') as f: + json.dump(ann, f, indent=2, sort_keys=True) + + return "ok" + + + @cherrypy.expose + @cherrypy.tools.json_out() + def cropscene(self): + rawbody = cherrypy.request.body.readline().decode('UTF-8') + data = json.loads(rawbody) + + rawdata = data["rawSceneId"] + + timestamp = rawdata.split("_")[0] + + print("generate scene") + log_file = "temp/crop-scene-"+timestamp+".log" + + cmd = "python ./tools/dataset_preprocess/crop_scene.py generate "+ \ + rawdata[0:10]+"/"+timestamp + "_preprocessed/dataset_2hz " + \ + "- " +\ + data["startTime"] + " " +\ + data["seconds"] + " " +\ + "\""+ data["desc"] + "\"" +\ + "> " + log_file + " 2>&1" + print(cmd) + + code = os.system(cmd) + + with open(log_file) as f: + log = list(map(lambda s: s.strip(), f.readlines())) + + os.system("rm "+log_file) + + return {"code": code, + "log": log + } + + + @cherrypy.expose + @cherrypy.tools.json_out() + def checkscene(self, scene): + ck = check.LabelChecker(os.path.join("./data", scene)) + ck.check() + print(ck.messages) + return ck.messages + + + # data N*3 numpy array + @cherrypy.expose + @cherrypy.tools.json_out() + def predict_rotation(self): + cl = cherrypy.request.headers['Content-Length'] + rawbody = cherrypy.request.body.readline().decode('UTF-8') + + data = json.loads(rawbody) + + return {"angle": pre_annotate.predict_yaw(data["points"])} + #return {} + + + @cherrypy.expose + @cherrypy.tools.json_out() + def auto_annotate(self, scene, frame): + print("auto annotate ", scene, frame) + return pre_annotate.annotate_file('./data/{}/lidar/{}.pcd'.format(scene,frame)) + + + + @cherrypy.expose + @cherrypy.tools.json_out() + def load_annotation(self, scene, frame): + return scene_reader.read_annotations(scene, frame) + + + @cherrypy.expose + @cherrypy.tools.json_out() + def load_ego_pose(self, scene, frame): + return scene_reader.read_ego_pose(scene, frame) + + + @cherrypy.expose + @cherrypy.tools.json_out() + def loadworldlist(self): + rawbody = cherrypy.request.body.readline().decode('UTF-8') + worldlist = json.loads(rawbody) + + anns = list(map(lambda w:{ + "scene": w["scene"], + "frame": w["frame"], + "annotation":scene_reader.read_annotations(w["scene"], w["frame"])}, + worldlist)) + + return anns + + + @cherrypy.expose + @cherrypy.tools.json_out() + def datameta(self): + return scene_reader.get_all_scenes() + + + @cherrypy.expose + @cherrypy.tools.json_out() + def scenemeta(self, scene): + return scene_reader.get_one_scene(scene) + + @cherrypy.expose + @cherrypy.tools.json_out() + def get_all_scene_desc(self): + return scene_reader.get_all_scene_desc() + + @cherrypy.expose + @cherrypy.tools.json_out() + def objs_of_scene(self, scene): + return self.get_all_objs(os.path.join("./data",scene)) + + def get_all_objs(self, path): + label_folder = os.path.join(path, "label") + if not os.path.isdir(label_folder): + return [] + + files = os.listdir(label_folder) + + files = filter(lambda x: x.split(".")[-1]=="json", files) + + + def file_2_objs(f): + with open(f) as fd: + boxes = json.load(fd) + objs = [x for x in map(lambda b: {"category":b["obj_type"], "id": b["obj_id"]}, boxes)] + return objs + + boxes = map(lambda f: file_2_objs(os.path.join(path, "label", f)), files) + + # the following map makes the category-id pairs unique in scene + all_objs={} + for x in boxes: + for o in x: + + k = str(o["category"])+"-"+str(o["id"]) + + if all_objs.get(k): + all_objs[k]['count']= all_objs[k]['count']+1 + else: + all_objs[k]= { + "category": o["category"], + "id": o["id"], + "count": 1 + } + + return [x for x in all_objs.values()] + +if __name__ == '__main__': + cherrypy.quickstart(Root(), '/', config="server.conf") +else: + application = cherrypy.Application(Root(), '/', config="server.conf") diff --git a/main2.py b/main2.py new file mode 100644 index 0000000..a916eaf --- /dev/null +++ b/main2.py @@ -0,0 +1,79 @@ +from fastapi import FastAPI, Request +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates + +from router import router + +# 1. 创建 FastAPI 应用实例 +app = FastAPI( + title="Annotation Tool API", + description="从 CherryPy 转换而来的标注工具 API", + version="2.0.0" +) + +# 2. 设置 Jinja2 模板 +# CherryPy 的 FileSystemLoader('./') 对应 FastAPI 的 directory="." +templates = Jinja2Templates(directory=".") + +# 3. 挂载静态文件 (对应 server.conf) +# app.mount("路径", StaticFiles(directory="本地目录"), name="唯一名称") +app.mount("/static", StaticFiles(directory="public"), name="public") +app.mount("/data", StaticFiles(directory="data"), name="data") +app.mount("/temp", StaticFiles(directory="temp"), name="temp") +app.mount("/views", StaticFiles(directory="views"), name="views") +app.mount("/assets", StaticFiles(directory="assets"), name="assets") + + +# 4. 定义 Pydantic 模型用于请求体验证 +# 这比直接解析 JSON 更安全、更清晰 + +# 5. 将 CherryPy 的类方法转换为 FastAPI 路由函数 + +# --- HTML 页面路由 --- + +@app.get("/icon") +def icon(request: Request): + """渲染测试图标页""" + return templates.TemplateResponse("test_icon.html", {"request": request}) + + +@app.get("/ml") +def ml(request: Request): + """渲染测试 ML 页""" + return templates.TemplateResponse("test_ml.html", {"request": request}) + + +@app.get("/reg") +def reg(request: Request): + """渲染注册演示页""" + return templates.TemplateResponse("registration_demo.html", {"request": request}) + + +@app.get("/view/{file_path:path}") +def view(request: Request, file_path: str): + """渲染查看页,:path 允许路径中包含斜杠""" + # 原始代码没有使用 file 参数,这里保持一致 + return templates.TemplateResponse("view.html", {"request": request}) + + +# --- API 接口路由 --- + +@app.get("/") +def index(request: Request, scene: str = "", frame: str = ""): + """渲染主页""" + return templates.TemplateResponse("index.html", {"request": request}) + + +app.include_router(router) +# 6. 启动服务器 (对应 if __name__ == '__main__') +# 在命令行运行: uvicorn main:app --host 0.0.0.0 --port 8081 --reload +if __name__ == "__main__": + import uvicorn + + print("Starting FastAPI server...") + # server.conf 中的 host 和 port 在这里配置 + uvicorn.run( + app, + host="0.0.0.0", + port=8081 + ) diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..0f98932 --- /dev/null +++ b/notes.md @@ -0,0 +1,9 @@ +1. the views on the left are 3 orthographical cameras installed in the scene according to the selected box. keys 5/6/7 to show/hide the installed camera frames. + +2. we use rear-view, other than front-view, so that when we adjust the position of box, this view moves +in the same direction as the top-down view, otherwise it would be very confusing (imagine the box in the +first view moves right but in the second view moves left.) + +2. the main view is a perspective camera by default. + + diff --git a/public/css/font-awesome.min.css b/public/css/font-awesome.min.css new file mode 100644 index 0000000..540440c --- /dev/null +++ b/public/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/public/css/icon.css b/public/css/icon.css new file mode 100644 index 0000000..8102dcf --- /dev/null +++ b/public/css/icon.css @@ -0,0 +1,61 @@ +.docIcon +{ + background:#eee; + background: linear-gradient(top, #ddd 0, #eee 15%, #fff 40%, #fff 70%, #eee 100%); + border:1px solid #ccc; + display:block; + width:40px; + height:56px; + position:relative; + margin:0px auto; + box-shadow:inset rgba(255,255,255,0.8) 0 1px 1px; + + text-indent:-9999em; + + border-radius:0px 15px 8px 3px; +} +.docIcon:before { + content: ""; + display: block; + position: absolute; + top: 0; + right: 0; + width: 15px; + height: 15px; + background: #ccc; + + background: -webkit-linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%); + background: -moz-linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%); + background: -o-linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%); + background: -ms-linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%); + background: linear-gradient(45deg, #fff 0, #eee 50%, #ccc 100%); + + -webkit-box-shadow: rgba(0,0,0,0.05) -1px 1px 1px, inset white 0 0 1px; + -moz-box-shadow: rgba(0,0,0,0.05) -1px 1px 1px, inset white 0 0 1px; + box-shadow: rgba(0,0,0,0.05) -1px 1px 1px, inset white 0 0 1px; + + border-bottom: 1px solid #ccc; + border-left: 1px solid #ccc; + -webkit-border-radius:3px 15px 3px 3px; + -moz-border-radius:3px 15px 3px 3px; + border-radius:3px 15px 3px 3px; +} + +.docIcon:after +{ + content:""; + display:block; + position:absolute; + left:0; + top:0; + width:60%; + margin:22px 20% 0; + height:15px; + + background:#ccc; + background: -webkit-linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%); + background: -moz-linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%); + background: -o-linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%); + background: -ms-linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%); + background:linear-gradient(top, #ccc 0, #ccc 20%, #fff 20%, #fff 40%, #ccc 40%, #ccc 60%, #fff 60%, #fff 80%, #ccc 80%, #ccc 100%); +} \ No newline at end of file diff --git a/public/css/main.css b/public/css/main.css new file mode 100644 index 0000000..719d2cc --- /dev/null +++ b/public/css/main.css @@ -0,0 +1,1083 @@ + + +.theme-dark { + --font-color: #ffffff; + --background-color: #121212; + --widget-background-color: #333333; + --window-background-color: #222222; + --widget-color: gray; + --highlight-background-color: darkgray; + --highlight-color: yellow; + --highlight-color-editor: #ffff0020; + + --highlight-background-transparent-color: #aaaaaa44; +} + +.theme-light { + --font-color: #000000; + --background-color: #ffffff; + --widget-background-color: #cccccc; + --window-background-color: #dddddd; + --widget-color: darkgray; + --highlight-background-color: gray; + --highlight-color: blue; + --highlight-color-editor: #0000ff20; + + --highlight-background-transparent-color: #22222244; +} + +html,body { + margin: 0; + height: 100%; + width: 100%; + background-color: var(--background-color); + color: var(--font-color); + /*font-family: Monospace;*/ + font-family: sans-serif, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana; + font-size: calc(1em * 0.875); + line-height: 24px; + overflow-x: hidden; + overflow-y: hidden; +} + + + +.red { + color: red; +} + + +a { + color: var(--font-color); + text-decoration: none; + /* pointer-events: auto; */ +} + +a:hover { + text-decoration: underline; +} + +button { + cursor: pointer; + /*text-transform: uppercase;*/ +} + +canvas { + display: block; +} + + + +#main-editor{ + position: absolute; + top: 0%; + left: 0%; + width: 100%; + height: 100%; +} + +#batch-editor{ + display: none; + position: absolute; + top: 0%; + left: 0%; + width: 100%; + height: 100%; +} + +#main-ui{ + position: relative; + width: 100%; + height: 100%; + /*overflow: hidden;*/ + +} + +#main-ui #content { + position:relative; + /* height: 90%; */ +} + +#container{ + position: relative; + width: 100%; + /* height: 100%; */ +} + +#global-info { + /* position: absolute; */ + top: 0px; + color: var(--font-color); + background-color: var(--widget-background-color); + width: auto; + font-size: inherit; + padding: 0px; + box-sizing: border-box; + text-align: left; + z-index: 3; /* TODO Solve this in HTML */ + font-size: smaller; + display: flex; + +} + + +#selectors { + background-color: inherit; + color: inherit; + border-width: 0; + display: inline-flex; +} + +#object-category-selector, +#object-track-id-editor, #attr-input, #scene-selector, +#frame-selector, #object-selector, +#camera-selector, #obj-ids-of-scene +{ + background-color: inherit; + color: inherit; + border-width: 0; + display: inline-block; +} + +#camera-selector { + position: relative; + padding-right: 5px; + padding-left: 5px; +} + +#camera-list { + position: absolute; + top: 100%; + left: 0%; + background-color: inherit; + width: 250px; + z-index: 1; +} + +.camera-item { + -webkit-user-select: none; /* Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+/Edge */ + user-select: none; /* Standard */ + + float: left; + padding-left: 5px; + padding-right: 5px; +} + +.camera-selected { + color: var(--highlight-color); +} + +.camera-item:hover { + background-color: var(--highlight-background-color); +} + + +#object-track-id-editor, #attr-input +{ + border-bottom-width: 1px; +} + +#attr-input +{ + margin-right: 5px; + line-height: 24px; +} +#object-category-attribute, #obj-ids-of-scene{ + display: none; +} + + +.alarm-mark:hover { + background-color: lightgray; +} + + +#static-buttons { + display: flex; +} + +#changed-mark { + position: relative; +} + +#log-button { + stroke-width: 5px; + display: none; +} + +#log-wrapper #tabs{ + display: flex; +} + +#log-wrapper .tab-button{ + color: var(--widget-color); + text-align: center; + width: 40px; + cursor:default; +} + +#log-wrapper .tab-selected{ + color: var(--font-color); + border-bottom-color: var(--font-color); + border-bottom-style: solid; + border-bottom-width: 1px; +} + +#changed-world-list-wrapper { + position: absolute; + top: 100%; + right: 0%; + display: none; + width: 400px; + background-color: var(--background-color); + line-height: 1.5; + z-index: 1; +} + +.modified-world-item { + padding: 0; + border: 0; + margin: 0; +} + +#header #buttons +{ + display: flex; + position: absolute; + right: 0; + top: 0; +} + +#config-button { + width: 20px; + height: 20px; +} + +/* .header-button { + height: 20px; + width: 20px; + padding: 2px; +} */ + + + +#maincanvas-svg { + position: absolute; + top: 0%; + width: 100%; + height:100%; + border: 0px; + padding: 0px; +} + +.image-wrapper #move-handle{ + position: absolute; + top: 0%; + left: 0%; + width: 100%; + height: 90%; +} + +.image-wrapper #header{ + position: absolute; + top: 0%; + left: 0%; + color: red; + padding-left: 5px; +} + +/* line { + stroke-width: 2; +} */ + +/* .box-svg :hover{ + stroke-width: 5px; +} */ + + +.box-svg{ + stroke-width: 3px; +} + +/* +this css can be moved to dynamic css in main.js, after obj-type css being set +for now when a box is selected, its objtype class in css is removed so the color is set by selected state. +dynamic set obj-type css has higher priority than this one. +*/ +.box-svg-selected{ + stroke: #ff00ff88; + fill: #ff00ff22; + stroke-width: 2px; +} + +.maincanvas-line{ + stroke: #00ff0088; + stroke-width: 2px; + fill: #00ff0022; +} + +.radar-points{ + stroke: #ff0000aa; + stroke-width: 2px; + fill: #ff0000aa; +} + +.radar-tracks{ + stroke: #00ff00aa; + stroke-width: 2px; + fill: #00ff00aa; + +} + +.image-wrapper { + position: absolute; + resize:both; + overflow:hidden; + top: 0%; + left: 20%; + width: 30%; + height: 30%; + padding: 0px; + margin: 0px; +} + + +#resize-handle { + position: absolute; + width: 30px; + height: 30px; + right: 0px; + bottom: 0px; +} +#resize-handle:hover{ + cursor: se-resize; +} + + +#maincanvas-c { + position: relative; + top: 0%; + left: 0%; + width: inherit; + height: 100%; + + padding: 0px; + box-sizing: border-box; + text-align: left; + align-content: left; + z-index: 1; /* TODO Solve this in HTML */ +} + + +#obj-editor{ + position: absolute; + /*color: #ff00ff; */ + background-color:var(--widget-background-color); + font-size: inherit; + padding: 0px; + box-sizing: border-box; + text-align: left; + display: none; /* defult hidden */ +} + +#obj-label { + display: none; +} + + +#attr-editor { + position: relative; +} + +#attr-selector { + position: absolute; + top: 100%; + left: 0%; + background-color: var(--widget-background-color); + width: 250px; +} + +.attr-item { + -webkit-user-select: none; /* Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+/Edge */ + user-select: none; /* Standard */ + + float: left; + padding-left: 5px; + padding-right: 5px; +} + +.attr-selected { + color: var(--highlight-color); +} + +.attr-item:hover { + background-color: var(--highlight-background-color); +} + +#camera { + width:100%; + height:100%; +} + +#main-view-grid { + position: absolute; + top: 0%; + left: 0%; + height: 100%; + width: 100%; +} + +#context-menu-wrapper { + position: absolute; + display: none; + top: 0%; + left: 0%; + height: 100%; + width: 100%; + z-index: 10; + line-height: 1.5; +} + +#context-menu, #object-context-menu, #box-editor-context-menu, #box-editor-manager-context-menu, #config-menu { + position: absolute; + display: block; + background-color: var(--widget-background-color); +} + +#new-submenu, #saveall-submenu, #play-submenu, #goto-submenu, #cm-fit-submenu, #cm-this-submenu { + display: none; + position: absolute; + /* left:100%; + top: 0%; */ + padding-left: 3px; + min-width: 150px; + background-color: var(--widget-background-color); +} + +#new-submenu { + width: 250px; +} + +.cm-new-item { + float: left; +} + +.menu-item-arrow { + display: inline; + position: absolute; + right: 0; +} + +.menu-item, .menu-nonclickable-item{ + padding-left: 10px; + padding-right: 10px; + position: relative; +} + +#context-menu,#object-context-menu,#box-editor-context-menu, #box-editor-manager-context-menu{ + width: 150px; +} +#config-menu{ + width: 300px; +} + +#cfg-experimental-submenu, #cfg-data-submenu { + display: none; + position: absolute; + right:100%; + top: 0%; + padding-left: 3px; + min-width: 400px; + background-color: var(--widget-background-color); +} + +.cfg-widget-group { + display: inline-flex; + position: absolute; + right: 10px; +} + +.cfg-widget { + background-color: var(--widget-background-color); + color: var(--font-color); +} + +.menu-button { + display: inline; + padding-left: 5px; + padding-right: 5px; +} + +.menu-button:hover { + background-color: var(--highlight-background-color); +} + +.menu-item:hover { + background-color: var(--highlight-background-color); +} + +.menu-item-icon { + display: inline; + height: 20px; + width: 20px; +} + + +.menu-item-text { + display: inline; +} + +.menu-seperator { + padding-top: 1px; + background-color:var(--widget-color); +} + + +#config-wrapper{ + display: none; + position: absolute; + top: 0%; + left: 0%; + height: 100%; + width: 100%; +} + +.dg.ac { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + z-index: 2 !important; /* TODO Solve this in HTML */ +} + + +.float-label { + color: #0000ff; + position: absolute; + display:inline-flex; +} + + +.non-selectable { + -webkit-user-select: none; /* Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+/Edge */ + user-select: none; /* Standard */ +} + +.label-obj-id-text, .label-obj-type-text, .label-obj-attr-text { + padding-left: 5px; +} + +.label-out-view { + display: none; +} + + + + + + +#batch-editor-tools-wrapper{ + width:auto; + display: flex; + justify-content: flex-end; +} + +#batch-editor-tools{ + background-color: var(--widget-background-color); + display: inherit; +} + +.obj-editor-row { + display: flex; + color: inherit; + background: inherit; + padding: inherit; + margin: 2px; +} + +#label-more { + position:relative; +} +#object-dropdown-menu { + position:absolute; + /* top: 100%; + left: 0%; */ + background-color: var(--widget-background-color); + width: 250px; + line-height: 1.5; + z-index: 1; + display: none; +} + +.ui-button { + background-color: var(--widget-background-color); + padding-left: 0; + padding-right: 0; + border-width: 1; + border-color: var(--widget-background-color); + color:var(--font-color); + height: 20px; + width: 20px; + padding: 2px; +} + +.ui-button:hover { + background-color: var(--highlight-background-color); +} + +.svg-menu-icon{ + /* pointer-events: none; */ + display: block; + width: 100%; + height: 100%; + fill:transparent; + stroke: var(--font-color); + stroke-width: 1px; +} + +.svg-button{ + /* pointer-events: none; */ + display: block; + width: 100%; + height: 100%; + fill:transparent; + stroke: var(--widget-color); + stroke-width: 1px; +} + +.alarm-mark .svg-button{ + /* pointer-events: none; */ + display: block; + width: 100%; + height: 100%; + fill:transparent; + stroke: red; + stroke-width: 1px; +} + +#main-box-editor-wrapper { + position: absolute; + top: 0%; + left: 0%; + width: 0%; + height: 100%; +} + + + +#main-box-editor-wrapper .v-buttons-wrapper { + position: absolute; + bottom: 0%; + right: 0%; + background-color: var(--widget-background-color);; + margin: 1px; + display: none; +} + + +#main-box-editor-wrapper #sub-views { + display: inline-block; + position: relative; + width: 320px; + height: 100%; + resize: both; + overflow: hidden; +} + +#batch-box-editor{ + position: absolute; + top: 0%; + left: 0%; + width: 100%; + height: 100%; + border: 0px; +} + +#batch-box-editor #sub-views { + display: inline-block; + position: relative; + width: 130px; + height: 450px; + /* resize: none; + overflow: hidden; */ +} + +#batch-box-editor .selected { + background-color: var(--highlight-color-editor); + color:var(--highlight-color); +} + +#batch-box-editor .v-buttons-wrapper { + display: none; + position: absolute; + bottom: 0%; + right: 0%; + background-color:var(--widget-background-color); + margin: 0; +} + +#batch-box-editor .ui-button { + display: block; + background-color:var(--widget-background-color); + padding-left: 0; + padding-right: 0; + border-width: 1; + border-color: var(--widget-background-color); + color: yellow; + padding: 0; + border: 0; + margin: 0; + height: auto; +} + +#batch-box-editor .ui-button:hover { + background-color: var(--highlight-background-color);; +} + + +#box-info{ + position: absolute; + top: 0px; + /*color: #ffff00; */ + /* background-color: var(--widget-background-color);; */ + width: 100%; + padding: 0px; + box-sizing: border-box; + text-align: left; + /*z-index: 3; /* TODO Solve this in HTML */ + font-size: x-small; +} + +.selected #box-info{ + background-color: var(--highlight-background-transparent-color); +} + +.view-manipulator { + width: 100%; + /*background-color: #00000000;*/ + border-width: 1px; + position: relative; + /* resize:both; */ + overflow: hidden; +} + +#z-view-manipulator { + height: 35%; +} + +#y-view-manipulator { + height: 20%; +} + +#x-view-manipulator { + height: 20%; +} + + +#focuscanvas { + color: #ffff00; + width: 100%; + height: 25%; + padding: 0px; + box-sizing: border-box; + text-align: left; + align-content: left; + z-index: 1; /* TODO Solve this in HTML */ +} + +#view-manipulator:hover{ + /* border-color: yellow; */ + +} + + +.ew-handle:hover{ + cursor: ew-resize; +} + +.ns-handle:hover{ + cursor: ns-resize; +} + + +.nw-handle:hover{ + cursor: nw-resize; +} +.ne-handle:hover{ + cursor: ne-resize; +} + +.sw-handle:hover{ + cursor: sw-resize; +} + +.se-handle:hover{ + cursor: se-resize; +} + +.grab-handle:hover{ + cursor: move; +} + +.v-table-wrapper { + display: none; +} + + +td { + padding: 0px; +} + + + +.svg-line{ + stroke-dasharray: 3,3; + stroke: #00000000; + stroke-width: 1px; + stroke-opacity: 0.6; +} + + +.subview-svg, .subview-obj-size{ + position: absolute; + width: 100%; + height: 100%; + top: 0%; + left: 0%; +} + +.subview-obj-size .obj-vertical-size { + position: absolute; + top: 50%; + left: 0%; +} + + +.origin-point-indicator{ + position: relative; + stroke: #ff0000; + stroke-width: 1px; + stroke-opacity: 0.7; + fill-opacity: 0.0; +} + +#select-box { + display: none; + position: absolute; + border: 1px solid #55aaff; + background-color: rgba(75, 160, 255, 0.3); + +} + +#main-view-svg{ + position: absolute; + top: 0%; + left: 0%; + height: 100%; + width: 100%; +} +#grid-lines-wrapper{ + position: relative; +} +.grid-line { + + stroke-dasharray: 3,3; + stroke: gray; + stroke-width: 1px; + stroke-opacity: 0.2; +} + + + +/* */ + +.popup-window-wrapper #view { + position: relative; + left: 20%; + top: 20%; + + height: 60%; + width: 60%; + background-color: var(--window-background-color); + border: 1px; + border-color: gray; + resize: both; + overflow: hidden; + z-index: 4; +} + + +.popup-window-wrapper #header { + background-color: var(--widget-background-color); + min-width: 400px; /*minwidth of dialog*/ +} + +.popup-window-wrapper #title { + padding-left: 5px; +} + + +.popup-window-wrapper #buttons { + display: inline-flex; + float: right; +} + +.popup-window-wrapper #btn-restore { + display: none; +} + + +.popup-window-wrapper { + position: absolute; + left: 0%; + top: 0%; + width: 100%; + height: 100%; + display: none; + +} + + +/* trajectory */ +#object-track-svg { + width: 100%; + height:100%; + border: 0px; + padding: 0px; +} + +#svg-arrows { + stroke: red; + fill:none; +} + + +#svg-scaler { + stroke: gray; + fill:none; +} + + +#track-ego-car { + stroke: green; +} + +.track-label { + width: 150px; + height: 28px; + /* font-size: 18px; */ + display: none; +} + +.scaler-label{ + width: 150px; + height: 28px; +} + + +.object-track-current-frame { + stroke: purple; +} + +.one-track:hover { + color: yellow; + stroke: yellow; + z-index: 10; +} + +.one-track:hover .track-label{ + display: inherit; +} + +.track-wrapper { + stroke: #00000000; + fill: #00000000; +} + + + +/* info */ +#info-wrapper { + background-color: #88888888; +} + +#info-wrapper #view { + height: fit-content; + width: fit-content; + resize: none; +} + +#info-content { + padding-left: 5px; + height: fit-content; + overflow-wrap: break-word; +} + +#info-wrapper #view #info-bottom-buttons{ + float: right; + padding: 5px; +} + + +/*crop scene */ +#crop-scene-wrapper { + width: 0%; + height: 0%; +} + +#crop-scene-wrapper #view { + left: 200px; + top: 200px; + + height: 400px; /*60%;*/ + width: 600px; /*60%; */ +} + + +#crop-scene-wrapper #content { + padding-left: 5px; +} + + +#log-wrapper { + width: 0%; + height: 0%; + display: inherit; +} + +#log-wrapper #view { + left: 400px; + top: 400px; + height: 400px; /*60%;*/ + width: 600px; /*60%; */ +} + +#log-wrapper #content-logs,#content-errors { + height: 100%; + width: 100%; + line-height: 1.5; + overflow-y: auto; +} + + + +.log-object-frame-id{ + cursor: pointer; +} + +#move-handle-wrapper { + position: absolute; + display: none; + cursor: move; + top: 0%; + left: 0%; + height: 100%; + width: 100%; + z-index: 10; +} \ No newline at end of file diff --git a/public/css/reg.css b/public/css/reg.css new file mode 100644 index 0000000..2e8745f --- /dev/null +++ b/public/css/reg.css @@ -0,0 +1,29 @@ +body { + margin: 0; + background-color: #000; + color: #fff; + font-family: Monospace; + font-size: 13px; + line-height: 24px; +} + + + +#info { + position: absolute; + top: 0px; + color: #ffff00; + font-size: 10px; + text-align: left; + z-index: 1; /* TODO Solve this in HTML */ +} + +td { + text-align: right; +} + +.selectBox { + border: 1px solid #55aaff; + background-color: rgba(75, 160, 255, 0.3); + position: fixed; +} \ No newline at end of file diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..a7c32a0 --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icon2.png b/public/icon2.png new file mode 100644 index 0000000..cb23fb4 Binary files /dev/null and b/public/icon2.png differ diff --git a/public/js/annotation.js b/public/js/annotation.js new file mode 100644 index 0000000..3c8255d --- /dev/null +++ b/public/js/annotation.js @@ -0,0 +1,591 @@ + + +import * as THREE from './lib/three.module.js'; +import {globalObjectCategory} from './obj_cfg.js'; +import {saveWorldList} from "./save.js" +import { intersect } from './util.js'; + + +function Annotation(sceneMeta, world, frameInfo){ + this.world = world; + this.data = this.world.data; + //this.coordinatesOffset = this.world.coordinatesOffset; + this.boxes_load_time = 0; + this.frameInfo = frameInfo; + + + this.modified = false; + this.setModified = function(){ + this.modified=true; + + if (pointsGlobalConfig.autoSave) + { + saveWorldList([this.world]); + } + }; + this.resetModified = function(){this.modified=false;}; + + + this.sort_boxes = function(){ + this.boxes = this.boxes.sort(function(x,y){ + return x.position.y - y.position.y; + }); + }; + this.findBoxByTrackId = function(id){ + if (this.boxes){ + let box = this.boxes.find(function(x){ + return x.obj_track_id == id; + }); + return box; + } + + return null; + }; + + this.findIntersectedBoxes = function(box){ + return this.boxes.filter(b=>b!=box).filter(b=>intersect(box, b)); + }; + + this.preload = function(on_preload_finished){ + this.on_preload_finished = on_preload_finished; + this.load_annotation((boxes)=>this.proc_annotation(boxes)); + }; + + + + this.go_cmd_received = false; + this.webglScene = null; + this.on_go_finished = null; + this.go = function(webglScene, on_go_finished){ + this.webglScene = webglScene; + + if (this.preloaded){ + + //this.boxes.forEach(b=>this.webglScene.add(b)); + if (this.data.cfg.color_obj != "no"){ + this.color_boxes(); + } + + if (on_go_finished) + on_go_finished(); + } else { + this.go_cmd_received = true; + this.on_go_finished = on_go_finished; + } + }; + + + // internal funcs below + this._afterPreload = function(){ + this.preloaded = true; + console.log("annotation preloaded"); + + if (this.on_preload_finished){ + this.on_preload_finished(); + } + if (this.go_cmd_received){ + this.go(this.webglScene, this.on_go_finished); + } + }; + + + this.unload = function(){ + if (this.boxes){ + this.boxes.forEach((b)=>{ + //this.webglGroup.remove(b); + + if (b.boxEditor) + b.boxEditor.detach(); + }); + } + }; + + this.deleteAll = function(){ + this.remove_all_boxes(); + }; + this.boxToAnn = function(box){ + let ann = { + psr: { + position:{ + x: box.position.x, + y: box.position.y, + z: box.position.z, + }, + scale:{ + x: box.scale.x, + y: box.scale.y, + z: box.scale.z, + }, + rotation:{ + x:box.rotation.x, + y:box.rotation.y, + z:box.rotation.z, + }, + }, + obj_type: box.obj_type, + obj_id: String(box.obj_track_id), + obj_attr: box.obj_attr, + //vertices: vertices, + }; + return ann; + }; + + this.toBoxAnnotations = function(){ + let anns = this.boxes.map((b)=>{ + //var vertices = psr_to_xyz(b.position, b.scale, b.rotation); + let ann = this.boxToAnn(b); + + if (b.annotator) + ann.annotator = b.annotator; + + if (b.follows) + ann.follows = b.follows; + return ann; + }); + + anns.sort((a,b)=>a.obj_id- b.obj_id); + + return anns; + }; + + + + // to real-world position (no offset) + this.ann_to_vector_global = function(box) { + let posG = this.world.lidarPosToScene(box.position); + let rotG = this.world.lidarRotToScene(box.rotation); + + return [ + posG.x - this.world.coordinatesOffset[0], posG.y-this.world.coordinatesOffset[1], posG.z-this.world.coordinatesOffset[2], + rotG.x, rotG.y, rotG.z, + box.scale.x, box.scale.y, box.scale.z, + ]; + + }; + + // real-world position to ann + this.vector_global_to_ann = function(v) + { + let posG = new THREE.Vector3(v[0]+this.world.coordinatesOffset[0], + v[1]+this.world.coordinatesOffset[1], + v[2]+this.world.coordinatesOffset[2]); + let rotG = new THREE.Euler(v[3],v[4],v[5]); + + let rotL = this.world.sceneRotToLidar(rotG); + let posL = this.world.scenePosToLidar(posG); + + return { + position: {x: posL.x, y: posL.y, z: posL.z}, + rotation: {x: rotL.x, y: rotL.y, z: rotL.z}, + scale: {x: v[6], y: v[7], z: v[8]} + }; + + }; + + + // this.vector_to_ann = function(v){ + // return { + // position:{ + // x:v[0],// + this.coordinatesOffset[0], + // y:v[1],// + this.coordinatesOffset[1], + // z:v[2],// + this.coordinatesOffset[2], + // }, + + + // rotation:{ + // x:v[3], + // y:v[4], + // z:v[5], + // }, + + // scale:{ + // x:v[6], + // y:v[7], + // z:v[8], + // }, + + // }; + // }; + + this.remove_all_boxes = function(){ + if (this.boxes){ + this.boxes.forEach((b)=>{ + this.webglGroup.remove(b); + this.world.data.dbg.free(); + b.geometry.dispose(); + b.material.dispose(); + b.world = null; + b.boxEditor = null; + }); + + this.boxes = []; + } + else{ + console.error("destroy empty world!") + } + }; + + this.new_bbox_cube=function(color){ + + var h = 0.5; + + var body = [ + //top + -h,h,h, h,h,h, + h,h,h, h,-h,h, + h,-h,h, -h,-h,h, + -h,-h,h, -h, h, h, + + //botom + -h,h,-h, h,h,-h, + h,h,-h, h,-h,-h, + h,-h,-h, -h,-h,-h, + -h,-h,-h, -h, h, -h, + + // vertical lines + -h,h,h, -h,h,-h, + h,h,h, h,h,-h, + h,-h,h, h,-h,-h, + -h,-h,h, -h,-h,-h, + + //direction + h, 0, h, 1.5*h, 0, h, + //h/2, -h, h+0.1, h, 0, h+0.1, + //h/2, h, h+0.1, h, 0, h+0.1, + + //side direction + // h, h/2, h, h, h, 0, + // h, h/2, -h, h, h, 0, + // h, 0, 0, h, h, 0, + + ]; + + + this.world.data.dbg.alloc(); + + var bbox = new THREE.BufferGeometry(); + bbox.setAttribute( 'position', new THREE.Float32BufferAttribute(body, 3 ) ); + + if (!color){ + color = 0x00ff00; + } + + /* + https://threejs.org/docs/index.html#api/en/materials/LineBasicMaterial + linewidth is 1, regardless of set value. + */ + + + var material = new THREE.LineBasicMaterial( { color: color, linewidth: 1, opacity: this.data.cfg.box_opacity, transparent: true } ); + var box = new THREE.LineSegments( bbox, material ); + + box.scale.x=1.8; + box.scale.y=4.5; + box.scale.z=1.5; + box.name="bbox"; + box.obj_type="car"; + + //box.computeLineDistances(); + + + + return box; + }; + + this.createCuboid = function(pos, scale, rotation, obj_type, track_id, obj_attr){ + let mesh = this.new_bbox_cube(parseInt("0x"+globalObjectCategory.get_obj_cfg_by_type(obj_type).color.slice(1))); + mesh.position.x = pos.x; + mesh.position.y = pos.y; + mesh.position.z = pos.z; + + mesh.scale.x = scale.x; + mesh.scale.y = scale.y; + mesh.scale.z = scale.z; + + mesh.rotation.x = rotation.x; + mesh.rotation.y = rotation.y; + mesh.rotation.z = rotation.z; + + mesh.obj_track_id = track_id; //tracking id + mesh.obj_type = obj_type; + mesh.obj_attr = obj_attr; + mesh.obj_local_id = this.get_new_box_local_id(); + + mesh.world = this.world; + + return mesh; + }; + /* + pos: offset position, after transformed + */ + + this.add_box=function(pos, scale, rotation, obj_type, track_id, obj_attr){ + + let mesh = this.createCuboid(pos, scale, rotation, obj_type, track_id, obj_attr) + + this.boxes.push(mesh); + this.sort_boxes(); + + this.webglGroup.add(mesh); + + return mesh; + }; + + this.load_box = function(box){ + + this.webglGroup.add(box); + }; + + this.unload_box = function(box){ + + this.webglGroup.remove(box); + }; + + this.remove_box=function(box){ + this.world.data.dbg.free(); + box.geometry.dispose(); + box.material.dispose(); + //selected_box.dispose(); + this.boxes = this.boxes.filter(function(x){return x !=box;}); + }; + + this.set_box_opacity=function(box_opacity){ + this.boxes.forEach(function(x){ + x.material.opacity = box_opacity; + }); + }; + + this.translate_box_position=function(pos, theta, axis, delta){ + switch (axis){ + case 'x': + pos.x += delta*Math.cos(theta); + pos.y += delta*Math.sin(theta); + break; + case 'y': + pos.x += delta*Math.cos(Math.PI/2 + theta); + pos.y += delta*Math.sin(Math.PI/2 + theta); + break; + case 'z': + pos.z += delta; + break; + + } + }; + + this.find_boxes_inside_rect = function(x,y,w,h, camera){ + + let selected_boxes_by_rect = []; + + if (!this.boxes) + return selected_boxes_by_rect; + + + var p = new THREE.Vector3(); + + for (var i=0; i< this.boxes.length; i++){ + let box_center = this.boxes[i].position; + + let pw = this.world.lidarPosToScene(box_center); + p.set(pw.x, pw.y, pw.z); + p.project(camera); + p.x = p.x/p.z; + p.y = p.y/p.z; + //console.log(p); + if ((p.x > x) && (p.x < x+w) && (p.y>y) && (p.ythis.webglGroup.add(b)); + + this.world.webglGroup.add(this.webglGroup); + + this.boxes_load_time = new Date().getTime(); + console.log(this.boxes_load_time, this.frameInfo.scene, this.frameInfo.frame, "loaded boxes ", this.boxes_load_time - this.create_time, "ms"); + + this.sort_boxes(); + + this._afterPreload(); + }; + + this.load_annotation=function(on_load){ + if (this.data.cfg.disableLabels){ + on_load([]); + }else { + var xhr = new XMLHttpRequest(); + // we defined the xhr + var _self = this; + xhr.onreadystatechange = function () { + if (this.readyState != 4) return; + + if (this.status == 200) { + let ann = _self.frameInfo.anno_to_boxes(this.responseText); + on_load(ann); + } + + // end of state change: it can be after some time (async) + }; + + xhr.open('GET', "/load_annotation"+"?scene="+this.frameInfo.scene+"&frame="+this.frameInfo.frame, true); + xhr.send(); + } + }; + + this.reloadAnnotation=function(done){ + this.load_annotation(ann=>{ + this.reapplyAnnotation(ann, done); + }); + }; + + + this.reapplyAnnotation = function(boxes, done){ + // these boxes haven't attached a world + //boxes = this.transformBoxesByOffset(boxes); + + // mark all old boxes + this.boxes.forEach(b=>{b.delete=true;}); + + let pendingBoxList=[]; + + boxes.forEach(nb=>{ // nb is annotation format, not a true box + let old_box = this.boxes.find(function(x){ + return x.obj_track_id == nb.obj_id && x.obj_track_id != "" && nb.obj_id != "" && x.obj_type == nb.obj_type;; + }); + + if (old_box){ + // found + // update psr + delete old_box.delete; // unmark delete flag + old_box.position.set(nb.psr.position.x, nb.psr.position.y, nb.psr.position.z); + old_box.scale.set(nb.psr.scale.x, nb.psr.scale.y, nb.psr.scale.z); + old_box.rotation.set(nb.psr.rotation.x, nb.psr.rotation.y, nb.psr.rotation.z); + old_box.obj_attr = nb.obj_attr; + old_box.annotator = nb.annotator; + old_box.changed=false; // clear changed flag. + + }else{ + // not found + let box=this.createOneBoxByAnn(nb); + pendingBoxList.push(box); + } + }); + + // delete removed + let toBeDelBoxes = this.boxes.filter(b=>b.delete); + toBeDelBoxes.forEach(b=>{ + if (b.boxEditor){ + + b.boxEditor.detach("donthide"); + } + + this.webglGroup.remove(b); + + this.remove_box(b); + }) + + pendingBoxList.forEach(b=>{ + this.boxes.push(b); + }) + + + //todo, restore point color + //todo, update imagecontext, selected box, ... + //refer to normal delete operation + // re-color again + this.world.lidar.recolor_all_points(); + + this.color_boxes(); + + // add new boxes + pendingBoxList.forEach(b=>{ + this.webglGroup.add(b); + }) + + + this.resetModified(); + + if (done) + done(); + + } + + this.createOneBoxByAnn = function(annotation){ + let b = annotation; + + let mesh = this.createCuboid(b.psr.position, + b.psr.scale, + b.psr.rotation, + b.obj_type, + b.obj_id, + b.obj_attr); + + if (b.annotator){ + mesh.annotator = b.annotator; + } + + if (b.follows) + mesh.follows = b.follows; + + return mesh; + }; + + this.createBoxes = function(annotations){ + return annotations.map((b)=>{ + return this.createOneBoxByAnn(b); + }); + }; + + + this.box_local_id = 0; + this.get_new_box_local_id=function(){ + var ret = this.box_local_id; + this.box_local_id+=1; + return ret; + }; + + + this.color_box = function(box) + { + if (this.data.cfg.color_obj == "category" || this.data.cfg.color_obj == "no") + { + let color = globalObjectCategory.get_color_by_category(box.obj_type); + box.material.color.r=color.x; + box.material.color.g=color.y; + box.material.color.b=color.z; + } + else + { + + let color = globalObjectCategory.get_color_by_id(box.obj_track_id); + box.material.color.r=color.x; + box.material.color.g=color.y; + box.material.color.b=color.z; + } + } + + this.color_boxes = function() + { + this.boxes.forEach(box=>{ + this.color_box(box); + }) + } +} + + +export{Annotation} \ No newline at end of file diff --git a/public/js/auto-adjust.js b/public/js/auto-adjust.js new file mode 100644 index 0000000..62483c6 --- /dev/null +++ b/public/js/auto-adjust.js @@ -0,0 +1,463 @@ + +import {transpose, matmul2, euler_angle_to_rotate_matrix_3by3,normalizeAngle } from "./util.js"; +import { logger } from "./log.js"; + +// todo: this module needs a proper name + +function AutoAdjust(boxOp, mouse, header){ + this.boxOp = boxOp, + this.mouse = mouse; + this.header = header; + var marked_object = null; + + // mark bbox, which will be used as reference-bbox of an object. + this.mark_bbox=function(box){ + if (box){ + this.marked_object = { + frame: box.world.frameInfo.frame, + scene: box.world.frameInfo.scene, + ann: box.world.annotation.boxToAnn(box), + } + + logger.log(`selected reference objcet ${this.marked_object}`); + + this.header.set_ref_obj(this.marked_object); + } + }; + + this.followStaticObjects = function(box) { + let world = box.world; + let staticObjects = world.annotation.boxes. + filter(b=>b!=box && b.obj_attr && b.obj_attr.search('static')>=0). + map(refObj=>{ + let coord = euler_angle_to_rotate_matrix_3by3(refObj.rotation); + let trans = transpose(coord, 3); + let p = [box.position.x - refObj.position.x, + box.position.y - refObj.position.y, + box.position.z - refObj.position.z]; + let relativePos = matmul2(trans, p, 3); + let relativeRot = { + x: normalizeAngle(box.rotation.x - refObj.rotation.x), + y: normalizeAngle(box.rotation.y - refObj.rotation.y), + z: normalizeAngle(box.rotation.z - refObj.rotation.z), + }; + + + let distance = Math.sqrt(relativePos[0]*relativePos[0] + relativePos[1]*relativePos[1] + relativePos[2]*relativePos[2]); + return { + obj_track_id: refObj.obj_track_id, + relativePos, + relativeRot, + distance + + } + }); + + let worldList = box.world.data.worldList; + //let saveList = []; + worldList.forEach(w=>{ + if (w === box.world){ + //current frame + return; + } + + let existedBox = w.annotation.boxes.find(b=>b.obj_track_id == box.obj_track_id); + if (existedBox && !existedBox.annotator) + { + // have same objects annotated. + // if its generated by machine, lets overwrite it + return; + } + + let candPoseSets = staticObjects.map(refObj=>{ + + let refObjInW = w.annotation.boxes.find(b=>b.obj_track_id == refObj.obj_track_id); + if (!refObjInW){ + // not found refobj in this world, give up + return null; + } + + let relativePos = refObj.relativePos; + let relativeRot = refObj.relativeRot; + + let coord = euler_angle_to_rotate_matrix_3by3(refObjInW.rotation); + + let rp = matmul2(coord, relativePos, 3); + let newObjPos = { + x: refObjInW.position.x + rp[0], + y: refObjInW.position.y + rp[1], + z: refObjInW.position.z + rp[2], + }; + + let newObjRot = { + x: normalizeAngle(refObjInW.rotation.x + relativeRot.x), + y: normalizeAngle(refObjInW.rotation.y + relativeRot.y), + z: normalizeAngle(refObjInW.rotation.z + relativeRot.z) + }; + + + + return { + distance: refObj.distance, + weight: Math.exp(-refObj.distance * (refObjInW.annotator?1:0.1)), + position: newObjPos, + rotation: newObjRot, + }; + }); + + candPoseSets = candPoseSets.filter(p=>!!p); + + + if (candPoseSets.length == 0) { + return; + } + + // calculate mean pos/rot + let denorm = candPoseSets.reduce((a,b)=>a+b.weight, 0); + + let newObjPos = {x:0, y:0, z:0}; + let newObjRot = {x:0, y:0, z:0, cosZ: 0, sinZ:0}; + candPoseSets.forEach(p=>{ + newObjPos.x += p.position.x * p.weight; + newObjPos.y += p.position.y * p.weight; + newObjPos.z += p.position.z * p.weight; + + newObjRot.x += p.rotation.x * p.weight; + newObjRot.y += p.rotation.y * p.weight; + //newObjRot.z += p.rotation.z * p.weight; + newObjRot.cosZ += Math.cos(p.rotation.z) * p.weight; + newObjRot.sinZ += Math.sin(p.rotation.z) * p.weight; + }); + + newObjPos.x /= denorm; + newObjPos.y /= denorm; + newObjPos.z /= denorm; + newObjRot.x /= denorm; + newObjRot.y /= denorm; + newObjRot.cosZ /= denorm; + newObjRot.sinZ /= denorm; + newObjRot.z = Math.atan2(newObjRot.sinZ, newObjRot.cosZ); + + + // ignor distant objects + + if (pointsGlobalConfig.ignoreDistantObject){ + let objDistance = Math.sqrt(newObjPos.x * newObjPos.x + newObjPos.y * newObjPos.y + newObjPos.z * newObjPos.z); + + if ((box.scale.z < 2 && objDistance > 100) || objDistance > 150) + { + return; + } + } + + // apply + if (existedBox){ + existedBox.position.x = newObjPos.x; + existedBox.position.y = newObjPos.y; + existedBox.position.z = newObjPos.z; + + existedBox.rotation.x = newObjRot.x; + existedBox.rotation.y = newObjRot.y; + existedBox.rotation.z = newObjRot.z; + + existedBox.scale.x = box.scale.x; + existedBox.scale.y = box.scale.y; + existedBox.scale.z = box.scale.z; + + existedBox.annotator="S"; + + + logger.log(`modified box in ${w}`); + } else{ + let newBox = w.annotation.add_box(newObjPos, + box.scale, + newObjRot, + box.obj_type, + box.obj_track_id, + box.obj_attr); + newBox.annotator="S"; + + w.annotation.load_box(newBox); + logger.log(`inserted box in ${w}`); + } + + console.log("added box in ", w.frameInfo.frame); + //saveList.push(w); + w.annotation.setModified(); + + + }); + + }; + + this.followsRef = function(box){ + //find ref object in current frame + let world = box.world; + let refObj = world.annotation.boxes.find(b=>b.obj_track_id == this.marked_object.ann.obj_id); + if (refObj){ + console.log("found ref obj in current frame"); + world.annotation.setModified() + + //compute relative position + // represent obj in coordinate system of refobj + + let coord = euler_angle_to_rotate_matrix_3by3(refObj.rotation); + let trans = transpose(coord, 3); + let p = [box.position.x - refObj.position.x, + box.position.y - refObj.position.y, + box.position.z - refObj.position.z]; + const relativePos = matmul2(trans, p, 3); + const relativeRot = { + x: box.rotation.x - refObj.rotation.x, + y: box.rotation.y - refObj.rotation.y, + z: box.rotation.z - refObj.rotation.z, + }; + + let worldList = box.world.data.worldList; + //let saveList = []; + worldList.forEach(w=>{ + if (w === box.world){ + //current frame + return; + } + + let existedBox = w.annotation.boxes.find(b=>b.obj_track_id == box.obj_track_id); + + if (existedBox && !existedBox.annotator) + { + // have same objects annotated. + // if its generated by machine, lets overwrite it + return; + } + + let refObjInW = w.annotation.boxes.find(b=>b.obj_track_id == refObj.obj_track_id); + if (!refObjInW){ + // not found refobj in this world, give up + return; + } + + let coord = euler_angle_to_rotate_matrix_3by3(refObjInW.rotation); + + let rp = matmul2(coord, relativePos, 3); + let newObjPos = { + x: refObjInW.position.x + rp[0], + y: refObjInW.position.y + rp[1], + z: refObjInW.position.z + rp[2], + }; + + let newObjRot = { + x: refObjInW.rotation.x + relativeRot.x, + y: refObjInW.rotation.y + relativeRot.y, + z: refObjInW.rotation.z + relativeRot.z + }; + + if (existedBox){ + existedBox.position.x = newObjPos.x; + existedBox.position.y = newObjPos.y; + existedBox.position.z = newObjPos.z; + + existedBox.rotation.x = newObjRot.x; + existedBox.rotation.y = newObjRot.y; + existedBox.rotation.z = newObjRot.z; + + existedBox.scale.x = box.scale.x; + existedBox.scale.y = box.scale.y; + existedBox.scale.z = box.scale.z; + + existedBox.annotator="F"; + existedBox.follows = { + obj_track_id: refObj.obj_track_id, + relative_position: { + x: relativePos[0], + y: relativePos[1], + z: relativePos[2], + }, + relative_rotation: relativeRot, + }; + + logger.log(`modified box in ${w}`); + } else{ + let newBox = w.annotation.add_box(newObjPos, + box.scale, + newObjRot, + box.obj_type, + box.obj_track_id, + box.obj_attr); + newBox.annotator="F"; + newBox.follows = { + obj_track_id: refObj.obj_track_id, + relative_position: { + x: relativePos[0], + y: relativePos[1], + z: relativePos[2], + }, + relative_rotation: relativeRot, + }; + + w.annotation.load_box(newBox); + logger.log(`inserted box in ${w}`); + } + + console.log("added box in ", w.frameInfo.frame); + //saveList.push(w); + w.annotation.setModified(); + }); + + //saveWorldList(saveList); + } + }; + + this.syncFollowers = function(box){ + let world = box.world; + let allFollowers = world.annotation.boxes.filter(b=>b.follows && b.follows.obj_track_id === box.obj_track_id); + + if (allFollowers.length == 0){ + console.log("no followers"); + return; + } + + let refObj = box; + let coord = euler_angle_to_rotate_matrix_3by3(refObj.rotation); + + + allFollowers.forEach(fb=>{ + let relpos = [fb.follows.relative_position.x, + fb.follows.relative_position.y, + fb.follows.relative_position.z, + ]; + + let rp = matmul2(coord, relpos, 3); + + fb.position.x = refObj.position.x + rp[0]; + fb.position.y = refObj.position.y + rp[1]; + fb.position.z = refObj.position.z + rp[2]; + + fb.rotation.x = refObj.rotation.x + fb.follows.relative_rotation.x; + fb.rotation.y = refObj.rotation.y + fb.follows.relative_rotation.y; + fb.rotation.z = refObj.rotation.z + fb.follows.relative_rotation.z; + }); + }; + + this.paste_bbox=function(pos, add_box){ + + if (!pos) + pos = this.marked_object.ann.psr.position; + else + pos.z = this.marked_object.ann.psr.position.z; + + return add_box(pos, this.marked_object.ann.psr.scale, this.marked_object.ann.psr.rotation, + this.marked_object.ann.obj_type, this.marked_object.ann.obj_id, this.marked_object.ann.obj_attr); + }; + + + // this.auto_adjust_bbox=function(box, done, on_box_changed){ + + // saveWorld(function(){ + // do_adjust(box, on_box_changed); + // }); + // let _self =this; + // function do_adjust(box, on_box_changed){ + // console.log("auto adjust highlighted bbox"); + + // var xhr = new XMLHttpRequest(); + // // we defined the xhr + + // xhr.onreadystatechange = function () { + // if (this.readyState != 4) return; + + // if (this.status == 200) { + // console.log(this.responseText) + // console.log(box.position); + // console.log(box.rotation); + + + // var trans_mat = JSON.parse(this.responseText); + + // var rotation = Math.atan2(trans_mat[4], trans_mat[0]) + box.rotation.z; + // var transform = { + // x: -trans_mat[3], + // y: -trans_mat[7], + // z: -trans_mat[11], + // } + + + + // /* + // cos sin x + // -sin cos y + // */ + // var new_pos = { + // x: Math.cos(-rotation) * transform.x + Math.sin(-rotation) * transform.y, + // y: -Math.sin(-rotation) * transform.x + Math.cos(-rotation) * transform.y, + // z: transform.z, + // }; + + + // box.position.x += new_pos.x; + // box.position.y += new_pos.y; + // box.position.z += new_pos.z; + + + + // box.scale.x = marked_object.scale.x; + // box.scale.y = marked_object.scale.y; + // box.scale.z = marked_object.scale.z; + + // box.rotation.z -= Math.atan2(trans_mat[4], trans_mat[0]); + + // console.log(box.position); + // console.log(box.rotation); + + // on_box_changed(box); + + // _self.header.mark_changed_flag(); + + // if (done){ + // done(); + // } + // } + + // // end of state change: it can be after some time (async) + // }; + + // xhr.open('GET', + // "/auto_adjust"+"?scene="+marked_object.scene + "&"+ + // "ref_frame=" + marked_object.frame + "&" + + // "object_id=" + marked_object.obj_track_id + "&" + + // "adj_frame=" + data.world.frameInfo.frame, + // true); + // xhr.send(); + // } + // }; + + this.smart_paste=function(selected_box, add_box, on_box_changed){ + var box = selected_box; + if (!box){ + let sceneP = this.mouse.get_mouse_location_in_world() + // trans pos to world local pos + //let pos = this.data.world.scenePosToLidar(sceneP); + box = this.paste_bbox(pos, add_box); + } + else if (this.marked_object){ + box.scale.x = this.marked_object.ann.psr.scale.x; + box.scale.y = this.marked_object.ann.psr.scale.y; + box.scale.z = this.marked_object.ann.psr.scale.z; + } + + // this.auto_adjust_bbox(box, + // function(){saveWorld();}, + // on_box_changed); + + // this.header.mark_changed_flag(); + + + + this.boxOp.auto_rotate_xyz(box, null, null, + on_box_changed, + "noscaling"); + }; + +} + + +export {AutoAdjust} \ No newline at end of file diff --git a/public/js/auto_annotate.js b/public/js/auto_annotate.js new file mode 100644 index 0000000..8f51f82 --- /dev/null +++ b/public/js/auto_annotate.js @@ -0,0 +1,30 @@ +import { globalObjectCategory } from "./obj_cfg.js"; + + + +function autoAnnotate(world, done, alg){ + var xhr = new XMLHttpRequest(); + // we defined the xhr + xhr.onreadystatechange = function () { + if (this.readyState != 4) return; + + if (this.status == 200) { + let anns = JSON.parse(this.responseText); + + anns.map(a=>a.obj_type = globalObjectCategory.guess_obj_type_by_dimension(a.psr.scale)); + + // load annotations + world.annotation.reapplyAnnotation(anns); + + if (done) + done(); + } + }; + + xhr.open('GET', "/auto_annotate?"+"scene="+world.frameInfo.scene+"&frame="+world.frameInfo.frame, true); + + xhr.send(); +} + + +export {autoAnnotate} \ No newline at end of file diff --git a/public/js/aux_lidar.js b/public/js/aux_lidar.js new file mode 100644 index 0000000..570a362 --- /dev/null +++ b/public/js/aux_lidar.js @@ -0,0 +1,403 @@ +import * as THREE from './lib/three.module.js'; +import { PCDLoader } from './lib/PCDLoader.js'; +import { matmul, euler_angle_to_rotate_matrix_3by3} from "./util.js" + + +//todo: clean arrows + +function AuxLidar(sceneMeta, world, frameInfo, auxLidarName){ + this.world = world; + this.frameInfo = frameInfo; + this.name = auxLidarName; + this.sceneMeta = sceneMeta; + this.coordinatesOffset = world.coordinatesOffset; + + this.showPointsOnly = true; + this.showCalibBox = false; + //this.cssStyleSelector = this.sceneMeta.calib.aux_lidar[this.name].cssstyleselector; + this.color = this.sceneMeta.calib.aux_lidar[this.name].color; + + + if (!this.color) + { + this.color = [ + this.world.data.cfg.point_brightness, + this.world.data.cfg.point_brightness, + this.world.data.cfg.point_brightness, + ]; + } + + this.lidar_points = null; // read from file, centered at 0 + this.elements = null; // geometry points + + this.preloaded = false; + this.loaded = false; + + + this.go_cmd_received = false; + this.webglScene = null; + this.on_go_finished = null; + this.go = function(webglScene, on_go_finished){ + this.webglScene = webglScene; + + if (this.preloaded){ + if (this.elements){ + this.webglScene.add(this.elements.points); + + + + if (this.showCalibBox) + this.webglScene.add(this.calib_box); + } + + this.loaded = true; + if (on_go_finished) + on_go_finished(); + } + + //anyway we save go cmd + { + this.go_cmd_received = true; + this.on_go_finished = on_go_finished; + } + }; + + this.showCalibBox = function(){ + this.showCalibBox = true; + this.webglScene.add(this.calib_box); + }; + + this.hideCalibBox = function(){ + this.showCalibBox = false; + this.webglScene.remove(this.calib_box); + }; + + this.get_unoffset_lidar_points = function(){ + if (this.elements){ + let pts = this.elements.points.geometry.getAttribute("position").array; + return pts.map((p,i)=>p-this.world.coordinatesOffset[i %3]); + } + else{ + return []; + } + }; + + // todo: what if it's not preloaded yet + this.unload = function(keep_box){ + if (this.elements){ + this.webglScene.remove(this.elements.points); + if (!this.showPointsOnly) + this.elements.arrows.forEach(a=>this.webglScene.remove(a)); + + if (!keep_box) + this.webglScene.remove(this.calib_box); + } + this.loaded = false; + }; + + // todo: its possible to remove points before preloading, + this.deleteAll = function(keep_box){ + if (this.loaded){ + this.unload(); + } + + if (this.elements){ + //this.scene.remove(this.points); + this.world.data.dbg.free(); + + if (this.elements.points) + { + this.elements.points.geometry.dispose(); + this.elements.points.material.dispose(); + } + + if (this.elements.arrows) + { + this.elements.arrows.forEach(a=>{ + this.world.data.dbg.free(); + a.geometry.dispose(); + a.material.dispose(); + }) + } + + this.elements = null; + } + + if (!keep_box && this.calib_box){ + this.world.data.dbg.free(); + this.calib_box.geometry.dispose(); + this.calib_box.material.dispose(); + this.calib_box = null; + } + }; + + this.filterPoints = function(position){ + let filtered_position = []; + + if (pointsGlobalConfig.enableFilterPoints) + { + for(let i = 0; i <= position.length; i+=3) + { + if (position[i+2] <= pointsGlobalConfig.filterPointsZ) + { + filtered_position.push(position[i]); + filtered_position.push(position[i+1]); + filtered_position.push(position[i+2]); + + } + } + } + + return filtered_position; + }; + + this.preload = function(on_preload_finished){ + + this.on_preload_finished = on_preload_finished; + + var loader = new PCDLoader(); + + var _self = this; + loader.load( this.frameInfo.get_aux_lidar_path(this.name), + //ok + function ( pcd ) { + var position = pcd.position; + + + //_self.points_parse_time = new Date().getTime(); + //console.log(_self.points_load_time, _self.frameInfo.scene, _self.frameInfo.frame, "parse pionts ", _self.points_parse_time - _self.create_time, "ms"); + _self.lidar_points = position; + + // add one box to calibrate lidar with lidar + _self.calib_box = _self.createCalibBox(); + + // install callback for box changing + _self.calib_box.on_box_changed = ()=>{ + _self.move_lidar(_self.calib_box); + }; + + //position = _self.transformPointsByOffset(position); + position = _self.move_points(_self.calib_box); + + + + let elements = _self.buildGeometry(position); + + _self.elements = elements; + //_self.points_backup = mesh; + + _self._afterPreload(); + + }, + + // on progress, + function(){}, + + // on error + function(){ + //error + console.log("load lidar failed."); + _self._afterPreload(); + }, + + // on file loaded + function(){ + //_self.points_readfile_time = new Date().getTime(); + //console.log(_self.points_load_time, _self.frameInfo.scene, _self.frameInfo.frame, "read file ", _self.points_readfile_time - _self.create_time, "ms"); + } + ); + }; + + // internal funcs below + this._afterPreload = function(){ + this.preloaded = true; + console.log(`lidar ${this.auxLidarName} preloaded`); + if (this.on_preload_finished){ + this.on_preload_finished(); + } + if (this.go_cmd_received){ + this.go(this.webglScene, this.on_go_finished); + } + }; + + this.createCalibBox = function(){ + if (this.sceneMeta.calib.aux_lidar && this.sceneMeta.calib.aux_lidar[this.name]){ + return this.world.annotation.createCuboid( + { + x: this.sceneMeta.calib.aux_lidar[this.name].translation[0] + this.coordinatesOffset[0], + y: this.sceneMeta.calib.aux_lidar[this.name].translation[1] + this.coordinatesOffset[1], + z: this.sceneMeta.calib.aux_lidar[this.name].translation[2] + this.coordinatesOffset[2], + }, + {x:0.5, y:0.5, z:0.5}, + { + x: this.sceneMeta.calib.aux_lidar[this.name].rotation[0], + y: this.sceneMeta.calib.aux_lidar[this.name].rotation[1], + z: this.sceneMeta.calib.aux_lidar[this.name].rotation[2], + }, + "lidar", + this.name); + + }else { + return this.world.annotation.createCuboid( + {x: this.coordinatesOffset[0], + y: this.coordinatesOffset[1], + z: this.coordinatesOffset[2]}, + {x:0.5, y:0.5, z:0.5}, + {x:0,y:0,z:0}, + "lidar", + this.name); + } + }; + + this.buildPoints = function(position){ + // build geometry + this.world.data.dbg.alloc(); + let geometry = new THREE.BufferGeometry(); + if ( position.length > 0 ) + geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( position, 3 ) ); + + + let pointColor = this.color; + let color=[]; + for (var i =0; i< position.length; i+=3){ + + color.push(pointColor[0]); + color.push(pointColor[1]); + color.push(pointColor[2]); + } + + geometry.addAttribute( 'color', new THREE.Float32BufferAttribute(color, 3 ) ); + + geometry.computeBoundingSphere(); + + // build material + let pointSize = this.sceneMeta.calib.aux_lidar[this.name].point_size; + if (!pointSize) + pointSize = 1; + + let material = new THREE.PointsMaterial( { size: pointSize, vertexColors: THREE.VertexColors } ); + //material.size = 2; + material.sizeAttenuation = false; + + // build mesh + let mesh = new THREE.Points( geometry, material ); + mesh.name = "lidar"; + + return mesh; + }; + + + this.buildGeometry = function(position){ + let points = this.buildPoints(position); + + return { + points: points, + }; + }; + + this.move_points = function(box){ + let points = this.lidar_points; + let trans = euler_angle_to_rotate_matrix_3by3(box.rotation); + let rotated_points = matmul(trans, points, 3); + let translation=[box.position.x, box.position.y, box.position.z]; + let translated_points = rotated_points.map((p,i)=>{ + return p + translation[i % 3]; + }); + + let filtered_position = this.filterPoints(translated_points); + return filtered_position; + }; + + + + this.move_lidar= function(box){ + + let translated_points = this.move_points(box); + + let elements = this.buildGeometry(translated_points); + + // remove old points + this.unload(true); + this.deleteAll(true); + + this.elements = elements; + //_self.points_backup = mesh; + if (this.go_cmd_received) // this should be always true + { + this.webglScene.add(this.elements.points); + if (!this.showPointsOnly) + this.elements.arrows.forEach(a=>this.webglScene.add(a)); + } + }; +} + +function AuxLidarManager(sceneMeta, world, frameInfo){ + this.lidarList = []; + + if (world.data.cfg.enableAuxLidar && sceneMeta.aux_lidar){ + let lidars = []; + + for (let r in sceneMeta.calib.aux_lidar){ + if (!sceneMeta.calib.aux_lidar[r].disable) + lidars.push(r); + } + + this.lidarList = lidars.map(name=>{ + return new AuxLidar(sceneMeta, world, frameInfo, name); + }); + } + + this.getAllBoxes = function() + { + if (this.showCalibBox) + { + return this.lidarList.map(r=>r.calib_box); + } + else + { + return []; + } + }; + + this.preloaded = function(){ + for (let r in this.lidarList){ + if (!this.lidarList[r].preloaded) + return false; + } + return true; + }; + + this.go = function(webglScene, on_go_finished){ + this.lidarList.forEach(r=>r.go(webglScene, on_go_finished)); + }; + + this.preload = function(on_preload_finished){ + this.lidarList.forEach(r=>r.preload(on_preload_finished)); + }; + + this.unload = function(){ + this.lidarList.forEach(r=>r.unload()); + }; + + this.deleteAll = function(){ + this.lidarList.forEach(r=>r.deleteAll()); + }; + + this.getOperableObjects = function(){ + return this.lidarList.flatMap(r=>r.getOperableObjects()); + }; + + this.showCalibBox = false; + this.showCalibBox = function(){ + this.showCalibBox = true; + this.lidarList.forEach(r=>r.showCalibBox()); + }; + + this.hideCalibBox = function(){ + this.showCalibBox = false; + this.lidarList.forEach(r=>r.hideCalibBox()); + } +}; + + +export {AuxLidarManager} \ No newline at end of file diff --git a/public/js/box_editor.js b/public/js/box_editor.js new file mode 100644 index 0000000..76cb159 --- /dev/null +++ b/public/js/box_editor.js @@ -0,0 +1,1839 @@ + +import {ProjectiveViewOps} from "./side_view_op.js" +import {BoxImageContext} from "./image.js"; +import {saveWorldList, reloadWorldList} from "./save.js" +import {objIdManager} from "./obj_id_list.js" +import { globalKeyDownManager } from "./keydown_manager.js"; +import{ml} from "./ml.js"; +import { BooleanKeyframeTrack } from "./lib/three.module.js"; +import { checkScene } from "./error_check.js"; +import { logger } from "./log.js"; + + +/* +2 ways to attach and edit a box +1) attach/detach +2) setTarget, tryAttach, resetTarget, this is only for batch-editor-manager +*/ +function BoxEditor(parentUi, boxEditorManager, viewManager, cfg, boxOp, + func_on_box_changed, func_on_box_remove, name){ + + this.boxEditorManager = boxEditorManager; + this.parentUi = parentUi; + this.name=name; + let uiTmpl = document.getElementById("box-editor-ui-template"); + let tmpui = uiTmpl.content.cloneNode(true); //sub-views + + parentUi.appendChild(tmpui); + this.ui = parentUi.lastElementChild; + this.boxInfoUi = this.ui.querySelector("#box-info"); + + this.viewManager = viewManager; + this.boxOp = boxOp; + this.boxView = this.viewManager.addBoxView(this.ui); //this.editorUi.querySelector("#sub-views") + this.projectiveViewOps = new ProjectiveViewOps( + this.ui, //this.editorUi.querySelector("#sub-views"), + cfg, + this, + this.boxView.views, + this.boxOp, + func_on_box_changed, + func_on_box_remove); + + this.focusImageContext = new BoxImageContext(this.ui.querySelector("#focuscanvas")); + + this.pseudoBox = { + position: {x: 0, y: 0, z: 0}, + rotation: {x: 0, y: 0, z: 0}, + scale: {x: 1, y: 1, z: 1}, + }; + + this.copyPseudoBox = function(b) + { + this.pseudoBox.position.x = b.position.x; + this.pseudoBox.position.y = b.position.y; + this.pseudoBox.position.z = b.position.z; + + this.pseudoBox.rotation.x = b.rotation.x; + this.pseudoBox.rotation.y = b.rotation.y; + this.pseudoBox.rotation.z = b.rotation.z; + + this.pseudoBox.scale.x = b.scale.x; + this.pseudoBox.scale.y = b.scale.y; + this.pseudoBox.scale.z = b.scale.z; + }; + + this.isInBatchMode = function(){ + return !!this.boxEditorManager; + } + + this.target = {}; + + this.setTarget = function(world, objTrackId, objType){ + this.target = { + world: world, + objTrackId: objTrackId, + objType: objType, + } + + if (this.isInBatchMode()){ + + this.pseudoBox.world = world; + this.boxView.attachBox(this.pseudoBox); + } + + this.tryAttach(); + this.ui.style.display="inline-block"; + this.updateInfo(); + }; + + this.setIndex = function(index){ + this.index = index; // index as of in all editors. + }; + + this.setSelected = function(selected, eventId){ + if (selected) + { + this.ui.className = "selected"; + this.selected = true; + this.selectEventId = eventId; + } + else + { + if (!eventId || (this.selectEventId == eventId)) + { + // cancel only you selected. + this.ui.className = ""; + this.selected = false; + this.selectEventId = null; + } + } + + + }; + + // this.onContextMenu = (event)=>{ + // if (this.boxEditorManager) // there is no manager for box editor in main ui + // this.boxEditorManager.onContextMenu(event, this); + // }; + + // this.ui.oncontextmenu = this.onContextMenu; + + this.resetTarget = function(){ + if (this.target.world){ + //unload if it's not the main world + + + // if (this.target.world !== this.target.world.data.world) + // this.target.world.unload(); + } + + this.detach(); + this.target = {}; + //this.ui.style.display="none"; + }; + + this.tryAttach = function(){ + // find target box, attach to me + if (this.target && this.target.world){ + + let box = this.target.world.annotation.findBoxByTrackId(this.target.objTrackId); + if (box){ + this.attachBox(box); + } + } + }; + + + /* + the projectiveView tiggers zoomratio changing event. + editormanager broaccasts it to all editors + */ + this._setViewZoomRatio = function(viewIndex, ratio){ + this.boxView.views[viewIndex].zoom_ratio = ratio; + }; + + this.updateViewZoomRatio = function(viewIndex, ratio){ + //this.upate(); + if (this.boxEditorManager) + this.boxEditorManager.updateViewZoomRatio(viewIndex, ratio); + else{ + this._setViewZoomRatio(viewIndex, ratio); + this.update(); + //this.viewManager.render(); + } + }; + + this.onOpCmd = function(cmd){ + if (this.boxEditorManager) + this.boxEditorManager.onOpCmd(cmd, this); + else{ + this.executeOpCmd(cmd); + } + }; + + this.executeOpCmd = function(cmd){ + + if (!this.box) + { + return; + } + + if (cmd.op == "translate") + { + for (let axis in cmd.params.delta) + { + this.boxOp.translate_box(this.box, axis, cmd.params.delta[axis]); + //this.boxOp.translate_box(this.box, "y", delta.y); + } + + func_on_box_changed(this.box); + } + } + + this.box = null; + this.attachBox = function(box){ + if (this.box && this.box !== box){ + this.box.boxEditor=null; + console.log("detach box editor"); + //todo de-highlight box + } + + this.box = null; + this.show(); + + if (box){ + box.boxEditor = this; + this.box=box; + //this.boxOp.highlightBox(box); + this.boxView.attachBox(box); + this.projectiveViewOps.attachBox(box); + this.focusImageContext.updateFocusedImageContext(box); + + //this.update(); + this.updateInfo(); + // this.boxView.render(); + + if (this.isInBatchMode()){ + this.boxEditorManager.onBoxChanged(this); + } + } + }; + + this.detach = function(dontHide){ + if (this.box){ + if (this.box.boxEditor === this){ + this.box.boxEditor = null; + } + //this.boxOp.unhighlightBox(this.box); + //todo de-highlight box + this.projectiveViewOps.detach(); + this.boxView.detach(); + + if (this.isInBatchMode()){ + this.copyPseudoBox(this.box); + this.boxView.attachBox(this.pseudoBox); + + } + + this.focusImageContext.clear_canvas(); + this.box = null; + } + + if (!dontHide) + this.hide(); + }; + + + + + this.hide = function(){ + this.ui.style.display="none"; + + + // this is a hack, if we don't have manager, this is the main editor + // hide parent ui + // todo, add a pseudo manager, hide itself when child hide + if (!this.boxEditorManager){ + this.parentUi.style.display="none"; + } + } + this.show = function(){ + this.ui.style.display="";//"inline-block"; + if (!this.boxEditorManager){ + this.parentUi.style.display=""; + } + } + + this.onBoxChanged=function(){ + + this.projectiveViewOps.update_view_handle(); + this.focusImageContext.updateFocusedImageContext(this.box); + this.boxView.onBoxChanged(); + + // mark + delete this.box.annotator; // human annotator doesn't need a name + delete this.box.follows; + this.box.changed = true; + + // don't mark world's change flag, for it's hard to clear it. + + // inform boxEditorMgr to transfer annotation to other frames. + if (this.boxEditorManager) + this.boxEditorManager.onBoxChanged(this); + + this.updateInfo(); + + //this.boxView.render(); + }; + + this.onDelBox = function(){ + let box = this.box; + this.detach("donthide"); + }; + + // windowresize... + this.update = function(dontRender=false){ + + if (this.boxView){ + this.boxView.onBoxChanged(dontRender); + + // this.boxView.updateCameraRange(this.box); + // this.boxView.updateCameraPose(this.box); + + // if (!dontRender) + // this.boxView.render(); + } + + // boxview should be updated for pseudobox. + + if (this.box === null) + return; + + this.projectiveViewOps.update_view_handle(); + + + + // this is not needed somtime + this.focusImageContext.updateFocusedImageContext(this.box); + + // should we update info? + this.updateInfo(); + }; + + this.updateInfo = function(){ + let info = "" + if (this.target.world){ + info += String(this.target.world.frameInfo.frame); + + if (this.box && this.box.annotator) + info += ","+this.box.annotator; + + // if (this.box && this.box.changed) + // info += " *"; + } + + this.boxInfoUi.innerHTML = info; + }; + + this.updateBoxDimension = function(){ + + }; + + + this.resize = function(width, height) + { + // if (height + "px" == this.ui.style.height && width + "px" == this.ui.style.width) + // { + // return; + // } + + this.ui.style.width = width + "px"; + this.ui.style.height = height + "px"; + this.boxView.render(); + }; + + this.setResize = function(option){ + this.ui.style.resize=option; + this.ui.style["z-index"] = "0"; + + if (option == 'both') + { + + this.lastSize= { + width: 0, + height: 0, + }; + + this.resizeObserver = new ResizeObserver(elements=>{ + + let rect = elements[0].contentRect; + console.log("sub-views resized.", rect); + + if (rect.height == 0 || rect.width == 0) + { + return; + } + + if (rect.height != this.lastSize.height || rect.width != this.lastSize.width) + { + // viewManager will clear backgound + // so this render is effectiveless. + //this.boxView.render(); + + // save + + if (this.boxEditorManager) // there is no manager for box editor in main ui + { + pointsGlobalConfig.setItem("batchModeSubviewSize", {width: rect.width, height: rect.height}); + this.boxEditorManager.onSubViewsResize(rect.width, rect.height); + } + else{ + this.boxView.render(); + } + + //save + this.lastSize.width = rect.width; + this.lastSize.height = rect.height; + } + + + + }); + + this.resizeObserver.observe(this.ui); + } + } + + + +} + + +//parentUi #batch-box-editor +function BoxEditorManager(parentUi, viewManager, objectTrackView, + cfg, boxOp, globalHeader, contextMenu, configMenu, + func_on_box_changed, func_on_box_remove, func_on_annotation_reloaded){ + this.viewManager = viewManager; + this.objectTrackView = objectTrackView; + this.boxOp = boxOp; + this.activeIndex = 0; + this.editorList = []; + this.cfg = cfg; + this.globalHeader = globalHeader; + this.contextMenu = contextMenu; + this.parentUi = parentUi; //#batch-box-editor + this.boxEditorGroupUi = parentUi.querySelector("#batch-box-editor-group"); + this.boxEditorHeaderUi = parentUi.querySelector("#batch-box-editor-header"); + this.batchSize = cfg.batchModeInstNumber; + //this.configMenu = configMenu; + + this.activeEditorList = function(){ + return this.editorList.slice(0, this.activeIndex); + }; + + this.editingTarget = { + data: null, + sceneMeta: "", + objTrackId: "", + frame:"", + frameIndex: NaN, + }; + + this.onExit = null; + // frame specifies the center frame to edit + + + // this.parentUi.addEventListener("contextmenu", event=>{ + // this.contextMenu.show("boxEditorManager", event.clientX, event.clientY, this); + // event.stopPropagation(); + // event.preventDefault(); + // }) + + this.onSubViewsResize = function(width, height) + { + this.viewManager.mainView.clearView(); + this.editorList.forEach(e=>{ + e.resize(width, height); + }); + + //this.viewManager.render(); + }; + + this.calculateBestSubviewSize=function(batchSize) + { + let parentRect = this.parentUi.getBoundingClientRect(); + let headerRect = this.boxEditorHeaderUi.getBoundingClientRect(); + let editorsGroupRect = this.boxEditorGroupUi.getBoundingClientRect(); + + let availableHeight = parentRect.height - headerRect.height; + let availableWidth = parentRect.width; + + if (availableHeight ==0 || availableWidth ==0) + { + this.batchSizeUpdated=true; + return; + } + + + let defaultBoxWidth=130; + let defaultBoxHeight=450; + + let rows = 1; + let w = availableWidth/Math.ceil(batchSize/rows); + let h = availableHeight/rows; + let cost = Math.abs((w/h) - (defaultBoxWidth/defaultBoxHeight)); + let minCost = cost; + let bestRows = rows; + while(true) + { + rows +=1; + + let w = Math.floor(availableWidth/Math.ceil(batchSize/rows)); + let h = Math.floor(availableHeight/rows); + let cost = Math.abs((w/h) - (defaultBoxWidth/defaultBoxHeight)); + + if (cost < minCost) + { + minCost = cost; + bestRows = rows; + } + else{ + break; + } + } + + //bestRows + pointsGlobalConfig.batchModeSubviewSize = { + width: Math.floor(availableWidth/Math.ceil(batchSize/bestRows)), + height: Math.floor(availableHeight/bestRows), + } + } + + this.setBatchSize = function(batchSize) + { + this.calculateBestSubviewSize(batchSize); + + this.batchSize = batchSize; + if (this.parentUi.style.display != "none") + { + + + this.edit( this.editingTarget.data, + this.editingTarget.sceneMeta, + this.editingTarget.frame, + this.editingTarget.objTrackId, + this.editingTarget.objType + ); + } + }; + + this.onWindowResize = function() + { + this.setBatchSize(this.batchSize); + }; + + this.edit = function(data, sceneMeta, frame, objTrackId, objType, onExit){ + + this.show(); + this.reset(); + + if (this.batchSizeUpdated) + { + this.batchSizeUpdated=false; + this.calculateBestSubviewSize(this.batchSize); + } + + + if (onExit){ + // next/prev call will not update onExit + this.onExit = onExit; + } + let sceneName = sceneMeta.scene; + + this.editingTarget.data = data; + this.editingTarget.sceneMeta = sceneMeta; + this.editingTarget.objTrackId = objTrackId; + + + this.editingTarget.objType = objType; + + this.editingTarget.frame = frame; + + // this.parentUi.querySelector("#object-track-id-editor").value=objTrackId; + // this.parentUi.querySelector("#object-category-selector").value=objType; + + + let centerIndex = sceneMeta.frames.findIndex(f=>f==frame); + this.editingTarget.frameIndex = centerIndex; + + if (centerIndex < 0){ + centerIndex = 0; + } + + + let startIndex = Math.max(0, centerIndex - this.batchSize/2); + + if(startIndex > 0) + { + if (startIndex + this.batchSize > sceneMeta.frames.length) + { + startIndex -= startIndex + this.batchSize - sceneMeta.frames.length; + + if (startIndex < 0) + { + startIndex = 0; + } + } + } + + + + let frames = sceneMeta.frames.slice(startIndex, startIndex+this.batchSize); + + + //this.viewManager.mainView.clearView(); + + frames.forEach(async (frame, editorIndex)=>{ + let world = await data.getWorld(sceneName, frame); + let editor = this.addEditor(); + //editor.setTarget(world, objTrackId, objType); + editor.setIndex(editorIndex); + editor.resize(pointsGlobalConfig.batchModeSubviewSize.width, pointsGlobalConfig.batchModeSubviewSize.height); + + if (this.editingTarget.frame == frame){ + editor.setSelected(true); + } + + data.activate_world(world, + ()=>{ + //editor.tryAttach(); + + editor.setTarget(world, objTrackId, objType); + + // + //this.viewManager.render(); + }, + true); + }); + + + // set obj selector + this.globalHeader.setObject(objTrackId); + }; + + this.onContextMenu = function(event, boxEditor) + { + this.firingBoxEditor = boxEditor; + + if (boxEditor.selected) + { + // ok + } + else + { + this.getSelectedEditors().forEach(e=>e.setSelected(false)); + boxEditor.setSelected(true); + } + + this.contextMenu.show("boxEditor", event.clientX, event.clientY, this); + event.stopPropagation(); + event.preventDefault(); + }; + + this.parentUi.oncontextmenu = (event)=>{ + let ed = this.getEditorByMousePosition(event.clientX, event.clientY); + + this.onContextMenu(event, ed); + }; + + + this.handleContextMenuKeydownEvent = function(event, menuPos) + { + switch(event.key){ + case 's': + this.activeEditorList().forEach(e=>e.setSelected(true)); + return true; + break; + case 'a': + this.autoAnnotateSelectedFrames(); + break; + case 'f': + this.finalizeSelectedBoxes(); + break; + case 'd': + this.deleteSelectedBoxes(menuPos); + break; + case 'e': + this.interpolateSelectedFrames(); + break; + case 'g': + this.gotoThisFrame(); + break; + case 't': + this.showTrajectory(); + break; + default: + return true; + } + + return false; + }; + + this.delayUpdateAutoGeneratedBoxesTimer = null; + + this.updateAutoGeneratedBoxes = function(){ + + if (this.delayUpdateAutoGeneratedBoxesTimer) + { + clearTimeout(this.delayUpdateAutoGeneratedBoxesTimer) + } + + this.delayUpdateAutoGeneratedBoxesTimer = setTimeout(async ()=>{ + + if (this.cfg.autoUpdateInterpolatedBoxes){ + await this.updateInterpolatedBoxes(); + } + + await this.updatePseudoBoxes(); + }, + 500); + }; + + this.updateInterpolatedBoxes = async function(){ + + let editorList = this.activeEditorList(); + let applyIndList = editorList.map(e=>e.box&&e.box.annotator=="i"); + + let boxList = editorList.map(e=>e.box); + let worldList = editorList.map(e=>e.target.world); + await this.boxOp.interpolateAsync(worldList, boxList, applyIndList); + //this.activeEditorList().forEach(e=>e.tryAttach()); + + this.globalHeader.updateModifiedStatus(); + //this.viewManager.render(); + editorList.forEach(e=>{ + if (e.box&&e.box.annotator=="i"){ + e.boxView.onBoxChanged(); + } + }); + + }; + + + this.updatePseudoBoxes = async function(){ + let editorList = this.activeEditorList(); + let boxList = editorList.map(e=>e.box); + let anns = boxList.map(b=> b?b.world.annotation.ann_to_vector_global(b):null); + + let ret = await ml.interpolate_annotation(anns); + + editorList.forEach((e,i)=>{ + if (!e.box){ + let ann = e.target.world.annotation.vector_global_to_ann(ret[i]); + e.copyPseudoBox(ann); + e.boxView.onBoxChanged(); + } + }); + }; + + + + // manager + this.onBoxChanged = function(e){ + this.updateAutoGeneratedBoxes(); + // + }; + + + let onBoxChangedInBatchMode = function(box) + { + if (box.boxEditor) // if in batch mode with less than 40 windows, some box don't have editor attached. + box.boxEditor.update(); //render. + + box.world.annotation.setModified(); + }; + + + this.finalizeSelectedBoxes = function() + { + this.getSelectedEditors().forEach(e=>{ + + if (e.box){ + if (e.box.annotator) + { + delete e.box.annotator; + func_on_box_changed(e.box); + //e.box.world.annotation.setModified(); + e.updateInfo(); + } + } + }); + + this.globalHeader.updateModifiedStatus();; + }; + + this.interpolateSelectedFrames = function(){ + let applyIndList = this.activeEditorList().map(e=>false); //all shoud be applied. + let selectedEditors = this.getSelectedEditors(); + + // if interpolate only one box, remove it if exist. + // no matter who is the annotator. + if (selectedEditors.length == 1) + { + if (selectedEditors[0].box) + { + func_on_box_remove(selectedEditors[0].box, true); + } + } + + selectedEditors.forEach(e=>applyIndList[e.index] = true); + this.interpolate(applyIndList); + + + this.updateAutoGeneratedBoxes(); + }; + + this.deleteEmptyBoxes = function() + { + let editors = this.activeEditorList(); + editors.forEach(e=>{ + if (e.box) + { + if (e.box.world.lidar.get_box_points_number(e.box) == 0) + { + func_on_box_remove(e.box, true); + } + } + }); + + this.updateAutoGeneratedBoxes(); + }; + + this.deleteIntersectedBoxes = function(){ + + let editors = this.getSelectedEditors(); + editors.forEach(e=>{ + if (e.box) + { + let boxes = e.box.world.annotation.findIntersectedBoxes(e.box); + + boxes.forEach(b=>{ + func_on_box_remove(b, true); + }); + + onBoxChangedInBatchMode(e.box); + } + }); + }; + + this.deleteSelectedBoxes = function(infoBoxPos) + { + let selectedEditors = this.getSelectedEditors(); + + if (selectedEditors.length >= 2) + { + window.editor.infoBox.show( + "Confirm", + `Delete ${selectedEditors.length} selected boxes?`, + ["yes", "no"], + (btn)=>{ + if (btn == "yes") + { + + selectedEditors.forEach(e=>{ + if (e.box) + func_on_box_remove(e.box, true); + }); + + this.updateAutoGeneratedBoxes(); + } + }, + infoBoxPos + ); + } + else{ + selectedEditors.forEach(e=>{ + if (e.box) + func_on_box_remove(e.box, true) + }); + + this.updateAutoGeneratedBoxes(); + } + }; + + this.autoAnnotateSelectedFrames = function() + { + let applyIndList = this.activeEditorList().map(e=>false); //all shoud be applied. + this.getSelectedEditors().forEach(e=>applyIndList[e.index] = true); + this.autoAnnotate(applyIndList); + }; + + this.onOpCmd = function(cmd, firingEditor){ + + firingEditor.executeOpCmd(cmd); + + if (this.cfg.linkEditorsInBatchMode) + { + let editors = this.getSelectedEditors(); + + if (editors.includes(firingEditor)) + { + editors.filter(x=>x!=firingEditor).forEach(e=>{ + if (e.box && !e.box.annotator) + { + e.executeOpCmd(cmd); + } + }); + } + } + }; + + + this.handleContextMenuEvent = function(event) + { + console.log(event.currentTarget.id, event.type); + switch(event.currentTarget.id) + { + + // manager + case 'cm-increase-box-editor': + this.batchSize += 1; + this.edit( + this.editingTarget.data, + this.editingTarget.sceneMeta, + this.editingTarget.sceneMeta.frame, + this.editingTarget.objTrackId, + this.editingTarget.objType + ); + break; + + case 'cm-decrease-box-editor': + this.batchSize -= 1; + this.edit( + this.editingTarget.data, + this.editingTarget.sceneMeta, + this.editingTarget.sceneMeta.frame, + this.editingTarget.objTrackId, + this.editingTarget.objType + ); + break; + + /////////////////////// obj instance // + + case 'cm-select-all': + this.activeEditorList().forEach(e=>e.setSelected(true)); + return false;//don't hide context menu + break; + case 'cm-select-all-previous': + this.activeEditorList().forEach(e=> e.setSelected(e.index <= this.firingBoxEditor.index)); + return false;//don't hide context menu + break; + case 'cm-select-all-next': + this.activeEditorList().forEach(e=> e.setSelected(e.index >= this.firingBoxEditor.index)); + return false;//don't hide context menu + break + + case 'cm-delete': + this.deleteSelectedBoxes( {x: event.clientX, y: event.clientY}); + break; + case 'cm-delete-empty-boxes': + this.deleteEmptyBoxes(); + break; + case 'cm-delete-intersected-boxes': + this.deleteIntersectedBoxes(); + break; + case 'cm-interpolate': + this.interpolateSelectedFrames(); + break; + + case 'cm-auto-annotate': + this.autoAnnotateSelectedFrames(); + break; + + case 'cm-auto-annotate-wo-rotation': + { + let applyIndList = this.activeEditorList().map(e=>false); //all shoud be applied. + this.getSelectedEditors().forEach(e=>applyIndList[e.index] = true); + this.autoAnnotate(applyIndList, "dontrotate"); + } + break; + + case 'cm-fit-moving-direction': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + + let currentBox = e.box; + let estimatedRot = boxOp.estimate_rotation_by_moving_direciton(currentBox); + + if (estimatedRot){ + currentBox.rotation.z = estimatedRot.z; + func_on_box_changed(currentBox); + } + }); + + this.updateAutoGeneratedBoxes(); + + break; + case 'cm-fit-size': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + + boxOp.fit_size(e.box, ['x','y']); + func_on_box_changed(e.box); + }); + + this.updateAutoGeneratedBoxes(); + break; + case 'cm-fit-position': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + boxOp.auto_rotate_xyz(e.box, null, + null,//{x:false, y:false, z:true}, + func_on_box_changed, //onBoxChangedInBatchMode, + "noscaling", "dontrotate"); + }); + + this.updateAutoGeneratedBoxes(); + break; + case 'cm-fit-rotation': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + boxOp.auto_rotate_xyz(e.box, null, + null, + func_on_box_changed,//onBoxChangedInBatchMode, // + "noscaling"); + + }); + + this.updateAutoGeneratedBoxes(); + break; + case 'cm-fit-bottom': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + boxOp.fit_bottom(e.box); + + func_on_box_changed(e.box); + }); + + this.updateAutoGeneratedBoxes(); + break; + case 'cm-fit-top': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + boxOp.fit_top(e.box); + + func_on_box_changed(e.box); + }); + + this.updateAutoGeneratedBoxes(); + break; + case 'cm-fit-left': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + boxOp.fit_left(e.box); + + func_on_box_changed(e.box); + }); + + this.updateAutoGeneratedBoxes(); + break; + case 'cm-fit-right': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + boxOp.fit_right(e.box); + + func_on_box_changed(e.box); + }); + + this.updateAutoGeneratedBoxes(); + break; + case 'cm-fit-rear': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + boxOp.fit_rear(e.box); + + func_on_box_changed(e.box); + }); + + this.updateAutoGeneratedBoxes(); + break; + case 'cm-fit-front': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + boxOp.fit_front(e.box); + + func_on_box_changed(e.box); + }); + + this.updateAutoGeneratedBoxes(); + break; + case 'cm-reverse-direction': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + if (e.box.rotation.z > 0){ + e.box.rotation.z -= Math.PI; + }else{ + e.box.rotation.z += Math.PI; + } + + onBoxChangedInBatchMode(e.box); + }); + + //this.viewManager.render(); + + this.updateAutoGeneratedBoxes(); + + break; + case 'cm-reset-roll-pitch': + this.getSelectedEditors().forEach(e=>{ + if (!e.box) + return; + e.box.rotation.x =0; + e.box.rotation.y =0; + e.update('dontrender'); + e.box.world.annotation.setModified(); + + onBoxChangedInBatchMode(e.box); + }); + + //this.viewManager.render(); + this.updateAutoGeneratedBoxes(); + + break; + case 'cm-show-trajectory': + this.showTrajectory(); + break; + + case 'cm-check': + { + let scene = this.editingTarget.sceneMeta.scene; + checkScene(scene); + logger.show(); + logger.errorBtn.onclick(); + } + break; + + case 'cm-finalize': + this.finalizeSelectedBoxes(); + break; + + case 'cm-sync-size': + editor.data.worldList.forEach(w=>{ + let box = w.annotation.boxes.find(b=>b.obj_track_id == this.firingBoxEditor.target.objTrackId); + if (box && box !== this.firingBoxEditor.box){ + box.scale.x = this.firingBoxEditor.box.scale.x; + box.scale.y = this.firingBoxEditor.box.scale.y; + box.scale.z = this.firingBoxEditor.box.scale.z; + //saveList.push(w); + w.annotation.setModified(); + + onBoxChangedInBatchMode(box); + } + }); + + //this.activeEditorList().forEach(e=>e.update('dontrender')); + //this.viewManager.render(); + this.updateAutoGeneratedBoxes(); + + break; + case 'cm-reload': + + { + let selectedEditors = this.getSelectedEditors(); + this.reloadAnnotation(selectedEditors); + + this.updateAutoGeneratedBoxes(); + + } + break; + + case 'cm-goto-this-frame': + { + this.gotoThisFrame(); + } + break; + case 'cm-follow-static-objects': + { + let b = this.firingBoxEditor.box; + editor.autoAdjust.followStaticObjects(b); + this.globalHeader.updateModifiedStatus(); + + this.activeEditorList().forEach(e=>{ + e.tryAttach(); + }); + + //this.viewManager.render(); + this.updateAutoGeneratedBoxes(); + + } + break; + }; + + + + return true; + }; + + this.reset = function(){ + this.activeEditorList().forEach(e=>{ + e.setSelected(false); + e.resetTarget(); + }); + + this.viewManager.mainView.clearView(); + + this.activeIndex = 0; + }; + + + this.keydownHandler = (event)=>{ + + switch(event.key){ + case 'a': + if (event.ctrlKey){ + this.activeEditorList().forEach(e=>e.setSelected(true)); + } + break; + + case 's': + if (event.ctrlKey){ + this._save(); + console.log("saved for batch editor"); + } + break; + case '+': + case '=': + this.editingTarget.data.scale_point_size(1.2); + this.viewManager.render(); + break; + case '-': + this.editingTarget.data.scale_point_size(0.8); + this.viewManager.render(); + break; + case 'v': + case 'Escape': + { + // let selected = this.getSelectedEditors(); + // if (selected.length >= 2){ + // selected.forEach(e=>e.setSelected(false)); + // } + // else + { + this.hide(); + this.reset(); + if (this.onExit) + this.onExit(); + } + } + break; + case 'PageUp': + case '3': + this.prevBatch(); + break; + case 'PageDown': + case '4': + this.nextBatch(); + break; + case 't': + this.showTrajectory(); + break; + default: + console.log(`key ${event.key} igonored`); + break; + } + + return false; + }; + + + let keydownHandler = (event)=>this.keydownHandler(event); + + + this.hide =function(){ + if (this.parentUi.style.display != "none") + { + this.parentUi.style.display = "none"; + this.toolbox.style.display = "none"; + //document.removeEventListener("keydown", keydownHandler); + globalKeyDownManager.deregister('batch-editor'); + } + + }; + this.show = function(){ + if (this.parentUi.style.display == "none") + { + this.parentUi.style.display = ""; + //document.addEventListener("keydown", keydownHandler); + globalKeyDownManager.register(keydownHandler, 'batch-editor'); + this.toolbox.style.display = ""; + } + }; + + this.render =function() + { + if (this.parentUi.style.display != "none") + { + this.viewManager.render(); + } + }; + + + this._addToolBox = function(){ + let template = document.getElementById("batch-editor-tools-template"); + let tool = template.content.cloneNode(true); + // this.boxEditorHeaderUi.appendChild(tool); + // return this.boxEditorHeaderUi.lastElementChild; + + document.getElementById("dynamic-buttons-placeholder").appendChild(tool); + return document.getElementById("dynamic-buttons-placeholder").lastElementChild; + }; + + this.toolbox = this._addToolBox(); + + this.reloadAnnotation = function(editorList){ + + let done = (anns)=>{ + // update editor + editorList.forEach(e=>{ + e.tryAttach(); + e.update("dontrender"); + }); + + // reload main view + if (func_on_annotation_reloaded) + func_on_annotation_reloaded(); + // render all, at last + + this.viewManager.render(); + + this.globalHeader.updateModifiedStatus(); + }; + + + let worldList = editorList.map(e=>e.target.world); + + let modifiedFrames =worldList.filter(w=>w && w.annotation.modified); + + if (modifiedFrames.length > 0) + { + window.editor.infoBox.show( + "Confirm", + `Discard changes to ${modifiedFrames.length} frames, continue to reload?`, + ["yes","no"], + (choice)=>{ + if (choice=="yes") + { + reloadWorldList(worldList, done); + } + } + ); + } + else{ + reloadWorldList(worldList, done); + } + + + + } + + this.interpolate = async function(applyIndList){ + let boxList = this.activeEditorList().map(e=>e.box); + let worldList = this.activeEditorList().map(e=>e.target.world); + await this.boxOp.interpolateAsync(worldList, boxList, applyIndList); + this.activeEditorList().forEach(e=>e.tryAttach()); + + this.globalHeader.updateModifiedStatus(); + + this.viewManager.render(); + }; + + this.gotoThisFrame = function(){ + let targetFrame = this.firingBoxEditor.target.world.frameInfo.frame; + let targetTrackId = this.firingBoxEditor.target.objTrackId; + this.hide(); + + this.reset(); + if (this.onExit) + this.onExit(targetFrame, targetTrackId); + }; + + this.autoAnnotate = async function(applyIndList, dontRotate){ + let editors = this.activeEditorList(); + let boxList = editors.map(e=>e.box); + let worldList = editors.map(e=>e.target.world); + + let onFinishOneBox = (i)=>{ + editors[i].tryAttach(); + editors[i].box.world.annotation.setModified(); + this.viewManager.render(); + + this.updateAutoGeneratedBoxes(); + } + + await this.boxOp.interpolateAndAutoAdjustAsync(worldList, boxList, onFinishOneBox, applyIndList, dontRotate); + + this.globalHeader.updateModifiedStatus(); + } + + // this.parentUi.querySelector("#object-track-id-editor").addEventListener("keydown", function(e){ + // e.stopPropagation();}); + + // this.parentUi.querySelector("#object-track-id-editor").addEventListener("keyup", function(e){ + // e.stopPropagation(); + // }); + + // this.parentUi.querySelector("#object-track-id-editor").onchange = (ev)=>this.object_track_id_changed(ev); + // this.parentUi.querySelector("#object-category-selector").onchange = (ev)=>this.object_category_changed(ev); + + + // this should follow addToolBox + + // this.parentUi.querySelector("#instance-number").value = this.batchSize; + // this.parentUi.querySelector("#instance-number").onchange = (ev)=>{ + // this.batchSize = parseInt(ev.currentTarget.value); + // this.edit( + // this.editingTarget.data, + // this.editingTarget.sceneMeta, + // this.editingTarget.frame, + // this.editingTarget.objTrackId, + // this.editingTarget.objType + // ); + // } + + this.showTrajectory = () =>{ + let tracks = this.editingTarget.data.worldList.map(w=>{ + let box = w.annotation.findBoxByTrackId(this.editingTarget.objTrackId); + let ann = null; + if (box){ + ann = w.annotation.boxToAnn(box); + ann.psr.position = w.lidarPosToUtm(ann.psr.position); + ann.psr.rotation = w.lidarRotToUtm(ann.psr.rotation); + } + return [w.frameInfo.frame, ann, false]; + }); + + tracks.sort((a,b)=> (a[0] > b[0])? 1 : -1); + + this.objectTrackView.setObject( + this.editingTarget.objType, + this.editingTarget.objTrackId, + tracks, + (targetFrame)=>{ //onExit + this.getSelectedEditors().forEach(e=>e.setSelected(false)); + this.activeEditorList().find(e=>e.target.world.frameInfo.frame == targetFrame).setSelected(true); + } + ); + }; + + this.toolbox.querySelector("#trajectory").onclick = (e)=>{ + this.showTrajectory(); + }; + + this.toolbox.querySelector("#reload").onclick = (e)=>{ + + let selectedEditors = this.activeEditorList(); + this.reloadAnnotation(selectedEditors); + }; + + this.toolbox.querySelector("#interpolate").onclick = async ()=>{ + //this.boxOp.interpolate_selected_object(this.editingTarget.scene, this.editingTarget.objTrackId, ""); + + let applyIndList = this.activeEditorList().map(e=>true); //all shoud be applied. + this.interpolate(applyIndList); + + }; + + this.toolbox.querySelector("#auto-annotate").onclick = async ()=>{ + let applyIndList = this.activeEditorList().map(e=>true); //all shoud be applied. + this.autoAnnotate(applyIndList); + }; + + this.toolbox.querySelector("#auto-annotate-translate-only").onclick = async ()=>{ + let applyIndList = this.activeEditorList().map(e=>true); //all shoud be applied. + this.autoAnnotate(applyIndList, "dontrotate"); + }; + + this.toolbox.querySelector("#exit").onclick = ()=>{ + this.hide(); + + this.reset(); + + if (this.onExit) + this.onExit(); + }; + + this.toolbox.querySelector("#next").onclick = ()=>{ + this.nextBatch(); + }; + + this.toolbox.querySelector("#prev").onclick = ()=>{ + this.prevBatch(); + }; + + this.nextBatch = function() + { + let maxFrameIndex = this.editingTarget.sceneMeta.frames.length-1; + + let editors = this.activeEditorList() + let lastEditor = editors[editors.length-1]; + if (lastEditor.target.world.frameInfo.frame_index == maxFrameIndex) + { + if (this.batchSize >= this.editingTarget.sceneMeta.frames.length) + { + this.nextObj(); + } + else + { + window.editor.infoBox.show("Info", "This is the last batch of frames."); + } + + } + else + { + this.edit( + this.editingTarget.data, + this.editingTarget.sceneMeta, + this.editingTarget.sceneMeta.frames[Math.min(this.editingTarget.frameIndex + this.batchSize/2, maxFrameIndex)], + this.editingTarget.objTrackId, + this.editingTarget.objType + ); + } + }; + + this.prevBatch = function() + { + let firstEditor = this.activeEditorList()[0]; + if (firstEditor.target.world.frameInfo.frame_index == 0) + { + + if (this.batchSize >= this.editingTarget.sceneMeta.frames.length) + { + this.prevObj(); + } + else + { + window.editor.infoBox.show("Info", "This is the first batch of frames"); + } + + } + else + { + this.edit( + this.editingTarget.data, + this.editingTarget.sceneMeta, + this.editingTarget.sceneMeta.frames[Math.max(this.editingTarget.frameIndex - this.batchSize/2, 0)], + this.editingTarget.objTrackId, + this.editingTarget.objType + ); + } + + }; + + this.prevObj = function(){ + let idx = objIdManager.objectList.findIndex(x=>x.id==this.editingTarget.objTrackId); + + let objNum = objIdManager.objectList.length; + + idx = (idx + objNum - 1) % objNum; + + let obj = objIdManager.objectList[idx]; + + + + this.edit( + this.editingTarget.data, + this.editingTarget.sceneMeta, + this.editingTarget.sceneMeta.frames[this.editingTarget.frameIndex], + obj.id, + obj.category, + ); + }; + + + this.gotoFrame = function(frameID){ + this.getSelectedEditors().forEach(e=>e.setSelected(false)); + this.activeEditorList().find(e=>e.target.world.frameInfo.frame == frameID).setSelected(true); + } + + this.gotoObjectFrame = function(frameId, objId){ + if (objId != this.editingTarget.objTrackId) + { + let obj = objIdManager.getObjById(objId); + + this.edit( + this.editingTarget.data, + this.editingTarget.sceneMeta, + frameId, + objId, + obj.category, + ); + } + + this.getSelectedEditors().forEach(e=>e.setSelected(false)); + this.activeEditorList().find(e=>e.target.world.frameInfo.frame == frameId).setSelected(true); + } + + this.nextObj = function(){ + let idx = objIdManager.objectList.findIndex(x=>x.id==this.editingTarget.objTrackId && x.category == this.editingTarget.objType); + + let objNum = objIdManager.objectList.length; + + idx = (idx + 1) % objNum; + + let obj = objIdManager.objectList[idx]; + + this.edit( + this.editingTarget.data, + this.editingTarget.sceneMeta, + this.editingTarget.sceneMeta.frames[this.editingTarget.frameIndex], + obj.id, + obj.category, + ); + }; + + // this.toolbox.querySelector("#save").onclick = ()=>{ + // this._save(); + // }; + + this.toolbox.querySelector("#finalize").onclick = ()=>{ + this.finalize(); + }; + + this.finalize = function(){ + this.activeEditorList().forEach(e=>{ + if (e.box){ + + if (e.box.annotator){ + delete e.box.annotator; + func_on_box_changed(e.box); + } + e.box.world.annotation.setModified(); + e.updateInfo(); + } + }); + + this.globalHeader.updateModifiedStatus(); + }; + + this.object_track_id_changed = function(event){ + var id = event.currentTarget.value; + + if (id == "new"){ + id = objIdManager.generateNewUniqueId(); + this.parentUi.querySelector("#object-track-id-editor").value=id; + } + + this.activeEditorList().forEach(e=>{ + if (e.box){ + e.box.obj_track_id = id; + } + }); + + }; + + this.object_category_changed = function(event){ + let obj_type = event.currentTarget.value; + this.activeEditorList().forEach(e=>{ + if (e.box){ + e.box.obj_type = obj_type; + } + }); + }; + + + this._save = function(){ + let worldList = [] + let editorList = [] + this.activeEditorList().forEach(e=>{ + worldList.push(e.target.world); + editorList.push(e); + }); + + + saveWorldList(worldList); + } + + + this.updateViewZoomRatio = function(viewIndex, ratio){ + const dontRender=true; + this.activeEditorList().forEach(e=>{ + e._setViewZoomRatio(viewIndex, ratio); + e.update(dontRender); + }) + + // render all + this.viewManager.render(); + } + + this.addEditor = function(){ + let editor = this.allocateEditor(); + this.activeIndex += 1; + return editor; + }; + + this.allocateEditor = function(){ + if (this.activeIndex >= this.editorList.length){ + let editor = new BoxEditor(this.boxEditorGroupUi, this, this.viewManager, cfg, this.boxOp, func_on_box_changed, func_on_box_remove, String(this.activeIndex)); + + // resizable for the first editor + + if (this.editorList.length == 0) + { + editor.setResize("both"); + } + + this.editorList.push(editor); + + return editor; + }else{ + return this.editorList[this.activeIndex]; + } + }; + + + this.getEditorByMousePosition = function(x,y){ + + return this.editorList.find(e=>{ + let rect = e.ui.getBoundingClientRect(); + + return x > rect.left && x < rect.right && y > rect.top && y < rect.bottom; + }) + }; + + + this.parentUi.onmousedown= (event)=>{ + + if (event.which!=1) + return; + + let eventId = Date.now(); + + let select_start_pos={ + x: event.clientX, + y: event.clientY, + } + + console.log("box editor manager, on mouse down.", select_start_pos); + + let select_end_pos={ + x: event.clientX, + y: event.clientY, + } + + let leftMouseDown = true; + + // a1 a2) [a1,a2]=[a2,a1]; + if (b1 > b2) [b1,b2]=[b2,b1]; + + return (a1 > b1 && a1 < b2) || (a2 > b1 && a2 < b2) || (b1 > a1 && b1 < a2) || (b2 > a1 && b2 < a2) + } + + // a,b: left, right, right, bottom + function intersect(domRect, mouseA, mouseB){ + return (lineIntersect(select_end_pos.x, select_start_pos.x, domRect.left, domRect.right) && + lineIntersect(select_end_pos.y, select_start_pos.y, domRect.top, domRect.bottom)) + } + + + + this.parentUi.onmousemove = (event)=>{ + select_end_pos.x = event.clientX; + select_end_pos.y = event.clientY; + + this.editorList.forEach(e=>{ + let rect = e.ui.getBoundingClientRect(); + let intersected = intersect(rect, select_start_pos, select_end_pos); + + e.setSelected(intersected, event.ctrlKey?eventId:null); + + }) + } + + this.parentUi.onmouseup = (event) =>{ + if (event.which!=1) + return; + + leftMouseDown = false; + this.parentUi.onmousemove = null; + this.parentUi.onmouseup = null; + + + if (event.clientX == select_start_pos.x && event.clientY == select_start_pos.y) + { // click + + let ed = this.getEditorByMousePosition(event.clientX, event.clientY); + + + if (event.shiftKey) + { + let selectedEditors = this.getSelectedEditors(); + if (selectedEditors.length == 0) + { + + } + else if (ed.index < selectedEditors[0].index) + { + this.activeEditorList().forEach(e=>{ + if (e.index >= ed.index && e.index < selectedEditors[0].index){ + e.setSelected(true); + } + }); + } + else if (ed.index > selectedEditors[selectedEditors.length-1].index) + { + this.activeEditorList().forEach(e=>{ + if (e.index <= ed.index && e.index > selectedEditors[selectedEditors.length-1].index){ + e.setSelected(true); + } + }); + } + } + else if (event.ctrlKey) + { + ed.setSelected(!ed.selected); + } + else + { + let selectedEditors = this.getSelectedEditors(); + + + if (ed){ + if (ed.selected && selectedEditors.length == 1) + { + ed.setSelected(false); + } + else + { + selectedEditors.forEach(e=>e.setSelected(false)); + ed.setSelected(true); + } + } + else{ + selectedEditors.forEach(e=>e.setSelected(false)); + } + } + } + } + } + + this.getSelectedEditors = function(){ + return this.editorList.filter(e=>e.selected); + } + +} +export {BoxEditorManager, BoxEditor}; \ No newline at end of file diff --git a/public/js/box_op.js b/public/js/box_op.js new file mode 100644 index 0000000..8f41f46 --- /dev/null +++ b/public/js/box_op.js @@ -0,0 +1,844 @@ +import * as THREE from './lib/three.module.js'; + +import {logger} from "./log.js" +import { + Quaternion, + Vector3 +} from "./lib/three.module.js"; + +import{ml} from "./ml.js"; +import {dotproduct, transpose, matmul, euler_angle_to_rotate_matrix_3by3} from "./util.js" + + +function BoxOp(){ + console.log("BoxOp called"); + this.grow_box_distance_threshold = 0.3; + this.init_scale_ratio = {x:2, y:2, z:3}; + + this.fit_bottom = function(box) + { + let bottom = box.world.lidar.findBottom(box, {x:2, y:2, z:3}); + this.translate_box(box, 'z', bottom + box.scale.z/2); + } + + this.fit_top = function(box) + { + let top = box.world.lidar.findTop(box, {x:1.2, y:1.2, z:2}); + this.translate_box(box, 'z', top - box.scale.z/2); + } + + this.fit_left = function(box) + { + var extreme = box.world.lidar.grow_box(box, this.grow_box_distance_threshold, this.init_scale_ratio); + + if (extreme){ + this.translate_box(box, 'y', extreme.max.y - box.scale.y/2); + } + } + + this.fit_right = function(box) + { + var extreme = box.world.lidar.grow_box(box, this.grow_box_distance_threshold, this.init_scale_ratio); + + if (extreme){ + this.translate_box(box, 'y', extreme.min.y + box.scale.y/2); + } + } + + this.fit_front = function(box) + { + var extreme = box.world.lidar.grow_box(box, this.grow_box_distance_threshold, this.init_scale_ratio); + + if (extreme){ + this.translate_box(box, 'x', extreme.max.x - box.scale.x/2); + } + } + + this.fit_rear = function(box) + { + var extreme = box.world.lidar.grow_box(box, this.grow_box_distance_threshold, this.init_scale_ratio); + + if (extreme){ + this.translate_box(box, 'x', extreme.min.x + box.scale.x/2); + } + } + + + + + this.fit_size = function(box,axies) + { + this.grow_box(box, this.grow_box_distance_threshold, {x:2, y:2, z:3}, axies); + } + + + + this.justifyAutoAdjResult = function(orgBox, box) + { + let distance = Math.sqrt((box.position.x-orgBox.position.x)*(box.position.x-orgBox.position.x) + + (box.position.y-orgBox.position.y)*(box.position.y-orgBox.position.y) + + (box.position.z-orgBox.position.z)*(box.position.z-orgBox.position.z)); + + if (distance > Math.sqrt(box.scale.x*box.scale.x + box.scale.y*box.scale.y + box.scale.z*box.scale.z)) + { + return false; + } + + // if (Math.abs(box.rotation.z - orgBox.rotation.z) > Math.PI/4) + // { + // return false; + // } + + if (box.scale.x > orgBox.scale.x*3 || + box.scale.y > orgBox.scale.y*3 || + box.scale.z > orgBox.scale.z*3) + { + return false; + } + + return true; + } + + + this.auto_rotate_xyz= async function(box, callback, apply_mask, on_box_changed, noscaling, rotate_method){ + + let orgBox = box; + box = { + position: {x: box.position.x, y: box.position.y, z: box.position.z}, + rotation: {x: box.rotation.x, y: box.rotation.y, z: box.rotation.z}, + scale: {x: box.scale.x, y: box.scale.y, z: box.scale.z}, + world: box.world, + }; + + // auto grow + // save scale + let grow = (box)=>{ + let org_scale = { + x: box.scale.x, + y: box.scale.y, + z: box.scale.z, + }; + this.grow_box(box, this.grow_box_distance_threshold, {x:2, y:2, z:3}); + this.auto_shrink_box(box); + // now box has been centered. + + let points_indices = box.world.lidar.get_points_of_box(box,1.0).index; + let extreme = box.world.lidar.get_dimension_of_points(points_indices, box); + // restore scale + if (noscaling){ + box.scale.x = org_scale.x; + box.scale.y = org_scale.y; + box.scale.z = org_scale.z; + } + // + return extreme; + }; + + //points is N*3 shape + + let applyRotation = (ret, extreme_after_grow)=>{ + + let angle = ret.angle; + if (!angle){ + console.log("prediction not implemented?"); + return; + } + + + //var points_indices = box.world.get_points_indices_of_box(box); + let points_indices = box.world.lidar.get_points_of_box(box,1.0).index; + + var euler_delta = { + x: angle[0], + y: angle[1], + z: angle[2] + }; + + if (euler_delta.z > Math.PI){ + euler_delta.z -= Math.PI*2; + }; + + /* + var composite_angel = linalg_std.euler_angle_composite(box.rotation, euler_delta); + + console.log("orig ", box.rotation.x, box.rotation.y, box.rotation.z); + console.log("delt ", euler_delta.x, euler_delta.y, euler_delta.z); + console.log("comp ", composite_angel.x, composite_angel.y, composite_angel.z); + + box.rotation.x = composite_angel.x; + box.rotation.y = composite_angel.y; + box.rotation.z = composite_angel.z; + */ + + if (apply_mask){ + if (apply_mask.x) + box.rotation.x = euler_delta.x; + if (apply_mask.y) + box.rotation.y = euler_delta.y; + if (apply_mask.z) + box.rotation.z = euler_delta.z; + } + else{ + box.rotation.x = euler_delta.x; + box.rotation.y = euler_delta.y; + box.rotation.z = euler_delta.z; + } + + + // rotation set, now rescaling the box + // important: should use original points before rotation set + var extreme = box.world.lidar.get_dimension_of_points(points_indices, box); + + let auto_adj_dimension = []; + + if (apply_mask){ + if (apply_mask.x || apply_mask.y) + auto_adj_dimension.push('z'); + + if (apply_mask.x || apply_mask.z) + auto_adj_dimension.push('y'); + + if (apply_mask.y || apply_mask.z) + auto_adj_dimension.push('x'); + } + else{ + auto_adj_dimension = ['x','y','z']; + } + + if (!noscaling){ + auto_adj_dimension.forEach((axis)=>{ + this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2); + box.scale[axis] = extreme.max[axis] - extreme.min[axis]; + }) + }else { + //anyway, we move the box in a way + let trans = euler_angle_to_rotate_matrix_3by3(box.rotation); + trans = transpose(trans, 3); + + // compute the relative position of the origin point,that is, the lidar's position + // note the origin point is offseted, we need to restore first. + let boxpos = box.position; + let orgPoint = [ + - boxpos.x, + - boxpos.y, + - boxpos.z, + ]; + let orgPointInBoxCoord = matmul(trans, orgPoint, 3); + let relativePosition = { + x: orgPointInBoxCoord[0], + y: orgPointInBoxCoord[1], + z: 1, //orgPointInBoxCoord[2], + } + + if (extreme_after_grow) + extreme = extreme_after_grow; + + auto_adj_dimension.forEach((axis)=>{ + if (relativePosition[axis]>0){ + //stick to max + this.translate_box(box, axis, extreme.max[axis] - box.scale[axis]/2); + }else{ + //stick to min + this.translate_box(box, axis, extreme.min[axis] + box.scale[axis]/2); + } + + + + }) + + } + + return box; + }; + + + + let postProc = (box)=>{ + + if (this.justifyAutoAdjResult(orgBox, box)) + { + // copy back + orgBox.position.x = box.position.x; + orgBox.position.y = box.position.y; + orgBox.position.z = box.position.z; + + orgBox.rotation.x = box.rotation.x; + orgBox.rotation.y = box.rotation.y; + orgBox.rotation.z = box.rotation.z; + + orgBox.scale.x = box.scale.x; + orgBox.scale.y = box.scale.y; + orgBox.scale.z = box.scale.z; + } + + if (on_box_changed) + on_box_changed(orgBox); + + if (callback){ + callback(); + } + return orgBox; + }; + + let extreme_after_grow = grow(box); + + if (!rotate_method){ + let points = box.world.lidar.get_points_relative_coordinates_of_box_wo_rotation(box, 1); + //let points = box.world.get_points_relative_coordinates_of_box(box, 1.0); + + points = points.filter(function(p){ + return p[2] > - box.scale.z/2 + 0.3; + }) + + let retBox = await ml.predict_rotation(points) + .then(applyRotation) + .then(postProc); + + return retBox; + } + if (rotate_method == "moving-direction") + { + let estimatedRot = this.estimate_rotation_by_moving_direciton(box); + + applyRotation({ + angle:[ + box.rotation.x, // use original rotation + box.rotation.y, // use original rotation + estimatedRot? estimatedRot.z : box.rotation.z, // use original rotation + ] + }, + extreme_after_grow); + + postProc(box); + return box; + } + else{ //dont rotate, or null + applyRotation({ + angle:[ + box.rotation.x, // use original rotation + box.rotation.y, // use original rotation + box.rotation.z, // use original rotation + ] + }, + extreme_after_grow); + + postProc(box); + return box; + } + + + } + + this.auto_shrink_box= function(box){ + var extreme = box.world.lidar.get_points_dimmension_of_box(box); + + ['x', 'y','z'].forEach((axis)=>{ + + this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2); + box.scale[axis] = extreme.max[axis]-extreme.min[axis]; + }) + + }; + + + this.estimate_rotation_by_moving_direciton = function(box) + { + let prevWorld = box.world.data.findWorld(box.world.frameInfo.scene, + box.world.frameInfo.frame_index-1); + + let nextWorld = box.world.data.findWorld(box.world.frameInfo.scene, + box.world.frameInfo.frame_index+1); + + let prevBox = prevWorld?prevWorld.annotation.findBoxByTrackId(box.obj_track_id): null; + let nextBox = nextWorld?nextWorld.annotation.findBoxByTrackId(box.obj_track_id): null; + + if (prevBox && nextBox) + { + if ((prevBox.annotator && nextBox.annotator) || (!prevBox.annotator && !nextBox.annotator)) + { + // all annotated by machine or man, it's ok + } + else + { + // only one is manually annotated, use this one. + if (prevBox.annotator) + prevBox = null; + + if (nextBox.annotator) + nextBox = null; + } + } + + + if (!nextBox && !prevBox){ + logger.logcolor("red", "Cannot estimate direction: neither previous nor next frame/box loaded/annotated.") + return null; + } + + let currentP = box.world.lidarPosToUtm(box.position); + let nextP = nextBox?nextBox.world.lidarPosToUtm(nextBox.position) : null; + let prevP = prevBox?prevBox.world.lidarPosToUtm(prevBox.position) : null; + + if (!prevP) + prevP = currentP; + + if (!nextP) + nextP = currentP; + + let azimuth = Math.atan2(nextP.y-prevP.y, nextP.x-prevP.x) + + let estimatedRot = box.world.utmRotToLidar(new THREE.Euler(0,0,azimuth, "XYZ")); + + return estimatedRot; + }; + + this.grow_box= function(box, min_distance, init_scale_ratio, axies){ + + if (!axies) + { + axies = ['x','y','z']; + } + + + var extreme = box.world.lidar.grow_box(box, min_distance, init_scale_ratio); + + if (extreme){ + + axies.forEach((axis)=>{ + this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2); + box.scale[axis] = extreme.max[axis] - extreme.min[axis]; + }) + } + + }; + + this.change_rotation_y = function(box, theta, sticky, on_box_changed){ + //box.rotation.x += theta; + //on_box_changed(box); + + var points_indices = box.world.lidar.get_points_indices_of_box(box); + + var _tempQuaternion = new Quaternion(); + var rotationAxis = new Vector3(0, 1, 0); + + // NOTE: the front/end subview is different from top/side view, that we look at the reverse direction of y-axis + // it's end view acturally. + // we could project front-view, but the translation (left, right) will be in reverse direction of top view. + /// that would be frustrating. + box.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, -theta ) ).normalize(); + + if (sticky){ + var extreme = box.world.lidar.get_dimension_of_points(points_indices, box); + + ['x','z'].forEach((axis)=>{ + + this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2); + box.scale[axis] = extreme.max[axis] - extreme.min[axis]; + + }) + } + + if (on_box_changed) + on_box_changed(box); + } + + + this.auto_rotate_y=function(box, on_box_changed){ + let points = box.world.lidar.get_points_of_box(box, 2.0); + + // 1. find surounding points + var side_indices = [] + var side_points = [] + points.position.forEach(function(p, i){ + if ((p[0] > box.scale.x/2 || p[0] < -box.scale.x/2) && (p[1] < box.scale.y/2 && p[1] > -box.scale.y/2)){ + side_indices.push(points.index[i]); + side_points.push(points.position[i]); + } + }) + + + var end_indices = [] + var end_points = [] + points.position.forEach(function(p, i){ + if ((p[0] < box.scale.x/2 && p[0] > -box.scale.x/2) && (p[1] > box.scale.y/2 || p[1] < -box.scale.y/2)){ + end_indices.push(points.index[i]); + end_points.push(points.position[i]); + } + }) + + + // 2. grid by 0.3 by 0.3 + + // compute slope (derivative) + // for side part (pitch/tilt), use y,z axis + // for end part (row), use x, z axis + + + + // box.world.lidar.set_spec_points_color(side_indices, {x:1,y:0,z:0}); + // box.world.lidar.set_spec_points_color(end_indices, {x:0,y:0,z:1}); + // box.world.lidar.update_points_color(); + + var x = end_points.map(function(x){return x[0]}); + //var y = side_points.map(function(x){return x[1]}); + var z = end_points.map(function(x){return x[2]}); + var z_mean = z.reduce(function(x,y){return x+y;}, 0)/z.length; + var z = z.map(function(x){return x-z_mean;}); + var theta = Math.atan2(dotproduct(x,z), dotproduct(x,x)); + console.log(theta); + + this.change_rotation_y(box, theta, false, on_box_changed); + } + + + + this.change_rotation_x=function(box, theta, sticky, on_box_changed){ + var points_indices = box.world.lidar.get_points_indices_of_box(box); + + //box.rotation.x += theta; + //on_box_changed(box); + var _tempQuaternion = new Quaternion(); + var rotationAxis = new Vector3(1,0,0); + box.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, theta ) ).normalize(); + + if (sticky){ + var extreme = box.world.lidar.get_dimension_of_points(points_indices, box); + + ['y','z'].forEach((axis)=>{ + + this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2); + box.scale[axis] = extreme.max[axis] - extreme.min[axis]; + + }) + } + + if (on_box_changed) + on_box_changed(box); + + }; + + + this.auto_rotate_x=function(box, on_box_changed){ + console.log("x auto ratote"); + + let points = box.world.lidar.get_points_of_box(box, 2.0); + + // 1. find surounding points + var side_indices = [] + var side_points = [] + points.position.forEach(function(p, i){ + if ((p[0] > box.scale.x/2 || p[0] < -box.scale.x/2) && (p[1] < box.scale.y/2 && p[1] > -box.scale.y/2)){ + side_indices.push(points.index[i]); + side_points.push(points.position[i]); + } + }) + + + var end_indices = [] + var end_points = [] + points.position.forEach(function(p, i){ + if ((p[0] < box.scale.x/2 && p[0] > -box.scale.x/2) && (p[1] > box.scale.y/2 || p[1] < -box.scale.y/2)){ + end_indices.push(points.index[i]); + end_points.push(points.position[i]); + } + }) + + + // 2. grid by 0.3 by 0.3 + + // compute slope (derivative) + // for side part (pitch/tilt), use y,z axis + // for end part (row), use x, z axis + + + + // box.world.lidar.set_spec_points_color(side_indices, {x:1,y:0,z:0}); + // box.world.lidar.set_spec_points_color(end_indices, {x:0,y:0,z:1}); + // box.world.lidar.update_points_color(); + //render(); + + var x = side_points.map(function(x){return x[0]}); + var y = side_points.map(function(x){return x[1]}); + var z = side_points.map(function(x){return x[2]}); + var z_mean = z.reduce(function(x,y){return x+y;}, 0)/z.length; + var z = z.map(function(x){return x-z_mean;}); + var theta = Math.atan2(dotproduct(y,z), dotproduct(y,y)); + console.log(theta); + + this.change_rotation_x(box, theta, false, on_box_changed); + }; + + + this.translate_box=function(box, axis, delta){ + let t = {x:0, y:0, z:0}; + + t[axis] = delta; + + // switch (axis){ + // case 'x': + + // box.position.x += delta*Math.cos(box.rotation.z); + // box.position.y += delta*Math.sin(box.rotation.z); + // break; + // case 'y': + // box.position.x += delta*Math.cos(Math.PI/2 + box.rotation.z); + // box.position.y += delta*Math.sin(Math.PI/2 + box.rotation.z); + // break; + // case 'z': + // box.position.z += delta; + // break; + + // } + + let trans = this.translateBoxInBoxCoord(box.rotation, t); + box.position.x += trans.x; + box.position.y += trans.y; + box.position.z += trans.z; + + }; + + this.translateBoxInBoxCoord = function(rotation, t) + { + // euler + let euler = new THREE.Euler(rotation.x, rotation.y, rotation.z, "XYZ") + + let trans = new THREE.Vector3(t.x, t.y, t.z).applyEuler(euler); + + return trans; + }; + + this.rotate_z=function(box, theta, sticky){ + // points indices shall be obtained before rotation. + var points_indices = box.world.lidar.get_points_indices_of_box(box); + + + var _tempQuaternion = new Quaternion(); + var rotationAxis = new Vector3(0,0,1); + box.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, theta ) ).normalize(); + + if (sticky){ + + var extreme = box.world.lidar.get_dimension_of_points(points_indices, box); + + ['x','y'].forEach((axis)=>{ + + this.translate_box(box, axis, (extreme.max[axis] + extreme.min[axis])/2); + box.scale[axis] = extreme.max[axis] - extreme.min[axis]; + + }) + } + }, + + + + this.interpolate_selected_object= function(sceneName, objTrackId, currentFrame, done){ + + // var xhr = new XMLHttpRequest(); + // // we defined the xhr + + // xhr.onreadystatechange = function () { + // if (this.readyState != 4) + // return; + + // if (this.status == 200) { + // var ret = JSON.parse(this.responseText); + // console.log(ret); + + // if (done) + // done(sceneName, ret); + // } + + // }; + + // xhr.open('GET', "/interpolate?scene="+sceneName+"&frame="+currentFrame+"&obj_id="+objTrackId, true); + // xhr.send(); + }; + + this.highlightBox = function(box){ + if (box){ + box.material.color.r=1; + box.material.color.g=0; + box.material.color.b=1; + box.material.opacity=1; + } + }; + + this.unhighlightBox = function(box){ + if (box){ + // box.material.color = new THREE.Color(parseInt("0x"+get_obj_cfg_by_type(box.obj_type).color.slice(1))); + + // box.material.opacity = box.world.data.cfg.box_opacity; + + box.world.annotation.color_box(box); + } + } + + this.interpolateAsync = async function(worldList, boxList, applyIndList){ + + // if annotator is not null, it's annotated by us algorithms + let anns = boxList.map(b=> (!b || b.annotator)? null : b.world.annotation.ann_to_vector_global(b)); + console.log(anns); + let ret = await ml.interpolate_annotation(anns); + console.log(ret); + + let refObj = boxList.find(b=>!!b); + let obj_type = refObj.obj_type; + let obj_track_id = refObj.obj_track_id; + let obj_attr = refObj.obj_attr; + + for (let i = 0; i< boxList.length; i++){ + if (!applyIndList[i]) + { + continue; + } + + // + let world = worldList[i]; + let ann = world.annotation.vector_global_to_ann(ret[i]); + + // don't roate x/y + if (!pointsGlobalConfig.enableAutoRotateXY) + { + ann.rotation.x = 0; + ann.rotation.y = 0; + } + + + // if (world.lidar.get_box_points_number(ann) == 0) + // { + // continue; + // } + + + if (!boxList[i]){ + // create new box + let newBox = world.annotation.add_box(ann.position, + ann.scale, + ann.rotation, + obj_type, + obj_track_id, + obj_attr); + newBox.annotator="i"; + world.annotation.load_box(newBox); + world.annotation.setModified(); + + } else if (boxList[i].annotator) { + // modify box attributes + let b = ann; + + boxList[i].position.x = b.position.x; + boxList[i].position.y = b.position.y; + boxList[i].position.z = b.position.z; + + boxList[i].scale.x = b.scale.x; + boxList[i].scale.y = b.scale.y; + boxList[i].scale.z = b.scale.z; + + boxList[i].rotation.x = b.rotation.x; + boxList[i].rotation.y = b.rotation.y; + boxList[i].rotation.z = b.rotation.z; + + boxList[i].annotator = "i"; + + boxList[i].world.annotation.setModified(); + } + } + }; + + this.interpolateAndAutoAdjustAsync = async function(worldList, boxList, onFinishOneBoxCB, applyIndList, dontRotate){ + + + // if annotator is not null, it's annotated by us algorithms + let anns = boxList.map((b,i)=> { + + if (!b) + return null; + + if (b.annotator) + return null; + + return b.world.annotation.ann_to_vector_global(b); + }); + + console.log("anns to interpolate", anns); + + let autoAdjAsync = async (index, newAnn)=>{ + //let box = boxList[index]; + let world = worldList[index]; + + let tempBox = world.annotation.vector_global_to_ann(newAnn); + tempBox.world = world; + + // autoadj is timecomsuming + // jump this step + let rotateThis = dontRotate; + if (!applyIndList[index]){ + rotateThis = "dontrotate"; + } + + let adjustedBox = await this.auto_rotate_xyz(tempBox, null, null, null, true, rotateThis); + return world.annotation.ann_to_vector_global(adjustedBox); + }; + + + let refObj = boxList.find(b=>!!b); + let obj_type = refObj.obj_type; + let obj_track_id = refObj.obj_track_id; + let obj_attr = refObj.obj_attr; + + let onFinishOneBox= (index)=>{ + console.log(`auto insert ${index} ${worldList[index].frameInfo.frame}done`); + let i = index; + + if (!applyIndList[i]){ + return; + } + + if (!boxList[i]){ + // create new box + let world = worldList[i]; + let ann = world.annotation.vector_global_to_ann(anns[i]); + + let newBox = world.annotation.add_box(ann.position, + ann.scale, + ann.rotation, + obj_type, + obj_track_id, + obj_attr); + newBox.annotator="a"; + world.annotation.load_box(newBox); + + } else if (boxList[i].annotator) { + // modify box attributes + let b = boxList[i].world.annotation.vector_global_to_ann(anns[i]); + boxList[i].position.x = b.position.x; + boxList[i].position.y = b.position.y; + boxList[i].position.z = b.position.z; + + boxList[i].scale.x = b.scale.x; + boxList[i].scale.y = b.scale.y; + boxList[i].scale.z = b.scale.z; + + boxList[i].rotation.x = b.rotation.x; + boxList[i].rotation.y = b.rotation.y; + boxList[i].rotation.z = b.rotation.z; + + boxList[i].annotator="a"; + } + + if (onFinishOneBoxCB) + onFinishOneBoxCB(i); + }; + + let ret = await ml.interpolate_annotation(anns, autoAdjAsync, onFinishOneBox); + console.log(ret); + + // for (let i = 0; i< boxList.length; i++){ + // onFinishOneBox(i); + // } + }; + + +} + +export {BoxOp} \ No newline at end of file diff --git a/public/js/box_op_desc.js b/public/js/box_op_desc.js new file mode 100644 index 0000000..e69de29 diff --git a/public/js/calib.js b/public/js/calib.js new file mode 100644 index 0000000..ab1ef38 --- /dev/null +++ b/public/js/calib.js @@ -0,0 +1,196 @@ + +import {rotation_matrix_to_euler_angle,euler_angle_to_rotate_matrix, matmul, transpose} from "./util.js" +//import {render_2d_image, update_image_box_projection} from "./image.js" + +function Calib(data, editor){ + this.data = data; + this.editor = editor; + + var euler_angle={x:0, y:0, y:0}; + var translate = {x:0, y:0, z:0}; + + this.save_calibration = function(){ + + + var scene_meta = data.meta[data.world.frameInfo.scene]; + + + var active_camera_name = data.world.cameras.active_name; + var calib = scene_meta.calib.camera[active_camera_name] + + var extrinsic = calib.extrinsic.map(function(x){return x*1.0;}); + + euler_angle = rotation_matrix_to_euler_angle(extrinsic); + translate = { + x: extrinsic[3]*1.0, + y: extrinsic[7]*1.0, + z: extrinsic[11]*1.0, + }; + + + console.log(extrinsic, euler_angle, translate); + + let matrix = euler_angle_to_rotate_matrix(euler_angle, translate) + console.log("restoreed matrix",matrix); + + + this.editor.infoBox.show("calib", JSON.stringify(matrix)); + } + + this.reset_calibration = function(){ + // to be done + this.editor.imageContextManager.render_2d_image(); + } + + this.calib_box = null; + + this.show_camera_pos = function(){ + this.editor.viewManager.mainView.dumpPose(); + }; + + + // show a manipulating box + this.start_calibration = function(){ + var scene_meta = this.data.meta[data.world.frameInfo.scene]; + + var active_camera_name = this.data.world.cameras.active_name; + var calib = scene_meta.calib.camera[active_camera_name] + var extrinsic = calib.extrinsic.map(function(x){return x*1.0;}); + let viewMatrix = [0, -1, 0, 0, //row vector + 0, 0, -1, 0, + 1, 0, 0, 0, + 0, 0, 0, 1]; + function transpose_transmatrix(m){ + //m=4*4 + return [ + m[0],m[4],m[8],m[3], + m[1],m[5],m[9],m[7], + m[2],m[6],m[10],m[11], + m[12],m[13],m[14],m[15], + + ]; + } + + var op_matrix = matmul (transpose_transmatrix(viewMatrix), + transpose_transmatrix(extrinsic), 4); + + var euler_angle = rotation_matrix_to_euler_angle(op_matrix); + var translate = { + x: extrinsic[3]*1.0, + y: extrinsic[7]*1.0, + z: extrinsic[11]*1.0, + }; + + console.log(euler_angle, translate); + this.show_camera_pos(); + + + if (!this.calib_box) + { + this.calib_box = this.data.world.annotation.createCuboid( + { + x: translate.x,// + this.data.world.coordinatesOffset[0], + y: translate.y,// + this.data.world.coordinatesOffset[1], + z: translate.z, // + this.data.world.coordinatesOffset[2] + }, + {x:1,y:1, z:1}, + { + x: euler_angle.x, + y: euler_angle.y, + z: euler_angle.z + }, + "camera", + "camera" + ); + + this.data.world.scene.add(this.calib_box); + + } + else{ + console.log("calib box exists."); + this.calib_box.position.x = translate.x;// + this.data.world.coordinatesOffset[0]; + this.calib_box.position.y = translate.y;// + this.data.world.coordinatesOffset[1]; + this.calib_box.position.z = translate.z;// + this.data.world.coordinatesOffset[2]; + + this.calib_box.rotation.x = euler_angle.x; + this.calib_box.rotation.y = euler_angle.y; + this.calib_box.rotation.z = euler_angle.z; + } + + console.log(this.calib_box); + this.editor.render(); + + + this.calib_box.on_box_changed = ()=>{ + console.log("calib box changed."); + + let real_pos = { + x: this.calib_box.position.x,// - this.data.world.coordinatesOffset[0], + y: this.calib_box.position.y,// - this.data.world.coordinatesOffset[1], + z: this.calib_box.position.z,// - this.data.world.coordinatesOffset[2], + }; + + let extrinsic = euler_angle_to_rotate_matrix(this.calib_box.rotation, real_pos); + calib.extrinsic = transpose_transmatrix(matmul (viewMatrix, extrinsic, 4)); + console.log("extrinsic", calib.extrinsic) + console.log("euler", euler_angle, "translate", translate); + + this.editor.imageContextManager.render_2d_image(); + } + + + + }; + + function stop_calibration() + { + //tbd + }; + + /* + function calibrate(ax, value){ + var scene_meta = data.meta[data.world.frameInfo.scene]; + + var active_camera_name = data.world.cameras.active_name; + var calib = scene_meta.calib.camera[active_camera_name] + var extrinsic = calib.extrinsic.map(function(x){return x*1.0;}); + + var euler_angle = rotation_matrix_to_euler_angle(extrinsic); + var translate = { + x: extrinsic[3]*1.0, + y: extrinsic[7]*1.0, + z: extrinsic[11]*1.0, + }; + + if (ax == 'z'){ + euler_angle.z += value; + }else if (ax == 'x'){ + euler_angle.x += value; + } + else if (ax == 'y'){ + euler_angle.y += value; + }else if (ax == 'tz'){ + translate.z += value; + }else if (ax == 'tx'){ + translate.x += value; + } + else if (ax == 'ty'){ + translate.y += value; + } + + calib.extrinsic = euler_angle_to_rotate_matrix(euler_angle, translate); + + console.log("extrinsic", calib.extrinsic) + console.log("euler", euler_angle, "translate", translate); + + render_2d_image(); + + if (selected_box) + update_image_box_projection(selected_box); + } + */ + +}; + + +export {Calib} \ No newline at end of file diff --git a/public/js/config.js b/public/js/config.js new file mode 100644 index 0000000..66418ad --- /dev/null +++ b/public/js/config.js @@ -0,0 +1,121 @@ + +class Config{ + + //dataCfg = { + + //disableLabels: true, + enablePreload = true; + color_points = "mono"; + enableRadar = false; + enableAuxLidar = false; + enableDynamicGroundLevel = true; + + coordinateSystem = 'utm'; + + point_size = 1; + point_brightness = 0.6; + box_opacity = 1; + show_background = true; + color_obj = "category"; + theme = "dark"; + + enableFilterPoints = false; + filterPointsZ = 2.0; + + batchModeInstNumber = 20; + batchModeSubviewSize = {width: 130, height: 450}; + + + // edit on one box, apply to all selected boxes. + linkEditorsInBatchMode = false; + + // only rotate z in 'auto/interpolate' algs + enableAutoRotateXY = false; + autoSave = true; + + autoUpdateInterpolatedBoxes = true; + + hideId = false; + hideCategory = false; + + moveStep = 0.01; // ratio, percentage + rotateStep = Math.PI/360; + + ignoreDistantObject = true; + + ///editorCfg + + //disableSceneSelector = true; + //disableFrameSelector = true; + //disableCameraSelector = true; + //disableFastToolbox= true; + //disableMainView= true; + //disableMainImageContext = true; + //disableGrid = true; + //disableRangeCircle = true; + //disableAxis = true; + //disableMainViewKeyDown = true; + //projectRadarToImage = true; + //projectLidarToImage = true; + + constructor() + { + + } + + readItem(name, defaultValue, castFunc){ + let ret = window.localStorage.getItem(name); + + if (ret) + { + if (castFunc) + return castFunc(ret); + else + return ret; + } + else + { + return defaultValue; + } + } + + setItem(name, value) + { + this[name] = value; + if (typeof value == 'object') + value = JSON.stringify(value); + window.localStorage.setItem(name, value); + } + + toBool(v) + { + return v==="true"; + } + + saveItems = [ + ["theme", null], + ["enableRadar", this.toBool], + ["enablePreload", this.toBool], + ["enableAuxLidar", this.toBool], + ["enableFilterPoints", this.toBool], + ["filterPointsZ", parseFloat], + ["color_points", null], + ["coordinateSystem", null], + ["batchModeInstNumber", parseInt], + ["batchModeSubviewSize", JSON.parse], + ["enableAutoRotateXY", this.toBool], + ["autoUpdateInterpolatedBoxes", this.toBool], + ]; + + load() + { + this.saveItems.forEach(item=>{ + let key = item[0]; + let castFunc = item[1]; + + this[key] = this.readItem(key, this[key], castFunc); + }) + } +}; + +export {Config}; \ No newline at end of file diff --git a/public/js/config_ui.js b/public/js/config_ui.js new file mode 100644 index 0000000..2bae001 --- /dev/null +++ b/public/js/config_ui.js @@ -0,0 +1,347 @@ +import { globalKeyDownManager } from "./keydown_manager.js"; +import {logger} from "./log.js"; + +class ConfigUi{ + + clickableItems = { + "#cfg-increase-size": (event)=>{ + this.editor.data.scale_point_size(1.2); + this.editor.render(); + this.editor.boxEditorManager.render(); + return false; + }, + + "#cfg-decrease-size": (event)=>{ + this.editor.data.scale_point_size(0.8); + this.editor.render(); + this.editor.boxEditorManager.render(); + return false; + }, + + "#cfg-increase-brightness": (event)=>{ + this.editor.data.scale_point_brightness(1.2); + this.editor.render(); + this.editor.boxEditorManager.render(); + return false; + }, + + "#cfg-decrease-brightness": (event)=>{ + this.editor.data.scale_point_brightness(0.8); + this.editor.render(); + this.editor.boxEditorManager.render(); + return false; + }, + + "#cfg-take-screenshot": (event)=>{ + this.editor.downloadWebglScreenShot(); + return true; + }, + + "#cfg-show-log": (event)=>{ + logger.show(); + return true; + }, + + "#cfg-start-calib":(event)=>{ + this.editor.calib.start_calibration(); + return true; + }, + + "#cfg-show-calib":(event)=>{ + this.editor.calib.save_calibration(); + return true; + }, + + // "#cfg-reset-calib":(event)=>{ + // this.editor.calib.reset_calibration(); + // return true; + // } + + "#cfg-crop-scene": (event)=>{ + this.editor.cropScene.show(); + + return true; + }, + + }; + + changeableItems = { + + "#cfg-theme-select":(event)=>{ + let theme = event.currentTarget.value; + + //let scheme = document.documentElement.className; + + + document.documentElement.className = "theme-"+theme; + + pointsGlobalConfig.setItem("theme", theme); + + this.editor.viewManager.setColorScheme(); + this.editor.render(); + this.editor.boxEditorManager.render(); + + return false; + }, + + "#cfg-hide-box-checkbox":(event)=>{ + let checked = event.currentTarget.checked; + + //let scheme = document.documentElement.className; + + if (checked) + this.editor.data.set_box_opacity(0); + else + this.editor.data.set_box_opacity(1); + + this.editor.render(); + this.editor.boxEditorManager.render(); + + + return false; + }, + + + "#cfg-hide-id-checkbox":(event)=>{ + let checked = event.currentTarget.checked; + this.editor.floatLabelManager.show_id(!checked); + return false; + }, + + + + "#cfg-hide-category-checkbox":(event)=>{ + let checked = event.currentTarget.checked; + this.editor.floatLabelManager.show_category(!checked); + return false; + }, + + "#cfg-hide-circle-ruler-checkbox": (event)=>{ + let checked = event.currentTarget.checked; + this.editor.showRangeCircle(!checked); + return false; + }, + + "#cfg-auto-rotate-xy-checkbox": (event)=>{ + let checked = event.currentTarget.checked; + pointsGlobalConfig.setItem("enableAutoRotateXY", checked); + return false; + }, + + '#cfg-auto-update-interpolated-boxes-checkbox': (event)=>{ + let checked = event.currentTarget.checked; + pointsGlobalConfig.setItem("autoUpdateInterpolatedBoxes", checked); + return false; + }, + + "#cfg-color-points-select": (event)=>{ + let value = event.currentTarget.value; + pointsGlobalConfig.setItem("color_points", value); + + this.editor.data.worldList.forEach(w=>{ + w.lidar.color_points(); + w.lidar.update_points_color(); + }); + this.editor.render(); + return false; + }, + + "#cfg-color-object-scheme":(event)=>{ + let value = event.currentTarget.value; + this.editor.data.set_obj_color_scheme(value); + this.editor.render(); + this.editor.imageContextManager.render_2d_image(); + + this.editor.floatLabelManager.set_color_scheme(value); + this.editor.render2dLabels(this.editor.data.world); + this.editor.boxEditorManager.render(); + + return false; + }, + + "#cfg-batch-mode-inst-number":(event)=>{ + let batchSize = parseInt(event.currentTarget.value); + + pointsGlobalConfig.setItem("batchModeInstNumber", batchSize); + + this.editor.boxEditorManager.setBatchSize(batchSize); + return false; + }, + + "#cfg-coordinate-system-select": (event)=>{ + let coord = event.currentTarget.value; + pointsGlobalConfig.setItem("coordinateSystem", coord); + + this.editor.data.worldList.forEach(w=>{ + w.calcTransformMatrix(); + }); + this.editor.render(); + }, + + "#cfg-data-aux-lidar-checkbox": (event)=>{ + let checked = event.currentTarget.checked; + + pointsGlobalConfig.setItem("enableAuxLidar", checked); + return false; + }, + + "#cfg-data-radar-checkbox": (event)=>{ + let checked = event.currentTarget.checked; + + pointsGlobalConfig.setItem("enableRadar", checked); + return false; + }, + + "#cfg-data-filter-points-checkbox": (event)=>{ + let checked = event.currentTarget.checked; + + pointsGlobalConfig.setItem("enableFilterPoints", checked); + return false; + }, + + "#cfg-data-filter-points-z": (event)=>{ + let z = event.currentTarget.value; + + pointsGlobalConfig.setItem("filterPointsZ", z); + return false; + }, + + + "#cfg-data-preload-checkbox": (event)=>{ + let checked = event.currentTarget.checked; + pointsGlobalConfig.setItem("enablePreload", checked); + return false; + } + + }; + + ignoreItems = [ + "#cfg-point-size", + "#cfg-point-brightness", + "#cfg-theme", + "#cfg-color-object", + "#cfg-menu-batch-mode-inst-number", + "#cfg-hide-box", + "#cfg-calib-camera-LiDAR", + "#cfg-experimental", + "#cfg-data", + ]; + + subMenus = [ + "#cfg-experimental", + "#cfg-data", + ]; + + constructor(button, wrapper, editor) + { + this.button = button; + this.wrapper = wrapper; + this.editor = editor; + this.editorCfg = editor.editorCfg; + this.dataCfg = editor.data.cfg; + this.menu = this.wrapper.querySelector("#config-menu"); + + this.wrapper.onclick = ()=>{ + this.hide(); + } + + this.button.onclick = (event)=>{ + this.show(event.currentTarget); + } + + for (let item in this.clickableItems) + { + this.menu.querySelector(item).onclick = (event)=>{ + let ret = this.clickableItems[item](event); + if (ret) + { + this.hide(); + } + + event.stopPropagation(); + } + } + + for (let item in this.changeableItems) + { + this.menu.querySelector(item).onchange = (event)=>{ + let ret = this.changeableItems[item](event); + if (ret) + { + this.hide(); + } + + event.stopPropagation(); + } + } + + this.ignoreItems.forEach(item=>{ + this.menu.querySelector(item).onclick = (event)=>{ + { + event.stopPropagation(); + } + } + }); + + this.subMenus.forEach(item=>{ + this.menu.querySelector(item).onmouseenter = function(event){ + if (this.timerId) + { + clearTimeout(this.timerId); + this.timerId = null; + } + + event.currentTarget.querySelector(item +"-submenu").style.display="inherit"; + } + + this.menu.querySelector(item).onmouseleave = function(event){ + let ui = event.currentTarget.querySelector(item +"-submenu"); + this.timerId = setTimeout(()=>{ + ui.style.display="none"; + this.timerId = null; + }, + 200); + } + }); + + this.menu.onclick = (event)=>{ + event.stopPropagation(); + }; + + + + // init ui + this.menu.querySelector("#cfg-theme-select").value = pointsGlobalConfig.theme; + this.menu.querySelector("#cfg-data-aux-lidar-checkbox").checked = pointsGlobalConfig.enableAuxLidar; + this.menu.querySelector("#cfg-data-radar-checkbox").checked = pointsGlobalConfig.enableRadar; + this.menu.querySelector("#cfg-color-points-select").value = pointsGlobalConfig.color_points; + this.menu.querySelector("#cfg-coordinate-system-select").value = pointsGlobalConfig.coordinateSystem; + this.menu.querySelector("#cfg-batch-mode-inst-number").value = pointsGlobalConfig.batchModeInstNumber; + this.menu.querySelector("#cfg-data-filter-points-checkbox").checked = pointsGlobalConfig.enableFilterPoints; + this.menu.querySelector("#cfg-data-filter-points-z").value = pointsGlobalConfig.filterPointsZ; + this.menu.querySelector("#cfg-hide-id-checkbox").value = pointsGlobalConfig.hideId; + this.menu.querySelector("#cfg-hide-category-checkbox").value = pointsGlobalConfig.hideCategory; + this.menu.querySelector("#cfg-data-preload-checkbox").checked = pointsGlobalConfig.enablePreload; + this.menu.querySelector("#cfg-auto-rotate-xy-checkbox").checked = pointsGlobalConfig.enableAutoRotateXY; + this.menu.querySelector("#cfg-auto-update-interpolated-boxes-checkbox").checked = pointsGlobalConfig.autoUpdateInterpolatedBoxes; + } + + + show(target){ + this.wrapper.style.display="inherit"; + + this.menu.style.right = "0px"; + this.menu.style.top = target.offsetHeight + "px"; + + globalKeyDownManager.register((event)=>false, 'config'); + } + + hide(){ + globalKeyDownManager.deregister('config'); + this.wrapper.style.display="none"; + } + +} + + +export {ConfigUi} \ No newline at end of file diff --git a/public/js/context_menu.js b/public/js/context_menu.js new file mode 100644 index 0000000..5cfadde --- /dev/null +++ b/public/js/context_menu.js @@ -0,0 +1,199 @@ +import { globalKeyDownManager } from "./keydown_manager.js"; + + +class ContextMenu { + constructor(ui) + { + this.wrapperUi = ui; + + this.menus = { + world: ui.querySelector("#context-menu"), + object: ui.querySelector("#object-context-menu"), + boxEditor: ui.querySelector("#box-editor-context-menu"), + boxEditorManager: ui.querySelector("#box-editor-manager-context-menu"), + playSubMenu: ui.querySelector("#play-submenu"), + gotoSubMenu: ui.querySelector("#goto-submenu"), + fitSubMenu: ui.querySelector("#cm-fit-submenu"), + //thisSubMenu: ui.querySelector("#cm-this-submenu"), + }; + + for (let m in this.menus){ + for (let i = 0; i < this.menus[m].children.length; i++) + { + this.menus[m].children[i].onclick = (event) => + { + //event.preventDefault(); + event.stopPropagation(); + + let ret = this.handler.handleContextMenuEvent(event); + if (ret) + { + this.hide(); + } + } + } + } + + let motherMenu = { + "#cm-goto": "#goto-submenu", + "#cm-new": "#new-submenu", + "#cm-play": "#play-submenu", + "#cm-fit": "#cm-fit-submenu", + //"#cm-this": "#cm-this-submenu", + }; + + + for (let item in motherMenu) + { + let menu = ui.querySelector(item); + menu.onclick = (event)=>{ + return false; + } + + let self = this; + menu.onmouseenter = function(event){ + if (this.timerId) + { + clearTimeout(this.timerId); + this.timerId = null; + } + + let menu = event.currentTarget.querySelector(motherMenu[item]); + menu.style.display="inherit"; + + let motherMenuRect = event.currentTarget.getBoundingClientRect(); + let posX = motherMenuRect.right; + let posY = motherMenuRect.bottom; + + if (self.wrapperUi.clientHeight < posY + menu.clientHeight){ + menu.style.bottom = "0%"; + menu.style.top = ""; + } + else{ + menu.style.top = "0%"; + menu.style.bottom = ""; + } + + + if (self.wrapperUi.clientWidth < posX + menu.clientWidth){ + menu.style.right = "100%"; + menu.style.left = ""; + } + else{ + menu.style.left = "100%"; + menu.style.right = ""; + } + } + + menu.onmouseleave = function(event){ + let ui = event.currentTarget.querySelector(motherMenu[item]); + this.timerId = setTimeout(()=>{ + ui.style.display="none"; + this.timerId = null; + }, + 200); + } + } + + + this.wrapperUi.onclick = (event)=>{ + this.hide(); + event.preventDefault(); + event.stopPropagation(); + }; + + this.wrapperUi.oncontextmenu = (event)=>{ + //event.currentTarget.style.display="none"; + event.preventDefault(); + event.stopPropagation(); + }; + } + + // install dynamic menu, like object new + installMenu(name, ui, funcHandler) + { + this.menus[name] = ui; + + for (let i = 0; i < ui.children.length; i++){ + ui.children[i].onclick = (event) => + { + //event.preventDefault(); + event.stopPropagation(); + + let ret = funcHandler(event); + if (ret) + { + this.hide(); + } + } + } + } + + hide() + { + this.wrapperUi.style.display = "none"; + globalKeyDownManager.deregister('context menu'); + + } + + show(name, posX, posY, handler, funcSetPos) + { + this.handler = handler; + + //hide all others + for (let m in this.menus) { + if (m !== name) + this.menus[m].style.display = 'none'; + } + + // show + this.wrapperUi.style.display = "block"; + + let menu = this.menus[name] + menu.style.display = "inherit"; + + this.currentMenu = menu; + + if (funcSetPos) + { + funcSetPos(menu); + } + else{ + + if (this.wrapperUi.clientHeight < posY + menu.clientHeight){ + menu.style.top = (this.wrapperUi.clientHeight - menu.clientHeight) + "px"; + } + else{ + menu.style.top = posY+"px"; + } + + + if (this.wrapperUi.clientWidth < posX + menu.clientWidth){ + menu.style.left = (this.wrapperUi.clientWidth - menu.clientWidth) + "px"; + } + else{ + menu.style.left = posX+"px"; + } + + } + + + globalKeyDownManager.register((event)=>{ + + let menuRect = this.currentMenu.getBoundingClientRect(); + let ret = this.handler.handleContextMenuKeydownEvent(event, + {x: menuRect.left, y: menuRect.top}); + if (!ret) + { + this.hide(); + } + + return false; // false means don't propogate + }, 'context menu'); + + } + + +}; + +export {ContextMenu}; diff --git a/public/js/crop_scene.js b/public/js/crop_scene.js new file mode 100644 index 0000000..d967b05 --- /dev/null +++ b/public/js/crop_scene.js @@ -0,0 +1,64 @@ +import { PopupDialog } from "./popup_dialog.js"; + + +class CropScene extends PopupDialog{ + + + + + constructor(ui, editor) + { + super(ui); + + this.ui = ui; //wrapper + this.editor = editor; + + this.contentUi = this.ui.querySelector("#content"); + + + let self = this; + + this.ui.querySelector("#btn-generate").onclick = (event)=>{ + var xhr = new XMLHttpRequest(); + // we defined the xhr + xhr.onreadystatechange = function () { + if (this.readyState != 4) return; + + if (this.status == 200) { + let ret = JSON.parse(this.responseText); + self.contentUi.querySelector("#log").innerText = JSON.stringify(ret, null,"\t"); + } + }; + + xhr.open('POST', "/cropscene", true); + + let para={ + rawSceneId: this.editor.data.world.frameInfo.scene, + //id: this.ui.querySelector("#scene-id").value, + desc: this.ui.querySelector("#scene-desc").value, + startTime: this.ui.querySelector("#scene-start-time").value, + seconds: this.ui.querySelector("#scene-seconds").value + }; + + xhr.send(JSON.stringify(para)); + } + } + + + + show() + { + + let frameInfo = this.editor.data.world.frameInfo; + this.ui.querySelector("#scene-start-time").value=parseInt(frameInfo.frame)-10; + this.ui.querySelector("#scene-seconds").value=20; + this.contentUi.querySelector("#log").innerText = ""; + super.show(); + } + + + +} + + +export {CropScene}; \ No newline at end of file diff --git a/public/js/data.js b/public/js/data.js new file mode 100644 index 0000000..043ddda --- /dev/null +++ b/public/js/data.js @@ -0,0 +1,468 @@ + + +import {World} from "./world.js"; +import {Debug} from "./debug.js"; +import {logger} from "./log.js" + +class Data +{ + + constructor(cfg) + { + this.cfg = cfg; + + } + + async readSceneList() + { + const req = new Request("/get_all_scene_desc"); + let init = { + method: 'GET', + //body: JSON.stringify({"points": data}) + }; + // we defined the xhr + + return fetch(req, init) + .then(response=>{ + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + }else{ + return response.json(); + } + }) + .then(ret=> + { + console.log(ret); + this.sceneDescList = ret; + return ret; + }) + .catch(reject=>{ + console.log("error read scene list!"); + }); + } + + async init(){ + await this.readSceneList(); + } + + // multiple world support + // place world by a offset so they don't overlap + dbg = new Debug(); + + worldGap=1000.0; + worldList=[]; + MaxWorldNumber=80; + createWorldIndex = 0; // this index shall not repeat, so it increases permanently + + async getWorld(sceneName, frame, on_preload_finished){ + // find in list + + if (!this.meta[sceneName]){ + await this.readSceneMetaData(sceneName) + } + + if (!this.meta[sceneName]) + { + logger.log("load scene failed", sceneName); + return null; + } + + let world = this.worldList.find((w)=>{ + return w.frameInfo.scene == sceneName && w.frameInfo.frame == frame; + }) + if (world) // found! + return world; + + + world = this._createWorld(sceneName, frame, on_preload_finished); + + return world; + }; + + _createWorld(sceneName, frame, on_preload_finished){ + + let [x,y,z] = this.allocateOffset(); + console.log("create world",x,y,z); + let world = new World(this, sceneName, frame, [this.worldGap*x, this.worldGap*y, this.worldGap*z], on_preload_finished); + world.offsetIndex = [x,y,z]; + this.createWorldIndex++; + this.worldList.push(world); + + return world; + + }; + + findWorld(sceneName, frameIndex){ + let world = this.worldList.find((w)=>{ + return w.frameInfo.scene == sceneName && w.frameInfo.frame_index == frameIndex; + }) + if (world) // found! + return world; + else + return null; + }; + + offsetList = [[0,0,0]]; + lastSeedOffset = [0,0,0]; + offsetsAliveCount = 0; + allocateOffset() + { + + // we need to make sure the first frame loaded in a scene + // got to locate in [0,0,0] + + if (this.offsetsAliveCount == 0) + { + //reset offsets. + this.offsetList = [[0,0,0]]; + this.lastSeedOffset = [0,0,0]; + } + + + + if (this.offsetList.length == 0) + { + let [x,y,z] = this.lastSeedOffset; + + if (x == y) + { + x = x+1; + y = 0; + } + else + { + y = y+1; + } + + this.lastSeedOffset = [x, y, 0]; + + this.offsetList.push([x,y,0]); + + if (x != 0) this.offsetList.push([-x,y,0]); + if (y != 0) this.offsetList.push([x,-y,0]); + if (x * y != 0) this.offsetList.push([-x,-y,0]); + + if (x != y) { + this.offsetList.push([y,x,0]); + + if (y != 0) this.offsetList.push([-y,x,0]); + if (x != 0) this.offsetList.push([y,-x,0]); + if (x * y != 0) this.offsetList.push([-y,-x,0]); + } + } + + let ret = this.offsetList.pop(); + this.offsetsAliveCount++; + + return ret; + }; + + returnOffset(offset) + { + this.offsetList.push(offset); + this.offsetsAliveCount--; + }; + + deleteDistantWorlds(world){ + let currentWorldIndex = world.frameInfo.frame_index; + + let disposable = (w)=>{ + let distant = Math.abs(w.frameInfo.frame_index - currentWorldIndex)>this.MaxWorldNumber; + let active = w.everythingDone; + if (w.annotation.modified) + { + console.log("deleting world not saved. stop."); + } + + return distant && !active && !w.annotation.modified; + } + + let distantWorldList = this.worldList.filter(w=>disposable(w)); + + distantWorldList.forEach(w=>{ + this.returnOffset(w.offsetIndex); + w.deleteAll(); + }); + + + this.worldList = this.worldList.filter(w=>!disposable(w)); + + }; + + deleteOtherWorldsExcept=function(keepScene){ + // release resources if scene changed + this.worldList.forEach(w=>{ + if (w.frameInfo.scene != keepScene){ + this.returnOffset(w.offsetIndex); + w.deleteAll(); + + this.removeRefEgoPoseOfScene(w.frameInfo.scene); + } + }) + this.worldList = this.worldList.filter(w=>w.frameInfo.scene==keepScene); + }; + + + refEgoPose={}; + getRefEgoPose(sceneName, currentPose) + { + if (this.refEgoPose[sceneName]){ + return this.refEgoPose[sceneName]; + } + else{ + this.refEgoPose[sceneName] = currentPose; + return currentPose; + } + } + + removeRefEgoPoseOfScene(sceneName) + { + if (this.refEgoPose[sceneName]) + delete this.refEgoPose[sceneName]; + } + + forcePreloadScene(sceneName, currentWorld){ + //this.deleteOtherWorldsExcept(sceneName); + let meta = currentWorld.sceneMeta; + + let currentWorldIndex = currentWorld.frameInfo.frame_index; + let startIndex = Math.max(0, currentWorldIndex - this.MaxWorldNumber/2); + let endIndex = Math.min(meta.frames.length, startIndex + this.MaxWorldNumber); + + this._doPreload(sceneName, startIndex, endIndex); + + logger.log(`${endIndex - startIndex} frames created`); + } + + preloadScene(sceneName, currentWorld){ + + // clean other scenes. + this.deleteOtherWorldsExcept(sceneName); + this.deleteDistantWorlds(currentWorld); + + if (!this.cfg.enablePreload) + return; + + this.forcePreloadScene(sceneName, currentWorld); + + }; + + _doPreload(sceneName, startIndex, endIndex) + { + let meta = this.getMetaBySceneName(sceneName); + + let numLoaded = 0; + let _need_create = (frame)=>{ + let world = this.worldList.find((w)=>{ + return w.frameInfo.scene == sceneName && w.frameInfo.frame == frame; + }) + + return !world; + } + + let _do_create = (frame)=>{ + this._createWorld(sceneName, frame); + numLoaded++; + }; + + let pendingFrames = meta.frames.slice(startIndex, endIndex).filter(_need_create); + + logger.log(`preload ${meta.scene} ${pendingFrames}`); + // if (numLoaded > 0){ + // meta.frames.slice(endIndex, Math.min(endIndex+5, meta.frames.length)).forEach(_do_create); + // meta.frames.slice(Math.max(0, startIndex-5), startIndex).forEach(_do_create); + // } + + pendingFrames.forEach(_do_create); + } + + + reloadAllAnnotation=function(done){ + this.worldList.forEach(w=>w.reloadAnnotation(done)); + }; + + onAnnotationUpdatedByOthers(scene, frames){ + frames.forEach(f=>{ + let world = this.worldList.find(w=>(w.frameInfo.scene==scene && w.frameInfo.frame==f)); + if (world) + world.annotation.reloadAnnotation(); + }) + }; + + webglScene = null; + set_webglScene=function(scene, mainScene){ + this.webglScene = scene; + this.webglMainScene = mainScene; + }; + + scale_point_size(v){ + this.cfg.point_size *= v; + // if (this.world){ + // this.world.lidar.set_point_size(this.cfg.point_size); + // } + + this.worldList.forEach(w=>{ + w.lidar.set_point_size(this.cfg.point_size); + }); + }; + + scale_point_brightness(v){ + this.cfg.point_brightness *= v; + + // if (this.world){ + // this.world.lidar.recolor_all_points(); + // } + + this.worldList.forEach(w=>{ + w.lidar.recolor_all_points(); + }) + }; + + set_box_opacity(opacity){ + this.cfg.box_opacity = opacity; + + this.worldList.forEach(w=>{ + w.annotation.set_box_opacity(this.cfg.box_opacity); + }); + }; + + toggle_background(){ + this.cfg.show_background = !this.cfg.show_background; + + if (this.cfg.show_background){ + this.world.lidar.cancel_highlight(); + } + else{ + this.world.lidar.hide_background(); + } + }; + + set_obj_color_scheme(scheme){ + + + pointsGlobalConfig.color_obj = scheme; + + // if (pointsGlobalConfig.color_obj != "no"){ + // this.world.lidar.color_points(); + // } else { + // this.world.lidar.set_points_color({ + // x: this.cfg.point_brightness, + // y: this.cfg.point_brightness, + // z: this.cfg.point_brightness, + // }); + // } + + // this.world.lidar.update_points_color(); + // this.world.annotation.color_boxes(); + + + // toto: move to world + this.worldList.forEach(w=>{ + if (pointsGlobalConfig.color_obj == "no") + { + w.lidar.color_points(); + } + else + { + w.lidar.color_objects(); + } + + w.lidar.update_points_color(); + + w.annotation.color_boxes(); + }) + }; + + // active_camera_name = ""; + + // // return null means not changed. + // set_active_image(name){ + // if (name === this.active_camera_name){ + // return null; + // } + + // this.active_camera_name = name; + // if (this.world){ + // this.world.cameras.activate(name); + // } + // this.worldList.forEach(w=>w.cameras.activate(name)); + + // return name; + // }; + + world=null; + + // this.future_world_buffer = []; + // this.put_world_into_buffer= function(world){ + // this.future_world_buffer.push(world); + // }; + + // this.reset_world_buffer= function(){ + // this.future_world_buffer=[]; + // }; + + // this.activateMultiWorld=function(world, on_finished){ + // world.activate(this.webglScene, + // null, //don't destroy old world + // on_finished); + // this.worldList.push(world); + // }; + + activate_world= function(world, on_finished, dontDestroyOldWorld){ + + if (dontDestroyOldWorld){ + world.activate(this.webglScene, null, on_finished); + } + else{ + var old_world = this.world; // current world, should we get current world later? + this.world = world; // swich when everything is ready. otherwise data.world is half-baked, causing mysterious problems. + + world.activate(this.webglMainScene, + function(){ + if (old_world) + old_world.unload(); + }, + on_finished); + } + }; + + + meta = {}; //meta data + + getMetaBySceneName = (sceneName)=>{ + return this.meta[sceneName]; + }; + + + get_current_world_scene_meta(){ + return this.getMetaBySceneName(this.world.frameInfo.scene); + }; + + + readSceneMetaData(sceneName) + { + let self =this; + return new Promise(function(resolve, reject){ + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function () { + if (this.readyState != 4) + return; + + if (this.status == 200) { + let sceneMeta = JSON.parse(this.responseText); + self.meta[sceneName] = sceneMeta; + resolve(sceneMeta); + } + + }; + + xhr.open('GET', `/scenemeta?scene=${sceneName}`, true); + xhr.send(); + }); + } +}; + + +export {Data}; + diff --git a/public/js/debug.js b/public/js/debug.js new file mode 100644 index 0000000..5850cb1 --- /dev/null +++ b/public/js/debug.js @@ -0,0 +1,18 @@ +function Debug(){ + this.res_count = 0; + + this.alloc = function(){ + this.res_count++; + }; + + this.free = function(){ + this.res_count--; + }; + + this.dump = function(){ + console.log(`number of resources: ${this.res_count}`); + } +}; + +export {Debug}; + diff --git a/public/js/editor.js b/public/js/editor.js new file mode 100644 index 0000000..8ea6b39 --- /dev/null +++ b/public/js/editor.js @@ -0,0 +1,2683 @@ +import * as THREE from './lib/three.module.js'; + +import {ViewManager} from "./view.js"; +import {FastToolBox, FloatLabelManager} from "./floatlabel.js"; +import {Mouse} from "./mouse.js"; +import {BoxEditor, BoxEditorManager} from "./box_editor.js"; +import {ImageContextManager} from "./image.js"; +import {globalObjectCategory} from "./obj_cfg.js"; + +import {objIdManager} from "./obj_id_list.js"; +import {Header} from "./header.js"; +import {BoxOp} from './box_op.js'; +import {AutoAdjust} from "./auto-adjust.js"; +import {PlayControl} from "./play.js"; +import {reloadWorldList, saveWorldList} from "./save.js"; +import {logger, create_logger} from "./log.js"; +import {autoAnnotate} from "./auto_annotate.js"; +import {Calib} from "./calib.js"; +import {Trajectory} from "./trajectory.js"; +import { ContextMenu } from './context_menu.js'; +import { InfoBox } from './info_box.js'; +import {CropScene} from './crop_scene.js'; +import { ConfigUi } from './config_ui.js'; +import { MovableView } from './popup_dialog.js'; +import {globalKeyDownManager} from './keydown_manager.js'; +import {vector_range} from "./util.js" +import { checkScene } from './error_check.js'; + +function Editor(editorUi, wrapperUi, editorCfg, data, name="editor"){ + + // create logger before anything else. + create_logger(editorUi.querySelector("#log-wrapper"), editorUi.querySelector("#log-button")); + this.logger = logger; + + this.editorCfg = editorCfg; + this.sideview_enabled = true; + this.editorUi = editorUi; + this.wrapperUi = wrapperUi; + this.container = null; + this.name = name; + + this.data = data; + this.scene = null; + this.renderer = null; + this.selected_box = null; + this.windowWidth = null; + this.windowHeight= null; + this.floatLabelManager = null; + this.operation_state = { + key_pressed : false, + box_navigate_index:0, + }; + this.view_state = { + lock_obj_track_id : "", + lock_obj_in_highlight : false, // focus mode + autoLock: function(trackid, focus){ + this.lock_obj_track_id = trackid; + this.lock_obj_in_highlight = focus; + } + }; + this.calib = new Calib(this.data, this); + + this.header = null; + this.imageContextManager = null; + this.boxOp = null; + this.boxEditorManager = null; + this.params={}; + + this.currentMainEditor = this; // who is on focus, this or batch-editor-manager? + + this.init = function(editorUi) { + + let self = this; + this.editorUi = editorUi; + + + + + this.playControl = new PlayControl(this.data); + + this.configUi = new ConfigUi(editorUi.querySelector("#config-button"), editorUi.querySelector("#config-wrapper"), this); + + this.header = new Header(editorUi.querySelector("#header"), this.data, this.editorCfg, + (e)=>{ + this.scene_changed(e.currentTarget.value); + //event.currentTarget.blur(); + }, + (e)=>{this.frame_changed(e)}, + (e)=>{this.object_changed(e)}, + (e)=>{this.camera_changed(e)} + ); + + + // + // that way, the operation speed may be better + // if we load all worlds, we can speed up batch-mode operations, but the singl-world operations slows down. + // if we use two seperate scenes. can we solve this problem? + // + this.scene = new THREE.Scene(); + this.mainScene = this.scene; //new THREE.Scene(); + + this.data.set_webglScene(this.scene, this.mainScene); + + + + this.renderer = new THREE.WebGLRenderer( { antialias: true, preserveDrawingBuffer: true } ); + this.renderer.setPixelRatio( window.devicePixelRatio ); + + this.container = editorUi.querySelector("#container"); + this.container.appendChild( this.renderer.domElement ); + + + + this.boxOp = new BoxOp(this.data); + this.viewManager = new ViewManager(this.container, this.scene, this.mainScene, this.renderer, + function(){self.render();}, + function(box){self.on_box_changed(box)}, + this.editorCfg); + + + this.imageContextManager = new ImageContextManager( + this.editorUi.querySelector("#content"), + this.editorUi.querySelector("#camera-selector"), + this.editorCfg, + (lidar_points)=>this.on_img_click(lidar_points)); + + + if (!this.editorCfg.disableRangeCircle) + this.addRangeCircle(); + + this.floatLabelManager = new FloatLabelManager(this.editorUi, this.container, this.viewManager.mainView,function(box){self.selectBox(box);}); + this.fastToolBox = new FastToolBox(this.editorUi.querySelector("#obj-editor"), (event)=>this.handleFastToolEvent(event)); + //this.controlGui = this.init_gui(); + + this.axis = new THREE.AxesHelper(1); + + this.scene.add(this.axis); + + window.addEventListener( 'resize', function(){self.onWindowResize();}, false ); + + + if (!this.editorCfg.disableMainViewKeyDown){ + // this.container.onmouseenter = (event)=>{ + // this.container.focus(); + // }; + + // this.container.onmouseleave = (event)=>{ + // this.container.blur(); + // }; + + //this.container.addEventListener( 'keydown', function(e){self.keydown(e);} ); + //this.editorUi.addEventListener( 'keydown', e=>this.keydown(e); ); + + this.keydownHandler = (event)=>this.keydown(event); + //this.keydownDisabled = false; + //document.removeEventListener('keydown', this.keydownHandler); + //document.addEventListener( 'keydown', this.keydownHandler); + globalKeyDownManager.register(this.keydownHandler, "main editor"); + } + + this.globalKeyDownManager = globalKeyDownManager; + + this.objectTrackView = new Trajectory( + this.editorUi.querySelector("#object-track-wrapper") + ); + + this.infoBox = new InfoBox( + this.editorUi.querySelector("#info-wrapper") + ); + + this.cropScene = new CropScene( + this.editorUi.querySelector("#crop-scene-wrapper"), + this + ); + + this.contextMenu = new ContextMenu(this.editorUi.querySelector("#context-menu-wrapper")); + + + + this.boxEditorManager = new BoxEditorManager( + document.querySelector("#batch-box-editor"), + this.viewManager, + this.objectTrackView, + this.editorCfg, + this.boxOp, + this.header, + this.contextMenu, + this.configUi, + (b)=>this.on_box_changed(b), + (b,r)=>this.remove_box(b,r), // on box remove + ()=>{ + // this.on_load_world_finished(this.data.world); + // this.imageContextManager.hide(); + // this.floatLabelManager.hide(); + + // this.viewManager.mainView.disable(); + // this.boxEditor.hide(); + // this.hideGridLines(); + // this.controlGui.hide(); + + }); //func_on_annotation_reloaded + this.boxEditorManager.hide(); + + let boxEditorUi = this.editorUi.querySelector("#main-box-editor-wrapper"); + this.boxEditor= new BoxEditor( + boxEditorUi, + null, // no box editor manager + this.viewManager, + this.editorCfg, + this.boxOp, + (b)=>this.on_box_changed(b), + (b)=>this.remove_box(b), + "main-boxe-ditor"); + this.boxEditor.detach(); // hide it + this.boxEditor.setResize("both"); + this.boxEditor.moveHandle = new MovableView( + boxEditorUi.querySelector("#focuscanvas"), + boxEditorUi.querySelector("#sub-views"), + ()=>{ + this.boxEditor.update(); + this.render(); + } + ); + + this.mouse = new Mouse( + this.viewManager.mainView, + this.operation_state, + this.container, + this.editorUi, + function(ev){self.handleLeftClick(ev);}, + function(ev){self.handleRightClick(ev);}, + function(x,y,w,h,ctl,shift){self.handleSelectRect(x,y,w,h,ctl,shift);}); + + this.autoAdjust=new AutoAdjust(this.boxOp, this.mouse, this.header); + + + //this.projectiveViewOps.hide(); + + if (!this.editorCfg.disableGrid) + this.installGridLines() + + window.onbeforeunload = function() { + return "Exit?"; + //if we return nothing here (just calling return;) then there will be no pop-up question at all + //return; + }; + + this.onWindowResize(); + }; + + + + this.run = function(){ + //this.animate(); + this.render(); + //$( "#maincanvas" ).resizable(); + + + this.imageContextManager.init_image_op(()=>this.selected_box); + + this.add_global_obj_type(); + }; + + this.hide = function(){ + this.wrapperUi.style.display="none"; + }; + this.show = function(){ + this.wrapperUi.style.display="block"; + }; + + + + + this.moveRangeCircle = function(world){ + if (this.rangeCircle.parent){ + world.webglGroup.add(this.rangeCircle); + } + }; + + this.addRangeCircle= function(){ + + var h = 1; + + var body = [ + ]; + + var segments=64; + for (var i = 0; ithis.on_box_changed(b)); + this.boxOp.auto_rotate_xyz(this.selected_box, null, null, + (b)=>this.on_box_changed(b), + "noscaling"); + //event.currentTarget.blur(); + break; + + case "label-batchedit": + { + + if (!this.ensureBoxTrackIdExist()) + break; + + if (!this.ensurePreloaded()) + break; + + this.header.setObject(this.selected_box.obj_track_id); + this.editBatch( + this.data.world.frameInfo.scene, + this.data.world.frameInfo.frame, + this.selected_box.obj_track_id, + this.selected_box.obj_type + ); + } + break; + + + case "label-trajectory": + this.showTrajectory(); + break; + + case "label-edit": + event.currentTarget.blur(); + self.selectBox(self.selected_box); + break; + + + // case "label-reset": + // event.currentTarget.blur(); + // if (self.selected_box){ + // //switch_bbox_type(this.selected_box.obj_type); + // self.transform_bbox("reset"); + // } + // break; + + case "label-highlight": + event.currentTarget.blur(); + if (self.selected_box.in_highlight){ + self.cancelFocus(self.selected_box); + self.view_state.lock_obj_in_highlight = false + } + else { + self.focusOnSelectedBox(self.selected_box); + } + break; + + case "label-rotate": + event.currentTarget.blur(); + self.transform_bbox("z_rotate_reverse"); + break; + + case "object-category-selector": + this.object_category_changed(event); + break; + case "object-track-id-editor": + this.object_track_id_changed(event); + break; + case "attr-input": + this.object_attribute_changed(event.currentTarget.value); + break; + default: + this.handleContextMenuEvent(event); + break; + } + + }; + + this.cancelFocus= function(box){ + + box.in_highlight = false; + //view_state.lock_obj_in_highlight = false; // when user unhighlight explicitly, set it to false + this.data.world.lidar.cancel_highlight(box); + this.floatLabelManager.restore_all(); + + this.viewManager.mainView.save_orbit_state(box.scale); + this.viewManager.mainView.orbit.reset(); + }; + + this.focusOnSelectedBox = function(box){ + if (this.editorCfg.disableMainView) + return; + + if (box){ + this.data.world.lidar.highlight_box_points(box); + + this.floatLabelManager.hide_all(); + this.viewManager.mainView.orbit.saveState(); + + //this.viewManager.mainView.camera.position.set(this.selected_box.position.x+this.selected_box.scale.x*3, this.selected_box.position.y+this.selected_box.scale.y*3, this.selected_box.position.z+this.selected_box.scale.z*3); + + let posG = this.data.world.lidarPosToScene(box.position); + this.viewManager.mainView.orbit.target.x = posG.x; + this.viewManager.mainView.orbit.target.y = posG.y; + this.viewManager.mainView.orbit.target.z = posG.z; + + this.viewManager.mainView.restore_relative_orbit_state(box.scale); + this.viewManager.mainView.orbit.update(); + + this.render(); + box.in_highlight=true; + this.view_state.lock_obj_in_highlight = true; + } + }; + + this.showTrajectory = function(){ + + if (!this.selected_box) + return; + + if (!this.selected_box.obj_track_id){ + console.error("no track id"); + return; + } + + let tracks = this.data.worldList.map(w=>{ + let box = w.annotation.findBoxByTrackId(this.selected_box.obj_track_id); + let ann = null; + if (box){ + ann = w.annotation.boxToAnn(box); + ann.psr.position = w.lidarPosToUtm(ann.psr.position); + ann.psr.rotation = w.lidarRotToUtm(ann.psr.rotation); + } + return [w.frameInfo.frame, ann, w===this.data.world] + }); + + + tracks.sort((a,b)=> (a[0] > b[0])? 1 : -1); + + this.objectTrackView.setObject( + this.selected_box.obj_type, + this.selected_box.obj_track_id, + tracks, + (targetFrame)=>{ //onExit + this.load_world(this.data.world.frameInfo.scene, targetFrame); + } + ); + } + + // return true to close contextmenu + // return false to keep contextmenu + this.handleContextMenuEvent = function(event){ + + switch(event.currentTarget.id) + { + + case "cm-play-2fps": + this.playControl.play((w)=>{this.on_load_world_finished(w)}, 2); + break; + case "cm-play-10fps": + this.playControl.play((w)=>{this.on_load_world_finished(w)}, 10); + break; + case "cm-play-20fps": + this.playControl.play((w)=>{this.on_load_world_finished(w)}, 20); + break; + case "cm-play-50fps": + this.playControl.play((w)=>{this.on_load_world_finished(w)}, 50); + break; + case 'cm-paste': + { + let box = this.add_box_on_mouse_pos_by_ref(); + + if (!event.shiftKey) + { + logger.log('paste without auto-adjusting'); + this.boxOp.auto_rotate_xyz(box, null, null, + b=>this.on_box_changed(b), + "noscaling"); + } + } + break; + case 'cm-prev-frame': + this.previous_frame(); + break; + case 'cm-next-frame': + this.next_frame(); + break; + case 'cm-last-frame': + this.last_frame(); + break; + case 'cm-first-frame': + this.first_frame(); + break; + case 'cm-go-to-10hz': + this.load_world(this.data.world.frameInfo.scene+"_10hz", this.data.world.frameInfo.frame) + + // { + // let link = document.createElement("a"); + // //link.download=`${this.data.world.frameInfo.scene}-${this.data.world.frameInfo.frame}-webgl`; + // link.href="http://localhost"; + // link.target="_blank"; + // link.click(); + // } + break; + case 'cm-go-to-full-2hz': + this.load_world(this.data.world.frameInfo.scene+"_full_2hz", this.data.world.frameInfo.frame) + break; + + case 'cm-go-to-2hz': + this.load_world(this.data.world.frameInfo.scene.split("_")[0], this.data.world.frameInfo.frame) + break; + + + + case 'cm-save': + saveWorldList(this.data.worldList); + break; + + case "cm-reload": + { + reloadWorldList([this.data.world], ()=>{ + this.on_load_world_finished(this.data.world); + this.header.updateModifiedStatus(); + }); + + } + break; + + case "cm-reload-all": + { + let modifiedFrames = this.data.worldList.filter(w=>w.annotation.modified); + + if (modifiedFrames.length > 0) + { + this.infoBox.show( + "Confirm", + `Discard changes to ${modifiedFrames.length} frames, continue to reload?`, + ["yes","no"], + (choice)=>{ + if (choice=="yes") + { + reloadWorldList(this.data.worldList, ()=>{ + this.on_load_world_finished(this.data.world); + this.header.updateModifiedStatus(); + }); + } + } + ); + } + else + { + reloadWorldList(this.data.worldList, ()=>{ + this.on_load_world_finished(this.data.world); + this.header.updateModifiedStatus(); + }); + + objIdManager.forceUpdate(); + } + } + break; + + + case "cm-stop": + this.playControl.stop_play(); + break; + case "cm-pause": + this.playControl.pause_resume_play(); + break; + + case "cm-prev-object": + this.select_previous_object(); + break; + + case "cm-next-object": + this.select_previous_object(); + break; + + case "cm-show-frame-info": + { + let info = {"scend-id": this.data.world.frameInfo.scene, + "frame": this.data.world.frameInfo.frame + }; + + if (this.data.world.frameInfo.sceneMeta.desc) + { + info = { + ...info, + ...this.data.world.frameInfo.sceneMeta.desc, + }; + } + + this.infoBox.show("Frame info - " + this.data.world.frameInfo.scene, JSON.stringify(info,null,"
")); + } + break; + + case "cm-show-stat": + { + let scene = this.data.world.frameInfo.scene; + objIdManager.load_obj_ids_of_scene(scene, (objs)=>{ + let info = { + objects: objs.length, + boxes: objs.reduce((a,b)=>a+b.count, 0), + frames: this.data.world.frameInfo.sceneMeta.frames.length, + }; + + this.infoBox.show("Stat - " + scene, JSON.stringify(info, null,"
")); + }); + } + break; + /// object + + case 'cm-check-scene': + { + let scene = this.data.world.frameInfo.scene; + checkScene(scene); + logger.show(); + logger.errorBtn.onclick(); + } + break; + case "cm-reset-view": + this.resetView(); + break; + case "cm-delete": + this.remove_selected_box(); + this.header.updateModifiedStatus(); + break; + + case "cm-edit-multiple-instances": + this.enterBatchEditMode(); + + break; + case "cm-auto-ann-background": + { + this.autoAnnInBackground(); + + } + break; + case "cm-interpolate-background": + { + this.interpolateInBackground(); + } + break; + case "cm-show-trajectory": + + this.showTrajectory(); + break; + + case "cm-select-as-ref": + if (!this.selected_box.obj_track_id) + { + this.infoBox.show("Error", "Please assign object track ID."); + return false; + } + else + { + this.autoAdjust.mark_bbox(this.selected_box); + } + break; + + case "cm-change-id-to-ref": + if (!this.ensureRefObjExist()) + break; + + this.setObjectId(this.autoAdjust.marked_object.ann.obj_id); + this.fastToolBox.setValue(this.selected_box.obj_type, + this.selected_box.obj_track_id, + this.selected_box.obj_attr); + + break; + case "cm-change-id-to-ref-in-scene": + + if (!this.ensureBoxTrackIdExist()) + break; + if (!this.ensurePreloaded()) + break; + if (!this.ensureRefObjExist()) + break; + + this.data.worldList.forEach(w=>{ + let box = w.annotation.boxes.find(b=>b.obj_track_id === this.selected_box.obj_track_id && b.obj_type === this.selected_box.obj_type); + if (box && box !== this.selected_box){ + box.obj_track_id = this.autoAdjust.marked_object.ann.obj_id; + w.annotation.setModified(); + } + }); + + + this.setObjectId(this.autoAdjust.marked_object.ann.obj_id); + this.fastToolBox.setValue(this.selected_box.obj_type, + this.selected_box.obj_track_id, + this.selected_box.obj_attr); + + break; + case "cm-follow-ref": + + if (!this.ensureBoxTrackIdExist()) + break; + if (!this.ensurePreloaded()) + break; + this.autoAdjust.followsRef(this.selected_box); + this.header.updateModifiedStatus(); + this.editBatch( + this.data.world.frameInfo.scene, + this.data.world.frameInfo.frame, + this.selected_box.obj_track_id, + this.selected_box.obj_type + ); + break; + case 'cm-follow-static-objects': + if (!this.ensureBoxTrackIdExist()) + break; + if (!this.ensurePreloaded()) + break; + this.autoAdjust.followStaticObjects(this.selected_box); + this.header.updateModifiedStatus(); + + this.editBatch( + this.data.world.frameInfo.scene, + this.data.world.frameInfo.frame, + this.selected_box.obj_track_id, + this.selected_box.obj_type + ); + + break; + case "cm-sync-followers": + + if (!this.ensurePreloaded()) + break; + this.autoAdjust.syncFollowers(this.selected_box); + this.header.updateModifiedStatus(); + this.render(); + break; + + + case "cm-delete-obj": + { + //let saveList=[]; + this.data.worldList.forEach(w=>{ + let box = w.annotation.boxes.find(b=>b.obj_track_id === this.selected_box.obj_track_id); + if (box && box !== this.selected_box){ + w.annotation.unload_box(box); + w.annotation.remove_box(box); + //saveList.push(w); + w.annotation.setModified(); + } + }); + + //saveWorldList(saveList); + this.remove_selected_box(); + this.header.updateModifiedStatus(); + } + break; + + case "cm-modify-obj-type": + { + if (!this.ensurePreloaded()) + break; + //let saveList=[]; + this.data.worldList.forEach(w=>{ + let box = w.annotation.boxes.find(b=>b.obj_track_id === this.selected_box.obj_track_id); + if (box && box !== this.selected_box){ + box.obj_type = this.selected_box.obj_type; + box.obj_attr = this.selected_box.obj_attr; + //saveList.push(w); + w.annotation.setModified(); + } + + }); + + //saveWorldList(saveList); + this.header.updateModifiedStatus(); + } + break; + + case "cm-modify-obj-size": + { + if (!this.ensurePreloaded()) + break; + //let saveList=[]; + this.data.worldList.forEach(w=>{ + let box = w.annotation.boxes.find(b=>b.obj_track_id == this.selected_box.obj_track_id); + if (box && box !== this.selected_box){ + box.scale.x = this.selected_box.scale.x; + box.scale.y = this.selected_box.scale.y; + box.scale.z = this.selected_box.scale.z; + //saveList.push(w); + + w.annotation.setModified(); + } + + }); + + //saveWorldList(saveList); + this.header.updateModifiedStatus(); + } + break; + + + default: + console.log('unhandled', event.currentTarget.id, event.type); + } + + return true; + }; + + // this.animate= function() { + // let self=this; + // requestAnimationFrame( function(){self.animate();} ); + // this.viewManager.mainView.orbit_orth.update(); + // }; + + + + this.render= function(){ + + this.viewManager.mainView.render(); + this.boxEditor.boxView.render(); + + this.floatLabelManager.update_all_position(); + if (this.selected_box){ + this.fastToolBox.setPos(this.floatLabelManager.getLabelEditorPos(this.selected_box.obj_local_id)); + } + }; + + + this.resetView = function(targetPos){ + + if (!targetPos){ + let center = this.data.world.lidar.computeCenter(); + targetPos = {...center};//{x:0, y:0, z:50}; + targetPos.z += 50; + } + else + targetPos.z = 50; + + let pos = this.data.world.lidarPosToScene(targetPos); + this.viewManager.mainView.orbit.object.position.set(pos.x, pos.y, pos.z); //object is camera + this.viewManager.mainView.orbit.target.set(pos.x, pos.y, 0); + this.viewManager.mainView.orbit.update(); + this.render(); + }; + + this.scene_changed= async function(sceneName){ + + //var sceneName = event.currentTarget.value; + + if (sceneName.length == 0){ + return; + } + + console.log("choose sceneName " + sceneName); + var meta = this.data.getMetaBySceneName(sceneName); + + if (!meta) + { + this.editorUi.querySelector("#frame-selector").innerHTML = ""; + meta = await this.data.readSceneMetaData(sceneName); + } + + var frame_selector_str = meta.frames.map(function(f){ + return ""; + }).reduce(function(x,y){return x+y;}, ""); + + this.editorUi.querySelector("#frame-selector").innerHTML = frame_selector_str; + + + if (meta.camera){ + this.imageContextManager.updateCameraList(meta.camera); + } + + //load_obj_ids_of_scene(sceneName); + }; + + this.frame_changed= function(event){ + var sceneName = this.editorUi.querySelector("#scene-selector").value; + + if (sceneName.length == 0 && this.data.world) + { + sceneName = this.data.world.frameInfo.scene; + } + + if (sceneName.length == 0){ + return; + } + + var frame = event.currentTarget.value; + console.log(sceneName, frame); + this.load_world(sceneName, frame); + event.currentTarget.blur(); + }; + + + this.ensureBoxTrackIdExist = function() + { + if (!this.selected_box.obj_track_id) + { + this.infoBox.show("Error", "Please assign object track ID."); + return false; + } + + return true; + } + + this.ensureRefObjExist = function() + { + if (!this.autoAdjust.marked_object) + { + this.infoBox.show("Notice", 'No reference object was selected'); + return false; + } + + + return true; + } + this.ensurePreloaded = function() + { + let worldList = this.data.worldList.filter(w=>w.frameInfo.scene == this.data.world.frameInfo.scene); + worldList = worldList.sort((a,b)=>a.frameInfo.frame_index - b.frameInfo.frame_index); + + let meta = this.data.get_current_world_scene_meta(); + + + let allLoaded = worldList.map(w=>w.preloaded()).reduce((a,b)=>a && b, true); + + if ((worldList.length < meta.frames.length && worldList.length <= 60) || (!allLoaded)) + { + this.data.forcePreloadScene(this.data.world.frameInfo.scene, this.data.world); + + this.infoBox.show("Notice", + `Loading scene in background. Please try again later.`); + return false; + } + + + return true; + } + + + this.interpolateInBackground = function() + { + + if (!this.ensureBoxTrackIdExist()) + return; + + if (!this.ensurePreloaded()) + return; + + let worldList = this.data.worldList.filter(w=>w.frameInfo.scene == this.data.world.frameInfo.scene); + worldList = worldList.sort((a,b)=>a.frameInfo.frame_index - b.frameInfo.frame_index); + let boxList = worldList.map(w=>w.annotation.findBoxByTrackId(this.selected_box.obj_track_id)); + + let applyIndList = boxList.map(b=>true); + this.boxOp.interpolateAsync(worldList, boxList, applyIndList).then(ret=>{ + this.header.updateModifiedStatus(); + this.viewManager.render(); + }); + }; + this.enterBatchEditMode = function() + { + if (!this.ensureBoxTrackIdExist()) + return; + + if (!this.ensurePreloaded()) + return; + + this.header.setObject(this.selected_box.obj_track_id); + + this.editBatch( + this.data.world.frameInfo.scene, + this.data.world.frameInfo.frame, + this.selected_box.obj_track_id, + this.selected_box.obj_type + ); + }; + + this.autoAnnInBackground = function() + { + if (!this.ensureBoxTrackIdExist()) + return; + + if (!this.ensurePreloaded()) + return; + + let worldList = this.data.worldList.filter(w=>w.frameInfo.scene == this.data.world.frameInfo.scene); + worldList = worldList.sort((a,b)=>a.frameInfo.frame_index - b.frameInfo.frame_index); + + + + let boxList = worldList.map(w=>w.annotation.findBoxByTrackId(this.selected_box.obj_track_id)); + + let onFinishOneBox = (i)=>{ + this.viewManager.render(); + } + let applyIndList = boxList.map(b=>true); + let dontRotate = false; + + this.boxOp.interpolateAndAutoAdjustAsync(worldList, boxList, onFinishOneBox, applyIndList, dontRotate).then(ret=>{ + this.header.updateModifiedStatus(); + }); + }; + + + this.editBatch = function(sceneName, frame, objectTrackId, objectType){ + + + + //this.keydownDisabled = true; + // hide something + this.imageContextManager.hide(); + this.floatLabelManager.hide(); + + //this.floatLabelManager.showFastToolbox(); + + this.viewManager.mainView.disable(); + this.boxEditor.hide(); + this.hideGridLines(); + //this.controlGui.hide(); + this.editorUi.querySelector("#selectors").style.display='none'; + //this.editorUi.querySelector("#object-selector").style.display='none'; + this.currentMainEditor = this.boxEditorManager; + + this.boxEditorManager.edit(this.data, + this.data.getMetaBySceneName(sceneName), + frame, + objectTrackId, + objectType, + (targetFrame, targetTrackId)=>{ //on exit + this.currentMainEditor = this + //this.keydownDisabled = false; + this.viewManager.mainView.enable(); + + this.imageContextManager.show(); + this.floatLabelManager.show(); + + if (targetTrackId) + this.view_state.lock_obj_track_id = targetTrackId; + + this.on_load_world_finished(this.data.world); + + // if (this.selected_box){ + // // attach again, restore box.boxEditor + // // obj type/id may have changed in batch mode + // this.floatLabelManager.set_object_track_id(this.selected_box.obj_local_id, this.selected_box.obj_track_id); + // this.boxEditor.attachBox(this.selected_box); + // this.boxEditor.update(); + + // // update fasttoolbox + // this.fastToolBox.setValue(this.selected_box.obj_type, this.selected_box.obj_track_id, this.selected_box.obj_attr); + // } + + + this.showGridLines(); + this.render(); + //this.controlGui.show(); + this.editorUi.querySelector("#selectors").style.display='inherit'; + + if (targetFrame) + { + this.load_world(this.data.world.frameInfo.scene, targetFrame, ()=>{ // onfinished + this.makeVisible(targetTrackId); + }); + } + } + ); + }; + + this.gotoObjectFrame = function(frame, objId) + { + this.load_world(this.data.world.frameInfo.scene, frame, ()=>{ // onfinished + this.makeVisible(objId); + }); + }; + + this.makeVisible = function(targetTrackId){ + let box = this.data.world.annotation.findBoxByTrackId(targetTrackId); + + if (box){ + if (this.selected_box != box){ + this.selectBox(box); + } + + this.resetView({x:box.position.x, y:box.position.y, z:50}); + } + + }; + + this.object_changed = function(event){ + var sceneName = this.data.world.frameInfo.scene; //this.editorUi.querySelector("#scene-selector").value; + + let objectTrackId = event.currentTarget.value; + let obj = objIdManager.getObjById(objectTrackId); + + this.editBatch(sceneName, null, objectTrackId, obj.category); + }; + + this.camera_changed= function(event){ + var camera_name = event.currentTarget.value; + + this.data.set_active_image(camera_name); + this.imageContextManager.render_2d_image(); + + event.currentTarget.blur(); + }; + + this.downloadWebglScreenShot = function(){ + let link = document.createElement("a"); + link.download=`${this.data.world.frameInfo.scene}-${this.data.world.frameInfo.frame}-webgl`; + link.href=this.renderer.domElement.toDataURL("image/png", 1); + link.click(); + }; + + this.showLog = function() { + + }; + + this.annotateByAlg1 = function(){ + autoAnnotate(this.data.world, ()=>this.on_load_world_finished(this.data.world)); + }; + + + + this.object_category_changed= function(event){ + if (this.selected_box){ + + let category = event.currentTarget.value; + + this.selected_box.obj_type = category; + this.floatLabelManager.set_object_type(this.selected_box.obj_local_id, this.selected_box.obj_type); + // this.header.mark_changed_flag(); + // this.updateBoxPointsColor(this.selected_box); + // this.imageContextManager.boxes_manager.update_obj_type(this.selected_box.obj_local_id, this.selected_box.obj_type); + + // this.render(); + this.on_box_changed(this.selected_box); + + //todo: we don't know if the old one is already deleted. + // could use object count number? + objIdManager.addObject({ + category: this.selected_box.obj_type, + id: this.selected_box.obj_track_id, + }); + + } + }; + + this.setObjectId = function(id) + { + this.selected_box.obj_track_id = id; + this.floatLabelManager.set_object_track_id(this.selected_box.obj_local_id, this.selected_box.obj_track_id); + + this.view_state.lock_obj_track_id = id; + + //this.header.mark_changed_flag(); + this.on_box_changed(this.selected_box); + + // + objIdManager.addObject({ + category: this.selected_box.obj_type, + id: this.selected_box.obj_track_id, + }); + } + + this.object_track_id_changed= function(event){ + if (this.selected_box){ + var id = event.currentTarget.value; + this.setObjectId(id); + } + }; + + this.object_attribute_changed = function(value){ + if (this.selected_box){ + this.selected_box.obj_attr = value; + this.floatLabelManager.set_object_attr(this.selected_box.obj_local_id, value); + this.data.world.annotation.setModified(); + this.header.updateModifiedStatus(); + } + }; + + // this.updateSubviewRangeByWindowResize= function(box){ + + // if (box === null) + // return; + + // if (box.boxEditor) + // box.boxEditor.onWindowResize(); + + // this.render(); + // }; + + this.handleRightClick= function(event){ + + // select new object + + if (!this.data.world){ + return; + } + + + if (event.shiftKey || event.ctrlKey) + { + // if ctrl or shift hold, don't select any object. + this.contextMenu.show("world",event.layerX, event.layerY, this); + return; + } + + + var intersects = this.mouse.getIntersects( this.mouse.onUpPosition, this.data.world.annotation.boxes ); + if ( intersects.length > 0 ) { + //var object = intersects[ 0 ].object; + var object = intersects[ 0 ].object; + let target_obj = object.userData.object; + if ( target_obj == undefined ) { + // helper + target_obj = object; + } + + if (target_obj != this.selected_box){ + this.selectBox(target_obj); + } + + // this.hide_world_context_menu(); + // this.show_object_context_menu(event.layerX, event.layerY); + this.contextMenu.show("object",event.layerX, event.layerY, this); + + } else { + // if no object is selected, popup context menu + //var pos = getMousePosition(renderer.domElement, event.clientX, event.clientY ); + this.contextMenu.show("world",event.layerX, event.layerY, this); + } + }; + + this.show_world_context_menu= function(posX, posY){ + let menu = this.editorUi.querySelector("#context-menu"); + menu.style.display = "inherit"; + menu.style.left = posX+"px"; + menu.style.top = posY+"px"; + this.editorUi.querySelector("#context-menu-wrapper").style.display = "block"; + }; + + this.hide_world_context_menu= function(){ + let menu = this.editorUi.querySelector("#context-menu"); + menu.style.display = "none"; + }; + + this.show_object_context_menu= function(posX, posY){ + let menu = this.editorUi.querySelector("#object-context-menu"); + menu.style.display = "inherit"; + menu.style.left = posX+"px"; + menu.style.top = posY+"px"; + this.editorUi.querySelector("#context-menu-wrapper").style.display = "block"; + }; + + this.hide_object_context_menu= function(){ + let menu = this.editorUi.querySelector("#object-context-menu"); + menu.style.display = "none"; + }; + + this.on_img_click = function(lidar_point_indices){ + + console.log(lidar_point_indices); + + var self=this; + let obj_type = "Car"; + this.data.world.lidar.set_spec_points_color(lidar_point_indices, {x:0,y:0,z:1}); + this.data.world.lidar.update_points_color(); + this.render(); + //return; + + let pos = this.data.world.lidar.get_centroid(lidar_point_indices); + pos.z = 0; + + let rotation = {x:0, y:0, z:this.viewManager.mainView.camera.rotation.z+Math.PI/2}; + + let obj_cfg = globalObjectCategory.get_obj_cfg_by_type(obj_type); + let scale = { + x: obj_cfg.size[0], + y: obj_cfg.size[1], + z: obj_cfg.size[2] + }; + + let box = this.add_box(pos, scale, rotation, obj_type, ""); + self.boxOp.auto_rotate_xyz(box, null, null, function(b){ + self.on_box_changed(b); + }); + + return; + /* + var box = this.data.world.lidar.create_box_by_points(lidar_point_indices, this.viewManager.mainView.camera); + + + this.scene.add(box); + + this.imageContextManager.boxes_manager.add_box(box); + + + this.boxOp.auto_shrink_box(box); + + + // guess obj type here + + box.obj_type = guess_obj_type_by_dimension(box.scale); + + this.floatLabelManager.add_label(box); + + this.selectBox(box); + this.on_box_changed(box); + + + this.boxOp.auto_rotate_xyz(box, function(){ + box.obj_type = guess_obj_type_by_dimension(box.scale); + self.floatLabelManager.set_object_type(box.obj_local_id, box.obj_type); + self.floatLabelManager.update_label_editor(box.obj_type, box.obj_track_id); + self.on_box_changed(box); + }); + */ + + }; + + this.handleSelectRect= function(x,y,w,h, ctrl, shift){ + // y = y+h; + // x = x*2-1; + // y = -y*2+1; + // w *= 2; + // h *= 2; + + // x,y: start cornor, w: width, h: height + + /* + console.log("main select rect", x,y,w,h); + + this.viewManager.mainView.camera.updateProjectionMatrix(); + this.data.world.select_points_by_view_rect(x,y,w,h, this.viewManager.mainView.camera); + render(); + render_2d_image(); + */ + + // check if any box is inside the rectangle + + this.viewManager.mainView.camera.updateProjectionMatrix(); + + let boxes = this.data.world.annotation.find_boxes_inside_rect(x,y,w,h, this.viewManager.mainView.camera); + if (boxes.length > 0) { + + if (boxes.length == 1){ + this.selectBox(boxes[0]) + } + else{ + // this is dangerous + // for (let b in boxes){ + // this.remove_box(boxes[b],false) + // } + // this.render(); + } + + return; + } + + let points = this.data.world.lidar.select_points_by_view_rect(x,y,w,h, this.viewManager.mainView.camera); + + // show color + //this.render(); + + // return; + // // create new box + // var self=this; + var center_pos = this.mouse.get_screen_location_in_world(x+w/2, y+h/2); + center_pos = this.data.world.scenePosToLidar(center_pos); + + let initRoationZ = this.viewManager.mainView.camera.rotation.z + Math.PI/2; + + let box = this.create_box_by_points(points, initRoationZ); + + let id = objIdManager.generateNewUniqueId(); + box.obj_track_id = id; + + + + //this.scene.add(box); + + + + if (!shift){ + try{ + this.boxOp.auto_shrink_box(box); + } + catch(e) + { + logger.log(e); + } + } + + // guess obj type here + + box.obj_type = globalObjectCategory.guess_obj_type_by_dimension(box.scale); + + objIdManager.addObject({ + category: box.obj_type, + id: box.obj_track_id, + }); + + + + this.imageContextManager.boxes_manager.add_box(box); + this.floatLabelManager.add_label(box); + + this.selectBox(box); + this.on_box_changed(box); + + if (!shift){ + this.boxOp.auto_rotate_xyz(box, ()=>{ + box.obj_type = globalObjectCategory.guess_obj_type_by_dimension(box.scale); + this.floatLabelManager.set_object_type(box.obj_local_id, box.obj_type); + this.fastToolBox.setValue(box.obj_type, box.obj_track_id, box.obj_attr); + this.on_box_changed(box); + }); + } + + + //floatLabelManager.add_label(box); + + + }; + + + + this.create_box_by_points=function(points, rotationZ){ + + let localRot = this.data.world.sceneRotToLidar(new THREE.Euler(0,0,rotationZ, "XYZ")); + + let transToBoxMatrix = new THREE.Matrix4().makeRotationFromEuler(localRot) + .setPosition(0, 0, 0) + .invert(); + + // var trans = transpose(euler_angle_to_rotate_matrix({x:0,y:0,z:rotation_z}, {x:0, y:0, z:0}), 4); + + let relative_position = []; + let v = new THREE.Vector3(); + points.forEach(function(p){ + v.set(p[0],p[1],p[2]); + let boxP = v.applyMatrix4(transToBoxMatrix); + relative_position.push([boxP.x,boxP.y, boxP.z]); + }); + + var relative_extreme = vector_range(relative_position); + var scale = { + x: relative_extreme.max[0] - relative_extreme.min[0], + y: relative_extreme.max[1] - relative_extreme.min[1], + z: relative_extreme.max[2] - relative_extreme.min[2], + }; + + // enlarge scale a little + + let center = this.boxOp.translateBoxInBoxCoord( + localRot, + { + x: (relative_extreme.max[0] + relative_extreme.min[0])/2, + y: (relative_extreme.max[1] + relative_extreme.min[1])/2, + z: (relative_extreme.max[2] + relative_extreme.min[2])/2, + } + ); + + return this.data.world.annotation.add_box(center, scale, localRot, "Unknown", ""); + }; + + + this.handleLeftClick= function(event) { + + if (event.ctrlKey){ + //Ctrl+left click to smart paste! + //smart_paste(); + } + else{ + //select box /unselect box + if (!this.data.world || (!this.data.world.annotation.boxes && this.data.world.radars.radarList.length==0 && !this.calib.calib_box)){ + return; + } + + let all_boxes = this.data.world.annotation.boxes.concat(this.data.world.radars.getAllBoxes()); + all_boxes = all_boxes.concat(this.data.world.aux_lidars.getAllBoxes()); + + if (this.calib.calib_box){ + all_boxes.push(this.calib.calib_box); + } + + let intersects = this.mouse.getIntersects( this.mouse.onUpPosition, all_boxes); + + if (intersects.length == 0){ + if (this.data.world.radar_box){ + intersects = this.mouse.getIntersects( this.mouse.onUpPosition, [this.data.world.radar_box]); + } + } + + if ( intersects.length > 0 ) { + //var object = intersects[ 0 ].object; + var object = intersects[ 0 ].object; + if ( object.userData.object !== undefined ) { + // helper + this.selectBox( object.userData.object ); + } else { + this.selectBox( object ); + } + } else { + this.unselectBox(null); + } + + //render(); + } + + + }; + + this.select_locked_object= function(){ + var self=this; + if (this.view_state.lock_obj_track_id != ""){ + var box = this.data.world.annotation.boxes.find(function(x){ + return x.obj_track_id == self.view_state.lock_obj_track_id; + }) + + if (box){ + this.selectBox(box); + + if (self.view_state.lock_obj_in_highlight){ + this.focusOnSelectedBox(box); + } + } + } + }; + + // new_object + this.unselectBox = function(new_object, keep_lock){ + + if (new_object==null){ + if (this.viewManager.mainView && this.viewManager.mainView.transform_control.visible) + { + //unselect first time + this.viewManager.mainView.transform_control.detach(); + }else{ + //unselect second time + if (this.selected_box){ + // restore from highlight + if (this.selected_box.in_highlight){ + this.cancelFocus(this.selected_box); + + if (!keep_lock){ + this.view_state.lock_obj_in_highlight = false; + } + } else{ + // unselected finally + //this.selected_box.material.color = new THREE.Color(parseInt("0x"+get_obj_cfg_by_type(this.selected_box.obj_type).color.slice(1))); + //this.selected_box.material.opacity = this.data.cfg.box_opacity; + this.boxOp.unhighlightBox(this.selected_box); + //this.floatLabelManager.unselect_box(this.selected_box.obj_local_id, this.selected_box.obj_type); + this.fastToolBox.hide(); + + if (!keep_lock){ + this.view_state.lock_obj_track_id = ""; + } + + this.imageContextManager.boxes_manager.onBoxUnselected(this.selected_box.obj_local_id, this.selected_box.obj_type); + this.selected_box = null; + this.boxEditor.detach(); + + this.onSelectedBoxChanged(null); + } + } + else{ + // just an empty click + return; + } + } + } + else{ + // selected other box + //unselect all + this.viewManager.mainView.transform_control.detach(); + + + if (this.selected_box){ + + // restore from highlight + + if (this.selected_box.in_highlight){ + this.cancelFocus(this.selected_box); + if (!keep_lock){ + this.view_state.lock_obj_in_highlight = false; + } + } + + this.selected_box.material.color = new THREE.Color(parseInt("0x"+globalObjectCategory.get_obj_cfg_by_type(this.selected_box.obj_type).color.slice(1))); + this.selected_box.material.opacity = this.data.cfg.box_opacity; + //this.floatLabelManager.unselect_box(this.selected_box.obj_local_id); + this.fastToolBox.hide(); + this.imageContextManager.boxes_manager.onBoxUnselected(this.selected_box.obj_local_id, this.selected_box.obj_type); + + this.selected_box = null; + this.boxEditor.detach(); + if (!keep_lock) + this.view_state.lock_obj_track_id = ""; + } + } + + + + this.render(); + + }; + + + + this.selectBox = function(object){ + + if (this.selected_box != object){ + // unselect old bbox + + var in_highlight = false; + + if (this.selected_box){ + in_highlight = this.selected_box.in_highlight; + this.unselectBox(this.selected_box); + } + + // select me, the first time + this.selected_box = object; + + // switch camera + if (!this.editorCfg.disableMainImageContext){ + var best_camera = this.imageContextManager.choose_best_camera_for_point( + this.selected_box.world.frameInfo.sceneMeta, + this.selected_box.position); + + if (best_camera){ + + //var image_changed = this.data.set_active_image(best_camera); + + // if (image_changed){ + // this.editorUi.querySelector("#camera-selector").value=best_camera; + // this.imageContextManager.boxes_manager.display_image(); + // } + + this.imageContextManager.setBestCamera(best_camera); + } + } + + // highlight box + // shold change this id if the current selected box changed id. + this.view_state.lock_obj_track_id = object.obj_track_id; + + //this.floatLabelManager.select_box(this.selected_box.obj_local_id); + + this.fastToolBox.setPos(this.floatLabelManager.getLabelEditorPos(this.selected_box.obj_local_id)); + this.fastToolBox.setValue(object.obj_type, object.obj_track_id, object.obj_attr); + this.fastToolBox.show(); + + this.boxOp.highlightBox(this.selected_box); + + if (in_highlight){ + this.focusOnSelectedBox(this.selected_box); + } + + this.save_box_info(object); // this is needed since when a frame is loaded, all box haven't saved anything. + // we could move this to when a frame is loaded. + this.boxEditor.attachBox(object); + this.onSelectedBoxChanged(object); + + } + else { + //reselect the same box + if (this.viewManager.mainView.transform_control.visible){ + this.change_transform_control_view(); + } + else{ + //select me the second time + //object.add(this.viewManager.mainView.transform_control); + this.viewManager.mainView.transform_control.attach( object ); + } + } + + this.render(); + + + }; + + this.adjustContainerSize = function() + { + let editorRect = this.editorUi.getBoundingClientRect(); + let headerRect = this.editorUi.querySelector("#header").getBoundingClientRect(); + + this.container.style.height = editorRect.height - headerRect.height + "px"; + } + + + this.onWindowResize= function() { + + this.adjustContainerSize(); + this.boxEditorManager.onWindowResize(); + + // use clientwidth and clientheight to resize container + // but use scrollwidth/height to place other things. + if ( this.windowWidth != this.container.clientWidth || this.windowHeight != this.container.clientHeight ) { + + //update_mainview(); + if (this.viewManager.mainView) + this.viewManager.mainView.onWindowResize(); + + if (this.boxEditor) + this.boxEditor.update("dontrender"); + + this.windowWidth = this.container.clientWidth; + this.windowHeight = this.container.clientHeight; + this.renderer.setSize( this.windowWidth, this.windowHeight ); + + //this.viewManager.updateViewPort(); + + // update sideview svg if there exists selected box + // the following update is called in updateSubviewRangeByWindowResize + // if (this.selected_box){ + // this.projectiveViewOps.update_view_handle(this.selected_box); + // } + } + + this.viewManager.render(); + }; + + this.change_transform_control_view= function(){ + if (this.viewManager.mainView.transform_control.mode=="scale"){ + this.viewManager.mainView.transform_control.setMode( "translate" ); + this.viewManager.mainView.transform_control.showY=true; + this.viewManager.mainView.transform_control.showX=true; + this.viewManager.mainView.transform_control.showz=true; + }else if (this.viewManager.mainView.transform_control.mode=="translate"){ + this.viewManager.mainView.transform_control.setMode( "rotate" ); + this.viewManager.mainView.transform_control.showY=false; + this.viewManager.mainView.transform_control.showX=false; + this.viewManager.mainView.transform_control.showz=true; + }else if (this.viewManager.mainView.transform_control.mode=="rotate"){ + this.viewManager.mainView.transform_control.setMode( "scale" ); + this.viewManager.mainView.transform_control.showY=true; + this.viewManager.mainView.transform_control.showX=true; + this.viewManager.mainView.transform_control.showz=true; + } + }; + + this.add_box_on_mouse_pos_by_ref = function(){ + + let globalP = this.mouse.get_mouse_location_in_world(); + // trans pos to world local pos + let pos = this.data.world.scenePosToLidar(globalP); + + let refbox = this.autoAdjust.marked_object.ann; + pos.z = refbox.psr.position.z; + + let id = refbox.obj_id; + + if (this.autoAdjust.marked_object.frame == this.data.world.frameInfo.frame) + { + id = ""; + } + + let box = this.add_box(pos, refbox.psr.scale, refbox.psr.rotation, refbox.obj_type, id, refbox.obj_attr); + + return box; + }; + + this.add_box_on_mouse_pos= function(obj_type){ + // todo: move to this.data.world + let globalP = this.mouse.get_mouse_location_in_world(); + + // trans pos to world local pos + let pos = this.data.world.scenePosToLidar(globalP); + + var rotation = new THREE.Euler(0, 0, this.viewManager.mainView.camera.rotation.z+Math.PI/2, "XYZ"); + rotation = this.data.world.sceneRotToLidar(rotation); + + var obj_cfg = globalObjectCategory.get_obj_cfg_by_type(obj_type); + var scale = { + x: obj_cfg.size[0], + y: obj_cfg.size[1], + z: obj_cfg.size[2] + }; + + pos.z = -1.8 + scale.z/2; // -1.8 is height of lidar + + let id = objIdManager.generateNewUniqueId(); + + objIdManager.addObject({ + category: obj_type, + id: id, + }); + + let box = this.add_box(pos, scale, rotation, obj_type, id); + + return box; + }; + + this.add_box= function(pos, scale, rotation, obj_type, obj_track_id, obj_attr){ + let box = this.data.world.annotation.add_box(pos, scale, rotation, obj_type, obj_track_id, obj_attr); + + this.floatLabelManager.add_label(box); + + this.imageContextManager.boxes_manager.add_box(box); + + this.selectBox(box); + return box; + }; + + this.save_box_info= function(box){ + box.last_info = { + //obj_type: box.obj_type, + position: { + x: box.position.x, + y: box.position.y, + z: box.position.z, + }, + rotation: { + x: box.rotation.x, + y: box.rotation.y, + z: box.rotation.z, + }, + scale: { + x: box.scale.x, + y: box.scale.y, + z: box.scale.z, + } + } + }; + + + // axix, xyz, action: scale, move, direction, up/down + this.transform_bbox= function(command){ + if (!this.selected_box) + return; + + switch (command){ + case 'x_move_up': + this.boxOp.translate_box(this.selected_box, 'x', 0.05); + break; + case 'x_move_down': + this.boxOp.translate_box(this.selected_box, 'x', -0.05); + break; + case 'x_scale_up': + this.selected_box.scale.x *= 1.01; + break; + case 'x_scale_down': + this.selected_box.scale.x /= 1.01; + break; + + case 'y_move_up': + this.boxOp.translate_box(this.selected_box, 'y', 0.05); + break; + case 'y_move_down': + this.boxOp.translate_box(this.selected_box, 'y', -0.05); + break; + case 'y_scale_up': + this.selected_box.scale.y *= 1.01; + break; + case 'y_scale_down': + this.selected_box.scale.y /= 1.01; + break; + + case 'z_move_up': + this.selected_box.position.z += 0.05; + break; + case 'z_move_down': + this.selected_box.position.z -= 0.05; + break; + case 'z_scale_up': + this.selected_box.scale.z *= 1.01; + break; + case 'z_scale_down': + this.selected_box.scale.z /= 1.01; + break; + + case 'z_rotate_left': + this.selected_box.rotation.z += 0.01; + break; + case 'z_rotate_right': + this.selected_box.rotation.z -= 0.01; + break; + + case 'z_rotate_reverse': + if (this.selected_box.rotation.z > 0){ + this.selected_box.rotation.z -= Math.PI; + }else{ + this.selected_box.rotation.z += Math.PI; + } + break; + case 'reset': + this.selected_box.rotation.x = 0; + this.selected_box.rotation.y = 0; + this.selected_box.rotation.z = 0; + this.selected_box.position.z = 0; + break; + + } + + this.on_box_changed(this.selected_box); + + }; + + + // function switch_bbox_type(target_type){ + // if (!this.selected_box) + // return; + + // if (!target_type){ + // target_type = get_next_obj_type_name(this.selected_box.obj_type); + // } + + // this.selected_box.obj_type = target_type; + // var obj_cfg = get_obj_cfg_by_type(target_type); + // this.selected_box.scale.x=obj_cfg.size[0]; + // this.selected_box.scale.y=obj_cfg.size[1]; + // this.selected_box.scale.z=obj_cfg.size[2]; + + + // this.floatLabelManager.set_object_type(this.selected_box.obj_local_id, this.selected_box.obj_type); + // this.floatLabelManager.update_label_editor(this.selected_box.obj_type, this.selected_box.obj_track_id); + + + + // } + + + + this.keydown= function( ev ) { + + // if (this.keydownDisabled) + // return; + + this.operation_state.key_pressed = true; + + switch ( ev.key) { + case '+': + case '=': + this.data.scale_point_size(1.2); + this.render(); + break; + case '-': + this.data.scale_point_size(0.8); + this.render(); + break; + case '1': + this.select_previous_object(); + break; + case '2': + this.select_next_object(); + break; + case '3': + case 'PageUp': + this.previous_frame(); + break; + case 'PageDown': + case '4': + this.next_frame(); + break; + case 'p': + this.downloadWebglScreenShot(); + break; + + // case 'v': + // this.change_transform_control_view(); + // break; + /* + case 'm': + case 'M': + smart_paste(); + break; + case 'N': + case 'n': + //add_bbox(); + //header.mark_changed_flag(); + break; + case 'B': + case 'b': + switch_bbox_type(); + self.header.mark_changed_flag(); + self.on_box_changed(this.selected_box); + break; + */ + case 'z': // X + this.viewManager.mainView.transform_control.showX = ! this.viewManager.mainView.transform_control.showX; + break; + case 'x': // Y + this.viewManager.mainView.transform_control.showY = ! this.viewManager.mainView.transform_control.showY; + break; + case 'c': // Z + if (ev.ctrlKey){ + this.mark_bbox(this.selected_box); + } else { + this.viewManager.mainView.transform_control.showZ = ! this.viewManager.mainView.transform_control.showZ; + } + break; + case ' ': // Spacebar + //this.viewManager.mainView.transform_control.enabled = ! this.viewManager.mainView.transform_control.enabled; + this.playControl.pause_resume_play(); + break; + + case '5': + case '6': + case '7': + this.boxEditor.boxView.views[ev.key-5].cameraHelper.visible = !this.boxEditor.boxView.views[ev.key-5].cameraHelper.visible; + this.render(); + break; + + + case 's': + if (ev.ctrlKey){ + saveWorldList(this.data.worldList); + } + else if (this.selected_box) + { + let v = Math.max(this.editorCfg.moveStep * this.selected_box.scale.x, 0.02); + this.boxOp.translate_box(this.selected_box, 'x', -v); + this.on_box_changed(this.selected_box); + } + break; + case 'w': + if (this.selected_box){ + let v = Math.max(this.editorCfg.moveStep * this.selected_box.scale.x, 0.02); + this.boxOp.translate_box(this.selected_box, 'x', v); + this.on_box_changed(this.selected_box); + } + break; + case 'a': + if (this.selected_box){ + let v = Math.max(this.editorCfg.moveStep * this.selected_box.scale.y, 0.02); + this.boxOp.translate_box(this.selected_box, 'y', v); + this.on_box_changed(this.selected_box); + } + break; + case 'd': + if (this.selected_box){ + let v = Math.max(this.editorCfg.moveStep * this.selected_box.scale.y, 0.02); + this.boxOp.translate_box(this.selected_box, 'y', -v); + this.on_box_changed(this.selected_box); + } + break; + + case 'q': + if (this.selected_box){ + this.boxOp.rotate_z(this.selected_box, this.editorCfg.rotateStep, false); + this.on_box_changed(this.selected_box); + } + break; + case 'e': + if (this.selected_box){ + this.boxOp.rotate_z(this.selected_box, -this.editorCfg.rotateStep, false); + this.on_box_changed(this.selected_box); + } + break; + case 'r': + if (this.selected_box){ + //this.transform_bbox("z_rotate_left"); + this.boxOp.rotate_z(this.selected_box, this.editorCfg.rotateStep, true); + this.on_box_changed(this.selected_box); + } + break; + + case 'f': + if (this.selected_box){ + //this.transform_bbox("z_rotate_right"); + this.boxOp.rotate_z(this.selected_box, -this.editorCfg.rotateStep, true); + this.on_box_changed(this.selected_box); + } + break; + case 'g': + this.transform_bbox("z_rotate_reverse"); + break; + case 't': + //this.transform_bbox("reset"); + this.showTrajectory(); + break; + case 'v': + this.enterBatchEditMode(); + break; + case 'd': + case 'D': + if (ev.ctrlKey){ + this.remove_selected_box(); + this.header.updateModifiedStatus(); + } + break; + case 'Delete': + this.remove_selected_box(); + this.header.updateModifiedStatus(); + break; + case 'Escape': + if (this.selected_box){ + this.unselectBox(null); + } + break; + } + }; + + + + this.previous_frame= function(){ + + + + if (!this.data.meta) + return; + + var scene_meta = this.data.get_current_world_scene_meta(); + + var frame_index = this.data.world.frameInfo.frame_index-1; + + if (frame_index < 0){ + console.log("first frame"); + this.infoBox.show("Notice", "This is the first frame"); + return; + } + + this.load_world(scene_meta.scene, scene_meta.frames[frame_index]); + + + + }; + + this.last_frame = function() + { + let scene_meta = this.data.get_current_world_scene_meta(); + this.load_world(scene_meta.scene, scene_meta.frames[scene_meta.frames.length-1]); + }; + this.first_frame = function() + { + let scene_meta = this.data.get_current_world_scene_meta(); + this.load_world(scene_meta.scene, scene_meta.frames[0]); + }; + + this.next_frame= function(){ + + + + if (!this.data.meta) + return; + + var scene_meta = this.data.get_current_world_scene_meta(); + + var num_frames = scene_meta.frames.length; + + var frame_index = (this.data.world.frameInfo.frame_index +1); + + if (frame_index >= num_frames){ + console.log("last frame"); + this.infoBox.show("Notice", "This is the last frame"); + return; + } + + this.load_world(scene_meta.scene, scene_meta.frames[frame_index]); + }; + + this.select_next_object= function(){ + + var self=this; + if (this.data.world.annotation.boxes.length<=0) + return; + + if (this.selected_box){ + this.operation_state.box_navigate_index = this.data.world.annotation.boxes.findIndex(function(x){ + return self.selected_box == x; + }); + } + + this.operation_state.box_navigate_index += 1; + this.operation_state.box_navigate_index %= this.data.world.annotation.boxes.length; + + this.selectBox(this.data.world.annotation.boxes[this.operation_state.box_navigate_index]); + + }; + + this.select_previous_object= function(){ + var self=this; + if (this.data.world.annotation.boxes.length<=0) + return; + + if (this.selected_box){ + this.operation_state.box_navigate_index = this.data.world.annotation.boxes.findIndex(function(x){ + return self.selected_box == x; + }); + } + + this.operation_state.box_navigate_index += this.data.world.annotation.boxes.length-1; + this.operation_state.box_navigate_index %= this.data.world.annotation.boxes.length; + + this.selectBox(this.data.world.annotation.boxes[this.operation_state.box_navigate_index]); + }; + + // this.centerMainView =function(){ + // let offset = this.data.world.coordinatesOffset; + // this.viewManager.mainView.orbit.target.x += offset[0]; + // this.viewManager.mainView.orbit.target.y += offset[1]; + // this.viewManager.mainView.orbit.target.z += offset[2]; + // }; + + this.on_load_world_finished= function(world){ + + document.title = "SUSTech POINTS-" + world.frameInfo.scene; + // switch view positoin + this.moveAxisHelper(world); + this.moveRangeCircle(world); + this.lookAtWorld(world); + this.unselectBox(null, true); + this.unselectBox(null, true); + this.render(); + this.imageContextManager.attachWorld(world); + this.imageContextManager.render_2d_image(); + this.render2dLabels(world); + this.update_frame_info(world.frameInfo.scene, world.frameInfo.frame); + + this.select_locked_object(); + + //load_obj_ids_of_scene(world.frameInfo.scene); + objIdManager.setCurrentScene(world.frameInfo.scene); + + // preload after the first world loaded + // otherwise the loading of the first world would be too slow + this.data.preloadScene(world.frameInfo.scene, world); + }; + this.moveAxisHelper = function(world) { + world.webglGroup.add(this.axis); + }; + + this.mainViewOffset = [0,0,0]; + + this.lookAtWorld = function(world){ + let newOffset = [ + world.coordinatesOffset[0] - this.mainViewOffset[0], + world.coordinatesOffset[1] - this.mainViewOffset[1], + world.coordinatesOffset[2] - this.mainViewOffset[2], + ]; + + this.mainViewOffset = world.coordinatesOffset; + + this.viewManager.mainView.orbit.target.x += newOffset[0]; + this.viewManager.mainView.orbit.target.y += newOffset[1]; + this.viewManager.mainView.orbit.target.z += newOffset[2]; + + this.viewManager.mainView.camera.position.x += newOffset[0]; + this.viewManager.mainView.camera.position.y += newOffset[1]; + this.viewManager.mainView.camera.position.z += newOffset[2]; + + this.viewManager.mainView.orbit.update(); + + }; + + this.load_world = async function(sceneName, frame, onFinished){ + + this.data.dbg.dump(); + + logger.log(`load ${sceneName}, ${frame}`); + + var self=this; + //stop if current world is not ready! + if (this.data.world && !this.data.world.preloaded()){ + console.error("current world is still loading."); + return; + } + + if (this.selected_box && this.selected_box.in_highlight){ + this.cancelFocus(this.selected_box); + } + + if (this.viewManager.mainView && this.viewManager.mainView.transform_control.visible) + { + //unselect first time + this.viewManager.mainView.transform_control.detach(); + } + + var world = await this.data.getWorld(sceneName, frame); + + if (world) + { + this.data.activate_world( + world, + function(){ + self.on_load_world_finished(world); + if (onFinished) + onFinished(); + + } + ); + } + + + }; + + + this.remove_box = function(box, render=true){ + if (box === this.selected_box){ + this.unselectBox(null,true); + this.unselectBox(null,true); //twice to safely unselect. + this.selected_box = null; + //this.remove_selected_box(); + } + + + + this.do_remove_box(box, false); // render later. + + // this should be after do-remove-box + // subview renderings don't need to be done again after + // the box is removed. + if (box.boxEditor) + { + if (box.boxEditor){ + box.boxEditor.detach("donthide"); + } + else{ + console.error("what?"); + } + } + + + this.header.updateModifiedStatus(); + + if (render) + this.render(); + + }; + + this.remove_selected_box= function(){ + this.remove_box(this.selected_box); + }; + + this.do_remove_box = function(box, render=true){ + + if (!box.annotator) + this.restore_box_points_color(box, render); + + this.imageContextManager.boxes_manager.remove_box(box.obj_local_id); + + this.floatLabelManager.remove_box(box); + this.fastToolBox.hide(); + + //this.selected_box.dispose(); + + box.world.annotation.unload_box(box); + box.world.annotation.remove_box(box); + + box.world.annotation.setModified(); + }, + + this.clear= function(){ + + this.header.clear_box_info(); + //this.editorUi.querySelector("#image").innerHTML = ''; + + this.unselectBox(null); + this.unselectBox(null); + + this.header.clear_frame_info(); + + this.imageContextManager.clear_main_canvas(); + this.boxEditor.detach(); + + + this.data.world.unload(); + this.data.world= null; //dump it + this.floatLabelManager.remove_all_labels(); + this.fastToolBox.hide(); + this.render(); + }; + + this.update_frame_info= function(scene, frame){ + var self = this; + this.header.set_frame_info(scene, frame, function(sceneName){ + self.scene_changed(sceneName)}); + }; + + //box edited + this.on_box_changed = function(box){ + + if (!this.imageContextManager.hidden()) + this.imageContextManager.boxes_manager.update_box(box); + + this.header.update_box_info(box); + //floatLabelManager.update_position(box, false); don't update position, or the ui is annoying. + + box.world.annotation.setModified(); + + + + this.updateBoxPointsColor(box); + this.save_box_info(box); + + + + if (box.boxEditor){ + box.boxEditor.onBoxChanged(); + } + else{ + console.error("what?"); + } + + this.autoAdjust.syncFollowers(box); + + // if (box === this.data.world.radar_box){ + // this.data.world.move_radar(box); + // } + + if (box.on_box_changed){ + box.on_box_changed(); + } + + this.header.updateModifiedStatus(); + this.render(); + }; + + // box removed, restore points color. + this.restore_box_points_color= function(box,render=true){ + if (this.data.cfg.color_obj != "no"){ + box.world.lidar.reset_box_points_color(box); + box.world.lidar.update_points_color(); + if (render) + this.render(); + } + + }; + + this.updateBoxPointsColor= function(box){ + if (this.data.cfg.color_obj != "no"){ + if (box.last_info){ + box.world.lidar.set_box_points_color(box.last_info, {x: this.data.cfg.point_brightness, y: this.data.cfg.point_brightness, z: this.data.cfg.point_brightness}); + } + + box.world.lidar.set_box_points_color(box); + box.world.lidar.update_points_color(); + } + }; + + this.onSelectedBoxChanged= function(box){ + + if (box){ + this.header.update_box_info(box); + // this.floatLabelManager.update_position(box, true); + // this.fastToolBox.setPos(this.floatLabelManager.getLabelEditorPos(box.obj_local_id)); + this.imageContextManager.boxes_manager.onBoxSelected(box.obj_local_id, box.obj_type); + + + //this.boxEditor.attachBox(box); + + this.render(); + //this.boxEditor.boxView.render(); + + //this.updateSubviewRangeByWindowResize(box); + + } else { + this.header.clear_box_info(); + } + + }; + + this.render2dLabels= function(world){ + if (this.editorCfg.disableMainView) + return; + + this.floatLabelManager.remove_all_labels(); + var self=this; + world.annotation.boxes.forEach(function(b){ + self.floatLabelManager.add_label(b); + }) + + if (this.selected_box){ + //this.floatLabelManager.select_box(this.selected_box.obj_local_id) + this.fastToolBox.show(); + this.fastToolBox.setValue(this.selected_box.obj_type, this.selected_box.obj_track_id, this.selected_box.obj_attr); + } + }; + + this.add_global_obj_type= function(){ + + var self = this; + var sheet = window.document.styleSheets[1]; + + let obj_type_map = globalObjectCategory.obj_type_map; + + for (var o in obj_type_map){ + var rule = '.'+o+ '{color:'+obj_type_map[o].color+';'+ + 'stroke:' +obj_type_map[o].color+ ';'+ + 'fill:' +obj_type_map[o].color+ '22' + ';'+ + '}'; + sheet.insertRule(rule, sheet.cssRules.length); + } + + function color_str(v){ + let c = Math.round(v*255); + if (c < 16) + return "0" + c.toString(16); + else + return c.toString(16); + } + + for (let idx=0; idx<=32; idx++){ + let c = globalObjectCategory.get_color_by_id(idx); + let color = "#" + color_str(c.x) + color_str(c.y) + color_str(c.z); + + var rule = '.color-'+idx+ '{color:'+color+';'+ + 'stroke:' +color+ ';'+ + 'fill:' +color+ '22' + ';'+ + '}'; + sheet.insertRule(rule, sheet.cssRules.length); + } + + // obj type selector + var options = ""; + for (var o in obj_type_map){ + options += ''; + } + + this.editorUi.querySelector("#floating-things #object-category-selector").innerHTML = options; + //this.editorUi.querySelector("#batch-editor-tools-wrapper #object-category-selector").innerHTML = options; + + // submenu of new + var items = ""; + for (var o in obj_type_map){ + items += ''; + } + + this.editorUi.querySelector("#new-submenu").innerHTML = items; + + this.contextMenu.installMenu("newSubMenu", this.editorUi.querySelector("#new-submenu"), (event)=>{ + let obj_type = event.currentTarget.getAttribute("uservalue"); + let box = self.add_box_on_mouse_pos(obj_type); + //switch_bbox_type(event.currentTarget.getAttribute("uservalue")); + //self.boxOp.grow_box(box, 0.2, {x:2, y:2, z:3}); + //self.auto_shrink_box(box); + //self.on_box_changed(box); + + let noscaling = event.shiftKey; + + self.boxOp.auto_rotate_xyz(box, null, null, function(b){ + self.on_box_changed(b); + }, noscaling); + return true; + }); + + // // install click actions + // for (var o in obj_type_map){ + // this.editorUi.querySelector("#cm-new-"+o).onclick = (event)=>{ + + // // hide context men + // // let context menu object handle this. + // // this.editorUi.querySelector("#context-menu-wrapper").style.display="none"; + + // // process event + // var obj_type = event.currentTarget.getAttribute("uservalue"); + // let box = self.add_box_on_mouse_pos(obj_type); + // //switch_bbox_type(event.currentTarget.getAttribute("uservalue")); + // //self.boxOp.grow_box(box, 0.2, {x:2, y:2, z:3}); + // //self.auto_shrink_box(box); + // //self.on_box_changed(box); + + // self.boxOp.auto_rotate_xyz(box, null, null, function(b){ + // self.on_box_changed(b); + // }); + + // } + // } + + }; + + this.interpolate_selected_object= function(){ + + let scene = this.data.world.frameInfo.scene; + let frame = this.data.world.frameInfo.frame; + let obj_id = this.selected_box.obj_track_id; + + this.boxOp.interpolate_selected_object(scene, obj_id, frame, (s,fs)=>{ + this.onAnnotationUpdatedByOthers(s, fs); + }); + + + }; + + this.onAnnotationUpdatedByOthers = function(scene, frames){ + this.data.onAnnotationUpdatedByOthers(scene, frames); + } + + this.init(editorUi); + +}; + +export{Editor} \ No newline at end of file diff --git a/public/js/ego_pose.js b/public/js/ego_pose.js new file mode 100644 index 0000000..3eebf5c --- /dev/null +++ b/public/js/ego_pose.js @@ -0,0 +1,78 @@ + + + + +class EgoPose +{ + constructor(sceneMeta, world, frameInfo) + { + this.world = world; + this.data = this.world.data; + this.sceneMeta = sceneMeta; + } + + + preload(on_preload_finished) + { + this.on_preload_finished = on_preload_finished; + this.load_ego_pose(); + }; + + + load_ego_pose(){ + + var xhr = new XMLHttpRequest(); + // we defined the xhr + var _self = this; + xhr.onreadystatechange = function () { + if (this.readyState != 4) return; + + if (this.status == 200) { + let egoPose = JSON.parse(this.responseText); + _self.egoPose = egoPose; + } + + console.log(_self.world.frameInfo.frame, "egopose", "loaded"); + _self.preloaded = true; + + if (_self.on_preload_finished){ + _self.on_preload_finished(); + } + if (_self.go_cmd_received){ + _self.go(this.webglScene, this.on_go_finished); + } + + // end of state change: it can be after some time (async) + }; + + xhr.open('GET', "/load_ego_pose"+"?scene="+this.world.frameInfo.scene+"&frame="+this.world.frameInfo.frame, true); + xhr.send(); + }; + + + go_cmd_received = false; + on_go_finished = null; + + go(webglScene, on_go_finished) + { + if (this.preloaded){ + if (on_go_finished) + on_go_finished(); + } else { + this.go_cmd_received = true; + this.on_go_finished = on_go_finished; + } + }; + + + + + unload() + { + + }; + +} + + +export{EgoPose} \ No newline at end of file diff --git a/public/js/error_check.js b/public/js/error_check.js new file mode 100644 index 0000000..b64aea4 --- /dev/null +++ b/public/js/error_check.js @@ -0,0 +1,30 @@ +import { logger } from "./log.js"; + + +function checkScene(scene) +{ + const req = new Request(`/checkscene?scene=${scene}`); + let init = { + method: 'GET', + //body: JSON.stringify({"points": data}) + }; + // we defined the xhr + + return fetch(req, init) + .then(response=>{ + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + }else{ + return response.json(); + } + }) + .then(ret=> + { + logger.setErrorsContent(ret); + }) + .catch(reject=>{ + console.log("error check scene!"); + }); +} + +export {checkScene} \ No newline at end of file diff --git a/public/js/floatlabel.js b/public/js/floatlabel.js new file mode 100644 index 0000000..edf1079 --- /dev/null +++ b/public/js/floatlabel.js @@ -0,0 +1,592 @@ + + +import {psr_to_xyz} from "./util.js" +import * as THREE from './lib/three.module.js'; +import { globalObjectCategory } from "./obj_cfg.js"; +class FastToolBox{ + constructor(ui, eventHandler) + { + let self = this; + this.ui = ui; + this.eventHandler = eventHandler; + + this.installEventHandler(); + + this.ui.querySelector("#attr-editor").onmouseenter=function(event){ + if (this.timerId) + { + clearTimeout(this.timerId); + this.timerId = null; + } + + event.target.querySelector("#attr-selector").style.display=""; + + }; + + + this.ui.querySelector("#attr-editor").onmouseleave=function(event){ + let ui = event.target.querySelector("#attr-selector"); + + this.timerId = setTimeout(()=>{ + ui.style.display="none"; + this.timerId = null; + }, + 200); + + }; + + this.ui.querySelector("#label-more").onmouseenter=function(event){ + if (this.timerId) + { + clearTimeout(this.timerId); + this.timerId = null; + } + let ui = event.target.querySelector("#object-dropdown-menu"); + ui.style.display="inherit"; + ui.style.top = "100%"; + ui.style.left = "0%"; + ui.style.right = null; + ui.style.bottom = null; + + let rect = ui.getClientRects()[0]; + if (window.innerHeight < rect.y+rect.height) + { + ui.style.top = null; + ui.style.bottom = "100%"; + } + + if (window.innerWidth < rect.x+rect.width) + { + ui.style.left = null; + ui.style.right = "0%"; + } + + }; + + this.ui.querySelector("#label-more").onmouseleave=function(event){ + let ui = event.target.querySelector("#object-dropdown-menu"); + this.timerId = setTimeout(()=>{ + ui.style.display="none"; + this.timerId = null; + }, + 200); + }; + + + let dropdownMenu = this.ui.querySelector("#object-dropdown-menu"); + for (let i = 0; i < dropdownMenu.children.length; i++) + { + dropdownMenu.children[i].onclick = (event) => + { + //event.preventDefault(); + event.stopPropagation(); + + this.eventHandler(event); + + this.ui.querySelector("#object-dropdown-menu").style.display="none"; + } + } + } + + hide() + { + this.ui.style.display = "none"; + } + + show() + { + this.ui.style.display = "inline-block"; + this.ui.querySelector("#attr-selector").style.display="none"; + } + + + setValue(obj_type, obj_track_id, obj_attr){ + this.ui.querySelector("#object-category-selector").value = obj_type; + + this.setAttrOptions(obj_type, obj_attr); + + this.ui.querySelector("#object-track-id-editor").value = obj_track_id; + + if (obj_attr) + this.ui.querySelector("#attr-input").value = obj_attr; + else + this.ui.querySelector("#attr-input").value = ""; + + } + + setPos(pos) + { + if (pos) + { + this.ui.style.top = pos.top; + this.ui.style.left = pos.left; + } + } + + setAttrOptions(obj_type, obj_attr) + { + + let attrs = ["static"]; + + + if (globalObjectCategory.obj_type_map[obj_type] && globalObjectCategory.obj_type_map[obj_type].attr) + attrs = attrs.concat(globalObjectCategory.obj_type_map[obj_type].attr); + + // merge attrs + let objAttrs = []; + + if (obj_attr){ + objAttrs = obj_attr.split(",").map(a=>a.trim()); + objAttrs.forEach(a=>{ + if (!attrs.find(x=>x==a)) + { + attrs.push(a); + } + }) + } + + + let items = ``; + + + attrs.forEach(a=>{ + if (objAttrs.find(x=>x==a)){ + items+= `
${a}
` + } + else { + items+= `
${a}
` + } + }); + + + this.ui.querySelector("#attr-selector").innerHTML = items; + + this.ui.querySelector("#attr-selector").onclick = (event)=>{ + + let attrs = this.ui.querySelector("#attr-input").value; + + let objCurrentAttrs = []; + if (attrs) + objCurrentAttrs = attrs.split(",").map(a=>a.trim()); + + + let clickedAttr = event.target.innerText; + + if (objCurrentAttrs.find(x=>x==clickedAttr)) + { + objCurrentAttrs = objCurrentAttrs.filter(x => x!= clickedAttr); + event.target.className = 'attr-item'; + } + else + { + objCurrentAttrs.push(clickedAttr); + event.target.className = 'attr-item attr-selected'; + } + + attrs = ""; + if (objCurrentAttrs.length > 0) + { + attrs = objCurrentAttrs.reduce((a,b)=>a+ (a?",":"") + b); + } + + this.ui.querySelector("#attr-input").value = attrs; + + this.eventHandler({ + currentTarget:{ + id: "attr-input", + value: attrs + } + }); + + } + + } + + installEventHandler(){ + + let btns = [ + "#label-del", + "#label-gen-id", + "#label-copy", + "#label-paste", + "#label-batchedit", + "#label-trajectory", + "#label-edit", + "#label-highlight", + "#label-rotate", + ]; + + btns.forEach(btn=>{ + this.ui.querySelector(btn).onclick = (event)=>{ + this.eventHandler(event); + }; + }); + + this.ui.querySelector("#object-category-selector").onchange = event=>{ + + //this.ui.querySelector("#attr-input").value=""; + this.setAttrOptions(event.currentTarget.value, this.ui.querySelector("#attr-input").value); + this.eventHandler(event); + }; + + + this.ui.querySelector("#object-track-id-editor").onchange = event=>this.eventHandler(event); + this.ui.querySelector("#object-track-id-editor").addEventListener("keydown", e=>e.stopPropagation()); + this.ui.querySelector("#object-track-id-editor").addEventListener("keyup", event=>{ + event.stopPropagation(); + this.eventHandler(event); + }); + + this.ui.querySelector("#attr-input").onchange = event=>this.eventHandler(event); + this.ui.querySelector("#attr-input").addEventListener("keydown", e=>e.stopPropagation()); + this.ui.querySelector("#attr-input").addEventListener("keyup", event=>{ + event.stopPropagation(); + this.eventHandler(event); + }); + } +} + + + +class FloatLabelManager { + + id_enabled = true; + category_enabled = true; + color_scheme = "category"; + + constructor(editor_ui, container_div, view, func_on_label_clicked) + { + this.view = view; //access camera by view, since camera is dynamic + this.editor_ui = editor_ui; + this.container = container_div; + this.labelsUi = editor_ui.querySelector("#floating-labels"); + this.floatingUi = editor_ui.querySelector("#floating-things"); + + + + this.style = document.createElement('style'); + this.temp_style = document.createElement('style'); + this.on_label_clicked = func_on_label_clicked; + + document.head.appendChild(this.style); + document.head.appendChild(this.temp_style); + + this.id_enabled = !pointsGlobalConfig.hideId; + this.category_enabled = !pointsGlobalConfig.hideCategory; + } + + hide(){ + this.floatingUi.style.display="none"; + } + + show(){ + this.floatingUi.style.display=""; + } + + + + + show_id(show){ + + this.id_enabled = show; + + if (!show){ + this.temp_style.sheet.insertRule(".label-obj-id-text {display: none}"); + } + else{ + for (let i = this.temp_style.sheet.cssRules.length-1; i >= 0; i--){ + var r = this.temp_style.sheet.cssRules[i]; + if (r.selectorText === ".label-obj-id-text"){ + this.temp_style.sheet.deleteRule(i); + } + } + + } + + } + + show_category(show){ + + this.category_enabled = show; + + if (!show){ + this.temp_style.sheet.insertRule(".label-obj-type-text {display: none}"); + this.temp_style.sheet.insertRule(".label-obj-attr-text {display: none}"); + } + else{ + for (let i = this.temp_style.sheet.cssRules.length-1; i >= 0; i--){ + var r = this.temp_style.sheet.cssRules[i]; + if (r.selectorText === ".label-obj-type-text" || r.selectorText === ".label-obj-attr-text"){ + this.temp_style.sheet.deleteRule(i); + } + } + } + + } + + // hide all temporarily when zoom in one object. + hide_all(){ + // if (this.temp_style.sheet.cssRules.length == 0){ + // this.temp_style.sheet.insertRule(".label-obj-id-text {display: none}"); + // this.temp_style.sheet.insertRule(".label-obj-type-text {display: none}"); + // this.temp_style.sheet.insertRule(".label-obj-attr-text {display: none}"); + // } + this.labelsUi.style.display = "none"; + } + + restore_all(){ + // this.show_category(this.category_enabled); + // this.show_id(this.id_enabled); + this.labelsUi.style.display = ""; + } + + remove_all_labels(){ + + var _self = this; + + if (this.labelsUi.children.length>0){ + for (var c=this.labelsUi.children.length-1; c >= 0; c--){ + this.labelsUi.children[c].remove(); + } + } + } + + + update_all_position(){ + if (this.labelsUi.children.length>0){ + for (var c=0; c < this.labelsUi.children.length; c++){ + var element = this.labelsUi.children[c]; + + var best_pos = this.compute_best_position(element.vertices); + var pos = this.coord_to_pixel(best_pos); + + element.style.top = Math.round(pos.y) + 'px'; + element.style.left = Math.round(pos.x) + 'px'; + + + element.className = element.orgClassName; + if (pos.out_view){ + element.className += " label-out-view"; + } + + } + } + } + + getLabelEditorPos(local_id) + { + let label = this.editor_ui.querySelector("#obj-local-"+local_id); + if (label) + { + // if label is hidden, we can't use its pos directly. + let best_pos = this.compute_best_position(label.vertices); + let pos = this.coord_to_pixel(best_pos); + + + return { + top: Math.round(pos.y) + label.offsetHeight + "px", + left: Math.round(pos.x) + 30 + "px", + }; + } + } + + + set_object_type(local_id, obj_type){ + var label = this.editor_ui.querySelector("#obj-local-"+local_id); + if (label){ + label.obj_type = obj_type; + label.update_text(); + this.update_color(label); + } + } + + set_object_attr(local_id, obj_attr){ + var label = this.editor_ui.querySelector("#obj-local-"+local_id); + if (label){ + label.obj_attr = obj_attr; + label.update_text(); + this.update_color(label); + } + } + + + set_object_track_id(local_id, track_id){ + var label = this.editor_ui.querySelector("#obj-local-"+local_id); + + if (label){ + label.obj_track_id = track_id; + label.update_text(); + this.update_color(label); + } + } + + translate_vertices_to_global(world, vertices) { + let ret = []; + for (let i = 0; i< vertices.length; i+=4) + { + let p = new THREE.Vector4().fromArray(vertices, i).applyMatrix4(world.webglGroup.matrix); + ret.push(p.x); + ret.push(p.y); + ret.push(p.z); + ret.push(p.w); + } + + return ret; + + } + + update_position(box, refresh){ + var label = this.editor_ui.querySelector("#obj-local-"+box.obj_local_id); + + if (label){ + + label.vertices = this.translate_vertices_to_global(box.world, psr_to_xyz(box.position, box.scale, box.rotation)); + + if (refresh){ + var best_pos = this.compute_best_position(label.vertices); + var pos = this.coord_to_pixel(best_pos); + + label.style.top = Math.round(pos.y) + 'px'; + label.style.left = Math.round(pos.x) + 'px'; + + label.className = label.orgClassName; + if (pos.out_view){ + label.className += " label-out-view"; + } + } + } + } + + + + remove_box(box){ + var label = this.editor_ui.querySelector("#obj-local-"+box.obj_local_id); + + if (label) + label.remove(); + } + + set_color_scheme(color_scheme){ + this.color_scheme = color_scheme; + } + update_color(label) + { + if (this.color_scheme == "id") + { + label.className = "float-label color-"+ (label.obj_track_id % 33); + } + else // by id + { + label.className = "float-label "+label.obj_type; + } + + label.orgClassName = label.className; + } + + add_label(box){ + + var label = document.createElement('div'); + + + + label.id = "obj-local-"+box.obj_local_id; + + var _self =this; + + label.update_text = function(){ + let label_text = '
'; + label_text += this.obj_type; + label_text += '
'; + + if (this.obj_attr) + { + label_text += '
'; + label_text += this.obj_attr; + label_text += '
'; + } + + label_text += '
'; + label_text += this.obj_track_id; + label_text += '
'; + + this.innerHTML = label_text; + } + + label.obj_type = box.obj_type; + label.obj_local_id = box.obj_local_id; + label.obj_track_id = box.obj_track_id; + label.obj_attr = box.obj_attr; + label.update_text(); + this.update_color(label); + + label.vertices = this.translate_vertices_to_global(box.world, psr_to_xyz(box.position, box.scale, box.rotation)); + + var best_pos = this.compute_best_position(label.vertices); + best_pos = this.coord_to_pixel(best_pos); + + var pos = best_pos; + + label.style.top = Math.round(pos.y) + 'px'; + label.style.left = Math.round(pos.x) + 'px'; + + if (pos.out_view){ + label.className += " label-out-view"; + } + + this.labelsUi.appendChild(label); + + let self = this; + label.onclick = ()=>{ + this.on_label_clicked(box); + }; + } + + + coord_to_pixel(p){ + var width = this.container.clientWidth, height = this.container.clientHeight; + var widthHalf = width / 2, heightHalf = height / 2; + + var ret={ + x: ( p.x * widthHalf ) + widthHalf + 10, + y: - ( p.y * heightHalf ) + heightHalf - 10, + out_view: p.x>0.9 || p.x<-0.6 || p.y<-0.9 || p.y>0.9 || p.z< -1 || p.z > 1, + // p.x<-0.6 to prevent it from appearing ontop of sideviews. + } + + return ret; + } + + compute_best_position(vertices){ + var _self = this; + var camera_p = [0,1,2,3,4,5,6,7].map(function(i){ + return new THREE.Vector3(vertices[i*4+0], vertices[i*4+1], vertices[i*4+2]); + }); + + camera_p.forEach(function(x){ + x.project(_self.view.camera); + }); + + var visible_p = camera_p; + + var best_p = {x:-1, y: -1, z: -2}; + + visible_p.forEach(function(p){ + if (p.x > best_p.x){ + best_p.x = p.x; + } + + if (p.y > best_p.y){ + best_p.y = p.y; + } + + if (p.z > best_p.z){ + best_p.z = p.z; + } + }) + + return best_p; + } +} + + +export {FloatLabelManager, FastToolBox}; \ No newline at end of file diff --git a/public/js/gui_worker.js b/public/js/gui_worker.js new file mode 100644 index 0000000..e69de29 diff --git a/public/js/header.js b/public/js/header.js new file mode 100644 index 0000000..b97930f --- /dev/null +++ b/public/js/header.js @@ -0,0 +1,156 @@ + +import { CubeRefractionMapping } from "./lib/three.module.js"; +import {saveWorldList} from "./save.js" + +var Header=function(ui, data, cfg, onSceneChanged, onFrameChanged, onObjectSelected, onCameraChanged){ + + this.ui = ui; + this.data = data; + this.cfg = cfg; + this.boxUi = ui.querySelector("#box"); + this.refObjUi = ui.querySelector("#ref-obj"); + this.sceneSelectorUi = ui.querySelector("#scene-selector"); + this.frameSelectorUi = ui.querySelector("#frame-selector"); + this.objectSelectorUi = ui.querySelector("#object-selector"); + this.cameraSelectorUi = ui.querySelector("#camera-selector"); + this.changedMarkUi = ui.querySelector("#changed-mark"); + + this.onSceneChanged = onSceneChanged; + this.onFrameChanged = onFrameChanged; + this.onObjectSelected = onObjectSelected; + this.onCameraChanged = onCameraChanged; + + + if (cfg.disableSceneSelector){ + this.sceneSelectorUi.style.display="none"; + } + + if (cfg.disableFrameSelector){ + this.frameSelectorUi.style.display="none"; + } + + if (cfg.disableCameraSelector){ + this.cameraSelectorUi.style.display="none"; + } + + // update scene selector ui + + + + + this.updateSceneList = function(sceneDescList){ + let scene_selector_str = ""; + for (let scene in sceneDescList) + { + if (data.sceneDescList[scene]) + scene_selector_str += ""; + else + scene_selector_str += ""; + } + + this.ui.querySelector("#scene-selector").innerHTML = scene_selector_str; + } + + this.updateSceneList(this.data.sceneDescList); + + this.ui.querySelector("#btn-reload-scene-list").onclick = (event)=>{ + let curentValue = this.sceneSelectorUi.value; + + this.data.readSceneList().then((sceneDescList=>{ + this.updateSceneList(sceneDescList); + this.sceneSelectorUi.value = curentValue; + })) + } + + + + this.sceneSelectorUi.onchange = (e)=>{this.onSceneChanged(e);}; + this.objectSelectorUi.onchange = (e)=>{this.onObjectSelected(e);}; + this.frameSelectorUi.onchange = (e)=>{this.onFrameChanged(e);}; + this.cameraSelectorUi.onchange = (e)=>{this.onCameraChanged(e);}; + + this.setObject = function(id) + { + this.objectSelectorUi.value = id; + } + + this.clear_box_info = function(){ + this.boxUi.innerHTML = ''; + }; + + this.update_box_info = function(box){ + var scale = box.scale; + var pos = box.position; + var rotation = box.rotation; + var points_number = box.world.lidar.get_box_points_number(box); + let distance = Math.sqrt(pos.x*pos.x + pos.y*pos.y).toFixed(2); + + this.boxUi.innerHTML = "" + box.obj_type +"-"+box.obj_track_id + + (box.annotator? (" | " + box.annotator) : "") + + " | " + distance + + " | "+pos.x.toFixed(2) +" "+pos.y.toFixed(2) + " " + pos.z.toFixed(2) + + " | " +scale.x.toFixed(2) +" "+scale.y.toFixed(2) + " " + scale.z.toFixed(2) + + " | " + + (rotation.x*180/Math.PI).toFixed(2)+" "+(rotation.y*180/Math.PI).toFixed(2)+" "+(rotation.z*180/Math.PI).toFixed(2)+ + " | " + + points_number + " "; + if (box.follows){ + this.boxUi.innerHTML += "| F:"+box.follows.obj_track_id; + } + }, + + this.set_ref_obj = function(marked_object){ + this.refObjUi.innerHTML="| Ref: "+marked_object.scene+"/"+marked_object.frame+": "+marked_object.ann.obj_type+"-"+marked_object.ann.obj_id; + }, + + this.set_frame_info =function(scene, frame, on_scene_changed){ + + if (this.sceneSelectorUi.value != scene){ + this.sceneSelectorUi.value = scene; + on_scene_changed(scene); + } + + this.frameSelectorUi.value = frame; + }, + + this.clear_frame_info = function(scene, frame){ + + }, + + this.updateModifiedStatus = function(){ + let frames = this.data.worldList.filter(w=>w.annotation.modified); + if (frames.length > 0) + { + this.ui.querySelector("#changed-mark").className = 'ui-button alarm-mark'; + } + else + { + this.ui.querySelector("#changed-mark").className = 'ui-button'; + } + } + + this.ui.querySelector("#changed-mark").onmouseenter = ()=>{ + + let items = ""; + let frames = this.data.worldList.filter(w=>w.annotation.modified).map(w=>w.frameInfo); + frames.forEach(f=>{ + items += "
" + f.frame + '
'; + }); + + if (frames.length > 0){ + this.ui.querySelector("#changed-world-list").innerHTML = items; + this.ui.querySelector("#changed-world-list-wrapper").style.display = 'inherit'; + } + } + + this.ui.querySelector("#changed-mark").onmouseleave = ()=>{ + this.ui.querySelector("#changed-world-list-wrapper").style.display = 'none'; + } + + this.ui.querySelector("#save-button").onclick = ()=>{ + saveWorldList(this.data.worldList); + } +}; + + +export {Header} \ No newline at end of file diff --git a/public/js/image.js b/public/js/image.js new file mode 100644 index 0000000..816b8aa --- /dev/null +++ b/public/js/image.js @@ -0,0 +1,1210 @@ + +import {vector4to3, vector3_nomalize, psr_to_xyz, matmul} from "./util.js" +import {globalObjectCategory, } from './obj_cfg.js'; +import { MovableView } from "./popup_dialog.js"; + +function BoxImageContext(ui){ + + this.ui = ui; + + // draw highlighted box + this.updateFocusedImageContext = function(box){ + var scene_meta = box.world.frameInfo.sceneMeta; + + + let bestImage = choose_best_camera_for_point( + scene_meta, + box.position); + + if (!bestImage){ + return; + } + + if (!scene_meta.calib.camera){ + return; + } + + var calib = scene_meta.calib.camera[bestImage] + if (!calib){ + return; + } + + if (calib){ + var img = box.world.cameras.getImageByName(bestImage); + if (img && (img.naturalWidth > 0)){ + + this.clear_canvas(); + + var imgfinal = box_to_2d_points(box, calib) + + if (imgfinal != null){ // if projection is out of range of the image, stop drawing. + var ctx = this.ui.getContext("2d"); + ctx.lineWidth = 0.5; + + // note: 320*240 should be adjustable + var crop_area = crop_image(img.naturalWidth, img.naturalHeight, ctx.canvas.width, ctx.canvas.height, imgfinal); + + ctx.drawImage(img, crop_area[0], crop_area[1],crop_area[2], crop_area[3], 0, 0, ctx.canvas.width, ctx.canvas.height);// ctx.canvas.clientHeight); + //ctx.drawImage(img, 0,0,img.naturalWidth, img.naturalHeight, 0, 0, 320, 180);// ctx.canvas.clientHeight); + var imgfinal = vectorsub(imgfinal, [crop_area[0],crop_area[1]]); + var trans_ratio = { + x: ctx.canvas.height/crop_area[3], + y: ctx.canvas.height/crop_area[3], + } + + draw_box_on_image(ctx, box, imgfinal, trans_ratio, true); + } + } + } + } + + this.clear_canvas = function(){ + var c = this.ui; + var ctx = c.getContext("2d"); + ctx.clearRect(0, 0, c.width, c.height); + } + + + function vectorsub(vs, v){ + var ret = []; + var vl = v.length; + + for (var i = 0; imaxx) maxx=x; + else if (xmaxy) maxy=y; + else if (y clientWidth/clientHeight){ + //increate height + targetHeight = targetWidth*clientHeight/clientWidth; + } + else{ + targetWidth = targetHeight*clientWidth/clientHeight; + } + + var centerx = (maxx+minx)/2; + var centery = (maxy+miny)/2; + + return [ + centerx - targetWidth/2, + centery - targetHeight/2, + targetWidth, + targetHeight + ]; + } + + function draw_box_on_image(ctx, box, box_corners, trans_ratio, selected){ + var imgfinal = box_corners; + + if (!selected){ + let target_color = null; + if (box.world.data.cfg.color_obj == "category") + { + target_color = globalObjectCategory.get_color_by_category(box.obj_type); + } + else // by id + { + let idx = (box.obj_track_id)?parseInt(box.obj_track_id): box.obj_local_id; + target_color = globalObjectCategory.get_color_by_id(idx); + } + + + + //ctx.strokeStyle = get_obj_cfg_by_type(box.obj_type).color; + + //var c = get_obj_cfg_by_type(box.obj_type).color; + var r ="0x"+(target_color.x*256).toString(16); + var g ="0x"+(target_color.y*256).toString(16);; + var b ="0x"+(target_color.z*256).toString(16);; + + ctx.fillStyle="rgba("+parseInt(r)+","+parseInt(g)+","+parseInt(b)+",0.2)"; + } + else{ + ctx.strokeStyle="#ff00ff"; + ctx.fillStyle="rgba(255,0,255,0.2)"; + } + + // front panel + ctx.beginPath(); + ctx.moveTo(imgfinal[3*2]*trans_ratio.x,imgfinal[3*2+1]*trans_ratio.y); + + for (var i=0; i < imgfinal.length/2/2; i++) + { + ctx.lineTo(imgfinal[i*2+0]*trans_ratio.x, imgfinal[i*2+1]*trans_ratio.y); + } + + ctx.closePath(); + ctx.fill(); + + // frame + ctx.beginPath(); + + ctx.moveTo(imgfinal[3*2]*trans_ratio.x,imgfinal[3*2+1]*trans_ratio.y); + + for (var i=0; i < imgfinal.length/2/2; i++) + { + ctx.lineTo(imgfinal[i*2+0]*trans_ratio.x, imgfinal[i*2+1]*trans_ratio.y); + } + //ctx.stroke(); + + + //ctx.strokeStyle="#ff00ff"; + //ctx.beginPath(); + + ctx.moveTo(imgfinal[7*2]*trans_ratio.x,imgfinal[7*2+1]*trans_ratio.y); + + for (var i=4; i < imgfinal.length/2; i++) + { + ctx.lineTo(imgfinal[i*2+0]*trans_ratio.x, imgfinal[i*2+1]*trans_ratio.y); + } + + ctx.moveTo(imgfinal[0*2]*trans_ratio.x,imgfinal[0*2+1]*trans_ratio.y); + ctx.lineTo(imgfinal[4*2+0]*trans_ratio.x, imgfinal[4*2+1]*trans_ratio.y); + ctx.moveTo(imgfinal[1*2]*trans_ratio.x,imgfinal[1*2+1]*trans_ratio.y); + ctx.lineTo(imgfinal[5*2+0]*trans_ratio.x, imgfinal[5*2+1]*trans_ratio.y); + ctx.moveTo(imgfinal[2*2]*trans_ratio.x,imgfinal[2*2+1]*trans_ratio.y); + ctx.lineTo(imgfinal[6*2+0]*trans_ratio.x, imgfinal[6*2+1]*trans_ratio.y); + ctx.moveTo(imgfinal[3*2]*trans_ratio.x,imgfinal[3*2+1]*trans_ratio.y); + ctx.lineTo(imgfinal[7*2+0]*trans_ratio.x, imgfinal[7*2+1]*trans_ratio.y); + + + ctx.stroke(); + } +} + + + +class ImageContext extends MovableView{ + + constructor(parentUi, name, autoSwitch, cfg, on_img_click){ + + // create ui + let template = document.getElementById("image-wrapper-template"); + let tool = template.content.cloneNode(true); + // this.boxEditorHeaderUi.appendChild(tool); + // return this.boxEditorHeaderUi.lastElementChild; + + parentUi.appendChild(tool); + let ui = parentUi.lastElementChild; + let handle = ui.querySelector("#move-handle"); + super(handle, ui); + + this.ui = ui; + this.cfg = cfg; + this.on_img_click = on_img_click; + this.autoSwitch = autoSwitch; + this.setImageName(name); + } + + remove(){ + this.ui.remove(); + } + + + setImageName(name) + { + this.name = name; + this.ui.querySelector("#header").innerText = (this.autoSwitch?"auto-":"")+name; + } + + + get_selected_box = null; + + + init_image_op(func_get_selected_box){ + this.ui.onclick = (e)=>this.on_click(e); + this.get_selected_box = func_get_selected_box; + // var h = parentUi.querySelector("#resize-handle"); + // h.onmousedown = resize_mouse_down; + + // c.onresize = on_resize; + } + clear_main_canvas(){ + + var boxes = this.ui.querySelector("#svg-boxes").children; + + if (boxes.length>0){ + for (var c=boxes.length-1; c >= 0; c--){ + boxes[c].remove(); + } + } + + var points = this.ui.querySelector("#svg-points").children; + + if (points.length>0){ + for (var c=points.length-1; c >= 0; c--){ + points[c].remove(); + } + } + } + + + + + + + world = null; + img = null; + + attachWorld(world){ + this.world = world; + }; + + hide (){ + this.ui.style.display="none"; + }; + + hidden(){ + this.ui.style.display=="none"; + }; + + show(){ + this.ui.style.display=""; + }; + + + + drawing = false; + points = []; + polyline; + + all_lines=[]; + + img_lidar_point_map = {}; + + point_color_by_distance(x,y) + { + // x,y are image coordinates + let p = this.img_lidar_point_map[y*this.img.width+x]; + + let distance = Math.sqrt(p[1]*p[1] + p[2]*p[2] + p[3]*p[3] ); + + if (distance > 60.0) + distance = 60.0; + else if (distance < 10.0) + distance = 10.0; + + return [(distance-10)/50.0, 1- (distance-10)/50.0, 0].map(c=>{ + let hex = Math.floor(c*255).toString(16); + if (hex.length == 1) + hex = "0"+hex; + return hex; + }).reduce((a,b)=>a+b,"#"); + + } + + to_polyline_attr(points){ + return points.reduce(function(x,y){ + return String(x)+","+y; + } + ) + } + + + to_viewbox_coord(x,y){ + var div = this.ui.querySelector("#maincanvas-svg"); + + x = Math.round(x*2048/div.clientWidth); + y = Math.round(y*1536/div.clientHeight); + return [x,y]; + + } + + on_click(e){ + var p= this.to_viewbox_coord(e.layerX, e.layerY); + var x=p[0]; + var y=p[1]; + + console.log("clicked",x,y); + + + if (!this.drawing){ + + if (e.ctrlKey){ + this.drawing = true; + var svg = this.ui.querySelector("#maincanvas-svg"); + //svg.style.position = "absolute"; + + this.polyline = document.createElementNS("http://www.w3.org/2000/svg", 'polyline'); + svg.appendChild(this.polyline); + this.points.push(x); + this.points.push(y); + + + this.polyline.setAttribute("class", "maincanvas-line") + this.polyline.setAttribute("points", this.to_polyline_attr(this.points)); + + var c = this.ui; + c.onmousemove = on_move; + c.ondblclick = on_dblclick; + c.onkeydown = on_key; + + } + else{ + // not drawing + //this is a test + if (false){ + let nearest_x = 100000; + let nearest_y = 100000; + let selected_pts = []; + + for (let i =x-100; i= this.img.width) + continue; + + for (let j = y-100; j= this.img.height) + continue; + + let lidarpoint = this.img_lidar_point_map[j*this.img.width+i]; + if (lidarpoint){ + //console.log(i,j, lidarpoint); + selected_pts.push(lidarpoint); //index of lidar point + + if (((i-x) * (i-x) + (j-y)*(j-y)) < ((nearest_x-x)*(nearest_x-x) + (nearest_y-y)*(nearest_y-y))){ + nearest_x = i; + nearest_y = j; + } + } + + } + } + console.log("nearest", nearest_x, nearest_y); + this.draw_point(nearest_x, nearest_y); + if (nearest_x < 100000) + { + this.on_img_click([this.img_lidar_point_map[nearest_y*this.img.width+nearest_x][0]]); + } + } + + } + + } else { + if (this.points[this.points.length-2]!=x || this.points[this.points.length-1]!=y){ + this.points.push(x); + this.points.push(y); + this.polyline.setAttribute("points", this.to_polyline_attr(this.points)); + } + + } + + + function on_move(e){ + var p= to_viewbox_coord(e.layerX, e.layerY); + var x=p[0]; + var y=p[1]; + + console.log(x,y); + this.polyline.setAttribute("points", this.to_polyline_attr(this.points) + ',' + x + ',' + y); + } + + function on_dblclick(e){ + + this.points.push(this.points[0]); + this.points.push(this.points[1]); + + this.polyline.setAttribute("points", this.to_polyline_attr(this.points)); + console.log(this.points) + + all_lines.push(this.points); + + this.drawing = false; + this.points = []; + + var c = this.ui; + c.onmousemove = null; + c.ondblclick = null; + c.onkeypress = null; + c.blur(); + } + + function cancel(){ + + polyline.remove(); + + this.drawing = false; + this.points = []; + var c = this.ui; + c.onmousemove = null; + c.ondblclick = null; + c.onkeypress = null; + + c.blur(); + } + + function on_key(e){ + if (e.key == "Escape"){ + cancel(); + + } + } + } + + + // all boxes + + + + getCalib(){ + var scene_meta = this.world.sceneMeta; + + if (!scene_meta.calib.camera){ + return null; + } + + //var active_camera_name = this.world.cameras.active_name; + var calib = scene_meta.calib.camera[this.name]; + + return calib; + } + + + + + get_trans_ratio(){ + var img = this.world.cameras.getImageByName(this.name); + + if (!img || img.width==0){ + return null; + } + + var clientWidth, clientHeight; + + clientWidth = 2048; + clientHeight = 1536; + + var trans_ratio ={ + x: clientWidth/img.naturalWidth, + y: clientHeight/img.naturalHeight, + }; + + return trans_ratio; + } + + show_image(){ + var svgimage = this.ui.querySelector("#svg-image"); + + // active img is set by global, it's not set sometimes. + var img = this.world.cameras.getImageByName(this.name); + if (img){ + svgimage.setAttribute("xlink:href", img.src); + } + + this.img = img; + + + } + + + points_to_svg(points, trans_ratio, cssclass, radius=2){ + var ptsFinal = points.map(function(x, i){ + if (i%2==0){ + return Math.round(x * trans_ratio.x); + }else { + return Math.round(x * trans_ratio.y); + } + }); + + var svg = document.createElementNS("http://www.w3.org/2000/svg", 'g'); + + if (cssclass) + { + svg.setAttribute("class", cssclass); + } + + for (let i = 0; i < ptsFinal.length; i+=2){ + + + let x = ptsFinal[i]; + let y = ptsFinal[i+1]; + + let p = document.createElementNS("http://www.w3.org/2000/svg", 'circle'); + p.setAttribute("cx", x); + p.setAttribute("cy", y); + p.setAttribute("r", 2); + p.setAttribute("stroke-width", "1"); + + if (! cssclass){ + let image_x = points[i]; + let image_y = points[i+1]; + let color = point_color_by_distance(image_x, image_y); + color += "24"; //transparency + p.setAttribute("stroke", color); + p.setAttribute("fill", color); + } + + svg.appendChild(p); + } + + return svg; + } + + draw_point(x,y){ + let trans_ratio = this.get_trans_ratio(); + let svg = this.ui.querySelector("#svg-points"); + let pts_svg = this.points_to_svg([x,y], trans_ratio, "radar-points"); + svg.appendChild(pts_svg); + }; + + + + + render_2d_image (){ + + + if (this.cfg.disableMainImageContext) + return; + this.clear_main_canvas(); + + this.show_image(); + this.draw_svg(); + } + + + hide_canvas(){ + //document.getElementsByClassName("ui-wrapper")[0].style.display="none"; + this.ui.style.display="none"; + } + + show_canvas(){ + this.ui.style.display="inline"; + } + + + draw_svg(){ + // draw picture + var img = this.world.cameras.getImageByName(this.name); + + if (!img || img.width==0){ + this.hide_canvas(); + return; + } + + this.show_canvas(); + + var trans_ratio = this.get_trans_ratio(); + + var calib = this.getCalib(); + if (!calib){ + return; + } + + let svg = this.ui.querySelector("#svg-boxes"); + + // draw boxes + this.world.annotation.boxes.forEach((box)=>{ + var imgfinal = box_to_2d_points(box, calib); + if (imgfinal){ + var box_svg = this.box_to_svg (box, imgfinal, trans_ratio, this.get_selected_box() == box); + svg.appendChild(box_svg); + } + + }); + + svg = this.ui.querySelector("#svg-points"); + + // draw radar points + if (this.cfg.projectRadarToImage) + { + this.world.radars.radarList.forEach(radar=>{ + let pts = radar.get_unoffset_radar_points(); + let ptsOnImg = points3d_to_image2d(pts, calib); + + // there may be none after projecting + if (ptsOnImg && ptsOnImg.length>0){ + let pts_svg = this.points_to_svg(ptsOnImg, trans_ratio, radar.cssStyleSelector); + svg.appendChild(pts_svg); + } + }); + } + + + + // project lidar points onto camera image + if (this.cfg.projectLidarToImage){ + let pts = this.world.lidar.get_all_points(); + let ptsOnImg = points3d_to_image2d(pts, calib, true, this.img_lidar_point_map, img.width, img.height); + + // there may be none after projecting + if (ptsOnImg && ptsOnImg.length>0){ + let pts_svg = this.points_to_svg(ptsOnImg, trans_ratio); + svg.appendChild(pts_svg); + } + } + + } + + box_to_svg(box, box_corners, trans_ratio, selected){ + + + var imgfinal = box_corners.map(function(x, i){ + if (i%2==0){ + return Math.round(x * trans_ratio.x); + }else { + return Math.round(x * trans_ratio.y); + } + }) + + + var svg = document.createElementNS("http://www.w3.org/2000/svg", 'g'); + svg.setAttribute("id", "svg-box-local-"+box.obj_local_id); + + if (selected){ + svg.setAttribute("class", box.obj_type+" box-svg box-svg-selected"); + } else{ + if (box.world.data.cfg.color_obj == "id") + { + svg.setAttribute("class", "color-"+box.obj_track_id%33); + } + else // by id + { + svg.setAttribute("class", box.obj_type + " box-svg"); + } + } + + + var front_panel = document.createElementNS("http://www.w3.org/2000/svg", 'polygon'); + svg.appendChild(front_panel); + front_panel.setAttribute("points", + imgfinal.slice(0, 4*2).reduce(function(x,y){ + return String(x)+","+y; + }) + ) + + /* + var back_panel = document.createElementNS("http://www.w3.org/2000/svg", 'polygon'); + svg.appendChild(back_panel); + back_panel.setAttribute("points", + imgfinal.slice(4*2).reduce(function(x,y){ + return String(x)+","+y; + }) + ) + */ + + for (var i = 0; i<4; ++i){ + var line = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + svg.appendChild(line); + line.setAttribute("x1", imgfinal[(4+i)*2]); + line.setAttribute("y1", imgfinal[(4+i)*2+1]); + line.setAttribute("x2", imgfinal[(4+(i+1)%4)*2]); + line.setAttribute("y2", imgfinal[(4+(i+1)%4)*2+1]); + } + + + for (var i = 0; i<4; ++i){ + var line = document.createElementNS("http://www.w3.org/2000/svg", 'line'); + svg.appendChild(line); + line.setAttribute("x1", imgfinal[i*2]); + line.setAttribute("y1", imgfinal[i*2+1]); + line.setAttribute("x2", imgfinal[(i+4)*2]); + line.setAttribute("y2", imgfinal[(i+4)*2+1]); + } + + return svg; + } + + + boxes_manager = { + display_image: ()=>{ + if (!this.cfg.disableMainImageContext) + this.render_2d_image(); + }, + + add_box: (box)=>{ + var calib = this.getCalib(); + if (!calib){ + return; + } + var trans_ratio = this.get_trans_ratio(); + if (trans_ratio){ + var imgfinal = box_to_2d_points(box, calib); + if (imgfinal){ + var imgfinal = imgfinal.map(function(x, i){ + if (i%2==0){ + return Math.round(x * trans_ratio.x); + }else { + return Math.round(x * trans_ratio.y); + } + }) + + var svg_box = this.box_to_svg(box, imgfinal, trans_ratio); + var svg = this.ui.querySelector("#svg-boxes"); + svg.appendChild(svg_box); + } + } + }, + + + onBoxSelected: (box_obj_local_id, obj_type)=>{ + var b = this.ui.querySelector("#svg-box-local-"+box_obj_local_id); + if (b){ + b.setAttribute("class", "box-svg-selected"); + } + }, + + + onBoxUnselected: (box_obj_local_id, obj_type)=>{ + var b = this.ui.querySelector("#svg-box-local-"+box_obj_local_id); + + if (b) + b.setAttribute("class", obj_type); + }, + + remove_box: (box_obj_local_id)=>{ + var b = this.ui.querySelector("#svg-box-local-"+box_obj_local_id); + + if (b) + b.remove(); + }, + + update_obj_type: (box_obj_local_id, obj_type)=>{ + this.onBoxSelected(box_obj_local_id, obj_type); + }, + + update_box: (box)=>{ + var b = this.ui.querySelector("#svg-box-local-"+box.obj_local_id); + if (!b){ + return; + } + + var children = b.childNodes; + + var calib = this.getCalib(); + if (!calib){ + return; + } + + var trans_ratio = this.get_trans_ratio(); + var imgfinal = box_to_2d_points(box, calib); + + if (!imgfinal){ + //box may go out of image + return; + } + var imgfinal = imgfinal.map(function(x, i){ + if (i%2==0){ + return Math.round(x * trans_ratio.x); + }else { + return Math.round(x * trans_ratio.y); + } + }) + + if (imgfinal){ + var front_panel = children[0]; + front_panel.setAttribute("points", + imgfinal.slice(0, 4*2).reduce(function(x,y){ + return String(x)+","+y; + }) + ) + + + + for (var i = 0; i<4; ++i){ + var line = children[1+i]; + line.setAttribute("x1", imgfinal[(4+i)*2]); + line.setAttribute("y1", imgfinal[(4+i)*2+1]); + line.setAttribute("x2", imgfinal[(4+(i+1)%4)*2]); + line.setAttribute("y2", imgfinal[(4+(i+1)%4)*2+1]); + } + + + for (var i = 0; i<4; ++i){ + var line = children[5+i]; + line.setAttribute("x1", imgfinal[i*2]); + line.setAttribute("y1", imgfinal[i*2+1]); + line.setAttribute("x2", imgfinal[(i+4)*2]); + line.setAttribute("y2", imgfinal[(i+4)*2+1]); + } + } + + } + } + +} + + +class ImageContextManager { + constructor(parentUi, selectorUi, cfg, on_img_click){ + this.parentUi = parentUi; + this.selectorUi = selectorUi; + this.cfg = cfg; + this.on_img_click = on_img_click; + + this.addImage("", true); + + + this.selectorUi.onmouseenter=function(event){ + if (this.timerId) + { + clearTimeout(this.timerId); + this.timerId = null; + } + + event.target.querySelector("#camera-list").style.display=""; + + }; + + + this.selectorUi.onmouseleave=function(event){ + let ui = event.target.querySelector("#camera-list"); + + this.timerId = setTimeout(()=>{ + ui.style.display="none"; + this.timerId = null; + }, + 200); + + }; + + this.selectorUi.querySelector("#camera-list").onclick = (event)=>{ + let cameraName = event.target.innerText; + + if (cameraName == "auto"){ + + let existed = this.images.find(x=>x.autoSwitch); + + if (existed) + { + this.removeImage(existed); + } + else + { + this.addImage("", true); + } + + } + else{ + let existed = this.images.find(x=>!x.autoSwitch && x.name == cameraName); + + if (existed) + { + this.removeImage(existed); + + } + else + { + this.addImage(cameraName); + } + } + }; + + } + + updateCameraList(cameras){ + + let autoCamera = '
auto
'; + + if (this.images.find(i=>i.autoSwitch)){ + autoCamera = '
auto
'; + } + + let camera_selector_str = cameras.map(c=>{ + + let existed = this.images.find(i=>i.name == c && !i.autoSwitch); + let className = existed?"camera-item camera-selected":"camera-item"; + + return `
${c}
`; + }).reduce((x,y)=>x+y, autoCamera); + + let ui = this.selectorUi.querySelector("#camera-list"); + ui.innerHTML = camera_selector_str; + ui.style.display="none"; + + this.setDefaultBestCamera(cameras[0]); + } + + setDefaultBestCamera(c){ + + if (!this.bestCamera) + { + let existed = this.images.find(x=>x.autoSwitch); + if (existed) + { + existed.setImageName(c); + } + + this.bestCamera = c; + } + } + + images = []; + addImage(name, autoSwitch) + { + + if (autoSwitch && this.bestCamera && !name) + name = this.bestCamera; + + let image = new ImageContext(this.parentUi, name, autoSwitch, this.cfg, this.on_img_click); + + this.images.push(image); + + if (this.init_image_op_para){ + image.init_image_op(this.init_image_op_para); + } + + if (this.world){ + image.attachWorld(this.world); + image.render_2d_image(); + } + + + let selectorName = autoSwitch?"auto":name; + + let ui = this.selectorUi.querySelector("#camera-item-"+selectorName); + if (ui) + ui.className = "camera-item camera-selected"; + + + return image; + } + + removeImage(image){ + + let selectorName = image.autoSwitch?"auto":image.name; + this.selectorUi.querySelector("#camera-item-"+selectorName).className = "camera-item"; + this.images = this.images.filter(x=>x!=image); + image.remove(); + + + } + + setBestCamera(camera) + { + this.images.filter(i=>i.autoSwitch).forEach(i=>{ + i.setImageName(camera); + i.boxes_manager.display_image(); + }); + + this.bestCamera = camera; + } + + render_2d_image(){ + this.images.forEach(i=>i.render_2d_image()); + } + + attachWorld(world){ + + this.world = world; + this.images.forEach(i=>i.attachWorld(world)); + } + + hide(){ + this.images.forEach(i=>i.hide()); + } + + + show(){ + this.images.forEach(i=>i.show()); + } + + clear_main_canvas(){ + this.images.forEach(i=>i.clear_main_canvas()); + } + + init_image_op(op){ + this.init_image_op_para = op; + this.images.forEach(i=>i.init_image_op(op)); + } + hidden(){ + return false; + } + + choose_best_camera_for_point = choose_best_camera_for_point; + + self = this; + + boxes_manager = { + + display_image: ()=>{ + if (!this.cfg.disableMainImageContext) + this.render_2d_image(); + }, + + add_box: (box)=>{ + this.images.forEach(i=>i.boxes_manager.add_box(box)); + }, + + + onBoxSelected: (box_obj_local_id, obj_type)=>{ + this.images.forEach(i=>i.boxes_manager.onBoxSelected(box_obj_local_id, obj_type)); + }, + + + onBoxUnselected: (box_obj_local_id, obj_type)=>{ + this.images.forEach(i=>i.boxes_manager.onBoxUnselected(box_obj_local_id, obj_type)); + }, + + remove_box: (box_obj_local_id)=>{ + this.images.forEach(i=>i.boxes_manager.remove_box(box_obj_local_id)); + }, + + update_obj_type: (box_obj_local_id, obj_type)=>{ + this.images.forEach(i=>i.boxes_manager.update_obj_type(box_obj_local_id, obj_type)); + }, + + update_box: (box)=>{ + this.images.forEach(i=>i.boxes_manager.update_box(box)); + } + } + + +} + +function box_to_2d_points(box, calib){ + var scale = box.scale; + var pos = box.position; + var rotation = box.rotation; + + var box3d = psr_to_xyz(pos, scale, rotation); + + //console.log(box.obj_track_id, box3d.slice(8*4)); + + box3d = box3d.slice(0,8*4); + return points3d_homo_to_image2d(box3d, calib); +} + +// points3d is length 4 row vector, homogeneous coordinates +// returns 2d row vectors +function points3d_homo_to_image2d(points3d, calib, accept_partial=false,save_map, img_dx, img_dy){ + var imgpos = matmul(calib.extrinsic, points3d, 4); + + //rect matrix shall be applied here, for kitti + if (calib.rect){ + imgpos = matmul(calib.rect, imgpos, 4); + } + + var imgpos3 = vector4to3(imgpos); + + var imgpos2; + if (calib.intrinsic.length>9) { + imgpos2 = matmul(calib.intrinsic, imgpos, 4); + } + else + imgpos2 = matmul(calib.intrinsic, imgpos3, 3); + + let imgfinal = vector3_nomalize(imgpos2); + let imgfinal_filterd = []; + + if (accept_partial){ + let temppos=[]; + let p = imgpos3; + for (var i = 0; i0){ + let x = imgfinal[i*2]; + let y = imgfinal[i*2+1]; + + x = Math.round(x); + y = Math.round(y); + if (x > 0 && x < img_dx && y > 0 && y < img_dy){ + if (save_map){ + save_map[img_dx*y+x] = [i, points3d[i*4+0], points3d[i*4+1], points3d[i*4+2]]; //save index? a little dangerous! //[points3d[i*4+0], points3d[i*4+1], points3d[i*4+2]]; + } + + imgfinal_filterd.push(x); + imgfinal_filterd.push(y); + + } + else{ + // console.log("points outside of image",x,y); + } + + } + } + + imgfinal = imgfinal_filterd; + //warning: what if calib.intrinsic.length + //todo: this function need clearance + //imgpos2 = matmul(calib.intrinsic, temppos, 3); + } + else if (!accept_partial && !all_points_in_image_range(imgpos3)){ + return null; + } + + return imgfinal; +} + +function point3d_to_homo(points){ + let homo=[]; + for (let i =0; i0){ + return valid_proj_pos[0].calib; + } + + return null; + +} + + +export {ImageContextManager, BoxImageContext}; diff --git a/public/js/info_box.js b/public/js/info_box.js new file mode 100644 index 0000000..425053e --- /dev/null +++ b/public/js/info_box.js @@ -0,0 +1,115 @@ +import { globalKeyDownManager } from "./keydown_manager.js"; +import { PopupDialog } from "./popup_dialog.js"; + + + +class InfoBox extends PopupDialog{ + + mouseDown = false; + mouseDwwnPos = {}; + + + constructor(ui) + { + super(ui); + + this.contentUi = this.ui.querySelector("#info-content"); + + this.buttons = { + "yes": this.ui.querySelector("#btn-yes"), + "no": this.ui.querySelector("#btn-no"), + "maximize": this.ui.querySelector("#btn-maximize"), + "restore": this.ui.querySelector("#btn-restore"), + "exit": this.ui.querySelector("#btn-exit"), + }; + + for (let btn in this.buttons) + { + this.buttons[btn].onclick = ()=>{ + this.hide(btn); + } + } + + this.ui.addEventListener("keydown", (ev)=>{ + //anykey + if ( ev.shiftKey || ev.ctrlKey || ev.altKey) + { + // + } + else + { + this.hide(); + ev.preventDefault(); + ev.stopPropagation(); + } + + }); + } + + + + showButtons(btns){ + for (let btn in this.buttons) + { + this.buttons[btn].style.display = 'none'; + } + + for (let btn in btns) + { + this.buttons[btns[btn]].style.display = ''; + } + } + + makeVisible(pointerPosition) + { + if (!pointerPosition) + { + + //default pos + let parentRect = this.ui.getBoundingClientRect(); + let viewRect = this.viewUi.getBoundingClientRect(); + + this.viewUi.style.top = (parentRect.top+parentRect.height/3) + "px"; + this.viewUi.style.left = (parentRect.left+parentRect.width/2-viewRect.width/2) + "px"; + } + else + { + let parentRect = this.ui.getBoundingClientRect(); + let viewRect = this.viewUi.getBoundingClientRect(); + + let left = pointerPosition.x - viewRect.width/2; + if (left < parentRect.left) left = parentRect.left; + if (left + viewRect.width > parentRect.right) + left -= left + viewRect.width - parentRect.right; + + let top = pointerPosition.y - viewRect.height/2; + if (top < parentRect.top) + top = parentRect.top; + + if (top + viewRect.height > parentRect.bottom) + top -= top + viewRect.height - parentRect.bottom; + + this.viewUi.style.top = top + "px"; + this.viewUi.style.left = left + "px"; + } + } + + + show(title, content, btnList, onexit, pointerPosition) + { + this.showButtons(btnList); + + this.titleUi.innerText = title; + this.contentUi.innerHTML = content; + + super.show(onexit); + + this.makeVisible(pointerPosition); + + this.ui.focus(); + } + +} + + +export {InfoBox}; \ No newline at end of file diff --git a/public/js/keydown_manager.js b/public/js/keydown_manager.js new file mode 100644 index 0000000..dca2829 --- /dev/null +++ b/public/js/keydown_manager.js @@ -0,0 +1,42 @@ + + +class KeyDownManager +{ + + handlerList = []; + + // return id; + register(handler, name) + { + this.handlerList.push([name, handler]); + console.log("register keydown", name); + } + + deregister(name) + { + console.log("deregister keydown", name); + this.handlerList = this.handlerList.filter(v=>v[0]!== name); + } + + constructor() + { + document.addEventListener( 'keydown', (event)=>{ + + for (let i = this.handlerList.length-1; i >= 0; i--) + { + let ret = this.handlerList[i][1](event); + + if (!ret) + { + break; + } + } + }); + } + +} + + +var globalKeyDownManager = new KeyDownManager(); + +export{globalKeyDownManager}; \ No newline at end of file diff --git a/public/js/lib/OrbitControls.js b/public/js/lib/OrbitControls.js new file mode 100644 index 0000000..904a2fd --- /dev/null +++ b/public/js/lib/OrbitControls.js @@ -0,0 +1,1299 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Spherical, + TOUCH, + Vector2, + Vector3 +} from './three.module.js'; + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +const _changeEvent = { type: 'change' }; +const _startEvent = { type: 'start' }; +const _endEvent = { type: 'end' }; + +class OrbitControls extends EventDispatcher { + + constructor( object, domElement ) { + + super(); + + if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + this.object = object; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 + + // The four arrow keys + this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; + + // Mouse buttons + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // the target DOM element for key events + this._domElementKeyEvents = null; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.getDistance = function () { + + return this.object.position.distanceTo( this.target ); + + }; + + this.listenToKeyEvents = function ( domElement ) { + + domElement.addEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = domElement; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( _changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + const offset = new Vector3(); + + // so camera.up is the orbit axis + const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + const quatInverse = quat.clone().invert(); + + const lastPosition = new Vector3(); + const lastQuaternion = new Quaternion(); + + const twoPI = 2 * Math.PI; + + return function update() { + + const position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + + let min = scope.minAzimuthAngle; + let max = scope.maxAzimuthAngle; + + if ( isFinite( min ) && isFinite( max ) ) { + + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + + if ( min <= max ) { + + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + + } else { + + spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, spherical.theta ) : + Math.min( max, spherical.theta ); + + } + + } + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + + if ( scope._domElementKeyEvents !== null ) { + + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + + } + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + const scope = this; + + const STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + let state = STATE.NONE; + + const EPS = 0.000001; + + // current position in spherical coordinates + const spherical = new Spherical(); + const sphericalDelta = new Spherical(); + + let scale = 1; + const panOffset = new Vector3(); + let zoomChanged = false; + + const rotateStart = new Vector2(); + const rotateEnd = new Vector2(); + const rotateDelta = new Vector2(); + + const panStart = new Vector2(); + const panEnd = new Vector2(); + const panDelta = new Vector2(); + + const dollyStart = new Vector2(); + const dollyEnd = new Vector2(); + const dollyDelta = new Vector2(); + + const pointers = []; + const pointerPositions = {}; + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + const panLeft = function () { + + const v = new Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + const panUp = function () { + + const v = new Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + const pan = function () { + + const offset = new Vector3(); + + return function pan( deltaX, deltaY ) { + + const element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + const position = scope.object.position; + offset.copy( position ).sub( scope.target ); + let targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + const element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( /*event*/ ) { + + // no-op + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + let needsUpdate = false; + + switch ( event.code ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate() { + + if ( pointers.length === 1 ) { + + rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan() { + + if ( pointers.length === 1 ) { + + panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly() { + + const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX; + const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enablePan ) handleTouchStartPan(); + + } + + function handleTouchStartDollyRotate() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enableRotate ) handleTouchStartRotate(); + + } + + function handleTouchMoveRotate( event ) { + + if ( pointers.length == 1 ) { + + rotateEnd.set( event.pageX, event.pageY ); + + } else { + + const position = getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + const element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( pointers.length === 1 ) { + + panEnd.set( event.pageX, event.pageY ); + + } else { + + const position = getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + const position = getSecondPointerPosition( event ); + + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyOut( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + function handleTouchEnd( /*event*/ ) { + + // no-op + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + if ( pointers.length === 0 ) { + + scope.domElement.setPointerCapture( event.pointerId ); + + scope.domElement.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.addEventListener( 'pointerup', onPointerUp ); + + } + + // + + addPointer( event ); + + if ( event.pointerType === 'touch' ) { + + onTouchStart( event ); + + } else { + + onMouseDown( event ); + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + onTouchMove( event ); + + } else { + + onMouseMove( event ); + + } + + } + + function onPointerUp( event ) { + + if ( scope.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + onTouchEnd(); + + } else { + + onMouseUp( event ); + + } + + removePointer( event ); + + // + + if ( pointers.length === 0 ) { + + scope.domElement.releasePointerCapture( event.pointerId ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + } + + } + + function onPointerCancel( event ) { + + removePointer( event ); + + } + + function onMouseDown( event ) { + + + // lie, redefined key usage. + if ( event.ctrlKey || event.metaKey || event.shiftKey ) + return; + + let mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( _startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + handleMouseUp( event ); + + scope.dispatchEvent( _endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + + scope.dispatchEvent( _startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( _endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + trackPointer( event ); + + switch ( pointers.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate(); + + state = STATE.TOUCH_ROTATE; + + break; + + case TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan(); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan(); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate(); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( _startEvent ); + + } + + } + + function onTouchMove( event ) { + + trackPointer( event ); + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + handleTouchEnd( event ); + + scope.dispatchEvent( _endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + function addPointer( event ) { + + pointers.push( event ); + + } + + function removePointer( event ) { + + delete pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < pointers.length; i ++ ) { + + if ( pointers[ i ].pointerId == event.pointerId ) { + + pointers.splice( i, 1 ); + return; + + } + + } + + } + + function trackPointer( event ) { + + let position = pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + function getSecondPointerPosition( event ) { + + const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ]; + + return pointerPositions[ pointer.pointerId ]; + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + + // force an update at start + + this.update(); + + } + +} + + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// This is very similar to OrbitControls, another set of touch behavior +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or arrow keys / touch: one-finger move + +class MapControls extends OrbitControls { + + constructor( object, domElement ) { + + super( object, domElement ); + + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = MOUSE.PAN; + this.mouseButtons.RIGHT = MOUSE.ROTATE; + + this.touches.ONE = TOUCH.PAN; + this.touches.TWO = TOUCH.DOLLY_ROTATE; + + } + +} + +export { OrbitControls, MapControls }; diff --git a/public/js/lib/PCDLoader.js b/public/js/lib/PCDLoader.js new file mode 100644 index 0000000..b84f1b8 --- /dev/null +++ b/public/js/lib/PCDLoader.js @@ -0,0 +1,534 @@ +/** + * @author Filipe Caixeta / http://filipecaixeta.com.br + * @author Mugen87 / https://github.com/Mugen87 + * + * Description: A THREE loader for PCD ascii and binary files. + * + * Limitations: Compressed binary files are not supported. + * + */ + + import { + DefaultLoadingManager, + FileLoader, + LoaderUtils, +} from "./three.module.js"; + + +var PCDLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + this.littleEndian = true; + +}; + +function decompressLZF(inData, outLength) { + var inLength = inData.length + var outData = new Uint8Array(outLength) + var inPtr = 0 + var outPtr = 0 + var ctrl + var len + var ref + do { + ctrl = inData[inPtr++] + if (ctrl < 1 << 5) { + ctrl++ + if (outPtr + ctrl > outLength) throw new Error('Output buffer is not large enough') + if (inPtr + ctrl > inLength) throw new Error('Invalid compressed data') + do { + outData[outPtr++] = inData[inPtr++] + } while (--ctrl) + } else { + len = ctrl >> 5 + ref = outPtr - ((ctrl & 0x1f) << 8) - 1 + if (inPtr >= inLength) throw new Error('Invalid compressed data') + if (len === 7) { + len += inData[inPtr++] + if (inPtr >= inLength) throw new Error('Invalid compressed data') + } + + ref -= inData[inPtr++] + if (outPtr + len + 2 > outLength) throw new Error('Output buffer is not large enough') + if (ref < 0) throw new Error('Invalid compressed data') + if (ref >= outPtr) throw new Error('Invalid compressed data') + do { + outData[outPtr++] = outData[ref++] + } while (--len + 2) + } + } while (inPtr < inLength) + + return outData + } + + +PCDLoader.prototype = { + + constructor: PCDLoader, + + load: function ( url, onLoad, onProgress, onError, onFileLoaded ) { + + var scope = this; + + var loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.load( url, function ( data ) { + + try { + if (onFileLoaded) + onFileLoaded(); + onLoad( scope.parse( data, url) ); + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + throw e; + + } + + } + + }, onProgress, onError ); + + }, + + setPath: function ( value ) { + + this.path = value; + return this; + + }, + + parse: function(data, url){ + var addr = url.split("."); + var file_ext = addr[addr.length-1]; + + if (file_ext === "pcd") + return this.parsePcd(data, url); + else { + console.log("load", file_ext, "file"); + return this.parseBin(data, url); + } + + }, + + parseBin: function(data, url){ + var dataview = new DataView( data, 0); + + var position = []; + var normal = []; + var color = []; + var intensity = []; + //kitti format, xyzi + var offset = 0; + + for ( var row = 0; row < data.byteLength/(4*4); row += 1 ) { + position.push( dataview.getFloat32( row*16 + 0, this.littleEndian ) ); + position.push( dataview.getFloat32( row*16 + 4, this.littleEndian ) ); + position.push( dataview.getFloat32( row*16 + 8, this.littleEndian ) ); + intensity.push(dataview.getFloat32( row*16 + 12, this.littleEndian ) ) + } + + return { + position: position, + color: color, + normal: normal, + intensity: intensity, + }; + }, + + parsePcd: function ( data, url) { + + function parseHeader( data ) { + + var PCDheader = {}; + var result1 = data.search( /[\r\n]DATA\s(\S*)\s/i ); + var result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.substr( result1 - 1 ) ); + + PCDheader.data = result2[ 1 ]; + PCDheader.headerLen = result2[ 0 ].length + result1; + PCDheader.str = data.substr( 0, PCDheader.headerLen ); + + // remove comments + + PCDheader.str = PCDheader.str.replace( /\#.*/gi, '' ); + + // parse + + PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str ); + PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str ); + PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str ); + PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str ); + PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str ); + PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str ); + PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str ); + PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str ); + PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str ); + + // evaluate + + if ( PCDheader.version !== null ) + PCDheader.version = parseFloat( PCDheader.version[ 1 ] ); + + if ( PCDheader.fields !== null ) + PCDheader.fields = PCDheader.fields[ 1 ].split( ' ' ); + + if ( PCDheader.type !== null ) + PCDheader.type = PCDheader.type[ 1 ].split( ' ' ); + + if ( PCDheader.width !== null ) + PCDheader.width = parseInt( PCDheader.width[ 1 ] ); + + if ( PCDheader.height !== null ) + PCDheader.height = parseInt( PCDheader.height[ 1 ] ); + + if ( PCDheader.viewpoint !== null ) + PCDheader.viewpoint = PCDheader.viewpoint[ 1 ]; + + if ( PCDheader.points !== null ) + PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 ); + + if ( PCDheader.points === null ) + PCDheader.points = PCDheader.width * PCDheader.height; + + if ( PCDheader.size !== null ) { + + PCDheader.size = PCDheader.size[ 1 ].split( ' ' ).map( function ( x ) { + + return parseInt( x, 10 ); + + } ); + + } + + if ( PCDheader.count !== null ) { + + PCDheader.count = PCDheader.count[ 1 ].split( ' ' ).map( function ( x ) { + + return parseInt( x, 10 ); + + } ); + + } else { + + PCDheader.count = []; + + for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) { + + PCDheader.count.push( 1 ); + + } + + } + + PCDheader.offset = {}; + + var sizeSum = 0; + + for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) { + + if ( PCDheader.data === 'ascii' || PCDheader.data === 'ascill') { + + PCDheader.offset[ PCDheader.fields[ i ] ] = i; + + } else { + + PCDheader.offset[ PCDheader.fields[ i ] ] = sizeSum; + sizeSum += PCDheader.size[ i ] * PCDheader.count[ i ]; + + } + + } + + // for binary only + + PCDheader.rowSize = sizeSum; + + return PCDheader; + + } + + var textData = LoaderUtils.decodeText( new Uint8Array( data ) ); + + // parse header (always ascii format) + + var PCDheader = parseHeader( textData ); + + // parse data + + var position = []; + var normal = []; + var color = []; + var velocity = []; + var intensity = []; + + // ascii + + function filterPoint(x,y,z) + { + if (isNaN(x)) + return true; + if (x == 0 && y== 0 && z==0) + return true; + // if (z >=2) + // return true; + } + + + if ( PCDheader.data === 'ascii' || PCDheader.data === 'ascill') { + + var offset = PCDheader.offset; + var pcdData = textData.substr( PCDheader.headerLen ); + var lines = pcdData.split( '\n' ); + + var intensity_index = PCDheader.fields.findIndex(n=>n==="intensity"); + var intensity_type = "F"; + var intensity_size = 4; + + if (intensity_index >= 0){ + intensity_type = PCDheader.type[intensity_index]; + intensity_size = PCDheader.size[intensity_index]; + } + + for ( var i = 0, l = lines.length; i < l; i ++ ) { + + if ( lines[ i ] === '' ) continue; + + var line = lines[ i ].split( ' ' ); + + if ( offset.x !== undefined ) { + var x,y,z; + x = parseFloat( line[ offset.x ] ); + y = parseFloat( line[ offset.y ] ); + z = parseFloat( line[ offset.z ] ); + + if (filterPoint(x,y,z)){ + continue; + } + + position.push( x ); + position.push( y ); + position.push( z ); + + } + + if ( offset.rgb !== undefined ) { + + var rgb = parseFloat( line[ offset.rgb ] ); + var r = ( rgb >> 16 ) & 0x0000ff; + var g = ( rgb >> 8 ) & 0x0000ff; + var b = ( rgb >> 0 ) & 0x0000ff; + color.push( r / 255, g / 255, b / 255 ); + + } + + if ( offset.normal_x !== undefined ) { + + normal.push( parseFloat( line[ offset.normal_x ] ) ); + normal.push( parseFloat( line[ offset.normal_y ] ) ); + normal.push( parseFloat( line[ offset.normal_z ] ) ); + + } + + if ( offset.vx !== undefined ) { + var vx,vy; + vx = parseFloat( line[ offset.vx ] ); + vy = parseFloat( line[ offset.vy ] ); + + velocity.push(vx); + velocity.push(vy); + velocity.push(0); + } + + + if (offset.intensity !== undefined) { + intensity.push( parseInt( line[ offset.intensity ] )); + } + + } + + } + + // binary + + if ( PCDheader.data === 'binary_compressed' ) { + + var sizes = new Uint32Array( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) ); + var compressedSize = sizes[ 0 ]; + var decompressedSize = sizes[ 1 ]; + var decompressed = decompressLZF( new Uint8Array( data, PCDheader.headerLen + 8, compressedSize ), decompressedSize ); + var dataview = new DataView( decompressed.buffer ); + + var offset = PCDheader.offset; + var intensity_index = PCDheader.fields.findIndex(n=>n==="intensity"); + var intensity_type = "F"; + var intensity_size = 4; + + if (intensity_index >= 0){ + intensity_type = PCDheader.type[intensity_index]; + intensity_size = PCDheader.size[intensity_index]; + } + + let size = {}; + + PCDheader.fields.forEach((n,i)=>size[n]=PCDheader.size[i]) + + + for ( var i = 0; i < PCDheader.points; i ++ ) { + + if ( offset.x !== undefined ) { + + if (size.x==8) + { + position.push( dataview.getFloat64( ( PCDheader.points * offset.x ) + size.x * i, this.littleEndian ) ); + position.push( dataview.getFloat64( ( PCDheader.points * offset.y ) + size.y * i, this.littleEndian ) ); + position.push( dataview.getFloat64( ( PCDheader.points * offset.z ) + size.z * i, this.littleEndian ) ); + } + else + { + position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + size.x * i, this.littleEndian ) ); + position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + size.y * i, this.littleEndian ) ); + position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + size.z * i, this.littleEndian ) ); + } + + } + + if ( offset.vx !== undefined ) { + + if (size.vx==8) + { + velocity.push( dataview.getFloat64( ( PCDheader.points * offset.vx ) + size.vx * i, this.littleEndian ) ); + velocity.push( dataview.getFloat64( ( PCDheader.points * offset.vy ) + size.vy * i, this.littleEndian ) ); + velocity.push( 0 ); + } + else + { + velocity.push( dataview.getFloat32( ( PCDheader.points * offset.vx ) + size.vx * i, this.littleEndian ) ); + velocity.push( dataview.getFloat32( ( PCDheader.points * offset.vy ) + size.vy * i, this.littleEndian ) ); + velocity.push( 0 ); + } + + } + + // if ( offset.rgb !== undefined ) { + + // color.push( dataview.getUint8( ( PCDheader.points * ( offset.rgb + 2 ) ) + PCDheader.size[ 3 ] * i ) / 255.0 ); + // color.push( dataview.getUint8( ( PCDheader.points * ( offset.rgb + 1 ) ) + PCDheader.size[ 3 ] * i ) / 255.0 ); + // color.push( dataview.getUint8( ( PCDheader.points * ( offset.rgb + 0 ) ) + PCDheader.size[ 3 ] * i ) / 255.0 ); + + // } + + // if ( offset.normal_x !== undefined ) { + + // normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ 4 ] * i, this.littleEndian ) ); + // normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ 5 ] * i, this.littleEndian ) ); + // normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ 6 ] * i, this.littleEndian ) ); + + // } + + if (offset.intensity !== undefined) { + if (intensity_type == "U" && intensity_size == 1){ + intensity.push( dataview.getUint8(PCDheader.points * offset.intensity + size.intensity*i)); + } + else if (intensity_type == "F" && intensity_size == 4){ + intensity.push( dataview.getFloat32(PCDheader.points * offset.intensity + size.intensity*i, this.littleEndian)); + } + } + } + + } + else if ( PCDheader.data === 'binary' ) { + + var dataview = new DataView( data, PCDheader.headerLen ); + var offset = PCDheader.offset; + + var intensity_index = PCDheader.fields.findIndex(n=>n==="intensity"); + var intensity_type = "F"; + var intensity_size = 4; + + if (intensity_index >= 0){ + intensity_type = PCDheader.type[intensity_index]; + intensity_size = PCDheader.size[intensity_index]; + } + + let x_index = PCDheader.fields.findIndex(n=>n==="x"); + let x_size = 4; + let x_type = 'F'; + if (x_index >= 0){ + x_type = PCDheader.type[x_index]; + x_size = PCDheader.size[x_index]; + } + + + for ( var i = 0, row = 0; i < PCDheader.points; i ++, row += PCDheader.rowSize ) { + + if ( offset.x !== undefined ) { + + let getFloat = (x_size==8)? dataview.getFloat64.bind(dataview) : dataview.getFloat32.bind(dataview); + + let x = getFloat( row + offset.x, this.littleEndian ); + let y = getFloat( row + offset.y, this.littleEndian ); + let z = getFloat( row + offset.z, this.littleEndian ); + + if (filterPoint(x,y,z)){ + continue; + } + + position.push( x ); + position.push( y ); + position.push( z ); + + } + + if ( offset.rgb !== undefined ) { + + color.push( dataview.getUint8( row + offset.rgb + 2 ) / 255.0 ); + color.push( dataview.getUint8( row + offset.rgb + 1 ) / 255.0 ); + color.push( dataview.getUint8( row + offset.rgb + 0 ) / 255.0 ); + + } + + if ( offset.normal_x !== undefined ) { + + normal.push( dataview.getFloat32( row + offset.normal_x, this.littleEndian ) ); + normal.push( dataview.getFloat32( row + offset.normal_y, this.littleEndian ) ); + normal.push( dataview.getFloat32( row + offset.normal_z, this.littleEndian ) ); + + } + + if ( offset.vx !== undefined ) { + velocity.push( dataview.getFloat32( row + offset.vx, this.littleEndian ) ); + velocity.push( dataview.getFloat32( row + offset.vy, this.littleEndian ) ); + velocity.push( 0 ); + } + + if (offset.intensity !== undefined) { + if (intensity_type == "U" && intensity_size == 1){ + intensity.push( dataview.getUint8(row + offset.intensity)); + } + else if (intensity_type == "F" && intensity_size == 4){ + intensity.push( dataview.getFloat32(row + offset.intensity, this.littleEndian)); + } + } + } + + } + + return { + position: position, + color: color, + normal: normal, + velocity: velocity, + intensity: intensity, + }; + + } + +}; + +export { PCDLoader }; diff --git a/public/js/lib/TransformControls.js b/public/js/lib/TransformControls.js new file mode 100644 index 0000000..dc95281 --- /dev/null +++ b/public/js/lib/TransformControls.js @@ -0,0 +1,1533 @@ +import { + BoxGeometry, + BufferGeometry, + CylinderGeometry, + DoubleSide, + Euler, + Float32BufferAttribute, + Line, + LineBasicMaterial, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + OctahedronGeometry, + PlaneGeometry, + Quaternion, + Raycaster, + SphereGeometry, + TorusGeometry, + Vector3 +} from './three.module.js'; + +const _raycaster = new Raycaster(); + +const _tempVector = new Vector3(); +const _tempVector2 = new Vector3(); +const _tempQuaternion = new Quaternion(); +const _unit = { + X: new Vector3( 1, 0, 0 ), + Y: new Vector3( 0, 1, 0 ), + Z: new Vector3( 0, 0, 1 ) +}; + +const _changeEvent = { type: 'change' }; +const _mouseDownEvent = { type: 'mouseDown' }; +const _mouseUpEvent = { type: 'mouseUp', mode: null }; +const _objectChangeEvent = { type: 'objectChange' }; + +class TransformControls extends Object3D { + + constructor( camera, domElement ) { + + super(); + + if ( domElement === undefined ) { + + console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' ); + domElement = document; + + } + + this.visible = false; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + const _gizmo = new TransformControlsGizmo(); + this._gizmo = _gizmo; + this.add( _gizmo ); + + const _plane = new TransformControlsPlane(); + this._plane = _plane; + this.add( _plane ); + + const scope = this; + + // Defined getter, setter and store for a property + function defineProperty( propName, defaultValue ) { + + let propValue = defaultValue; + + Object.defineProperty( scope, propName, { + + get: function () { + + return propValue !== undefined ? propValue : defaultValue; + + }, + + set: function ( value ) { + + if ( propValue !== value ) { + + propValue = value; + _plane[ propName ] = value; + _gizmo[ propName ] = value; + + scope.dispatchEvent( { type: propName + '-changed', value: value } ); + scope.dispatchEvent( _changeEvent ); + + } + + } + + } ); + + scope[ propName ] = defaultValue; + _plane[ propName ] = defaultValue; + _gizmo[ propName ] = defaultValue; + + } + + // Define properties with getters/setter + // Setting the defined property will automatically trigger change event + // Defined properties are passed down to gizmo and plane + + defineProperty( 'camera', camera ); + defineProperty( 'object', undefined ); + defineProperty( 'enabled', true ); + defineProperty( 'axis', null ); + defineProperty( 'mode', 'translate' ); + defineProperty( 'translationSnap', null ); + defineProperty( 'rotationSnap', null ); + defineProperty( 'scaleSnap', null ); + defineProperty( 'space', 'world' ); + defineProperty( 'size', 1 ); + defineProperty( 'dragging', false ); + defineProperty( 'showX', true ); + defineProperty( 'showY', true ); + defineProperty( 'showZ', true ); + + // Reusable utility variables + + const worldPosition = new Vector3(); + const worldPositionStart = new Vector3(); + const worldQuaternion = new Quaternion(); + const worldQuaternionStart = new Quaternion(); + const cameraPosition = new Vector3(); + const cameraQuaternion = new Quaternion(); + const pointStart = new Vector3(); + const pointEnd = new Vector3(); + const rotationAxis = new Vector3(); + const rotationAngle = 0; + const eye = new Vector3(); + + // TODO: remove properties unused in plane and gizmo + + defineProperty( 'worldPosition', worldPosition ); + defineProperty( 'worldPositionStart', worldPositionStart ); + defineProperty( 'worldQuaternion', worldQuaternion ); + defineProperty( 'worldQuaternionStart', worldQuaternionStart ); + defineProperty( 'cameraPosition', cameraPosition ); + defineProperty( 'cameraQuaternion', cameraQuaternion ); + defineProperty( 'pointStart', pointStart ); + defineProperty( 'pointEnd', pointEnd ); + defineProperty( 'rotationAxis', rotationAxis ); + defineProperty( 'rotationAngle', rotationAngle ); + defineProperty( 'eye', eye ); + + this._offset = new Vector3(); + this._startNorm = new Vector3(); + this._endNorm = new Vector3(); + this._cameraScale = new Vector3(); + + this._parentPosition = new Vector3(); + this._parentQuaternion = new Quaternion(); + this._parentQuaternionInv = new Quaternion(); + this._parentScale = new Vector3(); + + this._worldScaleStart = new Vector3(); + this._worldQuaternionInv = new Quaternion(); + this._worldScale = new Vector3(); + + this._positionStart = new Vector3(); + this._quaternionStart = new Quaternion(); + this._scaleStart = new Vector3(); + + this._getPointer = getPointer.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerHover = onPointerHover.bind( this ); + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointermove', this._onPointerHover ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); + + } + + // updateMatrixWorld updates key transformation variables + updateMatrixWorld() { + + if ( this.object !== undefined ) { + + this.object.updateMatrixWorld(); + + if ( this.object.parent === null ) { + + console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' ); + + } else { + + this.object.parent.matrixWorld.decompose( this._parentPosition, this._parentQuaternion, this._parentScale ); + + } + + this.object.matrixWorld.decompose( this.worldPosition, this.worldQuaternion, this._worldScale ); + + this._parentQuaternionInv.copy( this._parentQuaternion ).invert(); + this._worldQuaternionInv.copy( this.worldQuaternion ).invert(); + + } + + this.camera.updateMatrixWorld(); + this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale ); + + this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize(); + + super.updateMatrixWorld( this ); + + } + + pointerHover( pointer ) { + + if ( this.object === undefined || this.dragging === true ) return; + + _raycaster.setFromCamera( pointer, this.camera ); + + const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster ); + + if ( intersect ) { + + this.axis = intersect.object.name; + + } else { + + this.axis = null; + + } + + } + + pointerDown( pointer ) { + + if ( this.object === undefined || this.dragging === true || pointer.button !== 0 ) return; + + if ( this.axis !== null ) { + + _raycaster.setFromCamera( pointer, this.camera ); + + const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); + + if ( planeIntersect ) { + + this.object.updateMatrixWorld(); + this.object.parent.updateMatrixWorld(); + + this._positionStart.copy( this.object.position ); + this._quaternionStart.copy( this.object.quaternion ); + this._scaleStart.copy( this.object.scale ); + + this.object.matrixWorld.decompose( this.worldPositionStart, this.worldQuaternionStart, this._worldScaleStart ); + + this.pointStart.copy( planeIntersect.point ).sub( this.worldPositionStart ); + + } + + this.dragging = true; + _mouseDownEvent.mode = this.mode; + this.dispatchEvent( _mouseDownEvent ); + + } + + } + + pointerMove( pointer ) { + + const axis = this.axis; + const mode = this.mode; + const object = this.object; + let space = this.space; + + if ( mode === 'scale' ) { + + space = 'local'; + + } else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) { + + space = 'world'; + + } + + if ( object === undefined || axis === null || this.dragging === false || pointer.button !== - 1 ) return; + + _raycaster.setFromCamera( pointer, this.camera ); + + const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); + + if ( ! planeIntersect ) return; + + this.pointEnd.copy( planeIntersect.point ).sub( this.worldPositionStart ); + + if ( mode === 'translate' ) { + + // Apply translate + + this._offset.copy( this.pointEnd ).sub( this.pointStart ); + + if ( space === 'local' && axis !== 'XYZ' ) { + + this._offset.applyQuaternion( this._worldQuaternionInv ); + + } + + if ( axis.indexOf( 'X' ) === - 1 ) this._offset.x = 0; + if ( axis.indexOf( 'Y' ) === - 1 ) this._offset.y = 0; + if ( axis.indexOf( 'Z' ) === - 1 ) this._offset.z = 0; + + if ( space === 'local' && axis !== 'XYZ' ) { + + this._offset.applyQuaternion( this._quaternionStart ).divide( this._parentScale ); + + } else { + + this._offset.applyQuaternion( this._parentQuaternionInv ).divide( this._parentScale ); + + } + + object.position.copy( this._offset ).add( this._positionStart ); + + // Apply translation snap + + if ( this.translationSnap ) { + + if ( space === 'local' ) { + + object.position.applyQuaternion( _tempQuaternion.copy( this._quaternionStart ).invert() ); + + if ( axis.search( 'X' ) !== - 1 ) { + + object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; + + } + + object.position.applyQuaternion( this._quaternionStart ); + + } + + if ( space === 'world' ) { + + if ( object.parent ) { + + object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); + + } + + if ( axis.search( 'X' ) !== - 1 ) { + + object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; + + } + + if ( object.parent ) { + + object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); + + } + + } + + } + + } else if ( mode === 'scale' ) { + + if ( axis.search( 'XYZ' ) !== - 1 ) { + + let d = this.pointEnd.length() / this.pointStart.length(); + + if ( this.pointEnd.dot( this.pointStart ) < 0 ) d *= - 1; + + _tempVector2.set( d, d, d ); + + } else { + + _tempVector.copy( this.pointStart ); + _tempVector2.copy( this.pointEnd ); + + _tempVector.applyQuaternion( this._worldQuaternionInv ); + _tempVector2.applyQuaternion( this._worldQuaternionInv ); + + _tempVector2.divide( _tempVector ); + + if ( axis.search( 'X' ) === - 1 ) { + + _tempVector2.x = 1; + + } + + if ( axis.search( 'Y' ) === - 1 ) { + + _tempVector2.y = 1; + + } + + if ( axis.search( 'Z' ) === - 1 ) { + + _tempVector2.z = 1; + + } + + } + + // Apply scale + + object.scale.copy( this._scaleStart ).multiply( _tempVector2 ); + + if ( this.scaleSnap ) { + + if ( axis.search( 'X' ) !== - 1 ) { + + object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + } + + } else if ( mode === 'rotate' ) { + + this._offset.copy( this.pointEnd ).sub( this.pointStart ); + + const ROTATION_SPEED = 20 / this.worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) ); + + if ( axis === 'E' ) { + + this.rotationAxis.copy( this.eye ); + this.rotationAngle = this.pointEnd.angleTo( this.pointStart ); + + this._startNorm.copy( this.pointStart ).normalize(); + this._endNorm.copy( this.pointEnd ).normalize(); + + this.rotationAngle *= ( this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1 ); + + } else if ( axis === 'XYZE' ) { + + this.rotationAxis.copy( this._offset ).cross( this.eye ).normalize(); + this.rotationAngle = this._offset.dot( _tempVector.copy( this.rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED; + + } else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) { + + this.rotationAxis.copy( _unit[ axis ] ); + + _tempVector.copy( _unit[ axis ] ); + + if ( space === 'local' ) { + + _tempVector.applyQuaternion( this.worldQuaternion ); + + } + + this.rotationAngle = this._offset.dot( _tempVector.cross( this.eye ).normalize() ) * ROTATION_SPEED; + + } + + // Apply rotation snap + + if ( this.rotationSnap ) this.rotationAngle = Math.round( this.rotationAngle / this.rotationSnap ) * this.rotationSnap; + + // Apply rotate + if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) { + + object.quaternion.copy( this._quaternionStart ); + object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ).normalize(); + + } else { + + this.rotationAxis.applyQuaternion( this._parentQuaternionInv ); + object.quaternion.copy( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ); + object.quaternion.multiply( this._quaternionStart ).normalize(); + + } + + } + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _objectChangeEvent ); + + } + + pointerUp( pointer ) { + + if ( pointer.button !== 0 ) return; + + if ( this.dragging && ( this.axis !== null ) ) { + + _mouseUpEvent.mode = this.mode; + this.dispatchEvent( _mouseUpEvent ); + + } + + this.dragging = false; + this.axis = null; + + } + + dispose() { + + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointermove', this._onPointerHover ); + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + + this.traverse( function ( child ) { + + if ( child.geometry ) child.geometry.dispose(); + if ( child.material ) child.material.dispose(); + + } ); + + } + + // Set current object + attach( object ) { + + this.object = object; + this.visible = true; + + return this; + + } + + // Detatch from object + detach() { + + this.object = undefined; + this.visible = false; + this.axis = null; + + return this; + + } + + getRaycaster() { + + return _raycaster; + + } + + // TODO: deprecate + + getMode() { + + return this.mode; + + } + + setMode( mode ) { + + this.mode = mode; + + } + + setTranslationSnap( translationSnap ) { + + this.translationSnap = translationSnap; + + } + + setRotationSnap( rotationSnap ) { + + this.rotationSnap = rotationSnap; + + } + + setScaleSnap( scaleSnap ) { + + this.scaleSnap = scaleSnap; + + } + + setSize( size ) { + + this.size = size; + + } + + setSpace( space ) { + + this.space = space; + + } + + update() { + + console.warn( 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.' ); + + } + +} + +TransformControls.prototype.isTransformControls = true; + +// mouse / touch event handlers + +function getPointer( event ) { + + if ( this.domElement.ownerDocument.pointerLockElement ) { + + return { + x: 0, + y: 0, + button: event.button + }; + + } else { + + const rect = this.domElement.getBoundingClientRect(); + + return { + x: ( event.clientX - rect.left ) / rect.width * 2 - 1, + y: - ( event.clientY - rect.top ) / rect.height * 2 + 1, + button: event.button + }; + + } + +} + +function onPointerHover( event ) { + + if ( ! this.enabled ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + this.pointerHover( this._getPointer( event ) ); + break; + + } + +} + +function onPointerDown( event ) { + + if ( ! this.enabled ) return; + + this.domElement.setPointerCapture( event.pointerId ); + + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + + this.pointerHover( this._getPointer( event ) ); + this.pointerDown( this._getPointer( event ) ); + +} + +function onPointerMove( event ) { + + if ( ! this.enabled ) return; + + this.pointerMove( this._getPointer( event ) ); + +} + +function onPointerUp( event ) { + + if ( ! this.enabled ) return; + + this.domElement.releasePointerCapture( event.pointerId ); + + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + + this.pointerUp( this._getPointer( event ) ); + +} + +function intersectObjectWithRay( object, raycaster, includeInvisible ) { + + const allIntersections = raycaster.intersectObject( object, true ); + + for ( let i = 0; i < allIntersections.length; i ++ ) { + + if ( allIntersections[ i ].object.visible || includeInvisible ) { + + return allIntersections[ i ]; + + } + + } + + return false; + +} + +// + +// Reusable utility variables + +const _tempEuler = new Euler(); +const _alignVector = new Vector3( 0, 1, 0 ); +const _zeroVector = new Vector3( 0, 0, 0 ); +const _lookAtMatrix = new Matrix4(); +const _tempQuaternion2 = new Quaternion(); +const _identityQuaternion = new Quaternion(); +const _dirVector = new Vector3(); +const _tempMatrix = new Matrix4(); + +const _unitX = new Vector3( 1, 0, 0 ); +const _unitY = new Vector3( 0, 1, 0 ); +const _unitZ = new Vector3( 0, 0, 1 ); + +const _v1 = new Vector3(); +const _v2 = new Vector3(); +const _v3 = new Vector3(); + +class TransformControlsGizmo extends Object3D { + + constructor() { + + super(); + + this.type = 'TransformControlsGizmo'; + + // shared materials + + const gizmoMaterial = new MeshBasicMaterial( { + depthTest: false, + depthWrite: false, + fog: false, + toneMapped: false, + transparent: true + } ); + + const gizmoLineMaterial = new LineBasicMaterial( { + depthTest: false, + depthWrite: false, + fog: false, + toneMapped: false, + transparent: true + } ); + + // Make unique material for each axis/color + + const matInvisible = gizmoMaterial.clone(); + matInvisible.opacity = 0.15; + + const matHelper = gizmoLineMaterial.clone(); + matHelper.opacity = 0.5; + + const matRed = gizmoMaterial.clone(); + matRed.color.setHex( 0xff0000 ); + + const matGreen = gizmoMaterial.clone(); + matGreen.color.setHex( 0x00ff00 ); + + const matBlue = gizmoMaterial.clone(); + matBlue.color.setHex( 0x0000ff ); + + const matRedTransparent = gizmoMaterial.clone(); + matRedTransparent.color.setHex( 0xff0000 ); + matRedTransparent.opacity = 0.5; + + const matGreenTransparent = gizmoMaterial.clone(); + matGreenTransparent.color.setHex( 0x00ff00 ); + matGreenTransparent.opacity = 0.5; + + const matBlueTransparent = gizmoMaterial.clone(); + matBlueTransparent.color.setHex( 0x0000ff ); + matBlueTransparent.opacity = 0.5; + + const matWhiteTransparent = gizmoMaterial.clone(); + matWhiteTransparent.opacity = 0.25; + + const matYellowTransparent = gizmoMaterial.clone(); + matYellowTransparent.color.setHex( 0xffff00 ); + matYellowTransparent.opacity = 0.25; + + const matYellow = gizmoMaterial.clone(); + matYellow.color.setHex( 0xffff00 ); + + const matGray = gizmoMaterial.clone(); + matGray.color.setHex( 0x787878 ); + + // reusable geometry + + const arrowGeometry = new CylinderGeometry( 0, 0.04, 0.1, 12 ); + arrowGeometry.translate( 0, 0.05, 0 ); + + const scaleHandleGeometry = new BoxGeometry( 0.08, 0.08, 0.08 ); + scaleHandleGeometry.translate( 0, 0.04, 0 ); + + const lineGeometry = new BufferGeometry(); + lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) ); + + const lineGeometry2 = new CylinderGeometry( 0.0075, 0.0075, 0.5, 3 ); + lineGeometry2.translate( 0, 0.25, 0 ); + + function CircleGeometry( radius, arc ) { + + const geometry = new TorusGeometry( radius, 0.0075, 3, 64, arc * Math.PI * 2 ); + geometry.rotateY( Math.PI / 2 ); + geometry.rotateX( Math.PI / 2 ); + return geometry; + + } + + // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position + + function TranslateHelperGeometry() { + + const geometry = new BufferGeometry(); + + geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) ); + + return geometry; + + } + + // Gizmo definitions - custom hierarchy definitions for setupGizmo() function + + const gizmoTranslate = { + X: [ + [ new Mesh( arrowGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( arrowGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]], + [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]] + ], + Y: [ + [ new Mesh( arrowGeometry, matGreen ), [ 0, 0.5, 0 ]], + [ new Mesh( arrowGeometry, matGreen ), [ 0, - 0.5, 0 ], [ Math.PI, 0, 0 ]], + [ new Mesh( lineGeometry2, matGreen ) ] + ], + Z: [ + [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]], + [ new Mesh( lineGeometry2, matBlue ), null, [ Math.PI / 2, 0, 0 ]] + ], + XYZ: [ + [ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ]] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent.clone() ), [ 0.15, 0.15, 0 ]] + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + ] + }; + + const pickerTranslate = { + X: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]] + ], + Y: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]] + ], + Z: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]] + ], + XYZ: [ + [ new Mesh( new OctahedronGeometry( 0.2, 0 ), matInvisible ) ] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]] + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + ] + }; + + const helperTranslate = { + START: [ + [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] + ], + END: [ + [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] + ], + DELTA: [ + [ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ] + ], + X: [ + [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + ], + Y: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] + ], + Z: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] + ] + }; + + const gizmoRotate = { + XYZE: [ + [ new Mesh( CircleGeometry( 0.5, 1 ), matGray ), null, [ 0, Math.PI / 2, 0 ]] + ], + X: [ + [ new Mesh( CircleGeometry( 0.5, 0.5 ), matRed ) ] + ], + Y: [ + [ new Mesh( CircleGeometry( 0.5, 0.5 ), matGreen ), null, [ 0, 0, - Math.PI / 2 ]] + ], + Z: [ + [ new Mesh( CircleGeometry( 0.5, 0.5 ), matBlue ), null, [ 0, Math.PI / 2, 0 ]] + ], + E: [ + [ new Mesh( CircleGeometry( 0.75, 1 ), matYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]] + ] + }; + + const helperRotate = { + AXIS: [ + [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + ] + }; + + const pickerRotate = { + XYZE: [ + [ new Mesh( new SphereGeometry( 0.25, 10, 8 ), matInvisible ) ] + ], + X: [ + [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]], + ], + Y: [ + [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]], + ], + Z: [ + [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + ], + E: [ + [ new Mesh( new TorusGeometry( 0.75, 0.1, 2, 24 ), matInvisible ) ] + ] + }; + + const gizmoScale = { + X: [ + [ new Mesh( scaleHandleGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( scaleHandleGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]], + ], + Y: [ + [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.5, 0 ]], + [ new Mesh( lineGeometry2, matGreen ) ], + [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, - 0.5, 0 ], [ 0, 0, Math.PI ]], + ], + Z: [ + [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( lineGeometry2, matBlue ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]] + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + ], + XYZ: [ + [ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent.clone() ) ], + ] + }; + + const pickerScale = { + X: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]] + ], + Y: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]] + ], + Z: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]], + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]], + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]], + ], + XYZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 0 ]], + ] + }; + + const helperScale = { + X: [ + [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + ], + Y: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] + ], + Z: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] + ] + }; + + // Creates an Object3D with gizmos described in custom hierarchy definition. + + function setupGizmo( gizmoMap ) { + + const gizmo = new Object3D(); + + for ( const name in gizmoMap ) { + + for ( let i = gizmoMap[ name ].length; i --; ) { + + const object = gizmoMap[ name ][ i ][ 0 ].clone(); + const position = gizmoMap[ name ][ i ][ 1 ]; + const rotation = gizmoMap[ name ][ i ][ 2 ]; + const scale = gizmoMap[ name ][ i ][ 3 ]; + const tag = gizmoMap[ name ][ i ][ 4 ]; + + // name and tag properties are essential for picking and updating logic. + object.name = name; + object.tag = tag; + + if ( position ) { + + object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] ); + + } + + if ( rotation ) { + + object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] ); + + } + + if ( scale ) { + + object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] ); + + } + + object.updateMatrix(); + + const tempGeometry = object.geometry.clone(); + tempGeometry.applyMatrix4( object.matrix ); + object.geometry = tempGeometry; + object.renderOrder = Infinity; + + object.position.set( 0, 0, 0 ); + object.rotation.set( 0, 0, 0 ); + object.scale.set( 1, 1, 1 ); + + gizmo.add( object ); + + } + + } + + return gizmo; + + } + + // Gizmo creation + + this.gizmo = {}; + this.picker = {}; + this.helper = {}; + + this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) ); + this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) ); + this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) ); + this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) ); + this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) ); + this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) ); + this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) ); + this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) ); + this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) ); + + // Pickers should be hidden always + + this.picker[ 'translate' ].visible = false; + this.picker[ 'rotate' ].visible = false; + this.picker[ 'scale' ].visible = false; + + } + + // updateMatrixWorld will update transformations and appearance of individual handles + + updateMatrixWorld( force ) { + + const space = ( this.mode === 'scale' ) ? 'local' : this.space; // scale always oriented to local rotation + + const quaternion = ( space === 'local' ) ? this.worldQuaternion : _identityQuaternion; + + // Show only gizmos for current transform mode + + this.gizmo[ 'translate' ].visible = this.mode === 'translate'; + this.gizmo[ 'rotate' ].visible = this.mode === 'rotate'; + this.gizmo[ 'scale' ].visible = this.mode === 'scale'; + + this.helper[ 'translate' ].visible = this.mode === 'translate'; + this.helper[ 'rotate' ].visible = this.mode === 'rotate'; + this.helper[ 'scale' ].visible = this.mode === 'scale'; + + + let handles = []; + handles = handles.concat( this.picker[ this.mode ].children ); + handles = handles.concat( this.gizmo[ this.mode ].children ); + handles = handles.concat( this.helper[ this.mode ].children ); + + for ( let i = 0; i < handles.length; i ++ ) { + + const handle = handles[ i ]; + + // hide aligned to camera + + handle.visible = true; + handle.rotation.set( 0, 0, 0 ); + handle.position.copy( this.worldPosition ); + + let factor; + + if ( this.camera.isOrthographicCamera ) { + + factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom; + + } else { + + factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 ); + + } + + handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 4 ); + + // TODO: simplify helpers and consider decoupling from gizmo + + if ( handle.tag === 'helper' ) { + + handle.visible = false; + + if ( handle.name === 'AXIS' ) { + + handle.position.copy( this.worldPositionStart ); + handle.visible = !! this.axis; + + if ( this.axis === 'X' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, 0 ) ); + handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { + + handle.visible = false; + + } + + } + + if ( this.axis === 'Y' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, Math.PI / 2 ) ); + handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { + + handle.visible = false; + + } + + } + + if ( this.axis === 'Z' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) ); + handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { + + handle.visible = false; + + } + + } + + if ( this.axis === 'XYZE' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) ); + _alignVector.copy( this.rotationAxis ); + handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( _zeroVector, _alignVector, _unitY ) ); + handle.quaternion.multiply( _tempQuaternion ); + handle.visible = this.dragging; + + } + + if ( this.axis === 'E' ) { + + handle.visible = false; + + } + + + } else if ( handle.name === 'START' ) { + + handle.position.copy( this.worldPositionStart ); + handle.visible = this.dragging; + + } else if ( handle.name === 'END' ) { + + handle.position.copy( this.worldPosition ); + handle.visible = this.dragging; + + } else if ( handle.name === 'DELTA' ) { + + handle.position.copy( this.worldPositionStart ); + handle.quaternion.copy( this.worldQuaternionStart ); + _tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 ); + _tempVector.applyQuaternion( this.worldQuaternionStart.clone().invert() ); + handle.scale.copy( _tempVector ); + handle.visible = this.dragging; + + } else { + + handle.quaternion.copy( quaternion ); + + if ( this.dragging ) { + + handle.position.copy( this.worldPositionStart ); + + } else { + + handle.position.copy( this.worldPosition ); + + } + + if ( this.axis ) { + + handle.visible = this.axis.search( handle.name ) !== - 1; + + } + + } + + // If updating helper, skip rest of the loop + continue; + + } + + // Align handles to current local or world rotation + + handle.quaternion.copy( quaternion ); + + if ( this.mode === 'translate' || this.mode === 'scale' ) { + + // Hide translate and scale axis facing the camera + + const AXIS_HIDE_TRESHOLD = 0.99; + const PLANE_HIDE_TRESHOLD = 0.2; + + if ( handle.name === 'X' ) { + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'Y' ) { + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'Z' ) { + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'XY' ) { + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'YZ' ) { + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'XZ' ) { + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + } else if ( this.mode === 'rotate' ) { + + // Align handles to current local or world rotation + + _tempQuaternion2.copy( quaternion ); + _alignVector.copy( this.eye ).applyQuaternion( _tempQuaternion.copy( quaternion ).invert() ); + + if ( handle.name.search( 'E' ) !== - 1 ) { + + handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( this.eye, _zeroVector, _unitY ) ); + + } + + if ( handle.name === 'X' ) { + + _tempQuaternion.setFromAxisAngle( _unitX, Math.atan2( - _alignVector.y, _alignVector.z ) ); + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + handle.quaternion.copy( _tempQuaternion ); + + } + + if ( handle.name === 'Y' ) { + + _tempQuaternion.setFromAxisAngle( _unitY, Math.atan2( _alignVector.x, _alignVector.z ) ); + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + handle.quaternion.copy( _tempQuaternion ); + + } + + if ( handle.name === 'Z' ) { + + _tempQuaternion.setFromAxisAngle( _unitZ, Math.atan2( _alignVector.y, _alignVector.x ) ); + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + handle.quaternion.copy( _tempQuaternion ); + + } + + } + + // Hide disabled axes + handle.visible = handle.visible && ( handle.name.indexOf( 'X' ) === - 1 || this.showX ); + handle.visible = handle.visible && ( handle.name.indexOf( 'Y' ) === - 1 || this.showY ); + handle.visible = handle.visible && ( handle.name.indexOf( 'Z' ) === - 1 || this.showZ ); + handle.visible = handle.visible && ( handle.name.indexOf( 'E' ) === - 1 || ( this.showX && this.showY && this.showZ ) ); + + // highlight selected axis + + handle.material._color = handle.material._color || handle.material.color.clone(); + handle.material._opacity = handle.material._opacity || handle.material.opacity; + + handle.material.color.copy( handle.material._color ); + handle.material.opacity = handle.material._opacity; + + if ( this.enabled && this.axis ) { + + if ( handle.name === this.axis ) { + + handle.material.color.setHex( 0xffff00 ); + handle.material.opacity = 1.0; + + } else if ( this.axis.split( '' ).some( function ( a ) { + + return handle.name === a; + + } ) ) { + + handle.material.color.setHex( 0xffff00 ); + handle.material.opacity = 1.0; + + } + + } + + } + + super.updateMatrixWorld( force ); + + } + +} + +TransformControlsGizmo.prototype.isTransformControlsGizmo = true; + +// + +class TransformControlsPlane extends Mesh { + + constructor() { + + super( + new PlaneGeometry( 100000, 100000, 2, 2 ), + new MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } ) + ); + + this.type = 'TransformControlsPlane'; + + } + + updateMatrixWorld( force ) { + + let space = this.space; + + this.position.copy( this.worldPosition ); + + if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation + + _v1.copy( _unitX ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + _v2.copy( _unitY ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + _v3.copy( _unitZ ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + + // Align the plane for current transform mode, axis and space. + + _alignVector.copy( _v2 ); + + switch ( this.mode ) { + + case 'translate': + case 'scale': + switch ( this.axis ) { + + case 'X': + _alignVector.copy( this.eye ).cross( _v1 ); + _dirVector.copy( _v1 ).cross( _alignVector ); + break; + case 'Y': + _alignVector.copy( this.eye ).cross( _v2 ); + _dirVector.copy( _v2 ).cross( _alignVector ); + break; + case 'Z': + _alignVector.copy( this.eye ).cross( _v3 ); + _dirVector.copy( _v3 ).cross( _alignVector ); + break; + case 'XY': + _dirVector.copy( _v3 ); + break; + case 'YZ': + _dirVector.copy( _v1 ); + break; + case 'XZ': + _alignVector.copy( _v3 ); + _dirVector.copy( _v2 ); + break; + case 'XYZ': + case 'E': + _dirVector.set( 0, 0, 0 ); + break; + + } + + break; + case 'rotate': + default: + // special case for rotate + _dirVector.set( 0, 0, 0 ); + + } + + if ( _dirVector.length() === 0 ) { + + // If in rotate mode, make the plane parallel to camera + this.quaternion.copy( this.cameraQuaternion ); + + } else { + + _tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector ); + + this.quaternion.setFromRotationMatrix( _tempMatrix ); + + } + + super.updateMatrixWorld( force ); + + } + +} + +TransformControlsPlane.prototype.isTransformControlsPlane = true; + +export { TransformControls, TransformControlsGizmo, TransformControlsPlane }; diff --git a/public/js/lib/ml/lalolib.js b/public/js/lib/ml/lalolib.js new file mode 100644 index 0000000..a392383 --- /dev/null +++ b/public/js/lib/ml/lalolib.js @@ -0,0 +1,16501 @@ +///////////////////////////////////// +/// Stand alone lalolib base functions +//////////////////////////////////// +var printPrecision = 3; // number of digits to print + +var LALOLibPlotsIndex = 0; +var LALOLibPlots = new Array(); +var LALOLABPLOTMOVING = false; + +////////////////////////// +//// Cross-browser compatibility +/////////////////////////// + +if( typeof(console) == "undefined" ) { + // for Safari + var console = {log: function ( ) { } }; +} + +if( typeof(Math.sign) == "undefined" ) { + // for IE, Safari + Math.sign = function ( x ) { return ( x>=0 ? (x==0 ? 0 : 1) : -1 ) ;} +} + +////////////////////////// +//// printing +/////////////////////////// + +function laloprint( x , htmlId, append ) { + /* + use print(x) to print to the standard LALOLabOutput + + use print(x, id) to print to another html entity + + use str = print(x, true) to get the resulting string + */ + + if ( typeof(htmlId) == "undefined" ) + var htmlId = "LALOLibOutput"; + if ( typeof(append) == "undefined" ) + var append = true; + + return printMat(x, size(x), htmlId, append ) ; +} + +function printMat(A, size, htmlId, append) { + if (typeof(append) === "undefined") + var append = false; + if ( typeof(htmlId) == "undefined" || htmlId === true ) { + // return a string as [ [ .. , .. ] , [.. , ..] ] + if ( type(A) == "matrix" ) { + var str = "["; + var i; + var j; + var m = size[0]; + var n = size[1]; + + for (i=0;i"; + } + console.log(str); + return str; + } + } + else { + // Produce HTML code and load it in htmlId + + var html = ""; + var i; + var j; + + /*if (domathjax) { + html = tex ( A ) ; + } + else {*/ + if ( isScalar(A) ) { + html += A + "
" ; + } + else if (type(A) == "vector" ) { + var n = size[0]; + + // Vector (one column) + for (i=0;i"; + } + } + else { + // Matrix + var m = size[0]; + var n = size[1]; + + for (i=0;i"; + } + } + //} + if (append) + document.getElementById(htmlId).innerHTML += html; + else + document.getElementById(htmlId).innerHTML = html; + /* + if ( domathjax) + MathJax.Hub.Queue(["Typeset",MathJax.Hub,"output"]); + */ + } +} + +function printNumber ( x ) { + switch ( typeof(x) ) { + case "undefined": + return "" + 0;// for sparse matrices + break; + case "string": + /*if ( domathjax ) + return "\\verb&" + x + "&"; + else*/ + return x; + break; + case "boolean": + return x; + break; + default: + if ( x == Infinity ) + return "Inf"; + if ( x == -Infinity ) + return "-Inf"; + var x_int = Math.floor(x); + if ( Math.abs( x - x_int ) < 2.23e-16 ) { + return "" + x_int; + } + else + return x.toFixed( printPrecision ); + + break; + } +} + +//// Error handling + +function error( msg ) { + throw new Error ( msg ) ; +// postMessage( {"error": msg} ); +} + + +/////////// +// Plots +////////// +function plot(multiargs) { + // plot(x,y,"style", x2,y2,"style",y3,"style",... ) + + // Part copied from lalolabworker.js + + var data = new Array(); + var styles = new Array(); + var legends = new Array(); + var minX = Infinity; + var maxX = -Infinity; + var minY = Infinity; + var maxY = -Infinity; + + var p=0; // argument pointer + var x; + var y; + var style; + var i; + var n; + var c = 0; // index of current curve + while ( p < arguments.length) { + + if ( type( arguments[p] ) == "vector" ) { + + if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "vector" ) { + // classic (x,y) arguments + x = arguments[p]; + y = arguments[p+1]; + + p++; + } + else { + // only y provided => x = 0:n + y = arguments[p]; + x = range(y.length); + } + } + else if ( type( arguments[p] ) == "matrix" ) { + // argument = [x, y] + if ( arguments[p].n == 1 ) { + y = arguments[p].val; + x = range(y.length); + } + else if (arguments[p].m == 1 ) { + y = arguments[p].val; + x = range(y.length); + } + else if ( arguments[p].n == 2 ) { + // 2 columns => [x,y] + x = getCols(arguments[p], [0]); + y = getCols(arguments[p], [1]); + } + else { + // more columns => trajectories as rows + x = range(arguments[p].n); + for ( var row = 0; row < arguments[p].m; row++) { + y = arguments[p].row(row); + data[c] = [new Array(x.length), new Array(x.length)]; + for ( i=0; i < x.length; i++) { + data[c][0][i] = x[i]; + data[c][1][i] = y[i]; + if ( x[i] < minX ) + minX = x[i]; + if(x[i] > maxX ) + maxX = x[i]; + if ( y[i] > maxY ) + maxY = y[i]; + if ( y[i] < minY ) + minY = y[i]; + + } + + styles[c] = undefined; + legends[c] = ""; + + // Next curve + c++; + } + p++; + continue; + } + } + else { + return "undefined"; + } + + //Style + style = undefined; + if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "string" ) { + style = arguments[p+1]; + p++; + } + legend = ""; + if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "string" ) { + legend = arguments[p+1]; + p++; + } + + // Add the curve (x,y, style) to plot + data[c] = [new Array(x.length), new Array(x.length)]; + for ( i=0; i < x.length; i++) { + data[c][0][i] = x[i]; + data[c][1][i] = y[i]; + if ( x[i] < minX ) + minX = x[i]; + if(x[i] > maxX ) + maxX = x[i]; + if ( y[i] > maxY ) + maxY = y[i]; + if ( y[i] < minY ) + minY = y[i]; + + } + styles[c] = style; + legends[c] = legend; + + // Next curve + c++; + p++; // from next argument + } + + var widthX = maxX-minX; + var widthY = Math.max( maxY-minY, 1); + + maxX += 0.1*widthX; + minX -= 0.1*widthX; + maxY += 0.1*widthY; + minY -= 0.1*widthY; + + if ( minY > 0 ) + minY = -0.1*maxY; + + if ( maxY < 0 ) + maxY = -0.1*minY; + + var scaleY = 0.9 * (maxX-minX) / (2*maxY); + + var plotinfo = {"data" : data, "minX" : minX, "maxX" : maxX, "minY" : minY, "maxY": maxY, "styles" : styles, "legend": legends }; + + //////// Part from laloplots.html ////////// + + var plotid = "LALOLibPlot" + LALOLibPlotsIndex; + var legendwidth = 50; + + LALOLibOutput.innerHTML += "
"; + + // prepare legend + var ylegend = 20; + + // do plot + + LALOLibPlots[LALOLibPlotsIndex] = new Plot(plotid) ; + + LALOLibPlots[LALOLibPlotsIndex].setScalePlot(plotinfo.minX, plotinfo.maxX, 200, plotinfo.scaleY); + if ( plotinfo.minY && plotinfo.maxY ) { + LALOLibPlots[LALOLibPlotsIndex].view(plotinfo.minX, plotinfo.maxX, plotinfo.minY, plotinfo.maxY); + } + + var colors = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,0]; + + var p; + var color; + for (p = 0; p= 0 ) { + linestyle = false; + plotinfo.styles[p] = plotinfo.styles[p].replace(".",""); + } + if ( plotinfo.styles[p].indexOf("_") >= 0 ) { + pointstyle = false; + plotinfo.styles[p] = plotinfo.styles[p].replace("_",""); + } + color = parseColor(plotinfo.styles[p]); + + if ( color < 0 ) + color = colors.splice(0,1)[0]; // pick next unused color + else + colors.splice(colors.indexOf(color),1); // remove this color + } + else + color = color = colors.splice(0,1)[0]; // pick next unused color + + if ( typeof(color) == "undefined") // pick black if no next unused color + color = 0; + + for ( i=0; i < plotinfo.data[p][0].length; i++) { + if ( pointstyle ) + LALOLibPlots[LALOLibPlotsIndex].addPoint(plotinfo.data[p][0][i],plotinfo.data[p][1][i], color); + if ( linestyle && i < plotinfo.data[p][0].length-1 ) + LALOLibPlots[LALOLibPlotsIndex].plot_line(plotinfo.data[p][0][i],plotinfo.data[p][1][i], plotinfo.data[p][0][i+1],plotinfo.data[p][1][i+1], color); + } + + + // Legend + if ( plotinfo.legend[p] != "" ) { + var ctx = document.getElementById("legend" +LALOLibPlotsIndex).getContext("2d"); + setcolor(ctx, color); + ctx.lineWidth = "3"; + if ( pointstyle ) { + ctx.beginPath(); + ctx.arc( legendwidth/2 , ylegend, 5, 0, 2 * Math.PI , true); + ctx.closePath(); + ctx.fill(); + } + if( linestyle) { + ctx.beginPath(); + ctx.moveTo ( 0,ylegend); + ctx.lineTo (legendwidth, ylegend); + ctx.stroke(); + } + ylegend += 20; + + document.getElementById("legendtxt" +LALOLibPlotsIndex).innerHTML += plotinfo.legend[p] + "
"; + } + } + for ( var pi=0; pi <= LALOLibPlotsIndex; pi++) + LALOLibPlots[pi].replot(); + + // ZOOM + if(window.addEventListener) + document.getElementById(plotid).addEventListener('DOMMouseScroll', this.mousezoom, false);//firefox + + //for IE/OPERA etc + document.getElementById(plotid).onmousewheel = this.mousezoom; + + LALOLibPlotsIndex++; +} + +// Color plot +function colorplot(multiargs) { + // colorplot(x,y,z) or colorplot(X) or colorplot(..., "cmapname" ) + + // Part copied from lalolabworker.js + + var minX = Infinity; + var maxX = -Infinity; + var minY = Infinity; + var maxY = -Infinity; + var minZ = Infinity; + var maxZ = -Infinity; + + var x; + var y; + var z; + var i; + + var t0 = type( arguments[0] ); + if ( t0 == "matrix" && arguments[0].n == 3 ) { + x = getCols(arguments[0], [0]); + y = getCols(arguments[0], [1]); + z = getCols(arguments[0], [2]); + } + else if ( t0 == "matrix" && arguments[0].n == 2 && type(arguments[1]) == "vector" ) { + x = getCols(arguments[0], [0]); + y = getCols(arguments[0], [1]); + z = arguments[1]; + } + else if (t0 == "vector" && type(arguments[1]) == "vector" && type(arguments[2]) == "vector") { + x = arguments[0]; + y = arguments[1]; + z = arguments[2]; + } + else { + return "undefined"; + } + + var minX = min(x); + var maxX = max(x); + var minY = min(y); + var maxY = max(y); + var minZ = min(z); + var maxZ = max(z); + + var widthX = maxX-minX; + var widthY = Math.max( maxY-minY, 1); + + maxX += 0.1*widthX; + minX -= 0.1*widthX; + maxY += 0.1*widthY; + minY -= 0.1*widthY; + + if ( minY > 0 ) + minY = -0.1*maxY; + + if ( maxY < 0 ) + maxY = -0.1*minY; + + var plotinfo = {"x" : x, "y": y, "z": z, "minX" : minX, "maxX" : maxX, "minY" : minY, "maxY": maxY, "minZ" : minZ, "maxZ" : maxZ }; + + //////// Part from laloplots.html ////////// + + var plotid = "LALOLibPlot" + LALOLibPlotsIndex; + var legendwidth = 50; + + + LALOLibOutput.innerHTML += "


"; + + LALOLibPlots[LALOLibPlotsIndex] = new ColorPlot(plotid) ; + LALOLibPlots[LALOLibPlotsIndex].setScale(plotinfo.minX, plotinfo.maxX, plotinfo.minY, plotinfo.maxY,plotinfo.minZ, plotinfo.maxZ); + LALOLibPlots[LALOLibPlotsIndex].view(plotinfo.minX, plotinfo.maxX, plotinfo.minY, plotinfo.maxY); + + for (var i=0; i < plotinfo.x.length; i++) + LALOLibPlots[LALOLibPlotsIndex].addPoint(plotinfo.x[i],plotinfo.y[i],plotinfo.z[i]); + + LALOLibPlots[LALOLibPlotsIndex].replot(); + + var legendwidth = 50; +// plotlegend.innerHTML += plotinfo.maxZ.toFixed(3) + "

" + plotinfo.minZ.toFixed(3); + var ctx = document.getElementById("legend" +LALOLibPlotsIndex).getContext("2d"); + + var legendcanvas = document.getElementById("legend"+LALOLibPlotsIndex); + if ( legendcanvas ) + var legendheight = legendcanvas.height; + else + var legendheight = 500; + + var y; + for (var i=0; i< LALOLibPlots[LALOLibPlotsIndex].cmap.length;i++) { + y = Math.floor(i * legendheight / LALOLibPlots[LALOLibPlotsIndex].cmap.length); + ctx.fillStyle = "rgb(" + LALOLibPlots[LALOLibPlotsIndex].cmap[i][0] + "," + LALOLibPlots[LALOLibPlotsIndex].cmap[i][1] + "," + LALOLibPlots[LALOLibPlotsIndex].cmap[i][2] + ")"; + ctx.fillRect( 0, legendheight-y, legendwidth , (legendheight / LALOLibPlots[LALOLibPlotsIndex].cmap.length) + 1) ; + } + + document.getElementById("legendmaxZ" + LALOLibPlotsIndex).innerHTML = plotinfo.maxZ.toPrecision(3); + document.getElementById("legendminZ" + LALOLibPlotsIndex).innerHTML = plotinfo.minZ.toPrecision(3); + + if(window.addEventListener) + document.getElementById(plotid).addEventListener('DOMMouseScroll', this.mousezoom, false);//firefox + + //for IE/OPERA etc + document.getElementById(plotid).onmousewheel = this.mousezoom; + + LALOLibPlotsIndex++; +} + +// 3D plot +function plot3(multiargs) { + // plot3(x,y,z,"style", x2,y2,z2,"style",... ) + + var data = new Array(); + var styles = new Array(); + var legends = new Array(); + + var p=0; // argument pointer + var x; + var y; + var z; + var style; + var i; + var n; + var c = 0; // index of current curve + while ( p < arguments.length) { + + if ( type( arguments[p] ) == "vector" ) { + + if ( p + 2 < arguments.length && type ( arguments[p+1] ) == "vector" && type ( arguments[p+2] ) == "vector" ) { + // classic (x,y,z) arguments + x = arguments[p]; + y = arguments[p+1]; + z = arguments[p+2]; + + p += 2; + } + else { + return "undefined"; + } + } + else if ( type( arguments[p] ) == "matrix" ) { + // argument = [x, y, z] + n = arguments[p].length; + x = new Array(n); + y = new Array(n); + z = new Array(n); + for ( i=0; i < n; i++) { + x[i] = get(arguments[p], i, 0); + y[i] = get(arguments[p], i, 1); + z[i] = get(arguments[p], i, 2); + } + } + else { + return "undefined"; + } + + //Style + style = undefined; + if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "string" ) { + style = arguments[p+1]; + p++; + } + legend = ""; + if ( p + 1 < arguments.length && type ( arguments[p+1] ) == "string" ) { + legend = arguments[p+1]; + p++; + } + + // Add the curve (x,y,z, style) to plot + data[c] = new Array(); + for ( i=0; i < x.length; i++) { + data[c][i] = [x[i], y[i], z[i]]; + } + styles[c] = style; + legends[c] = legend; + + // Next curve + c++; + p++; // from next argument + + } + + + var plotinfo = { "data" : data, "styles" : styles, "legend": legends }; + + //////// Part from laloplots.html ////////// + + var plotid = "LALOLibPlot" + LALOLibPlotsIndex; + var legendwidth = 50; + + LALOLibOutput.innerHTML += '
'; + + var ylegend = 20; + + // do plot + + LALOLibPlots[LALOLibPlotsIndex] = new Plot3D(plotid) ; + + LALOLibPlots[LALOLibPlotsIndex].cameraDistance = 30; + LALOLibPlots[LALOLibPlotsIndex].angleX = Math.PI/10; + LALOLibPlots[LALOLibPlotsIndex].angleZ = Math.PI/10; + + LALOLibPlots[LALOLibPlotsIndex].axisNameX1 = "x"; + LALOLibPlots[LALOLibPlotsIndex].axisNameX2 = "y"; + LALOLibPlots[LALOLibPlotsIndex].axisNameX3 = "z"; + + + var colors = [1,2,3,4,5,0]; + + var p; + var color; + + for (p = 0; p= 0 ) { + linestyle = false; + plotinfo.styles[p] = plotinfo.styles[p].replace(".",""); + } + if ( plotinfo.styles[p].indexOf("_") >= 0 ) { + pointstyle = false; + plotinfo.styles[p] = plotinfo.styles[p].replace("_",""); + } + color = parseColor(plotinfo.styles[p]); + + if ( color < 0 ) + color = colors.splice(0,1)[0]; // pick next unused color + else + colors.splice(colors.indexOf(color),1); // remove this color + } + else + color = color = colors.splice(0,1)[0]; // pick next unused color + + if ( typeof(color) == "undefined") // pick black if no next unused color + color = 0; + + for ( i=0; i < plotinfo.data[p].length; i++) { + if ( pointstyle ) { + LALOLibPlots[LALOLibPlotsIndex].X.push( plotinfo.data[p][i] ); + LALOLibPlots[LALOLibPlotsIndex].Y.push( color ); + } + if ( linestyle && i < plotinfo.data[p].length-1 ) + LALOLibPlots[LALOLibPlotsIndex].plot_line(plotinfo.data[p][i], plotinfo.data[p][i+1], "", color); + } + + // Legend + if ( plotinfo.legend[p] != "" ) { + var ctx = document.getElementById("legend" +LALOLibPlotsIndex).getContext("2d"); + setcolor(ctx, color); + ctx.lineWidth = "3"; + if ( pointstyle ) { + ctx.beginPath(); + ctx.arc( legendwidth/2 , ylegend, 5, 0, 2 * Math.PI , true); + ctx.closePath(); + ctx.fill(); + } + if( linestyle) { + ctx.beginPath(); + ctx.moveTo ( 0,ylegend); + ctx.lineTo (legendwidth, ylegend); + ctx.stroke(); + } + ylegend += 20; + + document.getElementById("legendtxt" +LALOLibPlotsIndex).innerHTML += plotinfo.legend[p] + "
"; + } + } + LALOLibPlots[LALOLibPlotsIndex].computeRanges(); + LALOLibPlots[LALOLibPlotsIndex].replot(); + + LALOLibPlotsIndex++; +} + +// image +function image(X, title) { + if (type(X) == "vector") { + X = mat([X]); + } + + var style; + var minX = min(X); + var maxX = max(X); + var m = X.length; + var n = X.n; + var scale = (maxX - minX) ; + + var i; + var j; + var k = 0; + var data = new Array(); + for ( i=0; i < m; i++) { + var Xi = X.row(i); + for ( j=0; j < n; j++) { // could do for j in X[i] if colormap for 0 is white... + color = mul( ( Xi[j] - minX) / scale, ones(3) ) ; + data[k] = [i/m, j/n, color]; + k++; + } + } + style = [m,n,minX,maxX]; + + var imagedata = { "data" : data, "style" : style, "title": title }; + + ////// Part from laloplots.html + + + var plotid = "LALOLibPlot" + LALOLibPlotsIndex; + var legendwidth = 50; + var pixWidth ; + var pixHeight ; + + // prepare legend + var ylegend = 20; + + // do plot + + + var i; + + var width = 500; + var height = 500; + + var title = imagedata.title; + if(title) { + LALOLibOutput.innerHTML += "

"+title+"

" + " ( " + imagedata.style[0] + " by " + imagedata.style[1] + " matrix )"; + } + + if ( imagedata.style[1] > width ) { + width = imagedata.style[1]; + plotlegend.style.left = (width+60) +"px"; + } + if ( imagedata.style[0] > height ) + height = imagedata.style[0]; + + pixWidth = width / imagedata.style[1]; + pixHeight = height / imagedata.style[0]; + + var legendwidth = 50; + + LALOLibOutput.innerHTML += '
' + imagedata.style[2].toFixed(3) + '

' + imagedata.style[3].toFixed(3) + '
'; + + var x; + var y; + var color; + + LALOLibPlots[LALOLibPlotsIndex] = imagedata; + LALOLibPlots[LALOLibPlotsIndex].canvasId = plotid; + var canvas = document.getElementById(plotid); + + if (canvas.getContext) { + var ctx = canvas.getContext("2d"); + + for ( i=0; i < imagedata.data.length ; i++) { + x = canvas.width * LALOLibPlots[LALOLibPlotsIndex].data[i][1]; + y = canvas.height * LALOLibPlots[LALOLibPlotsIndex].data[i][0] ; + color = LALOLibPlots[LALOLibPlotsIndex].data[i][2]; + + ctx.fillStyle = "rgb(" + Math.floor(255*(1-color[0])) + "," + Math.floor(255*(1-color[1])) + "," + Math.floor(255*(1-color[2])) + ")"; + ctx.fillRect( x , y, pixWidth +1, pixHeight +1); // +1 to avoid blank lines between pixels + + } + } + + // add legend / colormap + + var legend = document.getElementById("legend" +LALOLibPlotsIndex); + var ctx = legend.getContext("2d"); + + for ( i=0; i< 255;i++) { + y = Math.floor(i * legend.height / 255); + ctx.fillStyle = "rgb(" + (255-i) + "," + (255-i) + "," + (255-i) + ")"; + ctx.fillRect( 0, y, legendwidth , (legend.height / 255) + 1) ; + } + + // Prepare mouseposition info + LALOLibPlots[LALOLibPlotsIndex].pixelWidth = pixWidth; + LALOLibPlots[LALOLibPlotsIndex].pixelHeight = pixHeight; + LALOLibPlotsIndex++; +} + + + +function parseColor( str ) { + if ( typeof(str) == "undefined") + return -1; + + var color; + switch( str ) { + case "k": + case "black": + color = 0; + break; + case "blue": + case "b": + color = 1; + break; + case "r": + case "red": + color = 2; + break; + case "g": + case "green": + color = 3; + break; + case "m": + case "magenta": + color = 4; + break; + case "y": + case "yellow": + color = 5; + break; + + default: + color = -1; + break; + } + return color; +} + +function mousezoom ( e, delta , plotidx) { + if (!e) + e = window.event; + + e.preventDefault(); + + if ( typeof(plotidx) == "undefined") + var plotidx = 0; + + if ( typeof(delta) == "undefined") { + var delta = 0; + + // normalize the delta + if (e.wheelDelta) { + // IE and Opera + delta = e.wheelDelta / 30; + } + else if (e.detail) { + delta = -e.detail ; + } + } + else { + if (e.button != 0 ) + delta *= -1; + } + + var plotcanvas = document.getElementById(LALOLibPlots[plotidx].canvasId); + var rect = plotcanvas.getBoundingClientRect(); + var x = e.clientX - rect.left; // mouse coordinates relative to plot + var y = e.clientY - rect.top; + LALOLibPlots[plotidx].zoom(1+delta/30,1+delta/30, x, y); +} +function zoomoriginal(plotidx) { + LALOLibPlots[plotidx].resetzoom(); +} +function mouseposition( e , plotidx) { + var plotcanvas = document.getElementById(LALOLibPlots[plotidx].canvasId); + var rect = plotcanvas.getBoundingClientRect(); + + var xmouse = e.clientX - rect.left; // mouse coordinates relative to plot + var ymouse = e.clientY - rect.top; + + if ( LALOLABPLOTMOVING ) { + var dx = xmouse - LALOLABPLOTxprev ; + var dy = ymouse - LALOLABPLOTyprev; + if ( Math.abs( dx ) > 1 || Math.abs( dy ) > 1 ) { + LALOLibPlots[plotidx].translate(dx, dy); + } + LALOLABPLOTxprev = xmouse; + LALOLABPLOTyprev = ymouse; + } + else { + var x = xmouse / LALOLibPlots[plotidx].scaleX + LALOLibPlots[plotidx].minX; + var y = (plotcanvas.height - ymouse ) / LALOLibPlots[plotidx].scaleY + LALOLibPlots[plotidx].minY; + + document.getElementById("lblposition" + plotidx).innerHTML = "x = " + x.toFixed(3) + ", y = " + y.toFixed(3); + } +} + +function mousestartmove( e , plotidx) { + if ( e.button == 0 ) { + LALOLABPLOTMOVING = true; + var plotcanvas = document.getElementById(LALOLibPlots[plotidx].canvasId); + var rect = plotcanvas.getBoundingClientRect(); + LALOLABPLOTxprev = e.clientX - rect.left; // mouse coordinates relative to plot + LALOLABPLOTyprev = e.clientY - rect.top; + } + else { + LALOLABPLOTMOVING = false; + } +} +function mousestopmove( e ) { + LALOLABPLOTMOVING = false; +} + +function mouseimageposition( e, plotidx ) { + var plotcanvas = document.getElementById(LALOLibPlots[plotidx].canvasId); + var rect = plotcanvas.getBoundingClientRect(); + + var xmouse = e.clientX - rect.left; // mouse coordinates relative to plot + var ymouse = e.clientY - rect.top; + + var n = LALOLibPlots[plotidx].style[1]; + var minX = LALOLibPlots[plotidx].style[2]; + var maxX = LALOLibPlots[plotidx].style[3]; + var i = Math.floor(ymouse / LALOLibPlots[plotidx].pixelHeight); + var j = Math.floor(xmouse / LALOLibPlots[plotidx].pixelWidth ); + if ( j < n ) { + var val = LALOLibPlots[plotidx].data[i*n + j][2][0]*(maxX - minX) + minX; + + document.getElementById("lblposition" + plotidx).innerHTML = "Matrix[ " + i + " ][ " + j + " ] = " + val.toFixed(3); + } +} +///////////////////////////////// +//// Parser +//////////////////////////////// + +function lalo( Command ) { + // Parse command line and execute in current scopes + var cmd = laloparse( Command ); + var res = self.eval(cmd); + return res; +} +function laloparse( WorkerCommand ) { + // Parse Commands + var WorkerCommandList = WorkerCommand.split("\n"); + var k; + var cmd = ""; + for (k = 0; k 0 ) { + if ( WorkerCommandList[k].indexOf("{") >= 0 || WorkerCommandList[k].indexOf("}") >= 0) { + // this line includes braces => plain javascript: do not parse it! + cmd += WorkerCommandList[k]; + if ( WorkerCommandList[k].indexOf("}") >= 0 ) { + // braces closed, we can end the line + cmd += " ;\n"; + } + } + else { + // standard lalolab line + cmd += parseCommand(WorkerCommandList[k]) + " ;\n"; + } + } + } + return cmd; +} +function parseSplittedCommand( cmd ) { + //console.log("parsing : " + cmd); + // !!! XXX should parse unary ops before all the others !!! + + var ops = ["==", "!=", ">=" ,"<=", ">", "<" , "\\" ,":", "+", "-", ".*", "*", "./" , "^", "'"]; // from lowest priority to highest + var opsFcts = ["isEqual" , "isNotEqual", "isGreaterOrEqual", "isLowerOrEqual", "isGreater" , "isLower", "solve","range", "add", "sub", "entrywisemul", "mul" , "entrywisediv", "pow", "undefined" ]; + var unaryOpsFcts = ["", "", "", "", "","", "","range","", "minus", "", "" , "", "", "transpose" ]; + + var o; + var i ; + var k; + var operandA; + var operandB; + + for ( o = 0; o < ops.length; o++) { + + var splitted_wrt_op = cmd.split(ops[o]); + + if ( splitted_wrt_op.length > 1) { + if ( removeSpaces(splitted_wrt_op[0]) != "" ) { + // there is actually a left-hand side operand + if( removeSpaces(splitted_wrt_op[1]) != "" ) { + // and a right-hand side operand + operandA = parseSplittedCommand(splitted_wrt_op[0]); + + for ( k = 1; k< splitted_wrt_op.length ; k++) { + operandB = splitted_wrt_op[k]; + operandA = opsFcts[o] + "(" + operandA + "," + parseSplittedCommand(operandB) + ")"; + } + cmd = operandA; + } + else { + // no right-hand side: like transpose operator + cmd = unaryOpsFcts[o] + "(" + parseSplittedCommand(splitted_wrt_op[0]) + ")"; + } + } + else { + // no left operand: like minus something... + + // Apply unary operator + operandA = unaryOpsFcts[o] + "(" + parseSplittedCommand(splitted_wrt_op[1]) + ")"; + + // and then binary operator for the remaining occurences + for ( k = 2; k< splitted_wrt_op.length ; k++) { + operandB = splitted_wrt_op[k]; + operandA = opsFcts[o] + "(" + operandA + "," + parseSplittedCommand(operandB) + ")"; + } + cmd = operandA; + } + } + } + + return cmd; + +} + +function parseAssignment ( assignmentStr ) { + if ( assignmentStr.indexOf("[") < 0 ) { + // straightforward assignment + return assignmentStr; + } + else { + var assign = removeSpaces(assignmentStr).replace("=","").replace(",","]["); + var middle = assign.indexOf("]["); + var start = assign.indexOf("["); + var varname = assign.substr(0,start); + if ( middle >= 0 ) { + // submatrix assignment + var rowsrange = assign.substr( start + 1, middle-start-1); + + // find last "]"; + var end = middle+1; + while ( assign.indexOf("]",end+1) >= 0) + end = assign.indexOf("]",end+1); + + var colsrange = assign.substr(middle+2, end - (middle+2)); // everything after "][" and before last "]" + + // Parse colon ranges + var rowssplit = rowsrange.split(":"); + if (rowssplit.length == 2 ){ + if ( rowssplit[0] =="" && rowssplit[1] =="" ) + rowsrange = "[]"; + else + rowsrange = "range(" + rowssplit[0] + "," + rowssplit[1] + ")"; + } + else if ( rowssplit.length == 3) + rowsrange = "range(" + rowssplit[0] + "," + rowssplit[2] + "," + rowssplit[1] + ")"; + + var colssplit = colsrange.split(":"); + if (colssplit.length == 2 ) { + if ( colssplit[0] =="" && colssplit[1] =="" ) + colsrange = "[]"; + else + colsrange = "range(" + colssplit[0] + "," + colssplit[1] + ")"; + } + else if ( colssplit.length == 3) + colsrange = "range(" + colssplit[0] + "," + colssplit[2] + "," + colssplit[1] + ")"; + + return "set( " + varname + "," + rowsrange + "," + colsrange + ", "; + } + else { + // subvector assignment + + // find last "]"; + var end = start; + while ( assign.indexOf("]",end+1) >= 0) + end = assign.indexOf("]",end+1); + + var rowsrange = assign.substr( start + 1, end-start-1); + + // Parse colon ranges + var rowssplit = rowsrange.split(":"); + if (rowssplit.length == 2 ){ + if ( rowssplit[0] =="" && rowssplit[1] =="" ) + rowsrange = "[]"; + else + rowsrange = "range(" + rowssplit[0] + "," + rowssplit[1] + ")"; + } + else if ( rowssplit.length == 3) + rowsrange = "range(" + rowssplit[0] + "," + rowssplit[2] + "," + rowssplit[1] + ")"; + + return "set( " + varname + "," + rowsrange + ", "; + } + } +} + +function parseBrackets( cmdString ) { + // Parse brackets => get matrix entries + + var delimiters = ["[", "(",",",";",")", "\\", "+", "-", "*", "/", ":", "^", "'", "=", ">", "<", "!"]; + + cmdString = cmdString.split("][").join(","); // replace ][ by , and ] by ) + + var cmd = cmdString.split(""); // string to array of char + + var i; + var j; + var k; + var l; + var lhs; + + // For the entire string: + i = cmd.length - 1; + while ( i >= 0 ) { + // Search for the right-most opening bracket: + while ( i >= 0 && cmd[i] != "[" ) + i--; + + if ( i >= 0 ) { + // found a bracket, find its corresponding closing bracket + j = i+1; + while ( j < cmd.length && cmd[j] != "]" ) + j++; + + if ( j < cmd.length ) { + + // then determine its left-hand side operand: + l = 0; + k = 0; + while ( k < i ) { + if ( delimiters.indexOf(cmd[k]) >= 0) + l = k+1; + k++; + } + lhs = cmd.slice(l,i).join(""); // should be LHS as string or "" if l >= i + + if ( removeSpaces(lhs) == "" ) { + // if the LHS operand is empty, leave the brackets untouched + cmd[i] = "#"; // (replace by # and $ re-replace at the end by a matrix creation) + + // look for semicolon within brackets: + k = i+1; + var rowwise = false; + var colwise = false; + while ( k < j ) { + if( cmd[k] == "," ) { + //colwise = true; + } + + if ( cmd[k] == ";" ) { + rowwise = true; // mark for rowwise mat + + if ( colwise ) { + cmd.splice(k,1, ["@", ","] ); // end previous row vector, replace by comma + colwise = false; + } + else { + cmd[k] = ","; // simply replace by comma + } + } + + + k++; + } + + if ( rowwise ) + cmd[j] = "$"; + else + cmd[j] = "@"; + + } + else { + // if not empty, implement a GET + cmd[l]="get(" + lhs ; + for ( k = l+1; k < i; k++) + cmd[k] = ""; + cmd[i] = ","; + cmd[j] = ")"; + } + } + else { + return undefined; // error no ending bracket; + } + } + i--; + } + + var cmdparsed = cmd.join("").split("#").join("mat([").split("$").join("], true)").split("@").join("])"); + //console.log(cmdparsed); + return cmdparsed; +} + +function parseCommand( cmdString ) { + + // Remove comments at the end of the line + var idxComments = cmdString.indexOf("//"); + if ( idxComments >= 0 ) + cmdString = cmdString.substr(0,idxComments); + + + // Parse "=" sign to divide between assignement String and computeString + var idxEqual = cmdString.split("==")[0].split("!=")[0].split(">=")[0].split("<=")[0].indexOf("="); + if ( idxEqual > 0 ) { + var assignmentStr = parseAssignment( cmdString.substr(0,idxEqual + 1) ); + var computeStr = cmdString.substr(idxEqual+1); + + // Check for simple assignments like A = B to force copy + if ( assignmentStr.indexOf("set(") < 0 && typeof(self[removeSpaces(computeStr)]) != "undefined" ) { //self.hasOwnProperty( removeSpaces(computeStr) ) ) { // self.hasOwnProperty does not work in Safari workers.... + + // computeStr is a varaible name + if ( !isScalar(self[ removeSpaces(computeStr) ] ) ) { + // the variable is a vector or matrix + var FinalCommand = assignmentStr + "matrixCopy(" + computeStr + ")"; + console.log(FinalCommand); + return FinalCommand; + } + } + } + else { + var assignmentStr = ""; + var computeStr = cmdString; + } + + // parse brackets: + var cmd = parseBrackets( computeStr ).split(""); // and convert string to Array + + // Parse delimiters + var startdelimiters = ["(","[",",",";"]; + var enddelimiters = [")","]",",",";"]; + var i; + var j; + var k; + var parsedContent = ""; + var parsedCommand = new Array(cmd.length); + + var map = new Array(cmd.length ) ; + for ( k=0;k= 0 ) { + // Find the most right starting delimiter + while ( i >= 0 && startdelimiters.indexOf(cmd[i]) < 0 ) + i--; + if ( i >= 0 ) { + // found a delimiter, search for the closest ending delimiter + j = i+1; + while ( j < cmd.length && enddelimiters.indexOf(cmd[j] ) < 0 ) { + j++; + } + if ( j < cmd.length ) { + // starting delimiter is at cmd[i] and ending one at cmd[j] + + // parse content within delimiters + parsedContent = parseSplittedCommand( parsedCommand.slice(map[i]+1,map[j]).join("") ) ; + // and replace the corresponding content in the parsed command + parsedCommand.splice (map[i]+1, map[j]-map[i]-1, parsedContent ) ; + + // remove delimiters from string to be parsed + if ( cmd[i] != "," ) + cmd[i] = " "; // except for commas that serve twice (once as start once as end) + cmd[j] = " "; + + // map position in the original cmd to positions in the parsedCommand to track brackets + for ( k=i+1; k < j;k++) + map[k] = map[i]+1; + var deltamap = map[j] - map[i] - 1; + for ( k=j; k < cmd.length;k++) + map[k] += 1 - deltamap; + + /*console.log(parsedCommand); + console.log(cmd.join("")); + console.log(map); + console.log(i + " : " + j);*/ + } + else { + return "undefined"; + } + } + i--; + } + var FinalCommand = assignmentStr + parseSplittedCommand(parsedCommand.join("")); + + // Parse brackets => get matrix entries + //cmdString = cmdString.split("][").join(",").split("]").join(")"); // replace ][ by , and ] by ) + // consider [ as a left-hand unary operator +// cmd = "get(" + parseSplittedCommand(splitted_wrt_op[0]) + ")"; + + + + if ( assignmentStr.substr(0,4) == "set(" ) + FinalCommand += " )"; + + FinalCommand = parseRangeRange( FinalCommand ); + + console.log(FinalCommand); + return FinalCommand; +} + +function parseRangeRange( cmd ) { + // parse complex ranges like 0:0.1:4 + var elems = cmd.split("range(range("); + var i; + var j; + var tmp; + var args; + var incargs; + var endargs; + for ( i = 0; i< elems.length - 1 ; i++) { + +// elems[i+1] = elems[i+1].replace(")",""); + + // ivert second and third arguments to get range(start, end, inc) from start:inc:end + args = elems[i+1].split(","); + tmp = args[2].split(")"); // keep only the content of the range and not the remaining commands + endargs = tmp[0]; + j = 0; // deal with cases like end="minus(4)" where the first closing bracket is not at the complete end + while ( tmp[j].indexOf("(") >= 0 ) { + endargs = endargs + ")" + tmp[j+1]; + j++; + } + + incargs = args[1].substr(0,args[1].length-1); // remove ")" + args[1] = endargs; + //endargs[0] = incargs; + args[2] = incargs + ")" + tmp.slice(j+1).join(")"); + elems[i+1] = args.join(","); + } + return elems.join("range(");//replace range(range( by range( +} + +function removeSpaces( str ) { + return str.split(" ").join(""); +} + +//////////////////////////// +/// Lab +//////////////////////////// +function MLlab ( id , path ) { + var that = new Lalolab ( id, true, path); + return that; +} +function Lalolab ( id, mllab , path ) { + // constructor for a Lab with independent scope running in a worker + this.id = id; + + this.callbacks = new Array(); + + // Create worker with a Blob to avoid distributing lalolibworker.js + // => does not work due to importScripts with relative path to the Blob unresolved (or cross-origin) + + if ( typeof(path) == "undefined" ) + var path = "http://mlweb.loria.fr/"; + else { + if (path.length > 0 && path[path.length-1] != "/" ) + path = [path,"/"].join(""); + } + + if ( typeof(mllab) != "undefined" && mllab ) { + this.worker = new Worker(path+"mlworker.js"); // need mlworker.js in same directory as web page + this.labtype = "ml"; + /* Using a Blob to avoid distributing mlworker.js: + does not work because of importScripts from cross origin... + var workerscript = "importScripts(\"ml.js\");\n onmessage = function ( WorkerEvent ) {\n var WorkerCommand = WorkerEvent.data.cmd;var mustparse = WorkerEvent.data.parse; \n if ( mustparse )\n var res = lalo(WorkerCommand);\n else {\n if ( WorkerCommand == \"load_mat\" ) {\n if ( type(WorkerEvent.data.data) == \"matrix\" )\n var res = new Matrix(WorkerEvent.data.data.m,WorkerEvent.data.data.n,WorkerEvent.data.data.val, true);\nelse\n var res = mat(WorkerEvent.data.data, true);\n eval(WorkerEvent.data.varname + \"=res\");\n}\n else\n var res = self.eval( WorkerCommand ) ;\n}\n try {\n postMessage( { \"cmd\" : WorkerCommand, \"output\" : res } );\n} catch ( e ) {\n try {\n postMessage( { \"cmd\" : WorkerCommand, \"output\" : res.info() } );\n } catch(e2) { \n postMessage( { \"cmd\" : WorkerCommand, \"output\" : undefined } );\n}\n}\n}"; + var blob = new Blob([workerscript], { "type" : "text/javascript" }); + var blobURL = window.URL.createObjectURL(blob); + console.log(blobURL); + this.worker = new Worker(blobURL);*/ + } + else { + this.worker = new Worker(path+"lalolibworker.js"); // need lalolibworker.js in same directory as web page + this.labtype = "lalo"; + } + this.worker.onmessage = this.onresult; + this.worker.parent = this; +} +Lalolab.prototype.close = function ( ) { + this.worker.terminate(); + this.worker.parent = null;// delete circular reference +} +Lalolab.prototype.onprogress = function ( ratio ) { + // do nothing by default; + // user must set lab.onprogress = function (ratio) { ... } to do something +} +Lalolab.prototype.onresult = function ( WorkerEvent ) { +// console.log(WorkerEvent, ""+ this.parent.callbacks); + if ( typeof(WorkerEvent.data.progress) != "undefined" ) { + this.parent.onprogress( WorkerEvent.data.progress ) ; + } + else { + var cb = this.parent.callbacks.splice(0,1)[0] ; // take first callback from the list + if ( typeof(cb) == "function" ) { + var WorkerCommand = WorkerEvent.data.cmd; + var WorkerResult = WorkerEvent.data.output; + cb( WorkerResult, WorkerCommand, this.parent.id ); // call the callback if present + } + } +} +Lalolab.prototype.do = function ( cmd , callback ) { + // prepare callback, parse cmd and execute in worker + this.callbacks.push( callback ) ; + this.worker.postMessage( {cmd: cmd, parse: true} ); +} +Lalolab.prototype.exec = function ( cmd , callback ) { + // prepare callback, and execute cmd in worker + this.callbacks.push( callback ); + this.worker.postMessage( {cmd: cmd, parse: false} ); +} +Lalolab.prototype.parse = function ( cmd , callback ) { + // prepare callback, parse cmd and execute in worker + this.callbacks.push( callback ); + this.worker.postMessage( {cmd: cmd, parse: false} ); +} +Lalolab.prototype.load = function ( data , varname, callback ) { + // load data in varname + this.callbacks.push( callback ) ; + if ( typeof(data) == "string" ){ + this.worker.postMessage( {"cmd" : varname + "= load_data (\"" + data + "\")", parse: false} ); + } + else { + this.worker.postMessage( {"cmd" : "load_mat", data: data, varname: varname, parse: false} ); + } +} +Lalolab.prototype.import = function ( script, callback ) { + // load a script in lalolib language + this.do('importLaloScript("' + script + '")', callback); +} +function importLaloScript ( script ) { + // load a script in lalolib language in the current Lab worker + var xhr = new XMLHttpRequest(); + xhr.open('GET', script, false); + xhr.send(); + var cmd = xhr.responseText; + return lalo(cmd); +} +Lalolab.prototype.importjs = function ( script, callback ) { + // load a script in javascript + this.exec("importScripts('" + script + "');", callback); +} +Lalolab.prototype.getObject = function ( varname, callback ) { + this.exec("getObjectWithoutFunc(" + varname +")", function (res) {callback(renewObject(res));} ); +} + +function getObjectWithoutFunc( obj ) { + // Functions and Objects with function members cannot be sent + // from one worker to another... + + if ( typeof(obj) != "object" ) + return obj; + else { + var res = {}; + + for (var p in obj ) { + switch( type(obj[p]) ) { + case "vector": + res[p] = {type: "vector", data: [].slice.call(obj[p])}; + break; + case "matrix": + res[p] = obj[p]; + res[p].val = [].slice.call(obj[p].val); + break; + case "spvector": + res[p] = obj[p]; + res[p].val = [].slice.call(obj[p].val); + res[p].ind = [].slice.call(obj[p].ind); + break; + case "spmatrix": + res[p] = obj[p]; + res[p].val = [].slice.call(obj[p].val); + res[p].cols = [].slice.call(obj[p].cols); + res[p].rows = [].slice.call(obj[p].rows); + break; + case "undefined": + res[p] = obj[p]; + break; + case "function": + break; + case "Array": + res[p] = getObjectWithoutFunc( obj[p] ); + res[p].type = "Array"; + res[p].length = obj[p].length; + break; + default: + res[p] = getObjectWithoutFunc( obj[p] ); + break; + } + } + return res; + } +} +function renewObject( obj ) { + // Recreate full object with member functions + // from an object created by getObjectWithoutFunc() + + var to = type(obj); + switch( to ) { + case "number": + case "boolean": + case "string": + case "undefined": + return obj; + break; + case "vector": + return new Float64Array(obj.data); + break; + case "matrix": + return new Matrix(obj.m, obj.n, obj.val); + break; + case "spvector": + return new spVector(obj.length,obj.val,obj.ind); + break; + case "spmatrix": + return new spMatrix(obj.m, obj.n, obj.val, obj.cols, obj.rows); + break; + case "object": + // Object without type property and thus without Class + var newobj = {}; + for ( var p in obj ) + newobj[p] = renewObject(obj[p]); + return newobj; + break; + case "Array": + var newobj = new Array(obj.length); + for ( var p in obj ) + newobj[p] = renewObject(obj[p]); + return newobj; + default: + // Structured Object like Classifier etc... + // type = Class:subclass + var typearray = obj.type.split(":"); + var Class = eval(typearray[0]); + if ( typearray.length == 1 ) + var newobj = new Class(); + else + var newobj = new Class(typearray[1]); + for ( var p in obj ) + newobj[p] = renewObject(obj[p]); + + // deal with particular cases: + // Rebuild kernelFunc + if (typearray[1] == "SVM" || typearray[1] == "SVR" ) { + newobj["kernelFunc"] = kernelFunction(newobj["kernel"], newobj["kernelpar"], type(newobj["SV"]) == "spmatrix"?"spvector":"vector"); + } + if (typearray[1] == "KernelRidgeRegression" ) { + newobj["kernelFunc"] = kernelFunction(newobj["kernel"], newobj["kernelpar"], type(newobj["X"]) == "spmatrix"?"spvector":"vector"); + } + + return newobj; + break; + } +} + +function load_data ( datastring ) { + + // convert a string into a matrix data + var i; + var cmd = "mat( [ "; + var row; + var rows = datastring.split("\n"); + var ri ; + for ( i=0; i< rows.length - 1; i++) { + ri = removeFirstSpaces(rows[i]); + if ( ri != "" ) { + row = ri.replace(/,/g," ").replace(/ +/g,","); + cmd += "new Float64Array([" + row + "]) ,"; + } + } + ri = removeFirstSpaces(rows[rows.length-1]); + if ( ri != "" ) { + row = ri.replace(/,/g," ").replace(/ +/g,","); + cmd += "new Float64Array([" + row + "]) ] , true) "; + } + else { + cmd = cmd.substr(0,cmd.length-1); // remove last comma + cmd += "] , true) "; + } + + return eval(cmd); + +} + +function removeFirstSpaces( str ) { + //remove spaces at begining of string + var i = 0; + while ( i < str.length && str[i] == " " ) + i++; + if ( i submatrix of M + get ( M, rows ) => subset of rows from M (equiv to rows(M,rows) ) + get ( M, [], cols ) => subset of cols (equiv to cols(M, cols) ) + get ( M, i, j) => M[i][j] converted to dense format (0 instead of undefined) + get ( M ) => M in dense format (with 0 instead of undefined) + + For VECTORS: + + get ( v, rows ) => subvector from v (equiv to rows(v,rows) ) + get ( v, i ) => v[i] converted to dense format (0 instead of undefined) + get ( v ) => v in dense format (with 0 instead of undefined) + +*/ +function get ( A , rowsrange, colsrange) { + + var typerows = typeof(rowsrange); + var typecols = typeof(colsrange); + + if (arguments.length == 1 ) + return matrixCopy(A); + + var typeA = type ( A ); + if ( typeA == "vector" ) { + + if ( typerows == "number" ) { + if (rowsrange >= 0 && rowsrange < A.length) + return A[rowsrange]; // get v[i] + else { + error("Error in a[i] = get(a,i): Index i="+rowsrange+" out of bounds [0,"+(A.length-1)+"]"); + return undefined; + } + } + else { + return getSubVector(A, rowsrange); + } + } + else if ( typeA == "matrix") { + + if ( typerows == "number" ) + rowsrange = [rowsrange]; + + if ( typecols == "number" ) + colsrange = [colsrange]; + + if ( rowsrange.length == 1 && colsrange.length == 1 ) + return A.val[rowsrange[0] * A.n + colsrange[0]]; // get ( A, i, j) + + if ( rowsrange.length == 0 ) + return getCols(A,colsrange);// get(A,[],4) <=> cols(A,4) + + if (colsrange.length == 0 ) + return getRows(A, rowsrange);// get(A,3,[]) <=> rows(A,3) + + // otherwise: + return getSubMatrix(A, rowsrange, colsrange); + + } + else if ( typeA == "Array" ) { + if ( typerows == "number" ) + return A[rowsrange]; + else + return getSubArray(A, rowsrange); + } + else if ( typeA == "spmatrix") { + + if ( typerows == "number" ) + rowsrange = [rowsrange]; + + if ( typecols == "number" ) + colsrange = [colsrange]; + + if ( rowsrange.length == 1 && colsrange.length == 1 ) + return A.get(rowsrange[0], colsrange[0]); // get ( A, i, j) + + if ( rowsrange.length == 1 && A.rowmajor ) + return A.row(rowsrange[0]); + if ( colsrange.length == 1 && !A.rowmajor ) + return A.col(colsrange[0]); + + if (colsrange.length == 0 ) + return spgetRows(A, rowsrange); + if ( rowsrange.length == 0 ) + return spgetCols(A,colsrange); + + // TODO + } + else if ( typeA == "spvector" ) { + + if ( typerows == "number" ) + return A.get( rowsrange ); // get v[i] + else + return getSubspVector(A, rowsrange);//TODO + } + else if ( typeA == "ComplexVector") { + if ( typerows == "number" ) + return A.get( rowsrange ); // get v[i] + else + return A.getSubVector(rowsrange); + } + else if ( typeA == "ComplexMatrix") { + + if ( typerows == "number" ) + rowsrange = [rowsrange]; + + if ( typecols == "number" ) + colsrange = [colsrange]; + + if ( rowsrange.length == 1 && colsrange.length == 1 ) + return A.get(i,j); + + if ( rowsrange.length == 0 ) + return A.getCols(colsrange);// get(A,[],4) <=> cols(A,4) + + if (colsrange.length == 0 ) + return A.getRows(rowsrange);// get(A,3,[]) <=> rows(A,3) + + // otherwise: + return A.getSubMatrix(rowsrange, colsrange); + } + return undefined; +} +function getSubMatrix(A, rowsrange, colsrange) { + var n = colsrange.length; + var i; + var j; + var res; + if ( n == 1 ) { + res = new Float64Array(rowsrange.length); + for (i= 0; i< rowsrange.length ; i++) { + res[i] = A.val[rowsrange[i] * A.n + colsrange[0]]; + } + } + else { + res = new Matrix(rowsrange.length, n); + var r = 0; + + for (i= 0; i< rowsrange.length ; i++) { + var rA = rowsrange[i]*A.n; + for ( j=0; j < n; j++) { + res.val[r+j] = A.val[rA + colsrange[j]]; + } + r += n; + } + } + return res; +} + +function getRows(A, rowsrange) { + var n = rowsrange.length; + if ( n > 1 ) { + var res = new Matrix(n, A.n); + var r=0; + for ( var i = 0; i < n; i++) { + for (var j=0; j < A.n; j++) + res.val[r + j] = A.val[rowsrange[i]*A.n + j]; + r += A.n; + } + return res; + } + else + return vectorCopy(A.val.subarray( rowsrange[0]*A.n, rowsrange[0]*A.n + A.n)); +} +function getCols(A, colsrange) { + var m = A.m; + var n = colsrange.length; + if( n > 1 ) { + var res = new Matrix(m, n); + var r = 0; + var rA = 0; + for ( var i = 0; i < m; i++) { + for ( var j = 0; j < n; j++) + res.val[r + j] = A.val[rA + colsrange[j]]; + + r += n; + rA += A.n; + } + return res; + } + else { + var res = new Float64Array(m); + var r = 0; + for ( var i = 0; i < m; i++) { + res[i] = A.val[r + colsrange[0]]; + r += A.n; + } + return res; + } +} +/** + * @param {Float64Array} + * @param {Array} + * @return {Float64Array} + */ +function getSubVector(a, rowsrange) { + const n = rowsrange.length; + var res= new Float64Array( n ); + for (var i = 0; i< n; i++) { + res[i] = a[rowsrange[i]]; + } + return res; +} + +/** + * @param {Array} + * @param {Array} + * @return {Array} + */ +function getSubArray(a, rowsrange) { + const n = rowsrange.length; + var res= new Array( n ); + for (var i = 0; i< n; i++) { + res[i] = a[rowsrange[i]]; + } + return res; +} + + +function getrowref(A, i) { + // return a pointer-like object on a row in a matrix, not a copy! + return A.val.subarray(i*A.n, (i+1)*A.n); +} + +/* + SET function : set values in a subset of entries of a matrix or vector + + For MATRICES: + + set ( M, rows, cols, A ) => submatrix of M = A + set ( M, rows, A ) => subset of rows from M = A + set ( M, [], cols, A ) => subset of cols from M = A + set ( M, i, [], A ) => fill row M[i] with vector A (transposed) + set ( M, i, j, A) => M[i][j] = A + + For VECTORS: + + set ( v, rows, a ) => subvector from v = a + set ( v, i , a) => v[i] = a + +*/ +function set ( A , rowsrange, colsrange, B) { + var i; + var j; + var k; + var l; + var n; + + var typerows = typeof(rowsrange); + var typecols = typeof(colsrange); + + if (arguments.length == 1 ) + return undefined; + + var typeA = type ( A ); + if ( typeA == "vector" ) { + B = colsrange; + if ( typerows == "number" ) { + A[rowsrange] = B; + return B; + } + else if ( rowsrange.length == 0 ) + rowsrange = range(A.length); + + if ( size(B,1) == 1 ) { + setVectorScalar (A, rowsrange, B); + } + else { + setVectorVector (A, rowsrange, B); + } + return B; + } + else if ( typeA == "matrix") { + + if ( typerows == "number" ) + rowsrange = [rowsrange]; + if ( typecols == "number" ) + colsrange = [colsrange]; + + if ( rowsrange.length == 1 && colsrange.length == 1 ) { + A.val[rowsrange[0]*A.n + colsrange[0]] = B; + return B; + } + + if ( rowsrange.length == 0 ) { + setCols(A, colsrange, B); + return B; + } + + if (colsrange.length == 0 ) { + setRows( A, rowsrange, B); + return B; + } + + // Set a submatrix + var sB = size(B); + var tB = type(B); + if ( sB[0] == 1 && sB[1] == 1 ) { + if ( tB == "number" ) + setMatrixScalar(A, rowsrange, colsrange, B); + else if ( tB == "vector" ) + setMatrixScalar(A, rowsrange, colsrange, B[0]); + else + setMatrixScalar(A, rowsrange, colsrange, B.val[0]); + } + else { + if ( colsrange.length == 1 ) + setMatrixColVector(A, rowsrange, colsrange[0], B); + else if ( rowsrange.length == 1 ) { + if ( tB == "vector" ) + setMatrixRowVector(A, rowsrange[0], colsrange, B); + else + setMatrixRowVector(A, rowsrange[0], colsrange, B.val); + } + else + setMatrixMatrix(A, rowsrange, colsrange, B); + } + return B; + } + else if ( typeA == "ComplexVector" ) { + B = colsrange; + if ( typerows == "number" ) { + A.set(rowsrange, B); + return B; + } + else if ( rowsrange.length == 0 ) + rowsrange = range(A.length); + + if ( size(B,1) == 1 ) { + A.setVectorScalar (rowsrange, B); + } + else { + A.setVectorVector (rowsrange, B); + } + return B; + } +} + +function setVectorScalar(A, rowsrange, B) { + var i; + for (i = 0; i< rowsrange.length; i++) + A[rowsrange[i]] = B; +} +function setVectorVector(A, rowsrange, B) { + var i; + for (i = 0; i< rowsrange.length; i++) + A[rowsrange[i]] = B[i]; +} + +function setMatrixScalar(A, rowsrange, colsrange, B) { + var i; + var j; + var m = rowsrange.length; + var n = colsrange.length; + for (i = 0; i< m; i++) + for(j=0; j < n; j++) + A.val[rowsrange[i]*A.n + colsrange[j]] = B; +} +function setMatrixMatrix(A, rowsrange, colsrange, B) { + var i; + var j; + var m = rowsrange.length; + var n = colsrange.length; + for (i = 0; i< m; i++) + for(j=0; j < n; j++) + A.val[rowsrange[i]*A.n + colsrange[j]] = B.val[i*B.n +j]; +} +function setMatrixColVector(A, rowsrange, col, B) { + var i; + var m = rowsrange.length; + for (i = 0; i< m; i++) + A.val[rowsrange[i]*A.n + col] = B[i]; +} +function setMatrixRowVector(A, row, colsrange, B) { + var j; + var n = colsrange.length; + for(j=0; j < n; j++) + A.val[row*A.n + colsrange[j]] = B[j]; +} +function setRows(A, rowsrange, B ) { + var i; + var j; + var m = rowsrange.length; + var rA; + switch( type(B) ) { + case "vector": + for ( i=0; i end ) { + if ( inc > 0) + inc *= -1; + var r = new Array( Math.floor ( ( start - end ) / Math.abs(inc) ) ); + var k = 0; + for ( var i = start; i> end; i+=inc) { + r[k] = i; + k++; + } + } + else { + var r = new Array( Math.floor ( ( end - start ) / inc ) ); + var k = 0; + for ( var i = start; i< end; i+=inc) { + r[k] = i; + k++; + } + } + return r; +} + +// Swaping +/** + * @param {Matrix} + */ +function swaprows ( A , i, j ) { + if ( i != j ) { + var ri = i*A.n; + var rj = j*A.n; + var tmp = vectorCopy(A.val.subarray(ri, ri+A.n)); + A.val.set(vectorCopy(A.val.subarray(rj, rj+A.n)), ri); + A.val.set(tmp, rj); + } +} +/** + * @param {Matrix} + */ +function swapcols ( A , j, k ) { + if ( j != k ) { + var tmp = getCols ( A, [j]); + setCols ( A, [j] , getCols ( A, [k]) ); + setCols ( A, [k], tmp); + } +} + +////////////////////////// +// Random numbers +//////////////////////////// + +// Gaussian random number (mean = 0, variance = 1; +// Gaussian noise with the polar form of the Box-Muller transformation +function randnScalar() { + + var x1; + var x2; + var w; + var y1; + var y2; + do { + x1 = 2.0 * Math.random() - 1.0; + x2 = 2.0 * Math.random() - 1.0; + w = x1 * x1 + x2 * x2; + } while ( w >= 1.0 ); + + w = Math.sqrt( (-2.0 * Math.log( w ) ) / w ); + y1 = x1 * w; + y2 = x2 * w; + + return y1; +} +function randn( dim1, dim2 ) { + var res; + + if ( typeof ( dim1 ) == "undefined" || (dim1 == 1 && typeof(dim2)=="undefined") || (dim1 == 1 && dim2==1)) { + return randnScalar(); + } + else if (typeof(dim2) == "undefined" || dim2 == 1 ) { + res = new Float64Array(dim1); + for (var i=0; i< dim1; i++) + res[i] = randnScalar(); + + return res; + } + else { + res = zeros(dim1, dim2); + for (var i=0; i< dim1*dim2; i++) { + res.val[i] = randnScalar(); + } + return res; + } +} + +// Uniform random numbers +/* + * @param{number} + * @return{Float64Array} + */ +function randVector(dim1) { + var res = new Float64Array(dim1); + for (var i=0; i< dim1; i++) { + res[i] = Math.random(); + } + return res; +} +/* + * @param{number} + * @param{number} + * @return{Matrix} + */ +function randMatrix(dim1,dim2) { + const n = dim1*dim2; + var res = new Float64Array(n); + for (var i=0; i< n; i++) { + res[i] = Math.random(); + } + return new Matrix(dim1,dim2,res,true); +} +function rand( dim1, dim2 ) { + var res; + if ( typeof ( dim1 ) == "undefined" || (dim1 == 1 && typeof(dim2)=="undefined") || (dim1 == 1 && dim2==1)) { + return Math.random(); + } + else if (typeof(dim2) == "undefined" || dim2 == 1) { + return randVector(dim1); + } + else { + return randMatrix(dim1,dim2); + } +} + +function randnsparse(NZratio, dim1, dim2) { + // Generates a sparse random matrix with NZratio * dim1*dim2 (or NZ if NZratio > 1 ) nonzeros + var NZ; + if ( NZratio > 1 ) + NZ = NZratio; + else + NZ = Math.floor(NZratio *dim1*dim2); + + var indexes; + var i; + var j; + var k; + var res; + + if ( typeof ( dim1 ) == "undefined" ) { + return randn(); + } + else if (typeof(dim2) == "undefined" || dim2 == 1) { + + indexes = randperm( dim1 ); + + res = zeros(dim1); + for (i=0; i< NZ; i++) { + res[indexes[i]] = randn(); + } + return res; + } + else { + res = zeros(dim1, dim2); + indexes = randperm( dim1*dim2 ); + for (k=0; k< NZ; k++) { + i = Math.floor(indexes[k] / dim2); + j = indexes[k] - i * dim2; + res.val[i*dim2+j] = randn(); + } + return res; + } +} +function randsparse(NZratio, dim1, dim2) { + // Generates a sparse random matrix with NZratio * dim1*dim2 (or NZ if NZratio > 1 ) nonzeros + if (typeof(dim2) == "undefined") + var dim2 = 1; + + var NZ; + if ( NZratio > 1 ) + NZ = NZratio; + else + NZ = Math.floor(NZratio *dim1*dim2); + + var indexes; + var i; + var j; + var k; + var res; + + if ( typeof ( dim1 ) == "undefined" ) { + return randn(); + } + else if (dim2 == 1) { + + indexes = randperm( dim1 ); + + res = zeros(dim1); + for (i=0; i< NZ; i++) { + res[indexes[i]] = Math.random(); + } + return res; + } + else { + res = zeros(dim1, dim2); + indexes = randperm( dim1*dim2 ); + + for (k=0; k< NZ; k++) { + i = Math.floor(indexes[k] / dim2); + j = indexes[k] - i * dim2; + res.val[i*dim2+j] = Math.random(); + } + return res; + } +} + +function randperm( x ) { + // return a random permutation of x (or of range(x) if x is a number) + + if ( typeof( x ) == "number" ) { + var perm = range(x); + } + else { + var perm = new Float64Array(x); + } + var i; + var j; + var k; + + // shuffle + for(i=perm.length - 1 ; i > 1; i--) { + j = Math.floor(Math.random() * i); + k = perm[j]; + perm[j] = perm[i]; + perm[i] = k; + } + return perm; +} +/////////////////////////////// +/// Basic Math function: give access to Math.* JS functions +/// and vectorize them +/////////////////////////////// + + +// automatically generate (vectorized) wrappers for Math functions +var MathFunctions = Object.getOwnPropertyNames(Math); +for ( var mf in MathFunctions ) { + if ( eval( "typeof(Math." + MathFunctions[mf] + ")") == "function") { + if ( eval( "Math." + MathFunctions[mf] + ".length") == 1 ) { + // this is a function of a scalar + // make generic function: + eval( MathFunctions[mf] + " = function (x) { return apply(Math."+ MathFunctions[mf] + " , x );};"); + // make vectorized version: + eval( MathFunctions[mf] + "Vector = function (x) { return applyVector(Math."+ MathFunctions[mf] + " , x );};"); + // make matrixized version: + eval( MathFunctions[mf] + "Matrix = function (x) { return applyMatrix(Math."+ MathFunctions[mf] + " , x );};"); + } + } + else if ( eval( "typeof(Math." + MathFunctions[mf] + ")") == "number") { + // Math constant: + eval( MathFunctions[mf] + " = Math."+ MathFunctions[mf] ) ; + } +} + +function apply( f, x ) { + // Generic wrapper to apply scalar functions + // element-wise to vectors and matrices + if ( typeof(f) != "function") + return undefined; + switch ( type( x ) ) { + case "number": + return f(x); + break; + case "Complex": + var ComplexFunctions = ["exp", "abs"]; + var fc = ComplexFunctions.indexOf(f.name); + if ( fc >= 0 ) + return eval(ComplexFunctions[fc] + "Complex(x);"); + else { + error("This function has no Complex counterpart (yet)."); + return undefined; + } + break; + case "vector": + return applyVector(f, x); + break; + case "spvector": + return applyspVector(f, x); + break; + case "ComplexVector": + if ( f.name == "abs" ) + return absComplex(x); + else + return applyComplexVector(f, x); + break; + case "matrix": + return applyMatrix(f, x); + break; + case "spmatrix": + return applyspMatrix(f, x); + break; + case "ComplexMatrix": + if ( f.name == "abs" ) + return absComplex(x); + else + return applyComplexMatrix(f, x); + break; + default: + return "undefined"; + } +} +function applyVector( f, x ) { + const nv = x.length; + var res = new Float64Array(nv); + for (var i=0; i< nv; i++) + res[i] = f(x[i]); + return res; +} +function applyComplexVector( f, x ) { + const nv = x.length; + var res = new ComplexVector(nv); + for (var i=0; i< nv; i++) + res.set(i, f(x.get(i) ) ); + return res; +} +function applyComplexMatrix( f, x ) { + const m = x.m; + const n = x.n; + var res = new ComplexMatrix(m, n); + for (var i=0; i< m; i++) + for ( var j =0; j < n; j++) + res.set(i, j, f(x.get(i,j) ) ); + return res; +} +function applyMatrix(f, x) { + return new Matrix(x.m, x.n, applyVector(f, x.val), true); +} +/////////////////////////////// +/// Operators +/////////////////////////////// + +function mul(a,b) { + var sa = size(a); + var sb = size(b); + if ( !isScalar(a) && sa[0] == 1 && sa[1] == 1 ) + a = get(a, 0, 0); + if ( !isScalar(b) && sb[0] == 1 && sb[1] == 1 ) + b = get(b, 0, 0); + + switch( type(a) ) { + case "number": + switch( type(b) ) { + case "number": + return a*b; + break; + case "Complex": + return mulComplexReal(b,a); + break; + case "vector": + return mulScalarVector(a,b); + break; + case "spvector": + return mulScalarspVector(a,b); + break; + case "ComplexVector": + return mulScalarComplexVector(a,b); + break; + case "matrix": + return mulScalarMatrix(a,b); + break; + case "spmatrix": + return mulScalarspMatrix(a,b); + break; + case "ComplexMatrix": + return mulScalarComplexMatrix(a,b); + break; + default: + return undefined; + break; + } + break; + case "Complex": + switch( type(b) ) { + case "number": + return mulComplexReal(a,b); + break; + case "Complex": + return mulComplex(a,b); + break; + case "vector": + return mulComplexVector(a,b); + break; + case "ComplexVector": + return mulComplexComplexVector(a,b); + break; + case "spvector": + return mulComplexspVector(a,b); + break; + case "matrix": + return mulComplexMatrix(a,b); + break; + case "ComplexMatrix": + return mulComplexComplexMatrix(a,b); + break; + case "spmatrix": + return mulComplexspMatrix(a,b); + break; + default: + return undefined; + break; + } + break; + case "vector": + switch( type(b) ) { + case "number": + return mulScalarVector(b,a); + break; + case "Complex": + return mulComplexVector(b,a); + break; + case "vector": + if ( a.length != b.length ) { + error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return dot(a,b); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotspVectorVector(b,a); + break; + case "ComplexVector": + if ( a.length != b.length ) { + error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotComplexVectorVector(b,a); + break; + case "matrix": + if ( b.m == 1) + return outerprodVectors(a , b.val ); + else { + error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]"); + return undefined; + } + break; + case "spmatrix": + if ( b.m == 1) + return outerprodVectors(a , fullMatrix(b).val ); + else { + error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]"); + return undefined; + } + break; + case "ComplexMatrix": + if ( b.m == 1) + return transpose(outerprodComplexVectorVector(new ComplexVector(b.re,b.im,true), a , b.val )); + else { + error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]"); + return undefined; + } + break; + default: + return undefined; + break; + } + break; + case "spvector": + switch( type(b) ) { + case "number": + return mulScalarspVector(b,a); + break; + case "vector": + if ( a.length != b.length ) { + error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotspVectorVector(a,b); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return spdot(b,a); + break; + case "matrix": + if ( b.m == 1) + return outerprodspVectorVector(a , b.val ); + else { + error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]"); + return undefined; + } + break; + case "spmatrix": + if ( b.m == 1) + return outerprodspVectorVector(a, fullMatrix(b).val); + else { + error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]"); + return undefined; + } + break; + default: + return undefined; + break; + } + break; + case "ComplexVector": + switch( type(b) ) { + case "number": + return mulScalarComplexVector(b,a); + break; + case "Complex": + return mulComplexComplexVector(b,a); + break; + case "vector": + if ( a.length != b.length ) { + error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotComplexVectorVector(a,b); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in mul(a,b) (dot product): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotComplexVectorspVector(a,b); + break; + case "matrix": + if ( b.m == 1) + return outerprodComplexVectorVector(a , b.val ); + else { + error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]"); + return undefined; + } + break; + case "spmatrix": + if ( b.m == 1) + return outerprodComplexVectorVector(a , fullMatrix(b).val ); + else { + error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]"); + return undefined; + } + break; + case "ComplexMatrix": + if ( b.m == 1) + return outerprodComplexVectors(a , new ComplexVector(b.re,b.im, true) ); + else { + error("Inconsistent dimensions in mul(a,B): size(a) = [" + sa[0] + "," + sa[1] + "], size(B) = [" + sb[0] + "," + sb[1] + "]"); + return undefined; + } + break; + default: + return undefined; + break; + } + break; + + case "matrix": + switch( type(b) ) { + case "number": + return mulScalarMatrix(b,a); + break; + case "Complex": + return mulComplexMatrix(b,a); + break; + case "vector": + if ( a.m == 1 ) { + // dot product with explicit transpose + if ( a.val.length != b.length ) { + error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length."); + return undefined; + } + return dot(a.val, b); + } + else { + if ( a.n != b.length ) { + error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length."); + return undefined; + } + return mulMatrixVector(a,b); + } + break; + case "spvector": + if ( a.m == 1 ) { + // dot product with explicit transpose + if ( a.val.length != b.length ) { + error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotspVectorVector(b, a.val); + } + else { + if ( a.n != b.length ) { + error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length."); + return undefined; + } + return mulMatrixspVector(a,b); + } + break; + case "ComplexVector": + if ( a.m == 1 ) { + // dot product with explicit transpose + if ( a.val.length != b.length ) { + error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotComplexVectorVector(b, a.val); + } + else { + if ( a.n != b.length ) { + error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length."); + return undefined; + } + return mulMatrixComplexVector(a,b); + } + break; + case "matrix": + if ( a.n != b.m ) { + error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m."); + return undefined; + } + return mulMatrixMatrix(a,b); + break; + case "spmatrix": + if ( a.n != b.m ) { + error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m."); + return undefined; + } + return mulMatrixspMatrix(a,b); + break; + case "ComplexMatrix": + if ( a.n != b.m ) { + error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m."); + return undefined; + } + return transpose(mulComplexMatrixMatrix(transpose(b),transpose(a))); + break; + default: + return undefined; + break; + } + break; + case "spmatrix": + switch( type(b) ) { + case "number": + return mulScalarspMatrix(b,a); + break; + case "vector": + if ( a.m == 1 ) { + // dot product with explicit transpose + if ( a.n != b.length ) { + error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length."); + return undefined; + } + return dot(fullMatrix(a).val, b); + } + else { + if ( a.n != b.length ) { + error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length."); + return undefined; + } + return mulspMatrixVector(a,b); + } + break; + case "spvector": + if ( a.m == 1 ) { + // dot product with explicit transpose + if ( a.n != b.length ) { + error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotspVectorVector(b, fullMatrix(a).val); + } + else { + if ( a.n != b.length ) { + error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length."); + return undefined; + } + return mulspMatrixspVector(a,b); + } + break; + case "matrix": + if ( a.n != b.m ) { + error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m."); + return undefined; + } + return mulspMatrixMatrix(a,b); + break; + case "spmatrix": + if ( a.n != b.m ) { + error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m."); + return undefined; + } + return mulspMatrixspMatrix(a,b); + break; + default: + return undefined; + break; + } + break; + case "ComplexMatrix": + switch( type(b) ) { + case "number": + return mulScalarComplexMatrix(b,a); + break; + case "Complex": + return mulComplexComplexMatrix(b,a); + break; + case "vector": + if ( a.m == 1 ) { + // dot product with explicit transpose + if ( a.val.length != b.length ) { + error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotComplexVectorVector(new ComplexVector(a.re,a.im,true), b); + } + else { + if ( a.n != b.length ) { + error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length."); + return undefined; + } + return mulComplexMatrixVector(a,b); + } + break; + case "spvector": + if ( a.m == 1 ) { + // dot product with explicit transpose + if ( a.val.length != b.length ) { + error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotComplexVectorspVector(new ComplexVector(a.re,a.im,true), b); + } + else { + if ( a.n != b.length ) { + error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length."); + return undefined; + } + return mulComplexMatrixspVector(a,b); + } + break; + case "ComplexVector": + if ( a.m == 1 ) { + // dot product with explicit transpose + if ( a.val.length != b.length ) { + error("Error in mul(a',b): a.length = " + a.val.length + " != " + b.length + " = b.length."); + return undefined; + } + return dotComplexVectors(new ComplexVector(a.re,a.im,true), b); + } + else { + if ( a.n != b.length ) { + error("Error in mul(A,b): A.n = " + a.n + " != " + b.length + " = b.length."); + return undefined; + } + return mulComplexMatrixComplexVector(a,b); + } + break; + case "matrix": + if ( a.n != b.m ) { + error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m."); + return undefined; + } + return mulComplexMatrixMatrix(a,b); + break; + case "spmatrix": + if ( a.n != b.m ) { + error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m."); + return undefined; + } + return mulComplexMatrixspMatrix(a,b); + break; + case "ComplexMatrix": + if ( a.n != b.m ) { + error("Error in mul(A,B): A.n = " + a.n + " != " + b.m + " = B.m."); + return undefined; + } + return mulComplexMatrices(a,b); + break; + default: + return undefined; + break; + } + break; + default: + return undefined; + break; + } +} + +/** + * @param {number} + * @param {Float64Array} + * @return {Float64Array} + */ +function mulScalarVector( scalar, vec ) { + var i; + const n = vec.length; + var res = new Float64Array(vec); + for ( i=0; i < n; i++) + res[i] *= scalar ; + return res; +} +/** + * @param {number} + * @param {Matrix} + * @return {Matrix} + */ +function mulScalarMatrix( scalar, A ) { + var res = new Matrix(A.m,A.n, mulScalarVector(scalar, A.val), true ); + + return res; +} + +/** + * @param {Float64Array} + * @param {Float64Array} + * @return {number} + */ +function dot(a, b) { + const n = a.length; + var i; + var res = 0; + for ( i=0; i< n; i++) + res += a[i]*b[i]; + return res; +} + +/** + * @param {Matrix} + * @param {Float64Array} + * @return {Float64Array} + */ +function mulMatrixVector( A, b ) { + const m = A.length; + var c = new Float64Array(m); + var r = 0; + for (var i=0; i < m; i++) { + c[i] = dot(A.val.subarray(r, r+A.n), b); + r += A.n; + } + + return c; +} +/** + * @param {Matrix} + * @param {Float64Array} + * @return {Float64Array} + */ +function mulMatrixTransVector( A, b ) { + const m = A.length; + const n = A.n; + var c = new Float64Array(n); + var rj = 0; + for (var j=0; j < m; j++) { + var bj = b[j]; + for (var i=0; i < n; i++) { + c[i] += A.val[rj + i] * bj; + } + rj += A.n; + } + return c; +} +/** + * @param {Matrix} + * @param {Matrix} + * @return {Matrix} + */ +function mulMatrixMatrix(A, B) { + const m = A.length; + const n = B.n; + const n2 = B.length; + + var Av = A.val; + var Bv = B.val; + + var C = new Float64Array(m*n); + var aik; + var Aik = 0; + var Ci = 0; + for (var i=0;i < m ; i++) { + var bj = 0; + for (var k=0; k < n2; k++ ) { + aik = Av[Aik]; + for (var j =0; j < n; j++) { + C[Ci + j] += aik * Bv[bj]; + bj++; + } + Aik++; + } + Ci += n; + } + return new Matrix(m,n,C, true); +} +/** + * @param {Float64Array} + * @param {Float64Array} + * @return {Float64Array} + */ +function entrywisemulVector( a, b) { + var i; + const n = a.length; + var res = new Float64Array(n); + for ( i=0; i < n; i++) + res[i] = a[i] * b[i]; + return res; +} +/** + * @param {Matrix} + * @param {Matrix} + * @return {Matrix} + */ +function entrywisemulMatrix( A, B) { + var res = new Matrix(A.m,A.n, entrywisemulVector(A.val, B.val), true ); + return res; +} + + +function entrywisemul(a,b) { + var sa = size(a); + var sb = size(b); + if (typeof(a) != "number" && sa[0] == 1 && sa[1] == 1 ) + a = get(a, 0, 0); + if (typeof(b) != "number" && sb[0] == 1 && sb[1] == 1 ) + b = get(b, 0, 0); + + switch( type(a) ) { + case "number": + switch( type(b) ) { + case "number": + return a*b; + break; + case "Complex": + return mulComplexReal(b,a); + break; + case "vector": + return mulScalarVector(a,b); + break; + case "spvector": + return mulScalarspVector(a,b); + break; + case "ComplexVector": + return mulScalarComplexVector(b,a); + break; + case "matrix": + return mulScalarMatrix(a,b); + break; + case "spmatrix": + return mulScalarspMatrix(a,b); + break; + case "ComplexMatrix": + return mulScalarComplexMatrix(b,a); + break; + default: + return undefined; + break; + } + break; + case "vector": + switch( type(b) ) { + case "number": + return mulScalarVector(b,a); + break; + case "Complex": + return mulComplexVector(b,a); + break; + case "vector": + if ( a.length != b.length ) { + error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return entrywisemulVector(a,b); + break; + case "ComplexVector": + if ( a.length != b.length ) { + error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return entrywisemulComplexVectorVector(b,a); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return entrywisemulspVectorVector(b,a); + break; + case "matrix": + case "spmatrix": + case "ComplexMatrix": + error("Error in entrywisemul(a,B): a is a vector and B is a matrix."); + return undefined; + break; + default: + return undefined; + break; + } + break; + case "spvector": + switch( type(b) ) { + case "number": + return mulScalarspVector(b,a); + break; + case "vector": + if ( a.length != b.length ) { + error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return entrywisemulspVectorVector(a,b); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return entrywisemulspVectors(a,b); + break; + case "matrix": + error("Error in entrywisemul(a,B): a is a vector and B is a Matrix."); + return undefined; + break; + case "spmatrix": + error("Error in entrywisemul(a,B): a is a vector and B is a Matrix."); + return undefined; + break; + default: + return undefined; + break; + } + break; + case "matrix": + switch( type(b) ) { + case "number": + return mulScalarMatrix(b,a); + break; + case "Complex": + return mulComplexMatrix(b,a); + break; + case "vector": + case "spvector": + case "ComplexVector": + error("Error in entrywisemul(A,b): A is a Matrix and b is a vector."); + return undefined; + break; + case "matrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return entrywisemulMatrix(a,b); + break; + case "spmatrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return entrywisemulspMatrixMatrix(b,a); + break; + case "ComplexMatrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return entrywisemulComplexMatrixMatrix(b,a); + break; + default: + return undefined; + break; + } + break; + case "spmatrix": + switch( type(b) ) { + case "number": + return mulScalarspMatrix(b,a); + break; + case "vector": + error("Error in entrywisemul(A,b): A is a Matrix and b is a vector."); + return undefined; + break; + case "spvector": + error("Error in entrywisemul(A,b): A is a Matrix and b is a vector."); + return undefined; + break; + case "matrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return entrywisemulspMatrixMatrix(a,b); + break; + case "spmatrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return entrywisemulspMatrices(a,b); + break; + default: + return undefined; + break; + } + break; + case "ComplexVector": + switch( type(b) ) { + case "number": + return mulScalarComplexVector(b,a); + break; + case "Complex": + return mulComplexComplexVector(b,a); + break; + case "vector": + if ( a.length != b.length ) { + error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return entrywisemulComplexVectorVector(a,b); + break; + case "ComplexVector": + if ( a.length != b.length ) { + error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return entrywisemulComplexVectors(a,b); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in entrywisemul(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return entrywisemulComplexVectorspVector(a,b); + break; + case "matrix": + case "spmatrix": + case "ComplexMatrix": + error("Error in entrywisemul(a,B): a is a vector and B is a matrix."); + return undefined; + break; + default: + return undefined; + break; + } + break; + case "ComplexMatrix": + switch( type(b) ) { + case "number": + return mulScalarComplexMatrix(b,a); + break; + case "Complex": + return mulComplexComplexMatrix(b,a); + break; + case "vector": + case "spvector": + case "ComplexVector": + error("Error in entrywisemul(A,b): A is a Matrix and b is a vector."); + return undefined; + break; + case "matrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return entrywisemulComplexMatrixMatrix(a,b); + break; + case "spmatrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return entrywisemulComplexMatrixspMatrix(a,b); + break; + case "ComplexMatrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisemul(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return entrywisemulComplexMatrices(a,b); + break; + default: + return undefined; + break; + } + break; + default: + return undefined; + break; + } +} + + +/** SAXPY : y = y + ax + * @param {number} + * @param {Float64Array} + * @param {Float64Array} + */ +function saxpy ( a, x, y) { + const n = y.length; + for ( var i=0; i < n; i++) + y[i] += a*x[i]; +} +/** GAXPY : y = y + Ax + * @param {Matrix} + * @param {Float64Array} + * @param {Float64Array} + */ +function gaxpy ( A, x, y) { + const m = A.m; + const n = A.n; + var r = 0; + for ( var i=0; i < m; i++) { + y[i] += dot(A.val.subarray(r, r + n),x); + r += n; + } +} + +/** + * @param {Float64Array} + * @param {number} + * @return {Float64Array} + */ +function divVectorScalar( a, b) { + var i; + const n = a.length; + var res = new Float64Array(a); + for ( i=0; i < n; i++) + res[i] /= b; + return res; +} +/** + * @param {number} + * @param {Float64Array} + * @return {Float64Array} + */ +function divScalarVector ( a, b) { + var i; + const n = b.length; + var res = new Float64Array(n); + for ( i=0; i < n; i++) + res[i] = a / b[i]; + return res; +} +/** + * @param {Float64Array} + * @param {Float64Array} + * @return {Float64Array} + */ +function divVectors( a, b) { + var i; + const n = a.length; + var res = new Float64Array(a); + for ( i=0; i < n; i++) + res[i] /= b[i]; + return res; +} +/** + * @param {Matrix} + * @param {number} + * @return {Matrix} + */ +function divMatrixScalar( A, b) { + var res = new Matrix(A.m, A.n, divVectorScalar(A.val , b ), true); + return res; +} +/** + * @param {number} + * @param {Matrix} + * @return {Matrix} + */ +function divScalarMatrix( a, B) { + var res = new Matrix(B.m, B.n, divScalarVector(a, B.val ), true); + return res; +} +/** + * @param {Matrix} + * @param {Matrix} + * @return {Matrix} + */ +function divMatrices( A, B) { + var res = new Matrix(A.m, A.n, divVectors(A.val, B.val ), true); + return res; +} + +function entrywisediv(a,b) { + var ta = type(a); + var tb = type(b); + + switch(ta) { + case "number": + switch(tb) { + case "number": + return a/b; + break; + case "vector": + return divScalarVector(a,b); + break; + case "matrix": + return divScalarMatrix(a,b); + break; + case "spvector": + return divScalarspVector(a,b); + break; + case "spmatrix": + return divScalarspMatrix(a,b); + break; + default: + error("Error in entrywisediv(a,b): b must be a number, a vector or a matrix."); + return undefined; + } + break; + case "vector": + switch(tb) { + case "number": + return divVectorScalar(a,b); + break; + case "vector": + if ( a.length != b.length ) { + error("Error in entrywisediv(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return divVectors(a,b); + break; + case "spvector": + error("Error in entrywisediv(a,b): b is a sparse vector with zeros."); + break; + default: + error("Error in entrywisediv(a,B): a is a vector and B is a " + tb + "."); + return undefined; + } + break; + case "spvector": + switch(tb) { + case "number": + return mulScalarspVector(1/b, a); + break; + case "vector": + if ( a.length != b.length ) { + error("Error in entrywisediv(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return divVectorspVector(a,b); + break; + case "spvector": + error("Error in entrywisediv(a,b): b is a sparse vector with zeros."); + return undefined; + break; + default: + error("Error in entrywisediv(a,B): a is a vector and B is a " + tb + "."); + return undefined; + } + break; + case "matrix": + switch(tb) { + case "number": + return divMatrixScalar(a,b); + break; + case "matrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisediv(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return divMatrices(a,b); + break; + case "spmatrix": + error("Error in entrywisediv(A,B): B is a sparse matrix with zeros."); + return undefined; + break; + default: + error("Error in entrywisediv(A,b): a is a matrix and B is a " + tb + "."); + return undefined; + } + case "spmatrix": + switch(tb) { + case "number": + return mulScalarspMatrix(1/b,a); + break; + case "matrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in entrywisediv(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return divMatrixspMatrix(a,b); + break; + case "spmatrix": + error("Error in entrywisediv(A,B): B is a sparse matrix with zeros."); + return undefined; + break; + default: + error("Error in entrywisediv(A,b): a is a matrix and B is a " + tb + "."); + return undefined; + } + break; + default: + error("Error in entrywisediv(a,b): a must be a number, a vector or a matrix."); + return undefined; + break; + } +} + +function outerprodVectors(a, b, scalar) { + var i; + var j; + var ui; + const m = a.length; + const n = b.length; + var res = new Matrix(m,n); + if( arguments.length == 3 ) { + for (i=0; i< m; i++) + res.val.set( mulScalarVector(scalar*a[i], b), i*n); + } + else { + for (i=0; i< m; i++) + res.val.set( mulScalarVector(a[i], b), i*n); + } + return res; +} +function outerprod( u , v, scalar ) { + // outer product of two vectors : res = scalar * u * v^T + + if (typeof(u) == "number" ) { + if ( typeof(v) == "number" ) { + if ( arguments.length == 2 ) + return u*v; + else + return u*v*scalar; + } + else { + if ( arguments.length == 2 ) + return new Matrix(1,v.length, mulScalarVector(u, v), true ); + else + return new Matrix(1,v.length, mulScalarVector(u*scalar, v), true ); + } + } + if ( u.length == 1 ) { + if ( typeof(v) == "number" ) { + if ( arguments.length == 2 ) + return u[0]*v; + else + return u[0]*v*scalar; + } + else { + if ( arguments.length == 2 ) + return new Matrix(1,v.length, mulScalarVector(u[0], v) , true); + else + return new Matrix(1,v.length, mulScalarVector(u[0]*scalar, v), true ); + } + } + if (typeof(v) == "number" ) { + if (arguments.length == 2 ) + return mulScalarVector(v, u); + else + return mulScalarVector( scalar * v , u); + } + if ( v.length == 1) { + if ( arguments.length == 2 ) + return mulScalarVector(v[0], u); + else + return mulScalarVector( scalar * v[0] , u); + } + + if ( arguments.length == 2 ) + return outerprodVectors(u,v); + else + return outerprodVectors(u,v, scalar); +} +/** + * @param {number} + * @param {Float64Array} + * @return {Float64Array} + */ +function addScalarVector ( scalar, vec ) { + const n = vec.length; + var res = new Float64Array(vec); + for (var i = 0 ; i< n; i++) + res[i] += scalar ; + + return res; +} +/** + * @param {number} + * @param {Matrix} + * @return {Matrix} + */ +function addScalarMatrix(a, B ) { + return new Matrix(B.m, B.n, addScalarVector(a, B.val), true ); +} +/** + * @param {Float64Array} + * @param {Float64Array} + * @return {Float64Array} + */ +function addVectors(a,b) { + const n = a.length; + var c = new Float64Array(a); + for (var i=0; i < n; i++) + c[i] += b[i]; + return c; +} +/** + * @param {Matrix} + * @param {Matrix} + * @return {Matrix} + */ +function addMatrices(A,B) { + return new Matrix(A.m, A.n, addVectors(A.val, B.val) , true); +} +function add(a,b) { + + const ta = type(a); + const tb = type(b); + if ( ta == "number" && tb == "number" || ta == "string" || tb == "string") + return a + b; + else if ( ta == "number") { + switch(tb) { + case "Complex": + return addComplexReal(b,a); + break; + case "vector": + return addScalarVector(a,b); + break; + case "matrix": + return addScalarMatrix(a,b); + break; + case "spvector": + return addScalarspVector(a,b); + break; + case "spmatrix": + return addScalarspMatrix(a,b); + break; + case "ComplexVector": + return addScalarComplexVector(a,b); + break; + case "ComplexMatrix": + return addScalarComplexMatrix(a,b); + break; + default: + return undefined; + break; + } + } + else if ( tb == "number" ) { + switch(ta) { + case "Complex": + return addComplexReal(a,b); + break; + case "vector": + return addScalarVector(b,a); + break; + case "matrix": + return addScalarMatrix(b,a); + break; + case "spvector": + return addScalarspVector(b,a); + break; + case "spmatrix": + return addScalarspMatrix(b,a); + break; + case "ComplexVector": + return addScalarComplexVector(b,a); + break; + case "ComplexMatrix": + return addScalarComplexMatrix(b,a); + break; + default: + return undefined; + break; + } + } + else if ( ta == "vector" ) { + switch(tb) { + case "vector": + // vector addition + if ( a.length != b.length ) { + error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return addVectors(a,b); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return addVectorspVector(a,b); + break; + case "ComplexVector": + if ( a.length != b.length ) { + error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return addComplexVectorVector(b,a); + break; + case "matrix": + case "spmatrix": + default: + error("Error in add(a,B): a is a vector and B is a " + tb + "."); + return undefined; + break; + } + } + else if ( ta == "matrix" ) { + switch(tb) { + case "matrix": + // Matrix addition + if ( a.m != b.m || a.n != b.n ) { + error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return addMatrices(a,b); + break; + case "spmatrix": + // Matrix addition + if ( a.m != b.m || a.n != b.n ) { + error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return addMatrixspMatrix(a,b); + break; + case "ComplexMatrix": + // Matrix addition + if ( a.m != b.m || a.n != b.n ) { + error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return addComplexMatrixMatrix(b,a); + break; + case "vector": + case "spvector": + default: + error("Error in add(A,b): a is a matrix and B is a " + tb + "."); + return undefined; + break; + } + } + else if ( ta == "spvector" ) { + switch(tb) { + case "vector": + // vector addition + if ( a.length != b.length ) { + error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return addVectorspVector(b,a); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return addspVectors(a,b); + break; + case "matrix": + case "spmatrix": + default: + error("Error in add(a,B): a is a sparse vector and B is a " + tb + "."); + return undefined; + break; + } + } + else if ( ta == "spmatrix" ) { + switch(tb) { + case "matrix": + // Matrix addition + if ( a.m != b.m || a.n != b.n ) { + error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return addMatrixspMatrix(b,a); + break; + case "spmatrix": + // Matrix addition + if ( a.m != b.m || a.n != b.n ) { + error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return addspMatrices(a,b); + break; + case "vector": + case "spvector": + default: + error("Error in add(A,b): a is a sparse matrix and B is a " + tb + "."); + return undefined; + break; + } + } + else if ( ta == "ComplexVector" ) { + switch(tb) { + case "vector": + // vector addition + if ( a.length != b.length ) { + error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return addComplexVectorVector(a,b); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return addComplexVectorspVector(a,b); + break; + case "ComplexVector": + if ( a.length != b.length ) { + error("Error in add(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return addComplexVectors(b,a); + break; + case "matrix": + case "spmatrix": + default: + error("Error in add(a,B): a is a vector and B is a " + tb + "."); + return undefined; + break; + } + } + else if ( ta == "ComplexMatrix" ) { + switch(tb) { + case "matrix": + // Matrix addition + if ( a.m != b.m || a.n != b.n ) { + error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return addComplexMatrixMatrix(a,b); + break; + case "spmatrix": + // Matrix addition + if ( a.m != b.m || a.n != b.n ) { + error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return addComplexMatrixspMatrix(a,b); + break; + case "ComplexMatrix": + // Matrix addition + if ( a.m != b.m || a.n != b.n ) { + error("Error in add(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return addComplexMatrices(a,b); + break; + case "vector": + case "spvector": + default: + error("Error in add(A,b): a is a matrix and B is a " + tb + "."); + return undefined; + break; + } + } + else + return undefined; +} +/** + * @param {number} + * @param {Float64Array} + * @return {Float64Array} + */ +function subScalarVector ( scalar, vec ) { + const n = vec.length; + var res = new Float64Array(n); + for (var i = 0 ; i< n; i++) + res[i] = scalar - vec[i]; + + return res; +} +/** + * @param {Float64Array} + * @param {number} + * @return {Float64Array} + */ +function subVectorScalar ( vec, scalar ) { + const n = vec.length; + var res = new Float64Array(vec); + for (var i = 0 ; i< n; i++) + res[i] -= scalar; + + return res; +} +/** + * @param {number} + * @param {Matrix} + * @return {Matrix} + */ +function subScalarMatrix(a, B ) { + return new Matrix(B.m, B.n, subScalarVector(a, B.val), true ); +} +/** + * @param {Matrix} + * @param {number} + * @return {Matrix} + */ +function subMatrixScalar(B, a ) { + return new Matrix(B.m, B.n, subVectorScalar(B.val, a) , true); +} +/** + * @param {Float64Array} + * @param {Float64Array} + * @return {Float64Array} + */ +function subVectors(a,b) { + const n = a.length; + var c = new Float64Array(a); + for (var i=0; i < n; i++) + c[i] -= b[i]; + return c; +} +/** + * @param {Matrix} + * @param {Matrix} + * @return {Matrix} + */ +function subMatrices(A,B) { + return new Matrix(A.m, A.n, subVectors(A.val, B.val), true ); +} +function sub(a,b) { + + const ta = type(a); + const tb = type(b); + if ( ta == "number" && tb == "number" ) + return a - b; + else if ( ta == "number") { + switch(tb) { + case "Complex": + return addComplexReal(minusComplex(b),a); + break; + case "vector": + return subScalarVector(a,b); + break; + case "matrix": + return subScalarMatrix(a,b); + break; + case "spvector": + return subScalarspVector(a,b); + break; + case "spmatrix": + return subScalarspMatrix(a,b); + break; + default: + return undefined; + break; + } + } + else if ( tb == "number" ) { + switch(ta) { + case "Complex": + return addComplexReal(b,-a); + break; + case "vector": + return subVectorScalar (a, b); + break; + case "matrix": + return subMatrixScalar(a,b); + break; + case "spvector": + return addScalarspVector(-b,a); + break; + case "spmatrix": + return addScalarspMatrix(-b,a); + break; + default: + return undefined; + break; + } + } + else if ( ta == "vector" ) { + switch(tb) { + case "vector": + // vector substraction + if ( a.length != b.length ) { + error("Error in sub(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return subVectors(a,b); + break; + case "spvector": + // vector substraction + if ( a.length != b.length ) { + error("Error in sub(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return subVectorspVector(a,b); + break; + case "matrix": + case "spmatrix": + default: + error("Error in sub(a,B): a is a vector and B is a " + tb + "."); + return undefined; + break; + } + } + else if ( ta == "matrix" ) { + switch(tb) { + case "matrix": + // Matrix sub + if ( a.m != b.m || a.n != b.n ) { + error("Error in sub(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return subMatrices(a,b); + break; + case "spmatrix": + // Matrix addition + if ( a.m != b.m || a.n != b.n ) { + error("Error in sub(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return subMatrixspMatrix(a,b); + break; + case "vector": + case "spvector": + default: + error("Error in sub(A,b): A is a matrix and b is a " + tb + "."); + return undefined; + break; + } + } + else if ( ta == "spvector" ) { + switch(tb) { + case "vector": + if ( a.length != b.length ) { + error("Error in sub(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return subspVectorVector(a,b); + break; + case "spvector": + if ( a.length != b.length ) { + error("Error in sub(a,b): a.length = " + a.length + " != " + b.length + " = b.length."); + return undefined; + } + return subspVectors(a,b); + break; + case "matrix": + case "spmatrix": + default: + error("Error in sub(a,B): a is a sparse vector and B is a " + tb + "."); + return undefined; + break; + } + } + else if ( ta == "spmatrix" ) { + switch(tb) { + case "matrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in sub(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return subspMatrixMatrix(a,b); + break; + case "spmatrix": + if ( a.m != b.m || a.n != b.n ) { + error("Error in sub(A,B): size(A) = [" + a.m + "," + a.n + "] != [" + b.m + "," + b.n + "] = size(B)."); + return undefined; + } + return subspMatrices(a,b); + break; + case "vector": + case "spvector": + default: + error("Error in sub(A,b): a is a sparse matrix and B is a " + tb + "."); + return undefined; + break; + } + } + else + return undefined; +} + +function pow(a,b) { + var i; + const ta = type(a); + const tb = type(b); + + if ( ta == "number" && tb == "number" ) + return Math.pow(a, b); + else if ( ta == "number") { + if ( tb == "vector" ) { + var c = zeros(b.length); + if ( !isZero(a) ) { + for (i=0;i 0 && a.val.length < a.length ) + return 0; + else + return m; + break; + case "matrix": + return minMatrix(a); + break; + case "spmatrix": + var m = minVector(a.val); + if ( m > 0 && a.val.length < a.m * a.n ) + return 0; + else + return m; + break; + default: + return a; + break; + } + } + + var tb = type(b); + if (ta == "spvector" ) { + a = fullVector(a); + ta = "vector"; + } + if (ta == "spmatrix" ) { + a = fullMatrix(a); + ta = "matrix"; + } + if (tb == "spvector" ) { + b = fullVector(b); + tb = "vector"; + } + if (tb == "spmatrix" ) { + b = fullMatrix(b); + tb = "matrix"; + } + + if ( ta == "number" && tb == "number" ) + return Math.min(a,b); + else if ( ta == "number") { + if ( tb == "vector" ) + return minVectorScalar(b, a ) ; + else + return minMatrixScalar(b, a ) ; + } + else if ( tb == "number" ) { + if ( ta == "vector" ) + return minVectorScalar(a, b); + else { + // MAtrix , scalar + if ( b == 1) + return minMatrixRows(a); // return row vector of min of columns + else if ( b == 2 ) + return minMatrixCols(a); // return column vector of min of rows + else + return minMatrixScalar(a, b); + } + } + else if ( ta == "vector" ) { + if ( tb == "vector" ) + return minVectorVector(a,b); + else + return "undefined"; + } + else { + if ( tb == "matrix" ) + return minMatrixMatrix(a,b); + else + return "undefined"; + } +} + +// maximum +/** + * @param {Float64Array} + * @return {number} + */ +function maxVector( a ) { + const n = a.length; + var res = a[0]; + for (var i = 1; i < n ; i++) { + if ( a[i] > res) + res = a[i]; + } + return res; +} +/** + * @param {Matrix} + * @return {number} + */ +function maxMatrix( A ) { + return maxVector(A.val); +} +/** + * @param {Float64Array} + * @param {number} + * @return {Float64Array} + */ +function maxVectorScalar(vec, scalar ) { + const n = vec.length; + var res = new Float64Array(vec); + for (var i = 0; i < n ; i++) { + if ( scalar > vec[i]) + res[i] = scalar; + } + return res; +} +/** + * @param {Matrix} + * @param {number} + * @return {Matrix} + */ +function maxMatrixScalar(A, scalar ) { + return maxVectorScalar(A.val, scalar); +} +/** + * @param {Matrix} + * @return {Matrix} + */ +function maxMatrixRows( A ) { + const m = A.m; + const n = A.n; + var res = new Float64Array(A.val.subarray(0,n) ); + var j; + var r = n; + for ( var i=1; i < m; i++) { + for ( j = 0; j < n; j++) + if( A.val[r + j] > res[j]) + res[j] = A.val[r + j]; + r += n; + } + return new Matrix(1,n,res,true); +} +/** + * @param {Matrix} + * @return {Float64Array} + */ +function maxMatrixCols( A ) { + const m = A.m; + var res = new Float64Array(m); + var r = 0; + for ( var i=0; i < m; i++) { + res[i] = maxVector(A.val.subarray(r, r+A.n) ); + r += A.n; + } + return res; +} +/** + * @param {Float64Array} + * @param {Float64Array} + * @return {Float64Array} + */ +function maxVectorVector(a, b) { + var n = a.length; + var res = new Float64Array(a); + for (var i = 0; i < n ; i++) { + if ( b[i] > a[i]) + res[i] = b[i]; + } + return res; +} +/** + * @param {Matrix} + * @param {Matrix} + * @return {Matrix} + */ +function maxMatrixMatrix( A, B ) { + return new Matrix(A.m, A.n, maxVectorVector(A.val, B.val), true); +} +function max(a,b) { + var ta = type(a); + + if ( arguments.length == 1 ) { + switch( ta ) { + case "vector": + return maxVector(a); + break; + case "spvector": + var m = maxVector(a.val); + if ( m < 0 && a.val.length < a.length ) + return 0; + else + return m; + break; + case "matrix": + return maxMatrix(a); + break; + case "spmatrix": + var m = maxVector(a.val); + if ( m < 0 && a.val.length < a.m * a.n ) + return 0; + else + return m; + break; + default: + return a; + break; + } + } + + var tb = type(b); + if (ta == "spvector" ) { + a = fullVector(a); + ta = "vector"; + } + if (ta == "spmatrix" ) { + a = fullMatrix(a); + ta = "matrix"; + } + if (tb == "spvector" ) { + b = fullVector(b); + tb = "vector"; + } + if (tb == "spmatrix" ) { + b = fullMatrix(b); + tb = "matrix"; + } + + if ( ta == "number" && tb == "number" ) + return Math.max(a,b); + else if ( ta == "number") { + if ( tb == "vector" ) + return maxVectorScalar(b, a ) ; + else + return maxMatrixScalar(b, a ) ; + } + else if ( tb == "number" ) { + if ( ta == "vector" ) + return maxVectorScalar(a, b); + else { + // MAtrix , scalar + if ( b == 1) + return maxMatrixRows(a); // return row vector of max of columns + else if ( b == 2 ) + return maxMatrixCols(a); // return column vector of max of rows + else + return maxMatrixScalar(a, b); + } + } + else if ( ta == "vector" ) { + if ( tb == "vector" ) + return maxVectorVector(a,b); + else + return "undefined"; + } + else { + if ( tb == "matrix" ) + return maxMatrixMatrix(a,b); + else + return "undefined"; + } +} +/** + * @param {Matrix} + */ +function transposeMatrix ( A ) { + var i; + var j; + const m = A.m; + const n = A.n; + if ( m > 1 ) { + var res = zeros( n,m); + var Aj = 0; + for ( j=0; j< m;j++) { + var ri = 0; + for ( i=0; i < n ; i++) { + res.val[ri + j] = A.val[Aj + i]; + ri += m; + } + Aj += n; + } + return res; + } + else { + return A.val; + } +} +/** + * @param {Float64Array} + * @return {Matrix} + */ +function transposeVector ( a ) { + return new Matrix(1,a.length, a); +} +function transpose( A ) { + var i; + var j; + switch( type( A ) ) { + case "number": + return A; + break; + case "vector": + var res = new Matrix(1,A.length, A); + return res; // matrix with a single row + break; + case "spvector": + return transposespVector(A); + break; + case "ComplexVector": + var res = new ComplexMatrix(1,A.length, conj(A)); + return res; // matrix with a single row + break; + case "matrix": + return transposeMatrix(A); + break; + case "spmatrix": + return transposespMatrix(A); + break; + case "ComplexMatrix": + return transposeComplexMatrix(A); + break; + default: + return undefined; + break; + } +} + +/** + * @param {Matrix} + * @return {number} + */ +function det( A ) { + const n = A.n; + if ( A.m != n || typeof(A.m) =="undefined") + return undefined; + + if ( n == 2 ) { + return A.val[0]*A.val[3] - A.val[1]*A.val[2]; + } + else { + var detA = 0; + var i,j; + for ( i=0; i < n; i++ ) { + var proddiag = 1; + for ( j=0; j < n ; j++) + proddiag *= A.val[( (i+j)%n ) * n + j]; + + detA += proddiag; + } + for ( i=0; i < n; i++ ) { + var proddiag = 1; + for ( j=0; j < n ; j++) + proddiag *= A.val[( (i+n-1-j)%n ) * n + j]; + + detA -= proddiag; + } + } + return detA; +} +function trace ( A ) { + if ( type(A) == "matrix") { + var n = A.length; + if ( A.m != n ) + return "undefined"; + var res = 0; + for ( var i =0; i< n;i++) + res += A.val[i*n + i]; + return res; + } + else { + return undefined; + } +} +/** + * @param {Matrix} + * @return {Matrix} + */ +function triu ( A ) { + // return the upper triangular part of A + var i; + var j; + const n = A.n; + const m = A.m; + var res = zeros(m, n); + var im = m; + if ( n < m ) + im = n; + var r = 0; + for (i=0; i < im; i++) { + for ( j=i; j < n; j++) + res.val[r + j] = A.val[r + j]; + r += n; + } + return res; +} +/** + * @param {Matrix} + * @return {Matrix} + */ +function tril ( A ) { + // return the lower triangular part of A + var i; + var j; + const n = A.n; + const m = A.m; + var res = zeros(m, n); + var im = m; + if ( n < m ) + im = n; + var r = 0; + for (i=0; i < im; i++) { + for ( j=0; j <= i; j++) + res.val[r + j] = A.val[r + j]; + r += n; + } + if ( m > im ) { + for (i=im; i < m; i++) { + for ( j=0; j < n; j++) + res.val[r + j] = A.val[r + j]; + r += n; + } + } + return res; +} + +/** + * @param {Matrix} + * @return {boolean} + */ +function issymmetric ( A ) { + const m = A.m; + const n= A.n; + if ( m != n ) + return false; + + for (var i=0;i < m; i++) + for ( var j=0; j < n; j++) + if ( A.val[i*n+j] != A.val[j*n+i] ) + return false; + + return true; +} + +/** Concatenate matrices/vectors + * @param {Array} + * @param {boolean} + * @return {Matrix} + */ +function mat( elems, rowwise ) { + var k; + var concatWithNumbers = false; + var elemtypes = new Array(elems.length); + for ( k=0; k < elems.length; k++) { + elemtypes[k] = type(elems[k]); + if ( elemtypes[k] == "number" ) + concatWithNumbers = true; + } + + + if (typeof(rowwise ) == "undefined") { + // check if vector of numbers + if ( type(elems) == "vector" ) + return new Float64Array(elems); + + // check if 2D Array => toMatrix rowwise + var rowwise = true; + for (k=0; k < elems.length; k++) { + if ( !Array.isArray(elems[k] ) || elemtypes[k] == "vector" ) { + rowwise = false; + if ( elemtypes[k] == "string" ) + return elems; // received vector of strings => return it directly + } + } + } + + if ( elems.length == 0 ) { + return []; + } + + var m = 0; + var n = 0; + var i; + var j; + if ( rowwise ) { + var res = new Array( ) ; + + for ( k= 0; k return Array2D + return elems; + break; + } + } + if ( n == 1) { + var M = new Float64Array(res); + return M; + } + var M = new Matrix( m , n ) ; + var p = 0; + for (k=0; k < res.length ; k++) { + if(res[k].buffer) { + M.val.set( res[k], p); + p += res[k].length; + } + else { + for ( j=0; j < res[k].length; j++) + M.val[p+j] = res[k][j]; + p += res[k].length; + } + } + return M; + } + else { + // compute size + m = size(elems[0], 1); + for ( k= 0; k EPS ) + res[i] = 1; + } + return res; + break; + case "matrix": + res = new Matrix(a.m, a.n, isGreater(a.val, b), true ); + return res; + break; + default: + return (a>b?1:0); + } + } + else if (ta == tb) { + + switch( ta ) { + case "number": + return (a>b?1:0); + break; + case "vector": + res = new Float64Array(a.length); + for ( i=0; i EPS ) + res[i] = 1; + } + return res; + break; + case "matrix": + res = new Matrix(a.m, a.n, isGreater(a.val, b.val), true ); + return res; + break; + default: + return (a>b?1:0); + } + } + else + return "undefined"; +} +function isGreaterOrEqual( a, b) { + var i; + var j; + var res; + var ta = type(a); + var tb = type(b); + + if ( ta == "number" && tb != "number" ) + return isGreaterOrEqual(b,a); + + if( ta != "number" && tb == "number" ) { + // vector/matrix + scalar + switch( ta ) { + case "vector": + res = new Float64Array(a.length); + for ( i=0; i -EPS ) + res[i] = 1; + } + return res; + break; + case "matrix": + res = new Matrix(a.m, a.n, isGreaterOrEqual(a.val, b), true ); + return res; + break; + default: + return (a>=b?1:0); + } + } + else if ( ta == tb ) { + + switch( ta ) { + case "number": + return (a>=b); + break; + case "vector": + res = new Float64Array(a.length); + for ( i=0; i -EPS ) + res[i] = 1; + } + return res; + break; + case "matrix": + res = new Matrix(a.m, a.n, isGreaterOrEqual(a.val, b.val), true ); + return res; + break; + default: + return (a>=b?1:0); + } + } + else + return "undefined"; +} + +function isLower( a, b) { + var i; + var j; + var res; + var ta = type(a); + var tb = type(b); + + if ( ta == "number" && tb != "number" ) + return isLower(b,a); + + if( ta != "number" && tb == "number" ) { + // vector/matrix + scalar + switch( ta ) { + case "vector": + res = new Float64Array(a.length); + for ( i=0; i EPS ) + res[i] = 1; + } + return res; + break; + case "matrix": + res = new Matrix(a.m, a.n, isLower(a.val, b), true ); + return res; + break; + default: + return (a EPS ) + res[i] = 1; + } + return res; + break; + case "matrix": + res = new Matrix(a.m, a.n, isLower(a.val, b.val), true ); + return res; + break; + default: + return (a -EPS ) + res[i] = 1; + } + return res; + break; + case "matrix": + res = new Matrix(a.m, a.n, isLowerOrEqual(a.val, b), true ); + return res; + break; + default: + return (a<=b?1:0); + } + } + else if ( ta == tb ) { + + switch( ta ) { + case "number": + return (a<=b?1:0); + break; + case "vector": + res = new Float64Array(a.length); + for ( i=0; i -EPS ) + res[i] = 1; + } + return res; + break; + case "matrix": + res = new Matrix(a.m, a.n, isLowerOrEqual(a.val, b.val) , true); + return res; + break; + default: + return (a<=b?1:0); + } + } + else + return "undefined"; +} + + +function find( b ) { + // b is a boolean vector of 0 and 1. + // return the indexes of the 1's. + var i; + var n = b.length; + var res = new Array(); + for ( i=0; i < n; i++) { + if ( b[i] != 0 ) + res.push(i); + } + return res; +} +argmax = findmax; +function findmax( x ) { + // return the index of the maximum in x + var i; + + switch ( type(x)) { + case "number": + return 0; + break; + case "vector": + var idx = 0; + var maxi = x[0]; + for ( i= 1; i< x.length; i++) { + if ( x[i] > maxi ) { + maxi = x[i]; + idx = i; + } + } + return idx; + break; + case "spvector": + var maxi = x.val[0]; + var idx = x.ind[0]; + + for ( i= 1; i< x.val.length; i++) { + if ( x.val[i] > maxi ) { + maxi = x.val[i]; + idx = x.ind[i]; + } + } + if ( maxi < 0 && x.val.length < x.length ) { + idx = 0; + while ( x.ind.indexOf(idx) >= 0 && idx < x.length) + idx++; + } + return idx; + break; + default: + return "undefined"; + } + +} +argmin = findmin; +function findmin( x ) { + // return the index of the minimum in x + var i; + + switch ( type(x)) { + case "number": + return 0; + break; + case "vector": + var idx = 0; + var mini = x[0]; + for ( i= 1; i< x.length; i++) { + if ( x[i] < mini ) { + mini = x[i]; + idx = i; + } + } + return idx; + break; + case "spvector": + var mini = x.val[0]; + var idx = x.ind[0]; + + for ( i= 1; i< x.val.length; i++) { + if ( x.val[i] < mini ) { + mini = x.val[i]; + idx = x.ind[i]; + } + } + if ( mini > 0 && x.val.length < x.length ) { + idx = 0; + while ( x.ind.indexOf(idx) >= 0 && idx < x.length) + idx++; + } + return idx; + break; + default: + return "undefined"; + } + +} + +/** + * @param {Float64Array} + * @param {boolean} + * @param {boolean} + * @return {Float64Array|Array} + */ +function sort( x, decreasingOrder , returnIndexes) { + // if returnIndexes = true : replace x with its sorted version + // otherwise return a sorted copy without altering x + + if ( typeof(decreasingOrder) == "undefined") + var decreasingOrder = false; + if ( typeof(returnIndexes) == "undefined") + var returnIndexes = false; + + var i; + var j; + var tmp; + + const n = x.length; + if ( returnIndexes ) { + var indexes = range(n); + for ( i=0; i < n - 1; i++) { + if ( decreasingOrder ) + j = findmax( get ( x, range(i,n) ) ) + i ; + else + j = findmin( get ( x, range(i,n) ) ) + i; + + if ( i!=j) { + tmp = x[i]; + x[i] = x[j]; + x[j] = tmp; + + tmp = indexes[i]; + indexes[i] = indexes[j]; + indexes[j] = tmp; + } + } + return indexes; + } + else { + var xs = vectorCopy(x); + for ( i=0; i < n - 1; i++) { + if ( decreasingOrder ) + j = findmax( get ( xs, range(i,n) ) ) + i; + else + j = findmin( get ( xs, range(i,n) ) ) + i; + + if ( i!=j) { + tmp = xs[i]; + xs[i] = xs[j]; + xs[j] = tmp; + } + } + return xs; + } +} + +/// Stats +/** + * @param {Float64Array} + * @return {number} + */ +function sumVector ( a ) { + var i; + const n = a.length; + var res = a[0]; + for ( i=1; i< n; i++) + res += a[i]; + return res; +} +/** + * @param {Matrix} + * @return {number} + */ +function sumMatrix ( A ) { + return sumVector(A.val); +} +/** + * @param {Matrix} + * @return {Matrix} + */ +function sumMatrixRows( A ) { + var i; + var j; + const m = A.m; + const n = A.n; + var res = new Float64Array(n); + var r = 0; + for ( i=0; i< m; i++) { + for (j=0; j < n; j++) + res[j] += A.val[r + j]; + r += n; + } + return new Matrix(1,n,res, true); // return row vector +} +/** + * @param {Matrix} + * @return {Float64Array} + */ +function sumMatrixCols( A ) { + const m = A.m; + var res = new Float64Array(m); + var r = 0; + for ( var i=0; i < m; i++) { + for (var j=0; j < A.n; j++) + res[i] += A.val[r + j]; + r += A.n; + } + return res; +} +function sum( A , sumalongdimension ) { + + switch ( type( A ) ) { + case "vector": + if ( arguments.length == 1 || sumalongdimension == 1 ) { + return sumVector(A); + } + else { + return vectorCopy(A); + } + break; + case "spvector": + if ( arguments.length == 1 || sumalongdimension == 1 ) + return sumVector(A.val); + else + return A.copy(); + break; + + case "matrix": + if( arguments.length == 1 ) { + return sumMatrix( A ) ; + } + else if ( sumalongdimension == 1 ) { + return sumMatrixRows( A ); + } + else if ( sumalongdimension == 2 ) { + return sumMatrixCols( A ); + } + else + return undefined; + break; + case "spmatrix": + if( arguments.length == 1 ) { + return sumVector( A.val ) ; + } + else if ( sumalongdimension == 1 ) { + return sumspMatrixRows( A ); + } + else if ( sumalongdimension == 2 ) { + return sumspMatrixCols( A ); + } + else + return undefined; + break; + default: + return A; + break; + } +} +/** + * @param {Float64Array} + * @return {number} + */ +function prodVector ( a ) { + var i; + const n = a.length; + var res = a[0]; + for ( i=1; i< n; i++) + res *= a[i]; + return res; +} +/** + * @param {Matrix} + * @return {number} + */ +function prodMatrix ( A ) { + return prodVector(A.val); +} +/** + * @param {Matrix} + * @return {Matrix} + */ +function prodMatrixRows( A ) { + var i; + var j; + const m = A.m; + const n = A.n; + var res = new Float64Array(A.row(0)); + var r = n; + for ( i=1; i< m; i++) { + for (j=0; j < n; j++) + res[j] *= A.val[r + j]; + r += A.n; + } + return new Matrix(1,n,res, true); // return row vector +} +/** + * @param {Matrix} + * @return {Float64Array} + */ +function prodMatrixCols( A ) { + const m = A.m; + var res = new Float64Array(m); + var r = 0; + for ( var i=0; i < m; i++) { + res[i] = A.val[r]; + for (var j=1; j < A.n; j++) + res[i] *= A.val[r + j]; + r += A.n; + } + return res; +} +function prod( A , prodalongdimension ) { + + switch ( type( A ) ) { + case "vector": + if ( arguments.length == 1 || prodalongdimension == 1 ) + return prodVector(A); + else + return vectorCopy(A); + break; + case "spvector": + if ( arguments.length == 1 || prodalongdimension == 1 ) { + if ( A.val.length < A.length ) + return 0; + else + return prodVector(A.val); + } + else + return A.copy(); + break; + case "matrix": + if( arguments.length == 1 ) { + return prodMatrix( A ) ; + } + else if ( prodalongdimension == 1 ) { + return prodMatrixRows( A ); + } + else if ( prodalongdimension == 2 ) { + return prodMatrixCols( A ); + } + else + return undefined; + break; + case "spmatrix": + if( arguments.length == 1 ) { + if ( A.val.length < A.m * A.n ) + return 0; + else + return prodVector( A.val ) ; + } + else if ( prodalongdimension == 1 ) { + return prodspMatrixRows( A ); + } + else if ( prodalongdimension == 2 ) { + return prodspMatrixCols( A ); + } + else + return undefined; + break; + default: + return A; + break; + } +} + +function mean( A , sumalongdimension ) { + + switch ( type( A ) ) { + case "vector": + if ( arguments.length == 1 || sumalongdimension == 1 ) { + return sumVector(A) / A.length; + } + else { + return vectorCopy(A); + } + break; + case "spvector": + if ( arguments.length == 1 || sumalongdimension == 1 ) + return sumVector(A.val) / A.length; + else + return A.copy(); + break; + + case "matrix": + if( arguments.length == 1 ) { + return sumMatrix( A ) / ( A.m * A.n); + } + else if ( sumalongdimension == 1 ) { + return mulScalarMatrix( 1/A.m, sumMatrixRows( A )); + } + else if ( sumalongdimension == 2 ) { + return mulScalarVector( 1/A.n, sumMatrixCols( A )) ; + } + else + return undefined; + break; + case "spmatrix": + if( arguments.length == 1 ) { + return sumVector( A.val ) / ( A.m * A.n); + } + else if ( sumalongdimension == 1 ) { + return mulScalarMatrix(1/A.m, sumspMatrixRows(A)); + } + else if ( sumalongdimension == 2 ) { + return mulScalarVector(1/A.n, sumspMatrixCols(A)); + } + else + return undefined; + break; + default: + return A; + break; + } +} + +function variance(A, alongdimension ) { + // variance = sum(A^2)/n - mean(A)^2 + if ( arguments.length > 1 ) + var meanA = mean(A, alongdimension); + else + var meanA = mean(A); + + switch ( type( A ) ) { + case "number": + return 0; + break; + case "vector": + if ( arguments.length == 1 || alongdimension == 1 ) { + var res = ( dot(A,A) / A.length ) - meanA*meanA; + return res ; + } + else { + return zeros(A.length); + } + break; + case "spvector": + if ( arguments.length == 1 || alongdimension == 1 ) { + var res = ( dot(A.val,A.val) / A.length ) - meanA*meanA; + return res ; + } + else + return zeros(A.length); + + break; + + case "matrix": + case "spmatrix": + if( typeof(alongdimension) == "undefined" ) { + var res = (sum(entrywisemul(A,A)) / (A.m * A.n ) ) - meanA*meanA; + return res; + } + else if ( alongdimension == 1 ) { + // var of columns + var res = sub( entrywisediv(sum(entrywisemul(A,A),1) , A.length ) , entrywisemul(meanA,meanA) ); + return res; + } + else if ( alongdimension == 2 ) { + // sum all columns, result is column vector + res = sub( entrywisediv(sum(entrywisemul(A,A),2) , A.n ) , entrywisemul(meanA,meanA) ); + return res; + } + else + return undefined; + break; + default: + return undefined; + } +} + +function std(A, alongdimension) { + if ( arguments.length > 1 ) + return sqrt(variance(A,alongdimension)); + else + return sqrt(variance(A)); +} + +/** + * Covariance matrix C = X'*X ./ X.m + * @param {Matrix|Float64Array|spVector} + * @return {Matrix|number} + */ +function cov( X ) { + switch ( type( X ) ) { + case "number": + return 0; + break; + case "vector": + var mu = mean(X); + return ( dot(X,X) / X.length - mu*mu); + break; + case "spvector": + var mu = mean(X); + return ( dot(X.val,X.val) / X.length - mu*mu); + break; + case "matrix": + var mu = mean(X,1).row(0); + return divMatrixScalar(xtx( subMatrices(X, outerprod(ones(X.m), mu ) ) ), X.m); + break; + case "spmatrix": + var mu = mean(X,1).row(0); + return divMatrixScalar(xtx( subspMatrixMatrix(X, outerprod(ones(X.m), mu ) ) ), X.m); + break; + default: + return undefined; + } +} +/** + * Compute X'*X + * @param {Matrix} + * @return {Matrix} + */ +function xtx( X ) { + const N = X.m; + const d = X.n; + + var C = new Matrix(d,d); + for (var i=0; i < N; i++) { + var xi= X.row(i); + for(var k = 0; k < d; k++) { + var xik = xi[k]; + for (var j=k; j < d; j++) { + C.val[k*d + j] += xik * xi[j]; + } + } + } + // Symmetric lower triangular part: + for(var k = 0; k < d; k++) { + var kd = k*d; + for (var j=k; j < d; j++) + C.val[j*d+k] = C.val[kd+j]; + } + return C; +} + +function norm( A , sumalongdimension ) { + // l2-norm (Euclidean norm) of vectors or Frobenius norm of matrix + var i; + var j; + switch ( type( A ) ) { + case "number": + return Math.abs(A); + break; + case "vector": + if ( arguments.length == 1 || sumalongdimension == 1 ) { + return Math.sqrt(dot(A,A)); + } + else + return abs(A); + break; + case "spvector": + if ( arguments.length == 1 || sumalongdimension == 1 ) { + return Math.sqrt(dot(A.val,A.val)); + } + else + return abs(A); + break; + case "matrix": + if( arguments.length == 1 ) { + return Math.sqrt(dot(A.val,A.val)); + } + else if ( sumalongdimension == 1 ) { + // norm of columns, result is row vector + const n = A.n; + var res = zeros(1, n); + var r = 0; + for (i=0; i< A.m; i++) { + for(j=0; j 0, consider values < epsilon as 0 + + var epsilon = EPS; + if ( arguments.length == 3 ) + epsilon = epsilonarg; + + var i; + var j; + switch ( type( A ) ) { + case "number": + return (Math.abs(A) > epsilon); + break; + case "vector": + if ( arguments.length == 1 || sumalongdimension == 1 ) { + return norm0Vector(A, epsilon); + } + else + return isGreater(abs(a), epsilon); + break; + case "spvector": + if ( arguments.length == 1 || sumalongdimension == 1 ) { + return norm0Vector(A.val, epsilon); + } + else + return isGreater(abs(a), epsilon); + break; + case "matrix": + if( arguments.length == 1 ) { + return norm0Vector(A.val, epsilon); + } + else if ( sumalongdimension == 1 ) { + // norm of columns, result is row vector + var res = zeros(1, A.n); + for (i=0; i< A.m; i++) { + for(j = 0; j < A.n; j++) + if ( Math.abs(A[i*A.n + j]) > epsilon ) + res.val[j]++; + } + return res; + } + else if ( sumalongdimension == 2 ) { + // norm of rows, result is column vector + var res = zeros(A.m); + for (i=0; i< A.m; i++) { + for(j = 0; j < A.n; j++) + if ( Math.abs(A[i*A.n + j]) > epsilon ) + res[i]++; + } + return res; + } + else + return undefined; + break; + case "spmatrix": + if( arguments.length == 1 ) { + return norm0Vector(A.val, epsilon); + } + else if ( sumalongdimension == 1 ) { + // norm of columns, result is row vector + var res = zeros(1, A.n); + if ( A.rowmajor ) { + for ( var k=0; k < A.val.length; k++) + if (Math.abs(A.val[k]) > epsilon) + res.val[A.cols[k]] ++; + } + else { + for ( var i=0; i epsilon) + res[A.rows[k]]++; + } + return res; + } + else + return undefined; + break; + default: + return undefined; + } +} +/** + * @param {Float64Array} + * @param {number} + * @return {number} + */ +function norm0Vector( x, epsilon ) { + const n = x.length; + var res = 0; + for (var i=0; i < n; i++) + if ( Math.abs(x[i]) > epsilon ) + res++; + return res; +} + +///////////////////////////////////////////: +// Linear systems of equations +/////////////////////////////////////// + +function solve( A, b ) { + /* Solve the linear system Ax = b */ + + var tA = type(A); + + if ( tA == "vector" || tA == "spvector" || (tA == "matrix" && A.m == 1) ) { + // One-dimensional least squares problem: + var AtA = mul(transpose(A),A); + var Atb = mul(transpose(A), b); + return Atb / AtA; + } + + if ( tA == "spmatrix" ) { + /*if ( A.m == A.n ) + return spsolvecg(A, b); // assume A is positive definite + else*/ + return spcgnr(A, b); + } + + if( type(b) == "vector" ) { + if ( A.m == A.n ) + return solveGaussianElimination(A, b) ; + else + return solveWithQRcolumnpivoting(A, b) ; + } + else + return solveWithQRcolumnpivotingMultipleRHS(A, b) ; // b is a matrix +} +/** + * Solve the linear system Ax = b given the Cholesky factor L of A + * @param {Matrix} + * @param {Float64Array} + * @return {Float64Array} + */ +function cholsolve ( L, b ) { + var z = forwardsubstitution(L, b); + var x = backsubstitution(transposeMatrix(L), z); + return x; +} + +/** + * @param {Matrix} + * @param {Float64Array} + * @return {Float64Array} + */ +function solveWithQRfactorization ( A, b ) { + const m = A.length; + const n = A.n; + var QRfact = qr(A); + var R = QRfact.R; + var beta = QRfact.beta; + + var btmp = vectorCopy(b); + var j; + var i; + var k; + var v; + + var smallb; + + for (j=0;jn + if ( m > n ) { + j = n-1; + + v = get(R, range(j,m), j) ; // get Householder vectors + v[0] = 1; + // b(j:m) = (I - beta v v^T ) * b(j:m) + smallb = get(btmp, range(j,m) ); + set ( btmp, range(j,m), sub ( smallb , mul( beta[j] * mul( v, smallb) , v ) ) ); + + } + + // Solve R x = b with backsubstitution (R is upper triangular, well it is not really here because we use the lower part to store the vectors v): + return backsubstitution ( R , get ( btmp, range(n)) ); + + +// return backsubstitution ( get ( R, range(n), range(n) ) , rows ( btmp, range(1,n)) ); +// we can spare the get and copy of R : backsubstitution will only use this part anyway +} + +/** + * @param {Matrix} + * @param {Float64Array} + * @return {Float64Array} + */ +function backsubstitution ( U, b ) { + // backsubstitution to solve a linear system U x = b with upper triangular U + + const n = b.length; + var j = n-1; + var x = zeros(n); + + if ( ! isZero(U.val[j*n+j]) ) + x[j] = b[j] / U.val[j*n+j]; + + j = n-2; + if ( !isZero(U.val[j*n+j]) ) + x[j] = ( b[j] - U.val[j*n+n-1] * x[n-1] ) / U.val[j*n+j]; + + for ( j=n-3; j >= 0 ; j-- ) { + if ( ! isZero(U.val[j*n+j]) ) + x[j] = ( b[j] - dot( U.row(j).subarray(j+1,n) , x.subarray(j+1,n) ) ) / U.val[j*n+j]; + } + + // solution + return x; +} +/** + * @param {Matrix} + * @param {Float64Array} + * @return {Float64Array} + */ +function forwardsubstitution ( L, b ) { + // forward substitution to solve a linear system L x = b with lower triangular L + + const n = b.length; + var j; + var x = zeros(n); + + if ( !isZero(L.val[0]) ) + x[0] = b[0] / L.val[0]; + + if ( ! isZero(L.val[n+1]) ) + x[1] = ( b[1] - L.val[n] * x[0] ) / L.val[n+1]; + + for ( j=2; j < n ; j++ ) { + if ( ! isZero(L.val[j*n+j]) ) + x[j] = ( b[j] - dot( L.row(j).subarray(0,j) , x.subarray(0,j) ) ) / L.val[j*n+j]; + } + + // solution + return x; +} +/** + * @param {Matrix} + * @param {Float64Array} + * @return {Float64Array} + */ +function solveWithQRcolumnpivoting ( A, b ) { + + var m; + var n; + var R; + var V; + var beta; + var r; + var piv; + if ( type( A ) == "matrix" ) { + // Compute the QR factorization + m = A.m; + n = A.n; + var QRfact = qr(A); + R = QRfact.R; + V = QRfact.V; + beta = QRfact.beta; + r = QRfact.rank; + piv = QRfact.piv; + } + else { + // we get the QR factorization in A + R = A.R; + r = A.rank; + V = A.V; + beta = A.beta; + piv = A.piv; + m = R.m; + n = R.n; + } + + var btmp = vectorCopy(b); + var j; + var i; + var k; + + var smallb; + // b = Q' * b + for (j=0;j < r; j++) { + + // b(j:m) = (I - beta v v^T ) * b(j:m) + smallb = get(btmp, range(j,m) ); + + set ( btmp, range(j,m), sub ( smallb , mul( beta[j] * mul( V[j], smallb) , V[j] ) ) ); + } + // Solve R x = b with backsubstitution + var x = zeros(n); + + if ( r > 1 ) { + set ( x, range(0,r), backsubstitution ( R , get ( btmp, range(r)) ) ); + // note: if m < n, backsubstitution only uses n columns of R. + } + else { + x[0] = btmp[0] / R.val[0]; + } + + // and apply permutations + for ( j=r-1; j>=0; j--) { + if ( piv[j] != j ) { + var tmp = x[j] ; + x[j] = x[piv[j]]; + x[piv[j]] = tmp; + } + } + return x; + +} +/** + * @param {Matrix} + * @param {Matrix} + * @return {Matrix} + */ +function solveWithQRcolumnpivotingMultipleRHS ( A, B ) { + + var m; + var n; + var R; + var V; + var beta; + var r; + var piv; + if ( type( A ) == "matrix" ) { + // Compute the QR factorization + m = A.m; + n = A.n; + var QRfact = qr(A); + R = QRfact.R; + V = QRfact.V; + beta = QRfact.beta; + r = QRfact.rank; + piv = QRfact.piv; + } + else { + // we get the QR factorization in A + R = A.R; + r = A.rank; + V = A.V; + beta = A.beta; + piv = A.piv; + m = R.m; + n = R.n; + } + + var btmp = matrixCopy(B); + var j; + var i; + var k; + + var smallb; + // B = Q' * B + for (j=0;j < r; j++) { + + // b(j:m) = (I - beta v v^T ) * b(j:m) + smallb = get(btmp, range(j,m), [] ); + + set ( btmp, range(j,m), [], sub ( smallb , mul(mul( beta[j], V[j]), mul( transpose(V[j]), smallb) ) ) ); + } + // Solve R X = B with backsubstitution + var X = zeros(n,m); + + if ( r > 1 ) { + for ( j=0; j < m; j++) + set ( X, range(0,r), j, backsubstitution ( R , get ( btmp, range(r), j) ) ); + // note: if m < n, backsubstitution only uses n columns of R. + } + else { + set(X, 0, [], entrywisediv(get(btmp, 0, []) , R.val[0]) ); + } + + // and apply permutations + for ( j=r-1; j>=0; j--) { + if ( piv[j] != j ) { + swaprows(X, j, piv[j]); + } + } + return X; + +} + +function solveGaussianElimination(Aorig, borig) { + + // Solve square linear system Ax = b with Gaussian elimination + + var i; + var j; + var k; + + var A = matrixCopy( Aorig ).toArrayOfFloat64Array(); // useful to quickly switch rows + var b = vectorCopy( borig ); + + const m = Aorig.m; + const n = Aorig.n; + if ( m != n) + return undefined; + + // Set to zero small values... ?? + + for (k=0; k < m ; k++) { + + // Find imax = argmax_i=k...m |A_i,k| + var imax = k; + var Aimaxk = Math.abs(A[imax][k]); + for (i=k+1; i Aimaxk ) { + imax = i; + Aimaxk = Aik; + } + } + if ( isZero( Aimaxk ) ) { + console.log("** Warning in solve(A,b), A is square but singular, switching from Gaussian elimination to QR method."); + return solveWithQRcolumnpivoting(A,b); + } + + if ( imax != k ) { + // Permute the rows + var a = A[k]; + A[k] = A[imax]; + A[imax] = a; + var tmpb = b[k]; + b[k] = b[imax]; + b[imax] = tmpb; + } + var Ak = A[k]; + + // Normalize row k + var Akk = Ak[k]; + b[k] /= Akk; + + //Ak[k] = 1; // not used afterwards + for ( j=k+1; j < n; j++) + Ak[j] /= Akk; + + if ( Math.abs(Akk) < 1e-8 ) { + console.log("** Warning in solveGaussianElimination: " + Akk + " " + k + ":" + m ); + } + + // Substract the kth row from others to get 0s in kth column + var Aik ; + var bk = b[k]; + for ( i=0; i< m; i++) { + if ( i != k ) { + var Ai = A[i]; + Aik = Ai[k]; + for ( j=k+1; j < n; j++) { // Aij = 0 with j < k and Aik = 0 after this operation but is never used + Ai[j] -= Aik * Ak[j]; + } + b[i] -= Aik * bk; + } + } + } + + // Solution: + return b; +} + +function inv( M ) { + if ( typeof(M) == "number" ) + return 1/M; + + // inverse matrix with Gaussian elimination + + var i; + var j; + var k; + const m = M.length; + const n = M.n; + if ( m != n) + return "undefined"; + + // Make extended linear system: + var A = matrixCopy(M) ; + var B = eye(n); + + for (k=0; k < m ; k++) { + var kn = k*n; + + // Find imax = argmax_i=k...m |A_i,k| + var imax = k; + var Aimaxk = Math.abs(A.val[imax*n + k]); + for (i=k+1; i Aimaxk ) { + imax = i; + Aimaxk = Math.abs(A.val[i * n + k]); + } + } + if ( Math.abs( Aimaxk ) < 1e-12 ) { + return "singular"; + } + + if ( imax != k ) { + // Permute the rows + swaprows(A, k, imax); + swaprows(B,k, imax); + } + + // Normalize row k + var Akk = A.val[kn + k]; + for ( j=0; j < n; j++) { + A.val[kn + j] /= Akk; + B.val[kn + j] /= Akk; + } + + if ( Math.abs(Akk) < 1e-8 ) + console.log("!! Warning in inv(): " + Akk + " " + k + ":" + m ); + + // Substract the kth row from others to get 0s in kth column + var Aik ; + for ( i=0; i< m; i++) { + if ( i != k ) { + var ri = i*n; + Aik = A.val[ri+k]; + if ( ! isZero(Aik) ) { + for ( j=0; j < n; j++) { + A.val[ri + j] -= Aik * A.val[kn+j]; + B.val[ri + j] -= Aik * B.val[kn+j] ; + } + } + } + } + } + + // Solution: + return B; +} + +function chol( A ) { + // Compute the Cholesky factorization A = L L^T with L lower triangular + // for a positive definite and symmetric A + // returns L or undefined if A is not positive definite + const n = A.m; + if ( A.n != n) { + error("Cannot compute the cholesky factorization: the matrix is not square."); + return undefined; + } + const n2= n*n; + const Aval = A.val; + var L = new Float64Array(n2); + + var i,j; + // first column = A(:,0) / sqrt(L(0,0) + var sqrtLjj = Math.sqrt(Aval[0]); + for ( i=0; i < n2 ; i+=n) { // i = i*n = ptr to row i + L[i] = Aval[i] / sqrtLjj; + } + // other colums + j = 1; + var jn = n; + while ( j < n && !isNaN(sqrtLjj)) { + for ( i = jn; i < n2; i+=n ) { // i = i*n + var Lij = Aval[i+j]; + for ( var k=0; k < j; k++) { + Lij -= L[jn + k] * L[i + k]; + } + if (i == jn) + sqrtLjj = Math.sqrt(Lij); + + L[i +j] = Lij / sqrtLjj; + } + j++; + jn += n; + } + if ( isNaN(sqrtLjj) ) + return undefined; // not positive definite + else + return new Matrix(n,n,L,true); +} + +function ldlsymmetricpivoting ( Aorig ) { + // LDL factorization for symmetric matrices + var A = matrixCopy( Aorig ); + var n = A.length; + if ( A.m != n ) { + error("Error in ldl(): the matrix is not square."); + return undefined; + } + var k; + var piv = zeros(n); + var alpha; + var v; + + for ( k=0; k < n-1; k++) { + + piv[k] = findmax(get(diag(A ), range(k,n) )); + swaprows(A, k, piv[k] ); + swapcols(A, k, piv[k] ); + alpha = A.val[k*n + k]; + v = getCols ( A, [k]).subarray(k+1,n); + + for ( var i=k+1;i < n; i++) + A.val[i*n + k] /= alpha; + + set( A, range(k+1,n),range(k+1,n), sub (get(A,range(k+1,n), range(k+1,n)), outerprod(v,v, 1/alpha))); + + } + + // Make it lower triangular + for (var j=0; j < n-1; j++) { + for (var k=j+1; k < n ; k++) + A.val[j*n + k] = 0; + } + return {L: A, piv: piv}; +} +/** + * @param {Float64Array} + * @return {{v: Float64Array, beta: number}} + */ +function house ( x ) { + // Compute Houselholder vector v such that + // P = (I - beta v v') is orthogonal and Px = ||x|| e_1 + + const n = x.length; + var i; + var mu; + var beta; + var v = zeros(n); + var v0; + var sigma ; + + var x0 = x[0]; + var xx = dot(x,x); + + // sigma = x(2:n)^T x(2:n) + sigma = xx -x0*x0; + + if ( isZero( sigma ) ) { + // x(2:n) is zero => v=[1,0...0], beta = 0 + beta = 0; + v[0] = 1; + } + else { + mu = Math.sqrt(xx); // norm(x) ; //Math.sqrt( x0*x0 + sigma ); + if ( x0 < EPS ) { + v0 = x0 - mu; + } + else { + v0 = -sigma / (x0 + mu); + } + + beta = 2 * v0 * v0 / (sigma + v0 * v0 ); + + // v = [v0,x(2:n)] / v0 + v[0] = 1; + for ( i=1; i< n; i++) + v[i] = x[i] / v0; + } + + return { "v" : v , "beta" : beta}; +} +/** + * @param {Matrix} + * @return {{Q: (Matrix|undefined), R: Matrix, beta: Float64Array} + */ +function qroriginal( A, compute_Q ) { + // QR factorization based on Householder reflections WITHOUT column pivoting + // A with m rows and n cols; m >= n + + // test with A = [[12,-51,4],[6,167,-68],[-4,24,-41]] + // then R = [ [14 -21 -14 ], [ -3, 175, -70], [2, -0.75, 35]] + + var m = A.length; + var n = A.n; + if ( n > m) + return "QR factorization unavailable for n > m."; + + var i; + var j; + var k; + var householder; + var R = matrixCopy(A); + var beta = zeros(n); + var outer; + var smallR; + var Q; + var V = new Array(); // store householder vectors + + + for ( j=0; j < n - 1 ; j++) { + householder = house( get( R, range(j,m), j) ); + // R(j:m,j:n) = ( I - beta v v' ) * R(j:m,j:n) = R - (beta v) (v'R) + smallR = get(R, range(j,m), range(j,n) ); + set ( R, range(j,m), range(j,n) , subMatrices ( smallR , outerprodVectors( householder.v, mulMatrixVector( transposeMatrix(smallR), householder.v) , householder.beta ) ) ) ; + + V[j] = householder.v; + beta[j] = householder.beta; + + } + // Last iteration only if m > n: if m=n, (I - beta v v' ) = 1 => R(n,n) is unchanged + if ( m > n ) { + j = n-1; + smallR = get( R, range(j,m), j) + householder = house( smallR ); + // R(j:m,n) = ( I - beta v v' ) * R(j:m, n) = R(j:m,n) - (beta v) (v'R(j:m,n) ) = Rn - ( beta *(v' * Rn) )* v + set ( R, range(j,m), n-1 , subVectors ( smallR , mulScalarVector( dot( householder.v, smallR ) * householder.beta, householder.v ) ) ) ; + + V[j] = vectorCopy(householder.v); + beta[j] = householder.beta; + + } + + if ( compute_Q ) { + var r; + if ( typeof( compute_Q ) == "number") { + // compute only first r columns of Q + r = compute_Q; + Q = eye(m,r); + } + else { + Q = eye(m); + r = m; + } + var smallQ; + var nmax = n-1; + if ( m<=n) + nmax = n-2; + if ( nmax >= r ) + nmax = r-1; + + for ( j=nmax; j >=0; j--) { + smallQ = get(Q, range(j,m), range(j,r) ); + + if ( r > 1 ) { + if ( j == r-1) + set ( Q, range(j,m), [j] , subVectors ( smallQ , mulScalarVector( dot( smallQ, V[j]) * beta[j], V[j] ) ) ); + else + set ( Q, range(j,m), range(j,r), sub ( smallQ , outerprod( V[j], mul( transpose( smallQ), V[j]), beta[j] ) ) ); + } + else + Q = subVectors ( smallQ , mulScalarVector( dot( smallQ, V[j]) * beta[j], V[j] ) ); + } + } + + return {"Q" : Q, "R" : R, "beta" : beta }; +} + +/** + * @param {Matrix} + * @return {{Q: (Matrix|undefined), R: Matrix, V: Array, beta: Float64Array, piv: Float64Array, rank: number} + */ +function qr( A, compute_Q ) { + // QR factorization with column pivoting AP = QR based on Householder reflections + // A with m rows and n cols; m >= n (well, it also works with m < n) + // piv = vector of permutations : P = P_rank with P_j = identity with swaprows ( j, piv(j) ) + + // Implemented with R transposed for faster computations on rows instead of columns + + /* TEST + A = [[12,-51,4],[6,167,-68],[-4,24,-41]] + QR = qr(A) + QR.R + + + */ + const m = A.m; + const n = A.n; + + /* + if ( n > m) + return "QR factorization unavailable for n > m."; + */ + + var i; + var j; + + var householder; + var R = transpose(A);// transposed for faster implementation + var Q; + + var V = new Array(); // store householder vectors in this list (not a matrix) + var beta = zeros(n); + var piv = zeros(n); + + var smallR; + + var r = -1; // rank estimate -1 + + var normA = norm(A); + var normR22 = normA; + var Rij; + + const TOL = 1e-5; + var TOLnormR22square = TOL * normA; + TOLnormR22square *= TOLnormR22square; + + var tau = 0; + var k = 0; + var c = zeros (n); + for ( j=0; j < n ; j++) { + var Rj = R.val.subarray(j*R.n,j*R.n + R.n); + c[j] = dot(Rj,Rj); + if ( c[j] > tau ) { + tau = c[j]; + k = j; + } + } + + var updateR = function (r, v, beta) { + // set ( R, range(r,n), range(r,m) , subMatrices ( smallR , outerprodVectors( mulMatrixVector( smallR, householder.v), householder.v, householder.beta ) ) ) ; + // most of the time is spent here... + var i,j,l; + var m_r = m-r; + for ( i=r; i < n; i++) { + var smallRiv = 0; + var Ri = i*m + r; // = i * R.n + r + var Rval = R.val.subarray(Ri,Ri+m_r); + for ( l = 0 ; l < m_r ; l ++) + smallRiv += Rval[l] * v[l]; //smallRiv += R.val[Ri + l] * v[l]; + smallRiv *= beta ; + for ( j=0; j < m_r ; j ++) { + Rval[j] -= smallRiv * v[j]; // R.val[Ri + j] -= smallRiv * v[j]; + } + } + }; + + // Update c + var updateC = function(r) { + var j; + for (j=r+1; j < n; j++) { + var Rjr = R.val[j*m + r]; + c[j] -= Rjr * Rjr; + } + + // tau, k = max ( c[r+1 : n] ) + k=r+1; + tau = c[r+1]; + for ( j=r+2; j tau ) { + tau = c[j]; + k = j; + } + } + }; + + // Compute norm of residuals + var computeNormR22 = function(r) { + //normR22 = norm(get ( R, range(r+1,n), range(r+1,m), ) ); + var normR22 = 0; + var i = r+1; + var ri = i*m; + var j; + while ( i < n && normR22 <= TOLnormR22square ) { + for ( j=r+1; j < m; j++) { + var Rij = R.val[ri + j]; + normR22 += Rij*Rij; + } + i++; + ri += m; + } + return normR22; + } + + + while ( tau > EPS && r < n-1 && normR22 > TOLnormR22square ) { + + r++; + + piv[r] = k; + swaprows ( R, r, k); + c[k] = c[r]; + c[r] = tau; + + if ( r < m-1) { + householder = house( R.val.subarray(r*R.n + r,r*R.n + m) ); // house only reads vec so subarray is ok + } + else { + householder.v = [1]; + householder.beta = 0; + //smallR = R[m-1][m-1]; + } + + if (r < n-1) { + // smallR is a matrix + updateR(r, householder.v, householder.beta); + } + else { + // smallR is a row vector (or a number if m=n): + if ( r < m-1) { + updateR(r, householder.v, householder.beta); + /* + var r_to_m = range(r,m); + smallR = get(R, r, r_to_m); + set ( R, r , r_to_m, sub ( smallR , transpose(mul( householder.beta * mul( smallR, householder.v) ,householder.v ) )) ) ;*/ + } + else { + //var smallRnumber = R.val[(m-1)*R.n + m-1]; // beta is zero, so no update + //set ( R, r , r, sub ( smallRnumber , transpose(mul( householder.beta * mul( smallRnumber, householder.v) ,householder.v ) )) ) ; + } + } + + // Store householder vectors and beta + V[r] = vectorCopy( householder.v ); + beta[r] = householder.beta; + + if ( r r+1) + nmax = r-1; + for ( j=nmax; j >=0; j--) { + if ( j == m-1 ) { + Q.val[j*m+j] -= beta[j] * V[j][0] * V[j][0] * Q.val[j*m+j]; + } + else { + var j_to_m = range(j,m); + smallQ = get(Q, j_to_m, j_to_m );// matrix + set ( Q, j_to_m, j_to_m, subMatrices ( smallQ , outerprodVectors( V[j], mulMatrixVector( transposeMatrix(smallQ), V[j]), beta[j] ) ) ); + } + } + } + + return {"Q" : Q, "R" : transpose(R), "V": V, "beta" : beta, "piv" : piv, "rank" : r+1 }; +} + +function qrRnotTransposed( A, compute_Q ) { + // QR factorization with column pivoting AP = QR based on Householder reflections + // A with m rows and n cols; m >= n (well, it also works with m < n) + // piv = vector of permutations : P = P_rank with P_j = identity with swaprows ( j, piv(j) ) + + // original implementation working on columns + + /* TEST + A = [[12,-51,4],[6,167,-68],[-4,24,-41]] + QR = qr(A) + QR.R + + + */ + var m = A.m; + var n = A.n; + + /* + if ( n > m) + return "QR factorization unavailable for n > m."; + */ + + var i; + var j; + + var householder; + var R = matrixCopy(A); + var Q; + + var V = new Array(); // store householder vectors in this list (not a matrix) + var beta = zeros(n); + var piv = zeros(n); + + var smallR; + + var r = -1; // rank estimate -1 + + var normA = norm(A); + var normR22 = normA; + + var TOL = 1e-6; + + var tau = 0; + var k = 0; + var c = zeros (n); + for ( j=0; j < n ; j++) { + var Aj = getCols ( A, [j]); + c[j] = dot(Aj, Aj); + if ( c[j] > tau ) { + tau = c[j]; + k = j; + } + } + + while ( tau > EPS && r < n-1 && normR22 > TOL * normA ) { + + r++; + + piv[r] = k; + swapcols ( R, r, k); + c[k] = c[r]; + c[r] = tau; + + if ( r < m-1) { + householder = house( get( R, range(r,m), r) ); + smallR = get(R, range(r,m), range(r,n) ); + } + else { + householder.v = [1]; + householder.beta = 0; + smallR = R[m-1][m-1]; + } + + if (r < n-1) { + // smallR is a matrix + set ( R, range(r,m), range(r,n) , subMatrices ( smallR , outerprodVectors( householder.v, mulMatrixVector( transposeMatrix(smallR), householder.v) , householder.beta ) ) ) ; + } + else { + // smallR is a vector (or a number if m=n): + set ( R, range(r,m), r , sub ( smallR , mul( householder.beta * mul( smallR, householder.v) ,householder.v ) ) ) ; + } + + // Store householder vectors and beta + if ( m > r+1 ) + V[r] = vectorCopy( householder.v ); + beta[r] = householder.beta; + + if ( r tau ) { + tau = c[j]; + k = j; + } + } + + // stopping criterion for rank estimation + if ( r < m-1 ) { + //normR22 = norm(get ( R, range(r+1,m),range(r+1,n) ) ); + normR22 = 0; + for ( i=r+1; i < m; i++) { + for ( j=r+1; j < n; j++) { + Rij = R[i][j]; + normR22 += Rij*Rij; + } + } + normR22 = Math.sqrt(normR22); + } + else + normR22 = 0; + } + } + + if ( compute_Q ) { + Q = eye(m); + var smallQ; + var nmax = r; + if ( m>r+1) + nmax = r-1; + for ( j=nmax; j >=0; j--) { + if ( j == m-1 ) { + Q.val[j*m+j] -= beta[j] * V[j][0] * V[j][0] * Q.val[j*m+j]; + } + else { + smallQ = get(Q, range(j,m), range(j,m) ); + set ( Q, range(j,m), range(j,m) , subMatrices ( smallQ , outerprodVectors( V[j], mulMatrixVector( transposeMatrix(smallQ), V[j]), beta[j] ) ) ); + } + } + + } + + return {"Q" : Q, "R" : R, "V": V, "beta" : beta, "piv" : piv, "rank" : r+1 }; +} + +/** Conjugate gradient method for solving the symmetyric positive definite system Ax = b + * @param{{Matrix|spMatrix}} + * @param{Float64Array} + * @return{Float64Array} + */ +function solvecg ( A, b) { + if( A.type == "spmatrix" ) + return spsolvecg(A,b); + else + return solvecgdense(A,b); +} + +/** Conjugate gradient method for solving the symmetyric positive definite system Ax = b + * @param{Matrix} + * @param{Float64Array} + * @return{Float64Array} + */ +function solvecgdense ( A, b) { +/* +TEST +A = randn(2000,1000) +x = randn(1000) +b = A*x + 0.01*randn(2000) +tic() +xx = solve(A,b) +t1 = toc() +ee = norm(A*xx - b) +tic() +xh=solvecg(A'*A, A'*b) +t2 = toc() +e = norm(A*xh - b) +*/ + + const n = A.n; + const m = A.m; + + var x = randn(n); //vectorCopy(x0); + var r = subVectors(b, mulMatrixVector(A, x)); + var rhoc = dot(r,r); + const TOL = 1e-8; + var delta2 = TOL * norm(b); + delta2 *= delta2; + + // first iteration: + var p = vectorCopy(r); + var w = mulMatrixVector(A,p); + var mu = rhoc / dot(p, w); + saxpy( mu, p, x); + saxpy( -mu, w, r); + var rho_ = rhoc; + rhoc = dot(r,r); + + var k = 1; + + var updateP = function (tau, r) { + for ( var i=0; i < m; i++) + p[i] = r[i] + tau * p[i]; + } + + while ( rhoc > delta2 && k < n ) { + updateP(rhoc/rho_, r); + w = mulMatrixVector(A,p); + mu = rhoc / dot(p, w); + saxpy( mu, p, x); + saxpy( -mu, w, r); + rho_ = rhoc; + rhoc = dot(r,r); + k++; + } + return x; +} +/** Conjugate gradient normal equation residual method for solving the rectangular system Ax = b + * @param{{Matrix|spMatrix}} + * @param{Float64Array} + * @return{Float64Array} + */ +function cgnr ( A, b) { + if( A.type == "spmatrix" ) + return spcgnr(A,b); + else + return cgnrdense(A,b); +} +/** Conjugate gradient normal equation residual method for solving the rectangular system Ax = b + * @param{Matrix} + * @param{Float64Array} + * @return{Float64Array} + */ +function cgnrdense ( A, b) { +/* +TEST +A = randn(2000,1000) +x = randn(1000) +b = A*x + 0.01*randn(2000) +tic() +xx = solve(A,b) +t1 = toc() +ee = norm(A*xx - b) +tic() +xh=cgnr(A, b) +t2 = toc() +e = norm(A*xh - b) +*/ + + const n = A.n; + const m = A.m; + + var x = randn(n); // vectorCopy(x0); + var At = transposeMatrix(A); + var r = subVectors(b, mulMatrixVector(A, x)); + const TOL = 1e-8; + var delta2 = TOL * norm(b); + delta2 *= delta2; + + // first iteration: + var z = mulMatrixVector(At, r); + var rhoc = dot(z,z); + var p = vectorCopy(z); + var w = mulMatrixVector(A,p); + var mu = rhoc / dot(w, w); + saxpy( mu, p, x); + saxpy( -mu, w, r); + z = mulMatrixVector(At, r); + var rho_ = rhoc; + rhoc = dot(z,z); + + var k = 1; + + var updateP = function (tau, z) { + for ( var i=0; i < m; i++) + p[i] = z[i] + tau * p[i]; + } + + while ( rhoc > delta2 && k < n ) { + updateP(rhoc/rho_, z); + w = mulMatrixVector(A,p); + mu = rhoc / dot(w, w); + saxpy( mu, p, x); + saxpy( -mu, w, r); + z = mulMatrixVector(At, r); + rho_ = rhoc; + rhoc = dot(z,z); + k++; + } + return x; +} + +/** Lanczos algorithm + * @param{Matrix} + */ +function lanczos ( A, q1 ) { + + const maxIters = 300; + const TOL = EPS * norm(A); + const n = A.n; + var i; + var k = 0; + var w = vectorCopy(q1); + var v = mulMatrixVector(A, w); + var alpha = dot(w,v); + saxpy(-alpha, w, v); + beta = norm(b); + + while ( beta > TOL && k < maxIters ) { + + for ( i=0; i < n; i++) { + var t = w[i]; + w[i] = v[i] / beta; + v[i] = -beta / t; + } + + var Aw = mulMatrixVector(A,w); + + for ( i=0; i < n; i++) + v[i] += Aw[i]; + + alpha = dot(w,v); + saxpy(-alpha,w,v); + beta = norm(v); + k++; + } +} + +/** + * @param{Matrix} + * @param{boolean} + * @return{Matrix} + */ +function tridiagonalize( A, returnQ ) { + // A : a square and symmetric matrix + // T = Q A Q' , where T is tridiagonal and Q = (H1 ... Hn-2)' is the product of Householder transformations. + // if returnQ, then T overwrites A + var k; + const n = A.length; + var T; + var Q; + var Pk; + if ( returnQ ) { + T = A; + Q = eye(n); + var beta = []; + var V = []; + } + else + T = matrixCopy(A); + var p; + var w; + var vwT; + var normTkp1k; + var householder; + + for (k=0; k < n-2; k++) { + Tkp1k = get ( T, range(k+1, n), k); + Tkp1kp1 = get ( T, range(k+1,n), range(k+1, n)); + + householder = house ( Tkp1k ); + p = mulScalarVector( householder.beta , mulMatrixVector( Tkp1kp1, householder.v ) ); + w = subVectors ( p, mulScalarVector( 0.5*householder.beta * dot(p, householder.v ), householder.v) ); + + /* + T[k+1][k] = norm ( Tkp1k ); + T[k][k+1] = T[k+1][k]; + */ + // make T really tridiagonal: the above does not modify the other entries to set them to 0 + normTkp1k = zeros(n-k-1); + normTkp1k[0] = norm ( Tkp1k ); + set ( T, k, range(k+1,n ), normTkp1k ); + set ( T, range(k+1,n), k, normTkp1k); + + vwT = outerprodVectors(householder.v,w); + set ( T, range(k+1,n), range(k+1, n), subMatrices( subMatrices ( Tkp1kp1, vwT) , transpose(vwT)) ); + + if ( returnQ ) { + V[k] = householder.v; + beta[k] = householder.beta; + } + } + if ( returnQ ) { + var updateQ = function(j, v, b) { + // Q = Q - b* v (Q'v)' + //smallQ = get(Q, range(j,n), range(j,n) );// matrix + //set ( Q, range(j,n), range(j,n) , subMatrices ( smallQ , outerprodVectors( V[k], mulMatrixVector( transposeMatrix(smallQ), V[k]), beta[k] ) ) ); + var i,k; + var Qtv = zeros(n-j); + var n_j = n-j; + for ( i=0; i=0; k--) { + updateQ(k+1,V[k], beta[k]); + } + return Q; + } + else + return T; +} +function givens(a,b,Gi,Gk,n) { + // compute a Givens rotation: + var c; + var s; + var tau; + var G; + + // Compute c and s + if ( b == 0) { + c = 1; + s = 0; + } + else { + if ( Math.abs(b) > Math.abs(a) ) { + tau = -a / b; + s = 1 / Math.sqrt(1+tau*tau); + c = s*tau; + } + else { + tau = -b / a; + c = 1 / Math.sqrt(1+tau*tau); + s = c * tau; + } + } + + if ( arguments.length == 5 ) { + // Build Givens matrix G from c and s: + G = eye(n) ; + G.val[Gi*n+Gi] = c; + G.val[Gi*n+Gk] = s; + G.val[Gk*n+Gi] = -s; + G.val[Gk*n+Gk] = c; + return G; + } + else { + return [c,s]; + } + +} +/** + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @param {Matrix} + */ +function premulGivens ( c, s, i, k, A) { + // apply a Givens rotation to A : A([i,k],:) = G' * A([i,k],:) + // with G = givens (a,b,i,k) and [c,s]=givens(a,b) + // NOTE: this modifies A + + const n = A.n; + var j; + const ri = i*n; + const rk = k*n; + var t1; + var t2; + for ( j=0; j < n; j++) { + t1 = A.val[ri + j]; + t2 = A.val[rk + j]; + A.val[ri + j] = c * t1 - s * t2; + A.val[rk + j] = s * t1 + c * t2; + } +} +/** + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @param {Matrix} + */ +function postmulGivens ( c, s, i, k, A) { + // apply a Givens rotation to A : A(:, [i,k]) = A(:, [i,k]) * G + // with G = givens (a,b,i,k) and [c,s]=givens(a,b) + // NOTE: this modifies A + + const m = A.length; + var j; + var t1; + var t2; + var rj = 0; + for ( j=0; j < m; j++) { + t1 = A.val[rj + i]; + t2 = A.val[rj + k]; + A.val[rj + i] = c * t1 - s * t2; + A.val[rj + k] = s * t1 + c * t2; + rj += A.n; + } +} + +function implicitSymQRWilkinsonShift( T , computeZ) { + // compute T = Z' T Z + // if computeZ: return {T,cs} such that T = Z' T Z with Z = G1.G2... + // and givens matrices Gk of parameters cs[k] + + const n = T.length; + const rn2 = n*(n-2); + const rn1 = n*(n-1); + + const d = ( T.val[rn2 + n-2] - T.val[rn1 + n-1] ) / 2; + const t2 = T.val[rn1 + n-2] * T.val[rn1 + n-2] ; + const mu = T.val[rn1 + n-1] - t2 / ( d + Math.sign(d) * Math.sqrt( d*d + t2) ); + var x = T.val[0] - mu; // T[0][0] + var z = T.val[n]; // T[1][0] + var cs; + if ( computeZ) + var csArray = new Array(n-1); + //var Z = eye(n); + + var k; + for ( k = 0; k < n-1; k++) { + /* + G = givens(x,z, k, k+1, n); + T = mul(transpose(G), mul(T, G) ); // can do this much faster + if ( computeZ ) { + Z = mul(Z, G ); + } + */ + cs = givens(x,z); + postmulGivens(cs[0], cs[1], k, k+1, T); + premulGivens(cs[0], cs[1], k, k+1, T); + if( computeZ ) + csArray[k] = [cs[0], cs[1]]; + //postmulGivens(cs[0], cs[1], k, k+1, Z); + + if ( k < n-2 ) { + var r = n*(k+1) + k; + x = T.val[r]; + z = T.val[r + n]; // [k+2][k]; + } + } + if ( computeZ) { + return {"T": T, "cs": csArray} ; +// return {"T": T, "Z": Z} ; + } + else + return T; +} + +function eig( A , computeEigenvectors ) { + // Eigendecomposition of a symmetric matrix A (QR algorithm) + + var Q; + var D; + if ( computeEigenvectors ) { + D = matrixCopy(A); + Q = tridiagonalize( D, true ); + } + else { + D = tridiagonalize( A ); + } + + var q; + var p; + const n = A.length; + var i; + + const TOL = 1e-12; //10 * EPS; + + do { + for ( i=0; i= n-1 ) + q = n; + } + + // find smallest p such that D[p:q][p:q] is unreduced ( without zeros on subdiagonal?) + p = -1; + var zerosOnSubdiagonal ; + do { + p++; + zerosOnSubdiagonal = false; + k=p; + while (k z0 ) + z0 = zi; + } + + /* + // polynomial evaluation and counting sign changes (original method) + var polya = function (x,a,b,n) { + var pr_2 = 1; + var pr_1 = a[0] - x; + var pr; + var signchanges = 0; + if ( pr_1 < EPS ) + signchanges = 1; + + var r; + for ( r = 1; r < n ; r++) { + pr = (a[r] - x) * pr_1 - b[r-1] * b[r-1] * pr_2; + + if ( Math.abs(pr) < EPS || (pr > 0 && pr_1 < 0 ) || (pr < 0) && (pr_1 > 0) ) + signchanges ++; + + pr_2 = pr_1; + pr_1 = pr; + } + return signchanges; + }; + */ + + // ratio of polynomials evaluation and counting sign changes + // (modification discussed in Barth et al., 1967 for better stability due to pr ~ 0 in the above) + var polyq = function (x,a,b,n) { + var qi_1 = a[0] - x; + var qi; + var signchanges = 0; + if ( qi_1 < EPS ) + signchanges = 1; + + var i; + for ( i = 1; i < n ; i++) { + qi = (a[i] - x) - b[i-1] * b[i-1] / qi_1; + + if ( qi < EPS ) + signchanges ++; + + if ( Math.abs(qi) < EPS ) + qi_1 = EPS; + else + qi_1 = qi; + } + return signchanges; + }; + + + // Start bisection + const TOL = 1e-10; + var lambda = zeros(K); + var xu = entrywisemul(z0,ones(K)); // upper bounds on lambdas + y = y0; + var n_lowerthan_x;// nb of eigenvalues lower than x + for ( var k = 1; k <= K ; k++ ) { + // k is the number of desired eigenvalues in this sweep + + z = xu[k-1]; + //y=y; from previous sweep + + // find the (n-k+1)th eigenvalue + while ( Math.abs(z - y) > TOL*(Math.abs(y) + Math.abs(z)) ) { + x = (y+z)/2; + n_lowerthan_x = polyq(x,a,b,n); + + if(n_lowerthan_x >= k ) + z = x; // enough eigenvalues below x, decrease upper bound to x + else + y = x; // not enough ev below x, increase lower bound to x + + // update boudns on other lambdas + for ( var j=k+1; j <= K; j++) + if ( n_lowerthan_x >= j ) + xu[j-1] = x; + + } + lambda[k-1] = (y+z)/2; + } + //return lambda; + + // Compute eigenvectors: XXX can be faster by using inverse iteration on the tridiagonal matrix + // with faster system solving + + var u = eigenvector( A, lambda[0] ); + var U = mat([u],false); + + for ( k = 1; k < K; k++) { + // deal with too close eigenvalues + var perturbtol = 10 * Math.max(EPS, Math.abs(EPS * lambda[k-1])); + if ( lambda[k] < lambda[k-1] + perturbtol ) + lambda[k] = lambda[k-1] + perturbtol; + + u = eigenvector( A, lambda[k] ); + U = mat([U, u], false ); + U = qroriginal( U, U.n ).Q; // orthogonalize + } + + + return {U: U, V: lambda}; +} + + +function bidiagonalize( A, computeU, thinU , computeV ) { + // B = U' A V , where B is upper bidiagonal + + var j; + const m = A.length; + const n = A.n; + var B; + B = matrixCopy( A ); + + var householder; + + if ( computeU ) { + if ( thinU ) { + var U = eye(m,n); + var nU = n; + } + else { + var U = eye(m); + var nU = m; + } + } + if ( computeV ) { + var V = eye(n); + } + + + var updateB1 = function (j, v, beta) { + // B = B - (beta v) ( v'* B) = B-outer(beta v, B'*v) + //Bjmjn = get ( B, range(j,m), range(j, n)); + //set ( B, range(j,m), range(j,n), sub ( Bjmjn , outerprod ( householder.v, mul(transpose(Bjmjn), householder.v), householder.beta) ) ); + + var i,k; + var Btv = zeros(n-j); + var n_j = n-j; + var m_j = m-j; + for ( i=0; i=0; j--) { + if (j=0; j--) { + updateU(j,hv[j], hb[j]); + } + } + + if ( computeU && computeV ) { + return { "U" : U, "V": V, "B": B}; + } + else if (computeV ) + return { "V": V, "B": B}; + else if (computeU) + return { "U" : U, "B": B}; + else + return B; +} + + +function GolubKahanSVDstep ( B, i, j, m, n, computeUV ) { + // Apply GolubKahanSVDstep to B(i:i+m, j:j+n) + // Note: working on Utrans + if (type ( B ) != "matrix" ) + return B; + + if ( n < 2 ) + return B; + + const rn2 = (i+n-2)*B.n + j; + const dm = B.val[rn2 + n-2]; + const fm = B.val[rn2 + n-1]; + var fm_1 ; + if ( n>2) + fm_1 = B.val[(i+n-3)*B.n + j + n-2]; + else + fm_1 = 0; + + const dn = B.val[(i+n-1)*B.n + j + n-1]; + + const d = ( dm*dm + fm_1*fm_1 - dn*dn - fm*fm ) / 2; + const t2 = dm*fm * dm*fm; + const mu = dn*dn+fm*fm - t2 / ( d + Math.sign(d) * Math.sqrt( d*d + t2) ); + + var k; + + //var B0 = getCols ( B, [0]); + //var B1 = getCols ( B, [1]) ; + //var y = mul( B0, B0 ) - mu; + //var z = mul( B0, B1 ); + var y = - mu; + var z = 0.0; + var r0 = i*B.n + j; + for ( k = 0; k< n; k++) { + y += B.val[r0] * B.val[r0]; + z += B.val[r0] * B.val[r0+1]; + r0 += B.n; + } + + + var G; + var cs; + + var postmulgivens = function ( c, s, k1, k2) { + // apply a Givens rotation to a subset of rows of B : B(i:i+m, [k1,k2]) = B(i:i+m, [k1,k2]) * G + var jj; + var t1; + var t2; + var rj = i*B.n + j; + for ( jj=0; jj < m; jj++) { + t1 = B.val[rj + k1]; + t2 = B.val[rj + k2]; + B.val[rj + k1] = c * t1 - s * t2; + B.val[rj + k2] = s * t1 + c * t2; + rj += B.n; + } + } + var premulgivens = function ( c, s, k1, k2) { + // apply a Givens rotation to a subset of cols of B : B([k1,k2],j:j+n) = G' * B([k1,k2],j:j+n) + var jj; + const ri = (i+k1)*B.n + j; + const rk = (i+k2)*B.n + j; + var t1; + var t2; + for ( jj=0; jj < n; jj++) { + t1 = B.val[ri + jj]; + t2 = B.val[rk + jj]; + B.val[ri + jj] = c * t1 - s * t2; + B.val[rk + jj] = s * t1 + c * t2; + } + } + + if ( computeUV) { + //var U = eye(m); + //var V = eye(n); + var csU = new Array(n-1); + var csV = new Array(n-1); + } + + for ( k = 0; k < n-1 ; k++) { + cs = givens(y,z); + postmulgivens(cs[0],cs[1], k, k+1); + + if ( computeUV ) { + csV[k] = [cs[0], cs[1]]; + // postmulGivens(cs[0],cs[1], k, k+1, V); + } + + + y = B.val[(i+k)*B.n + j + k]; + z = B.val[(i+k+1)*B.n + j + k]; + + cs = givens(y,z); + premulgivens(cs[0],cs[1], k, k+1); + + if ( computeUV ) { + csU[k] = [cs[0], cs[1]]; + //premulGivens(cs[0],cs[1], k, k+1, U); + } + + if ( k < n-2 ) { + y = B.val[(i+k)*B.n + j + k+1]; + z = B.val[(i+k)*B.n + j + k+2]; + } + + } + + if ( computeUV) + return {csU: csU, csV: csV}; +} + +function svd( A , computeUV ) { +/* TEST: +A=[ [-149,-50,-154],[537,180,546],[-27,-9,-25]] +s=svd(A) +should return [ 817.7597, 2.4750, 0.0030] +*/ + + if ( type(A) == "vector" || (type(A) == "matrix" && A.n == 1) ) { + return { "U" : matrixCopy(A), "S" : ones(1,1), "V" : ones(1,1), "s" : [1] }; + } + if ( A.m == 1) { + return { "U" : ones(1,1), "S" : ones(1,1), "V" : transpose(A), "s" : [1] }; + } + + + var i; + var m = A.length; + var n = A.n; + + + var Atransposed = false; + if ( n > m ) { + Atransposed = true; + var At = transposeMatrix(A); + n = m; + m = At.length; + } + + var computeU = false; + var computeV = false; + var thinU = false; + if ( typeof( computeUV) != "undefined" && computeUV!==false) { + + if ( computeUV === "full" ) { + computeU = true; + computeV = true; + thinU = false; + } + else if (computeUV === true || computeUV === "thin" ) { + computeU = true; + computeV = true; + thinU = true; + } + else if ( typeof(computeUV) == "string") { + if ( computeUV.indexOf("U") >=0 ) + computeU = true; + if ( computeUV.indexOf("V") >=0 ) + computeV = true; + if ( computeUV.indexOf("thin") >=0 ) + thinU = true; + } + var UBV; + if ( Atransposed ) { + var tmp = computeU; + computeU = computeV; + computeV = tmp; + UBV = bidiagonalize( At, computeU, thinU, computeV ); + } + else + UBV = bidiagonalize( A, computeU, thinU, computeV ); + + if ( computeU ) { + var U = transpose(UBV.U);//Utrans + } + else + var U = undefined; + + if( computeV ) { + var V = UBV.V; + var Vt = transposeMatrix(V); + } + else + var V = undefined; + + var B = UBV.B; + } + else { + if ( Atransposed ) + var B = bidiagonalize( At, false, false, false ); + else + var B = bidiagonalize( matrixCopy(A), false, false, false ); + } + + var B22; + var U22; + var V22; + var cs; + + var q; + var p; + var k; + + const TOL = 1e-11; + + do { + + for ( i=0; i TOL ) || (Math.abs(B.val[(n-2)*B.n + n-1]) > TOL ) ) { + q = 0; + } + else { + q = 1; + while ( q < n-1 && Math.abs( B.val[(n-q-1)*B.n + n-q-2] ) < TOL && Math.abs( B.val[(n-q-2)*B.n + n-q-1] ) < TOL ) { + q++; + } + if ( q >= n-1 ) + q = n; + } + + // find smallest p such that B[p:q][p:q] has no zeros on superdiag + p=n-q-1; + while ( p > 0 && ! isZero ( B.val[(p-1)*B.n + p] ) ) + p--; + + if ( q < n ) { + var DiagonalofB22isZero = -1; + for ( k=p; k< n-q-1 ; k++) { + if ( Math.abs( B.val[k*B.n + k] ) < TOL ) { + DiagonalofB22isZero = k; + break; + } + } + if ( DiagonalofB22isZero >= 0 ) { + + if ( DiagonalofB22isZero < n-q-1 ) { + // Zero B(k,k+1) and entire row k... + for (j=DiagonalofB22isZero+1; j < n; j++) { + + cs = givens(- B.val[j*B.n + j] , B.val[DiagonalofB22isZero * B.n + j] ); + premulGivens(cs[0],cs[1], DiagonalofB22isZero, j, B); + if ( computeU ) + premulGivens(cs[0],cs[1], DiagonalofB22isZero, j, U); + } + } + else { + // Zero B(k-1,k) and entire row k... + for (j=DiagonalofB22isZero - 1; j >= p; j--) { + + cs = givens(B.val[j*B.n * j] , B.val[j*B.n + n-q-1] ); + postmulGivens(cs[0],cs[1], j, n-q-1, B); + if ( computeV ) + premulGivens(cs[0],cs[1], j, n-q-1, Vt); +// postmulGivens(cs[0],cs[1], j, n-q-1, V); + + } + } + + } + else { + //B22 = get ( B, range(p , n - q ) , range (p , n-q ) ); + + if ( computeUV ) { + // UBV = GolubKahanSVDstep( B22, true ) ; + // set ( U, range(p,n-q), [], mul(UBV.U, get(U, range(p,n-q), []) ) ); + // set ( Vt, range(p,n-q), [], mul(transpose(UBV.V), getRows(Vt, range(p,n-q)) ) ); + + var GKstep = GolubKahanSVDstep( B, p, p, n-q-p, n-q-p, true ) ;// this updates B22 inside B + for ( var kk=0; kk < n-q-p-1; kk++) { + if ( computeU ) + premulGivens(GKstep.csU[kk][0], GKstep.csU[kk][1], p+kk, p+kk+1, U); + if ( computeV ) + premulGivens(GKstep.csV[kk][0], GKstep.csV[kk][1], p+kk, p+kk+1, Vt); // premul because Vtransposed + } + } + else { + GolubKahanSVDstep( B, p, p, n-q-p, n-q-p ) ; + } + //set ( B , range(p , n - q ) , range (p , n-q ), B22 ); + } + } + } while ( q < n ) ; + + if (computeUV ) { + + if ( computeV) + V = transposeMatrix(Vt); + + // Correct sign of singular values: + var s = diag(B); + var signs = zeros(n); + for ( i=0; i< n; i++) { + if (s[i] < 0) { + if ( computeV ) + set(V, [], i, minus(get(V,[],i))); + s[i] = -s[i]; + } + } + + // Rearrange in decreasing order: + var indexes = sort(s,true, true); + if(computeV) + V = get( V, [], indexes); + if(computeU) { + if ( !thinU) { + for ( i=n; i < m; i++) + indexes.push(i); + } + U = get(U, indexes,[]) ; + } + + if ( thinU ) + var S = diag(s) ; + else + var S = mat([diag(s), zeros(m-n,n)],true) ; + + var Ut = undefined; + if ( computeU ) + Ut = transpose(U); + + if ( Atransposed ) { + if ( thinU ) + return { "U" : V, "S" : S, "V" : Ut, "s" : s }; + else + return { "U" : V, "S" : transpose(S), "V" : Ut, "s" : s }; + } + else { + return { "U" : Ut, "S" : S, "V" : V, "s" : s }; + } + } + else + return sort(abs(diag(B)), true); +} + +function rank( A ) { + const s = svd(A); + var rank = 0; + var i; + for ( i=0;i < s.length;i++) + if ( s[i] > 1e-10 ) + rank++; + + return rank; +} + +function nullspace( A ) { + // Orthonormal basis for the null space of A + const s = svd( A, "V" ) ; + const n = A.n; + + var rank = 0; + const TOL = 1e-8; + while ( rank < n && s.s[rank] > TOL ) + rank++; + + if ( rank < n ) + return get ( s.V, [], range(rank, n) ); + else + return zeros(n); + +} + +function orth( A ) { + // Orthonormal basis for the range of A + const s = svd( A, "thinU" ) ; + const n = A.n; + + var rank = 0; + const TOL = 1e-8; + while ( rank < n && s.s[rank] > TOL ) + rank++; + + return get ( s.U, [], range(0,rank) ); + +} + +///////////////////////////// +//// Sparse matrix and vectors +///////////////////////////// + +/** + * + * new spVector(n) => allocate for n nonzeros with dim n + * new spVector(n, nnz) => allocate for nnz nonzeros out of n + * new spVector(n,values,indexes) => allocate for values.length nonzeros + * + * @constructor + * @struct + */ +function spVector(n, values, indexes) { + + /** @const */ this.length = n; + /** @const */ this.size = [n,1]; + /** @const */ this.type = "spvector"; + + if ( arguments.length <= 2) { + if ( arguments.length == 1) + var nnz = n; // too large but more efficient at some point... + else + var nnz = values; + + /** @type{Float64Array} */ this.val = new Float64Array(nnz); // nz values + /** @type{Uint32Array} */ this.ind = new Uint32Array(nnz); // ind[k] = index of val[k] + } + else { + var nnz = values.length; + /** @type{Float64Array} */ this.val = new Float64Array(values); // nz values + /** @type{Uint32Array} */ this.ind = new Uint32Array(indexes); // ind[k] = index of val[k] + } + + /** @const */ this.nnz = nnz; +} +/* + * @param{number} + * @return{number} + */ +spVector.prototype.get = function ( i ) { + var k = this.ind.indexOf(i); + if ( k < 0 ) + return 0; + else + return this.val[k]; +} +/* + * @param{number} + * @param{number} + */ +spVector.prototype.set = function ( i, value ) { + // Inefficient do not use this, use sparse(x) instead + if ( i > this.n ) { + error( "Error in spVector.set(i,value): i > this.length)"); + return undefined; + } + var k = this.ind.indexOf(i); + if ( k < 0 ) { + var ind = new Uint32Array(this.nnz + 1); + var val = new Float64Array(this.nnz + 1); + k = 0; + while ( this.ind[k] < i ) { // copy values until i + ind[k] = this.ind[k]; // making sure this.ind remains sorted + val[k] = this.val.ind[k]; + k++; + } + ind[k] = i;// insert value + val[k] = value; + ind.set(this.ind.subarray(k), k+1);// copy rest of vector + val.set(this.val.subarray(k), k+1); + this.nnz++; + } + else + this.val[k] = value; + + return value; +} +/* + * @return{spVector} + */ +spVector.prototype.copy = function () { + return new spVector(this.n, this.val, this.ind); +} + +/** + * + * new spMatrix(m,n) => allocate for m*n nonzeros + * new spMatrix(m,n, nnz) => allocate for nnz nonzeros + * new spMatrix(m,n,values,cols,rows) => allocate for values.length nonzeros + * + * @constructor + * @struct + */ +function spMatrix(m,n, values, cols, rows) { + + /** @const */ this.length = m; + /** @const */ this.m = m; + /** @const */ this.n = n; + /** @const */ this.size = [m,n]; + /** @const */ this.type = "spmatrix"; + + if ( arguments.length <= 3) { + if ( arguments.length == 2) + var nnz = m*n; // too large but more efficient at some point... + else + var nnz = values; + + /** @type{boolean} */ this.rowmajor = true; + /** @type{Float64Array} */ this.val = new Float64Array(nnz); // nnz values + /** @type{Uint32Array} */ this.cols = new Uint32Array(nnz); // cols[j] = starting index of col j in val and rows + /** @type{Uint32Array} */ this.rows = new Uint32Array(m+1); // rows[k] = row of val[k] + } + else { + var nnz = values.length; + if ( rows.length == nnz && cols.length == n+1 && cols[cols.length-1] == nnz ) { + /** @type{boolean} */ this.rowmajor = false; + /** @type{Float64Array} */ this.val = new Float64Array(values); // nz values + /** @type{Uint32Array} */ this.cols = new Uint32Array(cols); // cols[j] = starting index of col j in val and rows + /** @type{Uint32Array} */ this.rows = new Uint32Array(rows); // rows[k] = row of val[k] + } + else { + /** @type{boolean} */ this.rowmajor = true; + /** @type{Float64Array} */ this.val = new Float64Array(values); // nz values + /** @type{Uint32Array} */ this.cols = new Uint32Array(cols); // cols[k] = col of val[k] + /** @type{Uint32Array} */ this.rows = new Uint32Array(rows); // rows[i] = starting index of row i in val and cols + } + } + + /** @const */ this.nnz = nnz; + +} +/* + * @return{spMatrix} + */ +spMatrix.prototype.copy = function () { + return new spMatrix(this.m, this.n, this.val, this.cols, this.rows); +} +/* + * @return{spMatrix} + */ +spMatrix.prototype.toRowmajor = function () { + if ( this.rowmajor ) + return this.copy(); + else { + return sparseMatrixRowMajor( fullMatrix(this) ); + } +} +/* + * Get a pointer to the spVector for row i + * @return{spVector} + */ +spMatrix.prototype.row = function ( i ) { + if ( this.rowmajor ) { + return new spVector(this.n, this.val.subarray(this.rows[i], this.rows[i+1]), this.cols.subarray(this.rows[i], this.rows[i+1])); + /* + var s = this.rows[i]; + var e = this.rows[i+1]; + var vec = new spVector(this.n); + vec.val.set(this.val.subarray(s,e)); + vec.ind.set(this.cols.subarray(s,e)); + return vec;*/ + } + else { + error ("Cannot extract sparse column from a sparse matrix in row major format."); + return undefined; + } +} +/* + * Get a pointer to the spVector for column j + * @return{spVector} + */ +spMatrix.prototype.col = function ( j ) { + if ( ! this.rowmajor ) + return new spVector(this.m, this.val.subarray(this.cols[j], this.cols[j+1]), this.rows.subarray(this.cols[j], this.cols[j+1])); + else { + error ("Cannot extract sparse column from a sparse matrix in row major format."); + return undefined; + } +} + +/* + * @param{number} + * @param{number} + * @return{number} + */ +spMatrix.prototype.get = function ( i, j ) { + if ( this.rowmajor ) { + var rowind = this.cols.subarray(this.rows[i], this.rows[i+1]); + var k = rowind.indexOf(j); + if ( k < 0 ) + return 0; + else + return this.val[this.rows[i] + k]; + } + else { + var colind = this.rows.subarray(this.cols[j], this.cols[j+1]); + var k = colind.indexOf(i); + if ( k < 0 ) + return 0; + else + return this.val[this.cols[j] + k]; + } +} + +function spgetRows(A, rowsrange) { + var n = rowsrange.length; + if ( A.rowmajor) { + if ( n > 1 ) { + + var rowsidx = sort(rowsrange); + var Ai = new Array(n); + var nnz = 0; + for ( var i = 0; i < n; i++) { + Ai[i] = A.row(rowsidx[i]); + nnz += Ai[i].val.length; + } + var val = new Float64Array( nnz ); + var cols = new Uint32Array( nnz ); + var rows = new Uint32Array( n+1 ); + var k = 0; + for ( var i = 0; i < n; i++) { + rows[i] = k; + val.set(Ai[i].val, k); + cols.set(Ai[i].ind, k); + k += Ai[i].val.length; + } + rows[i] = k; + return new spMatrix(n, A.n, val, cols, rows); + } + else + return A.row( rowsrange[0] ) ; + } + else { + return getRows(fullMatrix(A), rowsrange); + } +} + +/** + * Return the full/dense version of the vector + * @param{spVector} + * @return{Float64Array} + */ +function fullVector (x) { + var k; + const n = x.length; + const nnz = x.val.length; + var a = new Float64Array(n); + + for ( k=0; k < nnz; k++) + a[x.ind[k]] = x.val[k]; + + return a; +} +/** + * Return the full/dense version of the matrix + * @param{spMatrix} + * @return{Matrix} + */ +function fullMatrix (S) { + const n = S.n; + if ( S.rowmajor ) { + var k; + const m = S.m; + var A = new Float64Array(m * n); + var ri = 0; + for (var i = 0; i < m; i++) { + var s = S.rows[i]; + var e = S.rows[i+1]; + for ( k=s; k < e; k++) { + A[ri + S.cols[k] ] = S.val[k]; + } + ri += n; + } + return new Matrix(m, n, A, true); + } + else { + var k; + var A = new Float64Array(S.m * n); + for (var j = 0; j < n; j++) { + var s = S.cols[j]; + var e = S.cols[j+1]; + for ( k=s; k < e; k++) { + var i = S.rows[k]; + A[i*n + j] = S.val[k]; + } + } + return new Matrix(S.m, n, A, true); + } +} +function full( A ) { + switch(type(A)) { + case "spvector": + return fullVector(A); + break; + case "spmatrix": + return fullMatrix(A); + break; + default: + return A; + break; + } +} + +/** + * @param{Float64Array} + * @return{spVector} + */ +function sparseVector( a ) { + var i,k; + const n = a.length; + var val = new Array(); + var ind = new Array(); + for ( i=0; i < n; i++) { + if (!isZero(a[i]) ) { + val.push(a[i]); + ind.push(i); + } + } + return new spVector(n,val,ind); +} +/** + * @param{Matrix} + * @return{spMatrix} + */ +function sparseMatrix( A ) { + var i,j; + const m = A.m; + const n = A.n; + var val = new Array(); + var rows = new Array(); + var cols = new Uint32Array(n+1); + var k; + for ( j=0; j< n; j++) { + k = j; + for ( i=0; i < m; i++) { + // k = i*n+j; + if (!isZero(A.val[k]) ) { + val.push(A.val[k]); + rows.push(i); + cols[j+1]++; + } + k += n; + } + } + for ( j=1; j< n; j++) + cols[j+1] += cols[j]; + + return new spMatrix(m,n,val,cols,rows); +} +/** + * @param{Matrix} + * @return{spMatrix} + */ +function sparseMatrixRowMajor( A ) { + var i,j; + const m = A.m; + const n = A.n; + var val = new Array(); + var cols = new Array(); + var rows = new Uint32Array(m+1); + var k = 0; + for ( i=0; i < m; i++) { + for ( j=0; j< n; j++) { + // k = i*n+j; + if (!isZero(A.val[k]) ) { + val.push(A.val[k]); + rows[i+1]++; + cols.push(j); + } + k++; + } + } + for ( i=1; i< m; i++) + rows[i+1] += rows[i]; + + return new spMatrix(m,n,val,cols,rows); +} + +function sparse( A , rowmajor ) { + if(typeof(rowmajor) == "undefined" ) + var rowmajor = true; + + switch(type(A)) { + case "vector": + return sparseVector(A); + break; + case "matrix": + if ( rowmajor ) + return sparseMatrixRowMajor(A); + else + return sparseMatrix(A); + break; + case "spvector": + case "spmatrix": + return A.copy(); + break; + default: + return A; + break; + } +} + +/** + * @param{number} + * @return{spMatrix} + */ +function speye(m,n) { + if ( typeof(n) == "undefined" ) + var n = m; + if ( m == 1 && n == 1) + return 1; + + var e = (m 1 ) { + M.val.set( new Float64Array(res[k].val), p); + M.cols.set( new Uint32Array(res[k].ind), p); + M.rows[k+1] = M.rows[k] + res[k].val.length; + p += res[k].val.length; + } + else if (res[k].val.length == 1) { + M.val[p] = res[k].val[0]; + M.cols[p] = res[k].ind[0]; + M.rows[k+1] = M.rows[k] + 1; + p += 1; + } + + } + return M; + } + else { + // not yet... + + error("spmat(..., false) for columnwise concatenation of sparse vectors not yet implemented"); + + return res; + } +} + + + +/** + * @param{number} + * @param{spVector} + * @return{spVector} + */ +function mulScalarspVector (a, b) { + const nnz = b.val.length; + var c = b.copy(); + for ( var k=0;k < nnz; k++) + c.val[k] *= a; + return c; +} +/** + * @param{number} + * @param{spMatrix} + * @return{spMatrix} + */ +function mulScalarspMatrix (a, B) { + const nnz = B.nnz; + var C = B.copy(); + for ( var k=0;k < nnz; k++) + C.val[k] *= a; + return C; +} + +/** + * @param{spVector} + * @param{spVector} + * @return{number} + */ +function spdot (a, b) { + const nnza = a.val.length; + const nnzb = b.val.length; + var c = 0; + var ka = 0; + var kb = 0; + while ( ka < nnza && kb < nnzb ){ + var i = a.ind[ka]; + while ( b.ind[kb] < i && kb < nnzb) + kb++; + if(b.ind[kb] == i) + c += a.val[ka] * b.val[kb]; + ka++; + } + return c; +} +/** + * @param{spVector} + * @param{Float64Array} + * @return{number} + */ +function dotspVectorVector (a, b) { + const nnza = a.val.length; + var c = 0; + for ( var ka=0;ka < nnza; ka++) + c += a.val[ka] * b[a.ind[ka]]; + + return c; +} +/** + * @param{Matrix} + * @param{spVector} + * @return{Float64Array} + */ +function mulMatrixspVector (A, b) { + const m = A.m; + const n = A.n; + const nnz = b.val.length; + var c = zeros(m); + var ri = 0; + for ( var i=0;i < n; i++) { + for ( var k=0; k < nnz; k++) + c[i] += A.val[ri + b.ind[k]] * b.val[k]; + ri+=n; + } + return c; +} +/** + * @param{spMatrix} + * @param{Float64Array} + * @return{Float64Array} + */ +function mulspMatrixVector (A, b) { + const m = A.m; + const n = A.n; + var c = zeros(m); + if ( A.rowmajor) { + for(var i=0; i < m; i++) { + var s = A.rows[i]; + var e = A.rows[i+1]; + for(var k = s; k < e; k++) { + c[i] += A.val[k] * b[A.cols[k]]; + } + } + } + else { + for ( var j=0;j < n; j++) { + var s = A.cols[j]; + var e = A.cols[j+1]; + var bj = b[j]; + for ( var k= s; k < e; k++) { + c[A.rows[k]] += A.val[k] * bj; + } + } + } + return c; +} +/** + * @param{spMatrix} + * @param{Float64Array} + * @return{Float64Array} + */ +function mulspMatrixTransVector (A, b) { + const m = A.m; + const n = A.n; + var c = zeros(n); + if ( A.rowmajor ) { + for ( var j=0;j < m; j++) { + var s = A.rows[j]; + var e = A.rows[j+1]; + var bj = b[j]; + for ( var k= s; k < e; k++) { + c[A.cols[k]] += A.val[k] * bj; + } + } + } + else { + for ( var j=0;j < n; j++) { + var s = A.cols[j]; + var e = A.cols[j+1]; + for ( var k= s; k < e; k++) { + c[j] += A.val[k] * b[A.rows[k]]; + } + } + } + return c; +} +/** + * @param{spMatrix} + * @param{spVector} + * @return{Float64Array} + */ +function mulspMatrixspVector (A, b) { + const m = A.m; + const n = A.n; + var c = zeros(m); + const nnzb = b.val.length; + if ( A.rowmajor) { + for(var i=0; i < m; i++) { + c[i] = spdot(A.row(i), b); + } + } + else { + for ( var kb=0;kb < nnzb; kb++) { + var j = b.ind[kb]; + var bj = b.val[kb]; + var s = A.cols[j]; + var e = A.cols[j+1]; + + for ( var k= s; k < e; k++) { + c[A.rows[k]] += A.val[k] * bj; + } + } + } + return c; +} +/** + * @param{spMatrix} + * @param{spVector} + * @return{Float64Array} + */ +function mulspMatrixTransspVector (A, b) { + const m = A.m; + const n = A.n; + var c = zeros(n); + const nnzb = b.val.length; + if (A.rowmajor) { + for ( var kb=0;kb < nnzb; kb++) { + var j = b.ind[kb]; + var bj = b.val[kb]; + var s = A.rows[j]; + var e = A.rows[j+1]; + for ( var k= s; k < e; k++) { + c[A.cols[k]] += A.val[k] * bj; + } + } + } + else { + for ( var i= 0; i < n; i++) { + var kb = 0; + var s = A.cols[i]; + var e = A.cols[i+1]; + + for ( var ka=s;ka < e; ka++) { + var j = A.rows[ka]; + while ( b.ind[kb] < j && kb < nnzb) + kb++; + if(b.ind[kb] == i) + c[i] += A.val[ka] * b.val[kb]; + } + } + } + return c; +} +/** + * @param{spMatrix} + * @param{spMatrix} + * @return{Matrix} + */ +function mulspMatrixspMatrix (A, B) { + const m = A.m; + const n = A.n; + const n2 = B.n; + var c = zeros(m, n2); + + if ( A.rowmajor ) { + if ( B.rowmajor ) { + for ( var ic = 0; ic < m; ic++) { + var sa = A.rows[ic]; + var ea = A.rows[ic+1]; + + for ( var ka = sa; ka < ea; ka++) { + var j = A.cols[ka]; + var aj = A.val[ka]; + + var s = B.rows[j]; + var e = B.rows[j+1]; + + var rc = ic * n2 ; + for (var k= s; k < e; k++) { + c.val[rc + B.cols[k] ] += aj * B.val[k] ; + } + } + } + } + else { + var kc = 0; + for ( var i=0; i < m; i++) { + for ( var j=0; j < n2; j++) { + c.val[kc] = spdot(A.row(i), B.col(j)); + kc++; + } + } + } + } + else { + if ( B.rowmajor ) { + for (var ja=0;ja < n; ja++) { + var sa = A.cols[ja]; + var ea = A.cols[ja+1]; + var sb = B.rows[ja]; + var eb = B.rows[ja+1]; + for ( var ka = sa; ka < ea; ka++) { + var rc = A.rows[ka] * n2; + var aij = A.val[ka]; + + for(var kb = sb; kb < eb; kb++) { + c.val[rc + B.cols[kb]] += aij * B.val[kb]; + } + } + } + } + else { + for ( var jc = 0; jc < n2; jc++) { + var sb = B.cols[jc]; + var eb = B.cols[jc+1]; + + for ( var kb = sb; kb < eb; kb++) { + var j = B.rows[kb]; + var bj = B.val[kb]; + + var s = A.cols[j]; + var e = A.cols[j+1]; + + for (var k= s; k < e; k++) { + c.val[A.rows[k] * n2 + jc] += A.val[k] * bj; + } + } + } + } + } + return c; +} +/** + * @param{Matrix} + * @param{spMatrix} + * @return{Matrix} + */ +function mulMatrixspMatrix (A, B) { + const m = A.m; + const n = A.n; + const n2 = B.n; + var c = zeros(m, n2); + + if ( B.rowmajor ) { + for (var ja=0;ja < n; ja++) { + var sb = B.rows[ja]; + var eb = B.rows[ja+1]; + for ( var i = 0; i < m; i++) { + var rc = i * n2; + var aij = A.val[i * n + ja]; + + for(var kb = sb; kb < eb; kb++) { + c.val[rc + B.cols[kb]] += aij * B.val[kb]; + } + } + } + } + else { + for ( var jc = 0; jc < n2; jc++) { + var sb = B.cols[jc]; + var eb = B.cols[jc+1]; + + for ( var kb = sb; kb < eb; kb++) { + var j = B.rows[kb]; + var bj = B.val[kb]; + + for ( i= 0; i < m; i++) { + c.val[i * n2 + jc] += A.val[i*n + j] * bj; + } + } + } + } + return c; +} + +/** + * @param{spMatrix} + * @param{Matrix} + * @return{Matrix} + */ +function mulspMatrixMatrix (A, B) { + const m = A.m; + const n = A.n; + const n2 = B.n; + var c = zeros(m, n2); + + if ( A.rowmajor ) { + for(var i=0; i < m; i++) { + var sa = A.rows[i]; + var ea = A.rows[i+1]; + for(var ka = sa; ka < ea; ka++) { + var ai = A.val[ka]; + var rb = A.cols[ka] * n2; + var rc = i*n2; + for ( j=0; j < n2; j++) { + c.val[rc + j] += ai * B.val[rb + j]; + } + } + } + } + else { + for(var j=0; j < n; j++) { + var s = A.cols[j]; + var e = A.cols[j+1]; + + for ( var k= s; k < e; k++) { + var i = A.rows[k]; + for ( var jc = 0; jc < n2; jc++) + c.val[i*n2 + jc ] += A.val[k] * B.val[j*n2 + jc]; + } + } + } + return c; +} + +/** + * @param{spVector} + * @param{spVector} + * @return{spVector} + */ +function entrywisemulspVectors (a, b) { + const nnza = a.val.length; + const nnzb = b.val.length; + var val = new Array(); + var ind = new Array(); + + var ka = 0; + var kb = 0; + while ( ka < nnza && kb < nnzb ){ + var i = a.ind[ka]; + while ( b.ind[kb] < i && kb < nnzb) + kb++; + if(b.ind[kb] == i) { + var aibi = a.val[ka] * b.val[kb]; + if ( !isZero(aibi) ) { + val.push(aibi); + ind.push(i); + } + } + ka++; + } + return new spVector(a.length, val, ind); +} +/** + * @param{spVector} + * @param{Float64Array} + * @return{spVector} + */ +function entrywisemulspVectorVector (a, b) { + // fast operation but might not yield optimal nnz: + var c = a.copy(); + const nnz = a.val.length; + for ( var k = 0; k< nnz; k++) { + c.val[k] *= b[a.ind[k]]; + } + return c; +} +/** + * @param{spMatrix} + * @param{spMatrix} + * @return{spMatrix} + */ +function entrywisemulspMatrices (A, B) { + if ( A.rowmajor ) { + if ( B.rowmajor ) { + var val = new Array(); + var cols = new Array(); + var rows = new Uint32Array(A.m+1); + var ka; + var kb; + var i; + for ( i=0; i < A.m; i++) { + ka = A.rows[i]; + kb = B.rows[i]; + var ea = A.rows[i+1]; + var eb = B.rows[i+1]; + while ( ka < ea & kb < eb ){ + var j = A.cols[ka]; + while ( B.cols[kb] < j && kb < eb) + kb++; + if(B.cols[kb] == j) { + val.push(A.val[ka] * B.val[kb]); + cols.push(j); + rows[i+1]++; + } + ka++; + } + } + for(i=1; i < A.m; i++) + rows[i+1] += rows[i]; + + return new spMatrix(A.m, A.n, val, cols, rows); + } + else { + return entrywisemulspMatrixMatrix(B, fullMatrix(A)); // perhaps not the fastest + } + } + else { + if ( B.rowmajor ) { + return entrywisemulspMatrixMatrix(A, fullMatrix(B)); // perhaps not the fastest + } + else { + var val = new Array(); + var cols = new Uint32Array(A.n+1); + var rows = new Array(); + var ka; + var kb; + var j; + for ( j=0; j < A.n; j++) { + ka = A.cols[j]; + kb = B.cols[j]; + var ea = A.cols[j+1]; + var eb = B.cols[j+1]; + while ( ka < ea & kb < eb ){ + var i = A.rows[ka]; + while ( B.rows[kb] < i && kb < eb) + kb++; + if(B.rows[kb] == i) { + val.push(A.val[ka] * B.val[kb]); + rows.push(i); + cols[j+1]++; + } + ka++; + } + } + for ( j=1; j< A.n; j++) + cols[j+1] += cols[j]; + + return new spMatrix(A.m, A.n, val, cols, rows); + } + } +} +/** + * @param{spMatrix} + * @param{Matrix} + * @return{spMatrix} + */ +function entrywisemulspMatrixMatrix (A, B) { + var c = A.copy(); + const nnz = A.val.length; + const n = A.n; + const m = A.m; + if ( A.rowmajor ) { + for ( i=0;i< m; i++) { + var s = c.rows[i]; + var e = c.rows[i+1]; + var r = i*n; + for ( var k = s; k< e; k++) { + c.val[k] *= B.val[r + c.cols[k] ]; + } + } + } + else { + for ( j=0;j< n; j++) { + var s = c.cols[j]; + var e = c.cols[j+1]; + for ( var k = s; k< e; k++) { + c.val[k] *= B.val[c.rows[k] * n + j]; + } + } + } + return c; +} + +/** + * @param{number} + * @param{spVector} + * @return{Float64Array} + */ +function addScalarspVector (a, b) { + const nnzb = b.val.length; + const n = b.length; + var c = zeros(n); + var k; + for ( k=0;k < n; k++) + c[k] = a; + for ( k=0;k < nnzb; k++) + c[b.ind[k]] += b.val[k]; + + return c; +} +/** + * @param{Float64Array} + * @param{spVector} + * @return{Float64Array} + */ +function addVectorspVector (a, b) { + const nnzb = b.val.length; + const n = b.length; + var c = new Float64Array(a); + for (var k=0;k < nnzb; k++) + c[b.ind[k]] += b.val[k]; + + return c; +} +/** + * @param{spVector} + * @param{spVector} + * @return{spVector} + */ +function addspVectors (a, b) { + const nnza = a.val.length; + const nnzb = b.val.length; + var c = zeros(a.length); + var k; + for ( k=0;k < nnza; k++) + c[a.ind[k]] = a.val[k]; + for ( k=0;k < nnzb; k++) + c[b.ind[k]] += b.val[k]; + + return sparseVector(c); +} + +/** + * @param{number} + * @param{spMatrix} + * @return{Matrix} + */ +function addScalarspMatrix (a, B) { + const nnzb = B.val.length; + const m = B.m; + const n = B.n; + const mn = m*n; + + var C = zeros(m,n); + var i; + for (i = 0; i < mn; i++) + C.val[i] = a; + if ( B.rowmajor ) { + var ri = 0; + for (i = 0; i < m; i++) { + var s = B.rows[i]; + var e = B.rows[i+1]; + for (var k= s; k < e; k++) + C.val[ri + B.cols[k]] += B.val[k]; + ri += n; + } + } + else { + for (i = 0; i < n; i++) { + var s = B.cols[i]; + var e = B.cols[i+1]; + for (var k= s; k < e; k++) + C.val[B.rows[k] * n + i] += B.val[k]; + } + } + return C; +} +/** + * @param{Matrix} + * @param{spMatrix} + * @return{Matrix} + */ +function addMatrixspMatrix (A, B) { + const nnzb = B.val.length; + const m = B.m; + const n = B.n; + const mn = m*n; + + var C = matrixCopy(A); + var i; + if ( B.rowmajor ) { + var ri = 0; + for (i = 0; i < m; i++) { + var s = B.rows[i]; + var e = B.rows[i+1]; + for (var k= s; k < e; k++) + C.val[ri + B.cols[k]] += B.val[k]; + ri += n; + } + } + else { + for (i = 0; i < n; i++) { + var s = B.cols[i]; + var e = B.cols[i+1]; + for (var k= s; k < e; k++) + C.val[B.rows[k] * n + i] += B.val[k]; + } + } + return C; +} +/** + * @param{spMatrix} + * @param{spMatrix} + * @return{spMatrix} + */ +function addspMatrices (A, B) { + const nnza = A.val.length; + const nnzb = B.val.length; + const m = A.m; + const n = A.n; + + var C = fullMatrix(A); + var i; + if ( B.rowmajor ) { + var ri = 0; + for (i = 0; i < m; i++) { + var s = B.rows[i]; + var e = B.rows[i+1]; + for (var k= s; k < e; k++) + C.val[ri + B.cols[k]] += B.val[k]; + ri += n; + } + } + else { + for (i = 0; i < n; i++) { + var s = B.cols[i]; + var e = B.cols[i+1]; + for (var k= s; k < e; k++) + C.val[B.rows[k] * n + i] += B.val[k]; + } + } + return sparseMatrixRowMajor(C); +} + +/** sparse SAXPY : y = y + ax with x sparse and y dense + * @param {number} + * @param {spVector} + * @param {Float64Array} + */ +function spsaxpy ( a, x, y) { + const nnz = x.val.length; + for (var k=0;k < nnz; k++) + y[x.ind[k]] += a * x.val[k]; +} + +/** + * @param{number} + * @param{spVector} + * @return{Float64Array} + */ +function subScalarspVector (a, b) { + const nnzb = b.val.length; + const n = b.length; + var c = zeros(n); + var k; + for ( k=0;k < n; k++) + c[k] = a; + for ( k=0;k < nnzb; k++) + c[b.ind[k]] -= b.val[k]; + + return c; +} +/** + * @param{Float64Array} + * @param{spVector} + * @return{Float64Array} + */ +function subVectorspVector (a, b) { + const nnzb = b.val.length; + const n = b.length; + var c = new Float64Array(a); + for (var k=0;k < nnzb; k++) + c[b.ind[k]] -= b.val[k]; + + return c; +} +/** + * @param{spVector} + * @param{Float64Array} + * @return{Float64Array} + */ +function subspVectorVector (a, b) { + return subVectors(fullVector(a), b); +} +/** + * @param{spVector} + * @param{spVector} + * @return{spVector} + */ +function subspVectors (a, b) { + const nnza = a.val.length; + const nnzb = b.val.length; + var c = zeros(a.length); + var k; + for ( k=0;k < nnza; k++) + c[a.ind[k]] = a.val[k]; + for ( k=0;k < nnzb; k++) + c[b.ind[k]] -= b.val[k]; + + return sparseVector(c); +} + +/** + * @param{number} + * @param{spMatrix} + * @return{Matrix} + */ +function subScalarspMatrix (a, B) { + const nnzb = B.val.length; + const m = B.m; + const n = B.n; + const mn = m*n; + + var C = zeros(m,n); + var i; + for (i = 0; i < mn; i++) + C.val[i] = a; + if ( B.rowmajor ) { + var ri = 0; + for (i = 0; i < m; i++) { + var s = B.rows[i]; + var e = B.rows[i+1]; + for (var k= s; k < e; k++) + C.val[ri + B.cols[k]] -= B.val[k]; + ri += n; + } + } + else { + for (i = 0; i < n; i++) { + var s = B.cols[i]; + var e = B.cols[i+1]; + for (var k= s; k < e; k++) + C.val[B.rows[k] * n + i] -= B.val[k]; + } + } + return C; +} +/** + * @param{spMatrix} + * @param{Matrix} + * @return{Matrix} + */ +function subspMatrixMatrix (A, B) { + return subMatrices(fullMatrix(A), B); +} +/** + * @param{Matrix} + * @param{spMatrix} + * @return{Matrix} + */ +function subMatrixspMatrix (A, B) { + const nnzb = B.val.length; + const m = B.m; + const n = B.n; + const mn = m*n; + + var C = matrixCopy(A); + var i; + if ( B.rowmajor ) { + var ri = 0; + for (i = 0; i < m; i++) { + var s = B.rows[i]; + var e = B.rows[i+1]; + for (var k= s; k < e; k++) + C.val[ri + B.cols[k]] -= B.val[k]; + ri += n; + } + } + else { + for (i = 0; i < n; i++) { + var s = B.cols[i]; + var e = B.cols[i+1]; + for (var k= s; k < e; k++) + C.val[B.rows[k] * n + i] -= B.val[k]; + } + } + return C; +} +/** + * @param{spMatrix} + * @param{spMatrix} + * @return{spMatrix} + */ +function subspMatrices (A, B) { + const nnza = A.val.length; + const nnzb = B.val.length; + const m = A.m; + const n = A.n; + + var C = fullMatrix(A); + var i; + if ( B.rowmajor ) { + var ri = 0; + for (i = 0; i < m; i++) { + var s = B.rows[i]; + var e = B.rows[i+1]; + for (var k= s; k < e; k++) + C.val[ri + B.cols[k]] -= B.val[k]; + ri += n; + } + } + else { + for (i = 0; i < n; i++) { + var s = B.cols[i]; + var e = B.cols[i+1]; + for (var k= s; k < e; k++) + C.val[B.rows[k] * n + i] -= B.val[k]; + } + } + return sparseMatrixRowMajor(C); +} + +/** + * @param{function} + * @param{spVector} + * @return{Float64Array} + */ +function applyspVector( f, x ) { + const nnz = x.val.length; + const n = x.length; + var res = new Float64Array(n); + var i; + const f0 = f(0); + for ( i=0; i< n; i++) + res[i] = f0; + for ( i=0; i< nnz; i++) + res[x.ind[i]] = f(x.val[i]); + return res; +} +/** + * @param{function} + * @param{spMatrix} + * @return{Matrix} + */ +function applyspMatrix( f, X ) { + const nnz = X.val.length; + const m = X.m; + const n = X.n; + const mn = m*n; + const f0 = f(0); + var C = zeros(m,n); + var i; + if ( !isZero(f0) ) { + for (i = 0; i < mn; i++) + C.val[i] = f0; + } + if ( X.rowmajor ) { + var ri = 0; + for (i = 0; i < m; i++) { + var s = X.rows[i]; + var e = X.rows[i+1]; + for (var k= s; k < e; k++) + C.val[ri + X.cols[k]] = f(X.val[k]); + ri += n; + } + } + else { + for (i = 0; i < n; i++) { + var s = X.cols[i]; + var e = X.cols[i+1]; + for (var k= s; k < e; k++) + C.val[X.rows[k] * n + i] += f(X.val[k]); + } + } + return C; +} +/** + * @param{spVector} + * @return{number} + */ +function sumspVector( a ) { + return sumVector(a.val); +} +/** + * @param{spMatrix} + * @return{number} + */ +function sumspMatrix( A ) { + return sumVector(A.val); +} +/** + * @param{spMatrix} + * @return{Matrix} + */ +function sumspMatrixRows( A ) { + var res = zeros(A.n); + if ( A.rowmajor ) { + for ( var k=0; k < A.val.length; k++) + res[A.cols[k]] += A.val[k]; + } + else { + for ( var i=0; i delta2 && k < n ) { + updateP(rhoc/rho_, r); + w = mulspMatrixVector(A,p); + mu = rhoc / dot(p, w); + saxpy( mu, p, x); + saxpy( -mu, w, r); + rho_ = rhoc; + rhoc = dot(r,r); + k++; + } + return x; +} +/** Sparse Conjugate gradient normal equation residual method for solving the rectangular system Ax = b + * @param{spMatrix} + * @param{Float64Array} + * @return{Float64Array} + */ +function spcgnr ( A, b) { +/* +TEST +A = randnsparse(0.3,10000,1000) +x = randn(1000) +b = A*x + 0.01*randn(10000) +tic() +xx = cgnr(A,b) +t1 = toc() +ee = norm(A*xx - b) +tic() +xh=spcgnr(sparse(A), b) +t2 = toc() +e = norm(A*xh - b) +*/ + + const n = A.n; + const m = A.m; + + var x = randn(n); + var r = subVectors(b, mulspMatrixVector(A, x)); + const TOL = 1e-8; + var delta2 = TOL * norm(b); + delta2 *= delta2; + + // first iteration: + var z = mulspMatrixTransVector(A, r); + var rhoc = dot(z,z); + var p = vectorCopy(z); + var w = mulspMatrixVector(A,p); + var mu = rhoc / dot(w, w); + saxpy( mu, p, x); + saxpy( -mu, w, r); + z = mulspMatrixTransVector(A, r); + var rho_ = rhoc; + rhoc = dot(z,z); + + var k = 1; + + var updateP = function (tau, z) { + for ( var i=0; i < m; i++) + p[i] = z[i] + tau * p[i]; + } + + while ( rhoc > delta2 && k < n ) { + updateP(rhoc/rho_, z); + w = mulspMatrixVector(A,p); + mu = rhoc / dot(w, w); + saxpy( mu, p, x); + saxpy( -mu, w, r); + z = mulspMatrixTransVector(A, r); + rho_ = rhoc; + rhoc = dot(z,z); + k++; + } + return x; +} + + +/* glpk.js is now included (cat) in lalolib.js +if ( self.hasOwnProperty("window") ) { + // in main window +} +else { + // in worker + importScripts("glpk.js"); + //importScripts("glpk.min.js"); +}*/ + +// Install glpk as lp function: +if ( typeof(lp) == "undefined" ) { + lp = glp; + linprog = glp; +} + +function glp (c, A, b, Aeq, beq, lb , ub, integer_variables, verbose) { +/* + Call GLPK to solve + min c' x s.t. Ax<= b, Aeq = beq, lb<= x <= ub, x[integer_variables] in Z +*/ + +/* TESTS: +Aineq = [[1, 1]; [-1,1]] +Bineq = [2; 1] +costineq = [-1; -2] +lb = [0;0] +xsol = glp(costineq, Aineq, Bineq, [], [], lb) + +A = [[3,2,1,1,0],[2,5,3,0,1]] +b=[10,15] +c=[-2,-3,-4,0,0] +lb = zeros(5) +xsol = glp(c, [],[],A, b,lb,[]) + +*/ + var prob = glp_create_prob(); + glp_set_obj_dir ( prob, GLP_MIN ) ; + + if ( typeof(Aeq) == "undefined" ) + var Aeq = []; + + glp_add_cols(prob, c.length); + if ( A.length + Aeq.length > 0 ) + glp_add_rows(prob, A.length + Aeq.length); + + var i; + var j; + var indexes ; + var values; + var n = c.length; + + if ( lb ) { + var lbdense = vectorCopy(lb); + for ( i=0; i < lbdense.length; i++){ + if ( !isFinite( lbdense[i] ) ) + lbdense[i] = NaN; + } + } + else + var lbdense = []; + + if ( ub ) { + var ubdense = vectorCopy(ub); + for ( i=0; i < ubdense.length; i++){ + if ( !isFinite( ubdense[i] ) ) + lbdense[i] = NaN; + } + } + else + var ubdense = []; + + for ( i=0; i < c.length; i++) { + // variable bounds + var lbi = NaN; + var ubi = NaN; + if ( lbdense.length > 0) + lbi = lbdense[i]; + if ( ubdense.length > 0 ) + ubi = ubdense[i] ; + + if ( !isNaN(lbi) && !isNaN(ubi)) + glp_set_col_bnds( prob, i+1, GLP_DB, lbi , ubi ); + else if ( !isNaN(lbi) ) + glp_set_col_bnds( prob, i+1, GLP_LO, lbi ); + else if ( !isNaN(ubi) ) + glp_set_col_bnds( prob, i+1, GLP_UP, 0, ubi ); + else + glp_set_col_bnds( prob, i+1, GLP_FR ); + + // cost + glp_set_obj_coef ( prob, i+1, c[i] ); + + } + + // Integer variables + if ( integer_variables ) { + for ( i=0; i< integer_variables.length ; i++) + glp_set_col_kind(prob, integer_variables[i]+1, GLP_IV ); + } + + // inequalities + if ( A.length == 1 && typeof(b) == "number") + b = [b]; + for ( i=0; i 0) { + // Solve with MILP solver + var iocp = new IOCP({presolve: GLP_ON}); + glp_scale_prob(prob, GLP_SF_AUTO); + rc = glp_intopt(prob, iocp); + + // get solution + if ( rc == 0 ) { + var sol = zeros(n); + for ( i=0; i";}); + return "RC=" + rc + " ; Status : " + glp_get_status(prob) + "(OPT=" + GLP_OPT + ",FEAS=" + GLP_FEAS + ",INFEAS=" + GLP_INFEAS + ",NOFEAS=" + GLP_NOFEAS + ",UNBND=" + GLP_UNBND + ",UNDEF=" + GLP_UNDEF + ")" ; + } + } + +} + +///////////////////////////////: +/////// L1-minimization and sparse recovery ////////// +/////////// +function minl1 ( A, b) { + /* + Solves min ||x||_1 s.t. Ax = b + + as + + min sum a_i s.t. -a <= x <= a and Ax = b + + example: +A = randn(10,20) +r = zeros(20) +r[0:3] = randn(3) +x=minl1(A,A*r) + + */ + const n = A.n; + + var Aineq = zeros ( 2*n, 2*n ) ; + var i; + + //set ( Aineq, range(0,n),range(0,n) , I) ; + //set ( Aineq, range(0,n),range(n,2*n) , I_) ; + //set ( Aineq, range(n,2*n),range(0,n) , I_) ; + //set ( Aineq, range(n,2*n),range(n,2*n) , I_) ; + for ( i=0; i < n; i++) { + Aineq.val[i*Aineq.n + i] = 1; + Aineq.val[i*Aineq.n + n+i] = -1; + Aineq.val[(n+i)*Aineq.n + i] = -1; + Aineq.val[(n+i)*Aineq.n + n+i] = -1; + } + var bineq = zeros ( 2*n); + + var Aeq = zeros(A.length, 2*n); + set ( Aeq , [], range( 0,n), A ); + + var cost = zeros(2*n); + set ( cost, range(n,2*n), ones(n) ); + + var lb = zeros(2*n); // better to constraint a>=0 + set ( lb, range(n), mulScalarVector(-Infinity , ones( n )) ) ; +//console.log( cost, Aineq, bineq, Aeq, b, lb); +// var lpsol = lp( cost, Aineq, bineq, Aeq, b, lb, [], 0 , 1e-6 ); + var lpsol = glp( cost, Aineq, bineq, Aeq, b, lb); + + return get(lpsol, range(n) ); +} + + + +function minl0 ( A, b, M) { + /* + Solves min ||x||_0 s.t. Ax = b -M <= x <= M + + as a mixed integer linear program + + min sum a_i s.t. -M a <= x <= M a , Ax = b and a_i in {0,1} + + example: +A = randn(10,20) +r = zeros(20) +r[0:3] = randn(3) +x=minl0(A,A*r) + + */ + + if ( typeof(M) == "undefined" ) + var M = 10; + + var n = A.n; + + var Aineq = zeros ( 2*n, 2*n ) ; + //set ( Aineq, range(0,n),range(0,n) , I) ; + //set ( Aineq, range(0,n),range(n,2*n) , mul(M, I_) ) ; + //set ( Aineq, range(n,2*n),range(0,n) , I_) ; + //set ( Aineq, range(n,2*n),range(n,2*n) ,mul(M, I_) ) ; + var i; + for ( i=0; i < n; i++) { + Aineq.val[i*Aineq.n + i] = 1; + Aineq.val[i*Aineq.n + n+i] = -M; + Aineq.val[(n+i)*Aineq.n + i] = -1; + Aineq.val[(n+i)*Aineq.n + n+i] = -M; + + } + var bineq = zeros ( 2*n); + + var Aeq = zeros(A.length, 2*n); + set ( Aeq , [], range( 0,n), A ); + + var cost = zeros(2*n); + set ( cost, range(n,2*n), ones(n) ); + + var lb = zeros(2*n); // better to constraint a>=0 + set ( lb, range(n), mulScalarVector(-M , ones( n )) ) ; + + var ub = ones(2*n) ; + set(ub, range(n), mulScalarVector(M, ones(n) ) ); + + var lpsol = glp( cost, Aineq, bineq, Aeq, b, lb, ub, range(n,2*n) ); // glptweak?? + + // set to 0 the x corresponding to 0 binary variables: + var x = entrywisemulVector( getSubVector(lpsol, range(n) ), getSubVector(lpsol, range(n,2*n) ) ); + + return x; +} + + +/////////////////////////////////////////// +/// Quadratic Programming +//////////////// +quadprog = qp; + +function qp(Q,c,A,b,Aeq,beq,lb,ub,x0, epsilon) { + // Solve quad prog by Frank-Wolfe algorithm + /* + min 0.5 x' * Q * x c' * x + s.t. Ax <= b and lu <= x <= ub + + NOTE: all variables should be bounded or constrained, + otherwise the LP might be unbounded even if the QP is well-posed + */ + if (typeof(epsilon) === 'undefined') + var epsilon = 1e-3; + + var normdiff; + var normgrad; + var grad; + var y; + var gamma; + var direction; + + var x; + if ( typeof(x0) === 'undefined' ) { + //console.log ("providing an initial x0 might be better for qp."); + x = glp(zeros(c.length),A, b, Aeq, beq, lb, ub, [], false) ; + if ( typeof(x) == "string") + return "infeasible"; + } + else { + x = vectorCopy(x0); + } + + var iter = 0; + do { + + // Compute gradient : grad = Qx + c + grad = add( mul( Q, x) , c ); + normgrad = norm(grad); + + // Find direction of desecnt : direction = argmin_y y'*grad s.t. same constraints as QP + y = glp(grad, A, b, Aeq, beq, lb, ub, [], false) ; +/* if ( typeof(y) == "string") + return x; // error return current solution; + */ + + // Step size: gamma = -(y - x)' [ Qx + c] / (y-x)'Q(y-x) = numerator / denominator + direction = sub (y, x); + + numerator = - mul(direction, grad); + + denominator = mul(direction, mul(Q, direction) ); + + if ( Math.abs(denominator) > 1e-8 && denominator > 0) + gamma = numerator / denominator; + else + gamma = 0; + + if ( gamma > 1 ) + gamma = 1; + + // Update x <- x + gamma * direction + if ( gamma > 0 ) { + x = add(x, mul(gamma, direction) ); + normdiff = gamma * norm(direction) ; + } + else + normdiff = 0; + + iter++; + } while ( normdiff > epsilon && normgrad > epsilon && iter < 10000) ; + + return x; +} + + + +/////////////////////////////////////////: +//// Unconstrained Minimization +///////////////////////////////////////// +function minimize( f, grad, x0 ) { +/* +function loss(x) { +return (norm(b - A*x)^2) +} +function grad(x) { +return (2*A'*A*x - 2*A'*b) +} +x = randn(10) +A = randn(100,10) +b = A*x + 0.01*randn(100) +xh = minimize(A.n, loss, grad) +norm(x - xh) +*/ + var x; + var n = 1; // dimension of x + + if ( arguments.length == 3 ) { + if ( typeof(x0) == "number" ) { + if( x0 > 0 && Math.floor(x0) == x0 ) { + n = x0; + x = sub(mul(20,rand(n)), 10); + } + else { + n = 1; + x = x0; + } + } + else { + n = x0.length; + x = x0; + } + } + else { + n = 1; + x = 20 * Math.random() - 10; + } + + if ( n == 1 ) + return secant(f, grad, x); + else if ( n > 500 ) + return steepestdescent(f, grad, x); + else + return bfgs(f, grad, x); +} + +function secant( f, grad, x0 ) { + // for a unidimensional function f + // find a root to f'(x) = 0 with the secant method + const TOLx = 1e-6; + + var x = x0; + var g = grad(x); + var dx = -0.01*g; + x += dx; + var gprev,dg; + do { + gprev = g; + g = grad(x); + dg = g-gprev; + + dx *= -g / dg; + x += dx; + + } while ( Math.abs(dx) > TOLx); + return x; +} + + +function steepestdescent(f, grad, x0) { + // assume x is a vector + + const TOLobj = 1e-8; + const TOLx = 1e-6; + const TOLgrad = 1e-4; + + var x = x0; + var xprev; + var obj = f(x); + var g = grad(x); + var normg = norm(g); + var iter = 0; + do { + + // line search + var linesearch = armijo(f, x, obj, g, normg); + + // take the step + xprev = vectorCopy(x); + prevobj = obj; + x = linesearch.x; + obj = linesearch.obj; + g = grad(x); + normg = norm(g); + + iter++; + //console.log(linesearch.lambda, x, obj, g); + } while ( normg > TOLgrad && prevobj - obj > TOLobj && norm(subVectors(x, xprev) ) > TOLx ) ; + console.log(" OBJ: " + obj + ", norm(grad): " + normg, "prevobj - obj", prevobj - obj, "iter: ", iter ); + return x; +} + +function bfgs( f, grad, x0 ) { + // assume x is a vector + + const n = x0.length; + const TOLobj = 1e-8; + const TOLx = 1e-6; + const TOLgrad = 1e-4; + + var x = x0; + var xprev; + var obj = f(x); + var H = eye(n); + var g,direction, delta, gamma, ls; + var normg; + var Hgamma; + var dH; + var iter = 0; + do { + g = grad(x); + normg = norm(g); + direction = minusVector( mulMatrixVector(H, g ) ); + + // line search + var linesearch = armijodir (f, x, obj, g, direction ); + + // take the step + xprev = vectorCopy(x); + prevobj = obj; + x = linesearch.x; + obj = linesearch.obj; + + // update Hessian inverse approximation + delta = subVectors(x,xprev); + gamma = subVectors(grad(x) , g); + + Hgamma = mulMatrixVector(H, gamma); + + var deltagamma = dot(delta,gamma); + var delta_ = mulScalarVector(1/deltagamma, delta); + + var deltagammaH = outerprodVectors(delta_, Hgamma); + + dH = subMatrices(outerprodVectors(delta_, delta, 1+ dot(gamma, Hgamma)/deltagamma) , addMatrices(deltagammaH, transposeMatrix(deltagammaH) ) ); + //-- + + H = add(H, dH); + + iter++; + + } while ( normg > TOLgrad && prevobj - obj > TOLobj && norm(subVectors(x, xprev) ) > TOLx ) ; + console.log(" OBJ: " + obj + ", norm(grad): " + normg, "prevobj - obj", prevobj - obj, "iters: ", iter ); + return x; +} + + +/** + * Return minimizer of p(x) = p0 + p1 x + p2 x^2 + p3 x^3 with p(x1) = px1, p(x2) = px2 + * within [lb, ub] + * + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @return {number} + */ +function mincubic(p0, p1, x1, px1, x2, px2, lb, ub) { + + const x1square = x1*x1; + const x2square = x2*x2; + + var A = new Matrix(2,2, [x1square, x1*x1square, x2square, x2*x2square]); + var b = new Float64Array([px1 - p0 - p1*x1, px2 - p0 - p1*x2]); + var c = solve(A,b); + var x = (-c[0] + Math.sqrt(c[0]*c[0] - 3 *c[1] * p1))/(3*c[1]); + + return Math.min(ub, Math.max(lb, x)); +} +/** + * Return minimizer of p(x) = p0 + p1 x + p2 x^2 with p(x1) = px1 (x1 ~ 1) + * within [lb, ub] + * + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @param {number} + * @return {number} + */ +function minquadratic(p0, p1, px1, x1, lb, ub) { + var x = - p1/(2 * x1 * (px1 - p0 - p1) ); + return Math.min(ub, Math.max(lb, x)); +} + +/** + * Armijo line search with objective function f + * and starting point xc, fc, g + * + * @param {function} + * @param {{Float64Array|number}} + * @param {number} + * @param {{Float64Array|number}} + * @param {number} + * @return {{Float64Array|number}} + */ +function armijo (f, xc, fc, g, normg ) { + // Armijo's rule line search in the direction of gradient g + const alpha = 0.0001; + const blow = 0.1; + const bhigh = 0.5; + const normg2 = normg * normg; + + var lambda = Math.min(1,100/(1+normg)); + var fgoal = fc - alpha * lambda * normg2; + + var lambda1 = lambda; + var xt = subVectors(xc, mulScalarVector(lambda, g) ); + var ft_1 = fc; + var ft = f(xt); + + var iter = 1; + + // first iter + lambda = minquadratic(fc, -normg2, lambda1, ft, blow*lambda1, bhigh*lambda1); + var ft_1 = ft; + var lambda2 = lambda1; + lambda1 = lambda; + + iter++; + // next iterations + while(ft > fgoal && iter <= 10) { + + lambda = mincubic(fc, -normg2, lambda1, ft, lambda2, ft_1, blow*lambda1, bhigh*lambda1); + lambda2 = lambda1; + lambda1 = lambda; + + xt = subVectors(xc, mulScalarVector(lambda, g) ); + ft_1 = ft; + ft = f(xt); + + fgoal = fc - alpha * lambda * normg2; + + iter++; + } + return {"lambda": lambda, "x": xt, "obj": ft}; +} +function armijodir (f, xc, fc, g, d ) { + // Armijo's rule line search in the direction d + const alpha = 0.0001; + const blow = 0.1; + const bhigh = 0.5; + const p1 = dot( g, d); + + var lambda = Math.min(1,100/(1+norm(g))); + var fgoal = fc + alpha * lambda * p1; + + var lambda1 = lambda; + var xt = addVectors(xc, mulScalarVector(lambda, d) ); + var ft_1 = fc; + var ft = f(xt); + + var iter = 1; + + // first iter + lambda = minquadratic(fc, p1, lambda1, ft, blow*lambda1, bhigh*lambda1); + var ft_1 = ft; + var lambda2 = lambda1; + lambda1 = lambda; + + iter++; + // next iterations + while(ft > fgoal && iter <= 10) { + + lambda=mincubic(fc, p1, lambda1, ft, lambda2, ft_1, blow*lambda1, bhigh*lambda1 ); + lambda2 = lambda1; + lambda1 = lambda; + + xt = addVectors(xc, mulScalarVector(lambda, d) ); + ft_1 = ft; + ft = f(xt); + + fgoal = fc + alpha * lambda * p1; + + iter++; + } + return {"lambda": lambda, "x": xt, "obj": ft}; +} + +/*! glpk.js - v4.49.0 +* https://github.com/hgourvest/glpk.js +* Copyright (c) 2013 Henri Gourvest; Licensed GPLv2 */ +(function(exports) { +var s=Number.MAX_VALUE,aa=Number.MIN_VALUE;function w(a){throw Error(a);}function x(){}exports.glp_get_print_func=function(){return x};exports.glp_set_print_func=function(a){x=a};function da(a,b){for(var c in b)a[c]=b[c]}function ga(a,b,c,d,e){for(;0=a||127==a}function ua(a){a="string"==typeof a?a.charCodeAt(0):-1;return 65<=a&&90>=a||97<=a&&122>=a}function va(a){a="string"==typeof a?a.charCodeAt(0):-1;return 65<=a&&90>=a||97<=a&&122>=a||48<=a&&57>=a}function wa(a){a="string"==typeof a?a.charCodeAt(0):-1;return 48<=a&&57>=a} +function xa(){function a(a,d,e,k,l,p,m){a>>>=0;e=e&&a&&{2:"0b",8:"0",16:"0x"}[d]||"";a=e+c(a.toString(d),p||0,"0",!1);return b(a,e,k,l,m)}function b(a,b,d,e,l,p){var m=e-a.length;0=b?"":Array(1+b-a.length>>>0).join(c);return d?a+b:b+a}var d=arguments,e=0;return d[e++].replace(/%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuideEfFgG])/g,function(f, +g,h,k,l,p,m){var q,r;if("%%"==f)return"%";var n=!1;r="";var t=l=!1;q=" ";for(var y=h.length,E=0;h&&Ek&&(k=-k,n=!0);if(!isFinite(k))throw Error("sprintf: (minimum-)width must be finite");p=p?"*"==p?+d[e++]:"*"==p.charAt(0)?+d[p.slice(1,-1)]:+p:-1<"fFeE".indexOf(m)?6:"d"==m?0:void 0;g=g? +d[g.slice(0,-1)]:d[e++];switch(m){case "s":return m=String(g),null!=p&&(m=m.slice(0,p)),b(m,"",n,k,l,q);case "c":return m=String.fromCharCode(+g),null!=p&&(m=m.slice(0,p)),b(m,"",n,k,l,void 0);case "b":return a(g,2,t,n,k,p,l);case "o":return a(g,8,t,n,k,p,l);case "x":return a(g,16,t,n,k,p,l);case "X":return a(g,16,t,n,k,p,l).toUpperCase();case "u":return a(g,10,t,n,k,p,l);case "i":case "d":return q=+g||0,q=Math.round(q-q%1),f=0>q?"-":r,g=f+c(String(Math.abs(q)),p,"0",!1),b(g,f,n,k,l);case "e":case "E":case "f":case "F":case "g":case "G":return q= ++g,f=0>q?"-":r,r=["toExponential","toFixed","toPrecision"]["efg".indexOf(m.toLowerCase())],m=["toString","toUpperCase"]["eEfFgG".indexOf(m)%2],g=f+Math.abs(q)[r](p),b(g,f,n,k,l)[m]();default:return f}})}function ya(a){a.Dd=3621377730;a.ke=null;a.V=null;a.name=null;a.eb=null;a.dir=za;a.ha=0;a.hb=100;a.K=200;a.g=a.i=0;a.L=0;a.n=Array(1+a.hb);a.f=Array(1+a.K);a.fc={};a.Lc={};a.valid=0;a.head=new Int32Array(1+a.hb);a.Qd=null;a.U=null;a.na=a.sa=Aa;a.aa=0;a.$=0;a.some=0;a.df=Aa;a.ae=0;a.za=Aa;a.ta=0} +var Ba=exports.glp_create_prob=function(){var a={};ya(a);return a},Ca=exports.glp_set_prob_name=function(a,b){var c=a.V;null!=c&&0!=c.reason&&w("glp_set_prob_name: operation not allowed");a.name=b},Da=exports.glp_set_obj_name=function(a,b){var c=a.V;null!=c&&0!=c.reason&&w("glp_set_obj_name: operation not allowed");a.eb=b},Fa=exports.glp_set_obj_dir=function(a,b){var c=a.V;null!=c&&0!=c.reason&&w("glp_set_obj_dir: operation not allowed");b!=za&&b!=Ea&&w("glp_set_obj_dir: dir = "+b+"; invalid direction flag"); +a.dir=b},La=exports.glp_add_rows=function(a,b){var c=a.V,d;1>b&&w("glp_add_rows: nrs = "+b+"; invalid number of rows");b>1E8-a.g&&w("glp_add_rows: nrs = "+b+"; too many rows");var e=a.g+b;if(a.hbb&&w("glp_add_cols: ncs = "+b+"; invalid number of columns");b>1E8-a.i&&w("glp_add_cols: ncs = "+b+"; too many columns");var d=a.i+b;if(a.K5E8-a.L&&w("glp_set_mat_row: i = "+b+"; len = "+c+"; too many constraint coefficients");for(h=1;h<=c;h++)g=d[h],1<=g&&g<=a.i||w("glp_set_mat_row: i = "+b+"; ind["+h+"] = "+g+"; column index out of range"),f=a.f[g],null!=f.k&&f.k.n.ea==b&&w("glp_set_mat_row: i = "+b+"; ind["+h+"] = "+g+"; duplicate column indices not allowed"),g={},a.L++,g.n=k,g.f=f,g.j=e[h],g.ua=null,g.B=k.k,g.ra=null,g.I=f.k,null!=g.B&&(g.B.ua=g),null!=g.I&&(g.I.ra=g),k.k=f.k=g,f.m==A&&0!=g.j&&(a.valid= +0);for(g=k.k;null!=g;g=b)b=g.B,0==g.j&&(null==g.ua?k.k=b:g.ua.B=b,null!=b&&(b.ua=g.ua),g.f.k=g.I,null!=g.I&&(g.I.ra=null),a.L--)},Za=exports.glp_set_mat_col=function(a,b,c,d,e){var f=a.V,g,h,k;null!=f&&0!=f.reason&&w("glp_set_mat_col: operation not allowed");1<=b&&b<=a.i||w("glp_set_mat_col: j = "+b+"; column number out of range");for(f=a.f[b];null!=f.k;)h=f.k,f.k=h.I,g=h.n,null==h.ua?g.k=h.B:h.ua.B=h.B,null!=h.B&&(h.B.ua=h.ua),a.L--;0<=c&&c<=a.g||w("glp_set_mat_col: j = "+b+"; len = "+c+"; invalid column length"); +c>5E8-a.L&&w("glp_set_mat_col: j = "+b+"; len = "+c+"; too many constraint coefficients");for(k=1;k<=c;k++)h=d[k],1<=h&&h<=a.g||w("glp_set_mat_col: j = "+b+"; ind["+k+"] = "+h+"; row index out of range"),g=a.n[h],null!=g.k&&g.k.f.C==b&&w("glp_set_mat_col: j = "+b+"; ind["+k+"] = "+h+"; duplicate row indices not allowed"),h={},a.L++,h.n=g,h.f=f,h.j=e[k],h.ua=null,h.B=g.k,h.ra=null,h.I=f.k,null!=h.B&&(h.B.ua=h),null!=h.I&&(h.I.ra=h),g.k=f.k=h;for(h=f.k;null!=h;h=b)b=h.I,0==h.j&&(h.n.k=h.B,null!=h.B&& +(h.B.ua=null),null==h.ra?f.k=b:h.ra.I=b,null!=b&&(b.ra=h.ra),a.L--);f.m==A&&(a.valid=0)}; +exports.glp_load_matrix=function(a,b,c,d,e){var f=a.V,g,h,k,l;null!=f&&0!=f.reason&&w("glp_load_matrix: operation not allowed");for(k=1;k<=a.g;k++)for(f=a.n[k];null!=f.k;)h=f.k,f.k=h.B,a.L--;for(h=1;h<=a.i;h++)a.f[h].k=null;0>b&&w("glp_load_matrix: ne = "+b+"; invalid number of constraint coefficients");5E8a&&w("glp_check_dup: m = %d; invalid parameter");0>b&&w("glp_check_dup: n = %d; invalid parameter");0>c&&w("glp_check_dup: ne = %d; invalid parameter");0=c&&w("glp_set_rii: i = "+b+"; rii = "+c+"; invalid scale factor");if(a.valid&&a.n[b].ma!=c)for(var d=a.n[b].k;null!=d;d=d.B)if(d.f.m==A){a.valid=0;break}a.n[b].ma=c},Ab=exports.glp_set_sjj=function(a,b,c){1<=b&&b<=a.i||w("glp_set_sjj: j = "+b+"; column number out of range");0>=c&&w("glp_set_sjj: j = "+b+"; sjj = "+c+"; invalid scale factor");a.valid&&a.f[b].va!=c&&a.f[b].m==A&&(a.valid=0);a.f[b].va=c},Cb=exports.glp_get_rii= +function(a,b){1<=b&&b<=a.g||w("glp_get_rii: i = "+b+"; row number out of range");return a.n[b].ma},Db=exports.glp_get_sjj=function(a,b){1<=b&&b<=a.i||w("glp_get_sjj: j = "+b+"; column number out of range");return a.f[b].va},Eb=exports.glp_unscale_prob=function(a){var b=kb(a),c=lb(a),d;for(d=1;d<=b;d++)zb(a,d,1);for(b=1;b<=c;b++)Ab(a,b,1)},Fb=exports.glp_set_row_stat=function(a,b,c){1<=b&&b<=a.g||w("glp_set_row_stat: i = "+b+"; row number out of range");c!=A&&c!=G&&c!=Ua&&c!=Ra&&c!=Na&&w("glp_set_row_stat: i = "+ +b+"; stat = "+c+"; invalid status");b=a.n[b];if(c!=A)switch(b.type){case Ka:c=Ra;break;case Sa:c=G;break;case Ta:c=Ua;break;case I:c!=Ua&&(c=G);break;case B:c=Na}if(b.m==A&&c!=A||b.m!=A&&c==A)a.valid=0;b.m=c},Gb=exports.glp_set_col_stat=function(a,b,c){1<=b&&b<=a.i||w("glp_set_col_stat: j = "+b+"; column number out of range");c!=A&&c!=G&&c!=Ua&&c!=Ra&&c!=Na&&w("glp_set_col_stat: j = "+b+"; stat = "+c+"; invalid status");b=a.f[b];if(c!=A)switch(b.type){case Ka:c=Ra;break;case Sa:c=G;break;case Ta:c= +Ua;break;case I:c!=Ua&&(c=G);break;case B:c=Na}if(b.m==A&&c!=A||b.m!=A&&c==A)a.valid=0;b.m=c},Hb=exports.glp_std_basis=function(a){var b;for(b=1;b<=a.g;b++)Fb(a,b,A);for(b=1;b<=a.i;b++){var c=a.f[b];c.type==I&&Math.abs(c.c)>Math.abs(c.d)?Gb(a,b,Ua):Gb(a,b,G)}},sc=exports.glp_simplex=function(a,b){function c(a,b){var c;if(!Ib(a)&&(c=Jb(a),0!=c&&(c==Kb?b.o>=Lb&&x("glp_simplex: initial basis is invalid"):c==Mb?b.o>=Lb&&x("glp_simplex: initial basis is singular"):c==Nb&&b.o>=Lb&&x("glp_simplex: initial basis is ill-conditioned")), +0!=c))return c;b.cb==Ob?c=Pb(a,b):b.cb==Qb?(c=Rb(a,b),c==Sb&&a.valid&&(c=Pb(a,b))):b.cb==Tb&&(c=Rb(a,b));return c}function d(a,b){function d(){Ub(e,f);f=null;Vb(e,a);return r=0}var e,f=null,g={},r;b.o>=Wb&&x("Preprocessing...");e=Xb();Yb(e,a,Zb);r=$b(e,0);0!=r&&(r==ac?b.o>=Wb&&x("PROBLEM HAS NO PRIMAL FEASIBLE SOLUTION"):r==bc&&b.o>=Wb&&x("PROBLEM HAS NO DUAL FEASIBLE SOLUTION"));if(0!=r)return r;f=Ba();cc(e,f);if(0==f.g&&0==f.i)return f.na=f.sa=dc,f.aa=f.ha,b.o>=fc&&0==b.fb&&x(a.$+": obj = "+f.aa+ +" infeas = 0.0"),b.o>=Wb&&x("OPTIMAL SOLUTION FOUND BY LP PREPROCESSOR"),d();b.o>=Wb&&x(f.g+" row"+(1==f.g?"":"s")+", "+f.i+" column"+(1==f.i?"":"s")+", "+f.L+" non-zero"+(1==f.L?"":"s")+"");eb(a,g);fb(f,g);var g=na,n=g.Fb;g.Fb=!n||b.o=Lb&&x("glp_simplex: unable to recover undefined or non-optimal solution"),0==r&&(f.na==jc?r=ac:f.sa==jc&&(r=bc)),r):d()}function e(a, +b){function c(){f.m=G;f.r=f.c}function d(){f.m=Ua;f.r=f.d}var e,f,g,n,t;a.valid=0;a.na=a.sa=dc;a.aa=a.ha;n=t=a.some=0;for(g=1;g<=a.g;g++){e=a.n[g];e.m=A;e.r=e.J=0;if(e.type==Sa||e.type==I||e.type==B)e.c>+b.Gb&&(a.na=jc,0==a.some&&b.cb!=Ob&&(a.some=g)),n<+e.c&&(n=+e.c);if(e.type==Ta||e.type==I||e.type==B)e.d<-b.Gb&&(a.na=jc,0==a.some&&b.cb!=Ob&&(a.some=g)),n<-e.d&&(n=-e.d)}for(e=g=1;e<=a.i;e++)f=a.f[e],gg*f.u?d():Math.abs(f.c)<=Math.abs(f.d)?c():d():f.type==B&&(f.m=Na,f.r=f.c);f.J=f.u;a.aa+=f.u*f.r;if(f.type==Ka||f.type==Sa)g*f.J<-b.tb&&(a.sa=jc,0==a.some&&b.cb==Ob&&(a.some=a.g+e)),t<-g*f.J&&(t=-g*f.J);if(f.type==Ka||f.type==Ta)g*f.J>+b.tb&&(a.sa=jc,0==a.some&&b.cb==Ob&&(a.some=a.g+e)),t<+g*f.J&&(t=+g*f.J)}b.o>=fc&&0==b.fb&&x("~"+a.$+": obj = "+a.aa+" infeas = "+(b.cb==Ob?n:t)+"");b.o>=Wb&&0==b.fb&&(a.na==dc&&a.sa==dc?x("OPTIMAL SOLUTION FOUND"): +a.na==jc?x("PROBLEM HAS NO FEASIBLE SOLUTION"):b.cb==Ob?x("PROBLEM HAS UNBOUNDED SOLUTION"):x("PROBLEM HAS NO DUAL FEASIBLE SOLUTION"))}var f;null!=a&&3621377730==a.Dd||w("glp_simplex: P = "+a+"; invalid problem object");null!=a.V&&0!=a.V.reason&&w("glp_simplex: operation not allowed");null==b&&(b=new kc);b.o!=lc&&b.o!=Lb&&b.o!=fc&&b.o!=Wb&&b.o!=mc&&w("glp_simplex: msg_lev = "+b.o+"; invalid parameter");b.cb!=Ob&&b.cb!=Qb&&b.cb!=Tb&&w("glp_simplex: meth = "+b.cb+"; invalid parameter");b.fd!=nc&&b.fd!= +oc&&w("glp_simplex: pricing = "+b.fd+"; invalid parameter");b.ne!=pc&&b.ne!=qc&&w("glp_simplex: r_test = "+b.ne+"; invalid parameter");0b.Gb||w("glp_simplex: tol_bnd = "+b.Gb+"; invalid parameter");0b.tb||w("glp_simplex: tol_dj = "+b.tb+"; invalid parameter");0b.xe||w("glp_simplex: tol_piv = "+b.xe+"; invalid parameter");0>b.oc&&w("glp_simplex: it_lim = "+b.oc+"; invalid parameter");0>b.sb&&w("glp_simplex: tm_lim = "+b.sb+"; invalid parameter");1>b.bc&&w("glp_simplex: out_frq = "+ +b.bc+"; invalid parameter");0>b.fb&&w("glp_simplex: out_dly = "+b.fb+"; invalid parameter");b.yc!=bb&&b.yc!=cb&&w("glp_simplex: presolve = "+b.yc+"; invalid parameter");a.na=a.sa=Aa;a.aa=0;a.some=0;for(f=1;f<=a.g;f++){var g=a.n[f];if(g.type==I&&g.c>=g.d)return b.o>=Lb&&x("glp_simplex: row "+f+": lb = "+g.c+", ub = "+g.d+"; incorrect bounds"),f=rc}for(f=1;f<=a.i;f++)if(g=a.f[f],g.type==I&&g.c>=g.d)return b.o>=Lb&&x("glp_simplex: column "+f+": lb = "+g.c+", ub = "+g.d+"; incorrect bounds"),f=rc;b.o>= +Wb&&(x("GLPK Simplex Optimizer, v"+ra()+""),x(a.g+" row"+(1==a.g?"":"s")+", "+a.i+" column"+(1==a.i?"":"s")+", "+a.L+" non-zero"+(1==a.L?"":"s")+""));0==a.L?(e(a,b),f=0):f=b.yc?d(a,b):c(a,b);return f},kc=exports.SMCP=function(a){a=a||{};this.o=a.msg_lev||Wb;this.cb=a.meth||Ob;this.fd=a.pricing||oc;this.ne=a.r_test||qc;this.Gb=a.tol_bnd||1E-7;this.tb=a.tol_dj||1E-7;this.xe=a.tol_piv||1E-10;this.hf=a.obj_ll||-s;this.jf=a.obj_ul||+s;this.oc=a.it_lim||2147483647;this.sb=a.tm_lim||2147483647;this.bc=a.out_frq|| +500;this.fb=a.out_dly||0;this.yc=a.presolve||cb},xc=exports.glp_get_status=function(a){var b;b=tc(a);switch(b){case dc:switch(uc(a)){case dc:b=vc;break;case jc:b=wc}}return b},tc=exports.glp_get_prim_stat=function(a){return a.na},uc=exports.glp_get_dual_stat=function(a){return a.sa},yc=exports.glp_get_obj_val=function(a){return a.aa},zc=exports.glp_get_row_stat=function(a,b){1<=b&&b<=a.g||w("glp_get_row_stat: i = "+b+"; row number out of range");return a.n[b].m},Ac=exports.glp_get_row_prim=function(a, +b){1<=b&&b<=a.g||w("glp_get_row_prim: i = "+b+"; row number out of range");return a.n[b].r},Bc=exports.glp_get_row_dual=function(a,b){1<=b&&b<=a.g||w("glp_get_row_dual: i = "+b+"; row number out of range");return a.n[b].J},Cc=exports.glp_get_col_stat=function(a,b){1<=b&&b<=a.i||w("glp_get_col_stat: j = "+b+"; column number out of range");return a.f[b].m},Dc=exports.glp_get_col_prim=function(a,b){1<=b&&b<=a.i||w("glp_get_col_prim: j = "+b+"; column number out of range");return a.f[b].r},Ec=exports.glp_get_col_dual= +function(a,b){1<=b&&b<=a.i||w("glp_get_col_dual: j = "+b+"; column number out of range");return a.f[b].J};exports.glp_get_unbnd_ray=function(a){var b=a.some;b>a.g+a.i&&(b=0);return b}; +var Hc=exports.glp_set_col_kind=function(a,b,c){1<=b&&b<=a.i||w("glp_set_col_kind: j = "+b+"; column number out of range");var d=a.f[b];switch(c){case Ma:d.kind=Ma;break;case Fc:d.kind=Fc;break;case Gc:d.kind=Fc;d.type==I&&0==d.c&&1==d.d||Wa(a,b,I,0,1);break;default:w("glp_set_col_kind: j = "+b+"; kind = "+c+"; invalid column kind")}},Ic=exports.glp_get_col_kind=function(a,b){1<=b&&b<=a.i||w("glp_get_col_kind: j = "+b+"; column number out of range");var c=a.f[b],d=c.kind;switch(d){case Fc:c.type== +I&&0==c.c&&1==c.d&&(d=Gc)}return d},Jc=exports.glp_get_num_int=function(a){for(var b,c=0,d=1;d<=a.i;d++)b=a.f[d],b.kind==Fc&&c++;return c},Kc=exports.glp_get_num_bin=function(a){for(var b,c=0,d=1;d<=a.i;d++)b=a.f[d],b.kind==Fc&&b.type==I&&0==b.c&&1==b.d&&c++;return c}; +exports.glp_intopt=function(a,b){function c(a,b){var c;if(xc(a)!=vc)return b.o>=Lb&&x("glp_intopt: optimal basis to initial LP relaxation not provided"),c=Lc;b.o>=Wb&&x("Integer optimization begins...");var d=a.g;c=a.i;var e,f;a.V=e={};e.i=c;e.wc=d;e.ac=new Int8Array(1+d+c);e.bd=new Float64Array(1+d+c);e.cd=new Float64Array(1+d+c);e.mf=new Int8Array(1+d+c);e.lf=new Float64Array(1+d+c);e.kf=new Float64Array(1+d+c);for(f=1;f<=d;f++){var q=a.n[f];e.ac[f]=q.type;e.bd[f]=q.c;e.cd[f]=q.d;e.mf[f]=q.m;e.lf[f]= +q.r;e.kf[f]=q.J}for(f=1;f<=c;f++)q=a.f[f],e.ac[d+f]=q.type,e.bd[d+f]=q.c,e.cd[d+f]=q.d,e.mf[d+f]=q.m,e.lf[d+f]=q.r,e.kf[d+f]=q.J;e.ih=a.aa;e.fe=0;e.Sc=0;e.ya=null;e.head=e.Xa=null;e.Pd=e.Zf=e.Jg=0;e.Ig=0;e.se=null;e.qe=e.te=null;e.re=null;e.N=null;e.A=a;e.ad=new Int8Array(1+c);e.Fg=e.Gg=0;e.qf=null;e.of=e.rf=null;e.pf=null;d={size:0};d.head=d.Xa=null;d.fh=0;d.N=null;e.Bd=d;e.Xf=null;e.Ne=null;e.Fd=null;e.Bg=new Int32Array(1+c);e.Tg=new Float64Array(1+c);e.p=b;e.hc=ja();e.Lg=0;e.qh=0;e.reason=0;e.pe= +0;e.tf=0;e.Tc=0;e.Jf=0;e.ud=0;e.gf=0;e.stop=0;Mc(e,null);c=Nc(e);var d=e.A,r=d.g;f=d.i;if(r!=e.wc){var n,r=r-e.wc;n=new Int32Array(1+r);for(q=1;q<=r;q++)n[q]=e.wc+q;ab(d,r,n)}r=e.wc;for(q=1;q<=r;q++)Va(d,q,e.ac[q],e.bd[q],e.cd[q]),Fb(d,q,e.mf[q]),d.n[q].r=e.lf[q],d.n[q].J=e.kf[q];for(q=1;q<=f;q++)Wa(d,q,e.ac[r+q],e.bd[r+q],e.cd[r+q]),Gb(d,q,e.mf[r+q]),d.f[q].r=e.lf[r+q],d.f[q].J=e.kf[r+q];d.na=d.sa=dc;d.aa=e.ih;Oc(e.Bd);d.V=null;0==c?a.za==dc?(b.o>=Wb&&x("INTEGER OPTIMAL SOLUTION FOUND"),a.za=vc): +(b.o>=Wb&&x("PROBLEM HAS NO INTEGER FEASIBLE SOLUTION"),a.za=jc):c==Pc?b.o>=Wb&&x("RELATIVE MIP GAP TOLERANCE REACHED; SEARCH TERMINATED"):c==Qc?b.o>=Wb&&x("TIME LIMIT EXCEEDED; SEARCH TERMINATED"):c==Sb?b.o>=Lb&&x("glp_intopt: cannot solve current LP relaxation"):c==Rc&&b.o>=Wb&&x("SEARCH TERMINATED BY APPLICATION");return c}function d(a,b){function d(){Ub(m,q);q=null;Vb(m,a);return n}var e=na,f=e.Fb,m,q=null,r={},n;b.o>=Wb&&x("Preprocessing...");m=Xb();Yb(m,a,Sc);e.Fb=!f||b.o=Wb&&x("PROBLEM HAS NO PRIMAL FEASIBLE SOLUTION"):n==bc&&b.o>=Wb&&x("LP RELAXATION HAS NO DUAL FEASIBLE SOLUTION"));if(0!=n)return n;q=Ba();cc(m,q);if(0==q.g&&0==q.i)return q.za=vc,q.ta=q.ha,b.o>=Wb&&(x("Objective value = "+q.ta+""),x("INTEGER OPTIMAL SOLUTION FOUND BY MIP PREPROCESSOR")),d();if(b.o>=Wb){var t=Jc(q),y=Kc(q);x(q.g+" row"+(1==q.g?"":"s")+", "+q.i+" column"+(1==q.i?"":"s")+", "+q.L+" non-zero"+(1==q.L?"":"s")+"");x(t+" integer variable"+(1==t?"":"s")+", "+(0== +y?"none of":1==t&&1==y?"":1==y?"one of":y==t?"all of":y+" of")+" which "+(1==y?"is":"are")+" binary")}eb(a,r);fb(q,r);e.Fb=!f||b.o=Wb&&x("Solving LP relaxation...");e=new kc;e.o=b.o;q.$=a.$;n=sc(q,e);a.$=q.$;if(0!=n)return b.o>=Lb&&x("glp_intopt: cannot solve LP relaxation"),n=Sb;n=xc(q);n==vc?n=0:n==jc?n=ac:n==wc&&(n=bc);if(0!=n)return n;q.$=a.$;n=c(q,b);a.$=q.$;return q.za!=vc&&q.za!=dc?(a.za=q.za,n):d()}var e,f;null!=a&& +3621377730==a.Dd||w("glp_intopt: P = "+a+"; invalid problem object");null!=a.V&&w("glp_intopt: operation not allowed");null==b&&(b=new Yc);b.o!=lc&&b.o!=Lb&&b.o!=fc&&b.o!=Wb&&b.o!=mc&&w("glp_intopt: msg_lev = "+b.o+"; invalid parameter");b.Jb!=Zc&&b.Jb!=$c&&b.Jb!=ad&&b.Jb!=bd&&b.Jb!=cd&&w("glp_intopt: br_tech = "+b.Jb+"; invalid parameter");b.kc!=dd&&b.kc!=ed&&b.kc!=fd&&b.kc!=gd&&w("glp_intopt: bt_tech = "+b.kc+"; invalid parameter");0b.Ub||w("glp_intopt: tol_int = "+b.Ub+"; invalid parameter"); +0b.we||w("glp_intopt: tol_obj = "+b.we+"; invalid parameter");0>b.sb&&w("glp_intopt: tm_lim = "+b.sb+"; invalid parameter");0>b.bc&&w("glp_intopt: out_frq = "+b.bc+"; invalid parameter");0>b.fb&&w("glp_intopt: out_dly = "+b.fb+"; invalid parameter");0<=b.Me&&256>=b.Me||w("glp_intopt: cb_size = "+b.Me+"; invalid parameter");b.ed!=hd&&b.ed!=id&&b.ed!=jd&&w("glp_intopt: pp_tech = "+b.ed+"; invalid parameter");0>b.ce&&w("glp_intopt: mip_gap = "+b.ce+"; invalid parameter");b.Ed!=bb&&b.Ed!=cb&& +w("glp_intopt: mir_cuts = "+b.Ed+"; invalid parameter");b.Ad!=bb&&b.Ad!=cb&&w("glp_intopt: gmi_cuts = "+b.Ad+"; invalid parameter");b.xd!=bb&&b.xd!=cb&&w("glp_intopt: cov_cuts = "+b.xd+"; invalid parameter");b.vd!=bb&&b.vd!=cb&&w("glp_intopt: clq_cuts = "+b.vd+"; invalid parameter");b.yc!=bb&&b.yc!=cb&&w("glp_intopt: presolve = "+b.yc+"; invalid parameter");b.sd!=bb&&b.sd!=cb&&w("glp_intopt: binarize = "+b.sd+"; invalid parameter");b.Xe!=bb&&b.Xe!=cb&&w("glp_intopt: fp_heur = "+b.Xe+"; invalid parameter"); +a.za=Aa;a.ta=0;for(e=1;e<=a.g;e++)if(f=a.n[e],f.type==I&&f.c>=f.d)return b.o>=Lb&&x("glp_intopt: row "+e+": lb = "+f.c+", ub = "+f.d+"; incorrect bounds"),e=rc;for(e=1;e<=a.i;e++)if(f=a.f[e],f.type==I&&f.c>=f.d)return b.o>=Lb&&x("glp_intopt: column "+e+": lb = "+f.c+", ub = "+f.d+"; incorrect bounds"),e=rc;for(e=1;e<=a.i;e++)if(f=a.f[e],f.kind==Fc){if((f.type==Sa||f.type==I)&&f.c!=Math.floor(f.c))return b.o>=Lb&&x("glp_intopt: integer column "+e+" has non-integer lower bound "+f.c+""),e=rc;if((f.type== +Ta||f.type==I)&&f.d!=Math.floor(f.d))return b.o>=Lb&&x("glp_intopt: integer column "+e+" has non-integer upper bound "+f.d+""),e=rc;if(f.type==B&&f.c!=Math.floor(f.c))return b.o>=Lb&&x("glp_intopt: integer column "+e+" has non-integer fixed value "+f.c+""),e=rc}b.o>=Wb&&(e=Jc(a),f=Kc(a),x("GLPK Integer Optimizer, v"+ra()+""),x(a.g+" row"+(1==a.g?"":"s")+", "+a.i+" column"+(1==a.i?"":"s")+", "+a.L+" non-zero"+(1==a.L?"":"s")+""),x(e+" integer variable"+(1==e?"":"s")+", "+(0==f?"none of":1==e&&1==f? +"":1==f?"one of":f==e?"all of":f+" of")+" which "+(1==f?"is":"are")+" binary"));return e=b.yc?d(a,b):c(a,b)}; +var Yc=exports.IOCP=function(a){a=a||{};this.o=a.msg_lev||Wb;this.Jb=a.br_tech||bd;this.kc=a.bt_tech||fd;this.Ub=a.tol_int||1E-5;this.we=a.tol_obj||1E-7;this.sb=a.tm_lim||2147483647;this.bc=a.out_frq||5E3;this.fb=a.out_dly||1E4;this.ob=a.cb_func||null;this.Uc=a.cb_info||null;this.Me=a.cb_size||0;this.ed=a.pp_tech||jd;this.ce=a.mip_gap||0;this.Ed=a.mir_cuts||cb;this.Ad=a.gmi_cuts||cb;this.xd=a.cov_cuts||cb;this.vd=a.clq_cuts||cb;this.yc=a.presolve||cb;this.sd=a.binarize||cb;this.Xe=a.fp_heur||cb}; +exports.glp_mip_status=function(a){return a.za};exports.glp_mip_obj_val=function(a){return a.ta}; +var kd=exports.glp_mip_row_val=function(a,b){1<=b&&b<=a.g||w("glp_mip_row_val: i = "+b+"; row number out of range");return a.n[b].Sa},ld=exports.glp_mip_col_val=function(a,b){1<=b&&b<=a.i||w("glp_mip_col_val: j = "+b+"; column number out of range");return a.f[b].Sa},Ib=exports.glp_bf_exists=function(a){return 0==a.g||a.valid},Jb=exports.glp_factorize=function(a){function b(a,b,c,d){var e=a.g,f;f=a.head[b];if(f<=e)b=1,c[1]=f,d[1]=1;else for(b=0,a=a.f[f-e].k;null!=a;a=a.I)b++,c[b]=a.n.ea,d[b]=-a.n.ma* +a.j*a.f.va;return b}var c=a.g,d=a.i,e=a.n,f=a.f,g=a.head,h,k,l;h=a.valid=0;for(k=1;k<=c+d;k++)if(k<=c?(l=e[k].m,e[k].bind=0):(l=f[k-c].m,f[k-c].bind=0),l==A){h++;if(h>c)return a=Kb;g[h]=k;k<=c?e[k].bind=h:f[k-c].bind=h}if(hc.Cd&&w("glp_set_bfcp: lu_size = "+c.Cd+"; invalid parameter"),0c.cc||w("glp_set_bfcp: piv_tol = "+c.cc+"; invalid parameter"),1>c.xc&&w("glp_set_bfcp: piv_lim = "+c.xc+"; invalid parameter"),c.gc!=bb&&c.gc!=cb&&w("glp_set_bfcp: suhl = "+c.gc+"; invalid parameter"),0<=c.Mb&&1E-6>=c.Mb|| +w("glp_set_bfcp: eps_tol = "+c.Mb+"; invalid parameter"),1>c.sc&&w("glp_set_bfcp: max_gro = "+c.sc+"; invalid parameter"),1<=c.$c&&32767>=c.$c||w("glp_set_bfcp: nfs_max = "+c.$c+"; invalid parameter"),0c.ic||w("glp_set_bfcp: upd_tol = "+c.ic+"; invalid parameter"),1<=c.vc&&32767>=c.vc||w("glp_set_bfcp: nrs_max = "+c.vc+"; invalid parameter"),0>c.kd&&w("glp_set_bfcp: rs_size = "+c.vc+"; invalid parameter"),0==c.kd&&(c.kd=20*c.vc));null!=a.U&&nd(a)},td=exports.glp_get_bhead=function(a,b){0== +a.g||a.valid||w("glp_get_bhead: basis factorization does not exist");1<=b&&b<=a.g||w("glp_get_bhead: k = "+b+"; index out of range");return a.head[b]},ud=exports.glp_get_row_bind=function(a,b){0==a.g||a.valid||w("glp_get_row_bind: basis factorization does not exist");1<=b&&b<=a.g||w("glp_get_row_bind: i = "+b+"; row number out of range");return a.n[b].bind},vd=exports.glp_get_col_bind=function(a,b){0==a.g||a.valid||w("glp_get_col_bind: basis factorization does not exist");1<=b&&b<=a.i||w("glp_get_col_bind: j = "+ +b+"; column number out of range");return a.f[b].bind},xd=exports.glp_ftran=function(a,b){var c=a.g,d=a.n,e=a.f,f,g;0==c||a.valid||w("glp_ftran: basis factorization does not exist");for(f=1;f<=c;f++)b[f]*=d[f].ma;0b.d+f&&(a.na=Ad)}for(d=1;d<=a.i;d++)if(b=a.f[d],b.m==A){b.r=e[b.bind];c=b.type;if(c==Sa||c==I||c==B)f=1E-6+1E-9*Math.abs(b.c),b.rb.d+f&&(a.na=Ad)}a.aa=a.ha;for(d=1;d<=a.i;d++)b=a.f[d],a.aa+=b.u*b.r;for(d=1;d<=a.g;d++)e[d]=0;for(d=1;d<=a.i;d++)b=a.f[d],b.m== +A&&(e[b.bind]=b.u);zd(a,e);a.sa=dc;for(d=1;d<=a.g;d++)if(b=a.n[d],b.m==A)b.J=0;else if(b.J=-e[d],c=b.m,b=a.dir==za?+b.J:-b.J,(c==Ra||c==G)&&-1E-5>b||(c==Ra||c==Ua)&&1E-5b||(c==Ra||c==Ua)&&1E-5f||w("glp_prim_rtest: eps = "+f+"; invalid parameter");h=kb(a);k=lb(a);l=0;C=s;r=0;for(p=1;p<=b;p++)if(g=c[p],1<=g&&g<=h+k||w("glp_prim_rtest: ind["+p+"] = "+g+"; variable number out of range"),g<=h?(m=ob(a,g),t=pb(a,g),y=qb(a,g),q=zc(a,g),n=Ac(a,g)):(m=rb(a,g-h),t=sb(a,g-h), +y=tb(a,g-h),q=Cc(a,g-h),n=Dc(a,g-h)),q!=A&&w("glp_prim_rtest: ind["+p+"] = "+g+"; non-basic variable not allowed"),g=0-f)continue;E=(t-n)/g}else if(m==Ta){if(g<+f)continue;E=(y-n)/g}else if(m==I)if(0>g){if(g>-f)continue;E=(t-n)/g}else{if(g<+f)continue;E=(y-n)/g}else if(m==B){if(-fE&&(E=0);if(C>E||C==E&&rf||w("glp_dual_rtest: eps = "+f+"; invalid parameter");h=kb(a);k=lb(a);n=jb(a)==za?1:-1;l=0;y=s;q=0;for(p=1;p<=b;p++){g=c[p];1<=g&&g<=h+k||w("glp_dual_rtest: ind["+p+"] = "+g+"; variable number out of range");g<=h?(m=zc(a,g),r=Bc(a,g)):(m=Cc(a,g-h),r=Ec(a,g-h));m==A&&w("glp_dual_rtest: ind["+p+"] = "+g+"; basic variable not allowed");g=0-f)continue;t=n*r/g}else if(m== +Ra){if(-ft&&(t=0);if(y>t||y==t&&q=f)return 1;l=1}else if(e==Ta){if(q<=f)return 1;l=-1}else w("glp_analyze_row: type = "+e+"; invalid parameter");e=f-q;b=Fd(a,b,c,d,l,1E-9);if(0==b)return 2;k=c[b];m=k<=a.g?a.n[k].r:a.f[k-a.g].r;c=e/d[b];g(b,m,c,q,e,k<=a.g?a.n[k].J*c:a.f[k-a.g].J*c);return p} +exports.glp_analyze_bound=function(a,b,c){var d,e,f,g,h,k,l,p,m,q,r,n,t,y;r=n=t=y=null;null!=a&&3621377730==a.Dd||w("glp_analyze_bound: P = "+a+"; invalid problem object");e=a.g;f=a.i;a.na==dc&&a.sa==dc||w("glp_analyze_bound: optimal basic solution required");0==e||a.valid||w("glp_analyze_bound: basis factorization required");1<=b&&b<=e+f||w("glp_analyze_bound: k = "+b+"; variable number out of range");d=b<=e?a.n[b]:a.f[b-e];g=d.m;f=d.r;g==A&&w("glp_analyze_bound: k = "+b+"; basic variable not allowed "); +g=new Int32Array(1+e);q=new Float64Array(1+e);k=Cd(a,b,g,q);for(b=-1;1>=b;b+=2)l=Ed(a,k,g,q,b,1E-9),0==l?(h=0,l=0>b?-s:+s):(h=g[l],h<=e?(d=a.n[h],p=pb(a,d.ea),m=qb(a,d.ea)):(d=a.f[h-e],p=sb(a,d.C),m=tb(a,d.C)),d=d.r,d=0>b&&0q[l]?p-d:m-d,l=f+d/q[l]),0>b?(r=l,n=h):(t=l,y=h);c(r,n,t,y)}; +exports.glp_analyze_coef=function(a,b,c){var d,e,f,g,h,k,l,p,m,q,r,n,t,y,E,C,D,H,R,V,O=null,Q=null,F=null,W=null,X=null,ca=null;null!=a&&3621377730==a.Dd||w("glp_analyze_coef: P = "+a+"; invalid problem object");e=a.g;f=a.i;a.na==dc&&a.sa==dc||w("glp_analyze_coef: optimal basic solution required");0==e||a.valid||w("glp_analyze_coef: basis factorization required");1<=b&&b<=e+f||w("glp_analyze_coef: k = "+b+"; variable number out of range");b<=e?(d=a.n[b],g=d.type,n=d.c,t=d.d,y=0):(d=a.f[b-e],g=d.type, +n=d.c,t=d.d,y=d.u);h=d.m;E=d.r;h!=A&&w("glp_analyze_coef: k = "+b+"; non-basic variable not allowed");h=new Int32Array(1+e);V=new Float64Array(1+e);r=new Int32Array(1+f);R=new Float64Array(1+f);m=Bd(a,b,r,R);for(f=-1;1>=f;f+=2)a.dir==za?l=-f:a.dir==Ea&&(l=+f),q=Fd(a,m,r,R,l,1E-9),0==q?(C=0>f?-s:+s,k=0,q=E):(k=r[q],d=k<=e?a.n[k]:a.f[k-e],l=d.J,d=-l/R[q],C=y+d,l=0>f&&0R[q]?1:-1,a.dir==Ea&&(l=-l),p=Cd(a,k,h,V),d=b<=e?a.n[b]:a.f[b-e],d.type=Ka,d.c=d.d=0,p=Ed(a,p,h,V,l,1E-9),d=b<=e?a.n[b]: +a.f[b-e],d.type=g,d.c=n,d.d=t,0==p?q=0>l&&0R[q]?-s:+s:(d=h[p],d<=e?(d=a.n[d],D=pb(a,d.ea),H=qb(a,d.ea)):(d=a.f[d-e],D=sb(a,d.C),H=tb(a,d.C)),d=d.r,d=0>l&&0V[p]?D-d:H-d,q=E+R[q]/V[p]*d)),0>f?(O=C,Q=k,F=q):(W=C,X=k,ca=q);c(O,Q,F,W,X,ca)};exports.glp_ios_reason=function(a){return a.reason};exports.glp_ios_get_prob=function(a){return a.A};function Hd(a){a.reason!=Ia&&w("glp_ios_pool_size: operation not allowed");return a.Bd.size} +function Id(a,b,c,d,e,f,g){a.reason!=Ia&&w("glp_ios_add_row: operation not allowed");var h=a.Bd,k,l;k={name:null};0<=b&&255>=b||w("glp_ios_add_row: klass = "+b+"; invalid cut class");k.qc=b;k.k=null;0<=c&&c<=a.i||w("glp_ios_add_row: len = "+c+"; invalid cut length");for(l=1;l<=c;l++)b={},1<=d[l]&&d[l]<=a.i||w("glp_ios_add_row: ind["+l+"] = "+d[l]+"; column index out of range"),b.C=d[l],b.j=e[l],b.e=k.k,k.k=b;f!=Sa&&f!=Ta&&f!=B&&w("glp_ios_add_row: type = "+f+"; invalid cut type");k.type=f;k.cg=g; +k.ca=h.Xa;k.e=null;null==k.ca?h.head=k:k.ca.e=k;h.Xa=k;h.size++}function Jd(a,b){1<=b&&b<=a.A.i||w("glp_ios_can_branch: j = "+b+"; column number out of range");return a.ad[b]} +function Kd(a,b){var c=a.A,d=a.wc,e=a.i,f,g;g=c.ha;for(f=1;f<=e;f++){var h=c.f[f];if(h.kind==Fc&&b[f]!=Math.floor(b[f]))return 1;g+=h.u*b[f]}if(c.za==dc)switch(c.dir){case za:if(g>=a.A.ta)return 1;break;case Ea:if(g<=a.A.ta)return 1}a.p.o>=fc&&x("Solution found by heuristic: "+g+"");c.za=dc;c.ta=g;for(f=1;f<=e;f++)c.f[f].Sa=b[f];for(e=1;e<=d;e++)for(f=c.n[e],f.Sa=0,g=f.k;null!=g;g=g.B)f.Sa+=g.j*g.f.Sa;return 0}exports.glp_mpl_alloc_wksp=function(){return Ld()}; +exports._glp_mpl_init_rand=function(a,b){0!=a.D&&w("glp_mpl_init_rand: invalid call sequence\n");Md(a.Gd,b)};var Od=exports.glp_mpl_read_model=function(a,b,c,d){0!=a.D&&w("glp_mpl_read_model: invalid call sequence");a=Nd(a,b,c,d);1==a||2==a?a=0:4==a&&(a=1);return a};exports.glp_mpl_read_model_from_string=function(a,b,c,d){var e=0;return Od(a,b,function(){return eh)h=0;if(g==Ka||g==Sa||1Math.abs(h)&&(h=0),1E-9>Math.abs(k)&&(k=0),ne(a,d,g,h,k); +for(d=1;d<=f;d++)c==Zb?(g=Cc(b,d),h=Dc(b,d),k=Ec(b,d)):c==le?(g=0,h=glp_ipt_col_prim(b,d),k=glp_ipt_col_dual(b,d)):c==Sc&&(g=0,h=ld(b,d),k=0),1E-9>Math.abs(h)&&(h=0),1E-9>Math.abs(k)&&(k=0),oe(a,d,g,h,k);a=pe(a);3==a?a=0:4==a&&(a=1);return a}; +function qe(a,b){var c,d,e;c=null;for(d=a.root;null!=d;)c=d,0>=a.yg(a.info,b,c.key)?(e=0,d=c.left,c.pa++):(e=1,d=c.right);d={};d.key=b;d.type=0;d.link=null;d.pa=1;d.R=c;d.ba=null==c?0:e;d.wa=0;d.left=null;d.right=null;a.size++;for(null==c?a.root=d:0==e?c.left=d:c.right=d;null!=c;){if(0==e){if(0c.wa){re(a,c);break}c.wa=-1}else{if(0>c.wa){c.wa=0;break}if(0b.wa?(c=b.R,d=b.left,e=d.right,0>=d.wa?(null==c?a.root=d:0==b.ba?c.left=d:c.right=d,b.pa-=d.pa,d.R=c,d.ba=b.ba,d.wa++,d.right=b,b.R=d,b.ba=1,b.wa=-d.wa,b.left=e,null!=e&&(e.R=b,e.ba=0)):(f=e.left,g=e.right,null==c?a.root=e:0==b.ba?c.left=e:c.right=e,b.pa-=d.pa+e.pa,e.pa+=d.pa,b.wa=0<=e.wa?0:1,d.wa=0>=e.wa?0:-1,e.R=c,e.ba=b.ba,e.wa=0,e.left=d,e.right=b,b.R=e,b.ba=1,b.left=g,d.R=e,d.ba=0,d.right=f,null!=f&&(f.R=d,f.ba=1),null!=g&&(g.R=b,g.ba=0))):(c=b.R,d=b.right,e= +d.left,0<=d.wa?(null==c?a.root=d:0==b.ba?c.left=d:c.right=d,d.pa+=b.pa,d.R=c,d.ba=b.ba,d.wa--,d.left=b,b.R=d,b.ba=0,b.wa=-d.wa,b.right=e,null!=e&&(e.R=b,e.ba=1)):(f=e.left,g=e.right,null==c?a.root=e:0==b.ba?c.left=e:c.right=e,d.pa-=e.pa,e.pa+=b.pa,b.wa=0>=e.wa?0:-1,d.wa=0<=e.wa?0:1,e.R=c,e.ba=b.ba,e.wa=0,e.left=b,e.right=d,b.R=e,b.ba=0,b.right=f,d.R=e,d.ba=1,d.left=g,null!=f&&(f.R=b,f.ba=1),null!=g&&(g.R=d,g.ba=0)))}var pd=1,qd=2,se=3,te=4,ue=5; +function od(a,b,c,d){var e,f;f=a.valid=0;switch(a.type){case md:a.Za=null;null==a.gb&&(f={},f.hb=f.g=0,f.valid=0,f.ia=ve(),f.$d=50,f.Pb=0,f.Ze=f.af=f.$e=null,f.je=f.ie=null,f.ug=null,f.vg=null,f.ic=1E-6,f.ag=0,a.gb=f,f=1);break;case rd:case sd:a.gb=null,null==a.Za&&(we&&x("lpf_create_it: warning: debug mode enabled"),f={valid:0},f.Nc=f.ef=0,f.ia=ve(),f.g=0,f.Cf=null,f.K=50,f.i=0,f.Md=f.Ld=null,f.Od=f.Nd=null,f.Qc=null,f.Ge=f.Fe=null,f.Ie=f.He=null,f.Jd=1E3,f.od=0,f.Wb=null,f.Xb=null,f.jb=f.Gc=null, +a.Za=f,f=1)}null!=a.gb?e=a.gb.ia:null!=a.Za&&(e=a.Za.ia);f&&(e.Va=a.Cd);e.cc=a.cc;e.xc=a.xc;e.gc=a.gc;e.Mb=a.Mb;e.sc=a.sc;null!=a.gb&&(f&&(a.gb.$d=a.$c),a.gb.ic=a.ic);null!=a.Za&&(f&&(a.Za.K=a.vc),f&&(a.Za.Jd=a.kd));if(null!=a.gb){a:{e=a.gb;1>b&&w("fhv_factorize: m = "+b+"; invalid parameter");1E8b&&w("lpf_factorize: m = "+b+"; invalid parameter");1E8=f||w("scf_create_it: n_max = "+f+"; invalid parameter"),g={},g.K=f,g.i=0,g.Nb=new Float64Array(1+f*f),g.v=new Float64Array(1+f*(f+1)/2),g.s=new Int32Array(1+f),g.fg=De,g.pa=0,g.l=Ce?new Float64Array(1+f*f):null,g.ig=new Float64Array(1+f),e.Qc=g);null==e.Wb&&(e.Wb=new Int32Array(1+ +e.Jd));null==e.Xb&&(e.Xb=new Float64Array(1+e.Jd));e.Ncb?"\n"==a.l?(a.count--,b=-1):(e(a,"missing final end of line"),b="\n"):"\n"!=b&&(0<=" \t\n\v\f\r".indexOf(b)?b=" ":ta(b)&&d(a,"invalid control character "+b.charCodeAt(0)));a.l=b}function g(a){a.h+=a.l;f(a)}function h(a,b){return a.toLowerCase()==b.toLowerCase()?1:0}function k(a){function b(){for(a.b= +Q;va(a.l)||0<=Xe.indexOf(a.l);)g(a);c&&(h(a.h,"minimize")?a.b=y:h(a.h,"minimum")?a.b=y:h(a.h,"min")?a.b=y:h(a.h,"maximize")?a.b=E:h(a.h,"maximum")?a.b=E:h(a.h,"max")?a.b=E:h(a.h,"subject")?" "==a.l&&(f(a),"t"==a.l.toLowerCase()&&(a.b=C,a.h+=" ",g(a),"o"!=a.l.toLowerCase()&&d(a,"keyword `subject to' incomplete"),g(a),ua(a.l)&&d(a,"keyword `"+a.h+a.l+"...' not recognized"))):h(a.h,"such")?" "==a.l&&(f(a),"t"==a.l.toLowerCase()&&(a.b=C,a.h+=" ",g(a),"h"!=a.l.toLowerCase()&&d(a,"keyword `such that' incomplete"), +g(a),"a"!=a.l.toLowerCase()&&d(a,"keyword `such that' incomplete"),g(a),"t"!=a.l.toLowerCase()&&d(a,"keyword `such that' incomplete"),g(a),ua(a.l)&&d(a,"keyword `"+a.h+a.l+"...' not recognized"))):h(a.h,"st")?a.b=C:h(a.h,"s.t.")?a.b=C:h(a.h,"st.")?a.b=C:h(a.h,"bounds")?a.b=D:h(a.h,"bound")?a.b=D:h(a.h,"general")?a.b=H:h(a.h,"generals")?a.b=H:h(a.h,"gen")?a.b=H:h(a.h,"integer")?a.b=R:h(a.h,"integers")?a.b=R:h(a.h,"int")?a.b=R:h(a.h,"binary")?a.b=V:h(a.h,"binaries")?a.b=V:h(a.h,"bin")?a.b=V:h(a.h,"end")&& +(a.b=O))}var c;a.b=-1;a.h="";for(a.value=0;;){for(c=0;" "==a.l;)f(a);if(-1==a.l)a.b=t;else if("\n"==a.l)if(f(a),ua(a.l))c=1,b();else continue;else if("\\"==a.l){for(;"\n"!=a.l;)f(a);continue}else if(ua(a.l)||"."!=a.l&&0<=Xe.indexOf(a.l))b();else if(wa(a.l)||"."==a.l){for(a.b=F;wa(a.l);)g(a);if("."==a.l)for(g(a),1!=a.h.length||wa(a.l)||d(a,"invalid use of decimal point");wa(a.l);)g(a);if("e"==a.l||"E"==a.l)for(g(a),"+"!=a.l&&"-"!=a.l||g(a),wa(a.l)||d(a,"numeric constant `"+a.h+"' incomplete");wa(a.l);)g(a); +a.value=Number(a.h);a.value==Number.NaN&&d(a,"numeric constant `"+a.h+"' out of range")}else"+"==a.l?(a.b=W,g(a)):"-"==a.l?(a.b=X,g(a)):":"==a.l?(a.b=ca,g(a)):"<"==a.l?(a.b=ka,g(a),"="==a.l&&g(a)):">"==a.l?(a.b=P,g(a),"="==a.l&&g(a)):"="==a.l?(a.b=u,g(a),"<"==a.l?(a.b=ka,g(a)):">"==a.l&&(a.b=P,g(a))):d(a,"character `"+a.l+"' not recognized");break}for(;" "==a.l;)f(a)}function l(a,b){var c=xb(a.Ka,b);if(0==c){c=Oa(a.Ka,1);Qa(a.Ka,c,b);if(a.Kf&&d(a,"invalid use of `-inf' as upper bound"),q(a,b,+s),k(a)):d(a,"missing upper bound")):a.b==F?(q(a,b,a.value),k(a)):d(a,"missing upper bound")): +a.b==P?(c&&d(a,"invalid bound definition"),k(a),a.b==W||a.b==X?(f=a.b==W?1:-1,k(a),a.b==F?(m(a,b,f*a.value),k(a)):h(a.h,"infinity")||0==h(a.h,"inf")?(0Xe.indexOf(a[b]))return 1;return 0}function e(a){for(var b=0;b= "+l.c;else if(l.type==Ta)q=" <= "+l.d;else if(l.type==I||l.type==B)q=" = "+l.c;72= "+l.c),r++):l.type==Ta?(c(" -Inf <= "+b+" <= "+l.d), +r++):l.type==I?(c(" "+l.c+" <= "+b+" <= "+l.d),r++):l.type==B&&(c(" "+b+" = "+l.c),r++);q&&c("");r++;q=0;for(m=1;m<=a.i;m++)l=a.f[m],l.kind!=Ma&&(q||(c("Generals"),q=1,r++),c(" "+g(k,m)),r++);q&&(c(""),r++);return h()};exports.glp_read_lp_from_string=function(a,b,c){var d=0;return Ye(a,b,function(){return dp[u]&&Ze(h,u,l[u]+10))return a.valid= +0,h.Va=h.Ga+h.Ga,a=Re;f=k[u]+l[u];D[f]=b;H[f]=ka[z];l[u]++;dd)return a.valid=0,a=ze;u=t[e];b=C[e];for(z=e;zn[b]&&$e(h,b,r[b]+10))return a.valid=0,h.Va=h.Ga+h.Ga,a=Re;X=q[b]+r[b];D[X]=u;H[X]=e;r[b]++;c++;ca[c]=b;ka[c]=e}if(p[u]=b?a:b));m=new Int32Array(1+a);q=new Int32Array(1+b);r=new Int32Array(1+a);n=new Int32Array(1+a);y=new Int32Array(1+b);E=new Int32Array(1+b);for(D=1;D<=b;D++)H=d(c,-D,p),y[D]=m[H],m[H]=D;for(H=t=0;H<=a;H++)for(D=m[H];0!=D;D=y[D])E[D]=t,t=D;H=0;for(D=t;0!=D;D=E[D])y[D]=H,H=D;for(C=1;C<=a;C++)m[C]=H=d(c,+C,p),r[C]=0,n[C]=q[H],0!=n[C]&&(r[n[C]]= +C),q[H]=C;for(C=1;C<=a;C++)k[C]=0;for(D=1;D<=b;D++)l[D]=0;R=1;for(V=b;R<=V;){C=q[1];if(0!=C){D=0;for(O=d(c,+C,p);1<=O;O--)H=p[O],0==l[H]&&(D=H);k[C]=l[D]=R;R++;Q++}else D=t,l[D]=V,V--;0==y[D]?t=E[D]:E[y[D]]=E[D];0!=E[D]&&(y[E[D]]=y[D]);for(O=d(c,-D,p);1<=O;O--)C=p[O],H=m[C],0==r[C]?q[H]=n[C]:n[r[C]]=n[C],0!=n[C]&&(r[n[C]]=r[C]),m[C]=--H,r[C]=0,n[C]=q[H],0!=n[C]&&(r[n[C]]=C),q[H]=C}for(C=1;C<=a;C++)0==k[C]&&(k[C]=R++);for(D=1;D<=b;D++);for(r=1;r<=a;r++)m[r]=0;for(C=1;C<=a;C++)r=k[C],m[r]=C;for(H=1;H<= +b;H++)q[H]=0;for(D=1;D<=b;D++)H=l[D],q[H]=D;for(r=1;r<=Q;r++)for(C=m[r],O=d(c,+C,p);1<=O;O--);return Q}function c(a,b,c){var d=kb(a);lb(a);var k,l,p,m=0;if(0c;d--)a.ya[d].rb=null,a.ya[d].e=a.Sc,a.Sc=d;d=a.Sc;a.Sc=a.ya[d].e;a.ya[d].e=0;c=d;d={};a.ya[c].rb=d;d.s=c;d.R=b;d.La=null==b?0:b.La+1;d.count=0;d.Na=null;d.zc=null;d.ec=null;d.eg=0;d.rc=null==b?a.A.dir==za?-s:+s:b.rc;d.bound=null==b?a.A.dir==za?-s:+s:b.bound;d.Tc=0;d.tg=0;d.Ag=0;d.Zc=0;d.Rd=0;d.data=0==a.p.Me?null:{};d.ja=null;d.ca=a.Xa;d.e=null;null==a.head? +a.head=d:a.Xa.e=d;a.Xa=d;a.Pd++;a.Zf++;a.Jg++;null!=b&&b.count++;return d} +function pf(a,b){var c=a.A,d,e,f,g;d=a.ya[b].rb;a.N=d;e=a.ya[1].rb;if(d!=e){for(d.ja=null;null!=d;d=d.R)null!=d.R&&(d.R.ja=d);for(d=e;null!=d;d=d.ja){var h=c.g;e=c.i;if(null==d.ja){a.Fg=h;a.Ggl;g--){h=b.n[g];t={};f=mb(b,g);t.name=null==f?null:f;t.type=h.type;t.c=h.c;t.d=h.d;t.k=null;p=ub(b,g,m,n);for(f=1;f<=p;f++)q={},q.C=m[f],q.j=n[f],q.e=t.k,t.k=q;t.ma=h.ma;t.m=h.m;t.e=e.ec;e.ec=t}if(c!=k){c-=k;e=new Int32Array(1+c);for(g=1;g<=c;g++)e[g]=k+g;ab(b,c,e)}c=b.g;for(g=1;g<=c;g++)Va(b,g,a.se[g],a.qe[g],a.te[g]),Fb(b,g,a.re[g]);for(k=1;k<=d;k++)Wa(b,k,a.se[c+k],a.qe[c+k],a.te[c+k]),Gb(b,k,a.re[c+k])}a.N= +null}function rf(a,b,c){var d;b=a.ya[b].rb;null==b.ca?a.head=b.e:b.ca.e=b.e;null==b.e?a.Xa=b.ca:b.e.ca=b.ca;b.ca=b.e=null;a.Pd--;for(d=1;2>=d;d++)c[d]=Mc(a,b).s} +function sf(a,b){var c;c=a.ya[b].rb;null==c.ca?a.head=c.e:c.ca.e=c.e;null==c.e?a.Xa=c.ca:c.e.ca=c.ca;c.ca=c.e=null;for(a.Pd--;;){for(var d;null!=c.Na;)d=c.Na,c.Na=d.e;for(;null!=c.zc;)d=c.zc,c.zc=d.e;for(;null!=c.ec;){d=c.ec;for(d.name=null;null!=d.k;)d.k=d.k.e;c.ec=d.e}b=c.s;a.ya[b].rb=null;a.ya[b].e=a.Sc;a.Sc=b;c=c.R;a.Zf--;if(null!=c&&(c.count--,0==c.count))continue;break}} +function tf(a,b,c){var d=a.A,e=d.g,f,g,h,k,l=a.Bg,p=a.Tg,m,q;xc(d);Ib(d);a=d.f[b].r;b=Bd(d,e+b,l,p);for(f=-1;1>=f;f+=2)if(h=l,g=Fd(d,b,h,p,f,1E-9),g=0==g?0:h[g],0==g)d.dir==za?0>f?m=+s:q=+s:d.dir==Ea&&(0>f?m=-s:q=-s);else{for(h=1;h<=b&&l[h]!=g;h++);h=p[h];g<=e?(k=d.n[g].m,g=d.n[g].J):(k=d.f[g-e].m,g=d.f[g-e].J);if(d.dir==za){if(k==G&&0>g||k==Ua&&0g||k==Ra)&&(g=0);k=(0>f?Math.floor(a):Math.ceil(a))-a;k/=h;h=g*k;0>f?m=d.aa+h:q=d.aa+h}c(m,q)} +function uf(a,b){var c=a.A,d=c.i,e,f,g,h=a.Bg,k;g=0;k=c.ha;e=0;for(f=1;f<=d;f++){var l=c.f[f];if(0!=l.u)if(l.type==B)k+=l.u*l.r;else{if(l.kind!=Fc||l.u!=Math.floor(l.u))return b;2147483647>=Math.abs(l.u)?h[++g]=Math.abs(l.u)|0:e=1}}if(0==e){if(0==g)return b;d=0;for(e=1;e<=g;e++){if(1==e)d=h[1];else for(f=h[e],l=void 0;0=Math.floor(c)+0.001&&(c=Math.ceil(c),b=e*c+k)):c.dir==Ea&&b!=-s&&(c=(b-k)/e,c<=Math.ceil(c)-0.001&&(c=Math.floor(c), +b=e*c+k));return b}function vf(a,b){var c=a.A,d=1,e;if(c.za==dc)switch(e=a.p.we*(1+Math.abs(c.ta)),c.dir){case za:b>=c.ta-e&&(d=0);break;case Ea:b<=c.ta+e&&(d=0)}else switch(c.dir){case za:b==+s&&(d=0);break;case Ea:b==-s&&(d=0)}return d}function wf(a){var b=null;switch(a.A.dir){case za:for(a=a.head;null!=a;a=a.e)if(null==b||b.bound>a.bound)b=a;break;case Ea:for(a=a.head;null!=a;a=a.e)if(null==b||b.boundb[f])if(d[f]==+s)if(0==g)g=f;else{h=-s;g=0;break}else h+=b[f]*d[f];e.Wd=h;e.Uf=g;g=h=0;for(f=1;f<=a;f++)if(0b[f])if(c[f]==-s)if(0==g)g=f;else{h=+s;g=0;break}else h+=b[f]*c[f];e.Vd=h;e.Tf=g}function d(a,b){b(0==a.Uf?a.Wd:-s,0==a.Tf?a.Vd:+s)}function e(a,b,c,d,e,f,g,h){var k, +l,m,n;c==-s||a.Vd==+s?k=-s:0==a.Tf?0b[g]&&(k=c-(a.Vd-b[g]*e[g])):k=a.Tf==g?c-a.Vd:-s;d==+s||a.Wd==-s?l=+s:0==a.Uf?0b[g]&&(l=d-(a.Wd-b[g]*f[g])):l=a.Uf==g?d-a.Wd:+s;1E-6>Math.abs(b[g])?(m=-s,n=+s):0b[g]&&(m=l==+s?-s:l/b[g],n=k==-s?+s:k/b[g]);h(m,n)}function f(a,b,c,e,f){var g=0,h=b[c],k=e[f],l=null,m=null;d(a,function(a,b){l=a;m=b});if(h!=-s&&(a=0.001*(1+Math.abs(h)),mk+a))return 1;h!=-s&&(a=1E-12*(1+Math.abs(h)),l>h-a&&(b[c]=-s));k!=+s&&(a=1E-12*(1+Math.abs(k)),mp-Math.floor(p)?Math.floor(p):Math.ceil(p)),r!=+s&&(r=0.001>Math.ceil(r)-r?Math.ceil(r):Math.floor(r)));if(n!=-s&&(a=0.001*(1+Math.abs(n)),rq+a))return 1;p!=-s&&(a=0.001*(1+Math.abs(p)),nr+a&&(q=r));n!=-s&&q!=+s&&(a=Math.abs(n),b=Math.abs(q),n>q-1E-10*(1+(a<=b?a:b))&&(n==f[k]?q=n:q==g[k]?n=q:a<=b?q=n:n=q));l(n,q);return m}function h(a,b,c,d,e){var f,g=0;b=0.25*f&&g++));c>e&&(a||c==+s?g++:(f=b==-s?1+Math.abs(c):1+(c-b),c-e>=0.25*f&&g++));return g}var k=a.A,l=k.g,p=k.i,m,q,r,n=0,t,y,E,C;t=new Float64Array(1+l);y=new Float64Array(1+l);switch(k.za){case Aa:t[0]=-s;y[0]=+s;break;case dc:switch(k.dir){case za:t[0]= +-s;y[0]=k.ta-k.ha;break;case Ea:t[0]=k.ta-k.ha,y[0]=+s}}for(m=1;m<=l;m++)t[m]=pb(k,m),y[m]=qb(k,m);E=new Float64Array(1+p);C=new Float64Array(1+p);for(m=1;m<=p;m++)E[m]=sb(k,m),C[m]=tb(k,m);q=l+1;r=new Int32Array(1+q);for(m=1;m<=q;m++)r[m]=m-1;if(function(a,b,d,e,k,l,m,n){var q=a.g,p=a.i,r={},t,u,E=0,C,v,y,M,ba,J,ea,fa;C=new Int32Array(1+p);v=new Int32Array(1+q+1);y=new Int32Array(1+q+1);M=new Int32Array(1+q+1);ba=new Float64Array(1+p);J=new Float64Array(1+p);ea=new Float64Array(1+p);u=0;for(t=1;t<= +l;t++)q=m[t],v[++u]=q,y[q]=1;for(;0=n||b[fa]==-s&&d[fa]==+s||0!=y[fa]||(v[++u]=fa,y[fa]=1)}}return E}(k,t,y,E,C,q,r,b))return 1;for(m=1;m<=l;m++)zc(k,m)==A&&(t[m]==-s&&y[m]==+s?Va(k,m,Ka,0,0):y[m]==+s?Va(k,m,Sa,t[m],0):t[m]==-s&&Va(k,m,Ta,0,y[m]));for(m=1;m<=p;m++)Wa(k,m,E[m]==-s&&C[m]==+s?Ka:C[m]==+s?Sa:E[m]==-s?Ta:E[m]!=C[m]?I:B,E[m],C[m]);return n} +function Nc(a){function b(a,b){var c,d,e,f;d=a.A.za==dc?String(a.A.ta):"not found yet";c=wf(a);0==c?e="tree is empty":(c=a.ya[c].rb.bound,e=c==-s?"-inf":c==+s?"+inf":c);a.A.dir==za?f=">=":a.A.dir==Ea&&(f="<=");c=xf(a);x("+"+a.A.$+": "+(b?">>>>>":"mip =")+" "+d+" "+f+" "+e+" "+(0==c?" 0.0%":0.001>c?" < 0.1%":9.999>=c?" "+Number(100*c).toFixed(1)+"%":"")+" ("+a.Pd+"; "+(a.Jg-a.Zf)+")");a.Lg=ja()}function c(a,b){return vf(a,a.ya[b].rb.bound)}function d(a){var b=a.A,c,d,e=0,f,g,h,k,l,m=0;for(c=1;c<= +b.i;c++)if(h=b.f[c],a.ad[c]=0,h.kind==Fc&&h.m==A){d=h.type;f=h.c;g=h.d;h=h.r;if(d==Sa||d==I||d==B){k=f-a.p.Ub;l=f+a.p.Ub;if(k<=h&&h<=l)continue;if(hg)continue}k=Math.floor(h+0.5)-a.p.Ub;l=Math.floor(h+0.5)+a.p.Ub;k<=h&&h<=l||(a.ad[c]=1,e++,k=h-Math.floor(h),l=Math.ceil(h)-h,m+=k<=l?k:l)}a.N.Ag=e;a.N.Zc=m;a.p.o>=mc&&(0==e?x("There are no fractional columns"):1==e?x("There is one fractional column, integer infeasibility is "+ +m+""):x("There are "+e+" fractional columns, integer infeasibility is "+m+""))}function e(a){var b=a.A,c;b.za=dc;b.ta=b.aa;for(c=1;c<=b.g;c++){var d=b.n[c];d.Sa=d.r}for(c=1;c<=b.i;c++)d=b.f[c],d.kind==Ma?d.Sa=d.r:d.kind==Fc&&(d.Sa=Math.floor(d.r+0.5));a.qh++}function f(a,b,c){var d=a.A,e,f=d.g,g,h,k,l,m,n=Array(3),q,p,r,t,y=null,v=null,S;g=d.f[b].type;q=d.f[b].c;p=d.f[b].d;e=d.f[b].r;r=Math.floor(e);t=Math.ceil(e);switch(g){case Ka:h=Ta;k=Sa;break;case Sa:h=q==r?B:I;k=Sa;break;case Ta:h=Ta;k=t==p? +B:I;break;case I:h=q==r?B:I,k=t==p?B:I}tf(a,b,function(a,b){y=a;v=b});g=uf(a,y);S=uf(a,v);l=!vf(a,g);m=!vf(a,S);if(l&&m)return a.p.o>=mc&&x("Both down- and up-branches are hopeless"),2;if(m)return a.p.o>=mc&&x("Up-branch is hopeless"),Wa(d,b,h,q,r),a.N.rc=y,d.dir==za?a.N.boundg&&(a.N.bound=g),1;if(l)return a.p.o>=mc&&x("Down-branch is hopeless"),Wa(d,b,k,t,p),a.N.rc=v,d.dir==za?a.N.boundS&&(a.N.bound=S),1;a.p.o>=mc&&x("Branching on column "+ +b+", primal value is "+e+"");l=a.N.s;a.N.Tc=b;a.N.tg=e;qf(a);rf(a,l,n);a.p.o>=mc&&x("Node "+n[1]+" begins down branch, node "+n[2]+" begins up branch ");e=a.ya[n[1]].rb;e.Na={};e.Na.pc=f+b;e.Na.type=h;e.Na.c=q;e.Na.d=r;e.Na.e=null;e.rc=y;d.dir==za?e.boundg&&(e.bound=g);e=a.ya[n[2]].rb;e.Na={};e.Na.pc=f+b;e.Na.type=k;e.Na.c=t;e.Na.d=p;e.Na.e=null;e.rc=v;d.dir==za?e.boundS&&(e.bound=S);c==Af?a.ud=0:c==Bf?a.ud=n[1]:c==Cf&&(a.ud=n[2]); +return 0}function g(a){var b=a.A,c,d,e=0,f,g,h,k;f=b.aa;for(c=1;c<=b.i;c++)if(k=b.f[c],k.kind==Fc)switch(g=k.c,h=k.d,d=k.m,k=k.J,b.dir){case za:d==G?(0>k&&(k=0),f+k>=b.ta&&(Wa(b,c,B,g,g),e++)):d==Ua&&(0=b.ta&&(Wa(b,c,B,h,h),e++));break;case Ea:d==G?(0k&&(k=0),f-k<=b.ta&&(Wa(b,c,B,h,h),e++))}a.p.o>=mc&&0!=e&&(1==e?x("One column has been fixed by reduced cost"):x(e+" columns have been fixed by reduced costs"))}function h(a){var b,c=0, +d=null;for(b=a.wc+1;b<=a.A.g;b++)a.A.n[b].origin==Ja&&a.A.n[b].La==a.N.La&&a.A.n[b].m==A&&(null==d&&(d=new Int32Array(1+a.A.g)),d[++c]=b);0c&&(c=1E3);d=0;for(b=a.wc+1;b<=a.A.g;b++)a.A.n[b].origin==Ja&&d++;if(!(d>=c)){a.p.Ad==bb&&5>a.N.Rd&&Hf(a);a.p.Ed==bb&&If(a,a.Xf);if(a.p.xd==bb){b=a.A;c=kb(b);var e=lb(b),f,g,h,k,l;xc(b);d=new Int32Array(1+e);k=new Float64Array(1+e);l=new Float64Array(1+e);for(e=1;e<=c;e++)for(h=1;2>=h;h++){g=ob(b,e)-Ka+gf;if(1==h){if(g!=lf&&g!=nf)continue;g=ub(b,e,d,k);k[0]=Jf(b,e)}else{if(g!=jf&&g!=nf)continue;g=ub(b,e,d,k);for(f=1;f<=g;f++)k[f]=-k[f];k[0]= +-Kf(b,e)}a:{var m=b;f=d;for(var n=k,q=l,p=null,r=null,t=Array(5),y=void 0,v=void 0,S=void 0,M=S=void 0,ba=void 0,J=M=M=void 0,S=0,v=1;v<=g;v++)y=f[v],rb(m,y)-Ka+gf==cf?n[0]-=n[v]*Lf(m,y):(S++,f[S]=f[v],n[S]=n[v]);g=S;S=0;for(v=1;v<=g;v++)y=f[v],(Ic(m,y)==Ma?Mf:Nf)==Nf&&rb(m,y)-Ka+gf==nf&&0==Lf(m,y)&&1==Of(m,y)&&(S++,ba=f[S],M=n[S],f[S]=f[v],n[S]=n[v],f[v]=ba,n[v]=M);if(2>S)g=0;else{ba=M=0;for(v=S+1;v<=g;v++){y=f[v];if(rb(m,y)-Ka+gf!=nf){g=0;break a}0J&&(J=0);J>M&&(J=M);n[0]-=ba;for(v=1;v<=S;v++)y=f[v],q[v]=Dc(m,y),0>q[v]&&(q[v]=0),1n[v]&&(f[v]=-f[v],n[v]=-n[v],n[0]+=n[v],q[v]=1-q[v]);m=S;v=n[0];y=J;J=void 0;for(J=1;J<=m;J++);for(J=1;J<=m;J++);J=void 0;b:{for(var ea=J=void 0,fa=0,sa=0,K=void 0,oa=void 0,Bb=0.001,K=0.001*(1+Math.abs(v)),J=1;J<=m;J++)for(ea=J+1;ea<=m;ea++){fa++;if(1E3v+K&&(oa=n[J]+ +n[ea]-v,p=1/(oa+M),r=2-p*oa,oa=q[J]+q[ea]+p*y-r,Bbv+oa&&(Bb=n[J]+n[ea]+n[fa]-v,p=1/(Bb+M),r=3-p*Bb,Bb=q[J]+q[ea]+q[fa]+p*y-r,ecv+Bb&&(ec=n[J]+n[ea]+n[fa]+n[sa]-v,p=1/(ec+M),r=4-p*ec,ec=q[J]+q[ea]+q[fa]+q[sa]+p*y-r,Wjn&&w("lpx_eval_row: len = "+n+"; invalid row length");for(S=1;S<=n;S++)t=q[S],1<=t&&t<=r||w("lpx_eval_row: j = "+t+"; column number out of range"),ba+=p[S]*Dc(f,t);f=ba-k[0];0.001>f||Id(a,Ff,g,d,k,Ta,k[0])}}}a.p.vd==bb&&null!=a.Ne&&(0==a.N.La&&50>a.N.Rd||0a.N.Rd)&&(c=a.Ne,d=lb(a.A),b=new Int32Array(1+d),d=new Float64Array(1+d),c=Pf(a.A,c,b,d),0=mc&&(1==e?x("One hopeless branch has been pruned"):1=mc&&x("Active list is empty!");n=0;r=3;break}if(null!=a.p.ob&&(a.reason=Qf,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}0==a.gf&&(a.gf=1==a.Pd?a.head.s:0!=a.ud?a.ud:Rf(a));pf(a,a.gf);a.gf=a.ud=0;null!=a.N.R&&a.N.R.s!=t&&(t=0);m=a.N.s;a.p.o>=mc&&(x("------------------------------------------------------------------------"), +x("Processing node "+m+" at level "+a.N.La+""));1==m&&(a.p.Ad==bb&&a.p.o>=Wb&&x("Gomory's cuts enabled"),a.p.Ed==bb&&(a.p.o>=Wb&&x("MIR cuts enabled"),a.Xf=Sf(a)),a.p.xd==bb&&a.p.o>=Wb&&x("Cover cuts enabled"),a.p.vd==bb&&(a.p.o>=Wb&&x("Clique cuts enabled"),a.Ne=Tf(a.A)));case 1:(a.p.o>=mc||a.p.o>=fc&&a.p.bc-1<=1E3*la(a.Lg))&&b(a,0);a.p.o>=Wb&&60<=la(y)&&(x("Time used: "+la(a.hc)+" secs"),y=ja());if(0=mc&&x("Relative gap tolerance reached; search terminated ");n=Pc; +r=3;break}if(2147483647>a.p.sb&&a.p.sb-1<=1E3*la(a.hc)){a.p.o>=mc&&x("Time limit exhausted; search terminated");n=Qc;r=3;break}if(null!=a.p.ob&&(a.reason=Uf,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}if(a.p.ed!=hd)if(a.p.ed==id){if(0==a.N.La&&zf(a,100)){r=2;break}}else if(a.p.ed==jd&&zf(a,0==a.N.La?100:10)){r=2;break}if(!c(a,m)){x("*** not tested yet ***");r=2;break}a.p.o>=mc&&x("Solving LP relaxation...");n=yf(a);if(0!=n&&n!=Vf&&n!=Wf){a.p.o>=Lb&&x("ios_driver: unable to solve current LP relaxation; glp_simplex returned "+ +n+"");n=Sb;r=3;break}q=a.A.na;r=a.A.sa;if(q==dc&&r==dc)a.p.o>=mc&&x("Found optimal solution to LP relaxation");else if(r==jc){a.p.o>=Lb&&x("ios_driver: current LP relaxation has no dual feasible solution");n=Sb;r=3;break}else if(q==Ad&&r==dc){a.p.o>=mc&&x("LP relaxation has no solution better than incumbent objective value");r=2;break}else if(q==jc){a.p.o>=mc&&x("LP relaxation has no feasible solution");r=2;break}q=a.N.rc=a.A.aa;q=uf(a,q);a.A.dir==za?a.N.bound +q&&(a.N.bound=q);a.p.o>=mc&&x("Local bound is "+q+"");if(!c(a,m)){a.p.o>=mc&&x("Current branch is hopeless and can be pruned");r=2;break}if(null!=a.p.ob){a.reason=Ga;a.p.ob(a,a.p.Uc);a.reason=0;if(a.stop){n=Rc;r=3;break}if(a.pe){a.pe=a.tf=0;r=1;break}a.tf&&(a.tf=0,Jb(a.A))}d(a);if(0==a.N.Ag){a.p.o>=mc&&x("New integer feasible solution found");a.p.o>=Wb&&k(a);e(a);a.p.o>=fc&&b(a,1);if(null!=a.p.ob&&(a.reason=Xf,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}r=2;break}a.A.za==dc&&g(a);if(null!= +a.p.ob){a.reason=Yf;a.p.ob(a,a.p.Uc);a.reason=0;if(a.stop){n=Rc;r=3;break}if(!c(a,m)){a.p.o>=mc&&x("Current branch became hopeless and can be pruned");r=2;break}}if(a.p.Xe&&(a.reason=Yf,Zf(a),a.reason=0,!c(a,m))){a.p.o>=mc&&x("Current branch became hopeless and can be pruned");r=2;break}if(null!=a.p.ob&&(a.reason=Ia,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}if(0==a.N.La||0==t)a.reason=Ia,l(a),a.reason=0;0=Wb&&0==a.N.La&&k(a);null!=a.Fd&&ag(a);if(null!=a.p.ob&&(a.reason=bg,a.p.ob(a,a.p.Uc),a.reason=0,a.stop)){n=Rc;r=3;break}0==a.Tc&&(a.Tc=cg(a,function(b){a.Jf=b}));q=a.N.s;n=f(a,a.Tc,a.Jf);a.Tc=a.Jf=0;if(0==n){t=q;r=0;break}else if(1==n){a.N.eg=a.N.Rd=0;r=1;break}else if(2==n){r=2;break}case 2:a.p.o>=mc&&x("Node "+m+" fathomed");qf(a);sf(a,m);a.A.za==dc&&p(a);r=t=0;break;case 3:return a.p.o>=fc&&b(a,0),a.Xf=null,a.Ne=null,n}if(null==r)break;q=r}} +function dg(a){var b;b={};b.i=a;b.L=0;b.Ja=new Int32Array(1+a);b.Z=new Int32Array(1+a);b.j=new Float64Array(1+a);return b}function eg(a,b,c){var d=a.Ja[b];0==c?0!=d&&(a.Ja[b]=0,dMath.abs(W))){switch(R){case Ra:return;case G:Q=-W;break;case Ua:Q=+W;break;case Na:continue}switch(H){case Fc:if(1E-10>Math.abs(Q-Math.floor(Q+0.5)))continue;else X=b(Q)<= +b(F)?b(Q):b(F)/(1-b(F))*(1-b(Q));break;case Ma:X=0<=Q?+Q:b(F)/(1-b(F))*-Q}switch(R){case G:c[l]=+X;ca+=X*V;break;case Ua:c[l]=-X,ca-=X*O}}}for(d=1;d<=f;d++)if(!(1E-10>Math.abs(c[d])))for(R=e.n[d],Q=R.k;null!=Q;Q=Q.B)c[f+Q.f.C]+=c[d]*Q.j;D=0;for(d=1;d<=g;d++)1E-10>Math.abs(c[f+d])||(R=e.f[d],R.type==B?ca-=c[f+d]*R.c:(D++,h[D]=d,k[D]=c[f+d]));1E-12>Math.abs(ca)&&(ca=0);for(l=1;l<=D;l++)if(0.001>Math.abs(k[l])||1E3=l&&(e++,g[e].C=h,g[e].Nb=l))}ma(g,e,function(a,b){return a.Nb>b.Nb?-1:a.NbMath.abs(b-Math.floor(b+0.5)))b=1;else{h=b-Math.floor(b);for(c=1;c<=a;c++)l=g[c]-Math.floor(g[c])-h,g[c]=0>=l?Math.floor(g[c]):Math.floor(g[c])+l/(1-h);q=Math.floor(b);r=1/(1-h);b=0}if(b)return 1;for(c=1;c<=a;c++)e[c]&&(g[c]=-g[c],q+=g[c]*d[c]);r/=f;return 0}var h,l,m,n,p;m=Array(4);var t,v,y,D;y=new Int8Array(1+a);D=Array(1+a);for(l=1;l<= +a;l++)y[l]=e[l]>=0.5*d[l];v=p=0;for(l=1;l<=a;l++)if(h=1E-9*(1+Math.abs(d[l])),!(e[l]d[l]-h||(h=k(a,b,c,d,y,Math.abs(b[l]),g),h))){t=-q-r*f;for(h=1;h<=a;h++)t+=g[h]*e[h];vv&&(v=0);if(0==v)return v;m[1]=p/2;m[2]=p/4;m[3]=p/8;for(l=1;3>=l;l++)if(h=k(a,b,c,d,y,m[l],g),!h){t=-q-r*f;for(h=1;h<=a;h++)t+=g[h]*e[h];vd[l]-h||(m++,D[m].C=l,D[m].xf=Math.abs(e[l]-0.5*d[l]));ma(D,m,function(a, +b){return a.xfb.xf?1:0});for(n=1;n<=m;n++)if(l=D[n].C,y[l]=!y[l],h=k(a,b,c,d,y,p,g),y[l]=!y[l],!h){t=-q-r*f;for(h=1;h<=a;h++)t+=g[h]*e[h];vg[e]&&(g[e]=0);k=0;for(e=h+1;e<=a.G.L;e++)f=a.G.Z[e],a.lb[f]==lg?(g=a.Yb[f],g=0==g?a.x[f]-a.c[f]:a.x[f]-a.c[f]*a.x[g]):a.lb[f]==mg&&(g=a.zb[f],g=0==g?a.d[f]-a.x[f]:a.d[f]*a.x[g]-a.x[f]), +0>g&&(g=0),k-=a.G.j[e]*g;k=c(h,a.G.j,a.Lb,l,g,k,m);if(0==k)return k;for(e=1;e<=h;e++)a.G.j[e]=m[e];for(e=h+1;e<=a.G.L;e++)f=a.G.Z[e],f<=b+d&&(a.G.j[e]*=0);a.Lb=null;return k}function k(a){var b,c,d,e;for(b=1;b<=a.G.L;b++)d=a.G.Z[b],a.ab[d]&&(a.lb[d]==lg?a.Lb+=a.G.j[b]*a.c[d]:a.lb[d]==mg&&(a.Lb-=a.G.j[b]*a.d[d],a.G.j[b]=-a.G.j[b]));for(b=1;b<=a.G.L;b++)d=a.G.Z[b],a.ab[d]||(a.lb[d]==lg?(e=a.Yb[d],0==e?a.Lb+=a.G.j[b]*a.c[d]:(c=a.G.Ja[e],0==c&&(eg(a.G,e,1),c=a.G.Ja[e],a.G.j[c]=0),a.G.j[c]-=a.G.j[b]*a.c[d])): +a.lb[d]==mg&&(e=a.zb[d],0==e?a.Lb-=a.G.j[b]*a.d[d]:(c=a.G.Ja[e],0==c&&(eg(a.G,e,1),c=a.G.Ja[e],a.G.j[c]=0),a.G.j[c]+=a.G.j[b]*a.d[d]),a.G.j[b]=-a.G.j[b]))}function l(a,b){var c=a.A,d=b.g,e,f,g,h;for(f=b.G.L;1<=f;f--)if(e=b.G.Z[f],!(e>d)){for(e=c.n[e].k;null!=e;e=e.B)g=d+e.f.C,h=b.G.Ja[g],0==h&&(eg(b.G,g,1),h=b.G.Ja[g],b.G.j[h]=0),b.G.j[h]+=b.G.j[f]*e.j;b.G.j[f]=0}gg(b.G,0)}function p(a,b){var c=b.g,d=b.i,e,f,g=new Int32Array(1+d),h=new Float64Array(1+d);f=0;for(d=b.G.L;1<=d;d--)e=b.G.Z[d],f++,g[f]= +e-c,h[f]=b.G.j[d];Id(a,Ef,f,g,h,Ta,b.Lb)}function m(a,b){var c=a.A,d=b.g,e=b.i,f,g,h,k=0,l=0,m,n=0;for(f=1;f<=b.Ya.L;f++)g=b.Ya.Z[f],g<=d||b.ab[g]||0.001>Math.abs(b.Ya.j[f])||(h=b.Yb[g],m=0==h?b.c[g]==-s?s:b.x[g]-b.c[g]:b.x[g]-b.c[g]*b.x[h],h=b.zb[g],h=0==h?b.zb[g]==+s?s:b.d[g]-b.x[g]:b.d[g]*b.x[h]-b.x[g],m=m<=h?m:h,!(0.001>m)&&nd)return 2;b.Je++;b.Ef[b.Je]= +g;b.Eb[g]=2;e=dg(d+e);eg(e,g,1);for(f=c.n[g].k;null!=f;f=f.B)eg(e,d+f.f.C,-f.j);f=b.Ya.Ja[k];c=b.Ya;d=-b.Ya.j[f]/e.j[e.Ja[k]];for(g=1;g<=e.L;g++)f=e.Z[g],n=void 0,n=c.Ja[f],n=0==n?0:c.j[n],m=e.j[g],eg(c,f,n+d*m);eg(b.Ya,k,0);return l}var q,r,n=b.g,t=b.i,y,E,C;(function(a,b){var c=a.A,d=b.g,e=b.i,f;for(f=1;f<=d;f++)b.x[f]=c.n[f].r;for(f=d+1;f<=d+e;f++)b.x[f]=c.f[f-d].r})(a,b);ha(b.lb,1,kg,n+t);for(y=1;y<=n;y++)if(!b.Eb[y]){for(d(a,b,y);;){e(b);if(ig)for(E=1;E<=n+t;E++);f(b);g(b);C=h(b);0f[h]){g=e(a,g);if(g==+s){k=-s;break}k+=f[h]*g}return k}function h(a,b,c,f){var g,h,k;k=0;for(h=1;h<=b;h++)if(g=c[h],0f[h]){g=d(a,g);if(g==-s){k=+s;break}k+=f[h]*g}return k}function k(a,b,c,d,e,f,g,h){b!=-s&&g&&(b-=a[f]);c!=+s&&g&&(c-=a[f]);d!=-s&&(0>a[f]&&(d-=a[f]),0> +a[h]&&(d-=a[h]));e!=+s&&(0f?1:0}var l=null,p,m,q,r,n,t,y,E,C,D,H,R,V,O,Q,F;x("Creating the conflict graph...");p=kb(a);m=lb(a);q=0;D=new Int32Array(1+m);H=new Int32Array(1+m);C=new Int32Array(1+m);F=new Float64Array(1+m);for(r=1;r<=p;r++)if(R=b(a,r),V=c(a,r),R!=-s||V!=+s)if(E=ub(a,r,C,F),!(500c?f(a,b*(b-1)/2+c):f(a,c*(c-1)/2+b)}function f(a,b){return a.Jc[b/1]&1<<0-b%1}function g(a,b,c,d,f,h){var k,l,m,n,q,p,r,ca;ca=new Int32Array(a.i);if(0>=b){if(0==b&&(a.set[d++]=c[0],f+=h),f>a.gd)for(a.gd=f,a.bg=d,k=0;kk&&(k=0),100l||f.Ic[h+1]==l&&q[h]>m)&&(l=f.Ic[h+1],m=q[h],k=h);b[a]=k;n[k]=1;for(h=0;h=q;q+=2){p=Fd(c,r,n,R,q,1E-9);0!= +p&&(p=n[p]);if(0==p)p=a.A.dir==za?+s:-s;else{for(m=1;m<=r&&n[m]!=p;m++);m=R[m];y=(0>q?Math.floor(t):Math.ceil(t))-t;y/=m;p>f&&Ic(c,p-f)!=Ma&&0.001p||m==Ua&&0p||m==Ra)p=0}p*=y}0>q?e=p:E=p}if(H=mc&&(x("branch_drtom: column "+l+" chosen to branch on"),Math.abs(C)==s?x("branch_drtom: down-branch is infeasible"):x("branch_drtom: down-branch bound is "+(yc(c)+C)+""),Math.abs(D)==s?x("branch_drtom: up-branch is infeasible"):x("branch_drtom: up-branch bound is "+(yc(c)+D)+"")),b(d));c=l}else a.p.Jb==cd&&(c=pg(a,b));return c} +function og(a,b){var c,d,e,f,g,h;d=0;g=s;for(c=1;c<=a.i;c++)a.ad[c]&&(f=Dc(a.A,c),h=Math.floor(f)+0.5,g>Math.abs(f-h)&&(d=c,g=Math.abs(f-h),e=fc?(d.yd[b]++,d.Ud[b]+=a):(d.Id[b]++,d.ye[b]+=a))} +function pg(a,b){function c(a,b,c){var d,e;xc(a);d=Ba();hb(d,a,0);Wa(d,b,B,c,c);b=new kc;b.o=lc;b.cb=Tb;b.oc=30;b.fb=1E3;b.cb=Tb;b=sc(d,b);0==b||b==rg?tc(d)==jc?e=s:uc(d)==dc?(a.dir==za?e=d.aa-a.aa:a.dir==Ea&&(e=a.aa-d.aa),e<1E-6*(1+0.001*Math.abs(a.aa))&&(e=0)):e=0:e=0;return e}function d(a,b,d){var e=a.Fd,f;if(d==Bf){if(0==e.yd[b]){d=a.A.f[b].r;a=c(a.A,b,Math.floor(d));if(a==s)return f=s;e.yd[b]=1;e.Ud[b]=a/(d-Math.floor(d))}f=e.Ud[b]/e.yd[b]}else if(d==Cf){if(0==e.Id[b]){d=a.A.f[b].r;a=c(a.A,b, +Math.ceil(d));if(a==s)return f=s;e.Id[b]=1;e.ye[b]=a/(Math.ceil(d)-d)}f=e.ye[b]/e.Id[b]}return f}function e(a){var b=a.Fd,c,d=0,e=0;for(c=1;c<=a.i;c++)Jd(a,c)&&(d++,0l?m:l;q=bb&&10<=la(f)&&(e(a),f=ja())}if(0==q)return h=og(a,b);b(k);return h} +function Zf(a){var b=a.A,c=b.i,d=null,e=null,f=null,g,h,k,l,p,m,q,r;for(l=0;;){var n=null;switch(l){case 0:xc(b);if(0!=a.N.La||1!=a.N.eg){n=5;break}q=0;for(k=1;k<=c;k++)if(g=b.f[k],g.kind!=Ma&&g.type!=B)if(g.type==I&&0==g.c&&1==g.d)q++;else{a.p.o>=Wb&&x("FPUMP heuristic cannot be applied due to general integer variables");n=5;break}if(null!=n)break;if(0==q){n=5;break}a.p.o>=Wb&&x("Applying FPUMP heuristic...");e=Array(1+q);ia(e,1,q);l=0;for(k=1;k<=c;k++)g=b.f[k],g.kind==Fc&&g.type==I&&(e[++l].C=k); +d=Ba();case 1:hb(d,b,cb);if(b.za==dc){La(d,1);p=new Int32Array(1+c);m=new Float64Array(1+c);for(k=1;k<=c;k++)p[k]=k,m[k]=b.f[k].u;Ya(d,d.g,c,p,m);p=0.1*b.aa+0.9*b.ta;b.dir==za?Va(d,d.g,Ta,0,p-b.ha):b.dir==Ea&&Va(d,d.g,Sa,p-b.ha,0)}m=0;for(l=1;l<=q;l++)e[l].x=-1;case 2:if(m++,a.p.o>=Wb&&x("Pass "+m+""),r=s,p=0,1n&&(n=0),g=Math.abs(e[l].x-g.r),0.5g.r?0:1, +e[l].x!=g&&(h=0,e[l].x=g);if(h){for(l=1;l<=q;l++)g=d.f[e[l].C],e[l].Td=Math.abs(g.r-e[l].x);ma(e,q,function(a,b){return a.Td>b.Td?-1:a.Tde[l].Td||10<=l);l++)e[l].x=1-e[l].x}case 4:if(2147483647>a.p.sb&&a.p.sb-1<=1E3*la(a.hc)){n=5;break}d.dir=za;d.ha=0;for(k=1;k<=c;k++)d.f[k].u=0;for(l=1;l<=q;l++)k=e[l].C,0==e[l].x?d.f[k].u=1:(d.f[k].u=-1,d.ha+=1);h=new kc;a.p.o<=Lb?h.o=a.p.o:a.p.o<=Wb&&(h.o=fc,h.fb=1E4);l=sc(d,h);if(0!=l){a.p.o>=Lb&&x("Warning: glp_simplex returned "+ +l+"");n=5;break}l=xc(d);if(l!=vc){a.p.o>=Lb&&x("Warning: glp_get_status returned "+l+"");n=5;break}a.p.o>=mc&&x("delta = "+d.aa+"");k=0.3*a.p.Ub;for(l=1;l<=q&&!(g=d.f[e[l].C],kq){g=new Float64Array(1+c);for(k=1;k<=c;k++)g[k]=d.f[k].r,b.f[k].kind==Fc&&(g[k]=Math.floor(g[k]+0.5));d.ha=b.ha;d.dir=b.dir;for(l=1;l<=q;l++)d.f[e[l].C].c=g[e[l].C],d.f[e[l].C].d=g[e[l].C],d.f[e[l].C].type=B;for(k=1;k<=c;k++)d.f[k].u=b.f[k].u;l=sc(d,h);if(0!=l){a.p.o>=Lb&&x("Warning: glp_simplex returned "+ +l+"");n=5;break}l=xc(d);if(l!=vc){a.p.o>=Lb&&x("Warning: glp_get_status returned "+l+"");n=5;break}for(k=1;k<=c;k++)b.f[k].kind!=Fc&&(g[k]=d.f[k].r);l=Kd(a,g);if(0==l){n=vf(a,a.N.bound)?1:5;break}}r==s||d.aa<=r-1E-6*(1+r)?(p=0,r=d.aa):p++;if(3>p){n=3;break}5>m&&(n=2)}if(null==n)break;l=n}} +function $f(a){function b(a,b,c){var d,e=0,f=0,g=0;for(d=a.k;null!=d;d=d.e)c[d.C]=d.j,f+=d.j*d.j;for(d=b.k;null!=d;d=d.e)e+=c[d.C]*d.j,g+=d.j*d.j;for(d=a.k;null!=d;d=d.e)c[d.C]=0;a=Math.sqrt(f)*Math.sqrt(g);4.930380657631324E-32>a&&(a=2.220446049250313E-16);return e/a}var c,d,e,f,g,h,k,l,p,m;c=a.Bd;f=Array(1+c.size);l=new Int32Array(1+a.i);p=new Float64Array(1+a.i);m=new Float64Array(1+a.i);g=0;for(d=c.head;null!=d;d=d.e)g++,f[g].Re=d,f[g].ba=0;for(g=1;g<=c.size;g++){var q=null,r=null;d=f[g].Re;h= +k=0;for(e=d.k;null!=e;e=e.e)k++,l[k]=e.C,p[k]=e.j,h+=e.j*e.j;4.930380657631324E-32>h&&(h=2.220446049250313E-16);k=Dd(a.A,k,l,p);d=Gd(a.A,k,l,p,d.type,d.cg,function(a,b,c,d,e,f){q=e;r=f});0==d?(f[g].Xc=Math.abs(q)/Math.sqrt(h),a.A.dir==za?(0>r&&(r=0),f[g].Ab=+r):(0f[g].Ab&&(f[g].Ab=0)}ma(f,c.size,function(a,b){if(0==a.Ab&&0==b.Ab){if(a.Xc>b.Xc)return-1;if(a.Xcb.Ab)return-1;if(a.Abc.size&&(h=c.size);for(g=1;g<=h;g++)if(!(0.01>f[g].Ab&&0.01>f[g].Xc)){for(c=1;ca.R.Zc&&(b=a.s,c=a.R.Zc);return b}function c(a){var b,c,d,e,p;b=a.ya[1].rb;e=(a.A.ta-b.bound)/b.Zc;c=0;d=s;for(b=a.head;null!=b;b=b.e)p=b.R.bound+e*b.R.Zc,a.A.dir==Ea&&(p=-p),d>p&&(c=b.s,d=p);return c}function d(a){var b,c=null,d,e;switch(a.A.dir){case za:d=+s;for(b=a.head;null!=b;b=b.e)d>b.bound&&(d=b.bound);e=0.001*(1+Math.abs(d));for(b=a.head;null!=b;b=b.e)b.bound<=d+e&&(null==c||c.R.Zc>b.R.Zc)&&(c=b);break;case Ea:d=-s; +for(b=a.head;null!=b;b=b.e)d=d-e&&(null==c||c.rc=a/Math.pow(2,b)?b-1:b)}function vg(a,b){var c=Number(a);if(isNaN(c))return 2;switch(c){case Number.POSITIVE_INFINITY:case Number.NEGATIVE_INFINITY:return 1;default:return b(c),0}} +function wg(a,b){var c=Number(a);if(isNaN(c))return 2;switch(c){case Number.POSITIVE_INFINITY:case Number.NEGATIVE_INFINITY:return 1;default:return 0==c%1?(b(c),0):2}}function xg(a,b,c){var d,e;if(!(1<=a&&31>=a&&1<=b&&12>=b&&1<=c&&4E3>=c))return-1;3<=b?b-=3:(b+=9,c--);d=c/100|0;c=(146097*d/4|0)+(1461*(c-100*d)/4|0);c+=(153*b+2)/5|0;c+=a+1721119;yg(c,function(a){e=a});a!=e&&(c=-1);return c} +function yg(a,b){var c,d,e;1721426<=a&&3182395>=a&&(a-=1721119,e=(4*a-1)/146097|0,c=(4*a-1)%146097/4|0,a=(4*c+3)/1461|0,c=((4*c+3)%1461+4)/4|0,d=(5*c-3)/153|0,c=(5*c-3)%153,c=(c+5)/5|0,e=100*e+a,9>=d?d+=3:(d-=9,e++),b(c,d,e))}var Ee=1;LPF_ECOND=2;LPF_ELIMIT=3;var we=0;function Me(a,b,c,d){var e=a.i,f=a.Md,g=a.Ld,h=a.Wb;a=a.Xb;var k,l,p,m;for(k=1;k<=e;k++){m=0;l=f[k];for(p=l+g[k];lM&&(M=-M),f=L)){S=p[V];if(0>S){for(F=O;F<=Q;F++)M=l[F],0>M&&(M=-M),SM&&(M=-M);if(!(MS){for(F=O;F<=Q;F++)M=l[F],0>M&&(M=-M),S=L)&&(M=l[F],0>M&&(M=-M),!(Mv&&(v=-v),Q[z]=0,P--,0==v||vk[u]){if(Ze(a,u,h[u]+P))return ca=1;M=g[b];ba=M+h[b]-1;l=p[c];J=l+m[c]-1}ka=0;for(P=M;P<=ba;P++)z=r[P],Q[z]?(v=S=-ea*F[z],0>v&&(v=-v),0==v||vq[z]){if($e(a,z,m[z]+10))return ca=1;M=g[b];ba=M+h[b]-1;l=p[c];J=l+m[c]-1}v=p[z]+m[z];r[v]=u;m[z]++}D[u]=0;H[u]=C[h[u]];0!=H[u]&&(D[H[u]]=u);C[h[u]]=u;E[u]=-1;if(1>a.Ma-a.Fa){af(a);if(1>a.Ma-a.Fa)return ca=1;M=g[b]; +ba=M+h[b]-1;l=p[c];J=l+m[c]-1}a.Ma--;r[a.Ma]=u;n[a.Ma]=ea;f[b]++}q[c]=0;L=d+c;0==t[L]?a.ld=y[L]:y[t[L]]=y[L];0==y[L]?a.Sb=t[L]:t[y[L]]=t[L];e[b]=a.Ma;for(P=M;P<=ba;P++)if(z=r[P],Q[z]=0,F[z]=0,1==m[z]||V[z]!=z||O[z]!=z)V[z]=0,O[z]=R[m[z]],0!=O[z]&&(V[O[z]]=z),R[m[z]]=z;return ca} +function Yg(a){var b=a.i,c=a.Fc,d=a.Ec,e=a.Dc,f=a.Cc,g=a.Rc,h=a.wb,k=a.xb,l=a.Hd,p=a.md,m,q,r,n;n=0;for(m=1;m<=b;m++){q=c[m];for(r=q+d[m]-1;q<=r;q++)g[h[q]]++;n+=d[m]}a.Qb=n;if(a.Ma-a.Fal*a.Cg)return a.pa=q-1,y=Ae,!1}af(a);return Yg(a)||Zg(a)?(a.Va=a.Ga+a.Ga,!0):!1}var f,g,h,k,l=a.sc,p,m,q,r,n,t,y=null;1>b&&w("luf_factorize: n = "+ +b+"; invalid parameter");1E8b&&(b=$g),a.Vc++,b==$g?"\n"==a.l?a.bb--:ok(a,"final NL missing before end of file"):"\n"!=b&&(0<=" \t\n\v\f\r".indexOf(b)?b=" ":ta(b)&&(mk(a),U(a,"control character "+b+" not allowed"))),a.l=b)}function pk(a){a.h+=a.l;a.Bb++;nk(a)} +function Y(a){function b(){mk(a);U(a,"keyword s.t. incomplete")}function c(){mk(a);U(a,"cannot convert numeric literal "+a.h+" to floating-point number")}function d(){if("e"==a.l||"E"==a.l)for(pk(a),"+"!=a.l&&"-"!=a.l||pk(a),wa(a.l)||(mk(a),U(a,"numeric literal "+a.h+" incomplete"));wa(a.l);)pk(a);if(ua(a.l)||"_"==a.l)mk(a),U(a,"symbol "+a.h+a.l+"... should be enclosed in quotes")}a.Hf=a.b;a.Gf=a.Bb;a.Ff=a.h;a.If=a.value;if(a.Ue)a.Ue=0,a.b=a.Mf,a.Bb=a.Lf,a.h=a.Kf,a.value=a.Nf;else{for(;;){a.b=0;a.Bb= +0;a.h="";for(a.value=0;" "==a.l||"\n"==a.l;)nk(a);if(a.l==$g)a.b=Ah;else if("#"==a.l){for(;"\n"!=a.l&&a.l!=$g;)nk(a);continue}else if(a.nc||!ua(a.l)&&"_"!=a.l)if(!a.nc&&wa(a.l)){for(a.b=Dh;wa(a.l);)pk(a);var e=!1;if("."==a.l)if(pk(a),"."==a.l)a.Bb--,a.h=a.h.substr(0,a.h.length-1),a.Te=1,e=!0;else for(;wa(a.l);)pk(a);e||d();vg(a.h,function(b){a.value=b})&&c()}else if("'"==a.l||'"'==a.l){var f=a.l,g=!1;a.b=Eh;nk(a);e=function(){for(;;){if("\n"==a.l&&!g||a.l==$g)mk(a),U(a,"unexpected end of line; string literal incomplete"); +if(a.l==f)if(nk(a),a.l==f){if(g)if(nk(a),a.l==f){nk(a);break}else a.h+='""',a.Bb+=2}else if(g)a.h+='"',a.Bb++;else break;pk(a)}};a.l==f?(nk(a),a.l==f&&(g=!0,nk(a),e())):e()}else if(a.nc||"+"!=a.l)if(a.nc||"-"!=a.l)if("*"==a.l)a.b=$h,pk(a),"*"==a.l&&(a.b=bi,pk(a));else if("/"==a.l){if(a.b=ai,pk(a),"*"==a.l){for(nk(a);;)if(a.l==$g)U(a,"unexpected end of file; comment sequence incomplete");else if("*"==a.l){if(nk(a),"/"==a.l)break}else nk(a);nk(a);continue}}else if("^"==a.l)a.b=bi,pk(a);else if("<"== +a.l)a.b=ci,pk(a),"="==a.l?(a.b=di,pk(a)):">"==a.l?(a.b=hi,pk(a)):"-"==a.l&&(a.b=yi,pk(a));else if("="==a.l)a.b=ei,pk(a),"="==a.l&&pk(a);else if(">"==a.l)a.b=gi,pk(a),"="==a.l?(a.b=fi,pk(a)):">"==a.l&&(a.b=wi,pk(a));else if("!"==a.l)a.b=Rh,pk(a),"="==a.l&&(a.b=hi,pk(a));else if("&"==a.l)a.b=ii,pk(a),"&"==a.l&&(a.b=Fh,pk(a));else if("|"==a.l)a.b=ji,pk(a),"|"==a.l&&(a.b=Sh,pk(a));else if(a.nc||"."!=a.l)if(","==a.l)a.b=li,pk(a);else if(":"==a.l)a.b=mi,pk(a),"="==a.l&&(a.b=oi,pk(a));else if(";"==a.l)a.b= +ni,pk(a);else if("("==a.l)a.b=qi,pk(a);else if(")"==a.l)a.b=ri,pk(a);else if("["==a.l)a.b=si,pk(a);else if("]"==a.l)a.b=ti,pk(a);else if("{"==a.l)a.b=ui,pk(a);else if("}"==a.l)a.b=vi,pk(a);else if("~"==a.l)a.b=xi,pk(a);else if(va(a.l)||0<="+-._".indexOf(a.l)){for(a.b=Ch;va(a.l)||0<="+-._".indexOf(a.l);)pk(a);switch(vg(a.h,function(b){a.value=b})){case 0:a.b=Dh;break;case 1:c()}}else mk(a),U(a,"character "+a.l+" not allowed");else if(a.b=ki,pk(a),a.Te)a.b=pi,a.Bb=2,a.h="..",a.Te=0;else if("."==a.l)a.b= +pi,pk(a);else{if(wa(a.l)){a.b=Dh;for(pk(a);wa(a.l);)pk(a);d();vg(a.h,function(b){a.value=b})&&c()}}else a.b=Zh,pk(a);else a.b=Yh,pk(a);else{for(a.b=Bh;va(a.l)||"_"==a.l;)pk(a);"and"==a.h?a.b=Fh:"by"==a.h?a.b=Gh:"cross"==a.h?a.b=Hh:"diff"==a.h?a.b=Ih:"div"==a.h?a.b=Jh:"else"==a.h?a.b=Kh:"if"==a.h?a.b=Lh:"in"==a.h?a.b=Mh:"Infinity"==a.h?a.b=Nh:"inter"==a.h?a.b=Oh:"less"==a.h?a.b=Ph:"mod"==a.h?a.b=Qh:"not"==a.h?a.b=Rh:"or"==a.h?a.b=Sh:"s"==a.h&&"."==a.l?(a.b=Th,pk(a),"t"!=a.l&&b(),pk(a),"."!=a.l&&b(), +pk(a)):"symdiff"==a.h?a.b=Uh:"then"==a.h?a.b=Vh:"union"==a.h?a.b=Wh:"within"==a.h&&(a.b=Xh)}break}mk(a);a.Pf=0}}function qk(a){a.Ue=1;a.Mf=a.b;a.Lf=a.Bb;a.Kf=a.h;a.Nf=a.value;a.b=a.Hf;a.Bb=a.Gf;a.h=a.Ff;a.value=a.If}function rk(a,b){return a.b==Bh&&a.h==b}function sk(a){return a.b==Fh&&"a"==a.h[0]||a.b==Gh||a.b==Hh||a.b==Ih||a.b==Jh||a.b==Kh||a.b==Lh||a.b==Mh||a.b==Oh||a.b==Ph||a.b==Qh||a.b==Rh&&"n"==a.h[0]||a.b==Sh&&"o"==a.h[0]||a.b==Uh||a.b==Vh||a.b==Wh||a.b==Xh} +function tk(a,b,c,d){var e={};e.Ta=a;e.T=0;e.a=lk();e.value={};switch(a){case Fi:e.a.Q=b.Q;break;case Gi:e.a.M=b.M;break;case Hi:e.a.index.ya=b.index.ya;e.a.index.e=b.index.e;break;case Ii:case Ji:for(a=b.S.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.S.S=b.S.S;e.a.S.list=b.S.list;break;case Ki:for(a=b.set.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.set.set=b.set.set;e.a.set.list=b.set.list;break;case Li:for(a=b.t.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.t.t=b.t.t;e.a.t.list=b.t.list;e.a.t.Ac=b.t.Ac; +break;case Mi:for(a=b.H.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.H.H=b.H.H;e.a.H.list=b.H.list;e.a.H.Ac=b.H.Ac;break;case Ni:case Oi:for(a=b.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T;e.a.list=b.list;break;case Pi:e.a.slice=b.slice;break;case Qi:case Ri:case Si:case Ti:e.T=1;break;case Ui:case Vi:case Wi:case Xi:case Yi:case Zi:case $i:case aj:case bj:case cj:case dj:case ej:case fj:case gj:case hj:case ij:case jj:case kj:case lj:case mj:case nj:case oj:b.a.x.R=e;e.T|=b.a.x.T;e.a.a.x=b.a.x;break;case pj:case qj:case rj:case sj:case tj:case uj:case vj:case wj:case xj:case yj:case zj:case Aj:a== +Aj&&(e.T=1);case Bj:a==Bj&&(e.T=1);case Cj:case Dj:case Ej:case Fj:case Gj:case Hj:case Ij:case Jj:case Kj:case Lj:case Mj:case Nj:case Oj:case Pj:case Qj:case Rj:case Sj:case Tj:case Uj:case Vj:case Xj:b.a.x.R=e;e.T|=b.a.x.T;b.a.y.R=e;e.T|=b.a.y.T;e.a.a.x=b.a.x;e.a.a.y=b.a.y;break;case Yj:case Zj:case ak:b.a.x.R=e;e.T|=b.a.x.T;b.a.y.R=e;e.T|=b.a.y.T;null!=b.a.z&&(b.a.z.R=e,e.T|=b.a.z.T);e.a.a.x=b.a.x;e.a.a.y=b.a.y;e.a.a.z=b.a.z;break;case bk:case ck:for(a=b.list;null!=a;a=a.e)a.x.R=e,e.T|=a.x.T; +e.a.list=b.list;break;case dk:case ek:case fk:case gk:case hk:case ik:case jk:case kk:a=b.loop.domain;null!=a.code&&(a.code.R=e,e.T|=a.code.T);for(a=a.list;null!=a;a=a.e)a.code.R=e,e.T|=a.code.T;null!=b.loop.x&&(b.loop.x.R=e,e.T|=b.loop.x.T);e.a.loop.domain=b.loop.domain;e.a.loop.x=b.loop.x}e.type=c;e.q=d;e.R=null;e.valid=0;e.value={};return e}function Z(a,b,c,d){var e=lk();e.a.x=b;return tk(a,e,c,d)}function uk(a,b,c,d,e){var f=lk();f.a.x=b;f.a.y=c;return tk(a,f,d,e)} +function vk(a,b,c,d,e,f){var g=lk();g.a.x=b;g.a.y=c;g.a.z=d;return tk(a,g,e,f)}function wk(a,b){var c={},d;c.x=b;c.e=null;if(null==a)a=c;else{for(d=a;null!=d.e;d=d.e);d.e=c}return a}function xk(a){var b;for(b=0;null!=a;a=a.e)b++;return b} +function yk(a){var b,c,d,e,f,g,h,k,l,p=lk(),m=a.V[a.h];null==m&&U(a,a.h+" not defined");switch(m.type){case kh:b=m.link;k=b.name;l=0;break;case uh:c=m.link;k=c.name;l=c.q;0==c.X&&(c.X=1);break;case sh:d=m.link;k=d.name;l=d.q;break;case yh:e=m.link;k=e.name;l=e.q;break;case ch:f=m.link,k=f.name,l=f.q}Y(a);if(a.b==si){0==l&&U(a,k+" cannot be subscripted");Y(a);for(var q=null;;)if(g=zk(a),g.type==N&&(g=Z(Vi,g,T,0)),g.type!=T&&U(a,"subscript expression has invalid type"),q=wk(q,g),a.b==li)Y(a);else if(a.b== +ti)break;else U(a,"syntax error in subscript list");g=q;l!=xk(g)&&U(a,k+" must have "+l+" subscript"+(1==l?"":"s")+" rather than "+xk(g));Y(a)}else 0!=l&&U(a,k+" must be subscripted"),g=null;l=a.Ob||m.type!=yh?Di:zi;a.b==ki&&(Y(a),a.b!=Bh&&U(a,"invalid use of period"),m.type!=yh&&m.type!=ch&&U(a,k+" cannot have a suffix"),"lb"==a.h?l=Ai:"ub"==a.h?l=Bi:"status"==a.h?l=Ci:"val"==a.h?l=Di:"dual"==a.h?l=Ei:U(a,"suffix ."+a.h+" invalid"),Y(a));switch(m.type){case kh:p.index.ya=b;p.index.e=b.list;h=tk(Hi, +p,T,0);b.list=h;break;case uh:p.set.set=c;p.set.list=g;h=tk(Ki,p,fh,c.X);break;case sh:p.S.S=d;p.S.list=g;h=d.type==T?tk(Ji,p,T,0):tk(Ii,p,N,0);break;case yh:a.Ob||l!=Ci&&l!=Di&&l!=Ei||U(a,"invalid reference to status, primal value, or dual value of variable "+e.name+" above solve statement");p.t.t=e;p.t.list=g;p.t.Ac=l;h=tk(Li,p,l==zi?jh:N,0);break;case ch:a.Ob||l!=Ci&&l!=Di&&l!=Ei||U(a,"invalid reference to status, primal value, or dual value of "+(f.type==ch?"constraint":"objective")+" "+f.name+ +" above solve statement"),p.H.H=f,p.H.list=g,p.H.Ac=l,h=tk(Mi,p,N,0)}return h}function Ak(a,b){var c=zk(a);c.type==T&&(c=Z(Ui,c,N,0));c.type!=N&&U(a,"argument for "+b+" has invalid type");return c}function Bk(a,b){var c=zk(a);c.type==N&&(c=Z(Vi,c,T,0));c.type!=T&&U(a,"argument for "+b+" has invalid type");return c}function Ck(a,b,c){var d={};d.name=b;d.code=c;d.value=null;d.list=null;d.e=null;if(null==a.list)a.list=d;else{for(a=a.list;null!=a.e;a=a.e);a.e=d}} +function Dk(a){var b,c=lk(),d=Array(21);ia(d,0,21);var e,f,g,h=0;e=a.Pf;Y(a);for(g=1;;g++){20=a.value&&Math.floor(a.value)==a.value||U(a,"dimension must be integer between 1 and 20");l=a.value+0.5|0;h&&U(a,"at most one dimension attribute allowed");0=k.set.X&&d();0==f.X&&(f.X=k.set.X-f.q);f.q+f.X>k.set.X?d():f.q+f.X= has invalid type");else if(a.b==di)null!=e.W&&(e.W==e.P?U(a,"both fixed value and upper bound not allowed"):U(a,"at most one upper bound allowed")),Y(a),e.W=zk(a),e.W.type==T&&(e.W=Z(Ui,e.W,N,0)),e.W.type!=N&&U(a,"expression following <= has invalid type");else if(a.b==ei){if(null!=e.P||null!=e.W)e.P==e.W?U(a,"at most one fixed value allowed"):null!=e.P?U(a,"both lower bound and fixed value not allowed"):U(a,"both upper bound and fixed value not allowed");f=a.h;Y(a); +e.P=zk(a);e.P.type==T&&(e.P=Z(Ui,e.P,N,0));e.P.type!=N&&U(a,"expression following "+f+" has invalid type");e.W=e.P}else a.b==ci||a.b==gi||a.b==hi?U(a,"strict bound not allowed"):U(a,"syntax error in variable statement")}null!=e.domain&&Hk(a,e.domain);Y(a);return e} +function al(a){function b(){U(a,"syntax error in constraint statement")}var c,d,e,f;a.Ob&&U(a,"constraint statement must precede solve statement");rk(a,"subject")?(Y(a),rk(a,"to")||U(a,"keyword subject to incomplete"),Y(a)):rk(a,"subj")?(Y(a),rk(a,"to")||U(a,"keyword subj to incomplete"),Y(a)):a.b==Th&&Y(a);a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"symbolic name missing where expected"));null!=a.V[a.h]&&U(a,a.h+" multiply declared");var g={};g.name=a.h;g.Ib=null;g.q=0;g.domain= +null;g.type=ch;g.code=null;g.P=null;g.W=null;g.O=null;Y(a);a.b==Eh&&(g.Ib=a.h,Y(a));a.b==ui&&(g.domain=Fk(a),g.q=Mk(g.domain));c=a.V[g.name]={};c.type=ch;c.link=g;a.b!=mi&&U(a,"colon missing where expected");Y(a);c=zk(a);c.type==T&&(c=Z(Ui,c,N,0));c.type!=N&&c.type!=jh&&U(a,"expression following colon has invalid type");a.b==li&&Y(a);switch(a.b){case di:case fi:case ei:break;case ci:case gi:case hi:U(a,"strict inequality not allowed");break;case ni:U(a,"constraint must be equality or inequality"); +break;default:b()}f=a.b;e=a.h;Y(a);d=zk(a);d.type==T&&(d=Z(Ui,d,N,0));d.type!=N&&d.type!=jh&&U(a,"expression following "+e+" has invalid type");a.b==li&&(Y(a),a.b==ni&&b());a.b==ci||a.b==di||a.b==ei||a.b==fi||a.b==gi||a.b==hi?(f!=ei&&a.b==f||U(a,"double inequality must be ... <= ... <= ... or ... >= ... >= ..."),c.type==jh&&U(a,"leftmost expression in double inequality cannot be linear form"),Y(a),e=zk(a),e.type==T&&(e=Z(Ui,d,N,0)),e.type!=N&&e.type!=jh&&U(a,"rightmost expression in double inequality constraint has invalid type"), +e.type==jh&&U(a,"rightmost expression in double inequality cannot be linear form")):e=null;null!=g.domain&&Hk(a,g.domain);c.type!=jh&&(c=Z(Yi,c,jh,0));d.type!=jh&&(d=Z(Yi,d,jh,0));null!=e&&(e=Z(Yi,e,jh,0));if(null==e)switch(f){case di:g.code=c;g.P=null;g.W=d;break;case fi:g.code=c;g.P=d;g.W=null;break;case ei:g.code=c,g.P=d,g.W=d}else switch(f){case di:g.code=d;g.P=c;g.W=e;break;case fi:g.code=d,g.P=e,g.W=c}a.b!=ni&&b();Y(a);return g} +function bl(a){var b,c={domain:null};c.list=b=null;Y(a);a.b==ui&&(c.domain=Fk(a));for(a.b==mi&&Y(a);;){var d={v:{}},e=function(){d.type=hh;d.v.code=Ek(a)};d.type=0;d.e=null;null==c.list?c.list=d:b.e=d;b=d;if(a.b==Bh){var f;Y(a);f=a.b;qk(a);if(f!=li&&f!=ni)e();else{e=a.V[a.h];null==e&&U(a,a.h+" not defined");d.type=e.type;switch(e.type){case kh:d.v.ya=e.link;break;case uh:d.v.set=e.link;break;case sh:d.v.S=e.link;break;case yh:d.v.t=e.link;a.Ob||U(a,"invalid reference to variable "+d.v.t.name+" above solve statement"); +break;case ch:d.v.H=e.link,a.Ob||U(a,"invalid reference to "+(d.v.H.type==ch?"constraint":"objective")+" "+d.v.H.name+" above solve statement")}Y(a)}}else e();if(a.b==li)Y(a);else break}null!=c.domain&&Hk(a,c.domain);a.b!=ni&&U(a,"syntax error in display statement");Y(a);return c} +function cl(a){!a.nc&&rk(a,"end")||a.nc&&dl(a,"end")?(Y(a),a.b==ni?Y(a):ok(a,"no semicolon following end statement; missing semicolon inserted")):ok(a,"unexpected end of file; missing end statement inserted");a.b!=Ah&&ok(a,"some text detected beyond end statement; text ignored")} +function el(a,b){var c={v:{}};c.bb=a.bb;c.Vc=a.Vc;c.e=null;if(rk(a,"set"))b&&U(a,"set statement not allowed here"),c.type=uh,c.v.set=Yk(a);else if(rk(a,"param"))b&&U(a,"parameter statement not allowed here"),c.type=sh,c.v.S=Zk(a);else if(rk(a,"var"))b&&U(a,"variable statement not allowed here"),c.type=yh,c.v.t=$k(a);else if(rk(a,"subject")||rk(a,"subj")||a.b==Th)b&&U(a,"constraint statement not allowed here"),c.type=ch,c.v.H=al(a);else if(rk(a,"minimize")||rk(a,"maximize")){b&&U(a,"objective statement not allowed here"); +c.type=ch;var d=c.v,e,f;rk(a,"minimize")?f=ph:rk(a,"maximize")&&(f=oh);a.Ob&&U(a,"objective statement must precede solve statement");Y(a);a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"symbolic name missing where expected"));null!=a.V[a.h]&&U(a,a.h+" multiply declared");e={};e.name=a.h;e.Ib=null;e.q=0;e.domain=null;e.type=f;e.code=null;e.P=null;e.W=null;e.O=null;Y(a);a.b==Eh&&(e.Ib=a.h,Y(a));a.b==ui&&(e.domain=Fk(a),e.q=Mk(e.domain));f=a.V[e.name]={};f.type=ch;f.link=e;a.b!=mi&& +U(a,"colon missing where expected");Y(a);e.code=zk(a);e.code.type==T&&(e.code=Z(Ui,e.code,N,0));e.code.type==N&&(e.code=Z(Yi,e.code,jh,0));e.code.type!=jh&&U(a,"expression following colon has invalid type");null!=e.domain&&Hk(a,e.domain);a.b!=ni&&U(a,"syntax error in objective statement");Y(a);d.H=e}else if(rk(a,"table")){b&&U(a,"table statement not allowed here");c.type=wh;var d=c.v,g,h,k;Y(a);a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"symbolic name missing where expected")); +null!=a.V[a.h]&&U(a,a.h+" multiply declared");e={v:{qa:{},Oc:{}}};e.name=a.h;Y(a);a.b==Eh?(e.Ib=a.h,Y(a)):e.Ib=null;a.b==ui?(e.type=rh,e.v.Oc.domain=Fk(a),rk(a,"OUT")||U(a,"keyword OUT missing where expected")):(e.type=lh,rk(a,"IN")||U(a,"keyword IN missing where expected"));Y(a);for(e.a=f=null;;)if(g={},a.b!=li&&a.b!=mi&&a.b!=ni||U(a,"argument expression missing where expected"),g.code=zk(a),g.code.type==N&&(g.code=Z(Vi,g.code,T,0)),g.code.type!=T&&U(a,"argument expression has invalid type"),g.e= +null,null==f?e.a=g:f.e=g,f=g,a.b==li)Y(a);else if(a.b==mi||a.b==ni)break;a.b==mi?Y(a):U(a,"colon missing where expected");switch(e.type){case lh:a.b==Bh?(g=a.V[a.h],null==g&&U(a,a.h+" not defined"),g.type!=uh&&U(a,a.h+" not a set"),e.v.qa.set=g.link,null!=e.v.qa.set.assign&&U(a,a.h+" needs no data"),0!=e.v.qa.set.q&&U(a,a.h+" must be a simple set"),Y(a),a.b==yi?Y(a):U(a,"delimiter <- missing where expected")):sk(a)?U(a,"invalid use of reserved keyword "+a.h):e.v.qa.set=null;e.v.qa.We=g=null;f=0;for(a.b== +si?Y(a):U(a,"field list missing where expected");;)if(h={},a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"field name missing where expected")),h.name=a.h,Y(a),h.e=null,null==g?e.v.qa.We=h:g.e=h,g=h,f++,a.b==li)Y(a);else if(a.b==ti)break;else U(a,"syntax error in field list");null!=e.v.qa.set&&e.v.qa.set.X!=f&&U(a,"there must be "+e.v.qa.set.X+" field"+(1==e.v.qa.set.X?"":"s")+" rather than "+f);Y(a);for(e.v.qa.list=h=null;a.b==li;)Y(a),k={},a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+ +a.h):U(a,"parameter name missing where expected")),g=a.V[a.h],null==g&&U(a,a.h+" not defined"),g.type!=sh&&U(a,a.h+" not a parameter"),k.S=g.link,k.S.q!=f&&U(a,a.h+" must have "+f+" subscript"+(1==f?"":"s")+" rather than "+k.S.q),null!=k.S.assign&&U(a,a.h+" needs no data"),Y(a),a.b==xi?(Y(a),a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"field name missing where expected")),g=a.h,Y(a)):g=k.S.name,k.name=g,k.e=null,null==h?e.v.qa.list=k:h.e=k,h=k;break;case rh:for(e.v.Oc.list=f=null;;)if(h= +{},a.b!=li&&a.b!=ni||U(a,"expression missing where expected"),g=a.b==Bh?a.h:"",h.code=zk(a),a.b==xi&&(Y(a),a.b!=Bh&&(sk(a)?U(a,"invalid use of reserved keyword "+a.h):U(a,"field name missing where expected")),g=a.h,Y(a)),""==g&&U(a,"field name required"),h.name=g,h.e=null,null==f?e.v.Oc.list=h:f.e=h,f=h,a.b==li)Y(a);else if(a.b==ni)break;else U(a,"syntax error in output list");Hk(a,e.v.Oc.domain)}a.b!=ni&&U(a,"syntax error in table statement");Y(a);d.nd=e}else if(rk(a,"solve"))b&&U(a,"solve statement not allowed here"), +c.type=vh,d=c.v,a.Ob&&U(a,"at most one solve statement allowed"),a.Ob=1,Y(a),a.b!=ni&&U(a,"syntax error in solve statement"),Y(a),d.zh=null;else if(rk(a,"check"))c.type=bh,d=c.v,e={domain:null,code:null},Y(a),a.b==ui&&(e.domain=Fk(a)),a.b==mi&&Y(a),e.code=Ek(a),e.code.type!=nh&&U(a,"expression has invalid type"),null!=e.domain&&Hk(a,e.domain),a.b!=ni&&U(a,"syntax error in check statement"),Y(a),d.Rg=e;else if(rk(a,"display"))c.type=dh,c.v.Sg=bl(a);else if(rk(a,"printf")){c.type=th;d=c.v;g={domain:null, +zd:null};g.list=f=null;Y(a);a.b==ui&&(g.domain=Fk(a));a.b==mi&&Y(a);g.zd=zk(a);g.zd.type==N&&(g.zd=Z(Vi,g.zd,T,0));for(g.zd.type!=T&&U(a,"format expression has invalid type");a.b==li;)Y(a),e={code:null,e:null},null==g.list?g.list=e:f.e=e,f=e,e.code=Gk(a),e.code.type!=N&&e.code.type!=T&&e.code.type!=nh&&U(a,"only numeric, symbolic, or logical expression allowed");null!=g.domain&&Hk(a,g.domain);g.Ea=null;g.Mg=0;if(a.b==gi||a.b==wi)g.Mg=a.b==wi,Y(a),g.Ea=zk(a),g.Ea.type==N&&(g.Ea=Z(Vi,g.Ea,T,0)),g.Ea.type!= +T&&U(a,"file name expression has invalid type");a.b!=ni&&U(a,"syntax error in printf statement");Y(a);d.nh=g}else if(rk(a,"for")){c.type=ih;d=c.v;g={domain:null};g.list=f=null;Y(a);a.b!=ui&&U(a,"indexing expression missing where expected");g.domain=Fk(a);a.b==mi&&Y(a);if(a.b!=ui)g.list=el(a,1);else{for(Y(a);a.b!=vi;)e=el(a,1),null==f?g.list=e:f.e=e,f=e;Y(a)}Hk(a,g.domain);d.Ug=g}else a.b==Bh?(b&&U(a,"constraint statement not allowed here"),c.type=ch,c.v.H=al(a)):sk(a)?U(a,"invalid use of reserved keyword "+ +a.h):U(a,"syntax error in model section");return c}function fl(a){var b,c;for(c=null;a.b!=Ah&&!rk(a,"data")&&!rk(a,"end");)b=el(a,0),null==c?a.uc=b:c.e=b,c=b}function gl(a,b){var c,d={};d.Y=b;d.e=null;if(null==a)a=d;else{for(c=a;null!=c.e;c=c.e);c.e=d}return a}function hl(a){for(var b=0;null!=a;a=a.e)b++;return b}function il(a){for(var b=0;null!=a;a=a.e)null==a.Y&&b++;return b}function jl(a){for(var b=null;00.999*s-c||0>b&&0>c&&b<-0.999*s-c)&&U(a,b+" + "+c+"; floating-point overflow");return b+c}function Ml(a,b,c){(0c&&b>0.999*s+c||0>b&&0c&&b>0.999*s+c&&U(a,b+" less "+c+"; floating-point overflow");return b-c} +function Ol(a,b,c){10.999*s/Math.abs(c)&&U(a,b+" * "+c+"; floating-point overflow");return b*c}function Pl(a,b,c){Math.abs(c)Math.abs(c)&&Math.abs(b)>0.999*s*Math.abs(c)&&U(a,b+" / "+c+"; floating-point overflow");return b/c} +function Ql(a,b,c){Math.abs(c)Math.abs(c)&&Math.abs(b)>0.999*s*Math.abs(c)&&U(a,b+" div "+c+"; floating-point overflow");b/=c;return 0b?Math.ceil(b):0}function Rl(a,b){var c;if(0==a)c=0;else if(0==b)c=a;else if(c=Math.abs(a)%Math.abs(b),0!=c&&(0>a&&(c=-c),0b||0>a&&0=c||0>b&&c!=Math.floor(c))&&U(a,b+" ** "+c+"; result undefined");0==b?a=Math.pow(b,c):((10.999*Math.log(s)/c||1>Math.abs(b)&&-1>c&&+Math.log(Math.abs(b))<0.999*Math.log(s)/c)&&U(a,b+" ** "+c+"; floating-point overflow"),a=1c&&-Math.log(Math.abs(b))<0.999*Math.log(s)/c||1>Math.abs(b)&&10.999*Math.log(s)/c?0:Math.pow(b,c));return a} +function Tl(a,b){b>0.999*Math.log(s)&&U(a,"exp("+b+"); floating-point overflow");return Math.exp(b)}function Ul(a,b){0>=b&&U(a,"log("+b+"); non-positive argument");return Math.log(b)}function Vl(a,b){0>=b&&U(a,"log10("+b+"); non-positive argument");return Math.log(b)/Math.LN10}function Wl(a,b){0>b&&U(a,"sqrt("+b+"); negative argument");return Math.sqrt(b)}function Xl(a,b){-1E6<=b&&1E6>=b||U(a,"sin("+b+"); argument too large");return Math.sin(b)} +function Yl(a,b){-1E6<=b&&1E6>=b||U(a,"cos("+b+"); argument too large");return Math.cos(b)}function Zl(a){return Math.atan(a)}function $l(a,b){return Math.atan2(a,b)}function am(a,b,c){c!=Math.floor(c)&&U(a,"round("+b+", "+c+"); non-integer second argument");18>=c&&(a=Math.pow(10,c),Math.abs(b)<0.999*s/a&&(b=Math.floor(b*a+0.5),0!=b&&(b/=a)));return b} +function bm(a,b,c){c!=Math.floor(c)&&U(a,"trunc("+b+", "+c+"); non-integer second argument");18>=c&&(a=Math.pow(10,c),Math.abs(b)<0.999*s/a&&(b=0<=b?Math.floor(b*a):Math.ceil(b*a),0!=b&&(b/=a)));return b}function cm(a,b,c){var d;b>=c&&U(a,"Uniform("+b+", "+c+"); invalid range");d=dm(a.Gd)/2147483648;return d=Ll(a,b*(1-d),c*d)}function em(a){var b,c;do b=-1+2*(dm(a.Gd)/2147483648),c=-1+2*(dm(a.Gd)/2147483648),b=b*b+c*c;while(1b.Q?1:0;else if(null==a.M)c=-1;else if(null==b.M)c=1;else{c=a.M;var d=b.M;c=c==d?0:c>d?1:-1}return c} +function rl(a){var b;if(null==a.M)b=String(a.Q);else{var c,d,e=a.M;if(ua(e[0])||"_"==e[0])for(a=!1,c=1;cd&&(b+=a,d++)};a&&f("'");for(c=0;cf&&(g+=a);f++}var d,e,f=0,g="",h="",k=xl(b);"["==a&&0b&&c>0.999*s+b?+s:0>c&&0Math.abs(d)&&Math.abs(e)>0.999*s*Math.abs(d)?e=0e&&0>d?+s:0:(e=Math.floor(e/d)+1,0>e&&(e=0));2147483646d?a.left:a.right}c=a;a=null==c?null:c.link}else for(a=b.head;null!=a&&0!=hm(a.w,c);a=a.e);return a}function Al(a,b){var c={};c.w=b;c.e=null;c.value={};a.size++;null==a.head?a.head=c:a.Xa.e=c;a.Xa=c;null!=a.V&&(qe(a.V,c.w).link=c);return c} +function tm(a){var b;if(null!=a.Le)for(b=a.list,a=a.Le;null!=b;b=b.e,a=a.e)a:{var c=b,d=a.Y,e=void 0,f=void 0;if(null!=c.value){if(0==gm(c.value,d))break a;c.value=null}for(e=c.list;null!=e;e=e.a.index.e)for(f=e;null!=f;f=f.R)f.valid&&(f.valid=0,rm(f.type,f.value));c.value=sl(d)}}function um(a,b,c,d,e){var f,g=0;if(!vm(a,b.code,c))return 1;f=b.Le;b.Le=c;tm(b);e(a,d);b.Le=f;tm(b);return g} +function wm(a,b){if(null!=b.Kc){var c,d,e=null,f=null;c=b.Kc;b.Kc=c.e;for(d=c.list;null!=d;d=d.e)null==e?e=f={}:f=f.e={},null==d.code?(f.Y=b.w.Y,b.w=b.w.e):f.Y=xm(a,d.code);f.e=null;um(a,c,e,b,wm)&&(b.Ve=1);for(d=c.list;null!=d;d=d.e)e=e.e}else null==b.domain.code||ym(a,b.domain.code)?b.Zd(a,b.info):b.Ve=2}function zm(a,b,c,d,e){var f={};null==b?(e(a,d),f.Ve=0):(f.domain=b,f.Kc=b.list,f.w=c,f.info=d,f.Zd=e,f.Ve=0,wm(a,f));return f.Ve} +function Am(a,b){if(null!=b.Kc){var c,d,e;c=b.Kc;b.Kc=c.e;e=null;for(d=c.list;null!=d;d=d.e)null!=d.code&&(e=tl(e,xm(a,d.code)));if(c.code.Ta==Yj){var f,g,h,k;g=$(a,c.code.a.a.x);h=$(a,c.code.a.a.y);k=null==c.code.a.a.z?1:$(a,c.code.a.a.z);e=lm(a,g,h,k);d=tl(null,ml(0));for(f=1;f<=e&&b.Wf;f++)d.Y.Q=mm(a,g,h,k,f),um(a,c,d,b,Am)}else for(f=Bm(a,c.code).head;null!=f&&b.Wf;f=f.e){g=f.w;h=e;k=!1;for(d=c.list;null!=d;d=d.e){if(null!=d.code){if(0!=gm(g.Y,h.Y)){k=!0;break}h=h.e}g=g.e}k||um(a,c,f.w,b,Am)}b.Kc= +c}else if(null==b.domain.code||ym(a,b.domain.code))b.Wf=!b.Zd(a,b.info)}function Cm(a,b,c,d){var e={};null==b?d(a,c):(e.domain=b,e.Kc=b.list,e.Wf=1,e.info=c,e.Zd=d,Am(a,e))}function Dm(a,b,c){U(a,b+zl("[",c)+" out of domain")}function Em(a){var b=null;if(null!=a)for(a=a.list;null!=a;a=a.e)for(var c=a.list;null!=c;c=c.e)null==c.code&&(b=tl(b,sl(c.value)));return b} +function Fm(a,b,c,d){for(var e=b.Bf,f=1;null!=e;e=e.e,f++)for(var g=d.head;null!=g;g=g.e)if(!vm(a,e.code,g.w)){var h=zl("(",g.w);U(a,b.name+zl("[",c)+" contains "+h+" which not within specified set; see ("+f+")")}}function Gm(a,b,c){function d(){Fm(a,b,c,e);f=Al(b.O,Il(c));f.value.set=e}var e,f=yl(a,b.O,c);null!=f?e=f.value.set:null!=b.assign?(e=Bm(a,b.assign),d()):null!=b.xa?(e=Bm(a,b.xa),d()):U(a,"no value for "+b.name+zl("[",c));return e} +function Hm(a,b){null!=b.ga?Fm(a,b.set,b.ga.w,b.ga.value.set):b.oe=Gm(a,b.set,b.w)} +function Im(a,b){var c=b.Yc,d,e,f,g=Array(20);x("Generating "+b.name+"...");d=c.set;Cm(a,d.domain,d,Jm);for(d=c.set.O.head.value.set.head;null!=d;d=d.e){f=Il(d.w);for(e=0;e=g||h(">=");break;case Hj:d>g||h(">");break;case Ij:d==g&&h("<>")}}f=1;for(e= +b.qa;null!=e;e=e.e,f++)h=tl(null,ml(d)),vm(a,e.code,h)||U(a,b.name+zl("[",c)+" = "+d+" not in specified set; see ("+f+")")}function Mm(a,b,c){function d(d){Lm(a,b,c,d);e=Al(b.O,Il(c));return e.value.Q=d}var e=yl(a,b.O,c);return null!=e?e.value.Q:null!=b.assign?d($(a,b.assign)):null!=b.xa?d($(a,b.xa)):null!=b.Wc?(null!=b.Wc.M&&U(a,"cannot convert "+rl(b.Wc)+" to floating-point number"),d(b.Wc.Q)):U(a,"no value for "+b.name+zl("[",c))} +function Nm(a,b){null!=b.ga?Lm(a,b.S,b.ga.w,b.ga.value.Q):b.value=Mm(a,b.S,b.w)}function Om(a,b,c){var d={};d.S=b;d.w=c;if(1==b.data)for(c=b.O.Xa,b.data=2,d.ga=b.O.head;null!=d.ga&&(zm(a,b.domain,d.ga.w,d,Nm)&&Dm(a,b.name,d.ga.w),d.ga!=c);d.ga=d.ga.e);d.ga=null;zm(a,d.S.domain,d.w,d,Nm)&&Dm(a,b.name,d.w);return d.value} +function Pm(a,b,c,d){var e,f=1;for(e=b.wd;null!=e;e=e.e,f++){var g;g=xm(a,e.code);switch(e.jd){case Dj:0>gm(d,g)||(g=rl(g),U(a,b.name+zl("[",c)+" = "+rl(d)+" not < "+g));break;case Ej:0>=gm(d,g)||(g=rl(g),U(a,b.name+zl("[",c)+" = "+rl(d)+" not <= "+g));break;case Fj:0!=gm(d,g)&&(g=rl(g),U(a,b.name+zl("[",c)+" = "+rl(d)+" not = "+g));break;case Gj:0<=gm(d,g)||(g=rl(g),U(a,b.name+zl("[",c)+" = "+rl(d)+" not >= "+g));break;case Hj:0 "+g));break; +case Ij:0==gm(d,g)&&(g=rl(g),U(a,b.name+zl("[",c)+" <> "+rl(d)+" not > "+g))}}f=1;for(e=b.qa;null!=e;e=e.e,f++)g=tl(null,sl(d)),vm(a,e.code,g)||U(a,b.name,zl("[",c)+" = "+rl(d)+" not in specified set; see ("+f+")")}function Qm(a,b,c){function d(d){Pm(a,b,c,d);e=Al(b.O,Il(c));e.value.Y=sl(d);return d}var e=yl(a,b.O,c);return null!=e?sl(e.value.Y):null!=b.assign?d(xm(a,b.assign)):null!=b.xa?d(xm(a,b.xa)):null!=b.Wc?sl(b.Wc):U(a,"no value for "+b.name+zl("[",c))} +function Rm(a,b){null!=b.ga?Pm(a,b.S,b.ga.w,b.ga.value.Y):b.value=Qm(a,b.S,b.w)}function Sm(a,b,c){var d={};d.S=b;d.w=c;if(1==b.data)for(c=b.O.Xa,b.data=2,d.ga=b.O.head;null!=d.ga&&(zm(a,b.domain,d.ga.w,d,Rm)&&Dm(a,b.name,d.ga.w),d.ga!=c);d.ga=d.ga.e);d.ga=null;zm(a,d.S.domain,d.w,d,Rm)&&Dm(a,b.name,d.w);return d.value}function Tm(a,b){var c=Em(b.domain);switch(b.type){case N:case mh:case ah:Om(a,b,c);break;case T:Sm(a,b,c)}return 0} +function Um(a,b){var c=b.t,d=b.w,e=yl(a,c.O,d);null!=e?d=e.value.t:(e=Al(c.O,Il(d)),d=e.value.t={},d.C=0,d.t=c,d.ga=e,d.P=null==c.P?0:$(a,c.P),d.W=null==c.W?0:c.W==c.P?d.P:$(a,c.W),d.ja=0,d.m=0,d.r=d.J=0);b.oe=d}function Vm(a,b,c){var d={};d.t=b;d.w=c;zm(a,d.t.domain,d.w,d,Um)&&Dm(a,b.name,d.w);return d.oe} +function Wm(a,b,c){var d=null,e=yl(a,b.O,c);if(null!=e)c=e.value.H;else{e=Al(b.O,Il(c));c=e.value.H={};c.ea=0;c.H=b;c.ga=e;c.form=Xm(a,b.code);if(null==b.P&&null==b.W)c.form=qm(a,c.form,function(a){d=a}),c.P=c.W=-d;else if(null!=b.P&&null==b.W)c.form=pm(a,1,c.form,-1,Xm(a,b.P)),c.form=qm(a,c.form,function(a){d=a}),c.P=-d,c.W=0;else if(null==b.P&&null!=b.W)c.form=pm(a,1,c.form,-1,Xm(a,b.W)),c.form=qm(a,c.form,function(a){d=a}),c.P=0,c.W=-d;else if(b.P==b.W)c.form=pm(a,1,c.form,-1,Xm(a,b.P)),c.form= +qm(a,c.form,function(a){d=a}),c.P=c.W=-d;else{var f=null,g=null;c.form=qm(a,c.form,function(a){d=a});qm(a,Xm(a,b.P),function(a){f=a});qm(a,Xm(a,b.W),function(a){g=a});c.P=Ml(a,f,d);c.W=Ml(a,g,d)}c.m=0;c.r=c.J=0}return c}function Ym(a,b){b.oe=Wm(a,b.H,b.w)}function Zm(a,b,c){var d={};d.H=b;d.w=c;zm(a,d.H.domain,d.w,d,Ym)&&Dm(a,b.name,d.w);return d.oe}function $m(a,b){var c=Em(b.domain);Zm(a,b,c);return 0} +function an(a,b){var c=$(a,b.code.a.loop.x);switch(b.code.Ta){case dk:b.value=Ll(a,b.value,c);break;case ek:b.value=Ol(a,b.value,c);break;case fk:b.value>c&&(b.value=c);break;case gk:b.valued&&(c=d);break;case ck:c=-s;for(e=b.a.list;null!=e;e=e.e)d=$(a,e.x),ce||e>c.length+1)&&U(a,"substr('...', "+e+"); substring out of range")):(e=$(a,b.a.a.y),d=$(a,b.a.a.z),e==Math.floor(e)&&d==Math.floor(d)||U(a,"substr('...', "+e+", "+d+"); non-integer second and/or third argument"),(1>e||0>d||e+d>c.length+1)&&U(a,"substr('...', "+e+", "+d+"); substring out of range"));c=nl(c.slice(e-1,e+d-1));break;case Xj:d=$(a,b.a.a.x), +c=xm(a,b.a.a.y),c=dn(a,d,null==c.M?String(c.Q):c.M),c=nl(c)}b.valid=1;b.value.Y=sl(c);return c}function en(a,b){var c=0;switch(b.code.Ta){case hk:b.value&=ym(a,b.code.a.loop.x);b.value||(c=1);break;case ik:b.value|=ym(a,b.code.a.loop.x),b.value&&(c=1)}return c} +function ym(a,b){var c,d;b.T&&b.valid&&(b.valid=0,rm(b.type,b.value));if(b.valid)return b.value.sg;switch(b.Ta){case Wi:c=0!=$(a,b.a.a.x);break;case aj:c=!ym(a,b.a.a.x);break;case Dj:b.a.a.x.type==N?c=$(a,b.a.a.x)<$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0>gm(c,d));break;case Ej:b.a.a.x.type==N?c=$(a,b.a.a.x)<=$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0>=gm(c,d));break;case Fj:b.a.a.x.type==N?c=$(a,b.a.a.x)==$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0==gm(c,d));break;case Gj:b.a.a.x.type== +N?c=$(a,b.a.a.x)>=$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0<=gm(c,d));break;case Hj:b.a.a.x.type==N?c=$(a,b.a.a.x)>$(a,b.a.a.y):(c=xm(a,b.a.a.x),d=xm(a,b.a.a.y),c=0b&&!(e<=c&&c<=f)){d=0;break}d=mm(a,f,e,b,((c-f)/b+0.5|0)+1)==c;break;case Zj:d=ym(a,b.a.a.x)?vm(a,b.a.a.y,c):vm(a,b.a.a.z,c);break;case jk:U(a,"implementation restriction; in/within setof{} not allowed");break;case kk:f=im(c,b.q),d=0==zm(a,b.a.loop.domain,f,null,hn)}return d} +function jn(a,b){switch(b.code.Ta){case dk:var c;c=Xm(a,b.code.a.loop.x);for(null==b.value?b.value=c:b.Xa.e=c;null!=c;c=c.e)b.Xa=c}return 0} +function Xm(a,b){var c;b.T&&b.valid&&(b.valid=0,rm(b.type,b.value));if(b.valid)return om(b.value.form);switch(b.Ta){case Li:var d=null;for(c=b.a.t.list;null!=c;c=c.e)d=tl(d,xm(a,c.x));c=Vm(a,b.a.t.t,d);d={u:1};d.t=c;d.e=null;c=d;break;case Yi:c=nm($(a,b.a.a.x));break;case Zi:c=pm(a,0,nm(0),1,Xm(a,b.a.a.x));break;case $i:c=pm(a,0,nm(0),-1,Xm(a,b.a.a.x));break;case pj:c=pm(a,1,Xm(a,b.a.a.x),1,Xm(a,b.a.a.y));break;case qj:c=pm(a,1,Xm(a,b.a.a.x),-1,Xm(a,b.a.a.y));break;case sj:c=b.a.a.x.type==N?pm(a, +$(a,b.a.a.x),Xm(a,b.a.a.y),0,nm(0)):pm(a,$(a,b.a.a.y),Xm(a,b.a.a.x),0,nm(0));break;case tj:c=pm(a,Pl(a,1,$(a,b.a.a.y)),Xm(a,b.a.a.x),0,nm(0));break;case Zj:c=ym(a,b.a.a.x)?Xm(a,b.a.a.y):null==b.a.a.z?nm(0):Xm(a,b.a.a.z);break;case dk:c={};c.code=b;c.value=nm(0);c.Xa=null;Cm(a,b.a.loop.domain,c,jn);c=c.value;for(var e,f=0,d=c;null!=d;d=d.e)null==d.t?f=Ll(a,f,d.u):d.t.ja=Ll(a,d.t.ja,d.u);e=c;c=null;for(d=e;null!=d;d=e)e=d.e,null==d.t&&0!=f?(d.u=f,f=0,d.e=c,c=d):null!=d.t&&0!=d.t.ja&&(d.u=d.t.ja,d.t.ja= +0,d.e=c,c=d)}b.valid=1;b.value.form=om(c);return c}var kn=exports.mpl_tab_num_args=function(a){return a.ff},ln=exports.mpl_tab_get_arg=function(a,b){return a.a[b]};exports.mpl_tab_get_args=function(a){return a.a}; +var mn=exports.mpl_tab_num_flds=function(a){return a.Wa},nn=exports.mpl_tab_get_name=function(a,b){return a.name[b]},on=exports.mpl_tab_get_type=function(a,b){return a.type[b]},pn=exports.mpl_tab_get_num=function(a,b){return a.Q[b]},qn=exports.mpl_tab_get_str=function(a,b){return a.M[b]},rn=exports.mpl_tab_set_num=function(a,b,c){a.type[b]="N";a.Q[b]=c},sn=exports.mpl_tab_set_str=function(a,b,c){a.type[b]="S";a.M[b]=c}; +function tn(a,b){var c=a.Mc,d,e,f;f=0;for(d=b.v.Oc.list;null!=d;d=d.e)switch(f++,d.code.type){case N:c.type[f]="N";c.Q[f]=$(a,d.code);c.M[f][0]="\x00";break;case T:e=xm(a,d.code),null==e.M?(c.type[f]="N",c.Q[f]=e.Q,c.M[f][0]="\x00"):(c.type[f]="S",c.Q[f]=0,c.M[f]=e.M)}c=a.Mc;c.link.writeRecord(c)&&U(a,"error on writing data to table "+a.ib.v.nd.name);return 0}function un(a,b){ym(a,b.code)||U(a,"check"+zl("[",Em(b.domain))+" failed");return 0} +function vn(a,b,c){var d=c.value.set;wn(a,b.name+zl("[",c.w)+(null==d.head?" is empty":":"));for(b=d.head;null!=b;b=b.e)wn(a," "+zl("(",b.w))}function xn(a,b,c){switch(b.type){case N:case mh:case ah:wn(a,b.name+zl("[",c.w)+" = "+c.value.Q);break;case T:wn(a,b.name+zl("[",c.w)+" = "+rl(c.value.Y))}} +function yn(a,b,c,d){d==zi||d==Di?wn(a,b.name+zl("[",c.w)+".val = "+c.value.t.r):d==Ai?wn(a,b.name+zl("[",c.w)+".lb = "+(null==c.value.t.t.P?-s:c.value.t.P)):d==Bi?wn(a,b.name+zl("[",c.w)+".ub = "+(null==c.value.t.t.W?+s:c.value.t.W)):d==Ci?wn(a,b.name+zl("[",c.w)+".status = "+c.value.t.m):d==Ei&&wn(a,b.name+zl("[",c.w)+".dual = "+c.value.t.J)} +function zn(a,b,c,d){d==zi||d==Di?wn(a,b.name+zl("[",c.w)+".val = "+c.value.H.r):d==Ai?wn(a,b.name+zl("[",c.w)+".lb = "+(null==c.value.H.H.P?-s:c.value.H.P)):d==Bi?wn(a,b.name+zl("[",c.w)+".ub = "+(null==c.value.H.H.W?+s:c.value.H.W)):d==Ci?wn(a,b.name+zl("[",c.w)+".status = "+c.value.H.m):d==Ei&&wn(a,b.name+zl("[",c.w)+".dual = "+c.value.H.J)} +function An(a,b){for(var c,d=b.list;null!=d;d=d.e)if(d.type==kh)c=d.v.ya,wn(a,c.name+" = "+rl(c.value));else if(d.type==uh){var e=d.v.set;null!=e.assign?Cm(a,e.domain,e,Jm):(null!=e.Yc&&0==e.data&&Im(a,e),null!=e.O.head&&Km(a,e,e.O.head.w));null==e.O.head&&wn(a,e.name+" has empty content");for(c=e.O.head;null!=c;c=c.e)vn(a,e,c)}else if(d.type==sh)for(e=d.v.S,null!=e.assign?Cm(a,e.domain,e,Tm):null!=e.O.head&&(e.type!=T?Om(a,e,e.O.head.w):Sm(a,e,e.O.head.w)),null==e.O.head&&wn(a,e.name+" has empty content"), +c=e.O.head;null!=c;c=c.e)xn(a,e,c);else if(d.type==yh)for(e=d.v.t,null==e.O.head&&wn(a,e.name+" has empty content"),c=e.O.head;null!=c;c=c.e)yn(a,e,c,zi);else if(d.type==ch)for(e=d.v.H,null==e.O.head&&wn(a,e.name+" has empty content"),c=e.O.head;null!=c;c=c.e)zn(a,e,c,zi);else if(d.type==hh)if(e=d.v.code,e.Ta==Ii||e.Ta==Ji||e.Ta==Ki||e.Ta==Li||e.Ta==Mi){c=a;var f={value:{}},g=void 0;f.w=null;for(g=e.a.S.list||e.a.t.list;null!=g;g=g.e)f.w=tl(f.w,xm(c,g.x));switch(e.Ta){case Ii:f.value.Q=Om(c,e.a.S.S, +f.w);xn(c,e.a.S.S,f);break;case Ji:f.value.Y=Sm(c,e.a.S.S,f.w);xn(c,e.a.S.S,f);break;case Ki:f.value.set=Km(c,e.a.set.set,f.w);vn(c,e.a.set.set,f);break;case Li:f.value.t=Vm(c,e.a.t.t,f.w);yn(c,e.a.t.t,f,e.a.t.Ac);break;case Mi:f.value.H=Zm(c,e.a.H.H,f.w),zn(c,e.a.H.H,f,e.a.H.Ac)}}else switch(c=a,e.type){case N:e=$(c,e);wn(c,String(e));break;case T:e=xm(c,e);wn(c,rl(e));break;case nh:e=ym(c,e);wn(c,e?"true":"false");break;case xh:e=fn(c,e);wn(c,zl("(",e));break;case fh:e=Bm(c,e);0==e.head&&wn(c,"set is empty"); +for(e=e.head;null!=e;e=e.e)wn(c," "+zl("(",e.w));break;case jh:for(f=void 0,e=Xm(c,e),null==e&&wn(c,"linear form is empty"),f=e;null!=f;f=f.e)null==f.t?wn(c," "+f.u):wn(c," "+f.u+" "+f.t.t.name+zl("[",f.t.ga.w))}return 0}function Bn(a,b){null==a.Hg?"\n"==b?(a.he(a.dd,a.le),a.dd=""):a.dd+=b:a.Hg(b)}function Cn(a,b){for(var c=0;c=g||U(a,"cannot convert "+g+" to integer"),Cn(a,xa(d.slice(e,f+1),Math.floor(g+0.5)|0))):Cn(a,xa(d.slice(e,f+1),g))}else if("s"==d[f]){switch(c.code.type){case N:g=String($(a,c.code));break;case nh:g=ym(a,c.code)?"T":"F";break;case T:h=xm(a,c.code),g=null==h.M?String(h.Q):h.M}Cn(a,xa(d.slice(e,f+1),g))}else U(a,"format specifier missing or invalid");c=c.e}else"\\"==d[f]?(f++,"t"==d[f]?Bn(a,"\t"):"n"==d[f]?Bn(a, +"\n"):"\x00"==d[f]?U(a,"invalid use of escape character \\ in format control string"):Bn(a,d[f])):Bn(a,d[f]);return 0}function En(a,b){for(var c=a.ib,d=b.list;null!=d;d=d.e)Fn(a,d);a.ib=c;return 0} +function Fn(a,b){a.ib=b;switch(b.type){case ch:x("Generating "+b.v.H.name+"...");var c=b.v.H;Cm(a,c.domain,c,$m);break;case wh:switch(b.v.nd.type){case lh:x("Reading "+b.v.nd.name+"...");break;case rh:x("Writing "+b.v.nd.name+"...")}var c=b.v.nd,d,e,f,g;a.Mc=f={};f.id=0;f.link=null;f.ff=0;f.a=null;f.Wa=0;f.name=null;f.type=null;f.Q=null;f.M=null;for(d=c.a;null!=d;d=d.e)f.ff++;f.a=Array(1+f.ff);for(g=1;g<=f.ff;g++)f.a[g]=null;g=0;for(d=c.a;null!=d;d=d.e)g++,e=xm(a,d.code),e=null==e.M?String(e.Q):e.M, +f.a[g]=e;switch(c.type){case lh:g=c.v.qa.set;null!=g&&(g.data&&U(a,g.name+" already provided with data"),Al(g.O,null).value.set=Bl(a,qh,g.X),g.data=1);for(e=c.v.qa.list;null!=e;e=e.e)e.S.data&&U(a,e.S.name+" already provided with data"),e.S.data=1;for(e=c.v.qa.We;null!=e;e=e.e)f.Wa++;for(e=c.v.qa.list;null!=e;e=e.e)f.Wa++;f.name=Array(1+f.Wa);f.type=Array(1+f.Wa);f.Q=new Float64Array(1+f.Wa);f.M=Array(1+f.Wa);g=0;for(e=c.v.qa.We;null!=e;e=e.e)g++,f.name[g]=e.name,f.type[g]="?",f.Q[g]=0,f.M[g]=""; +for(e=c.v.qa.list;null!=e;e=e.e)g++,f.name[g]=e.name,f.type[g]="?",f.Q[g]=0,f.M[g]="";for(Gn(a,"R");;){for(g=1;g<=f.Wa;g++)f.type[g]="?";g=a;d=g.Mc;d=d.link.readRecord(d);0 "+(" "==a.Zb[0]?"":"...")+a.Zb.join("").trim());break;case 3:d=null==a.ib?0:a.ib.bb;var e=null==a.ib?0:a.ib.Vc;c=Error(d+": "+b);c.line=d;c.column=e}a.D=4;throw c;} +function ok(a,b){switch(a.D){case 1:case 2:x(a.bf+":"+a.bb+": warning: "+b);break;case 3:x(a.Yf+":"+(null==a.ib?0:a.ib.bb)+": warning: "+b)}} +var Ld=exports.mpl_initialize=function(){var a={bb:0,Vc:0,l:0,b:0,Bb:0,h:"",value:0,Hf:0,Gf:0,Ff:"",If:0,Te:0,Ue:0,Mf:0,Lf:0,Kf:"",Nf:0};a.Zb=Array(zh);ha(a.Zb,0," ",zh);a.lc=0;a.nc=0;a.V={};a.uc=null;a.Pf=0;a.rg=0;a.qg=0;a.Ke=0;a.Ob=0;a.pg=null;a.Ah="";a.Bh="";a.Gd=sg();a.Of=0;a.ib=null;a.Mc=null;a.g=0;a.i=0;a.n=null;a.f=null;a.cf=null;a.bf=null;a.he=null;a.jh=null;a.Hg=null;a.le=null;a.D=0;a.Yf=null;a.xh="";return a},Nd=exports.mpl_read_model=function(a,b,c,d){function e(){x(a.bb+" line"+(1==a.bb? +"":"s")+" were read");a.cf=null;return a.D}0!=a.D&&w("mpl_read_model: invalid call sequence");null==c&&w("mpl_read_model: no input specified");a.D=1;x("Reading model section from "+b+" ...");In(a,b,c);fl(a);null==a.uc&&U(a,"empty model section not allowed");a.Yf=a.bf;Hn(a);if(rk(a,"data")){if(d)return ok(a,"data section ignored"),e();a.nc=1;Y(a);a.b!=ni&&U(a,"semicolon missing where expected");Y(a);a.D=2;x("Reading data section from "+b+" ...");Kl(a)}cl(a);return e()},Pd=exports.mpl_read_data=function(a, +b,c){1!=a.D&&2!=a.D&&w("mpl_read_data: invalid call sequence");null==c&&w("mpl_read_data: no input specified");a.D=2;x("Reading data section from "+b+" ...");a.nc=1;In(a,b,c);dl(a,"data")&&(Y(a),a.b!=ni&&U(a,"semicolon missing where expected"),Y(a));Kl(a);cl(a);x(a.bb+" line"+(1==a.bb?"":"s")+" were read");a.cf=null;return a.D},Rd=exports.mpl_generate=function(a,b,c,d){1!=a.D&&2!=a.D&&w("mpl_generate: invalid call sequence");a.D=3;a.ve=d;Jn(a,b,c);for(b=a.uc;null!=b&&(Fn(a,b),a.ib.type!=vh);b=b.e); +a.ib=b;Kn(a);for(b=a.uc;null!=b;b=b.e)if(b.type==yh)for(c=b.v.t,c=c.O.head;null!=c;c=c.e);for(b=a.uc;null!=b;b=b.e)if(b.type==ch)for(c=b.v.H,c=c.O.head;null!=c;c=c.e)for(c.value.H.ea=++a.g,d=c.value.H.form;null!=d;d=d.e)d.t.ga.value.t.C=-1;for(b=a.uc;null!=b;b=b.e)if(b.type==yh)for(c=b.v.t,c=c.O.head;null!=c;c=c.e)0!=c.value.t.C&&(c.value.t.C=++a.i);a.n=Array(1+a.g);for(d=1;d<=a.g;d++)a.n[d]=null;for(b=a.uc;null!=b;b=b.e)if(b.type==ch)for(c=b.v.H,c=c.O.head;null!=c;c=c.e)d=c.value.H.ea,a.n[d]=c.value.H; +for(d=1;d<=a.g;d++);a.f=Array(1+a.i);for(d=1;d<=a.i;d++)a.f[d]=null;for(b=a.uc;null!=b;b=b.e)if(b.type==yh)for(c=b.v.t,c=c.O.head;null!=c;c=c.e)d=c.value.t.C,0!=d&&(a.f[d]=c.value.t);for(d=1;d<=a.i;d++);x("Model has been successfully generated");return a.D},Sd=exports.mpl_get_prob_name=function(a){return a.Yf},Td=exports.mpl_get_num_rows=function(a){3!=a.D&&w("mpl_get_num_rows: invalid call sequence");return a.g},be=exports.mpl_get_num_cols=function(a){3!=a.D&&w("mpl_get_num_cols: invalid call sequence"); +return a.i},Ud=exports.mpl_get_row_name=function(a,b){3!=a.D&&w("mpl_get_row_name: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_row_name: i = "+b+"; row number out of range");var c=a.n[b].H.name,c=c+zl("[",a.n[b].ga.w).slice(0,255);255==c.length&&(c=c.slice(0,252)+"...");return c},ie=exports.mpl_get_row_kind=function(a,b){var c;3!=a.D&&w("mpl_get_row_kind: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_row_kind: i = "+b+"; row number out of range");switch(a.n[b].H.type){case ch:c=411;break; +case ph:c=je;break;case oh:c=ke}return c},Vd=exports.mpl_get_row_bnds=function(a,b,c){var d;3!=a.D&&w("mpl_get_row_bnds: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_row_bnds: i = "+b+"; row number out of range");d=a.n[b];a=null==d.H.P?-s:d.P;b=null==d.H.W?+s:d.W;a==-s&&b==+s?(d=Wd,a=b=0):b==+s?(d=Xd,b=0):a==-s?(d=Yd,a=0):d=d.H.P!=d.H.W?Zd:$d;c(a,b);return d},he=exports.mpl_get_mat_row=function(a,b,c,d){var e=0;3!=a.D&&w("mpl_get_mat_row: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_mat_row: i = "+ +b+"; row number out of range");for(a=a.n[b].form;null!=a;a=a.e)e++,null!=c&&(c[e]=a.t.C),null!=d&&(d[e]=a.u);return e},ae=exports.mpl_get_row_c0=function(a,b){var c;3!=a.D&&w("mpl_get_row_c0: invalid call sequence");1<=b&&b<=a.g||w("mpl_get_row_c0: i = "+b+"; row number out of range");c=a.n[b];return null==c.H.P&&null==c.H.W?-c.P:0},ce=exports.mpl_get_col_name=function(a,b){3!=a.D&&w("mpl_get_col_name: invalid call sequence");1<=b&&b<=a.i||w("mpl_get_col_name: j = "+b+"; column number out of range"); +var c=a.f[b].t.name,c=c+zl("[",a.f[b].ga.w);255==c.length&&(c=c.slice(0,252)+"...");return c},de=exports.mpl_get_col_kind=function(a,b){var c;3!=a.D&&w("mpl_get_col_kind: invalid call sequence");1<=b&&b<=a.i||w("mpl_get_col_kind: j = "+b+"; column number out of range");switch(a.f[b].t.type){case N:c=421;break;case mh:c=ee;break;case ah:c=fe}return c},ge=exports.mpl_get_col_bnds=function(a,b,c){var d;3!=a.D&&w("mpl_get_col_bnds: invalid call sequence");1<=b&&b<=a.i||w("mpl_get_col_bnds: j = "+b+"; column number out of range"); +d=a.f[b];a=null==d.t.P?-s:d.P;b=null==d.t.W?+s:d.W;a==-s&&b==+s?(d=Wd,a=b=0):b==+s?(d=Xd,b=0):a==-s?(d=Yd,a=0):d=d.t.P!=d.t.W?Zd:$d;c(a,b);return d},me=exports.mpl_has_solve_stmt=function(a){3!=a.D&&w("mpl_has_solve_stmt: invalid call sequence");return a.Ob},ne=exports.mpl_put_row_soln=function(a,b,c,d,e){a.n[b].m=c;a.n[b].r=d;a.n[b].J=e},oe=exports.mpl_put_col_soln=function(a,b,c,d,e){a.f[b].m=c;a.f[b].r=d;a.f[b].J=e},pe=exports.mpl_postsolve=function(a){(3!=a.D||a.Of)&&w("mpl_postsolve: invalid call sequence"); +var b;a.Of=1;for(b=a.ib;null!=b;b=b.e)Fn(a,b);a.ib=null;Kn(a);x("Model has been successfully processed");return a.D},Ln="Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),Mn="January February March April May June July August September October November December".split(" ");function Nn(a){for(var b="";0=k;k++){y=Mn[k-1];var E=!1;for(g=0;2>=g;g++)if(n[g].toUpperCase()!=y[g].toUpperCase()){E= +!0;break}if(!E){n+=3;for(g=3;"\x00"!=y[g]&&b[n].toUpperCase()==y[g].toUpperCase();g++)n++;break}}12=b[n]||On(a,b,n,c,t,"day missing or invalid");l=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(l=10*l+(b[n++]-0));1<=l&&31>=l||On(a,b,n,c,t,"day out of range")}else if("H"==c[t]){for(0<=p&&On(a,b,n,c,t,"hour multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">= +b[n]||On(a,b,n,c,t,"hour missing or invalid");p=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(p=10*p+(b[n++]-0));0<=p&&23>=p||On(a,b,n,c,t,"hour out of range")}else if("m"==c[t]){for(0<=k&&On(a,b,n,c,t,"month multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]||On(a,b,n,c,t,"month missing or invalid");k=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(k=10*k+(b[n++]-0));1<=k&&12>=k||On(a,b,n,c,t,"month out of range")}else if("M"==c[t]){for(0<=m&&On(a,b,n,c,t,"minute multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]|| +On(a,b,n,c,t,"minute missing or invalid");m=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(m=10*m+(b[n++]-0));0<=m&&59>=m||On(a,b,n,c,t,"minute out of range")}else if("S"==c[t]){for(0<=q&&On(a,b,n,c,t,"second multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]||On(a,b,n,c,t,"second missing or invalid");q=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(q=10*q+(b[n++]-0));0<=q&&60>=q||On(a,b,n,c,t,"second out of range")}else if("y"==c[t]){for(0<=h&&On(a,b,n,c,t,"year multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]|| +On(a,b,n,c,t,"year missing or invalid");h=b[n++]-0;"0"<=b[n]&&"9">=b[n]&&(h=10*h+(b[n++]-0));h+=69<=h?1900:2E3}else if("Y"==c[t]){for(0<=h&&On(a,b,n,c,t,"year multiply specified");" "==b[n];)n++;"0"<=b[n]&&"9">=b[n]||On(a,b,n,c,t,"year missing or invalid");h=0;for(g=1;4>=g&&"0"<=b[n]&&"9">=b[n];g++)h=10*h+(b[n++]-0);1<=h&&4E3>=h||On(a,b,n,c,t,"year out of range")}else if("z"==c[t]){var C;for(2147483647!=r&&On(a,b,n,c,t,"time zone offset multiply specified");" "==b[n];)n++;if("Z"==b[n])C=p=m=0,n++; +else{"+"==b[n]?(C=1,n++):"-"==b[n]?(C=-1,n++):On(a,b,n,c,t,"time zone offset sign missing");p=0;for(g=1;2>=g;g++)"0"<=b[n]&&"9">=b[n]||d(),p=10*p+(b[n++]-0);23=b[n]||d());m=0;if("0"<=b[n]&&"9">=b[n]){for(g=1;2>=g;g++)"0"<=b[n]&&"9">=b[n]||d(),m=10*m+(b[n++]-0);59h&&(h=1970);0>k&&(k=1);0>l&&(l=1);0>p&&(p=0);0>m&&(m=0);0>q&&(q=0);2147483647==r&&(r=0);g=xg(l, +k,h);return 60*(60*(24*(g-xg(1,1,1970))+p)+m)+q-60*r}function Pn(a,b,c){x("Format string passed to time2str:");x(b);x(Nn(c));U(a,"invalid conversion specifier")}function Qn(a){return(a+xg(1,1,1970))%7+1}function Rn(a){a=xg(1,1,a)-xg(1,1,1970);switch(Qn(a)){case 1:a+=0;break;case 2:a-=1;break;case 3:a-=2;break;case 4:a-=3;break;case 5:a+=3;break;case 6:a+=2;break;case 7:a+=1}Qn(a);return a} +function dn(a,b,c){var d,e=0,f=0,g=0,h,k,l,p="",m;-62135596800<=b&&64092211199>=b||U(a,"time2str("+b+",...); argument out of range");b=Math.floor(b+0.5);h=Math.abs(b)/86400;d=Math.floor(h);0>b&&(d=h==Math.floor(h)?-d:-(d+1));yg(d+xg(1,1,1970),function(a,b,c){g=a;f=b;e=c});k=b-86400*d|0;h=k/60;k%=60;b=h/60;h%=60;for(l=0;l=b?b:b-12):"j"==c[l]?m=String(xg(g,f,e)-xg(1,1,e)+1):"k"==c[l]?m=String(b):"l"==c[l]?m=String(0==b?12:12>=b?b:b-12):"m"==c[l]?m=String(f):"M"==c[l]?m=String(h):"p"==c[l]?m=11>=b?"AM":"PM":"P"==c[l]?m=11>=b?"am":"pm":"r"==c[l]?m= +(0==b?12:12>=b?b:b-12)+":"+h+":"+k+" "+(11>=b?"AM":"PM"):"R"==c[l]?m=b+":"+h:"S"==c[l]?m=String(k):"T"==c[l]?m=b+":"+h+":"+k:"u"==c[l]?m=String(Qn(d)):"U"==c[l]?(m=xg(1,1,e)-xg(1,1,1970),m+=7-Qn(m),m=String((d+7-m)/7)):"V"==c[l]?(m=dkn(a)&&w("csv_driver: file name not specified\n");this.Ea=ln(a,2);if("R"==b){c?(this.data=c(a.a,b),this.cursor=0):w("csv_driver: unable to open "+this.Ea);this.Eg=0;for(Vn(this);;){Vn(this);if(this.Hb==this.Ae)break;this.Hb!=this.Be&&w(this.Ea+":"+this.count+": invalid field name\n");this.Wa++;for(b=mn(a);1<=b&&nn(a,b)!=this.pb;b--);this.Oa[this.Wa]= +b}for(b=mn(a);1<=b&&"RECNO"!=nn(a,b);b--);this.Oa[0]=b}else if("W"==b){this.data="";c=mn(a);for(b=1;b<=c;b++)this.data+=nn(a,b)+(be;e++)this.data='"'==d[e]?this.data+'""':this.data+d[e];this.data+='"'}this.data+=bkn(a)&&w("json driver: file name not specified");this.Ea=ln(a,2);if("R"==b)for(this.Oa={},c?(this.data=c(a.a,b),"string"==typeof this.data&&(this.data=JSON.parse(this.data)),this.cursor=1):w("json driver: unable to open "+this.Ea),a=0,b=this.data[0];ad||(ho(a,function(a,b){if(a.da==Zb)if(a.la[b.s]==A)a.la[b.s]=A;else if(a.la[b.s]==Na)a.la[b.s]=0<=a.Aa[b.s]?G:Ua;else return 1;return 0}).s=b.ea,c=0.5*(b.d+b.c),e=Math.floor(c+0.5),Math.abs(c-e)<=d&&(c=e),b.c=b.d=c)} +function po(a,b){var c,d,e,f;f=1E-9+1E-12*Math.abs(b.c);if(b.d-b.c>f)return 0;c=ho(a,function(a,b){var c,d;if(a.da==Zb)if(a.ka[b.F]==A)a.ka[b.F]=A;else if(a.ka[b.F]==Na){d=b.l;for(c=b.k;null!=c;c=c.e)d-=c.j*a.Aa[c.Oa];a.ka[b.F]=0<=d?G:Ua}else return 1;return 0});c.F=b.C;c.l=b.u;c.k=null;if(a.da==Zb)for(d=b.k;null!=d;d=d.I)e={},e.Oa=d.n.ea,e.j=d.j,e.e=c.k,c.k=e;c=0.5*(b.d+b.c);d=Math.floor(c+0.5);Math.abs(c-d)<=f&&(c=d);b.c=b.d=c;return 1} +function qo(a,b){if(0.001b.d)return 1;b.c=-s;b.d=+s;lo(a,b);return 0}function ro(a,b){function c(){e.m=G;b.d=b.c}function d(){e.m=Ua;b.c=b.d}var e;if(0.001b.u&&b.d==+s)return 1;e=ho(a,function(a,b){a.da==Zb&&(a.ka[b.F]=b.m);return 0});e.F=b.C;b.c==-s&&b.d==+s?(e.m=Ra,b.c=b.d=0):b.d==+s?c():b.c==-s?d():b.c!=b.d?2.220446049250313E-16<=b.u?c():-2.220446049250313E-16>=b.u?d():Math.abs(b.c)<=Math.abs(b.d)?c():d():e.m=Na;no(a,b);return 0} +function so(a,b){var c;if(a.Ra)if(c=Math.floor(b+0.5),1E-5>=Math.abs(b-c))b=c;else return 2;if(a.c!=-s){c=a.Ra?1E-5:1E-5+1E-8*Math.abs(a.c);if(ba.d+c)return 1;if(b>a.d-0.001*c)return a.c=a.d,0}a.c=a.d=b;return 0} +function to(a,b){var c,d,e;e=b.k;d=e.f;c=so(d,b.c/e.j);if(0!=c)return c;c=ho(a,function(a,b){var c,d;if(a.da==Zb){if(a.ka[b.F]!=Na)return 1;a.la[b.s]=Na;a.ka[b.F]=A}if(a.da!=Sc){d=b.l;for(c=b.k;null!=c;c=c.e)d-=c.j*a.Aa[c.Oa];a.Aa[b.s]=d/b.Da}return 0});c.s=b.ea;c.F=d.C;c.Da=e.j;c.l=d.u;c.k=null;if(a.da!=Sc)for(e=d.k;null!=e;e=e.I)e.n!=b&&(d={},d.Oa=e.n.ea,d.j=e.j,d.e=c.k,c.k=d);jo(a,b);return 0} +function uo(a,b){var c;a.Ra&&(c=Math.floor(b+0.5),b=1E-5>=Math.abs(b-c)?c:Math.ceil(b));if(a.c!=-s&&(c=a.Ra?0.001:0.001+1E-6*Math.abs(a.c),ba.d+c)return 4;if(b>a.d-0.001*c)return a.c=a.d,3}c=a.c==-s?2:a.Ra&&b>a.c+0.5?2:b>a.c+0.3*(1+Math.abs(a.c))?2:1;a.c=b;return c} +function vo(a,b){var c;a.Ra&&(c=Math.floor(b+0.5),b=1E-5>=Math.abs(b-c)?c:Math.floor(b));if(a.d!=+s&&(c=a.Ra?0.001:0.001+1E-6*Math.abs(a.d),b>a.d-c))return 0;if(a.c!=-s){c=a.Ra?1E-5:1E-5+1E-8*Math.abs(a.c);if(bb.Da&&b.d!=+s||!b.Vf))return a.ka[b.F]=G,c();if(-1E-7>d&&(0b.Da&&b.c!=-s||!b.gg))return a.ka[b.F]=Ua,e();if(b.c!=-s&&b.d==+s)a.la[b.s]=G;else if(b.c==-s&&b.d!=+s)a.la[b.s]=Ua;else if(b.c!=-s&&b.d!=+s)a.la[b.s]=b.Da*a.Pa[b.F]<=0.5* +(b.c+b.d)?G:Ua;else return 1;a.ka[b.F]=A;a.Aa[b.s]=d/b.Da}else return 1}a.da==le&&(a.Aa[b.s]=2.220446049250313E-16d&&b.gg?d/b.Da:0);return 0});c.s=b.ea;c.F=d.C;c.Da=e.j;c.l=d.u;c.c=b.c;c.d=b.d;c.Vf=g;c.gg=h;c.k=null;if(a.da!=Sc)for(d=d.k;null!=d;d=d.I)d!=e&&(f={},f.Oa=d.n.ea,f.j=d.j,f.e=c.k,c.k=f);jo(a,b);return g>=h?g:h} +function xo(a,b){var c,d,e,f;e=b.k;d=e.n;c=ho(a,function(a,b){var c,d;if(a.da==Zb){if(a.la[b.s]==A||a.la[b.s]==Ra)a.ka[b.F]=a.la[b.s];else if(a.la[b.s]==G)a.ka[b.F]=0b.d+l))return 1;b.c=-s;b.d=+s;e=ho(a,function(a,b){if(a.da==Zb)if(a.la[b.s]==A)a.la[b.s]=A;else if(a.la[b.s]==Na)a.la[b.s]=b.m;else return 1;return 0});e.s=f.ea;e.m=-1;g=b.u/g.j;if(2.220446049250313E-16g)if(f.d!=+s)d();else{if(-1E-5>g)return 2;c()}else f.d==+s?c():f.c==-s?d():Math.abs(f.c)<=Math.abs(f.d)?c():d();return 0} +function zo(a,b,c){var d,e=null,f,g,h;d=1;for(g=b.k;null!=g;g=g.B)de&&fg.j||0!=c&&0e)||a.d!=+s&&(b=0.001+1E-6*Math.abs(a.d),a.d+bd&&(c=a.c+b<=e?c|1:c|2));a.d!=+s&&(b=1E-9+1E-12*Math.abs(a.d),a.d+b=d?c| +16:c|32));return c}function Bo(a,b,c){a.da==Zb&&(a=ho(a,function(a,b){if(a.da!=Zb)return 1;a.la[b.s]=a.la[b.s]==A?A:b.m;return 0}),a.s=b.ea,a.m=b.d==+s?G:b.c==-s?Ua:b.c!=b.d?0==c?Ua:G:Na);0==c?b.c=-s:1==c&&(b.d=+s)} +function Co(a){var b,c,d,e,f,g,h,k,l,p,m;k=l=p=m=0;for(d=a.td;null!=d;d=d.ca)if(d.Ra&&d.c!=d.d&&(0!=d.c||1!=d.d))if(-1E6>d.c||1E6=c;)g++,c+=c;p+=g;b=ho(a,function(a,b){var c,d,e=a.Pa[b.F];c=1;for(d=2;cd.fa&&(b-=d.fa);for(d=a;null!=d;d=d.e)if(Math.abs(d.fa)>b)return 0;e=null;for(d=a;null!=d;d=d.e)if(null==e||Math.abs(e.fa)>Math.abs(d.fa))e=d;f=null;for(d=a;null!=d;d=d.e)d!=e&&(null==f||Math.abs(f.fa)>Math.abs(d.fa))&&(f=d);if(Math.abs(e.fa)+Math.abs(f.fa)<=b+(0.001+1E-6*Math.abs(b)))return 0;b=1;for(d=a;null!=d;d=d.e)0=f;f++){if(0==f){if(b.d==+s)continue;e=Do(b,1);h=+b.d}else{if(b.c==-s)continue;e=Do(b,-1);h=-b.c}c=Eo(e,h,function(a){h=a});if(1==f&&1==c||2==c){g++;if(b.c==-s||b.d==+s)c=null;else for(c=eo(a),0==f?(c.c=b.c,c.d=+s):(c.c=-s,c.d=b.d),d=b.k;null!=d;d=d.B)go(c,d.f,d.j);io(b);b.c=-s;for(b.d=h;null!=e;e=e.e)go(b,e.jc,e.fa);null!=c&&(b=c)}}return g} +function Go(a,b,c){var d,e;for(d=a;null!=d;d=d.e);e=0;for(d=a;null!=d;d=d.e)if(1!=d.fa)if(-1==d.fa)e++;else break;if(null==d&&b==1-e)return 1;for(d=a;null!=d;d=d.e)0>d.fa&&(b-=d.fa);if(0.001>b)return 0;e=1E-9+1E-12*Math.abs(b);for(d=a;null!=d;d=d.e)if(Math.abs(d.fa)=f;f++){if(0==f){if(b.c==-s)continue;e=Do(b,1);h=+b.c}else{if(b.d==+s)continue;e=Do(b,-1);h=-b.d}c=Go(e,h,function(a){h=a});if(1==f&&1==c||2==c){g++;if(b.c==-s||b.d==+s)c=null;else for(c=eo(a),0==f?(c.c=-s,c.d=b.d):(c.c=b.c,c.d=+s),d=b.k;null!=d;d=d.B)go(c,d.f,d.j);io(b);b.c=h;for(b.d=+s;null!=e;e=e.e)go(b,e.jc,e.fa);null!=c&&(b=c)}}return g} +function Io(a,b,c){var d,e=0,f,g;f=0;for(d=a;null!=d;d=d.e)if(0=0.01*(1+d.fa)&&(d.fa=g,e++))):(a=f-d.fa,b=g&&g-d.fa>=0.01*(1-d.fa)&&(d.fa=g,f+=a-b,b=a,e++))));c(b);return e} +function Jo(a,b){var c,d,e,f,g=Array(2),h;for(f=g[0]=g[1]=0;1>=f;f++){if(0==f){if(b.c==-s)continue;e=Do(b,1);h=+b.c}else{if(b.d==+s)continue;e=Do(b,-1);h=-b.d}g[f]=Io(e,h,function(a){h=a});if(0=k){co(a,e);if(2<=k)for(f=e.k;null!=f;f=f.I)$n(a,f.n);3==k&&no(a,e);return 0}if(4==k)return ac}k=Ao(b);if(51==k)return ac;if(0==(k&15))b.c!=-s&&Bo(a, +b,0);else if(1!=(k&15)&&2==(k&15)&&0==zo(a,b,0))return d();if(0==(k&240))b.d!=+s&&Bo(a,b,1);else if(16!=(k&240)&&32==(k&240)&&0==zo(a,b,1))return d();if(b.c==-s&&b.d==+s){for(f=b.k;null!=f;f=f.B)co(a,f.f);lo(a,b);return 0}return a.da==Sc&&c&&0>Lo(a,b,1)?ac:0} +function Lo(a,b,c){var d,e,f,g,h,k=0,l;h=!1;e=1;for(d=b.k;null!=d;d=d.B)d.f.qb.qb=-s,d.f.ub.ub=+s,ed.j&&d.f.c==-s)if(null==e)e=d;else{h=!0;break}if(!h){h=b.c;for(d=b.k;null!=d;d=d.B)d!=e&&(h=0=+g?d.f.qb.qb=d.f.d+h/d.j:d.j<=-g&&(d.f.ub.ub=d.f.c+h/d.j);else e.j>=+g?e.f.qb.qb=h/e.j:e.j<=-g&&(e.f.ub.ub=h/e.j)}}h=!1;if(b.d!=+s){e= +null;for(d=b.k;null!=d;d=d.B)if(0d.j&&d.f.d==+s)if(null==e)e=d;else{h=!0;break}if(!h){h=b.d;for(d=b.k;null!=d;d=d.B)d!=e&&(h=0=+g?d.f.ub.ub=d.f.c+h/d.j:d.j<=-g&&(d.f.qb.qb=d.f.d+h/d.j);else e.j>=+g?e.f.ub.ub=h/e.j:e.j<=-g&&(e.f.qb.qb=h/e.j)}}for(e=b.k;null!=e;)for(d=e.f,e=e.B,g=0;1>=g;g++){f=d.c;l=d.d;if(0==g){if(d.qb.qb==-s)continue;h=uo(d,d.qb.qb)}else{if(d.ub.ub==+s)continue;h=vo(d,d.ub.ub)}if(0==h||1==h)d.c= +f,d.d=l;else if(2==h||3==h){k++;if(c)for(f=d.k;null!=f;f=f.I)f.n!=b&&$n(a,f.n);if(3==h){no(a,d);break}}else if(4==h)return-1}return k}function Mo(a,b){var c,d,e;if(null==b.k){e=ro(a,b);if(0==e)return 0;if(1==e)return bc}if(null==b.k.I){c=b.k.n;var f=function(){xo(a,b);if(c.c==-s&&c.d==+s){for(d=c.k;null!=d;d=d.B)co(a,d.f);lo(a,c)}else $n(a,c);return 0};if(c.c==c.d){if(!b.Ra)return f()}else if(!b.Ra){e=yo(a,b);if(0==e)return f();if(1!=e&&2==e)return bc}}return 0} +function $b(a,b){var c,d,e;for(c=a.Db;null!=c;c=d)d=c.e,c.c==-s&&c.d==+s&&lo(a,c);for(c=a.Db;null!=c;c=d)d=c.e,c.c!=-s&&c.d!=+s&&c.cLo(a,c,0))return c=ac;return 0} +function Tc(a,b){var c,d,e,f,g;c=$b(a,1);if(0!=c)return c;b.sd&&Co(a);g=0;for(c=a.Pc;null!=c;c=d)if(d=c.ca,(c.c!=-s||c.d!=+s)&&c.c!=c.d&&null!=c.k&&null!=c.k.B){for(f=c.k;null!=f&&(e=f.f,e.Ra&&0==e.c&&1==e.d);f=f.B);null==f&&(g+=Fo(a,c))}0