mirror of
https://github.com/IllusionMan1212/3dobs.git
synced 2026-10-24 10:12:35 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
/target
|
||||
|
||||
3rdparty/*/target
|
||||
models/**/*
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "glad-gl"
|
||||
version = "2.0.4"
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "glad-gl"
|
||||
version = "2.0.4"
|
||||
authors = ["David Herberth <github@dav1d.de>"]
|
||||
license = "(WTFPL OR CC0-1.0) AND Apache-2.0"
|
||||
|
||||
[features]
|
||||
Vendored
+13014
File diff suppressed because it is too large
Load Diff
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
#[allow(clippy::all)]
|
||||
pub mod gl;
|
||||
Generated
+2290
File diff suppressed because it is too large
Load Diff
+26
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "threedobs"
|
||||
version = "0.1.0"
|
||||
description = "3dobs is a simple OBJ and STL viewer"
|
||||
authors = ["IllusionMan1212"]
|
||||
edition = "2021"
|
||||
license = "GPL-3.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
glfw = "0.51.0"
|
||||
glad-gl = { path = "./3rdparty/glad-gl" }
|
||||
image = "0.24.6"
|
||||
glm = "0.2.3"
|
||||
mint = "0.5.9"
|
||||
anyhow = "1.0.71"
|
||||
rfd = "0.11.3"
|
||||
|
||||
[dependencies.imgui]
|
||||
version = "0.11.0"
|
||||
features = ["docking", "tables-api"]
|
||||
|
||||
[dependencies.russimp]
|
||||
version = "2.0.5"
|
||||
features = ["prebuilt"]
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
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 tocopy 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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
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
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,14 @@
|
||||
3dobs TODOs:
|
||||
- [x] Help menu
|
||||
- [x] About window
|
||||
- [ ] File logger
|
||||
- [x] Move console to be under objects
|
||||
- [x] Don't have the viewport window be scrollable
|
||||
- [x] Object rotation with left click + mouse movement (very rudimentary implementation, but works)
|
||||
- [x] Camera movement with shift + left click + mouse movement
|
||||
- [x] Ability to change active model
|
||||
- [x] Hide inactive models
|
||||
- [x] Scroll zoom
|
||||
- [x] (BUG) Fix some meshes not rotating around their origin point (in actuality, they don't have an origin point and are only rotating around 0.0, 0.0, 0.0)
|
||||
- First thing that comes to mind is just rotating them around the center_point of the mesh (which is the bounding box's center point)
|
||||
- [x] Default material
|
||||
@@ -0,0 +1,186 @@
|
||||
#version 330 core
|
||||
in vec3 fragNormals;
|
||||
in vec3 fragPos;
|
||||
in vec2 texCoords;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
struct Material {
|
||||
sampler2D texture_diffuse1;
|
||||
sampler2D texture_diffuse2;
|
||||
sampler2D texture_diffuse3;
|
||||
sampler2D texture_diffuse4;
|
||||
sampler2D texture_diffuse5;
|
||||
sampler2D texture_diffuse6;
|
||||
sampler2D texture_diffuse7;
|
||||
sampler2D texture_diffuse8;
|
||||
sampler2D texture_diffuse9;
|
||||
sampler2D texture_diffuse10;
|
||||
sampler2D texture_diffuse11;
|
||||
sampler2D texture_diffuse12;
|
||||
sampler2D texture_diffuse13;
|
||||
sampler2D texture_diffuse14;
|
||||
sampler2D texture_diffuse15;
|
||||
sampler2D texture_diffuse16;
|
||||
sampler2D texture_specular1;
|
||||
sampler2D texture_specular2;
|
||||
sampler2D texture_specular3;
|
||||
sampler2D texture_specular4;
|
||||
sampler2D texture_specular5;
|
||||
sampler2D texture_specular6;
|
||||
sampler2D texture_specular7;
|
||||
sampler2D texture_specular8;
|
||||
sampler2D texture_specular9;
|
||||
sampler2D texture_specular10;
|
||||
sampler2D texture_specular11;
|
||||
sampler2D texture_specular12;
|
||||
sampler2D texture_specular13;
|
||||
sampler2D texture_specular14;
|
||||
sampler2D texture_specular15;
|
||||
sampler2D texture_specular16;
|
||||
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
vec3 specular;
|
||||
float shininess;
|
||||
};
|
||||
|
||||
struct DirLight {
|
||||
vec3 direction;
|
||||
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
vec3 specular;
|
||||
};
|
||||
|
||||
struct PointLight {
|
||||
vec3 position;
|
||||
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
vec3 specular;
|
||||
|
||||
float constant;
|
||||
float linear;
|
||||
float quadratic;
|
||||
};
|
||||
|
||||
struct SpotLight {
|
||||
vec3 position;
|
||||
vec3 direction;
|
||||
float cutOff;
|
||||
float outerCutOff;
|
||||
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
vec3 specular;
|
||||
|
||||
float constant;
|
||||
float linear;
|
||||
float quadratic;
|
||||
};
|
||||
|
||||
#define NR_POINT_LIGHTS 4
|
||||
|
||||
uniform vec3 viewPos;
|
||||
uniform Material material;
|
||||
uniform DirLight dirLight;
|
||||
uniform PointLight pointLights[NR_POINT_LIGHTS];
|
||||
uniform SpotLight spotLight;
|
||||
|
||||
vec3 CalculateDirLight(DirLight light, vec3 normal, vec3 viewDir) {
|
||||
vec3 lightDir = normalize(-light.direction);
|
||||
|
||||
float diff = max(dot(lightDir, normal), 0.0);
|
||||
|
||||
vec3 reflectDir = reflect(-lightDir, normal);
|
||||
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
|
||||
|
||||
//vec3 ambient = light.ambient * vec3(texture(material.texture_diffuse1, texCoords)) * material.ambient;
|
||||
//vec3 diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse1, texCoords)) * material.diffuse;
|
||||
//vec3 specular = light.specular * spec * vec3(texture(material.texture_specular1, texCoords)) * material.specular;
|
||||
vec3 ambient = light.ambient * material.ambient;
|
||||
vec3 diffuse = light.diffuse * diff * material.diffuse;
|
||||
vec3 specular = light.specular * spec * material.specular;
|
||||
|
||||
return (ambient + diffuse + specular);
|
||||
}
|
||||
|
||||
vec3 CalculatePointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) {
|
||||
vec3 lightDir = normalize(light.position - fragPos);
|
||||
|
||||
float diff = max(dot(lightDir, normal), 0.0);
|
||||
|
||||
vec3 reflectDir = reflect(-lightDir, normal);
|
||||
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
|
||||
|
||||
// attenuation
|
||||
float distance = length(light.position - fragPos);
|
||||
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance + distance));
|
||||
|
||||
//vec3 ambient = light.ambient * vec3(texture(material.texture_diffuse1, texCoords)) * material.ambient;
|
||||
//vec3 diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse1, texCoords)) * material.diffuse;
|
||||
//vec3 specular = light.specular * spec * vec3(texture(material.texture_specular1, texCoords)) * material.specular;
|
||||
vec3 ambient = light.ambient * material.ambient;
|
||||
vec3 diffuse = light.diffuse * diff * material.diffuse;
|
||||
vec3 specular = light.specular * spec * material.specular;
|
||||
|
||||
ambient *= attenuation;
|
||||
diffuse *= attenuation;
|
||||
specular *= attenuation;
|
||||
|
||||
return (ambient + diffuse + specular);
|
||||
}
|
||||
|
||||
vec3 CalculateSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) {
|
||||
vec3 lightDir = normalize(light.position - fragPos);
|
||||
|
||||
float diff = max(dot(lightDir, normal), 0.0);
|
||||
|
||||
vec3 reflectDir = reflect(-lightDir, normal);
|
||||
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
|
||||
|
||||
// attenuation
|
||||
float distance = length(light.position - fragPos);
|
||||
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance + distance));
|
||||
|
||||
// spotlight
|
||||
float theta = dot(lightDir, normalize(-light.direction));
|
||||
float epsilon = light.cutOff - light.outerCutOff;
|
||||
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
|
||||
|
||||
//vec3 ambient = light.ambient * vec3(texture(material.texture_diffuse1, texCoords)) * material.ambient;
|
||||
//vec3 diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse1, texCoords)) * material.diffuse;
|
||||
//vec3 specular = light.specular * spec * vec3(texture(material.texture_specular1, texCoords)) * material.specular;
|
||||
vec3 ambient = light.ambient * material.ambient;
|
||||
vec3 diffuse = light.diffuse * diff * material.diffuse;
|
||||
vec3 specular = light.specular * spec * material.specular;
|
||||
|
||||
diffuse *= intensity;
|
||||
specular *= intensity;
|
||||
|
||||
ambient *= attenuation;
|
||||
diffuse *= attenuation;
|
||||
specular *= attenuation;
|
||||
|
||||
return (ambient + diffuse + specular);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 norm = normalize(fragNormals);
|
||||
vec3 viewDir = normalize(viewPos - fragPos);
|
||||
|
||||
// direction light
|
||||
vec3 result = CalculateDirLight(dirLight, norm, viewDir);
|
||||
|
||||
// point lights
|
||||
for (int i = 0; i < NR_POINT_LIGHTS; i++) {
|
||||
result += CalculatePointLight(pointLights[i], norm, fragPos, viewDir);
|
||||
}
|
||||
|
||||
result += CalculateSpotLight(spotLight, norm, fragPos, viewDir);
|
||||
|
||||
FragColor = vec4(result, 1.0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#version 330 core
|
||||
in vec3 nearPoint;
|
||||
in vec3 farPoint;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat4 view;
|
||||
uniform float near;
|
||||
uniform float far;
|
||||
|
||||
vec4 grid(vec3 fragPos3D, float scale, float lineWidth, float lineOpacity) {
|
||||
vec2 coord = fragPos3D.xz * scale; // use the scale variable to set the distance between the lines
|
||||
vec2 derivative = fwidth(coord);
|
||||
vec2 grid = abs(fract(coord - 0.5) - 0.5) / derivative;
|
||||
float line = min(grid.x, grid.y);
|
||||
float minimumz = min(derivative.y, 1);
|
||||
float minimumx = min(derivative.x, 1);
|
||||
vec4 color = vec4(0.2, 0.2, 0.2, lineOpacity * (1.0 - min(line, 1.0)));
|
||||
// z axis
|
||||
if(abs(fragPos3D.x) < lineWidth * minimumx)
|
||||
color.z = 1.0;
|
||||
// x axis
|
||||
if(abs(fragPos3D.z) < lineWidth * minimumz)
|
||||
color.x = 1.0;
|
||||
return color;
|
||||
}
|
||||
|
||||
float computeDepth(vec3 pos) {
|
||||
vec4 clip_space_pos = projection * view * vec4(pos.xyz, 1.0);
|
||||
return (clip_space_pos.z / clip_space_pos.w);
|
||||
}
|
||||
|
||||
float computeLinearDepth(vec3 pos) {
|
||||
vec4 clip_space_pos = projection * view * vec4(pos.xyz, 1.0);
|
||||
float clip_space_depth = (clip_space_pos.z / clip_space_pos.w) * 2.0 - 1.0; // put back between -1 and 1
|
||||
float linearDepth = (2.0 * near * far) / (far + near - clip_space_depth * (far - near)); // get linear value between 0.01 and 100
|
||||
return linearDepth / far; // normalize
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
float t = -nearPoint.y / (farPoint.y - nearPoint.y);
|
||||
vec3 fragPos3D = nearPoint + t * (farPoint - nearPoint);
|
||||
//gl_FragDepth = computeDepth(fragPos3D);
|
||||
// above depth calculation is buggy
|
||||
// see https://stackoverflow.com/questions/72791713/issues-with-infinite-grid-in-opengl-4-5-with-glsl
|
||||
// for solution
|
||||
gl_FragDepth = ((gl_DepthRange.diff * computeDepth(fragPos3D)) +
|
||||
gl_DepthRange.near + gl_DepthRange.far) / 2.0;
|
||||
|
||||
float linearDepth = computeLinearDepth(fragPos3D);
|
||||
float fading = max(0, (0.5 - linearDepth));
|
||||
|
||||
float lineWidth = 0.1; // width of the grid lines in world space units
|
||||
float lineOpacity = 0.7; // opacity of the grid lines
|
||||
|
||||
// Compute the grid colors at different resolutions and blend them together
|
||||
FragColor = (grid(fragPos3D, 5, lineWidth, lineOpacity) + grid(fragPos3D, 0.5, lineWidth, lineOpacity)) * float(t > 0);
|
||||
|
||||
// Fade the grid out as it gets farther away
|
||||
FragColor.a *= fading;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#version 330 core
|
||||
out vec3 nearPoint;
|
||||
out vec3 farPoint;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat4 view;
|
||||
|
||||
// Grid position are in xy clipped space
|
||||
vec3 gridPlane[6] = vec3[](
|
||||
vec3(1, 1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0),
|
||||
vec3(-1, -1, 0), vec3(1, 1, 0), vec3(1, -1, 0)
|
||||
);
|
||||
|
||||
vec3 UnprojectPoint(float x, float y, float z, mat4 v, mat4 proj) {
|
||||
mat4 viewInv = inverse(v);
|
||||
mat4 projInv = inverse(proj);
|
||||
vec4 unprojectedPoint = viewInv * projInv * vec4(x, y, z, 1.0);
|
||||
return unprojectedPoint.xyz / unprojectedPoint.w;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 p = gridPlane[gl_VertexID].xyz;
|
||||
nearPoint = UnprojectPoint(p.x, p.y, 0.0, view, projection).xyz; // unprojecting on the near plane
|
||||
farPoint = UnprojectPoint(p.x, p.y, 1.0, view, projection).xyz; // unprojecting on the far plane
|
||||
gl_Position = vec4(p, 1.0); // using directly the clipped coordinates
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#version 330 core
|
||||
out vec4 FragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = vec4(1.0);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec3 aNormal;
|
||||
layout (location = 2) in vec2 aTexCoords;
|
||||
out vec3 fragNormals;
|
||||
out vec3 fragPos;
|
||||
out vec2 texCoords;
|
||||
|
||||
uniform mat4 model;
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = projection * view * model * vec4(aPos, 1.0f);
|
||||
fragNormals = mat3(transpose(inverse(model))) * aNormal;
|
||||
fragPos = vec3(model * vec4(aPos, 1.0));
|
||||
texCoords = aTexCoords;
|
||||
}
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
use glad_gl::gl;
|
||||
|
||||
use crate::{mesh::Vertex, shader::Shader};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AABB {
|
||||
pub min: glm::Vec3,
|
||||
pub max: glm::Vec3,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AABBMesh {
|
||||
pub vertices: Vec<Vertex>,
|
||||
pub indices: Vec<u32>,
|
||||
pub vao: u32,
|
||||
}
|
||||
|
||||
impl AABBMesh {
|
||||
pub fn new(aabb: &AABB) -> AABBMesh {
|
||||
let mut vao = 0;
|
||||
let mut vbo = 0;
|
||||
let mut ebo = 0;
|
||||
|
||||
let vertices = vec![
|
||||
Vertex { position: glm::vec3(aabb.min.x, aabb.min.y, aabb.min.z), tex_coords: glm::vec2(0.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) },
|
||||
Vertex { position: glm::vec3(aabb.max.x, aabb.min.y, aabb.min.z), tex_coords: glm::vec2(1.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) },
|
||||
Vertex { position: glm::vec3(aabb.max.x, aabb.max.y, aabb.min.z), tex_coords: glm::vec2(1.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) },
|
||||
Vertex { position: glm::vec3(aabb.min.x, aabb.max.y, aabb.min.z), tex_coords: glm::vec2(0.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) },
|
||||
Vertex { position: glm::vec3(aabb.min.x, aabb.min.y, aabb.max.z), tex_coords: glm::vec2(0.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) },
|
||||
Vertex { position: glm::vec3(aabb.max.x, aabb.min.y, aabb.max.z), tex_coords: glm::vec2(1.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) },
|
||||
Vertex { position: glm::vec3(aabb.max.x, aabb.max.y, aabb.max.z), tex_coords: glm::vec2(1.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) },
|
||||
Vertex { position: glm::vec3(aabb.min.x, aabb.max.y, aabb.max.z), tex_coords: glm::vec2(0.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) },
|
||||
];
|
||||
|
||||
let indices = vec![
|
||||
0, 1, 2, 2, 3, 0, // front
|
||||
1, 5, 6, 6, 2, 1, // right
|
||||
5, 4, 7, 7, 6, 5, // back
|
||||
4, 0, 3, 3, 7, 4, // left
|
||||
3, 2, 6, 6, 7, 3, // top
|
||||
4, 5, 1, 1, 0, 4, // bottom
|
||||
];
|
||||
|
||||
unsafe {
|
||||
gl::GenVertexArrays(1, &mut vao);
|
||||
gl::GenBuffers(1, &mut vbo);
|
||||
gl::GenBuffers(1, &mut ebo);
|
||||
|
||||
gl::BindVertexArray(vao);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
||||
|
||||
gl::BufferData(gl::ARRAY_BUFFER, (std::mem::size_of::<Vertex>() * vertices.len() as usize) as isize, vertices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
|
||||
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
|
||||
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, (std::mem::size_of::<u32>() * indices.len() as usize) as isize, indices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
|
||||
|
||||
// vertex positions
|
||||
gl::EnableVertexAttribArray(0);
|
||||
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, std::ptr::null());
|
||||
|
||||
// vertex normals
|
||||
gl::EnableVertexAttribArray(1);
|
||||
gl::VertexAttribPointer(1, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, (3 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
|
||||
|
||||
// vertex texture coords
|
||||
gl::EnableVertexAttribArray(2);
|
||||
gl::VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, (6 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
|
||||
|
||||
gl::BindVertexArray(0);
|
||||
}
|
||||
|
||||
AABBMesh {
|
||||
vertices,
|
||||
indices,
|
||||
vao,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, shader: &Shader, model_mat: &glm::Mat4) {
|
||||
shader.use_shader();
|
||||
|
||||
shader.set_mat4fv("model", &model_mat);
|
||||
|
||||
unsafe {
|
||||
// draw Mesh
|
||||
gl::BindVertexArray(self.vao);
|
||||
gl::PolygonMode(gl::FRONT_AND_BACK, gl::LINE);
|
||||
gl::DrawElements(gl::TRIANGLES, self.indices.len() as i32, gl::UNSIGNED_INT, std::ptr::null());
|
||||
|
||||
// reset stuff to default
|
||||
gl::BindVertexArray(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use glm;
|
||||
|
||||
use crate::model;
|
||||
|
||||
pub struct Camera {
|
||||
pub position: glm::Vec3,
|
||||
pub front: glm::Vec3,
|
||||
pub up: glm::Vec3,
|
||||
pub pitch: f32,
|
||||
pub yaw: f32,
|
||||
pub speed: f32,
|
||||
_speed: f32,
|
||||
pub sensitivity: f32,
|
||||
pub fov: f32,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
pub fn new() -> Camera {
|
||||
Camera {
|
||||
position: glm::vec3(0.0, 3.0, 3.0),
|
||||
front: glm::vec3(0.0, 0.0, -1.0),
|
||||
up: glm::vec3(0.0, 1.0, 0.0),
|
||||
pitch: 0.0,
|
||||
yaw: -90.0,
|
||||
_speed: 0.0,
|
||||
speed: 10.0,
|
||||
sensitivity: 0.05,
|
||||
fov: 45.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_mouse_scroll(&mut self, yoffset: f32, can_capture_cursor: bool) {
|
||||
if !can_capture_cursor { return }
|
||||
self.fov -= yoffset;
|
||||
|
||||
if self.fov <= 0.5 {
|
||||
self.fov = 0.5;
|
||||
}
|
||||
if self.fov >= 45.0 {
|
||||
self.fov = 45.0;
|
||||
}
|
||||
|
||||
// if yoffset > 0.0 {
|
||||
// self.position = self.position + (self.front * self._speed) + glm::vec3(0.0, 0.0, yoffset);
|
||||
// } else {
|
||||
// self.position = self.position - (self.front * self._speed) + glm::vec3(0.0, 0.0, yoffset);
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn move_camera(&mut self, xoffset: f32, yoffset: f32) {
|
||||
let new_x = xoffset * self.sensitivity * self._speed;
|
||||
let new_y = yoffset * self.sensitivity * self._speed;
|
||||
|
||||
self.position = self.position + glm::vec3(new_x, new_y, 0.0);
|
||||
}
|
||||
|
||||
pub fn update_speed(&mut self, delta_time: f32) {
|
||||
self._speed = self.speed * delta_time;
|
||||
}
|
||||
|
||||
pub fn update_position(&mut self, active_model: Option<u32>, objects: &Vec<model::Model>) {
|
||||
if let Some(id) = active_model {
|
||||
for obj in objects {
|
||||
if obj.id == id {
|
||||
// we scale the center of the object since the model (and therefore the AABB) is scaled
|
||||
let center_x = ((obj.aabb.max.x / 2.0) + (obj.aabb.min.x / 2.0)) * obj.scaling_factor;
|
||||
let center_y = ((obj.aabb.max.y / 2.0) + (obj.aabb.min.y / 2.0)) * obj.scaling_factor;
|
||||
let z = obj.aabb.max.z * obj.scaling_factor + 10.0;
|
||||
self.position = glm::vec3(center_x, center_y, z);
|
||||
self.front = glm::vec3(0.0, 0.0, -1.0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
//! Provides a glfw-based backend platform for imgui-rs. This crate is modeled
|
||||
//! after the winit version.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! 1. Initialize a `GlfwPlatform`
|
||||
//! 2. Attach it to a glfw `Window`
|
||||
//! 3. Optionally, enable platform clipboard integration
|
||||
//! 4. Pass events to the platform (every frame)
|
||||
//! 5. Call frame preparation (every frame)
|
||||
//! 6. Call render preperation (every frame)
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! The [examples](https://github.com/aloucks/imgui-glfw-support/tree/master/examples) can be found on github.
|
||||
|
||||
use glfw::{
|
||||
Action, Cursor, CursorMode, Key as GlfwKey, Modifiers, MouseButton, StandardCursor, Window,
|
||||
WindowEvent,
|
||||
};
|
||||
use imgui::{BackendFlags, ConfigFlags, Context, Io, Key, Ui};
|
||||
|
||||
pub struct GlfwPlatform {
|
||||
hidpi_mode: ActiveHiDpiMode,
|
||||
hidpi_factor: f64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
enum ActiveHiDpiMode {
|
||||
Default,
|
||||
Rounded,
|
||||
Locked,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum HiDpiMode {
|
||||
/// The DPI factor from glfw is used directly without adjustment
|
||||
Default,
|
||||
/// The DPI factor from glfw is rounded to an integer value.
|
||||
///
|
||||
/// This prevents the user interface from becoming blurry with non-integer scaling.
|
||||
Rounded,
|
||||
/// The DPI factor from glfw is ignored, and the included value is used instead.
|
||||
///
|
||||
/// This is useful if you want to force some DPI factor (e.g. 1.0) and not care about the value
|
||||
/// coming from glfw.
|
||||
Locked(f64),
|
||||
}
|
||||
|
||||
struct Clipboard {
|
||||
window_ptr: *mut glfw::ffi::GLFWwindow,
|
||||
}
|
||||
|
||||
impl imgui::ClipboardBackend for Clipboard {
|
||||
fn set(&mut self, s: &str) {
|
||||
unsafe {
|
||||
glfw::ffi::glfwSetClipboardString(self.window_ptr, s.as_ptr() as *const std::ffi::c_char);
|
||||
}
|
||||
}
|
||||
fn get(&mut self) -> std::option::Option<String> {
|
||||
unsafe {
|
||||
let s = glfw::ffi::glfwGetClipboardString(self.window_ptr);
|
||||
let s = std::ffi::CStr::from_ptr(s);
|
||||
let bytes = s.to_bytes();
|
||||
if !bytes.is_empty() {
|
||||
let v = String::from_utf8_lossy(bytes);
|
||||
Some(imgui::ImString::new(v).to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HiDpiMode {
|
||||
fn apply(&self, hidpi_factor: f64) -> (ActiveHiDpiMode, f64) {
|
||||
match *self {
|
||||
HiDpiMode::Default => (ActiveHiDpiMode::Default, hidpi_factor),
|
||||
HiDpiMode::Rounded => (ActiveHiDpiMode::Rounded, hidpi_factor.round()),
|
||||
HiDpiMode::Locked(value) => (ActiveHiDpiMode::Locked, value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlfwPlatform {
|
||||
/// Initializes a glfw platform instance and configures imgui.
|
||||
///
|
||||
/// * backend flgs are updated
|
||||
/// * keys are configured
|
||||
/// * platform name is set
|
||||
pub fn init(imgui: &mut Context) -> GlfwPlatform {
|
||||
let io = imgui.io_mut();
|
||||
io.backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS);
|
||||
io.backend_flags.insert(BackendFlags::HAS_SET_MOUSE_POS);
|
||||
io[Key::Tab] = GlfwKey::Tab as _;
|
||||
io[Key::LeftArrow] = GlfwKey::Left as _;
|
||||
io[Key::RightArrow] = GlfwKey::Right as _;
|
||||
io[Key::UpArrow] = GlfwKey::Up as _;
|
||||
io[Key::DownArrow] = GlfwKey::Down as _;
|
||||
io[Key::PageUp] = GlfwKey::PageUp as _;
|
||||
io[Key::PageDown] = GlfwKey::PageDown as _;
|
||||
io[Key::Home] = GlfwKey::Home as _;
|
||||
io[Key::End] = GlfwKey::End as _;
|
||||
io[Key::Insert] = GlfwKey::Insert as _;
|
||||
io[Key::Delete] = GlfwKey::Delete as _;
|
||||
io[Key::Backspace] = GlfwKey::Backspace as _;
|
||||
io[Key::Space] = GlfwKey::Space as _;
|
||||
io[Key::Enter] = GlfwKey::Enter as _;
|
||||
io[Key::Escape] = GlfwKey::Escape as _;
|
||||
io[Key::KeypadEnter] = GlfwKey::KpEnter as _;
|
||||
io[Key::A] = GlfwKey::A as _;
|
||||
io[Key::C] = GlfwKey::C as _;
|
||||
io[Key::V] = GlfwKey::V as _;
|
||||
io[Key::X] = GlfwKey::X as _;
|
||||
io[Key::Y] = GlfwKey::Y as _;
|
||||
io[Key::Z] = GlfwKey::Z as _;
|
||||
imgui.set_platform_name(Some(format!(
|
||||
"imgui-glfw-support {}",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
)));
|
||||
GlfwPlatform {
|
||||
hidpi_mode: ActiveHiDpiMode::Default,
|
||||
hidpi_factor: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds platform clipboard integration for the provided window. The caller **must** ensure that
|
||||
/// the `Window` outlives the imgui `Context` **and** that any imgui functions that may access
|
||||
/// the clipboard are called from the **main thread** (the thread that's executing the event polling).
|
||||
pub fn set_clipboard_backend(&self, imgui: &mut Context, window: &Window) {
|
||||
use glfw::Context;
|
||||
let window_ptr = window.window_ptr();
|
||||
imgui.set_clipboard_backend(Clipboard { window_ptr });
|
||||
}
|
||||
|
||||
/// Attaches the platform instance to a glfw window.
|
||||
///
|
||||
/// * framebuffer sacle (i.e. DPI factor) is set
|
||||
/// * display size is set
|
||||
pub fn attach_window(&mut self, io: &mut Io, window: &Window, hidpi_mode: HiDpiMode) {
|
||||
let (scale_factor_x, _scale_factor_y) = window.get_content_scale();
|
||||
let (hidpi_mode, hidpi_factor) = hidpi_mode.apply(scale_factor_x as _);
|
||||
self.hidpi_mode = hidpi_mode;
|
||||
self.hidpi_factor = hidpi_factor;
|
||||
io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32];
|
||||
let (width, height) = window.get_size();
|
||||
io.display_size = [width as f32, height as f32];
|
||||
}
|
||||
|
||||
pub fn hidpi_factor(&self) -> f64 {
|
||||
self.hidpi_factor
|
||||
}
|
||||
|
||||
/// Handles a glfw window event
|
||||
///
|
||||
/// * keyboard state is updated
|
||||
/// * mouse state is updated
|
||||
pub fn handle_event(&self, io: &mut Io, _window: &Window, event: &WindowEvent) {
|
||||
match *event {
|
||||
WindowEvent::Key(key, _scancode, action, modifiers) => {
|
||||
if key as i32 >= 0 {
|
||||
if action == Action::Release {
|
||||
io.keys_down[key as usize] = false;
|
||||
} else {
|
||||
io.keys_down[key as usize] = true;
|
||||
}
|
||||
}
|
||||
io.key_shift = modifiers.contains(Modifiers::Shift);
|
||||
io.key_ctrl = modifiers.contains(Modifiers::Control);
|
||||
io.key_alt = modifiers.contains(Modifiers::Alt);
|
||||
io.key_super = modifiers.contains(Modifiers::Super);
|
||||
}
|
||||
WindowEvent::Size(width, height) => {
|
||||
io.display_size = [width as _, height as _];
|
||||
}
|
||||
WindowEvent::Char(ch) => {
|
||||
// Exclude the backspace key
|
||||
if ch != '\u{7f}' {
|
||||
io.add_input_character(ch);
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorPos(x, y) => {
|
||||
io.mouse_pos = [x as _, y as _];
|
||||
}
|
||||
WindowEvent::Scroll(x, y) => {
|
||||
io.mouse_wheel_h = x as _;
|
||||
io.mouse_wheel = y as _;
|
||||
}
|
||||
WindowEvent::MouseButton(button, action, _modifiers) => {
|
||||
let pressed = action == Action::Press;
|
||||
match button {
|
||||
MouseButton::Button1 => io.mouse_down[0] = pressed,
|
||||
MouseButton::Button2 => io.mouse_down[1] = pressed,
|
||||
MouseButton::Button3 => io.mouse_down[2] = pressed,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the window for the next frame.
|
||||
///
|
||||
/// Call before calling the imgui-rs `Context::frame` function.
|
||||
///
|
||||
/// * mouse cursor is repositioned if requested by imgui
|
||||
pub fn prepare_frame(&self, io: &mut Io, window: &mut Window) -> Result<(), String> {
|
||||
if io.want_set_mouse_pos {
|
||||
let [x, y] = io.mouse_pos;
|
||||
window.set_cursor_pos(x as _, y as _);
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the window for rendering.
|
||||
///
|
||||
/// Call before calling the imgui backend renderer function (e.g. `imgui_wgpu::Renderer::render`).
|
||||
///
|
||||
/// * the mouse cursor is changed or hidden if requested by imgui
|
||||
pub fn prepare_render(&self, ui: &Ui, window: &mut Window) {
|
||||
let io = ui.io();
|
||||
if !io
|
||||
.config_flags
|
||||
.contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE)
|
||||
{
|
||||
match ui.mouse_cursor() {
|
||||
Some(mouse_cursor) if !io.mouse_draw_cursor => {
|
||||
window.set_cursor_mode(CursorMode::Normal);
|
||||
window.set_cursor(Some(match mouse_cursor {
|
||||
// TODO: GLFW has more cursor options on master, but they aren't released yet
|
||||
imgui::MouseCursor::Arrow => Cursor::standard(StandardCursor::Arrow),
|
||||
imgui::MouseCursor::ResizeAll => Cursor::standard(StandardCursor::Arrow),
|
||||
imgui::MouseCursor::ResizeNS => Cursor::standard(StandardCursor::VResize),
|
||||
imgui::MouseCursor::ResizeEW => Cursor::standard(StandardCursor::HResize),
|
||||
imgui::MouseCursor::ResizeNESW => Cursor::standard(StandardCursor::Arrow),
|
||||
imgui::MouseCursor::ResizeNWSE => Cursor::standard(StandardCursor::Arrow),
|
||||
imgui::MouseCursor::Hand => Cursor::standard(StandardCursor::Hand),
|
||||
imgui::MouseCursor::NotAllowed => {
|
||||
Cursor::standard(StandardCursor::Crosshair)
|
||||
}
|
||||
imgui::MouseCursor::TextInput => Cursor::standard(StandardCursor::IBeam),
|
||||
}));
|
||||
}
|
||||
_ => window.set_cursor_mode(CursorMode::Hidden),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
extern crate imgui;
|
||||
|
||||
use imgui::Context;
|
||||
use std::mem;
|
||||
|
||||
use glad_gl::gl;
|
||||
|
||||
pub struct Renderer {
|
||||
program: gl::GLuint,
|
||||
locs: Locs,
|
||||
vbo: gl::GLuint,
|
||||
ebo: gl::GLuint,
|
||||
font_texture: gl::GLuint,
|
||||
}
|
||||
|
||||
struct Locs {
|
||||
texture: gl::GLint,
|
||||
proj_mtx: gl::GLint,
|
||||
position: gl::GLuint,
|
||||
uv: gl::GLuint,
|
||||
color: gl::GLuint,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(
|
||||
imgui: &mut Context,
|
||||
) -> Self
|
||||
{
|
||||
|
||||
unsafe {
|
||||
#[cfg(target_os = "macos")]
|
||||
let glsl_version = b"#version 150\n\0";
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let glsl_version = b"#version 130\n\0";
|
||||
|
||||
let vert_source = b"
|
||||
uniform mat4 ProjMtx;
|
||||
in vec2 Position;
|
||||
in vec2 UV;
|
||||
in vec4 Color;
|
||||
out vec2 Frag_UV;
|
||||
out vec4 Frag_Color;
|
||||
void main()
|
||||
{
|
||||
Frag_UV = UV;
|
||||
Frag_Color = Color;
|
||||
gl_Position = ProjMtx * vec4(Position.xy,0,1);
|
||||
}
|
||||
\0";
|
||||
|
||||
let frag_source = b"
|
||||
uniform sampler2D Texture;
|
||||
in vec2 Frag_UV;
|
||||
in vec4 Frag_Color;
|
||||
out vec4 Out_Color;
|
||||
void main()
|
||||
{
|
||||
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
|
||||
}
|
||||
\0";
|
||||
|
||||
let vert_sources = [glsl_version.as_ptr() as *const gl::GLchar,
|
||||
vert_source.as_ptr() as *const gl::GLchar];
|
||||
let vert_sources_len = [glsl_version.len() as gl::GLint - 1,
|
||||
vert_source.len() as gl::GLint - 1];
|
||||
let frag_sources = [glsl_version.as_ptr() as *const gl::GLchar,
|
||||
frag_source.as_ptr() as *const gl::GLchar];
|
||||
let frag_sources_len = [glsl_version.len() as gl::GLint - 1,
|
||||
frag_source.len() as gl::GLint - 1];
|
||||
|
||||
let program = gl::CreateProgram();
|
||||
let vert_shader = gl::CreateShader(gl::VERTEX_SHADER);
|
||||
let frag_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
|
||||
gl::ShaderSource(vert_shader, 2, vert_sources.as_ptr(), vert_sources_len.as_ptr());
|
||||
gl::ShaderSource(frag_shader, 2, frag_sources.as_ptr(), frag_sources_len.as_ptr());
|
||||
gl::CompileShader(vert_shader);
|
||||
gl::CompileShader(frag_shader);
|
||||
gl::AttachShader(program, vert_shader);
|
||||
gl::AttachShader(program, frag_shader);
|
||||
gl::LinkProgram(program);
|
||||
gl::DeleteShader(vert_shader);
|
||||
gl::DeleteShader(frag_shader);
|
||||
|
||||
let locs = Locs{
|
||||
texture: gl::GetUniformLocation(program, b"Texture\0".as_ptr() as _),
|
||||
proj_mtx: gl::GetUniformLocation(program, b"ProjMtx\0".as_ptr() as _),
|
||||
position: gl::GetAttribLocation(program, b"Position\0".as_ptr() as _) as _,
|
||||
uv: gl::GetAttribLocation(program, b"UV\0".as_ptr() as _) as _,
|
||||
color: gl::GetAttribLocation(program, b"Color\0".as_ptr() as _) as _,
|
||||
};
|
||||
|
||||
let vbo = return_param(|x| gl::GenBuffers(1, x) );
|
||||
let ebo = return_param(|x| gl::GenBuffers(1, x) );
|
||||
|
||||
let mut current_texture = 0;
|
||||
gl::GetIntegerv(gl::TEXTURE_BINDING_2D, &mut current_texture);
|
||||
|
||||
|
||||
let font_texture = return_param(|x| gl::GenTextures(1, x));
|
||||
gl::BindTexture(gl::TEXTURE_2D, font_texture);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
|
||||
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
{
|
||||
let atlas = imgui.fonts();
|
||||
|
||||
let texture = atlas.build_rgba32_texture();
|
||||
gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGBA as _, texture.width as _, texture.height as _, 0, gl::RGBA, gl::UNSIGNED_BYTE, texture.data.as_ptr() as _);
|
||||
|
||||
atlas.tex_id = (font_texture as usize).into();
|
||||
}
|
||||
|
||||
gl::BindTexture(gl::TEXTURE_2D, current_texture as _);
|
||||
|
||||
Self{
|
||||
program,
|
||||
locs,
|
||||
vbo,
|
||||
ebo,
|
||||
font_texture,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
ctx: &mut Context,
|
||||
) {
|
||||
use imgui::{DrawVert,DrawIdx,DrawCmd,DrawCmdParams};
|
||||
|
||||
unsafe {
|
||||
let last_active_texture = return_param(|x| gl::GetIntegerv(gl::ACTIVE_TEXTURE, x));
|
||||
gl::ActiveTexture(gl::TEXTURE0);
|
||||
let last_program = return_param(|x| gl::GetIntegerv(gl::CURRENT_PROGRAM, x));
|
||||
let last_texture = return_param(|x| gl::GetIntegerv(gl::TEXTURE_BINDING_2D, x));
|
||||
let last_sampler = return_param(|x| gl::GetIntegerv(gl::SAMPLER_BINDING, x));
|
||||
let last_array_buffer = return_param(|x| gl::GetIntegerv(gl::ARRAY_BUFFER_BINDING, x));
|
||||
let last_element_array_buffer = return_param(|x| gl::GetIntegerv(gl::ELEMENT_ARRAY_BUFFER_BINDING, x));
|
||||
let last_vertex_array = return_param(|x| gl::GetIntegerv(gl::VERTEX_ARRAY_BINDING, x));
|
||||
let last_polygon_mode = return_param(|x: &mut [gl::GLint; 2]| gl::GetIntegerv(gl::POLYGON_MODE, x.as_mut_ptr()));
|
||||
let last_viewport = return_param(|x: &mut [gl::GLint; 4]| gl::GetIntegerv(gl::VIEWPORT, x.as_mut_ptr()));
|
||||
let last_scissor_box = return_param(|x: &mut [gl::GLint; 4]| gl::GetIntegerv(gl::SCISSOR_BOX, x.as_mut_ptr()));
|
||||
let last_blend_src_rgb = return_param(|x| gl::GetIntegerv(gl::BLEND_SRC_RGB, x));
|
||||
let last_blend_dst_rgb = return_param(|x| gl::GetIntegerv(gl::BLEND_DST_RGB, x));
|
||||
let last_blend_src_alpha = return_param(|x| gl::GetIntegerv(gl::BLEND_SRC_ALPHA, x));
|
||||
let last_blend_dst_alpha = return_param(|x| gl::GetIntegerv(gl::BLEND_DST_ALPHA, x));
|
||||
let last_blend_equation_rgb = return_param(|x| gl::GetIntegerv(gl::BLEND_EQUATION_RGB, x));
|
||||
let last_blend_equation_alpha = return_param(|x| gl::GetIntegerv(gl::BLEND_EQUATION_ALPHA, x));
|
||||
let last_enable_blend = gl::IsEnabled(gl::BLEND) == gl::TRUE;
|
||||
let last_enable_cull_face = gl::IsEnabled(gl::CULL_FACE) == gl::TRUE;
|
||||
let last_enable_depth_test = gl::IsEnabled(gl::DEPTH_TEST) == gl::TRUE;
|
||||
let last_enable_scissor_test = gl::IsEnabled(gl::SCISSOR_TEST) == gl::TRUE;
|
||||
|
||||
|
||||
gl::Enable(gl::BLEND);
|
||||
gl::BlendEquation(gl::FUNC_ADD);
|
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||
gl::Disable(gl::CULL_FACE);
|
||||
gl::Disable(gl::DEPTH_TEST);
|
||||
gl::Enable(gl::SCISSOR_TEST);
|
||||
gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL);
|
||||
|
||||
|
||||
let [width, height] = ctx.io().display_size;
|
||||
let [scale_w, scale_h] = ctx.io().display_framebuffer_scale;
|
||||
|
||||
let fb_width = width * scale_w;
|
||||
let fb_height = height * scale_h;
|
||||
|
||||
gl::Viewport(0, 0, fb_width as _, fb_height as _);
|
||||
let matrix = [
|
||||
[ 2.0 / width as f32, 0.0, 0.0, 0.0],
|
||||
[ 0.0, 2.0 / -(height as f32), 0.0, 0.0],
|
||||
[ 0.0, 0.0, -1.0, 0.0],
|
||||
[-1.0, 1.0, 0.0, 1.0],
|
||||
];
|
||||
gl::UseProgram(self.program);
|
||||
gl::Uniform1i(self.locs.texture, 0);
|
||||
gl::UniformMatrix4fv(self.locs.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
|
||||
gl::BindSampler(0, 0);
|
||||
|
||||
|
||||
let vao = return_param(|x| gl::GenVertexArrays(1, x));
|
||||
gl::BindVertexArray(vao);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
|
||||
gl::EnableVertexAttribArray(self.locs.position);
|
||||
gl::EnableVertexAttribArray(self.locs.uv);
|
||||
gl::EnableVertexAttribArray(self.locs.color);
|
||||
gl::VertexAttribPointer(self.locs.position, 2, gl::FLOAT, gl::FALSE, mem::size_of::<DrawVert>() as _, field_offset::<DrawVert, _, _>(|v| &v.pos) as _);
|
||||
gl::VertexAttribPointer(self.locs.uv, 2, gl::FLOAT, gl::FALSE, mem::size_of::<DrawVert>() as _, field_offset::<DrawVert, _, _>(|v| &v.uv) as _);
|
||||
gl::VertexAttribPointer(self.locs.color, 4, gl::UNSIGNED_BYTE, gl::TRUE, mem::size_of::<DrawVert>() as _, field_offset::<DrawVert, _, _>(|v| &v.col) as _);
|
||||
|
||||
|
||||
let draw_data = ctx.render();
|
||||
|
||||
for draw_list in draw_data.draw_lists() {
|
||||
let vtx_buffer = draw_list.vtx_buffer();
|
||||
let idx_buffer = draw_list.idx_buffer();
|
||||
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
|
||||
gl::BufferData(gl::ARRAY_BUFFER, (vtx_buffer.len() * mem::size_of::<DrawVert>()) as _, vtx_buffer.as_ptr() as _, gl::STREAM_DRAW);
|
||||
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
|
||||
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, (idx_buffer.len() * mem::size_of::<DrawIdx>()) as _, idx_buffer.as_ptr() as _, gl::STREAM_DRAW);
|
||||
|
||||
for cmd in draw_list.commands() {
|
||||
match cmd {
|
||||
DrawCmd::Elements {
|
||||
count,
|
||||
cmd_params: DrawCmdParams {
|
||||
clip_rect: [x, y, z, w],
|
||||
texture_id,
|
||||
idx_offset,
|
||||
..
|
||||
},
|
||||
} => {
|
||||
gl::BindTexture(gl::TEXTURE_2D, texture_id.id() as _);
|
||||
|
||||
gl::Scissor((x * scale_w) as gl::GLint,
|
||||
(fb_height - w * scale_h) as gl::GLint,
|
||||
((z - x) * scale_w) as gl::GLint,
|
||||
((w - y) * scale_h) as gl::GLint);
|
||||
|
||||
let idx_size = if mem::size_of::<DrawIdx>() == 2 { gl::UNSIGNED_SHORT } else { gl::UNSIGNED_INT };
|
||||
|
||||
gl::DrawElements(gl::TRIANGLES, count as _, idx_size, (idx_offset * mem::size_of::<DrawIdx>()) as _);
|
||||
},
|
||||
DrawCmd::ResetRenderState => {
|
||||
unimplemented!("Haven't implemented DrawCmd::ResetRenderState yet");
|
||||
},
|
||||
DrawCmd::RawCallback { .. } => {
|
||||
unimplemented!("Haven't implemented user callbacks yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gl::DeleteVertexArrays(1, &vao);
|
||||
|
||||
gl::UseProgram(last_program as _);
|
||||
gl::BindTexture(gl::TEXTURE_2D, last_texture as _);
|
||||
gl::BindSampler(0, last_sampler as _);
|
||||
gl::ActiveTexture(last_active_texture as _);
|
||||
gl::BindVertexArray(last_vertex_array as _);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, last_array_buffer as _);
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, last_element_array_buffer as _);
|
||||
gl::BlendEquationSeparate(last_blend_equation_rgb as _, last_blend_equation_alpha as _);
|
||||
gl::BlendFuncSeparate(last_blend_src_rgb as _, last_blend_dst_rgb as _, last_blend_src_alpha as _, last_blend_dst_alpha as _);
|
||||
if last_enable_blend { gl::Enable(gl::BLEND) } else { gl::Disable(gl::BLEND) };
|
||||
if last_enable_cull_face { gl::Enable(gl::CULL_FACE) } else { gl::Disable(gl::CULL_FACE) };
|
||||
if last_enable_depth_test { gl::Enable(gl::DEPTH_TEST) } else { gl::Disable(gl::DEPTH_TEST) };
|
||||
if last_enable_scissor_test { gl::Enable(gl::SCISSOR_TEST) } else { gl::Disable(gl::SCISSOR_TEST) };
|
||||
gl::PolygonMode(gl::FRONT_AND_BACK, last_polygon_mode[0] as _);
|
||||
gl::Viewport(last_viewport[0] as _, last_viewport[1] as _, last_viewport[2] as _, last_viewport[3] as _);
|
||||
gl::Scissor(last_scissor_box[0] as _, last_scissor_box[1] as _, last_scissor_box[2] as _, last_scissor_box[3] as _);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Renderer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
gl::DeleteBuffers(1, &self.vbo);
|
||||
gl::DeleteBuffers(1, &self.ebo);
|
||||
|
||||
gl::DeleteProgram(self.program);
|
||||
|
||||
gl::DeleteTextures(1, &self.font_texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn field_offset<T, U, F: for<'a> FnOnce(&'a T) -> &'a U>(f: F) -> usize {
|
||||
unsafe {
|
||||
let instance = mem::zeroed::<T>();
|
||||
|
||||
let offset = {
|
||||
let field: &U = f(&instance);
|
||||
field as *const U as usize - &instance as *const T as usize
|
||||
};
|
||||
|
||||
mem::forget(instance);
|
||||
|
||||
offset
|
||||
}
|
||||
}
|
||||
|
||||
fn return_param<T, F>(f: F) -> T where F: FnOnce(&mut T) {
|
||||
let mut val = unsafe{ mem::zeroed() };
|
||||
f(&mut val);
|
||||
val
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
pub mod shader;
|
||||
pub mod camera;
|
||||
#[path="imgui-glfw-support/mod.rs"]
|
||||
pub mod imgui_glfw_support;
|
||||
#[path="imgui-opengl-renderer/mod.rs"]
|
||||
pub mod imgui_opengl_renderer;
|
||||
pub mod utils;
|
||||
pub mod mesh;
|
||||
pub mod model;
|
||||
pub mod ui;
|
||||
pub mod log;
|
||||
pub mod aabb;
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LogLevel {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl From<LogLevel> for mint::Vector4<f32> {
|
||||
fn from(level: LogLevel) -> Self {
|
||||
match level {
|
||||
LogLevel::Debug => [0.5, 0.5, 1.0, 1.0].into(),
|
||||
LogLevel::Info => [0.5, 0.5, 0.5, 1.0].into(),
|
||||
LogLevel::Warning => [1.0, 0.64, 0.0, 1.0].into(),
|
||||
LogLevel::Error => [1.0, 0.0, 0.0, 1.0].into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LogMessage {
|
||||
pub level: LogLevel,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl LogMessage {
|
||||
pub fn new(level: LogLevel, message: &str) -> Self {
|
||||
let severity = match level {
|
||||
LogLevel::Debug => "DEBUG",
|
||||
LogLevel::Info => "INFO",
|
||||
LogLevel::Warning => "WARNING",
|
||||
LogLevel::Error => "ERROR",
|
||||
};
|
||||
|
||||
Self {
|
||||
level,
|
||||
message: format!("[{}]: {}", severity, message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Log {
|
||||
pub history: Vec<LogMessage>,
|
||||
}
|
||||
|
||||
impl Log {
|
||||
pub fn clear(&mut self) {
|
||||
self.history.clear();
|
||||
}
|
||||
|
||||
pub fn log(&mut self, message: &str, level: LogLevel) {
|
||||
// TODO: also write to a log file
|
||||
self.history.push(LogMessage::new(level, message));
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Log {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
history: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
use glfw::{Action, Context, Key, Modifiers};
|
||||
use glad_gl::gl;
|
||||
use anyhow;
|
||||
|
||||
use threedobs::{shader, ui::ui};
|
||||
|
||||
fn main() -> anyhow::Result<(), Box<dyn std::error::Error>> {
|
||||
let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS)?;
|
||||
|
||||
glfw::WindowHint::ContextVersion(3, 3);
|
||||
glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core);
|
||||
glfw::WindowHint::OpenGlForwardCompat(true);
|
||||
|
||||
let (mut window, events) = glfw.create_window(1200, 800, "3dobs", glfw::WindowMode::Windowed).expect("Failed to create GLFW window");
|
||||
|
||||
window.set_all_polling(true);
|
||||
window.set_cursor_mode(glfw::CursorMode::Disabled);
|
||||
window.make_current();
|
||||
|
||||
let mut state = ui::State::default();
|
||||
|
||||
glfw.set_swap_interval(glfw::SwapInterval::Sync(1));
|
||||
|
||||
let (mut imgui, glfw_platform, renderer) = ui::init_imgui(&mut window);
|
||||
|
||||
let mesh_shader = shader::Shader::new("shaders/vertex.glsl", "shaders/frag.glsl")?;
|
||||
let light_shader = shader::Shader::new("shaders/vertex.glsl", "shaders/light_f.glsl")?;
|
||||
let grid_shader = shader::Shader::new("shaders/grid_v.glsl", "shaders/grid_f.glsl")?;
|
||||
|
||||
let vertices: [f32; 288] = [
|
||||
// positions // normals // texture coords
|
||||
-0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0,
|
||||
0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 0.0,
|
||||
0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0,
|
||||
0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0,
|
||||
-0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 1.0,
|
||||
-0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0,
|
||||
-0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0,
|
||||
0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0,
|
||||
0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0,
|
||||
0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0,
|
||||
-0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 1.0,
|
||||
-0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0,
|
||||
-0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 0.0,
|
||||
-0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 1.0,
|
||||
-0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 0.0, 1.0,
|
||||
-0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 0.0, 1.0,
|
||||
-0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 0.0, 0.0,
|
||||
-0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 0.0,
|
||||
0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 0.0,
|
||||
0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 1.0, 1.0,
|
||||
0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 1.0,
|
||||
0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 1.0,
|
||||
0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 0.0,
|
||||
-0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 1.0,
|
||||
0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 1.0, 1.0,
|
||||
0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 0.0,
|
||||
0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 0.0,
|
||||
-0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 0.0, 0.0,
|
||||
-0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 1.0,
|
||||
-0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||
0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 1.0,
|
||||
0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 0.0,
|
||||
0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 0.0,
|
||||
-0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 0.0,
|
||||
-0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 1.0
|
||||
];
|
||||
|
||||
let points_lights: [glm::Vec3; 4] = [
|
||||
glm::vec3(0.7, 0.2, 2.0),
|
||||
glm::vec3(2.3, -3.3, -4.0),
|
||||
glm::vec3(-4.0, 2.0, -12.0),
|
||||
glm::vec3(0.0, 0.0, -3.0),
|
||||
];
|
||||
|
||||
let ident_mat = glm::mat4(
|
||||
1., 0., 0., 0.,
|
||||
0., 1., 0., 0.,
|
||||
0., 0., 1., 0.,
|
||||
0., 0., 0., 1.);
|
||||
|
||||
let mut delta_time: f32 = 0.0;
|
||||
let mut last_frame: f32 = 0.0;
|
||||
let mut last_cursor = None;
|
||||
|
||||
let (w, h) = window.get_size();
|
||||
let mut last_x: f32 = w as f32 / 2.0;
|
||||
let mut last_y: f32 = h as f32 / 2.0;
|
||||
let mut first_mouse: bool = true;
|
||||
|
||||
unsafe {
|
||||
// Object 1: Cube/container
|
||||
//
|
||||
let mut vbo: u32 = 0;
|
||||
gl::GenBuffers(1, &mut vbo);
|
||||
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
||||
gl::BufferData(gl::ARRAY_BUFFER, (vertices.len() * core::mem::size_of::<f32>()) as isize, vertices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
|
||||
|
||||
// position attribute, maps to 'aPos' in vertex shader with location 0
|
||||
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, 8 * std::mem::size_of::<f32>() as i32, std::ptr::null());
|
||||
gl::EnableVertexAttribArray(0);
|
||||
|
||||
// normal attribute, maps to 'aNormal' in vertex shader with location 1
|
||||
gl::VertexAttribPointer(1, 3, gl::FLOAT, gl::FALSE, 8 * std::mem::size_of::<f32>() as i32, (3 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
|
||||
gl::EnableVertexAttribArray(1);
|
||||
|
||||
// texture attribute, maps to 'aTexCoords' in vertex shader with location 2
|
||||
gl::VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, 8 * std::mem::size_of::<f32>() as i32, (6 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
|
||||
gl::EnableVertexAttribArray(2);
|
||||
|
||||
// Object 2: Light source
|
||||
let mut light_vao: u32 = 0;
|
||||
gl::GenVertexArrays(1, &mut light_vao);
|
||||
|
||||
gl::BindVertexArray(light_vao);
|
||||
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
||||
|
||||
// position attribute, maps to 'aPos' in vertex shader with location 0
|
||||
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, 8 * std::mem::size_of::<f32>() as i32, std::ptr::null());
|
||||
gl::EnableVertexAttribArray(0);
|
||||
|
||||
grid_shader.use_shader();
|
||||
grid_shader.set_float("near", 0.01);
|
||||
grid_shader.set_float("far", 200.0);
|
||||
|
||||
mesh_shader.use_shader();
|
||||
|
||||
// set light uniforms
|
||||
for i in 0..points_lights.len() {
|
||||
mesh_shader.set_3fv(&format!("pointLights[{}].position", i), points_lights[i]);
|
||||
|
||||
mesh_shader.set_float(&format!("pointLights[{}].constant", i), 1.0);
|
||||
mesh_shader.set_float(&format!("pointLights[{}].linear", i), 0.09);
|
||||
mesh_shader.set_float(&format!("pointLights[{}].quadratic", i), 0.032);
|
||||
|
||||
mesh_shader.set_3fv(&format!("pointLights[{}].ambient", i), glm::vec3(0.2, 0.2, 0.2));
|
||||
mesh_shader.set_3fv(&format!("pointLights[{}].diffuse", i), glm::vec3(0.5, 0.5, 0.5));
|
||||
mesh_shader.set_3fv(&format!("pointLights[{}].specular", i), glm::vec3(1.0, 1.0, 1.0));
|
||||
}
|
||||
mesh_shader.set_float("spotLight.cutOff", glm::cos(glm::radians(12.5)));
|
||||
mesh_shader.set_float("spotLight.outerCutOff", glm::cos(glm::radians(15.0)));
|
||||
mesh_shader.set_3fv("spotLight.ambient", glm::vec3(0.2, 0.2, 0.2));
|
||||
mesh_shader.set_3fv("spotLight.diffuse", glm::vec3(0.2, 0.2, 0.2));
|
||||
mesh_shader.set_3fv("spotLight.specular", glm::vec3(1.0, 1.0, 1.0));
|
||||
mesh_shader.set_float("spotLight.constant", 1.0);
|
||||
mesh_shader.set_float("spotLight.linear", 0.09);
|
||||
mesh_shader.set_float("spotLight.quadratic", 0.032);
|
||||
|
||||
mesh_shader.set_3fv("dirLight.direction", glm::vec3(-0.2, -1.0, -0.3));
|
||||
mesh_shader.set_3fv("dirLight.ambient", glm::vec3(0.2, 0.2, 0.2));
|
||||
mesh_shader.set_3fv("dirLight.diffuse", glm::vec3(0.5, 0.5, 0.5));
|
||||
mesh_shader.set_3fv("dirLight.specular", glm::vec3(1.0, 1.0, 1.0));
|
||||
|
||||
let scene_fb = create_scene_framebuffer();
|
||||
|
||||
// main loop
|
||||
while !window.should_close() {
|
||||
let current_frame = glfw.get_time() as f32;
|
||||
delta_time = current_frame - last_frame;
|
||||
last_frame = current_frame;
|
||||
|
||||
imgui.io_mut().update_delta_time(std::time::Duration::from_secs_f32(delta_time));
|
||||
|
||||
state.camera.update_speed(delta_time);
|
||||
|
||||
// camera matrices
|
||||
let view_mat = glm::ext::look_at(state.camera.position, state.camera.position + state.camera.front, state.camera.up);
|
||||
let projection_mat = glm::ext::perspective(glm::radians(state.camera.fov), state.viewport_size[0] / state.viewport_size[1], 0.01, 200.0);
|
||||
|
||||
for (_, event) in glfw::flush_messages(&events) {
|
||||
// order of handling events is important here
|
||||
// we need to handle window events first to have an updated
|
||||
// is_cursor_captured
|
||||
handle_window_event(&mut window, &event, &mut state);
|
||||
if !state.is_cursor_captured {
|
||||
glfw_platform.handle_event(imgui.io_mut(), &window, &event);
|
||||
}
|
||||
|
||||
match event {
|
||||
glfw::WindowEvent::CursorPos(xpos, ypos) => {
|
||||
if first_mouse {
|
||||
last_x = xpos as f32;
|
||||
last_y = ypos as f32;
|
||||
first_mouse = false;
|
||||
}
|
||||
|
||||
let xoffset = xpos as f32 - last_x;
|
||||
let yoffset = last_y - ypos as f32;
|
||||
last_x = xpos as f32;
|
||||
last_y = ypos as f32;
|
||||
|
||||
if state.can_capture_cursor && window.get_mouse_button(glfw::MouseButtonLeft) == Action::Press {
|
||||
if window.get_key(glfw::Key::LeftShift) == Action::Press {
|
||||
state.camera.move_camera(xoffset, yoffset);
|
||||
} else {
|
||||
if let Some(active_model) = state.active_model {
|
||||
let model = state.objects.iter_mut().find(|m| m.id == active_model).unwrap();
|
||||
model.rotate(xoffset * state.camera.sensitivity, yoffset * state.camera.sensitivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// draw scene to framebuffer
|
||||
//
|
||||
let (scene_texture, rbo) = create_scene_texture_and_renderbuffer(&window, scene_fb);
|
||||
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, scene_fb);
|
||||
gl::Enable(gl::DEPTH_TEST);
|
||||
gl::Enable(gl::BLEND);
|
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||
gl::ClearColor(0.2, 0.2, 0.2, 1.0);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
|
||||
|
||||
mesh_shader.use_shader();
|
||||
|
||||
mesh_shader.set_mat4fv("view", &view_mat);
|
||||
mesh_shader.set_mat4fv("projection", &projection_mat);
|
||||
|
||||
mesh_shader.set_3fv("spotLight.position", state.camera.position);
|
||||
mesh_shader.set_3fv("spotLight.direction", state.camera.front);
|
||||
mesh_shader.set_3fv("viewPos", state.camera.position);
|
||||
|
||||
for obj in &state.objects {
|
||||
if state.wireframe {
|
||||
gl::PolygonMode(gl::FRONT_AND_BACK, gl::LINE);
|
||||
} else {
|
||||
gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL);
|
||||
}
|
||||
if Some(obj.id) == state.active_model {obj.draw(&mesh_shader, state.draw_aabb);}
|
||||
}
|
||||
gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL);
|
||||
|
||||
light_shader.use_shader();
|
||||
light_shader.set_mat4fv("view", &view_mat);
|
||||
light_shader.set_mat4fv("projection", &projection_mat);
|
||||
|
||||
// for i in 0..points_lights.len() {
|
||||
// let light_model = glm::ext::translate(&ident_mat, points_lights[i]);
|
||||
// let light_model = glm::ext::scale(&light_model, glm::vec3(0.2, 0.2, 0.2));
|
||||
// light_shader.set_mat4fv("model", &light_model);
|
||||
|
||||
// gl::BindVertexArray(light_vao);
|
||||
// gl::DrawArrays(gl::TRIANGLES, 0, 36);
|
||||
// }
|
||||
|
||||
// draw grid
|
||||
if state.draw_grid {draw_grid(&grid_shader, &view_mat, &projection_mat);}
|
||||
|
||||
//
|
||||
// draw ui
|
||||
//
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
||||
gl::ClearColor(0.1, 0.1, 0.1, 1.0);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
gl::Disable(gl::DEPTH_TEST);
|
||||
gl::Disable(gl::BLEND);
|
||||
ui::draw_ui(&mut imgui, &renderer, &glfw_platform, &mut window, &mut state, delta_time, &mut last_cursor, scene_texture);
|
||||
|
||||
glfw.poll_events();
|
||||
window.swap_buffers();
|
||||
|
||||
gl::DeleteTextures(1, &scene_texture);
|
||||
gl::DeleteRenderbuffers(1, &rbo);
|
||||
}
|
||||
|
||||
gl::DeleteVertexArrays(1, &light_vao);
|
||||
gl::DeleteBuffers(1, &vbo);
|
||||
gl::DeleteFramebuffers(1, &scene_fb);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_grid(shader: &threedobs::shader::Shader, view_mat: &glm::Mat4, projection_mat: &glm::Mat4) {
|
||||
shader.use_shader();
|
||||
shader.set_mat4fv("view", &view_mat);
|
||||
shader.set_mat4fv("projection", &projection_mat);
|
||||
unsafe {
|
||||
gl::DrawArrays(gl::TRIANGLES, 0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_window_event(window: &mut glfw::Window, event: &glfw::WindowEvent, state: &mut ui::State) {
|
||||
match event {
|
||||
glfw::WindowEvent::Key(Key::O, _, Action::Press, Modifiers::Control) => {
|
||||
ui::import_model(state);
|
||||
}
|
||||
glfw::WindowEvent::Key(Key::Q, _, Action::Press, Modifiers::Control) => {
|
||||
window.set_should_close(true);
|
||||
}
|
||||
glfw::WindowEvent::Key(Key::LeftControl, _, Action::Press, _) => {
|
||||
state.camera.speed *= 5.0;
|
||||
}
|
||||
glfw::WindowEvent::Key(Key::LeftControl, _, Action::Release, _) => {
|
||||
state.camera.speed /= 5.0;
|
||||
}
|
||||
glfw::WindowEvent::MouseButton(glfw::MouseButtonLeft, Action::Press, _) => {
|
||||
if !state.can_capture_cursor { return; }
|
||||
state.is_cursor_captured = true;
|
||||
window.set_cursor_mode(glfw::CursorMode::Disabled);
|
||||
}
|
||||
glfw::WindowEvent::MouseButton(glfw::MouseButtonLeft, Action::Release, _) => {
|
||||
if !state.can_capture_cursor { return; }
|
||||
state.is_cursor_captured = false;
|
||||
window.set_cursor_mode(glfw::CursorMode::Normal);
|
||||
}
|
||||
glfw::WindowEvent::Scroll(_, yoff) => {
|
||||
state.camera.handle_mouse_scroll(*yoff as f32, state.can_capture_cursor);
|
||||
}
|
||||
glfw::WindowEvent::FramebufferSize(w, h) => {
|
||||
unsafe {
|
||||
gl::Viewport(0, 0, *w, *h);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_scene_framebuffer() -> u32 {
|
||||
let mut fb: u32 = 0;
|
||||
|
||||
unsafe {
|
||||
gl::GenFramebuffers(1, &mut fb);
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, fb);
|
||||
}
|
||||
|
||||
return fb;
|
||||
}
|
||||
|
||||
fn create_scene_texture_and_renderbuffer(window: &glfw::Window, fbo: u32) -> (u32, u32) {
|
||||
let mut fb_texture: u32 = 0;
|
||||
let mut rbo: u32 = 0;
|
||||
|
||||
let (w, h) = window.get_size();
|
||||
|
||||
unsafe {
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
|
||||
// texture
|
||||
gl::GenTextures(1, &mut fb_texture);
|
||||
gl::BindTexture(gl::TEXTURE_2D, fb_texture);
|
||||
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
|
||||
gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGB as i32, w, h, 0, gl::RGB, gl::UNSIGNED_BYTE, std::ptr::null());
|
||||
|
||||
gl::FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, fb_texture, 0);
|
||||
|
||||
// renderbuffer for depth
|
||||
gl::GenRenderbuffers(1, &mut rbo);
|
||||
gl::BindRenderbuffer(gl::RENDERBUFFER, rbo);
|
||||
gl::RenderbufferStorage(gl::RENDERBUFFER, gl::DEPTH24_STENCIL8, w, h);
|
||||
gl::FramebufferRenderbuffer(gl::FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, gl::RENDERBUFFER, rbo);
|
||||
|
||||
if gl::CheckFramebufferStatus(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE {
|
||||
panic!("ERROR::FRAMEBUFFER:: Framebuffer is not complete!");
|
||||
}
|
||||
}
|
||||
|
||||
return (fb_texture, rbo);
|
||||
}
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
use glad_gl::gl;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{shader::Shader, utils};
|
||||
|
||||
fn decompose_mat(matrix: &mut glm::Mat4) -> (glm::Vec3, glm::Vec3, glm::Vec3) {
|
||||
let pos = glm::vec3(matrix.c0.w, matrix.c1.w, matrix.c2.w);
|
||||
matrix.c3.x = 0.0;
|
||||
matrix.c3.y = 0.0;
|
||||
matrix.c3.z = 0.0;
|
||||
|
||||
let scale_x = glm::length(glm::vec3(matrix.c0.x, matrix.c1.x, matrix.c2.x));
|
||||
let scale_y = glm::length(glm::vec3(matrix.c0.y, matrix.c1.y, matrix.c2.y));
|
||||
let scale_z = glm::length(glm::vec3(matrix.c0.z, matrix.c1.z, matrix.c2.z));
|
||||
let scale = glm::vec3(scale_x, scale_y, scale_z);
|
||||
|
||||
let matrix = glm::mat3(
|
||||
matrix[0][0] / scale_x, matrix[0][1] / scale_y, matrix[0][2] / scale_z,
|
||||
matrix[1][0] / scale_x, matrix[1][1] / scale_y, matrix[1][2] / scale_z,
|
||||
matrix[2][0] / scale_x, matrix[2][1] / scale_y, matrix[2][2] / scale_z,
|
||||
);
|
||||
|
||||
let mut pitch = 0.0;
|
||||
let mut yaw = 0.0;
|
||||
let mut roll = 0.0;
|
||||
|
||||
// source: https://www.geometrictools.com/Documentation/EulerAngles.pdf
|
||||
if matrix[0][2] < 1.0 {
|
||||
if matrix[0][2] > -1.0 {
|
||||
pitch = (-matrix[1][2]).atan2(matrix[2][2]).to_degrees();
|
||||
yaw = matrix[0][2].asin().to_degrees();
|
||||
roll = (-matrix[0][1]).atan2(matrix[0][0]).to_degrees();
|
||||
} else {
|
||||
pitch = -(matrix[1][0].atan2(matrix[1][1])).to_degrees();
|
||||
yaw = -(std::f32::consts::FRAC_PI_2).to_degrees();
|
||||
roll = 0.0;
|
||||
}
|
||||
} else {
|
||||
pitch = matrix[1][0].atan2(matrix[1][1]).to_degrees();
|
||||
yaw = std::f32::consts::FRAC_PI_2.to_degrees();
|
||||
roll = 0.0;
|
||||
}
|
||||
|
||||
let rotation = glm::vec3(pitch, yaw, roll);
|
||||
|
||||
(pos, rotation, scale)
|
||||
}
|
||||
|
||||
fn create_rotation_matrix(pitch: f32, yaw: f32, roll: f32, pivot: glm::Vec3) -> glm::Mat4 {
|
||||
let pitch = pitch.to_radians();
|
||||
let yaw = yaw.to_radians();
|
||||
let roll = roll.to_radians();
|
||||
|
||||
let mat = utils::mat_ident();
|
||||
|
||||
let rot_z = glm::ext::rotate(&mat, roll, glm::vec3(0.0, 0.0, 1.0));
|
||||
let rot_x = glm::ext::rotate(&mat, pitch, glm::vec3(1.0, 0.0, 0.0));
|
||||
let rot_y = glm::ext::rotate(&mat, yaw, glm::vec3(0.0, 1.0, 0.0));
|
||||
let trans_to_origin = glm::ext::translate(&mat, -glm::vec3(pivot.x, pivot.y, pivot.z));
|
||||
let trans_back_to_center = glm::ext::translate(&mat, glm::vec3(pivot.x, pivot.y, pivot.z));
|
||||
|
||||
let combined = trans_back_to_center * rot_z * rot_y * rot_x * trans_to_origin;
|
||||
|
||||
combined
|
||||
}
|
||||
|
||||
pub fn apply_rotation(matrix: &glm::Mat4, rot: glm::Vec3, pivot: glm::Vec3) -> glm::Mat4 {
|
||||
let rot = create_rotation_matrix(rot.x, rot.y, rot.z, pivot);
|
||||
|
||||
return rot * *matrix;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mesh {
|
||||
pub name: String,
|
||||
pub position: glm::Vec3,
|
||||
pub rotation: glm::Vec3,
|
||||
pub scale: glm::Vec3,
|
||||
pub pivot: glm::Vec3,
|
||||
|
||||
pub vertices: Vec<Vertex>,
|
||||
pub indices: Vec<u32>,
|
||||
pub textures: Vec<Texture>,
|
||||
pub material: Material,
|
||||
|
||||
vao: u32,
|
||||
}
|
||||
|
||||
impl Mesh {
|
||||
pub fn new(name: &str, vertices: Vec<Vertex>, indices: Vec<u32>, textures: Vec<Texture>, material: Material, transformation: &mut glm::Mat4) -> Mesh {
|
||||
let mut vao = 0;
|
||||
let mut vbo = 0;
|
||||
let mut ebo = 0;
|
||||
|
||||
unsafe {
|
||||
gl::GenVertexArrays(1, &mut vao);
|
||||
gl::GenBuffers(1, &mut vbo);
|
||||
gl::GenBuffers(1, &mut ebo);
|
||||
|
||||
gl::BindVertexArray(vao);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
||||
|
||||
gl::BufferData(gl::ARRAY_BUFFER, (std::mem::size_of::<Vertex>() * vertices.len() as usize) as isize, vertices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
|
||||
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
|
||||
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, (std::mem::size_of::<u32>() * indices.len() as usize) as isize, indices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
|
||||
|
||||
// vertex positions
|
||||
gl::EnableVertexAttribArray(0);
|
||||
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, std::ptr::null());
|
||||
|
||||
// vertex normals
|
||||
gl::EnableVertexAttribArray(1);
|
||||
gl::VertexAttribPointer(1, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, (3 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
|
||||
|
||||
// vertex texture coords
|
||||
gl::EnableVertexAttribArray(2);
|
||||
gl::VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, (6 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
|
||||
|
||||
gl::BindVertexArray(0);
|
||||
}
|
||||
|
||||
let (position, rotation, scale) = decompose_mat(transformation);
|
||||
|
||||
Mesh {
|
||||
name: name.to_string(),
|
||||
vertices,
|
||||
indices,
|
||||
textures,
|
||||
material,
|
||||
vao,
|
||||
position,
|
||||
rotation,
|
||||
scale,
|
||||
pivot: position,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, shader: &Shader, scale: f32, pivot: glm::Vec3) {
|
||||
let mut diffuse = 1;
|
||||
let mut specular = 1;
|
||||
|
||||
shader.use_shader();
|
||||
|
||||
let model_mat = glm::ext::scale(&utils::mat_ident(), glm::vec3(scale, scale, scale));
|
||||
let model_mat = apply_rotation(&model_mat, self.rotation, pivot);
|
||||
let model_mat = glm::ext::translate(&model_mat, glm::vec3(self.position.x, self.position.y, self.position.z));
|
||||
shader.set_mat4fv("model", &model_mat);
|
||||
|
||||
shader.set_3fv("material.ambient", self.material.ambient);
|
||||
shader.set_3fv("material.diffuse", self.material.diffuse);
|
||||
shader.set_3fv("material.specular", self.material.specular);
|
||||
shader.set_float("material.shininess", self.material.shininess);
|
||||
|
||||
for i in 0..self.textures.len() {
|
||||
unsafe {
|
||||
gl::ActiveTexture(gl::TEXTURE0 + i as u32);
|
||||
match self.textures[i].typ {
|
||||
russimp::material::TextureType::Diffuse => {
|
||||
diffuse += 1;
|
||||
shader.set_int(format!("material.texture_diffuse{}", diffuse).as_str(), i as i32);
|
||||
},
|
||||
russimp::material::TextureType::Specular => {
|
||||
specular += 1;
|
||||
shader.set_int(format!("material.texture_specular{}", specular).as_str(), i as i32);
|
||||
},
|
||||
_ => {}, // don't do anything because unsupported texture types are logged
|
||||
// when the model is loaded
|
||||
}
|
||||
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.textures[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// draw Mesh
|
||||
gl::BindVertexArray(self.vao);
|
||||
gl::DrawElements(gl::TRIANGLES, self.indices.len() as i32, gl::UNSIGNED_INT, std::ptr::null());
|
||||
|
||||
// reset stuff to default
|
||||
gl::ActiveTexture(gl::TEXTURE0);
|
||||
gl::BindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate(&mut self, rotation: glm::Vec3) {
|
||||
self.rotation = self.rotation + rotation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(packed(2))]
|
||||
pub struct Vertex {
|
||||
pub position: glm::Vec3,
|
||||
pub normal: glm::Vec3,
|
||||
pub tex_coords: glm::Vec2,
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
pub fn new(position: glm::Vec3, normal: glm::Vec3, tex_coords: glm::Vec2) -> Self {
|
||||
Vertex {
|
||||
position,
|
||||
normal,
|
||||
tex_coords
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Texture {
|
||||
pub id: u32,
|
||||
pub typ: russimp::material::TextureType,
|
||||
pub path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub fn new(path: std::path::PathBuf, typ: russimp::material::TextureType) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let path_str = match path.to_str() {
|
||||
Some(path) => path,
|
||||
None => return Err("Failed to convert texture path to string".into()),
|
||||
};
|
||||
let id = utils::load_texture(path_str)?;
|
||||
|
||||
Ok(Texture {
|
||||
id,
|
||||
typ,
|
||||
path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Material {
|
||||
pub name: String,
|
||||
pub ambient: glm::Vec3,
|
||||
pub diffuse: glm::Vec3,
|
||||
pub specular: glm::Vec3,
|
||||
pub shininess: f32,
|
||||
}
|
||||
|
||||
impl Material {
|
||||
pub fn new(name: String, ambient: glm::Vec3, diffuse: glm::Vec3, specular: glm::Vec3, shininess: f32) -> Self {
|
||||
Material {
|
||||
name,
|
||||
ambient,
|
||||
diffuse,
|
||||
specular,
|
||||
shininess,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Material {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Ambient: {:?}\nDiffuse: {:?}\nSpecular: {:?}\nShininess: {}", self.ambient, self.diffuse, self.specular, self.shininess)
|
||||
}
|
||||
}
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
use crate::{mesh::{Mesh, Vertex, Texture, Material, apply_rotation}, shader::Shader, utils, ui::ui, log, aabb};
|
||||
|
||||
use russimp;
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
const SUPPORTED_TEXTURE_TYPES: [russimp::material::TextureType; 2] = [
|
||||
russimp::material::TextureType::Diffuse,
|
||||
russimp::material::TextureType::Specular,
|
||||
];
|
||||
|
||||
const SCALING_FACTOR: f32 = 10.0;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Model {
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub meshes: Vec<Mesh>,
|
||||
pub aabb: aabb::AABB,
|
||||
pub aabb_mesh: aabb::AABBMesh,
|
||||
pub scaling_factor: f32,
|
||||
}
|
||||
|
||||
fn process_node<'a>(
|
||||
node: &russimp::node::Node,
|
||||
scene: &'a russimp::scene::Scene,
|
||||
meshes: &mut Vec<Mesh>,
|
||||
dir: &std::path::PathBuf,
|
||||
loaded_textures: &mut Vec<Texture>,
|
||||
init_trans: &glm::Mat4,
|
||||
aabb: &mut aabb::AABB,
|
||||
scale: &mut f32,
|
||||
) -> Vec<Box<dyn std::error::Error>> {
|
||||
let mut errors = vec![];
|
||||
|
||||
let node_trans = glm::mat4(
|
||||
node.transformation.a1, node.transformation.a2, node.transformation.a3, node.transformation.a4,
|
||||
node.transformation.b1, node.transformation.b2, node.transformation.b3, node.transformation.b4,
|
||||
node.transformation.c1, node.transformation.c2, node.transformation.c3, node.transformation.c4,
|
||||
node.transformation.d1, node.transformation.d2, node.transformation.d3, node.transformation.d4,
|
||||
);
|
||||
let mut new_trans = *init_trans * node_trans;
|
||||
|
||||
for i in 0..node.meshes.len() {
|
||||
let mesh = &scene.meshes[node.meshes[i] as usize];
|
||||
let (processed_mesh, mut errs) = process_mesh(mesh, scene, dir, loaded_textures, &mut new_trans, aabb);
|
||||
errors.append(&mut errs);
|
||||
meshes.push(processed_mesh);
|
||||
}
|
||||
|
||||
for child in node.children.borrow().clone().into_iter() {
|
||||
let mut errs = process_node(&child, scene, meshes, dir, loaded_textures, &new_trans, aabb, scale);
|
||||
errors.append(&mut errs);
|
||||
}
|
||||
|
||||
let scale_factor_x = SCALING_FACTOR / (aabb.max.x - aabb.min.x);
|
||||
let scale_factor_y = SCALING_FACTOR / (aabb.max.y - aabb.min.y);
|
||||
let scale_factor_z = SCALING_FACTOR / (aabb.max.z - aabb.min.z);
|
||||
|
||||
// Use the minimum scaling factor to maintain proportions
|
||||
*scale = scale_factor_x.min(scale_factor_y).min(scale_factor_z);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
fn process_mesh(
|
||||
mesh: &russimp::mesh::Mesh,
|
||||
scene: &russimp::scene::Scene,
|
||||
dir: &std::path::PathBuf,
|
||||
loaded_textures: &mut Vec<Texture>,
|
||||
transformation: &mut glm::Mat4,
|
||||
aabb: &mut aabb::AABB
|
||||
) -> (Mesh, Vec<Box<dyn std::error::Error>>) {
|
||||
let mut vertices = vec![];
|
||||
let mut indices = vec![];
|
||||
let mut textures = vec![];
|
||||
|
||||
for i in 0..mesh.vertices.len() {
|
||||
let pos = glm::vec4(mesh.vertices[i].x, mesh.vertices[i].y, mesh.vertices[i].z, 1.0);
|
||||
|
||||
let norm = match mesh.normals.len() {
|
||||
0 => glm::vec3(0.0, 0.0, 0.0),
|
||||
_ => glm::vec3(mesh.normals[i].x, mesh.normals[i].y, mesh.normals[i].z),
|
||||
};
|
||||
|
||||
let t = &mesh.texture_coords[0];
|
||||
let tex_coords = match t {
|
||||
Some(tex) => {
|
||||
glm::vec2(tex[i].x, tex[i].y)
|
||||
},
|
||||
None => glm::vec2(0.0, 0.0)
|
||||
};
|
||||
|
||||
let vertex = Vertex::new(pos.truncate(3), norm, tex_coords);
|
||||
|
||||
if vertex.position.x < aabb.min.x {
|
||||
aabb.min.x = vertex.position.x;
|
||||
}
|
||||
|
||||
if vertex.position.y < aabb.min.y {
|
||||
aabb.min.y = vertex.position.y;
|
||||
}
|
||||
|
||||
if vertex.position.z < aabb.min.z {
|
||||
aabb.min.z = vertex.position.z;
|
||||
}
|
||||
|
||||
if vertex.position.x > aabb.max.x {
|
||||
aabb.max.x = vertex.position.x;
|
||||
}
|
||||
|
||||
if vertex.position.y > aabb.max.y {
|
||||
aabb.max.y = vertex.position.y;
|
||||
}
|
||||
|
||||
if vertex.position.z > aabb.max.z {
|
||||
aabb.max.z = vertex.position.z;
|
||||
}
|
||||
|
||||
vertices.push(vertex);
|
||||
}
|
||||
|
||||
for i in 0..mesh.faces.len() {
|
||||
for j in 0..mesh.faces[i].0.len() {
|
||||
indices.push(mesh.faces[i].0[j]);
|
||||
}
|
||||
}
|
||||
|
||||
let mat = &scene.materials[mesh.material_index as usize];
|
||||
|
||||
let material = process_material(mat);
|
||||
|
||||
let (mut found_textures, errs) = load_material_textures(mat, dir, loaded_textures);
|
||||
textures.append(&mut found_textures);
|
||||
|
||||
let mesh = Mesh::new(mesh.name.as_str(), vertices, indices, textures, material, transformation);
|
||||
return (mesh, errs);
|
||||
}
|
||||
|
||||
fn process_material(mat: &russimp::material::Material) -> Material {
|
||||
let mut mat_name = String::from("Default_Mat");
|
||||
let mut ambient = glm::vec3(0.2, 0.2, 0.2);
|
||||
let mut diffuse = glm::vec3(0.7, 0.7, 0.7);
|
||||
let mut specular = glm::vec3(0.1, 0.1, 0.1);
|
||||
let mut shininess = 32.0;
|
||||
|
||||
// TODO: better way of mapping properties
|
||||
// for property in mat.properties.iter() {
|
||||
// match property.key.as_str() {
|
||||
// "$clr.ambient" => {
|
||||
// ambient = match &property.data {
|
||||
// russimp::material::PropertyTypeInfo::FloatArray(a) => {
|
||||
// glm::vec3(a[0], a[1], a[2])
|
||||
// },
|
||||
// _ => panic!("Property should not be this type: {}", property.key)
|
||||
// };
|
||||
// },
|
||||
// "$clr.diffuse" => {
|
||||
// diffuse = match &property.data {
|
||||
// russimp::material::PropertyTypeInfo::FloatArray(a) => {
|
||||
// glm::vec3(a[0], a[1], a[2])
|
||||
// },
|
||||
// _ => panic!("Property should not be this type: {}", property.key)
|
||||
// };
|
||||
// },
|
||||
// "$clr.specular" => {
|
||||
// specular = match &property.data {
|
||||
// russimp::material::PropertyTypeInfo::FloatArray(a) => {
|
||||
// glm::vec3(a[0], a[1], a[2])
|
||||
// },
|
||||
// _ => panic!("Property should not be this type: {}", property.key)
|
||||
// }
|
||||
// }
|
||||
// "?mat.name" => {
|
||||
// mat_name = match &property.data {
|
||||
// russimp::material::PropertyTypeInfo::String(s) => {
|
||||
// s.to_string()
|
||||
// },
|
||||
// _ => panic!("Property should not be this type: {}", property.key)
|
||||
// };
|
||||
// }
|
||||
// "$mat.shininess" => {
|
||||
// shininess = match &property.data {
|
||||
// russimp::material::PropertyTypeInfo::FloatArray(a) => {
|
||||
// a[0]
|
||||
// },
|
||||
// _ => panic!("Property should not be this type: {}", property.key)
|
||||
// };
|
||||
// }
|
||||
// _ => {},
|
||||
// }
|
||||
// }
|
||||
|
||||
Material::new(mat_name, ambient, diffuse, specular, shininess)
|
||||
}
|
||||
|
||||
// TODO: might nuke this cuz we don't need textures I think
|
||||
fn load_material_textures(
|
||||
mat: &russimp::material::Material,
|
||||
dir: &std::path::PathBuf,
|
||||
loaded_textures: &mut Vec<Texture>
|
||||
) -> (Vec<Texture>, Vec<Box<dyn std::error::Error>>) {
|
||||
|
||||
let mut textures = vec![];
|
||||
let mut errors = vec![];
|
||||
|
||||
for (typ, tex) in mat.textures.iter() {
|
||||
if SUPPORTED_TEXTURE_TYPES.contains(typ) {
|
||||
let texture = tex.borrow();
|
||||
let mut skip = false;
|
||||
let tex_filename = &texture.filename;
|
||||
// HACK: fix this
|
||||
if tex_filename.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// println!("texture filename: {}", tex_filename);
|
||||
let path = dir.join(tex_filename);
|
||||
|
||||
for loaded_tex in &mut *loaded_textures {
|
||||
if loaded_tex.path == path {
|
||||
textures.push(loaded_tex.clone());
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !skip {
|
||||
match Texture::new(path, *typ) {
|
||||
Ok(texture) => {
|
||||
loaded_textures.push(texture.clone());
|
||||
textures.push(texture);
|
||||
},
|
||||
Err(e) => {
|
||||
let err = anyhow!("Error loading texture: {}", e);
|
||||
println!("{}", err);
|
||||
errors.push(err.into());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (textures, errors);
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn new(path: &str, state: &mut ui::State) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let scene = russimp::scene::Scene::from_file(path,
|
||||
vec![
|
||||
russimp::scene::PostProcess::Triangulate,
|
||||
russimp::scene::PostProcess::GenerateNormals,
|
||||
russimp::scene::PostProcess::FlipUVs,
|
||||
])
|
||||
.map_err(|e| {
|
||||
let e = match e {
|
||||
russimp::RussimpError::TextureNotFound => anyhow!("Texture not found"),
|
||||
_ => anyhow!("{}", e)
|
||||
};
|
||||
return e;
|
||||
})?;
|
||||
|
||||
let root_node = match &scene.root {
|
||||
Some(root) => root,
|
||||
None => return Err("Scene has no root node".into()),
|
||||
};
|
||||
|
||||
if scene.flags & russimp::sys::AI_SCENE_FLAGS_INCOMPLETE == 1 {
|
||||
return Err("Scene is incomplete")?;
|
||||
}
|
||||
|
||||
let directory = match std::path::Path::new(path).parent() {
|
||||
Some(dir) => dir,
|
||||
None => return Err("Model path has no parent directory".into()),
|
||||
}.to_path_buf();
|
||||
|
||||
let mut loaded_textures = vec![];
|
||||
let mut meshes = vec![];
|
||||
let init_trans_mat = utils::mat_ident();
|
||||
let mut aabb = aabb::AABB{
|
||||
min: glm::vec3(std::f32::MAX, std::f32::MAX, std::f32::MAX),
|
||||
max: glm::vec3(std::f32::MIN, std::f32::MIN, std::f32::MIN),
|
||||
};
|
||||
let mut scale: f32 = 1.0;
|
||||
let errors = process_node(&root_node, &scene, &mut meshes, &directory, &mut loaded_textures, &init_trans_mat, &mut aabb, &mut scale);
|
||||
|
||||
for err in errors {
|
||||
state.logger.history.push(log::LogMessage::new(log::LogLevel::Warning, &err.to_string()));
|
||||
}
|
||||
|
||||
Ok(Model {
|
||||
id: state.get_next_id(),
|
||||
name: root_node.name.to_owned(),
|
||||
aabb_mesh: aabb::AABBMesh::new(&aabb),
|
||||
aabb,
|
||||
scaling_factor: scale,
|
||||
meshes,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn draw(&self, shader: &Shader, draw_aabb: bool) {
|
||||
let center_x = ((self.aabb.max.x / 2.0) + (self.aabb.min.x / 2.0)) * self.scaling_factor;
|
||||
let center_y = ((self.aabb.max.y / 2.0) + (self.aabb.min.y / 2.0)) * self.scaling_factor;
|
||||
let center_z = ((self.aabb.max.z / 2.0) + (self.aabb.min.z / 2.0)) * self.scaling_factor;
|
||||
let pivot = glm::vec3(center_x, center_y, center_z);
|
||||
|
||||
let model_mat = glm::ext::scale(&utils::mat_ident(), glm::vec3(self.scaling_factor, self.scaling_factor, self.scaling_factor));
|
||||
let model_mat = apply_rotation(&model_mat, self.meshes[0].rotation, pivot);
|
||||
let model_mat = glm::ext::translate(&model_mat, glm::vec3(self.meshes[0].position.x, self.meshes[0].position.y, self.meshes[0].position.z));
|
||||
|
||||
|
||||
for mesh in &self.meshes {
|
||||
mesh.draw(shader, self.scaling_factor, pivot);
|
||||
}
|
||||
|
||||
if draw_aabb {
|
||||
self.aabb_mesh.draw(shader, &model_mat);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate(&mut self, xoffset: f32, yoffset: f32) {
|
||||
let rotation = glm::vec3(-yoffset, xoffset, 0.0);
|
||||
for mesh in &mut self.meshes {
|
||||
mesh.rotate(rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
use glad_gl::gl;
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
pub struct Shader {
|
||||
pub program_id: gl::GLuint,
|
||||
}
|
||||
|
||||
impl Shader {
|
||||
pub fn new(vertex_path: &str, frag_path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let mut vertex_shader_source = std::fs::read_to_string(vertex_path).with_context(|| format!("Failed to read vertex shader file: {}", vertex_path))?;
|
||||
vertex_shader_source.push('\0');
|
||||
let vertex_shader_source = std::ffi::CStr::from_bytes_with_nul(vertex_shader_source.as_bytes())?;
|
||||
|
||||
let mut frag_shader_source = std::fs::read_to_string(frag_path).with_context(|| format!("Failed to read frag shader file: {}", frag_path))?;
|
||||
frag_shader_source.push('\0');
|
||||
let frag_shader_source = std::ffi::CStr::from_bytes_with_nul(frag_shader_source.as_bytes())?;
|
||||
|
||||
unsafe {
|
||||
let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER);
|
||||
gl::CreateShader(vertex_shader);
|
||||
gl::ShaderSource(vertex_shader, 1, &vertex_shader_source.as_ptr(), std::ptr::null());
|
||||
gl::CompileShader(vertex_shader);
|
||||
let mut success1 = 0;
|
||||
gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success1);
|
||||
println!("vertex shader {:?} compiled with status: {}",
|
||||
std::path::Path::new(vertex_path).file_name().unwrap(),
|
||||
success1
|
||||
);
|
||||
if success1 == 0 {
|
||||
let info_buf = [0u8; 512];
|
||||
gl::GetShaderInfoLog(vertex_shader as u32, 512, std::ptr::null_mut(), info_buf.as_ptr() as *mut i8);
|
||||
println!("vertex shader info: {}", std::str::from_utf8(&info_buf).unwrap());
|
||||
}
|
||||
|
||||
let frag_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
|
||||
gl::CreateShader(frag_shader);
|
||||
gl::ShaderSource(frag_shader, 1, &frag_shader_source.as_ptr(), std::ptr::null());
|
||||
gl::CompileShader(frag_shader);
|
||||
|
||||
let mut success2 = 0;
|
||||
gl::GetShaderiv(frag_shader, gl::COMPILE_STATUS, &mut success2);
|
||||
println!("frag shader {:?} compiled with status: {}",
|
||||
std::path::Path::new(frag_path).file_name().unwrap(),
|
||||
success2
|
||||
);
|
||||
if success2 == 0 {
|
||||
let info_buf2 = [0u8; 512];
|
||||
gl::GetShaderInfoLog(frag_shader as u32, 512, std::ptr::null_mut(), info_buf2.as_ptr() as *mut i8);
|
||||
println!("frag shader info: {}", std::str::from_utf8(&info_buf2).unwrap());
|
||||
}
|
||||
|
||||
let shader_program = gl::CreateProgram();
|
||||
gl::AttachShader(shader_program, vertex_shader);
|
||||
gl::AttachShader(shader_program, frag_shader);
|
||||
gl::LinkProgram(shader_program);
|
||||
|
||||
gl::DeleteShader(vertex_shader);
|
||||
gl::DeleteShader(frag_shader);
|
||||
|
||||
Ok(Self {
|
||||
program_id: shader_program,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_shader(&self) {
|
||||
unsafe {
|
||||
gl::UseProgram(self.program_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_bool(&self, name: &str, value: bool) {
|
||||
unsafe {
|
||||
let c_str = std::ffi::CString::new(name).unwrap();
|
||||
gl::Uniform1i(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), value as i32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_int(&self, name: &str, value: i32) {
|
||||
let c_str = std::ffi::CString::new(name).unwrap();
|
||||
unsafe {
|
||||
gl::Uniform1i(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_float(&self, name: &str, value: f32) {
|
||||
let c_str = std::ffi::CString::new(name).unwrap();
|
||||
unsafe {
|
||||
gl::Uniform1f(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_float(&self, name: &str) -> f32 {
|
||||
let c_str = std::ffi::CString::new(name).unwrap();
|
||||
let mut value = 0.0;
|
||||
|
||||
unsafe {
|
||||
gl::GetUniformfv(self.program_id, gl::GetUniformLocation(self.program_id, c_str.as_ptr()), &mut value);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mat4fv(&self, name: &str, value: &glm::Mat4) {
|
||||
let c_str = std::ffi::CString::new(name).unwrap();
|
||||
unsafe {
|
||||
gl::UniformMatrix4fv(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), 1, gl::FALSE, value.as_array().as_ptr() as *const f32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_3fv(&self, name: &str, value: glm::Vec3) {
|
||||
let c_str = std::ffi::CString::new(name).unwrap();
|
||||
unsafe {
|
||||
gl::Uniform3fv(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), 1, value.as_array() as *const f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
use std::os::raw::c_char;
|
||||
|
||||
pub struct DockNode {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
impl DockNode {
|
||||
fn new(id: u32) -> Self {
|
||||
Self { id }
|
||||
}
|
||||
|
||||
pub fn is_split(&self) -> bool {
|
||||
unsafe {
|
||||
let node = imgui::sys::igDockBuilderGetNode(self.id);
|
||||
if node.is_null() {
|
||||
false
|
||||
} else {
|
||||
imgui::sys::ImGuiDockNode_IsSplitNode(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Dock window into this dockspace
|
||||
#[doc(alias = "DockBuilder::DockWindow")]
|
||||
pub fn dock_window(&self, window: &str) {
|
||||
let window = imgui::ImString::from(window.to_string());
|
||||
unsafe { imgui::sys::igDockBuilderDockWindow(window.as_ptr(), self.id) }
|
||||
}
|
||||
|
||||
#[doc(alias = "DockBuilder::SplitNode")]
|
||||
pub fn split<D, O>(&self, split_dir: imgui::Direction, size_ratio: f32, dir: D, opposite_dir: O)
|
||||
where
|
||||
D: FnOnce(DockNode),
|
||||
O: FnOnce(DockNode),
|
||||
{
|
||||
if self.is_split() {
|
||||
// Can't split an already split node (need to split the
|
||||
// node within)
|
||||
return;
|
||||
}
|
||||
|
||||
let mut out_id_at_dir: imgui::sys::ImGuiID = 0;
|
||||
let mut out_id_at_opposite_dir: imgui::sys::ImGuiID = 0;
|
||||
unsafe {
|
||||
imgui::sys::igDockBuilderSplitNode(
|
||||
self.id,
|
||||
split_dir as i32,
|
||||
size_ratio,
|
||||
&mut out_id_at_dir,
|
||||
&mut out_id_at_opposite_dir,
|
||||
);
|
||||
}
|
||||
|
||||
dir(DockNode::new(out_id_at_dir));
|
||||
opposite_dir(DockNode::new(out_id_at_opposite_dir));
|
||||
}
|
||||
}
|
||||
|
||||
/// # Docking
|
||||
|
||||
pub struct UiDocking {}
|
||||
|
||||
impl UiDocking {
|
||||
#[doc(alias = "IsWindowDocked")]
|
||||
pub fn is_window_docked(&self) -> bool {
|
||||
unsafe { imgui::sys::igIsWindowDocked() }
|
||||
}
|
||||
/// Create dockspace with given label. Returns a handle to the
|
||||
/// dockspace which can be used to, say, programatically split or
|
||||
/// dock windows into it
|
||||
#[doc(alias = "DockSpace")]
|
||||
pub fn dockspace(&self, label: &str) -> DockNode {
|
||||
let label = imgui::ImString::from(label.to_string());
|
||||
unsafe {
|
||||
let id = imgui::sys::igGetID_Str(label.as_ptr() as *const c_char);
|
||||
imgui::sys::igDockSpace(
|
||||
id,
|
||||
[0.0, 0.0].into(),
|
||||
0,
|
||||
std::ptr::null::<imgui::sys::ImGuiWindowClass>(),
|
||||
);
|
||||
DockNode { id }
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "DockSpaceOverViewport")]
|
||||
pub fn dockspace_over_viewport(&self) {
|
||||
unsafe {
|
||||
imgui::sys::igDockSpaceOverViewport(
|
||||
imgui::sys::igGetMainViewport(),
|
||||
0,
|
||||
std::ptr::null::<imgui::sys::ImGuiWindowClass>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod docking;
|
||||
pub mod ui;
|
||||
+432
@@ -0,0 +1,432 @@
|
||||
use glad_gl::gl;
|
||||
|
||||
use crate::{camera::Camera, model, imgui_glfw_support, imgui_opengl_renderer, mesh, ui, log};
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub struct State {
|
||||
pub active_model: Option<u32>,
|
||||
pub show_console: bool,
|
||||
pub show_help_menu_about: bool,
|
||||
pub is_cursor_captured: bool,
|
||||
pub can_capture_cursor: bool,
|
||||
pub draw_grid: bool,
|
||||
pub draw_aabb: bool,
|
||||
pub wireframe: bool,
|
||||
pub first_frame_drawn: bool,
|
||||
pub camera: Camera,
|
||||
pub objects: Vec<model::Model>,
|
||||
pub viewport_size: [f32; 2],
|
||||
pub logger: log::Log,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active_model: None,
|
||||
show_console: false,
|
||||
first_frame_drawn: false,
|
||||
is_cursor_captured: false,
|
||||
can_capture_cursor: false,
|
||||
draw_grid: true,
|
||||
draw_aabb: false,
|
||||
show_help_menu_about: false,
|
||||
wireframe: false,
|
||||
camera: Camera::new(),
|
||||
objects: vec![],
|
||||
viewport_size: [0.0, 0.0],
|
||||
logger: log::Log::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn get_next_id(&self) -> u32 {
|
||||
let mut id = 0;
|
||||
if let Some(last_model) = self.objects.last() {
|
||||
id = last_model.id + 1;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_imgui(window: &mut glfw::Window) -> (imgui::Context, imgui_glfw_support::GlfwPlatform, imgui_opengl_renderer::Renderer) {
|
||||
let mut imgui = imgui::Context::create();
|
||||
imgui.set_ini_filename(None);
|
||||
imgui.io_mut().config_flags.insert(imgui::ConfigFlags::DOCKING_ENABLE);
|
||||
imgui.io_mut().config_flags.set(imgui::ConfigFlags::NAV_ENABLE_KEYBOARD, true);
|
||||
|
||||
let mut glfw_platform = imgui_glfw_support::GlfwPlatform::init(&mut imgui);
|
||||
glfw_platform.attach_window(
|
||||
imgui.io_mut(),
|
||||
&window,
|
||||
imgui_glfw_support::HiDpiMode::Rounded
|
||||
);
|
||||
|
||||
imgui
|
||||
.fonts()
|
||||
.add_font(&[imgui::FontSource::DefaultFontData { config: None }]);
|
||||
|
||||
imgui.io_mut().font_global_scale = (1.0 / glfw_platform.hidpi_factor()) as f32;
|
||||
|
||||
gl::load(|e| window.get_proc_address(e) as *const std::os::raw::c_void);
|
||||
|
||||
let renderer = imgui_opengl_renderer::Renderer::new(&mut imgui);
|
||||
glfw_platform.set_clipboard_backend(&mut imgui, &window);
|
||||
|
||||
(imgui, glfw_platform, renderer)
|
||||
}
|
||||
|
||||
pub fn import_model(state: &mut State) {
|
||||
let models = match rfd::FileDialog::new()
|
||||
.set_title("Import Model(s)")
|
||||
.set_directory("./")
|
||||
.add_filter("All supported files", &["obj", "OBJ", "stl", "STL"])
|
||||
.add_filter("Wavefront OBJ (.obj)", &["obj", "OBJ"])
|
||||
.add_filter("STL (.stl)", &["stl", "STL"])
|
||||
.pick_files() {
|
||||
Some(m) => m,
|
||||
None => return,
|
||||
};
|
||||
for model_path in &models {
|
||||
let model = model::Model::new(model_path.to_str().unwrap(), state);
|
||||
match model {
|
||||
Ok(m) => {
|
||||
state.active_model = Some(m.id);
|
||||
state.objects.push(m);
|
||||
state.camera.update_position(state.active_model, &state.objects);
|
||||
},
|
||||
Err(e) => {
|
||||
let error = format!("Error loading model \"{}\": {}", model_path.to_str().unwrap(), e);
|
||||
println!("{}", error);
|
||||
|
||||
state.logger.log(&error, log::LogLevel::Error);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_main_menu_bar(ui: &imgui::Ui, state: &mut State, window: &mut glfw::Window, delta_time: f32) {
|
||||
ui.main_menu_bar(|| {
|
||||
ui.menu("File", || {
|
||||
if ui.menu_item_config("Import Model(s)").shortcut("Ctrl+O").build() {
|
||||
import_model(state);
|
||||
}
|
||||
if ui.menu_item_config("Quit").shortcut("Ctrl+Q").build() {
|
||||
window.set_should_close(true);
|
||||
}
|
||||
});
|
||||
ui.menu("View", || {
|
||||
if ui.menu_item_config("Show Grid").selected(state.draw_grid).build() {
|
||||
state.draw_grid = !state.draw_grid;
|
||||
}
|
||||
});
|
||||
ui.menu("Help", || {
|
||||
if ui.menu_item_config("About").selected(state.show_help_menu_about).build() {
|
||||
state.show_help_menu_about = !state.show_help_menu_about;
|
||||
}
|
||||
});
|
||||
let fps = format!("FPS: {:.1}", 1.0 / delta_time);
|
||||
let avail_size = [*ui.content_region_avail().get(0).unwrap() - ui.calc_text_size(&fps)[0], 0.0];
|
||||
ui.dummy(avail_size);
|
||||
ui.text(&fps);
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_transformations(ui: &imgui::Ui, mesh: &mut mesh::Mesh) {
|
||||
imgui::Drag::new("###XPos")
|
||||
.range(f32::NEG_INFINITY, f32::INFINITY)
|
||||
.speed(0.1)
|
||||
.display_format("X: %.3f")
|
||||
.build(ui, &mut mesh.position.x);
|
||||
imgui::Drag::new("###YPos")
|
||||
.range(f32::NEG_INFINITY, f32::INFINITY)
|
||||
.speed(0.1)
|
||||
.display_format("Y: %.3f")
|
||||
.build(ui, &mut mesh.position.y);
|
||||
imgui::Drag::new("###ZPos")
|
||||
.range(f32::NEG_INFINITY, f32::INFINITY)
|
||||
.speed(0.1)
|
||||
.display_format("Z: %.3f")
|
||||
.build(ui, &mut mesh.position.z);
|
||||
imgui::Drag::new("Scale")
|
||||
.range(f32::NEG_INFINITY, f32::INFINITY)
|
||||
.speed(0.1)
|
||||
.display_format("%.7f")
|
||||
.build_array(ui, mesh.scale.as_array_mut());
|
||||
imgui::Drag::new("Rotation")
|
||||
.range(f32::NEG_INFINITY, f32::INFINITY)
|
||||
.speed(1.0)
|
||||
.display_format("%.2f")
|
||||
.build_array(ui, mesh.rotation.as_array_mut());
|
||||
}
|
||||
|
||||
fn draw_mesh_hierarchy(ui: &imgui::Ui, mesh: &mut mesh::Mesh, i: usize) {
|
||||
ui.tree_node_config(format!("{}###{}", mesh.name.as_str(), i)).build(|| {
|
||||
ui.text(format!("Vertices: {}", mesh.vertices.len()));
|
||||
ui.text(format!("Faces: {}", mesh.vertices.len() / 3));
|
||||
ui.text(format!("Textures: {}", mesh.textures.len()));
|
||||
ui.tree_node_config(mesh.material.name.as_str()).build(|| {
|
||||
ui.text(format!("{}", mesh.material));
|
||||
});
|
||||
ui.tree_node_config("Transformations").build(|| {
|
||||
draw_transformations(ui, mesh);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_object_hierarchy(ui: &imgui::Ui, state: &mut State, idx: usize) -> bool {
|
||||
let object = &mut state.objects[idx];
|
||||
if let Some(..) = ui.begin_table_with_sizing("Objects Table", 2, imgui::TableFlags::SIZING_STRETCH_PROP, [0.0, 0.0], 0.0) {
|
||||
ui.table_next_row();
|
||||
ui.table_next_column();
|
||||
ui.tree_node_config(format!("{}###{}", object.name.as_str(), idx))
|
||||
.build(|| {
|
||||
for (j, mesh) in &mut object.meshes.iter_mut().enumerate() {
|
||||
draw_mesh_hierarchy(ui, mesh, j);
|
||||
}
|
||||
});
|
||||
|
||||
ui.table_next_column();
|
||||
if ui.small_button(format!("X###{}-{}", object.name.as_str(), idx)) {
|
||||
let output = format!("Removing object {}", object.name);
|
||||
println!("{}", output);
|
||||
|
||||
state.logger.log(&output, log::LogLevel::Info);
|
||||
return true;
|
||||
}
|
||||
if ui.checkbox("Selected", &mut (Some(state.objects[idx].id) == state.active_model)) {
|
||||
state.active_model = Some(state.objects[idx].id);
|
||||
state.camera.update_position(state.active_model, &state.objects);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn draw_objects_window(ui: &imgui::Ui, state: &mut State) {
|
||||
ui.window("Objects")
|
||||
.size([500.0, 200.0], imgui::Condition::FirstUseEver)
|
||||
.build(|| {
|
||||
let mut i = 0;
|
||||
|
||||
while i < state.objects.len() {
|
||||
if draw_object_hierarchy(ui, state, i) {
|
||||
state.objects.remove(i);
|
||||
state.active_model = state.objects.last().and_then(|o| Some(o.id));
|
||||
state.camera.update_position(state.active_model, &state.objects);
|
||||
continue;
|
||||
}
|
||||
|
||||
i = i + 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn draw_console(ui: &imgui::Ui, state: &mut State) {
|
||||
ui.window("Console")
|
||||
.size([500.0, 200.0], imgui::Condition::FirstUseEver)
|
||||
.build(|| {
|
||||
ui.child_window("###ConsoleHistory")
|
||||
.size([0.0, -27.0])
|
||||
.build(|| {
|
||||
for line in state.logger.history.iter() {
|
||||
let style = ui.push_style_color(imgui::StyleColor::Text, line.level.clone());
|
||||
ui.text_wrapped(line.message.clone());
|
||||
style.pop();
|
||||
}
|
||||
if ui.scroll_y() >= ui.scroll_max_y() {
|
||||
ui.set_scroll_here_y_with_ratio(1.0);
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
if ui.button("Clear") {
|
||||
state.logger.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_initial_docking(ui: &imgui::Ui, state: &mut State) {
|
||||
let flags =
|
||||
// No borders etc for top-level window
|
||||
imgui::WindowFlags::NO_DECORATION | imgui::WindowFlags::NO_MOVE
|
||||
// Show menu bar
|
||||
| imgui::WindowFlags::MENU_BAR
|
||||
// Don't raise window on focus (as it'll clobber floating windows)
|
||||
| imgui::WindowFlags::NO_BRING_TO_FRONT_ON_FOCUS | imgui::WindowFlags::NO_NAV_FOCUS
|
||||
// Don't want the dock area's parent to be dockable!
|
||||
| imgui::WindowFlags::NO_DOCKING
|
||||
;
|
||||
|
||||
let padding = ui.push_style_var(imgui::StyleVar::WindowPadding([0.0, 0.0]));
|
||||
let rounding = ui.push_style_var(imgui::StyleVar::WindowRounding(0.0));
|
||||
|
||||
ui.window("Main Window")
|
||||
.flags(flags)
|
||||
.position([0.0, 0.0], imgui::Condition::Always)
|
||||
.size(ui.io().display_size, imgui::Condition::Always)
|
||||
.build(|| {
|
||||
// Create top-level docking area, needs to be made early (before docked windows)
|
||||
let ui_d = ui::docking::UiDocking {};
|
||||
let space = ui_d.dockspace("MainDockArea");
|
||||
|
||||
// Set up splits, docking windows. This can be done conditionally,
|
||||
// or calling it every time is also mostly fine
|
||||
if !state.first_frame_drawn {
|
||||
space.split(
|
||||
imgui::Direction::Right,
|
||||
0.3,
|
||||
|right| {
|
||||
right.dock_window("Objects");
|
||||
right.dock_window("Console");
|
||||
},
|
||||
|left| {
|
||||
left.dock_window("Viewer");
|
||||
}
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
padding.pop();
|
||||
rounding.pop();
|
||||
}
|
||||
|
||||
fn draw_viewport(ui: &imgui::Ui, state: &mut State, texture: u32) {
|
||||
ui.window("Viewer")
|
||||
.size(ui.content_region_avail(), imgui::Condition::FirstUseEver)
|
||||
.no_decoration()
|
||||
.resizable(true)
|
||||
.build(|| {
|
||||
let mut tex_size = ui.content_region_avail();
|
||||
tex_size[1] -= 25.0;
|
||||
state.viewport_size = tex_size;
|
||||
|
||||
if ui.button("Reset Camera") {
|
||||
state.camera.update_position(state.active_model, &state.objects);
|
||||
}
|
||||
ui.same_line();
|
||||
if ui.button("Capture Scene") {
|
||||
let now = std::time::Instant::now();
|
||||
let mut w = 0;
|
||||
let mut h = 0;
|
||||
|
||||
unsafe {
|
||||
gl::GetTextureLevelParameteriv(texture, 0, gl::TEXTURE_WIDTH, &mut w);
|
||||
gl::GetTextureLevelParameteriv(texture, 0, gl::TEXTURE_HEIGHT, &mut h);
|
||||
}
|
||||
|
||||
let mut pixels = vec![0u8; (w * h * 4) as usize];
|
||||
|
||||
unsafe {
|
||||
gl::GetTextureImage(texture, 0, gl::RGBA, gl::UNSIGNED_BYTE, (w * h * 4) as i32, pixels.as_mut_ptr() as *mut std::ffi::c_void);
|
||||
}
|
||||
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Current time to not be before the UNIX epoch");
|
||||
let file_name = format!("capture-{}.png", timestamp.as_secs());
|
||||
let save_path = std::path::Path::new(file_name.as_str());
|
||||
let capture = image::ImageBuffer::<image::Rgba<u8>, _>::from_raw(w as u32, h as u32, pixels).unwrap();
|
||||
let capture = image::DynamicImage::ImageRgba8(capture);
|
||||
let capture = capture.flipv();
|
||||
let capture = capture.resize_exact(tex_size[0] as u32, tex_size[1] as u32, image::imageops::FilterType::Gaussian);
|
||||
let _ = capture.save(save_path);
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
state.logger.log(
|
||||
format!("Scene capture saved to: {} successfully",
|
||||
save_path
|
||||
.canonicalize()
|
||||
.expect("Capture path to be canonicalized")
|
||||
.to_str()
|
||||
.expect("Capture path to be valid unicode"))
|
||||
.as_str(),
|
||||
log::LogLevel::Info);
|
||||
|
||||
state.logger.log(
|
||||
format!("Scene capture took: {}ms",
|
||||
elapsed.as_millis())
|
||||
.as_str(),
|
||||
log::LogLevel::Debug);
|
||||
|
||||
unsafe {
|
||||
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
||||
}
|
||||
}
|
||||
ui.same_line();
|
||||
ui.checkbox("Wireframe", &mut state.wireframe);
|
||||
ui.same_line();
|
||||
ui.checkbox("Bounding box", &mut state.draw_aabb);
|
||||
ui.same_line();
|
||||
ui.set_next_item_width(200.0);
|
||||
imgui::Drag::new("Camera Speed")
|
||||
.range(1.0, 10000.0)
|
||||
.speed(1.0)
|
||||
.display_format("%.3f")
|
||||
.build(ui, &mut state.camera.speed);
|
||||
imgui::Image::new(imgui::TextureId::new(texture.try_into().unwrap()), tex_size)
|
||||
// flip the image vertically
|
||||
.uv0([0.0, 1.0])
|
||||
.uv1([1.0, 0.0])
|
||||
.build(ui);
|
||||
|
||||
// only allow capturing the cursor if the mouse is over the viewport
|
||||
state.can_capture_cursor = ui.is_item_hovered();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw_ui(
|
||||
imgui: &mut imgui::Context,
|
||||
renderer: &imgui_opengl_renderer::Renderer,
|
||||
glfw_platform: &imgui_glfw_support::GlfwPlatform,
|
||||
window: &mut glfw::Window,
|
||||
state: &mut State,
|
||||
delta_time: f32,
|
||||
last_cursor: &mut Option<imgui::MouseCursor>,
|
||||
scene_fb_texture: u32,
|
||||
) {
|
||||
glfw_platform.prepare_frame(imgui.io_mut(), window).expect("Failed to prepare imgui frame");
|
||||
|
||||
let display_size = imgui.io().display_size;
|
||||
let ui = imgui.new_frame();
|
||||
create_initial_docking(ui, state);
|
||||
|
||||
draw_main_menu_bar(ui, state, window, delta_time);
|
||||
|
||||
if state.show_help_menu_about {
|
||||
ui.window("About")
|
||||
.size([300.0, 150.0], imgui::Condition::FirstUseEver)
|
||||
.opened(&mut state.show_help_menu_about)
|
||||
.position([display_size[0] / 2.0, display_size[1] / 2.0], imgui::Condition::Always)
|
||||
.position_pivot([0.5, 0.5])
|
||||
.build(|| {
|
||||
ui.text("3dobs - 3D Object Browser");
|
||||
ui.text(format!("Version: {}-{}", env!("CARGO_PKG_VERSION"), option_env!("GIT_HASH").unwrap_or("unknown")));
|
||||
ui.text(format!("{}", env!("CARGO_PKG_DESCRIPTION")));
|
||||
ui.spacing();
|
||||
ui.spacing();
|
||||
ui.text(format!("Made by: {}", env!("CARGO_PKG_AUTHORS")));
|
||||
});
|
||||
}
|
||||
|
||||
draw_viewport(ui, state, scene_fb_texture);
|
||||
draw_objects_window(ui, state);
|
||||
draw_console(ui, state);
|
||||
|
||||
ui.end_frame_early();
|
||||
|
||||
if !state.is_cursor_captured {
|
||||
let cursor = ui.mouse_cursor();
|
||||
if *last_cursor != cursor {
|
||||
*last_cursor = cursor;
|
||||
glfw_platform.prepare_render(&ui, window);
|
||||
}
|
||||
}
|
||||
|
||||
imgui.update_platform_windows();
|
||||
|
||||
renderer.render(imgui);
|
||||
state.first_frame_drawn = true;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use glad_gl::gl;
|
||||
use glm;
|
||||
use anyhow::{Result, Context};
|
||||
|
||||
pub fn load_texture(path: &str) -> Result<u32> {
|
||||
let tex = image::io::Reader::open(path)
|
||||
.with_context(|| format!("Failed to open texture file: {}", path))?
|
||||
.decode()
|
||||
.with_context(|| format!("Failed to decode texture: {}", path))?;
|
||||
|
||||
let mut texture_id: u32 = 0;
|
||||
let format = match tex.color().channel_count() {
|
||||
1 => gl::RED,
|
||||
2 => gl::RG,
|
||||
3 => gl::RGB,
|
||||
4 => gl::RGBA,
|
||||
_ => panic!("Unknown image format")
|
||||
};
|
||||
|
||||
unsafe {
|
||||
gl::GenTextures(1, &mut texture_id);
|
||||
gl::BindTexture(gl::TEXTURE_2D, texture_id);
|
||||
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR_MIPMAP_LINEAR as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
|
||||
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
|
||||
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
|
||||
|
||||
gl::TexImage2D(gl::TEXTURE_2D, 0, format as i32, tex.width() as i32, tex.height() as i32, 0, format, gl::UNSIGNED_BYTE, tex.as_bytes().as_ptr() as *const std::ffi::c_void);
|
||||
gl::GenerateMipmap(gl::TEXTURE_2D);
|
||||
}
|
||||
|
||||
Ok(texture_id)
|
||||
}
|
||||
|
||||
pub fn mat_ident() -> glm::Mat4 {
|
||||
glm::mat4(
|
||||
1., 0., 0., 0.,
|
||||
0., 1., 0., 0.,
|
||||
0., 0., 1., 0.,
|
||||
0., 0., 0., 1.
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user