88 Commits

Author SHA1 Message Date
38021852d0 Made inventory windows draggable 2021-12-22 01:41:55 +03:00
9c85164ed1 Fixed inventory hiding and improved HUD event handling 2021-12-21 23:28:19 +03:00
ce573b51ce Merge branch 'master' into add-items 2021-12-21 17:12:45 +03:00
92f9639c4b Improved input event handling
- Added Component.passInputToChildren()
- Input events can now be processed in parallel or recursively
2021-12-21 00:31:35 +03:00
f350467942 Removed jCenter(); formatted build.gradle
- Fixed #23
- Fixed whitespace in build.gradle
2021-12-21 00:13:04 +03:00
a028f6f3c7 Merge branch 'master' into add-items
If I ever become a supervillain, I will torture my victims by forcing
them to merge branches in Progressia

Does not work properly, waiting for an additional patch from master
2021-12-20 23:36:55 +03:00
23b0af1731 Update log4j dependency to version 2.17.0 😡
Fix for CVE-2021-45105
2021-12-19 23:46:30 +03:00
0389f97e59 Update log4j dependency to version 2.16.0
Fix for CVE-2021-45046
2021-12-16 18:09:37 +03:00
706800218c Merge branch 'overhaul-input'
Summary of changes:
- Refactored input handling
- Added noclip/freecam mode for debugging
- Added drag events
- Removed useless info from debug layer
- Movement controls now work in menus (feature? bug? not sure)
2021-12-16 15:17:08 +03:00
ca2c7b58d8 Added drag events 2021-12-16 15:12:49 +03:00
359879b0fe Completed input refactoring
- Moved everything related to controls into controls package
- Moved controls related to placing/breaking stuff into
InteractionControls
- Localizer now provides access to the list of languages
- Fixed a random bug in TestMusicPlayer because why not amirite
2021-12-15 00:20:50 +03:00
a06d8ee056 Fixed temporary player entity data transfer 2021-12-13 20:49:32 +03:00
c8138faabe Refactored camera controls and added noclip mode
- Refactored camera controls into a separate class
- Added noclip mode
  - Toggle with V
  - Simply uncouples camera from character; does not change interactions
or load chunks!
  - Implemented sloppily, expect bugs in the future
- EntityData's .equals() is now equivalent to == and .hashCode() hashes
entity ID only
- Collision can now be disabled for an object by setting its
CollisionModel to null
2021-12-13 19:36:26 +03:00
49d283e7a3 Refactored F3 layer
- Renamed from LayerTestGUI to LayerDebug
- Many of the displays have been removed
- Fullscreen and VSync displays have been merged with FPS display
- Updates are automatic
2021-12-13 17:03:23 +03:00
c93f0df30d Refactored movement controls
- Movement control code is now in its own class
- Movement direction controls are now more bug-resistant
  - No longer event-driven; uses InputTracker instead
  - Temporarily? function with menus open
- Other movement controls are now registered local Controls
- Removed flying and sprinting indicators from F3 layer
- TestPlayerControls is now a singleton
  - resetInstance() now resets the instance's fields
2021-12-13 11:06:28 +03:00
75ea7baf9c Update junit dependency to version 4.13.2
Fix for CVE-2020-15250
2021-12-12 15:41:10 +03:00
c4a9a17c7c Update log4j dependency to version 2.15.0 2021-12-10 11:05:30 +03:00
b4ff5114bd Refactored input event pipeline
- Merged Input functionality into InputEvent; removed Input
  - InputEvents can now be consumed directly
  - Removed Input.Target; functionality moved into InputBus
- Refactored GUI input event handling
  - Optimized and reduced recursion by unrolling recursion
  - All input events are now delivered to all components
    - Filtering occurs at listener level
- Refactored InputBus
  - Listeners may override former Input.Target logic
  - Improved registration method
  - Added exception handling
- Improved KeyMatcher
  - Removed builder in favour of copying with modifications
  - Added String parsing
  - Added KeyMatcher.matchesIgnoringAction()
  - Added integration with InputBus
- Renamed Component.{add,remove}Listener methods about InputListeners to
Component.{add,remove}InputListener to avoid confusion
2021-12-08 18:06:30 +03:00
9b67897896 Moved window layout into a separate file 2021-12-07 14:40:31 +03:00
efc6a8ecd0 Hastily added pine trees and hazel bushes
- Added ShapePrototype wrapper for ShapePart batch creation
- Added pine trees and hazel bushes
  - Leaves with custom block models
  - Improved tree generator
2021-11-19 21:26:04 +03:00
3641c4130b Rewrote ItemContainers from scratch
- ItemContainers have been entirely rewritten
  - ItemSlots are now wrappers, not owners
- Added InventoryOwners
  - Not yet used
- Added ItemDataWithContainers
- Added some basic windowing functionality to WindowedHUD
2021-11-16 23:49:17 +03:00
4b10dc82ed Improved formatting and phrasing 2021-10-31 16:19:21 +03:00
be6203719a Attempted to make slots dynamic and failed 2021-10-31 15:26:48 +03:00
c04894a0c9 Finally made this
I think its pretty good, but I cant think very well rn.
2021-10-24 20:09:44 -04:00
9885a1ca42 Visual improvements
- Added a custom font for item amount displays
- Added an indicator for openable items
- Changed open item indicator to be more obvious
- Fixed ExponentAnimation overshooting target during FPS spikes
2021-10-17 15:10:24 +03:00
c6e6dc6851 Added the concept of inventories
I'll end myself if I spend any more time on this neverending commit
2021-10-17 14:02:54 +03:00
28b19c8f35 Added more flowers and more grass types, fixed cross tile render
- Added Clover, Daisy, Dandelion, Geranium, Knapweed, Yellow Pea,
Bluegrass
- Renamed grass herbs and flat flowers
- Cross tile renderer now uses a kostyl that forces face normals to be
oriented vertically
- Refactored flower generation
- Added a coherent error message when a texture could not be found
2021-09-22 23:48:38 +03:00
eb5aa59941 Removed plain sand block and tile; incremented version
To be honest, I kinda forgot to increment version in last commit, so I
decided to make a pointless change to justify an entire commit =\
2021-09-11 21:12:42 +03:00
409bbdb680 Merge branch 'save-world' 2021-09-11 19:42:36 +03:00
0c41350ae7 Added file identification header
-All files now start with a 16 byte header consisting of "PROG" followed
by the integer position of the region in region coordinates.
	-regionCoords is passed through some functions to allow for this to be
confirmed.
2021-09-10 22:19:18 -04:00
a633c8324e Added Reset World button and fixed some translations 2021-09-11 00:47:28 +03:00
6b33f231b4 Moved region implementation to .server.world.io.region 2021-09-10 23:59:03 +03:00
e2308b825d Removed warnings and formatted code 2021-09-10 23:31:14 +03:00
30464febf6 Added item containers
- Added ItemDataContainer. It is an item that has a container
  - Added Test:CardboardBackpack
- Added back inventory GUIs
  - Now in window form!
    - *not interactive (don't sue us)
- Hand background icons now only render when an inventory is open
- Removed intrinsic player inventory because we're hardcore
2021-09-10 23:11:15 +03:00
e0f6a08740 Bug fixes
-Now offset technically starts at 1, so any chunks with offset 0 are
ignored.
-Reduced number of unused chunks, storage efficiency is at about 99% (if
null sectors are counted as useful)
2021-09-10 14:14:18 -04:00
2820e01974 Finished Partition Logic
Note: it still does waste a lot of space, I will work on that next
-Added back the confirmHeaderHealth logic
-Checks to make sure it will not overwrite important chunks
	-Uses PartitionLink chunks to move to a different part of the file
-Added allocateEmptySector() to allow for the file size to be increased
without moving the origin point of the chunk.
2021-09-10 13:51:37 -04:00
782b3ef553 Hotfixed custom renderables and Hider
Man, flat render code is a huge mess
2021-09-10 20:38:42 +03:00
46bcb85044 Region File better????
-Moved most of the file accessing of Region to RegionFile
-Disabled most of the header check except the length check(it will be
back soon)
-Max chunk size arbitrarily raised to 4MiB because I wanted sectors
longer than 16B
-Sectors now have a mandatory 1B header that identifies it
	-0 is a null sector, it ends every chunk
	-1 is a normal data sector, it has a "parity" byte that makes sure it
is reading chunks in linear order(fun fact: it isnt at the moment)
	-2 is a jump to a different location. this isnt implemented well yet
	-3 will be a "bulk data" sector. Multiple chunks with identical data
can point here. Probably only useful when it is easily identifiable,
like multiple chunks being one entire block, like air.
-Removed all chunk length references as I think they do not make sense
when it can use different sectors for non-data purposes.
2021-09-09 19:58:44 -04:00
05b1c73fbc Rewrote inventory GUI code to be more coherent. WIP
- Added an equipment slot
- Added equipment slot displays
- Added HUDManager and refactored layer management
- Moved most files from .test.inv to .client.graphics.world.hud
- Added component hider
  - Which is broken because z-index
    - Why do I use depth stencils in flat layers again?
- Refactored KeyMatcher: removed Builders, added action selection
2021-09-10 00:27:12 +03:00
c5dfe3d0b7 Saving Modifications
-Safer saving, it waits for the file to stop being used to close(still
has holes)
-If a chunk is too large, it is moved to the end of the file.
	-Calculates the efficiency of each region file in confirmHeaderHealth
-Fixed import warning
2021-09-09 16:52:55 -04:00
bd6442318d Added hand icons by IvanZ 2021-09-05 21:04:48 +03:00
6cd812f7c3 Added player species and refactored everything hands-related
- Players species is now a thing
  - Currently defines hands, equipment slots (unused), collision model
and appearance
  - EntityDataPlayer now contains a species-provided datalet
  - EntityRenderPlayer now searches for a species-defined delegate to
render players
- Hand count can now be arbitrary
- Split ItemContainerSingle into ItemContainerHand and
ItemContainerEquipment
- Added HUDTextures class
2021-09-05 11:16:54 +03:00
73ee339dcc Working toward player species? I think?
- Left/right hand can now be permanently switched by tapping ctrl
- Moved LayerHUD into .hud subpackage
  - Extracted PermanentHUD out of LayerHUD
- Fixed the issuer of NewLocalEntityEvent
2021-09-04 17:12:54 +03:00
4749be6c60 Added hand item indicators, placeholder art included 2021-09-02 22:54:25 +03:00
a85fc27f8b Added _UNFINISHED_ water, beaches and mantle
- Refactored terrain generation
- Added PiecewiseLinearFunction
- Added some placeholder content
  - Added Test:Water, the solid, opaque water
  - Added beaches
  - Added Test:Mantle
- Tweaked rock distribution parameters
2021-08-31 17:27:08 +03:00
a4b731e8a5 Added mass/volume limit enforcement and displays
- Also added Test:RedGraniteCobblestone
2021-08-30 23:36:13 +03:00
e7d0e8fe40 Added a visual indicator of selected hand and added scroll actions 2021-08-30 21:33:01 +03:00
711e4a2bb4 Added formatting templates guide for Intellij IDEA 2021-08-30 19:51:28 +03:00
0100c8791d Added player saving and loading from disk 2021-08-30 18:23:42 +03:00
040a4f7fd7 Added stack merging, RMB actions, Sticks and fixed an unholy bug
- Added Test:Stick
- When left-clicking, stacks that can merge will
- Added right-clicking behavior straight from Minecraft
- hooooooly sh*t man StatefulObject.equals was totally broken omfg how
tf i did notice :o
2021-08-30 18:09:08 +03:00
e967a64401 More Compact region files
-Sector length increased to a short, the minimum sector size is now 16
bytes
2021-08-30 10:52:56 -04:00
d2ffe1fe0e Fixed the bug that opfromthestart found, reloading now works 2021-08-29 12:04:02 +03:00
f4300558d5 Formatted and broke the saving mechanism. I'm too tired to bugfix
- Refactored and formatted TestWorldDiskIO
- Removed HashableVec3i
- Added Coordinates methods for custom bit count
- Properly reverted commit 98250cd
- Known bugs:
  - Server shutdown close()s regions too early
  - Re-entering a world does not show saved changes
2021-08-29 02:08:19 +03:00
cd16334db8 Moved TestWorldDiskIO to a subpackage and introduced some abstractions 2021-08-28 23:31:50 +03:00
41a2909f7c Reverted last two commits because no one wants to fix the bugs 2021-08-28 21:31:34 +03:00
a222ea8f67 Fixed multithread chunk IO 2021-08-28 21:14:35 +03:00
b3ac7b6afe Item stack size is now defined by ItemSlot, not ItemData 2021-08-28 21:07:08 +03:00
2fe84dc59e Working on item management 2021-08-28 17:50:45 +03:00
00ea4a6281 Added player hand containers
- Added EntityDataPlayer.{left,right}Hand
  - No in-world effect yet
- List-like functionality of ItemContainer extracted into
ItemContainerMixed
- Reworked implementation of Inventory screen
- Added optional arguments for Group
- Added Components.center()
2021-08-28 00:26:35 +03:00
98250cd524 Some changes for efficiency(not yet)
-Tried to use threads/executors in ChunkRequestDaemon, it just hangs.
-Added isEmpty and isOpaque attributes to DefaultChunkData (should this
just be in ChunkData?)
	-Added compute and getter functions to access(for everything after
loading)
	-Doesn't render empty chunks(not yet used)
-Using format 65537 allows the empty and opaqueness to be saved. They do
not do anything yet and there is no way to set them yet
	-Added loadRegionX and ".progressia_chunkx" file
-removed formats 0 and 1, which use individual chunk files.
2021-08-27 16:59:05 -04:00
f74c731a3d Fixed GUI buttons and a rare crash on startup 2021-08-27 12:20:58 +03:00
f186fc602d Added ItemData, ItemRender and related stuff
- Added ItemData, ItemSlot, ItemContainer
- Added ItemRender
- Added a player inventory
- Added temporary inventory display
- Font now has a scale setting
2021-08-26 18:56:32 +03:00
98c383bf7d Fixed a stupid typo in previous commit 2021-08-26 12:11:21 +03:00
dd80df2cf2 Fixed a GUI reassembly issue and added cursor disabling suppression
- Buttons no longer reassemble every frame
- Label.setFont() no longer requests reassembly
- Component.reassembleAt now actually works
- Using a very long flag, cursor capturing can be disabled to facilitate
GUI debugging
2021-08-26 12:09:22 +03:00
9dcb3a7748 -Better HashableVec3i compare
-Added RandomFileMapped class
	-Made it a wrapper for RandomAccessFile and contains maps of locations
and lengths
-Added confirmHeaderHealth method to made sure the file isnt corrupted
-Changed everything from MappedByteBuffers to RandomFileMappeds.
2021-08-25 16:15:38 -04:00
1727a2a4a1 Added Object fields to StatefulObjects and added some utility methods
- Added ObjectStateField
- Added WorldGenericContextRO.findClosestEntity, .forEachEntity
- Added VectorUtil.lookAt, .distance{,Sq}
2021-08-25 16:47:56 +03:00
20fb8f0597 Fixed missing popTrasnform in Statie render 2021-08-24 15:12:35 +03:00
1d28f32865 Implemented entity spawning and despawning and changed some stuff
- Non-player entities can now be added and removed properly
  - WorldLogic.spawnEntity can be used to add entity and create an
entity ID
- Statie is back, more beautiful than ever!
  - Place Test:StatieSpawner block and wait to make her spawn
- TPS display now features a visual tick indicator
- Updated Fern texture
2021-08-24 13:59:28 +03:00
0ccc108ddd Merge branch 'save-world'
Third time's the charm!
2021-08-24 01:38:22 +03:00
c7e7d3bdac Added ferns, fixed herb render and cleaned up TestContent
- Added Test:Fern
- Herbs are no longer stretched horizontally
- Formatted and cleaned up TestContent
2021-08-23 18:36:15 +03:00
62729f5873 Added packet buffering and fix crash when placing flowers on leaves
- Packets are now buffered before processing to reduce stack depth
- Attempts to place tiles on invalid locations get rejected earlier
2021-08-23 17:37:25 +03:00
84864f8947 Added more grass variants
- Added flat grass tiles with varying opaqueness
- Renamed Test:Grass to Test:GrassOpaque
- Added Chernozem
2021-08-23 15:18:37 +03:00
d01ef3654f Added grasses and flowers
- Added CROSimple to optimize simple non-Surface textures
- Added TileRenderCross
- Added Low, Medium and Tall grass
- Added Blue, Purple and White flat flowers
- Added Bushes
- Added tiny Dandelions and tiny Lavanders
- Improved grass and log textures
2021-08-22 22:18:42 +03:00
fae09edb16 Fixes n Speed n Stuff
-Better comparison for HashableVec3i
-Changed RandomAccessFile to MappedByteBuffer
-natFromInt now works properly
-better region selection
-Changes to make different strategies work
2021-08-19 10:47:16 -04:00
e4ced6507e Hash Things
-Removed unused imports
-Using HashMap to keep track of RandomAccessFile instances
-Using those instances to do stuff
-Made new HashableVec3i class that allows for hashing of 3d vectors.
2021-08-18 18:50:48 -04:00
b7dcbb0f30 Fixing stuff
-Got rid of some annoying logging
-Format
2021-08-18 13:21:54 -04:00
9c26418354 Well, it works now ig
-Better file lengthening
-Made temp files
    -Exchanged temp files for ByteArrayIn/OutputBuffers
-Fixed loadRegion to actually read sector sizes
2021-08-18 13:15:30 -04:00
6891d3a095 Trying to get it to work
-Uses many streams to keep track of things
-Fixed some bad things, still are a ton.
2021-08-06 12:56:21 -04:00
8167c40f64 IDK these errors are weird
-Changed the loading to fit the byte changes
2021-08-05 14:12:37 -04:00
8bc23acb61 idk changes so I can show OLEGSHA
-changed number of index bytes from 4 to 3
-exposed some variables for help with debugger
2021-08-05 13:39:42 -04:00
254faca0a5 Better deletion
-Makes new TestPlayerControls instance each time you enter a world
-Some dynamic sizing of chunk data within region files.
2021-08-05 12:29:29 -04:00
0c66f1751e Made outline of region file
-Improved loading screen loading(so it doesnt crash)
-Implemented region file related things, but it doesnt quite work
    -Uses file header to try to look up location of data
    -Writes the data at the end of the file(I need info to how much it writes)
2021-08-04 19:17:49 -04:00
c88dea6030 Fixing Bugs mostly
-Cleaning up imports
-Better error detection
-Actual thread deletion(still needs a bit of work to delete all conected objects)
-Starting making format file and inplementing it
2021-08-04 16:57:21 -04:00
6521cb5749 Better Listeners
-Specific listeners for the start and stop of loading
-Better(?) layer deletion after exiting a world
-Actual server thread shutdown
2021-08-04 12:52:37 -04:00
94db44e443 Im tired i need sleep
-Added listeners for saving and loading chunks
-Made loading screens for between title and game(they dont work yet)
-Added localized text(some)
-Safeish deletion and saving of chunks
    -It still keeps them in memory I think so this needs work too
2021-08-03 22:42:46 -04:00
53f72b068a Merge branch 'title-screen' into save-world 2021-08-03 20:08:39 -04:00
a9ca5f6b17 Everything Excpeti polishing and options
-Added LayerTitle class that is the title menu
-Edited the launcher and proxy so it only starts the game when it needs to
-Made buttons work with MutableString objects
2021-08-03 19:42:04 -04:00
4ab7cb738e Testing some stuff
-Made functions to convert integers into natural numbers (cause why not)
-Ideas/plans of dynamic/custom region file sizes
2021-08-03 17:53:49 -04:00
584 changed files with 16944 additions and 4568 deletions

View File

@ -40,7 +40,6 @@ compileJava {
repositories {
mavenCentral()
jcenter()
/*
* Specify Windcorp Maven repository
@ -66,12 +65,12 @@ dependencies {
// Log4j
// A logging library
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3'
implementation 'org.apache.logging.log4j:log4j-api:2.17.0'
implementation 'org.apache.logging.log4j:log4j-core:2.17.0'
// JUnit
// A unit-testing library
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13.2'
// See LWJGL dependencies below
}
@ -173,13 +172,20 @@ compileJava.mustRunAfter addNativeDependencies // Make sure runtimeOnly has not
task requestLinuxDependencies {
description 'Adds linux, linux-arm64 and linux-arm32 native libraries to built artifacts.'
doFirst {
project.ext.platforms.addAll(['natives-linux', 'natives-linux-arm64', 'natives-linux-arm32'])
project.ext.platforms.addAll([
'natives-linux',
'natives-linux-arm64',
'natives-linux-arm32'
])
}
}
task requestWindowsDependencies {
description 'Adds windows and windows-x86 native libraries to built artifacts.'
doFirst {
project.ext.platforms.addAll(['natives-windows', 'natives-windows-x86'])
project.ext.platforms.addAll([
'natives-windows',
'natives-windows-x86'
])
}
}
task requestMacOSDependencies {

12
docs/ProgressiaRegion.md Normal file
View File

@ -0,0 +1,12 @@
# Progressia Region File
## Description
The `.progressia_region` file type is used for all region files in the game Progressia. Each region file contains a cube of 16x16x16 chunks.
## Header
The header of the file is 16 400 bytes. Every file starts with the string byte sequence `\x50\x52\x4F\x47` (UTF-8 for `PROG`), followed with the three integer values of the region position, in region coordinates. After this, there is exactly 16KiB of space in the header, which stores the offsets to the chunks' data. This space holds an integer, 4 bytes, for each chunk in the region. The integer value starts at 0 for every chunk, and is changed to the location of the chunk data once created. These are indexed in order by flattening the 3D in-chunk coordinates into a number between 0 and 4095 according to the formula `offset = 256*x+ 16*y + z` for chunk at (x, y, z). To convert from this offset value to the offset in bytes, use `byte_offset = 16400 + 64*n`.
## Sectors
Sectors are what is used to store chunk data, and are not linear, but are followed until they reach an ending block. Each is 64 bytes, which is used in the header section to find the byte offset. Each sector starts with a identification byte, followed by the sector data.
0. Ending - This sector is empty, and marks the end of the chunk data (This may change in the future.
1. Data - This sector contains chunk data for a single chunk. The second byte of this sector contains a counter byte, which is a form of "checksum" to make sure that the program is reading the proper sectors in order. This starts at 0 for the first data sector and increments by one for each new data sector.
2. Partition Link - This sector only contains another offset value, which is where the next sector is. This allows for infinite chunk size, avoiding "chunk dupes" as were present in Minecraft without reverting any chunks.
3. Bulk Data - These would be used for many chunks in the same region that contain exactly the same data, e.g. all solid chunks underground. Exists so the program knows not to overwrite them, and just make new chunk data if modified. Not yet implemented.

View File

@ -42,3 +42,16 @@ Run configurations are used by Intellij IDEA to specify how a project must be ru
9. Click 'Apply' to save changes.
Step 8 is required to specify that the game must run in some directory other than the project root, which is the default in Intellij IDEA.
### Applying formatting templates
Windcorp's Progressia repository is formatted with a style defined for Eclipse IDE (sic) in
`templates_and_presets/eclipse_ide`.
Please apply these templates to the project to automatically format the source in a similar fashion.
1. In project context menu, click 'File->Properties'. (`Ctrl+Alt+S`)
2. In 'Editor' > 'Code Style' > 'Java', press gear icon, then click 'Import Scheme' > 'Eclipse code style'
3. In Scheme select 'Project'
4. Open the file `templates_and_presets/eclipse_ide/FormatterProfile.xml` in 'Select Path'.
5. Inside 'Import Scheme' widow click 'Current Scheme' check box after press OK

View File

@ -72,9 +72,9 @@ public class OpenSimplex2S {
}
/**
* 2D SuperSimplex noise, with Y pointing down the main diagonal. Might be
* better for a 2D sandbox style game, where Y is vertical. Probably
* slightly less optimal for heightmaps or continent maps.
* 2D SuperSimplex noise, with Y pointing down the main diagonal.
* Might be better for a 2D sandbox style game, where Y is vertical.
* Probably slightly less optimal for heightmaps or continent maps.
*/
public double noise2_XBeforeY(double x, double y) {
@ -86,8 +86,8 @@ public class OpenSimplex2S {
}
/**
* 2D SuperSimplex noise base. Lookup table implementation inspired by
* DigitalShadow.
* 2D SuperSimplex noise base.
* Lookup table implementation inspired by DigitalShadow.
*/
private double noise2_Base(double xs, double ys) {
double value = 0;
@ -98,7 +98,10 @@ public class OpenSimplex2S {
// Index to point list
int a = (int)(xsi + ysi);
int index = (a << 2) | (int) (xsi - ysi / 2 + 1 - a / 2.0) << 3 | (int) (ysi - xsi / 2 + 1 - a / 2.0) << 4;
int index =
(a << 2) |
(int)(xsi - ysi / 2 + 1 - a / 2.0) << 3 |
(int)(ysi - xsi / 2 + 1 - a / 2.0) << 4;
double ssi = (xsi + ysi) * -0.211324865405187;
double xi = xsi + ssi, yi = ysi + ssi;
@ -109,8 +112,7 @@ public class OpenSimplex2S {
double dx = xi + c.dx, dy = yi + c.dy;
double attn = 2.0 / 3.0 - dx * dx - dy * dy;
if (attn <= 0)
continue;
if (attn <= 0) continue;
int pxm = (xsb + c.xsv) & PMASK, pym = (ysb + c.ysv) & PMASK;
Grad2 grad = permGrad2[perm[pxm] ^ pym];
@ -124,16 +126,15 @@ public class OpenSimplex2S {
}
/**
* 3D Re-oriented 8-point BCC noise, classic orientation Proper substitute
* for what 3D SuperSimplex would be, in light of Forbidden Formulae. Use
* noise3_XYBeforeZ or noise3_XZBeforeY instead, wherever appropriate.
* 3D Re-oriented 8-point BCC noise, classic orientation
* Proper substitute for what 3D SuperSimplex would be,
* in light of Forbidden Formulae.
* Use noise3_XYBeforeZ or noise3_XZBeforeY instead, wherever appropriate.
*/
public double noise3_Classic(double x, double y, double z) {
// Re-orient the cubic lattices via rotation, to produce the expected
// look on cardinal planar slices.
// If texturing objects that don't tend to have cardinal plane faces,
// you could even remove this.
// Re-orient the cubic lattices via rotation, to produce the expected look on cardinal planar slices.
// If texturing objects that don't tend to have cardinal plane faces, you could even remove this.
// Orthonormal rotation. Not a skew transform.
double r = (2.0 / 3.0) * (x + y + z);
double xr = r - x, yr = r - y, zr = r - z;
@ -144,17 +145,15 @@ public class OpenSimplex2S {
/**
* 3D Re-oriented 8-point BCC noise, with better visual isotropy in (X, Y).
* Recommended for 3D terrain and time-varied animations. The Z coordinate
* should always be the "different" coordinate in your use case. If Y is
* vertical in world coordinates, call noise3_XYBeforeZ(x, z, Y) or use
* noise3_XZBeforeY. If Z is vertical in world coordinates, call
* noise3_XYBeforeZ(x, y, Z). For a time varied animation, call
* noise3_XYBeforeZ(x, y, T).
* Recommended for 3D terrain and time-varied animations.
* The Z coordinate should always be the "different" coordinate in your use case.
* If Y is vertical in world coordinates, call noise3_XYBeforeZ(x, z, Y) or use noise3_XZBeforeY.
* If Z is vertical in world coordinates, call noise3_XYBeforeZ(x, y, Z).
* For a time varied animation, call noise3_XYBeforeZ(x, y, T).
*/
public double noise3_XYBeforeZ(double x, double y, double z) {
// Re-orient the cubic lattices without skewing, to make X and Y
// triangular like 2D.
// Re-orient the cubic lattices without skewing, to make X and Y triangular like 2D.
// Orthonormal rotation. Not a skew transform.
double xy = x + y;
double s2 = xy * -0.211324865405187;
@ -168,23 +167,20 @@ public class OpenSimplex2S {
/**
* 3D Re-oriented 8-point BCC noise, with better visual isotropy in (X, Z).
* Recommended for 3D terrain and time-varied animations. The Y coordinate
* should always be the "different" coordinate in your use case. If Y is
* vertical in world coordinates, call noise3_XZBeforeY(x, Y, z). If Z is
* vertical in world coordinates, call noise3_XZBeforeY(x, Z, y) or use
* noise3_XYBeforeZ. For a time varied animation, call noise3_XZBeforeY(x,
* T, y) or use noise3_XYBeforeZ.
* Recommended for 3D terrain and time-varied animations.
* The Y coordinate should always be the "different" coordinate in your use case.
* If Y is vertical in world coordinates, call noise3_XZBeforeY(x, Y, z).
* If Z is vertical in world coordinates, call noise3_XZBeforeY(x, Z, y) or use noise3_XYBeforeZ.
* For a time varied animation, call noise3_XZBeforeY(x, T, y) or use noise3_XYBeforeZ.
*/
public double noise3_XZBeforeY(double x, double y, double z) {
// Re-orient the cubic lattices without skewing, to make X and Z
// triangular like 2D.
// Re-orient the cubic lattices without skewing, to make X and Z triangular like 2D.
// Orthonormal rotation. Not a skew transform.
double xz = x + z;
double s2 = xz * -0.211324865405187;
double yy = y * 0.577350269189626;
double xr = x + s2 - yy;
double zr = z + s2 - yy;
double xr = x + s2 - yy; double zr = z + s2 - yy;
double yr = xz * 0.577350269189626 + yy;
// Evaluate both lattices to form a BCC lattice.
@ -192,10 +188,10 @@ public class OpenSimplex2S {
}
/**
* Generate overlapping cubic lattices for 3D Re-oriented BCC noise. Lookup
* table implementation inspired by DigitalShadow. It was actually faster to
* narrow down the points in the loop itself, than to build up the index
* with enough info to isolate 8 points.
* Generate overlapping cubic lattices for 3D Re-oriented BCC noise.
* Lookup table implementation inspired by DigitalShadow.
* It was actually faster to narrow down the points in the loop itself,
* than to build up the index with enough info to isolate 8 points.
*/
private double noise3_BCC(double xr, double yr, double zr) {
@ -203,10 +199,8 @@ public class OpenSimplex2S {
int xrb = fastFloor(xr), yrb = fastFloor(yr), zrb = fastFloor(zr);
double xri = xr - xrb, yri = yr - yrb, zri = zr - zrb;
// Identify which octant of the cube we're in. This determines which
// cell
// in the other cubic lattice we're in, and also narrows down one point
// on each.
// Identify which octant of the cube we're in. This determines which cell
// in the other cubic lattice we're in, and also narrows down one point on each.
int xht = (int)(xri + 0.5), yht = (int)(yri + 0.5), zht = (int)(zri + 0.5);
int index = (xht << 0) | (yht << 1) | (zht << 2);
@ -236,9 +230,9 @@ public class OpenSimplex2S {
*/
/**
* Generate the 2D noise over a large area. Propagates by flood-fill instead
* of iterating over a range. Results may occasionally slightly exceed [-1,
* 1] due to the grid-snapped pre-generated kernel.
* Generate the 2D noise over a large area.
* Propagates by flood-fill instead of iterating over a range.
* Results may occasionally slightly exceed [-1, 1] due to the grid-snapped pre-generated kernel.
*/
public void generate2(GenerateContext2D context, double[][] buffer, int x0, int y0) {
int height = buffer.length;
@ -247,12 +241,11 @@ public class OpenSimplex2S {
}
/**
* Generate the 2D noise over a large area. Propagates by flood-fill instead
* of iterating over a range. Results may occasionally slightly exceed [-1,
* 1] due to the grid-snapped pre-generated kernel.
* Generate the 2D noise over a large area.
* Propagates by flood-fill instead of iterating over a range.
* Results may occasionally slightly exceed [-1, 1] due to the grid-snapped pre-generated kernel.
*/
public void generate2(GenerateContext2D context, double[][] buffer, int x0, int y0, int width, int height,
int skipX, int skipY) {
public void generate2(GenerateContext2D context, double[][] buffer, int x0, int y0, int width, int height, int skipX, int skipY) {
Queue<AreaGenLatticePoint2D> queue = new LinkedList<AreaGenLatticePoint2D>();
Set<AreaGenLatticePoint2D> seen = new HashSet<AreaGenLatticePoint2D>();
@ -273,13 +266,10 @@ public class OpenSimplex2S {
}
// Get started with one point/vertex.
// For some lattices, you might need to try a handful of points in the
// cell,
// or flip a couple of coordinates, to guarantee it or a neighbor
// contributes.
// For some lattices, you might need to try a handful of points in the cell,
// or flip a couple of coordinates, to guarantee it or a neighbor contributes.
// For An* lattices, the base coordinate seems fine.
double x0f = x0Skipped * context.xFrequency;
double y0f = y0Skipped * context.yFrequency;
double x0f = x0Skipped * context.xFrequency; double y0f = y0Skipped * context.yFrequency;
double x0s = context.orientation.s00 * x0f + context.orientation.s01 * y0f;
double y0s = context.orientation.s10 * x0f + context.orientation.s11 * y0f;
int x0sb = fastFloor(x0s), y0sb = fastFloor(y0s);
@ -297,16 +287,11 @@ public class OpenSimplex2S {
Grad2 grad = context.orientation.gradients[perm[perm[pxm] ^ pym]];
double gx = grad.dx * context.xFrequency;
double gy = grad.dy * context.yFrequency;
double gOff = 0.5 * (gx + gy); // to correct for (0.5, 0.5)-offset
// kernel
double gOff = 0.5 * (gx + gy); // to correct for (0.5, 0.5)-offset kernel
// Contribution kernel bounds
int yy0 = destPointY - scaledRadiusY;
if (yy0 < y0Skipped)
yy0 = y0Skipped;
int yy1 = destPointY + scaledRadiusY;
if (yy1 > y0 + height)
yy1 = y0 + height;
int yy0 = destPointY - scaledRadiusY; if (yy0 < y0Skipped) yy0 = y0Skipped;
int yy1 = destPointY + scaledRadiusY; if (yy1 > y0 + height) yy1 = y0 + height;
// For each row of the contribution circle,
for (int yy = yy0; yy < yy1; yy++) {
@ -315,22 +300,16 @@ public class OpenSimplex2S {
// Set up bounds so we only loop over what we need to
int thisScaledRadiusX = context.kernelBounds[ky];
int xx0 = destPointX - thisScaledRadiusX;
if (xx0 < x0Skipped)
xx0 = x0Skipped;
int xx1 = destPointX + thisScaledRadiusX;
if (xx1 > x0 + width)
xx1 = x0 + width;
int xx0 = destPointX - thisScaledRadiusX; if (xx0 < x0Skipped) xx0 = x0Skipped;
int xx1 = destPointX + thisScaledRadiusX; if (xx1 > x0 + width) xx1 = x0 + width;
// For each point on that row
for (int xx = xx0; xx < xx1; xx++) {
int dx = xx - destPointX;
int kx = dx + scaledRadiusX;
// gOff accounts for our choice to offset the pre-generated
// kernel by (0.5, 0.5) to avoid the zero center.
// I found almost no difference in performance using gOff vs
// not (under 1ns diff per value on my system)
// gOff accounts for our choice to offset the pre-generated kernel by (0.5, 0.5) to avoid the zero center.
// I found almost no difference in performance using gOff vs not (under 1ns diff per value on my system)
double extrapolation = gx * dx + gy * dy + gOff;
buffer[yy - y0][xx - x0] += kernel[ky][kx] * extrapolation;
@ -339,14 +318,13 @@ public class OpenSimplex2S {
// For each neighbor of the point
for (int i = 0; i < NEIGHBOR_MAP_2D.length; i++) {
AreaGenLatticePoint2D neighbor = new AreaGenLatticePoint2D(context, point.xsv + NEIGHBOR_MAP_2D[i][0],
point.ysv + NEIGHBOR_MAP_2D[i][1]);
AreaGenLatticePoint2D neighbor = new AreaGenLatticePoint2D(context,
point.xsv + NEIGHBOR_MAP_2D[i][0], point.ysv + NEIGHBOR_MAP_2D[i][1]);
// If it's in range of the buffer region and not seen before
if (neighbor.destPointX + scaledRadiusX >= x0Skipped
&& neighbor.destPointX - scaledRadiusX <= x0 + width - 1
&& neighbor.destPointY + scaledRadiusY >= y0Skipped
&& neighbor.destPointY - scaledRadiusY <= y0 + height - 1 && !seen.contains(neighbor)) {
if (neighbor.destPointX + scaledRadiusX >= x0Skipped && neighbor.destPointX - scaledRadiusX <= x0 + width - 1
&& neighbor.destPointY + scaledRadiusY >= y0Skipped && neighbor.destPointY - scaledRadiusY <= y0 + height - 1
&& !seen.contains(neighbor)) {
// Add it to the queue so we can process it at some point
queue.add(neighbor);
@ -359,9 +337,9 @@ public class OpenSimplex2S {
}
/**
* Generate the 3D noise over a large area/volume. Propagates by flood-fill
* instead of iterating over a range. Results may occasionally slightly
* exceed [-1, 1] due to the grid-snapped pre-generated kernel.
* Generate the 3D noise over a large area/volume.
* Propagates by flood-fill instead of iterating over a range.
* Results may occasionally slightly exceed [-1, 1] due to the grid-snapped pre-generated kernel.
*/
public void generate3(GenerateContext3D context, double[][][] buffer, int x0, int y0, int z0) {
int depth = buffer.length;
@ -371,12 +349,11 @@ public class OpenSimplex2S {
}
/**
* Generate the 3D noise over a large area/volume. Propagates by flood-fill
* instead of iterating over a range. Results may occasionally slightly
* exceed [-1, 1] due to the grid-snapped pre-generated kernel.
* Generate the 3D noise over a large area/volume.
* Propagates by flood-fill instead of iterating over a range.
* Results may occasionally slightly exceed [-1, 1] due to the grid-snapped pre-generated kernel.
*/
public void generate3(GenerateContext3D context, double[][][] buffer, int x0, int y0, int z0, int width, int height,
int depth, int skipX, int skipY, int skipZ) {
public void generate3(GenerateContext3D context, double[][][] buffer, int x0, int y0, int z0, int width, int height, int depth, int skipX, int skipY, int skipZ) {
Queue<AreaGenLatticePoint3D> queue = new LinkedList<AreaGenLatticePoint3D>();
Set<AreaGenLatticePoint3D> seen = new HashSet<AreaGenLatticePoint3D>();
@ -388,10 +365,8 @@ public class OpenSimplex2S {
// Quaternion multiplication for rotation.
// https://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/
double qx = context.orientation.qx, qy = context.orientation.qy, qz = context.orientation.qz,
qw = context.orientation.qw;
double x0f = x0Skipped * context.xFrequency, y0f = y0Skipped * context.yFrequency,
z0f = z0Skipped * context.zFrequency;
double qx = context.orientation.qx, qy = context.orientation.qy, qz = context.orientation.qz, qw = context.orientation.qw;
double x0f = x0Skipped * context.xFrequency, y0f = y0Skipped * context.yFrequency, z0f = z0Skipped * context.zFrequency;
double tx = 2 * (qy * z0f - qz * y0f);
double ty = 2 * (qz * x0f - qx * z0f);
double tz = 2 * (qx * y0f - qy * x0f);
@ -417,16 +392,11 @@ public class OpenSimplex2S {
double gx = grad.dx * context.xFrequency;
double gy = grad.dy * context.yFrequency;
double gz = grad.dz * context.zFrequency;
double gOff = 0.5 * (gx + gy + gz); // to correct for (0.5, 0.5,
// 0.5)-offset kernel
double gOff = 0.5 * (gx + gy + gz); // to correct for (0.5, 0.5, 0.5)-offset kernel
// Contribution kernel bounds.
int zz0 = destPointZ - scaledRadiusZ;
if (zz0 < z0Skipped)
zz0 = z0Skipped;
int zz1 = destPointZ + scaledRadiusZ;
if (zz1 > z0 + depth)
zz1 = z0 + depth;
int zz0 = destPointZ - scaledRadiusZ; if (zz0 < z0Skipped) zz0 = z0Skipped;
int zz1 = destPointZ + scaledRadiusZ; if (zz1 > z0 + depth) zz1 = z0 + depth;
// For each x/y slice of the contribution sphere,
for (int zz = zz0; zz < zz1; zz++) {
@ -435,12 +405,8 @@ public class OpenSimplex2S {
// Set up bounds so we only loop over what we need to
int thisScaledRadiusY = context.kernelBoundsY[kz];
int yy0 = destPointY - thisScaledRadiusY;
if (yy0 < y0Skipped)
yy0 = y0Skipped;
int yy1 = destPointY + thisScaledRadiusY;
if (yy1 > y0 + height)
yy1 = y0 + height;
int yy0 = destPointY - thisScaledRadiusY; if (yy0 < y0Skipped) yy0 = y0Skipped;
int yy1 = destPointY + thisScaledRadiusY; if (yy1 > y0 + height) yy1 = y0 + height;
// For each row of the contribution circle,
for (int yy = yy0; yy < yy1; yy++) {
@ -449,21 +415,15 @@ public class OpenSimplex2S {
// Set up bounds so we only loop over what we need to
int thisScaledRadiusX = context.kernelBoundsX[kz][ky];
int xx0 = destPointX - thisScaledRadiusX;
if (xx0 < x0Skipped)
xx0 = x0Skipped;
int xx1 = destPointX + thisScaledRadiusX;
if (xx1 > x0 + width)
xx1 = x0 + width;
int xx0 = destPointX - thisScaledRadiusX; if (xx0 < x0Skipped) xx0 = x0Skipped;
int xx1 = destPointX + thisScaledRadiusX; if (xx1 > x0 + width) xx1 = x0 + width;
// For each point on that row
for (int xx = xx0; xx < xx1; xx++) {
int dx = xx - destPointX;
int kx = dx + scaledRadiusX;
// gOff accounts for our choice to offset the
// pre-generated kernel by (0.5, 0.5, 0.5) to avoid the
// zero center.
// gOff accounts for our choice to offset the pre-generated kernel by (0.5, 0.5, 0.5) to avoid the zero center.
double extrapolation = gx * dx + gy * dy + gz * dz + gOff;
buffer[zz - z0][yy - y0][xx - x0] += kernel[kz][ky][kx] * extrapolation;
@ -475,16 +435,13 @@ public class OpenSimplex2S {
for (int i = 0; i < NEIGHBOR_MAP_3D[0].length; i++) {
int l = point.lattice;
AreaGenLatticePoint3D neighbor = new AreaGenLatticePoint3D(context,
point.xsv + NEIGHBOR_MAP_3D[l][i][0], point.ysv + NEIGHBOR_MAP_3D[l][i][1],
point.zsv + NEIGHBOR_MAP_3D[l][i][2], 1 ^ l);
point.xsv + NEIGHBOR_MAP_3D[l][i][0], point.ysv + NEIGHBOR_MAP_3D[l][i][1], point.zsv + NEIGHBOR_MAP_3D[l][i][2], 1 ^ l);
// If it's in range of the buffer region and not seen before
if (neighbor.destPointX + scaledRadiusX >= x0Skipped
&& neighbor.destPointX - scaledRadiusX <= x0 + width - 1
&& neighbor.destPointY + scaledRadiusY >= y0Skipped
&& neighbor.destPointY - scaledRadiusY <= y0 + height - 1
&& neighbor.destPointZ + scaledRadiusZ >= z0Skipped
&& neighbor.destPointZ - scaledRadiusZ <= z0 + depth - 1 && !seen.contains(neighbor)) {
if (neighbor.destPointX + scaledRadiusX >= x0Skipped && neighbor.destPointX - scaledRadiusX <= x0 + width - 1
&& neighbor.destPointY + scaledRadiusY >= y0Skipped && neighbor.destPointY - scaledRadiusY <= y0 + height - 1
&& neighbor.destPointZ + scaledRadiusZ >= z0Skipped && neighbor.destPointZ - scaledRadiusZ <= z0 + depth - 1
&& !seen.contains(neighbor)) {
// Add it to the queue so we can process it at some point
queue.add(neighbor);
@ -518,35 +475,11 @@ public class OpenSimplex2S {
for (int i = 0; i < 8; i++) {
int i1, j1, i2, j2;
if ((i & 1) == 0) {
if ((i & 2) == 0) {
i1 = -1;
j1 = 0;
if ((i & 2) == 0) { i1 = -1; j1 = 0; } else { i1 = 1; j1 = 0; }
if ((i & 4) == 0) { i2 = 0; j2 = -1; } else { i2 = 0; j2 = 1; }
} else {
i1 = 1;
j1 = 0;
}
if ((i & 4) == 0) {
i2 = 0;
j2 = -1;
} else {
i2 = 0;
j2 = 1;
}
} else {
if ((i & 2) != 0) {
i1 = 2;
j1 = 1;
} else {
i1 = 0;
j1 = 1;
}
if ((i & 4) != 0) {
i2 = 1;
j2 = 2;
} else {
i2 = 1;
j2 = 0;
}
if ((i & 2) != 0) { i1 = 2; j1 = 1; } else { i1 = 0; j1 = 1; }
if ((i & 4) != 0) { i2 = 1; j2 = 2; } else { i2 = 1; j2 = 0; }
}
LOOKUP_2D[i * 4 + 0] = new LatticePoint2D(0, 0);
LOOKUP_2D[i * 4 + 1] = new LatticePoint2D(1, 1);
@ -556,15 +489,10 @@ public class OpenSimplex2S {
for (int i = 0; i < 8; i++) {
int i1, j1, k1, i2, j2, k2;
i1 = (i >> 0) & 1;
j1 = (i >> 1) & 1;
k1 = (i >> 2) & 1;
i2 = i1 ^ 1;
j2 = j1 ^ 1;
k2 = k1 ^ 1;
i1 = (i >> 0) & 1; j1 = (i >> 1) & 1; k1 = (i >> 2) & 1;
i2 = i1 ^ 1; j2 = j1 ^ 1; k2 = k1 ^ 1;
// The two points within this octant, one from each of the two cubic
// half-lattices.
// The two points within this octant, one from each of the two cubic half-lattices.
LatticePoint3D c0 = new LatticePoint3D(i1, j1, k1, 0);
LatticePoint3D c1 = new LatticePoint3D(i1 + i2, j1 + j2, k1 + k2, 1);
@ -597,36 +525,27 @@ public class OpenSimplex2S {
c1.nextOnFailure = c1.nextOnSuccess = c2;
// If c2 is in range, then we know c3 and c4 are not.
c2.nextOnFailure = c3;
c2.nextOnSuccess = c5;
c3.nextOnFailure = c4;
c3.nextOnSuccess = c4;
c2.nextOnFailure = c3; c2.nextOnSuccess = c5;
c3.nextOnFailure = c4; c3.nextOnSuccess = c4;
// If c4 is in range, then we know c5 is not.
c4.nextOnFailure = c5;
c4.nextOnSuccess = c6;
c4.nextOnFailure = c5; c4.nextOnSuccess = c6;
c5.nextOnFailure = c5.nextOnSuccess = c6;
// If c6 is in range, then we know c7 and c8 are not.
c6.nextOnFailure = c7;
c6.nextOnSuccess = c9;
c7.nextOnFailure = c8;
c7.nextOnSuccess = c8;
c6.nextOnFailure = c7; c6.nextOnSuccess = c9;
c7.nextOnFailure = c8; c7.nextOnSuccess = c8;
// If c8 is in range, then we know c9 is not.
c8.nextOnFailure = c9;
c8.nextOnSuccess = cA;
c8.nextOnFailure = c9; c8.nextOnSuccess = cA;
c9.nextOnFailure = c9.nextOnSuccess = cA;
// If cA is in range, then we know cB and cC are not.
cA.nextOnFailure = cB;
cA.nextOnSuccess = cD;
cB.nextOnFailure = cC;
cB.nextOnSuccess = cC;
cA.nextOnFailure = cB; cA.nextOnSuccess = cD;
cB.nextOnFailure = cC; cB.nextOnSuccess = cC;
// If cC is in range, then we know cD is not.
cC.nextOnFailure = cD;
cC.nextOnSuccess = null;
cC.nextOnFailure = cD; cC.nextOnSuccess = null;
cD.nextOnFailure = cD.nextOnSuccess = null;
LOOKUP_3D[i] = c0;
@ -635,24 +554,28 @@ public class OpenSimplex2S {
}
// Hexagon surrounding each vertex.
private static final int[][] NEIGHBOR_MAP_2D = { { 1, 0 }, { 1, 1 }, { 0, 1 }, { 0, -1 }, { -1, -1 }, { -1, 0 } };
private static final int[][] NEIGHBOR_MAP_2D = {
{ 1, 0 }, { 1, 1 }, { 0, 1 }, { 0, -1 }, { -1, -1 }, { -1, 0 }
};
// Cube surrounding each vertex.
// Alternates between half-lattices.
private static final int[][][] NEIGHBOR_MAP_3D = {
{ { 1024, 1024, 1024 }, { 1025, 1024, 1024 }, { 1024, 1025, 1024 }, { 1025, 1025, 1024 },
{ 1024, 1024, 1025 }, { 1025, 1024, 1025 }, { 1024, 1025, 1025 }, { 1025, 1025, 1025 } },
{ { -1024, -1024, -1024 }, { -1025, -1024, 1024 }, { -1024, -1025, -1024 }, { -1025, -1025, -1024 },
{ -1024, -1024, -1025 }, { -1025, -1024, -1025 }, { -1024, -1025, -1025 },
{ -1025, -1025, 1025 } }, };
{
{ 1024, 1024, 1024 }, { 1025, 1024, 1024 }, { 1024, 1025, 1024 }, { 1025, 1025, 1024 },
{ 1024, 1024, 1025 }, { 1025, 1024, 1025 }, { 1024, 1025, 1025 }, { 1025, 1025, 1025 }
},
{
{ -1024, -1024, -1024 }, { -1025, -1024, 1024 }, { -1024, -1025, -1024 }, { -1025, -1025, -1024 },
{ -1024, -1024, -1025 }, { -1025, -1024, -1025 }, { -1024, -1025, -1025 }, { -1025, -1025, 1025 }
},
};
private static class LatticePoint2D {
int xsv, ysv;
double dx, dy;
public LatticePoint2D(int xsv, int ysv) {
this.xsv = xsv;
this.ysv = ysv;
this.xsv = xsv; this.ysv = ysv;
double ssv = (xsv + ysv) * -0.211324865405187;
this.dx = -xsv - ssv;
this.dy = -ysv - ssv;
@ -663,42 +586,29 @@ public class OpenSimplex2S {
public double dxr, dyr, dzr;
public int xrv, yrv, zrv;
LatticePoint3D nextOnFailure, nextOnSuccess;
public LatticePoint3D(int xrv, int yrv, int zrv, int lattice) {
this.dxr = -xrv + lattice * 0.5;
this.dyr = -yrv + lattice * 0.5;
this.dzr = -zrv + lattice * 0.5;
this.xrv = xrv + lattice * 1024;
this.yrv = yrv + lattice * 1024;
this.zrv = zrv + lattice * 1024;
this.dxr = -xrv + lattice * 0.5; this.dyr = -yrv + lattice * 0.5; this.dzr = -zrv + lattice * 0.5;
this.xrv = xrv + lattice * 1024; this.yrv = yrv + lattice * 1024; this.zrv = zrv + lattice * 1024;
}
}
private static class AreaGenLatticePoint2D {
int xsv, ysv;
int destPointX, destPointY;
public AreaGenLatticePoint2D(GenerateContext2D context, int xsv, int ysv) {
this.xsv = xsv;
this.ysv = ysv;
this.xsv = xsv; this.ysv = ysv;
// Matrix multiplication for inverse rotation. Simplex skew
// transforms have always been shorthand for matrices.
this.destPointX = (int) Math
.ceil((context.orientation.t00 * xsv + context.orientation.t01 * ysv) * context.xFrequencyInverse);
this.destPointY = (int) Math
.ceil((context.orientation.t10 * xsv + context.orientation.t11 * ysv) * context.yFrequencyInverse);
//Matrix multiplication for inverse rotation. Simplex skew transforms have always been shorthand for matrices.
this.destPointX = (int)Math.ceil((context.orientation.t00 * xsv + context.orientation.t01 * ysv) * context.xFrequencyInverse);
this.destPointY = (int)Math.ceil((context.orientation.t10 * xsv + context.orientation.t11 * ysv) * context.yFrequencyInverse);
}
@Override
public int hashCode() {
return xsv * 7841 + ysv;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof AreaGenLatticePoint2D))
return false;
if (!(obj instanceof AreaGenLatticePoint2D)) return false;
AreaGenLatticePoint2D other = (AreaGenLatticePoint2D) obj;
return (other.xsv == this.xsv && other.ysv == this.ysv);
}
@ -707,20 +617,15 @@ public class OpenSimplex2S {
private static class AreaGenLatticePoint3D {
int xsv, ysv, zsv, lattice;
int destPointX, destPointY, destPointZ;
public AreaGenLatticePoint3D(GenerateContext3D context, int xsv, int ysv, int zsv, int lattice) {
this.xsv = xsv;
this.ysv = ysv;
this.zsv = zsv;
this.lattice = lattice;
this.xsv = xsv; this.ysv = ysv; this.zsv = zsv; this.lattice = lattice;
double xr = (xsv - lattice * 1024.5);
double yr = (ysv - lattice * 1024.5);
double zr = (zsv - lattice * 1024.5);
// Quaternion multiplication for inverse rotation.
// https://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/
double qx = -context.orientation.qx, qy = -context.orientation.qy, qz = -context.orientation.qz,
qw = context.orientation.qw;
double qx = -context.orientation.qx, qy = -context.orientation.qy, qz = -context.orientation.qz, qw = context.orientation.qw;
double tx = 2 * (qy * zr - qz * yr);
double ty = 2 * (qz * xr - qx * zr);
double tz = 2 * (qx * yr - qy * xr);
@ -732,19 +637,15 @@ public class OpenSimplex2S {
this.destPointY = (int)Math.ceil(yrr * context.yFrequencyInverse);
this.destPointZ = (int)Math.ceil(zrr * context.zFrequencyInverse);
}
@Override
public int hashCode() {
return xsv * 2122193 + ysv * 2053 + zsv * 2 + lattice;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof AreaGenLatticePoint3D))
return false;
if (!(obj instanceof AreaGenLatticePoint3D)) return false;
AreaGenLatticePoint3D other = (AreaGenLatticePoint3D) obj;
return (other.xsv == this.xsv && other.ysv == this.ysv && other.zsv == this.zsv
&& other.lattice == this.lattice);
return (other.xsv == this.xsv && other.ysv == this.ysv && other.zsv == this.zsv && other.lattice == this.lattice);
}
}
@ -760,8 +661,7 @@ public class OpenSimplex2S {
int[] kernelBounds;
LatticeOrientation2D orientation;
public GenerateContext2D(LatticeOrientation2D orientation, double xFrequency, double yFrequency,
double amplitude) {
public GenerateContext2D(LatticeOrientation2D orientation, double xFrequency, double yFrequency, double amplitude) {
// These will be used by every call to generate
this.orientation = orientation;
@ -783,9 +683,10 @@ public class OpenSimplex2S {
for (int yy = 0; yy < scaledRadiusY * 2; yy++) {
// Pre-generate boundary of circle
kernelBounds[yy] = (int) Math.ceil(Math.sqrt(
1.0 - (yy + 0.5 - scaledRadiusY) * (yy + 0.5 - scaledRadiusY) / (scaledRadiusY * scaledRadiusY))
* scaledRadiusX);
kernelBounds[yy] = (int)Math.ceil(
Math.sqrt(1.0
- (yy + 0.5 - scaledRadiusY) * (yy + 0.5 - scaledRadiusY) / (scaledRadiusY * scaledRadiusY)
) * scaledRadiusX);
if (yy < scaledRadiusY) {
kernel[yy] = new double[scaledRadiusX * 2];
@ -823,8 +724,7 @@ public class OpenSimplex2S {
int[][] kernelBoundsX;
LatticeOrientation3D orientation;
public GenerateContext3D(LatticeOrientation3D orientation, double xFrequency, double yFrequency,
double zFrequency, double amplitude) {
public GenerateContext3D(LatticeOrientation3D orientation, double xFrequency, double yFrequency, double zFrequency, double amplitude) {
// These will be used by every call to generate
this.orientation = orientation;
@ -851,9 +751,9 @@ public class OpenSimplex2S {
for (int zz = 0; zz < scaledRadiusZ * 2; zz++) {
// Pre-generate boundary of sphere
kernelBoundsY[zz] = (int) Math.ceil(Math.sqrt(
1.0 - (zz + 0.5 - scaledRadiusZ) * (zz + 0.5 - scaledRadiusZ) / (scaledRadiusZ * scaledRadiusZ))
* scaledRadiusY);
kernelBoundsY[zz] = (int)Math.ceil(
Math.sqrt(1.0 - (zz + 0.5 - scaledRadiusZ) * (zz + 0.5 - scaledRadiusZ)
/ (scaledRadiusZ * scaledRadiusZ)) * scaledRadiusY);
if (zz < scaledRadiusZ) {
kernel[zz] = new double[scaledRadiusY * 2][];
@ -867,12 +767,11 @@ public class OpenSimplex2S {
for (int yy = 0; yy < scaledRadiusY * 2; yy++) {
// Pre-generate boundary of sphere
kernelBoundsX[zz][yy] = (int) Math.ceil(Math.sqrt(1.0
- (yy + 0.5 - scaledRadiusY) * (yy + 0.5 - scaledRadiusY)
/ (scaledRadiusY * scaledRadiusY)
- (zz + 0.5 - scaledRadiusZ) * (zz + 0.5 - scaledRadiusZ)
/ (scaledRadiusZ * scaledRadiusZ))
* scaledRadiusX);
kernelBoundsX[zz][yy] = (int)Math.ceil(
Math.sqrt(1.0
- (yy + 0.5 - scaledRadiusY) * (yy + 0.5 - scaledRadiusY) / (scaledRadiusY * scaledRadiusY)
- (zz + 0.5 - scaledRadiusZ) * (zz + 0.5 - scaledRadiusZ) / (scaledRadiusZ * scaledRadiusZ)
) * scaledRadiusX);
if (yy < scaledRadiusY) {
kernel[zz][yy] = new double[scaledRadiusX * 2];
@ -891,8 +790,7 @@ public class OpenSimplex2S {
}
}
} else
kernel[zz][yy] = kernel[zz][2 * scaledRadiusY - yy - 1];
} else kernel[zz][yy] = kernel[zz][2 * scaledRadiusY - yy - 1];
}
}
}
@ -900,50 +798,40 @@ public class OpenSimplex2S {
}
public enum LatticeOrientation2D {
// Simplex skew transforms have always been shorthand for the matrices
// they represent.
// But when we bake the rotation into the skew transform, we need to use
// the general form.
Standard(GRADIENTS_2D, 1.366025403784439, 0.366025403784439, 0.366025403784439, 1.366025403784439,
0.788675134594813, -0.211324865405187, -0.211324865405187,
0.788675134594813), XBeforeY(GRADIENTS_2D_X_BEFORE_Y, 0.7071067811865476, 1.224744871380249,
-0.7071067811865476, 1.224744871380249, 0.7071067811865476, -0.7071067811865476,
0.40824829046764305, 0.40824829046764305);
// Simplex skew transforms have always been shorthand for the matrices they represent.
// But when we bake the rotation into the skew transform, we need to use the general form.
Standard(GRADIENTS_2D,
1.366025403784439, 0.366025403784439, 0.366025403784439, 1.366025403784439,
0.788675134594813, -0.211324865405187, -0.211324865405187, 0.788675134594813),
XBeforeY(GRADIENTS_2D_X_BEFORE_Y,
0.7071067811865476, 1.224744871380249, -0.7071067811865476, 1.224744871380249,
0.7071067811865476, -0.7071067811865476, 0.40824829046764305, 0.40824829046764305);
Grad2[] gradients;
double s00, s01, s10, s11;
double t00, t01, t10, t11;
private LatticeOrientation2D(Grad2[] gradients, double s00, double s01, double s10, double s11, double t00,
double t01, double t10, double t11) {
private LatticeOrientation2D(Grad2[] gradients,
double s00, double s01, double s10, double s11,
double t00, double t01, double t10, double t11) {
this.gradients = gradients;
this.s00 = s00;
this.s01 = s01;
this.s10 = s10;
this.s11 = s11;
this.t00 = t00;
this.t01 = t01;
this.t10 = t10;
this.t11 = t11;
this.s00 = s00; this.s01 = s01; this.s10 = s10; this.s11 = s11;
this.t00 = t00; this.t01 = t01; this.t10 = t10; this.t11 = t11;
}
}
public enum LatticeOrientation3D {
// Quaternions for 3D. Could use matrices, but I already wrote this code
// before I moved them into here.
Classic(GRADIENTS_3D_CLASSIC, 0.577350269189626, 0.577350269189626, 0.577350269189626, 0), XYBeforeZ(
GRADIENTS_3D_XY_BEFORE_Z, 0.3250575836718682, -0.3250575836718682, 0, 0.8880738339771154), XZBeforeY(
GRADIENTS_3D_XZ_BEFORE_Y, -0.3250575836718682, 0, 0.3250575836718682, 0.8880738339771154);
// Quaternions for 3D. Could use matrices, but I already wrote this code before I moved them into here.
Classic(GRADIENTS_3D_CLASSIC, 0.577350269189626, 0.577350269189626, 0.577350269189626, 0),
XYBeforeZ(GRADIENTS_3D_XY_BEFORE_Z, 0.3250575836718682, -0.3250575836718682, 0, 0.8880738339771154),
XZBeforeY(GRADIENTS_3D_XZ_BEFORE_Y, -0.3250575836718682, 0, 0.3250575836718682, 0.8880738339771154);
Grad3[] gradients;
double qx, qy, qz, qw;
private LatticeOrientation3D(Grad3[] gradients, double qx, double qy, double qz, double qw) {
this.gradients = gradients;
this.qx = qx;
this.qy = qy;
this.qz = qz;
this.qw = qw;
this.qx = qx; this.qy = qy; this.qz = qz; this.qw = qw;
}
}
@ -953,20 +841,15 @@ public class OpenSimplex2S {
public static class Grad2 {
double dx, dy;
public Grad2(double dx, double dy) {
this.dx = dx;
this.dy = dy;
this.dx = dx; this.dy = dy;
}
}
public static class Grad3 {
double dx, dy, dz;
public Grad3(double dx, double dy, double dz) {
this.dx = dx;
this.dy = dy;
this.dz = dz;
this.dx = dx; this.dy = dy; this.dz = dz;
}
}
@ -978,23 +861,35 @@ public class OpenSimplex2S {
GRADIENTS_2D = new Grad2[PSIZE];
GRADIENTS_2D_X_BEFORE_Y = new Grad2[PSIZE];
Grad2[] grad2 = { new Grad2(0.130526192220052, 0.99144486137381),
new Grad2(0.38268343236509, 0.923879532511287), new Grad2(0.608761429008721, 0.793353340291235),
new Grad2(0.793353340291235, 0.608761429008721), new Grad2(0.923879532511287, 0.38268343236509),
new Grad2(0.99144486137381, 0.130526192220051), new Grad2(0.99144486137381, -0.130526192220051),
new Grad2(0.923879532511287, -0.38268343236509), new Grad2(0.793353340291235, -0.60876142900872),
new Grad2(0.608761429008721, -0.793353340291235), new Grad2(0.38268343236509, -0.923879532511287),
new Grad2(0.130526192220052, -0.99144486137381), new Grad2(-0.130526192220052, -0.99144486137381),
new Grad2(-0.38268343236509, -0.923879532511287), new Grad2(-0.608761429008721, -0.793353340291235),
new Grad2(-0.793353340291235, -0.608761429008721), new Grad2(-0.923879532511287, -0.38268343236509),
new Grad2(-0.99144486137381, -0.130526192220052), new Grad2(-0.99144486137381, 0.130526192220051),
new Grad2(-0.923879532511287, 0.38268343236509), new Grad2(-0.793353340291235, 0.608761429008721),
new Grad2(-0.608761429008721, 0.793353340291235), new Grad2(-0.38268343236509, 0.923879532511287),
new Grad2(-0.130526192220052, 0.99144486137381) };
Grad2[] grad2 = {
new Grad2( 0.130526192220052, 0.99144486137381),
new Grad2( 0.38268343236509, 0.923879532511287),
new Grad2( 0.608761429008721, 0.793353340291235),
new Grad2( 0.793353340291235, 0.608761429008721),
new Grad2( 0.923879532511287, 0.38268343236509),
new Grad2( 0.99144486137381, 0.130526192220051),
new Grad2( 0.99144486137381, -0.130526192220051),
new Grad2( 0.923879532511287, -0.38268343236509),
new Grad2( 0.793353340291235, -0.60876142900872),
new Grad2( 0.608761429008721, -0.793353340291235),
new Grad2( 0.38268343236509, -0.923879532511287),
new Grad2( 0.130526192220052, -0.99144486137381),
new Grad2(-0.130526192220052, -0.99144486137381),
new Grad2(-0.38268343236509, -0.923879532511287),
new Grad2(-0.608761429008721, -0.793353340291235),
new Grad2(-0.793353340291235, -0.608761429008721),
new Grad2(-0.923879532511287, -0.38268343236509),
new Grad2(-0.99144486137381, -0.130526192220052),
new Grad2(-0.99144486137381, 0.130526192220051),
new Grad2(-0.923879532511287, 0.38268343236509),
new Grad2(-0.793353340291235, 0.608761429008721),
new Grad2(-0.608761429008721, 0.793353340291235),
new Grad2(-0.38268343236509, 0.923879532511287),
new Grad2(-0.130526192220052, 0.99144486137381)
};
Grad2[] grad2XBeforeY = new Grad2[grad2.length];
for (int i = 0; i < grad2.length; i++) {
grad2[i].dx /= N2;
grad2[i].dy /= N2;
grad2[i].dx /= N2; grad2[i].dy /= N2;
// Unrotated gradients for XBeforeY 2D
double xx = grad2[i].dx * 0.7071067811865476;
@ -1010,43 +905,61 @@ public class OpenSimplex2S {
GRADIENTS_3D_CLASSIC = new Grad3[PSIZE];
GRADIENTS_3D_XY_BEFORE_Z = new Grad3[PSIZE];
GRADIENTS_3D_XZ_BEFORE_Y = new Grad3[PSIZE];
Grad3[] grad3 = { new Grad3(-2.22474487139, -2.22474487139, -1.0),
Grad3[] grad3 = {
new Grad3(-2.22474487139, -2.22474487139, -1.0),
new Grad3(-2.22474487139, -2.22474487139, 1.0),
new Grad3(-3.0862664687972017, -1.1721513422464978, 0.0),
new Grad3(-1.1721513422464978, -3.0862664687972017, 0.0),
new Grad3(-2.22474487139, -1.0, -2.22474487139), new Grad3(-2.22474487139, 1.0, -2.22474487139),
new Grad3(-2.22474487139, -1.0, -2.22474487139),
new Grad3(-2.22474487139, 1.0, -2.22474487139),
new Grad3(-1.1721513422464978, 0.0, -3.0862664687972017),
new Grad3(-3.0862664687972017, 0.0, -1.1721513422464978),
new Grad3(-2.22474487139, -1.0, 2.22474487139), new Grad3(-2.22474487139, 1.0, 2.22474487139),
new Grad3(-2.22474487139, -1.0, 2.22474487139),
new Grad3(-2.22474487139, 1.0, 2.22474487139),
new Grad3(-3.0862664687972017, 0.0, 1.1721513422464978),
new Grad3(-1.1721513422464978, 0.0, 3.0862664687972017), new Grad3(-2.22474487139, 2.22474487139, -1.0),
new Grad3(-2.22474487139, 2.22474487139, 1.0), new Grad3(-1.1721513422464978, 3.0862664687972017, 0.0),
new Grad3(-1.1721513422464978, 0.0, 3.0862664687972017),
new Grad3(-2.22474487139, 2.22474487139, -1.0),
new Grad3(-2.22474487139, 2.22474487139, 1.0),
new Grad3(-1.1721513422464978, 3.0862664687972017, 0.0),
new Grad3(-3.0862664687972017, 1.1721513422464978, 0.0),
new Grad3(-1.0, -2.22474487139, -2.22474487139), new Grad3(1.0, -2.22474487139, -2.22474487139),
new Grad3(-1.0, -2.22474487139, -2.22474487139),
new Grad3( 1.0, -2.22474487139, -2.22474487139),
new Grad3( 0.0, -3.0862664687972017, -1.1721513422464978),
new Grad3( 0.0, -1.1721513422464978, -3.0862664687972017),
new Grad3(-1.0, -2.22474487139, 2.22474487139), new Grad3(1.0, -2.22474487139, 2.22474487139),
new Grad3(-1.0, -2.22474487139, 2.22474487139),
new Grad3( 1.0, -2.22474487139, 2.22474487139),
new Grad3( 0.0, -1.1721513422464978, 3.0862664687972017),
new Grad3(0.0, -3.0862664687972017, 1.1721513422464978), new Grad3(-1.0, 2.22474487139, -2.22474487139),
new Grad3(1.0, 2.22474487139, -2.22474487139), new Grad3(0.0, 1.1721513422464978, -3.0862664687972017),
new Grad3(0.0, 3.0862664687972017, -1.1721513422464978), new Grad3(-1.0, 2.22474487139, 2.22474487139),
new Grad3(1.0, 2.22474487139, 2.22474487139), new Grad3(0.0, 3.0862664687972017, 1.1721513422464978),
new Grad3(0.0, 1.1721513422464978, 3.0862664687972017), new Grad3(2.22474487139, -2.22474487139, -1.0),
new Grad3(2.22474487139, -2.22474487139, 1.0), new Grad3(1.1721513422464978, -3.0862664687972017, 0.0),
new Grad3(3.0862664687972017, -1.1721513422464978, 0.0), new Grad3(2.22474487139, -1.0, -2.22474487139),
new Grad3(2.22474487139, 1.0, -2.22474487139), new Grad3(3.0862664687972017, 0.0, -1.1721513422464978),
new Grad3(1.1721513422464978, 0.0, -3.0862664687972017), new Grad3(2.22474487139, -1.0, 2.22474487139),
new Grad3(2.22474487139, 1.0, 2.22474487139), new Grad3(1.1721513422464978, 0.0, 3.0862664687972017),
new Grad3(3.0862664687972017, 0.0, 1.1721513422464978), new Grad3(2.22474487139, 2.22474487139, -1.0),
new Grad3(2.22474487139, 2.22474487139, 1.0), new Grad3(3.0862664687972017, 1.1721513422464978, 0.0),
new Grad3(1.1721513422464978, 3.0862664687972017, 0.0) };
new Grad3( 0.0, -3.0862664687972017, 1.1721513422464978),
new Grad3(-1.0, 2.22474487139, -2.22474487139),
new Grad3( 1.0, 2.22474487139, -2.22474487139),
new Grad3( 0.0, 1.1721513422464978, -3.0862664687972017),
new Grad3( 0.0, 3.0862664687972017, -1.1721513422464978),
new Grad3(-1.0, 2.22474487139, 2.22474487139),
new Grad3( 1.0, 2.22474487139, 2.22474487139),
new Grad3( 0.0, 3.0862664687972017, 1.1721513422464978),
new Grad3( 0.0, 1.1721513422464978, 3.0862664687972017),
new Grad3( 2.22474487139, -2.22474487139, -1.0),
new Grad3( 2.22474487139, -2.22474487139, 1.0),
new Grad3( 1.1721513422464978, -3.0862664687972017, 0.0),
new Grad3( 3.0862664687972017, -1.1721513422464978, 0.0),
new Grad3( 2.22474487139, -1.0, -2.22474487139),
new Grad3( 2.22474487139, 1.0, -2.22474487139),
new Grad3( 3.0862664687972017, 0.0, -1.1721513422464978),
new Grad3( 1.1721513422464978, 0.0, -3.0862664687972017),
new Grad3( 2.22474487139, -1.0, 2.22474487139),
new Grad3( 2.22474487139, 1.0, 2.22474487139),
new Grad3( 1.1721513422464978, 0.0, 3.0862664687972017),
new Grad3( 3.0862664687972017, 0.0, 1.1721513422464978),
new Grad3( 2.22474487139, 2.22474487139, -1.0),
new Grad3( 2.22474487139, 2.22474487139, 1.0),
new Grad3( 3.0862664687972017, 1.1721513422464978, 0.0),
new Grad3( 1.1721513422464978, 3.0862664687972017, 0.0)
};
Grad3[] grad3Classic = new Grad3[grad3.length];
Grad3[] grad3XYBeforeZ = new Grad3[grad3.length];
Grad3[] grad3XZBeforeY = new Grad3[grad3.length];
for (int i = 0; i < grad3.length; i++) {
grad3[i].dx /= N3;
grad3[i].dy /= N3;
grad3[i].dz /= N3;
grad3[i].dx /= N3; grad3[i].dy /= N3; grad3[i].dz /= N3;
double gxr = grad3[i].dx, gyr = grad3[i].dy, gzr = grad3[i].dz;
// Unrotated gradients for classic 3D

View File

@ -611,7 +611,8 @@ public class ArrayUtil {
int end = offset + length;
if (end > arrayLength || offset < 0)
throw new IllegalArgumentException(
"Array contains [0; " + arrayLength + "), requested [" + offset + "; " + end + ")");
"Array contains [0; " + arrayLength + "), requested [" + offset + "; " + end + ")"
);
return length;
}
@ -627,7 +628,8 @@ public class ArrayUtil {
if (end > arrayLength || start < 0)
throw new IllegalArgumentException(
"Array contains [0; " + arrayLength + "), requested [" + start + "; " + end + ")");
"Array contains [0; " + arrayLength + "), requested [" + start + "; " + end + ")"
);
return end;
}

View File

@ -30,8 +30,18 @@ public class PrimitiveUtil {
private static final Map<Class<?>, Object> PRIMITIVE_TO_NULL = new HashMap<>();
static {
for (Class<?> boxed : new Class<?>[] { Boolean.class, Byte.class, Short.class, Character.class, Integer.class,
Long.class, Float.class, Double.class }) {
for (
Class<?> boxed : new Class<?>[] {
Boolean.class,
Byte.class,
Short.class,
Character.class,
Integer.class,
Long.class,
Float.class,
Double.class
}
) {
try {
PRIMITIVE_TO_BOXED.put((Class<?>) boxed.getField("TYPE").get(null), boxed);
} catch (Exception e) {

View File

@ -40,7 +40,8 @@ import java.util.stream.Stream;
/**
* Contains static methods to create {@link Stream Streams} that synchronize
* their <a href=
* their
* <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps">
* terminal operations</a> on a given monitor.
*
@ -1069,18 +1070,21 @@ public class SyncStreams {
}
/**
* Wraps the given {@link Stream} to make all <a href=
* Wraps the given {@link Stream} to make all
* <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps">
* terminal operations</a> acquire the provided monitor's lock before
* execution. Intermediate operations return streams that are also
* synchronized on the same object. The created stream will behave
* identically to the provided stream in all other aspects. Use this to
* synchronize access to stream's source.
* execution. Intermediate operations
* return streams that are also synchronized on the same object. The created
* stream will behave identically
* to the provided stream in all other aspects. Use this to synchronize
* access to stream's source.
* <p>
* <i>The returned {@code Stream}'s {@link Stream#iterator() iterator()} and
* {@link Stream#spliterator() spliterator()} methods return regular
* non-synchronized iterators and spliterators respectively</i>. It is the
* user's responsibility to avoid concurrency issues:
* {@link Stream#spliterator()
* spliterator()} methods return regular non-synchronized iterators and
* spliterators respectively</i>. It
* is the user's responsibility to avoid concurrency issues:
*
* <pre>
* synchronized (stream.getMonitor()) {
@ -1099,17 +1103,14 @@ public class SyncStreams {
* stream.forEach(System.out::println); // Should never throw a ConcurrentModificationException
* </pre>
*
* @param <T>
* the class of objects in the Stream
* @param stream
* the stream to wrap.
* @param monitor
* the object that the stream will use for synchronization. When
* {@code null}, the stream will synchronize on itself.
* @param <T> the class of objects in the Stream
* @param stream the stream to wrap.
* @param monitor the object that the stream will use for synchronization.
* When {@code null}, the stream
* will synchronize on itself.
* @return a {@link SyncStream SyncStream&lt;T&gt;} synchronized on
* {@code monitor} and backed by {@code stream}.
* @throws NullPointerException
* if {@code stream == null}.
* @throws NullPointerException if {@code stream == null}.
*/
public static <T> SyncStream<T> synchronizedStream(Stream<T> stream, Object monitor) {
Objects.requireNonNull(stream, "stream cannot be null");
@ -1117,19 +1118,22 @@ public class SyncStreams {
}
/**
* Wraps the given {@link IntStream} to make all <a href=
* Wraps the given {@link IntStream} to make all
* <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps">
* terminal operations</a> acquire the provided monitor's lock before
* execution. Intermediate operations return streams that are also
* synchronized on the same object. The created stream will behave
* identically to the provided stream in all other aspects. Use this to
* synchronize access to stream's source.
* execution. Intermediate operations
* return streams that are also synchronized on the same object. The created
* stream will behave identically
* to the provided stream in all other aspects. Use this to synchronize
* access to stream's source.
* <p>
* <i>The returned {@code IntStream}'s {@link IntStream#iterator()
* iterator()} and {@link IntStream#spliterator() spliterator()} methods
* return regular non-synchronized iterators and spliterators
* respectively</i>. It is the user's responsibility to avoid concurrency
* issues:
* iterator()} and
* {@link IntStream#spliterator() spliterator()} methods return regular
* non-synchronized iterators and
* spliterators respectively</i>. It is the user's responsibility to avoid
* concurrency issues:
*
* <pre>
* synchronized (stream.getMonitor()) {
@ -1148,15 +1152,13 @@ public class SyncStreams {
* stream.forEach(System.out::println); // Should never throw a ConcurrentModificationException
* </pre>
*
* @param stream
* the stream to wrap.
* @param monitor
* the object that the stream will use for synchronization. When
* {@code null}, the stream will synchronize on itself.
* @param stream the stream to wrap.
* @param monitor the object that the stream will use for synchronization.
* When {@code null}, the stream
* will synchronize on itself.
* @return a {@link SyncIntStream} synchronized on {@code monitor} and
* backed by {@code stream}.
* @throws NullPointerException
* if {@code stream == null}.
* @throws NullPointerException if {@code stream == null}.
*/
public static SyncIntStream synchronizedStream(IntStream stream, Object monitor) {
Objects.requireNonNull(stream, "stream cannot be null");
@ -1164,19 +1166,22 @@ public class SyncStreams {
}
/**
* Wraps the given {@link LongStream} to make all <a href=
* Wraps the given {@link LongStream} to make all
* <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps">
* terminal operations</a> acquire the provided monitor's lock before
* execution. Intermediate operations return streams that are also
* synchronized on the same object. The created stream will behave
* identically to the provided stream in all other aspects. Use this to
* synchronize access to stream's source.
* execution. Intermediate operations
* return streams that are also synchronized on the same object. The created
* stream will behave identically
* to the provided stream in all other aspects. Use this to synchronize
* access to stream's source.
* <p>
* <i>The returned {@code LongStream}'s {@link LongStream#iterator()
* iterator()} and {@link LongStream#spliterator() spliterator()} methods
* return regular non-synchronized iterators and spliterators
* respectively</i>. It is the user's responsibility to avoid concurrency
* issues:
* iterator()} and
* {@link LongStream#spliterator() spliterator()} methods return regular
* non-synchronized iterators and
* spliterators respectively</i>. It is the user's responsibility to avoid
* concurrency issues:
*
* <pre>
* synchronized (stream.getMonitor()) {
@ -1195,15 +1200,13 @@ public class SyncStreams {
* stream.forEach(System.out::println); // Should never throw a ConcurrentModificationException
* </pre>
*
* @param stream
* the stream to wrap.
* @param monitor
* the object that the stream will use for synchronization. When
* {@code null}, the stream will synchronize on itself.
* @param stream the stream to wrap.
* @param monitor the object that the stream will use for synchronization.
* When {@code null}, the stream
* will synchronize on itself.
* @return a {@link SyncLongStream} synchronized on {@code monitor} and
* backed by {@code stream}.
* @throws NullPointerException
* if {@code stream == null}.
* @throws NullPointerException if {@code stream == null}.
*/
public static SyncLongStream synchronizedStream(LongStream stream, Object monitor) {
Objects.requireNonNull(stream, "stream cannot be null");
@ -1211,19 +1214,22 @@ public class SyncStreams {
}
/**
* Wraps the given {@link DoubleStream} to make all <a href=
* Wraps the given {@link DoubleStream} to make all
* <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps">
* terminal operations</a> acquire the provided monitor's lock before
* execution. Intermediate operations return streams that are also
* synchronized on the same object. The created stream will behave
* identically to the provided stream in all other aspects. Use this to
* synchronize access to stream's source.
* execution. Intermediate operations
* return streams that are also synchronized on the same object. The created
* stream will behave identically
* to the provided stream in all other aspects. Use this to synchronize
* access to stream's source.
* <p>
* <i>The returned {@code DoubleStream}'s {@link DoubleStream#iterator()
* iterator()} and {@link DoubleStream#spliterator() spliterator()} methods
* return regular non-synchronized iterators and spliterators
* respectively</i>. It is the user's responsibility to avoid concurrency
* issues:
* iterator()} and
* {@link DoubleStream#spliterator() spliterator()} methods return regular
* non-synchronized iterators and
* spliterators respectively</i>. It is the user's responsibility to avoid
* concurrency issues:
*
* <pre>
* synchronized (stream.getMonitor()) {
@ -1242,15 +1248,13 @@ public class SyncStreams {
* stream.forEach(System.out::println); // Should never throw a ConcurrentModificationException
* </pre>
*
* @param stream
* the stream to wrap.
* @param monitor
* the object that the stream will use for synchronization. When
* {@code null}, the stream will synchronize on itself.
* @param stream the stream to wrap.
* @param monitor the object that the stream will use for synchronization.
* When {@code null}, the stream
* will synchronize on itself.
* @return a {@link SyncDoubleStream} synchronized on {@code monitor} and
* backed by {@code stream}.
* @throws NullPointerException
* if {@code stream == null}.
* @throws NullPointerException if {@code stream == null}.
*/
public static SyncDoubleStream synchronizedStream(DoubleStream stream, Object monitor) {
Objects.requireNonNull(stream, "stream cannot be null");

View File

@ -103,8 +103,14 @@ public class Escaper {
}
public static final Escaper JAVA = new Escaper('\\', 'u', "tbnrf'\"".toCharArray(), "\t\b\n\r\f\'\"".toCharArray(),
true, true);
public static final Escaper JAVA = new Escaper(
'\\',
'u',
"tbnrf'\"".toCharArray(),
"\t\b\n\r\f\'\"".toCharArray(),
true,
true
);
private final char escapeChar;
private final char unicodeEscapeChar;
@ -114,8 +120,14 @@ public class Escaper {
private final boolean preferUnicode;
private final boolean strict;
protected Escaper(char escapeChar, char unicodeEscapeChar, char[] safes, char[] unsafes, boolean preferUnicode,
boolean strict) {
protected Escaper(
char escapeChar,
char unicodeEscapeChar,
char[] safes,
char[] unsafes,
boolean preferUnicode,
boolean strict
) {
this.escapeChar = escapeChar;
this.unicodeEscapeChar = unicodeEscapeChar;
this.safes = safes;
@ -140,7 +152,8 @@ public class Escaper {
for (char c : unsafes) {
if (c == escapeChar)
throw new IllegalArgumentException(
"Unsafe characters contain escape chatacter (escape character is escaped automatically)");
"Unsafe characters contain escape chatacter (escape character is escaped automatically)"
);
if (c == unicodeEscapeChar)
throw new IllegalArgumentException("Unsafe characters contain Unicode escape chatacter");
}
@ -160,7 +173,11 @@ public class Escaper {
end = Integer.MAX_VALUE;
else
end = src.getPosition() + length;
while (src.has() && src.getPosition() < end && (until == null || !until.test(src.current())))
while (
src.has() &&
src.getPosition() < end &&
(until == null || !until.test(src.current()))
)
escape(src.consume(), output);
}
@ -208,7 +225,11 @@ public class Escaper {
int result = 0;
while (src.has() && src.getPosition() < end && (until == null || !until.test(src.current()))) {
while (
src.has() &&
src.getPosition() < end &&
(until == null || !until.test(src.current()))
) {
result += getEscapedLength(src.consume());
}
@ -236,7 +257,11 @@ public class Escaper {
end = Integer.MAX_VALUE;
else
end = src.getPosition() + length;
while (src.has() && src.getPosition() < end && (until == null || !until.test(src.current()))) {
while (
src.has() &&
src.getPosition() < end &&
(until == null || !until.test(src.current()))
) {
output.accept(unescapeOneSequence(src));
}
}
@ -257,8 +282,10 @@ public class Escaper {
if (src.current() == unicodeEscapeChar) {
src.next();
return (char) (hexValue(src.consume()) << (4 * 3) | hexValue(src.consume()) << (4 * 2)
| hexValue(src.consume()) << (4 * 1) | hexValue(src.consume()) << (4 * 0));
return (char) (hexValue(src.consume()) << (4 * 3) |
hexValue(src.consume()) << (4 * 2) |
hexValue(src.consume()) << (4 * 1) |
hexValue(src.consume()) << (4 * 0));
}
int index = ArrayUtil.firstIndexOf(safes, src.current());
@ -288,7 +315,11 @@ public class Escaper {
int result = 0;
while (src.has() && src.getPosition() < end && (until == null || !until.test(src.current()))) {
while (
src.has() &&
src.getPosition() < end &&
(until == null || !until.test(src.current()))
) {
skipOneSequence(src);
result++;
}
@ -297,7 +328,11 @@ public class Escaper {
}
public void skipOneSequence(CharReader src) {
if (src.current() == escapeChar && src.next() == unicodeEscapeChar) {
if (
src.current() == escapeChar
&&
src.next() == unicodeEscapeChar
) {
src.advance(4);
}
src.next();

View File

@ -41,8 +41,13 @@ public class StringUtil {
private static final String EMPTY_PLACEHOLDER = "[empty]";
private static final String DEFAULT_SEPARATOR = "; ";
public static <T> String arrayToString(T[] array, String separator, String empty, String nullPlaceholder,
String nullArray) {
public static <T> String arrayToString(
T[] array,
String separator,
String empty,
String nullPlaceholder,
String nullArray
) {
if (separator == null) {
throw new IllegalArgumentException(new NullPointerException());
@ -74,8 +79,13 @@ public class StringUtil {
return arrayToString(array, DEFAULT_SEPARATOR);
}
public static String iteratorToString(Iterator<?> iterator, String separator, String empty, String nullPlaceholder,
String nullIterator) {
public static String iteratorToString(
Iterator<?> iterator,
String separator,
String empty,
String nullPlaceholder,
String nullIterator
) {
if (separator == null) {
throw new IllegalArgumentException(new NullPointerException());
@ -109,8 +119,13 @@ public class StringUtil {
return iteratorToString(iterator, DEFAULT_SEPARATOR);
}
public static String iterableToString(Iterable<?> iterable, String separator, String empty, String nullPlaceholder,
String nullIterable) {
public static String iterableToString(
Iterable<?> iterable,
String separator,
String empty,
String nullPlaceholder,
String nullIterable
) {
if (separator == null) {
throw new IllegalArgumentException(new NullPointerException());
@ -131,8 +146,14 @@ public class StringUtil {
return iterableToString(iterable, DEFAULT_SEPARATOR);
}
public static <T> String supplierToString(IntFunction<T> supplier, int length, String separator, String empty,
String nullPlaceholder, String nullSupplier) {
public static <T> String supplierToString(
IntFunction<T> supplier,
int length,
String separator,
String empty,
String nullPlaceholder,
String nullSupplier
) {
if (separator == null)
throw new IllegalArgumentException(new NullPointerException());
@ -142,15 +163,28 @@ public class StringUtil {
return empty;
if (length > 0) {
return supplierToStringExactly(supplier, length, separator, nullPlaceholder);
return supplierToStringExactly(
supplier,
length,
separator,
nullPlaceholder
);
} else {
return supplierToStringUntilNull(supplier, separator, empty);
return supplierToStringUntilNull(
supplier,
separator,
empty
);
}
}
private static <T> String supplierToStringExactly(IntFunction<T> supplier, int length, String separator,
String nullPlaceholder) {
private static <T> String supplierToStringExactly(
IntFunction<T> supplier,
int length,
String separator,
String nullPlaceholder
) {
T element = supplier.apply(0);
StringBuilder sb = new StringBuilder(element == null ? nullPlaceholder : element.toString());
@ -164,7 +198,11 @@ public class StringUtil {
return sb.toString();
}
private static <T> String supplierToStringUntilNull(IntFunction<T> supplier, String separator, String empty) {
private static <T> String supplierToStringUntilNull(
IntFunction<T> supplier,
String separator,
String empty
) {
T element = supplier.apply(0);
if (element == null) {
@ -328,7 +366,11 @@ public class StringUtil {
StringBuilder sb = new StringBuilder();
charLoop: for (char c : src.toCharArray()) {
if ((resultIndex + 1) < arrayLength && test.test(c)) {
if (
(resultIndex + 1) < arrayLength
&&
test.test(c)
) {
result[resultIndex] = resetStringBuilder(sb);
++resultIndex;
continue charLoop;
@ -347,17 +389,17 @@ public class StringUtil {
* index.
* <p>
* Indices {@code 0} and {@code src.length() - 1} produce {@code str}
* excluding the specified character and {@code ""}.
* excluding
* the specified character and {@code ""}.
* <p>
*
* @param src
* the String to split
* @param at
* index to split at
* @throws IllegalArgumentException
* if the index is out of bounds for {@code src}
* @param src the String to split
* @param at index to split at
* @throws IllegalArgumentException if the index is out of bounds for
* {@code src}
* @return an array containing the substrings, in order of encounter in
* {@code src}. Its length is always 2.
* {@code src}.
* Its length is always 2.
*/
public static String[] splitAt(String src, int at) {
Objects.requireNonNull(src, "src");
@ -374,7 +416,10 @@ public class StringUtil {
return new String[] { src.substring(0, src.length() - 1), "" };
}
return new String[] { src.substring(0, at), src.substring(at + 1) };
return new String[] {
src.substring(0, at),
src.substring(at + 1)
};
}
/**
@ -382,7 +427,8 @@ public class StringUtil {
* indices.
* <p>
* Indices {@code 0} and {@code src.length() - 1} produce extra zero-length
* outputs. Duplicate indices produce extra zero-length outputs.
* outputs.
* Duplicate indices produce extra zero-length outputs.
* <p>
* Examples:
*
@ -393,14 +439,13 @@ public class StringUtil {
* splitAt("a.b", 1, 1, 1) -> {"a", "", "", "b"}
* </pre>
*
* @param src
* the String to split
* @param at
* indices to split at, in any order
* @throws IllegalArgumentException
* if some index is out of bounds for {@code src}
* @param src the String to split
* @param at indices to split at, in any order
* @throws IllegalArgumentException if some index is out of bounds for
* {@code src}
* @return an array containing the substrings, in order of encounter in
* {@code src}. Its length is always {@code at.length + 1}.
* {@code src}.
* Its length is always {@code at.length + 1}.
*/
public static String[] splitAt(String src, int... at) {
Objects.requireNonNull(src, "src");
@ -508,8 +553,10 @@ public class StringUtil {
}
if (endPos < beginPos) {
throw new IllegalArgumentException("endPos must be greater than or equal to beginPos (endPos=" + endPos
+ ", beginPos=" + beginPos + ")");
throw new IllegalArgumentException(
"endPos must be greater than or equal to beginPos (endPos="
+ endPos + ", beginPos=" + beginPos + ")"
);
}
if (endPos >= Math.min(a.length, b.length)) {
@ -545,7 +592,8 @@ public class StringUtil {
/**
* Finds and returns the index of the specified appearance of the specified
* character in the given array. The search starts at index 0.
* character
* in the given array. The search starts at index 0.
* <p>
* Examples:
* <p>
@ -582,12 +630,10 @@ public class StringUtil {
* </tr>
* </table>
*
* @param src
* - the array to search in.
* @param target
* - the character to search for.
* @param skip
* - the amount of <code>target</code> characters to be skipped.
* @param src - the array to search in.
* @param target - the character to search for.
* @param skip - the amount of <code>target</code> characters to be
* skipped.
* @return The index of the <code>skip+1</code>th <code>target</code>
* character or -1, if none found.
* @see StringUtil#indexFromEnd(char[], char, int)
@ -607,7 +653,8 @@ public class StringUtil {
/**
* Finds and returns the index of the specified appearance of the specified
* character in the given array. The search starts at index
* character
* in the given array. The search starts at index
* <code>src.length - 1</code>.
* <p>
* Examples:
@ -645,15 +692,13 @@ public class StringUtil {
* </tr>
* </table>
*
* @param src
* - the array to search in.
* @param target
* - the character to search for.
* @param skip
* - the amount of <code>target</code> characters to be skipped.
* @param src - the array to search in.
* @param target - the character to search for.
* @param skip - the amount of <code>target</code> characters to be
* skipped.
* @return The index of the <code>skip+1</code>th
* <code>target</code>character from the end of the array or -1, if
* none found.
* <code>target</code>character
* from the end of the array or -1, if none found.
* @see StringUtil#indexFromBeginning(char[], char, int)
*/
public static int indexFromEnd(char[] src, char target, int skip) {
@ -828,8 +873,12 @@ public class StringUtil {
return result;
}
private static void buildCombinations(StringBuilder sb, Collection<String> result, Iterable<String>[] parts,
int index) {
private static void buildCombinations(
StringBuilder sb,
Collection<String> result,
Iterable<String>[] parts,
int index
) {
if (index >= parts.length) {
result.add(sb.toString());
} else {
@ -855,8 +904,13 @@ public class StringUtil {
return result;
}
private static void buildCombinations(StringBuilder sb, String[] result, int[] resultIndex, String[][] parts,
int index) {
private static void buildCombinations(
StringBuilder sb,
String[] result,
int[] resultIndex,
String[][] parts,
int index
) {
if (index >= parts.length) {
result[resultIndex[0]++] = sb.toString();
} else {
@ -931,7 +985,10 @@ public class StringUtil {
}
private static char hexDigit(long value, int digit) {
return hexDigit((int) (value >>> (4 * digit)) & 0xF);
return hexDigit(
(int) (value >>> (4 * digit))
& 0xF
);
}
public static char hexDigit(int value) {

View File

@ -27,9 +27,10 @@ public abstract class AbstractCharReader implements CharReader {
/**
* Current position of this CharReader. The reader maps its input to
* positions starting from 0. Positions that are negative or lower than 0
* are invalid. {@link #current()} will throw an exception if position is
* invalid.
* positions starting from 0.
* Positions that are negative or lower than 0 are invalid.
* {@link #current()}
* will throw an exception if position is invalid.
*/
protected int position = 0;

View File

@ -51,12 +51,9 @@ public abstract class BufferedCharReader extends AbstractCharReader {
/**
* Acquires next characters and stores them in the array.
*
* @param buffer
* the output array
* @param offset
* index of the first character
* @param length
* maximum amount of characters to be pulled
* @param buffer the output array
* @param offset index of the first character
* @param length maximum amount of characters to be pulled
* @return the amount of characters actually pulled
*/
protected int pullChars(char[] buffer, int offset, int length) {

View File

@ -179,7 +179,8 @@ public interface CharReader {
/**
* Skips to the end of the current line. Both <code>"\n"</code>,
* <code>"\r"</code> and <code>"\r\n"</code> are considered line separators.
* <code>"\r"</code>
* and <code>"\r\n"</code> are considered line separators.
*
* @return the amount of characters in the skipped line
*/

View File

@ -38,7 +38,8 @@ public class StringCharReader extends AbstractCharReader {
int end = offset + length;
if (end > str.length() || offset < 0)
throw new IllegalArgumentException(
"String contains [0; " + str.length() + "), requested [" + offset + "; " + end + ")");
"String contains [0; " + str.length() + "), requested [" + offset + "; " + end + ")"
);
this.offset = offset;
this.length = length;

View File

@ -45,15 +45,8 @@ public interface ThrowingBiConsumer<T, U, E extends Exception> {
public static <T, U, E extends Exception> ThrowingBiConsumer<T, U, E> concat(
ThrowingBiConsumer<? super T, ? super U, ? extends E> first,
ThrowingBiConsumer<? super T, ? super U, ? extends E> second) {
return (t, u) -> {
first.accept(t, u);
second.accept(t, u);
};
}
public static <T, U, E extends Exception> ThrowingBiConsumer<T, U, E> concat(BiConsumer<? super T, ? super U> first,
ThrowingBiConsumer<? super T, ? super U, E> second) {
ThrowingBiConsumer<? super T, ? super U, ? extends E> second
) {
return (t, u) -> {
first.accept(t, u);
second.accept(t, u);
@ -61,7 +54,19 @@ public interface ThrowingBiConsumer<T, U, E extends Exception> {
}
public static <T, U, E extends Exception> ThrowingBiConsumer<T, U, E> concat(
ThrowingBiConsumer<? super T, ? super U, E> first, BiConsumer<? super T, ? super U> second) {
BiConsumer<? super T, ? super U> first,
ThrowingBiConsumer<? super T, ? super U, E> second
) {
return (t, u) -> {
first.accept(t, u);
second.accept(t, u);
};
}
public static <T, U, E extends Exception> ThrowingBiConsumer<T, U, E> concat(
ThrowingBiConsumer<? super T, ? super U, E> first,
BiConsumer<? super T, ? super U> second
) {
return (t, u) -> {
first.accept(t, u);
second.accept(t, u);

View File

@ -39,24 +39,30 @@ public interface ThrowingConsumer<T, E extends Exception> {
};
}
public static <T, E extends Exception> ThrowingConsumer<T, E> concat(ThrowingConsumer<? super T, ? extends E> first,
ThrowingConsumer<? super T, ? extends E> second) {
public static <T, E extends Exception> ThrowingConsumer<T, E> concat(
ThrowingConsumer<? super T, ? extends E> first,
ThrowingConsumer<? super T, ? extends E> second
) {
return t -> {
first.accept(t);
second.accept(t);
};
}
public static <T, E extends Exception> ThrowingConsumer<T, E> concat(Consumer<? super T> first,
ThrowingConsumer<? super T, ? extends E> second) {
public static <T, E extends Exception> ThrowingConsumer<T, E> concat(
Consumer<? super T> first,
ThrowingConsumer<? super T, ? extends E> second
) {
return t -> {
first.accept(t);
second.accept(t);
};
}
public static <T, E extends Exception> ThrowingConsumer<T, E> concat(ThrowingConsumer<? super T, ? extends E> first,
Consumer<? super T> second) {
public static <T, E extends Exception> ThrowingConsumer<T, E> concat(
ThrowingConsumer<? super T, ? extends E> first,
Consumer<? super T> second
) {
return t -> {
first.accept(t);
second.accept(t);

View File

@ -28,8 +28,10 @@ public interface ThrowingFunction<T, R, E extends Exception> {
R apply(T t) throws E;
@SuppressWarnings("unchecked")
default Function<T, R> withHandler(BiConsumer<? super T, ? super E> handler,
Function<? super T, ? extends R> value) {
default Function<T, R> withHandler(
BiConsumer<? super T, ? super E> handler,
Function<? super T, ? extends R> value
) {
return t -> {
try {
return apply(t);
@ -57,17 +59,22 @@ public interface ThrowingFunction<T, R, E extends Exception> {
public static <T, R, I, E extends Exception> ThrowingFunction<T, R, E> compose(
ThrowingFunction<? super T, I, ? extends E> first,
ThrowingFunction<? super I, ? extends R, ? extends E> second) {
return t -> second.apply(first.apply(t));
}
public static <T, R, I, E extends Exception> ThrowingFunction<T, R, E> compose(Function<? super T, I> first,
ThrowingFunction<? super I, ? extends R, E> second) {
ThrowingFunction<? super I, ? extends R, ? extends E> second
) {
return t -> second.apply(first.apply(t));
}
public static <T, R, I, E extends Exception> ThrowingFunction<T, R, E> compose(
ThrowingFunction<? super T, I, E> first, Function<? super I, ? extends R> second) {
Function<? super T, I> first,
ThrowingFunction<? super I, ? extends R, E> second
) {
return t -> second.apply(first.apply(t));
}
public static <T, R, I, E extends Exception> ThrowingFunction<T, R, E> compose(
ThrowingFunction<? super T, I, E> first,
Function<? super I, ? extends R> second
) {
return t -> second.apply(first.apply(t));
}

View File

@ -38,8 +38,10 @@ public interface ThrowingRunnable<E extends Exception> {
};
}
public static <E extends Exception> ThrowingRunnable<E> concat(ThrowingRunnable<? extends E> first,
ThrowingRunnable<? extends E> second) {
public static <E extends Exception> ThrowingRunnable<E> concat(
ThrowingRunnable<? extends E> first,
ThrowingRunnable<? extends E> second
) {
return () -> {
first.run();
second.run();

View File

@ -49,8 +49,10 @@ public class RangeIterator<E> implements Iterator<E> {
public E next() {
update();
if (nextIndex >= from + amount) {
throw new NoSuchElementException("RangeIterator about to retrieve element " + nextIndex
+ " which exceeds upper boundary " + (from + amount));
throw new NoSuchElementException(
"RangeIterator about to retrieve element " + nextIndex
+ " which exceeds upper boundary " + (from + amount)
);
}
E result = parent.next();

View File

@ -18,18 +18,28 @@
package ru.windcorp.progressia;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
import ru.windcorp.progressia.common.util.crash.providers.*;
import ru.windcorp.progressia.test.LayerTitle;
public class ProgressiaLauncher {
public static String[] arguments;
private static Proxy proxy;
public static void launch(String[] args, Proxy proxy) {
arguments = args.clone();
setupCrashReports();
proxy.initialize();
ProgressiaLauncher.proxy = proxy;
GUI.addTopLayer(new LayerTitle("Title"));
}
public static Proxy getProxy() {
return proxy;
}
private static void setupCrashReports() {

View File

@ -18,22 +18,43 @@
package ru.windcorp.progressia.client;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.comms.DefaultClientCommsListener;
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.client.events.ClientEvent;
import ru.windcorp.progressia.client.events.NewLocalEntityEvent;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
import ru.windcorp.progressia.client.graphics.world.hud.HUDManager;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.util.crash.ReportingEventBus;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerDebug;
import ru.windcorp.progressia.test.LayerTestUI;
public class Client {
private final WorldRender world;
private final LayerWorld layerWorld = new LayerWorld(this);
private final LayerTestUI layerTestUI = new LayerTestUI();
private final LayerAbout layerAbout = new LayerAbout();
private final LayerDebug layerDebug = new LayerDebug();
private final LocalPlayer localPlayer = new LocalPlayer(this);
private final Camera camera = new Camera((float) Math.toRadians(70));
private final EventBus eventBus = ReportingEventBus.create("ClientEvents");
private final HUDManager hudManager = new HUDManager(this);
private final ServerCommsChannel comms;
public Client(DefaultWorldData world, ServerCommsChannel comms) {
@ -41,6 +62,22 @@ public class Client {
this.comms = comms;
comms.addListener(new DefaultClientCommsListener(this));
subscribe(this);
}
public void install() {
GUI.addBottomLayer(layerWorld);
GUI.addTopLayer(layerTestUI);
hudManager.install();
GUI.addTopLayer(layerAbout);
}
public void remove() {
GUI.removeLayer(layerWorld);
GUI.removeLayer(layerTestUI);
hudManager.remove();
GUI.removeLayer(layerAbout);
GUI.removeLayer(layerDebug);
}
public WorldRender getWorld() {
@ -63,13 +100,44 @@ public class Client {
return comms;
}
public void onLocalPlayerEntityChanged(EntityData entity, EntityData lastKnownEntity) {
if (entity == null) {
public HUDManager getHUD() {
return hudManager;
}
public void toggleDebugLayer() {
if (GUI.getLayers().contains(layerDebug)) {
GUI.removeLayer(layerDebug);
} else {
GUI.addTopLayer(layerDebug);
}
}
@Subscribe
private void onLocalPlayerEntityChanged(NewLocalEntityEvent e) {
if (e.getNewEntity() == null) {
getCamera().setAnchor(null);
return;
}
getCamera().setAnchor(new EntityAnchor(getWorld().getEntityRenderable(entity)));
getCamera().setAnchor(
new EntityAnchor(
getWorld().getEntityRenderable(e.getNewEntity())
)
);
}
public void subscribe(Object object) {
eventBus.register(object);
}
public void unsubscribe(Object object) {
eventBus.unregister(object);
}
public void postEvent(ClientEvent event) {
event.setClient(this);
eventBus.post(event);
event.setClient(null);
}
}

View File

@ -27,10 +27,10 @@ import ru.windcorp.progressia.client.graphics.font.GNUUnifontLoader;
import ru.windcorp.progressia.client.graphics.font.Typefaces;
import ru.windcorp.progressia.client.graphics.texture.Atlases;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.graphics.world.hud.HUDTextures;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestMusicPlayer;
@ -38,12 +38,17 @@ public class ClientProxy implements Proxy {
@Override
public void initialize() {
GraphicsBackend.initialize();
try {
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
RenderTaskQueue.waitAndInvoke(() -> Typefaces
.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz"))));
RenderTaskQueue.waitAndInvoke(
() -> Typefaces
.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz")))
);
RenderTaskQueue.waitAndInvoke(HUDTextures::loadItemAmountTypeface);
} catch (InterruptedException e) {
throw CrashReports.report(e, "ClientProxy failed");
}
@ -56,10 +61,6 @@ public class ClientProxy implements Proxy {
AudioSystem.initialize();
ServerState.startServer();
ClientState.connectToLocalServer();
TestMusicPlayer.start();
}
}

View File

@ -20,11 +20,10 @@ package ru.windcorp.progressia.client;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerTestUI;
import ru.windcorp.progressia.test.LayerTestText;
import ru.windcorp.progressia.test.TestContent;
public class ClientState {
@ -43,18 +42,38 @@ public class ClientState {
DefaultWorldData world = new DefaultWorldData();
LocalServerCommsChannel channel = new LocalServerCommsChannel(ServerState.getInstance());
LocalServerCommsChannel channel = new LocalServerCommsChannel(
ServerState.getInstance()
);
Client client = new Client(world, channel);
channel.connect(TestContent.PLAYER_LOGIN);
setInstance(client);
displayLoadingScreen();
GUI.addBottomLayer(new LayerWorld(client));
GUI.addTopLayer(new LayerTestUI());
GUI.addTopLayer(new LayerAbout());
}
private static void displayLoadingScreen() {
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> {
Client client = ClientState.getInstance();
// TODO refacetor and remove
if (client != null) {
client.getComms().processPackets();
}
if (client != null && client.getLocalPlayer().hasEntity()) {
GUI.removeLayer(layer);
client.install();
}
}));
}
public static void disconnectFromLocalServer() {
getInstance().getComms().disconnect();
getInstance().remove();
}
private ClientState() {

View File

@ -43,7 +43,10 @@ public class AudioManager {
private static Speaker musicSpeaker;
public static void initAL() {
String defaultDeviceName = alcGetString(0, ALC_DEFAULT_DEVICE_SPECIFIER);
String defaultDeviceName = alcGetString(
0,
ALC_DEFAULT_DEVICE_SPECIFIER
);
device = alcOpenDevice(defaultDeviceName);
@ -72,7 +75,10 @@ public class AudioManager {
lastSoundIndex = 0;
}
speaker = soundSpeakers.get(lastSoundIndex);
} while (speaker.getState().equals(Speaker.State.PLAYING_LOOP));
} while (
speaker.getState()
.equals(Speaker.State.PLAYING_LOOP)
);
return speaker;
}

View File

@ -29,7 +29,10 @@ public class AudioSystem {
}
static void loadAudioData() {
AudioManager.loadSound(ResourceManager.getResource("assets/sounds/block_destroy_clap.ogg"),
"Progressia:BlockDestroy", AudioFormat.MONO);
AudioManager.loadSound(
ResourceManager.getResource("assets/sounds/block_destroy_clap.ogg"),
"Progressia:BlockDestroy",
AudioFormat.MONO
);
}
}

View File

@ -22,7 +22,10 @@ import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.client.audio.backend.Speaker;
public class Music extends Sound {
public class Music
extends Sound {
public Music(SoundType soundType, int timeLength, float pitch, float gain) {
super(soundType, timeLength, new Vec3(), new Vec3(), pitch, gain);

View File

@ -40,7 +40,14 @@ public class Sound {
this(AudioRegistry.getInstance().get(id));
}
public Sound(String id, int timeLength, Vec3 position, Vec3 velocity, float pitch, float gain) {
public Sound(
String id,
int timeLength,
Vec3 position,
Vec3 velocity,
float pitch,
float gain
) {
this(id);
this.position = position;
this.velocity = velocity;
@ -48,7 +55,14 @@ public class Sound {
this.gain = gain;
}
public Sound(SoundType soundType, int timeLength, Vec3 position, Vec3 velocity, float pitch, float gain) {
public Sound(
SoundType soundType,
int timeLength,
Vec3 position,
Vec3 velocity,
float pitch,
float gain
) {
this(soundType);
this.position = position;
this.velocity = velocity;

View File

@ -39,7 +39,12 @@ public class AudioReader {
ShortBuffer rawAudio = decodeVorbis(resource, channelBuffer, rateBuffer);
return new SoundType(id, rawAudio, format, rateBuffer.get(0));
return new SoundType(
id,
rawAudio,
format,
rateBuffer.get(0)
);
}
public static SoundType readAsMono(Resource resource, String id) {
@ -50,7 +55,15 @@ public class AudioReader {
return readAsSpecified(resource, id, AL_FORMAT_STEREO16);
}
private static ShortBuffer decodeVorbis(Resource dataToDecode, IntBuffer channelsBuffer, IntBuffer rateBuffer) {
return stb_vorbis_decode_memory(dataToDecode.readAsBytes(), channelsBuffer, rateBuffer);
private static ShortBuffer decodeVorbis(
Resource dataToDecode,
IntBuffer channelsBuffer,
IntBuffer rateBuffer
) {
return stb_vorbis_decode_memory(
dataToDecode.readAsBytes(),
channelsBuffer,
rateBuffer
);
}
}

View File

@ -55,8 +55,9 @@ public class Listener {
if (isInWorld) {
if (wasInWorld) {
velocity.set(camera.getLastAnchorPosition()).sub(position)
.div((float) GraphicsInterface.getFrameLength());
velocity.set(camera.getLastAnchorPosition()).sub(position).div(
(float) GraphicsInterface.getFrameLength()
);
} else {
// If !wasInWorld, previous position is nonsence. Assume 0.
velocity.set(0);
@ -71,9 +72,9 @@ public class Listener {
}
/*
* Only apply if there is a chance that params changed. This can only
* happen if we are in world now (isInWorld) or we just left world
* (wasInWorld, then we need to reset).
* Only apply if there is a chance that params changed.
* This can only happen if we are in world now (isInWorld) or we just
* left world (wasInWorld, then we need to reset).
*/
if (isInWorld || wasInWorld) {
applyParams();
@ -90,7 +91,17 @@ public class Listener {
private void applyParams() {
alListener3f(AL_POSITION, position.x, position.y, position.z);
alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z);
alListenerfv(AL_ORIENTATION, new float[] { oriAt.x, oriAt.y, oriAt.z, oriUp.x, oriUp.y, oriUp.z });
alListenerfv(
AL_ORIENTATION,
new float[] {
oriAt.x,
oriAt.y,
oriAt.z,
oriUp.x,
oriUp.y,
oriUp.z
}
);
}
}

View File

@ -34,7 +34,12 @@ public class SoundType extends Namespaced {
private int audioBuffer;
private double duration;
public SoundType(String id, ShortBuffer rawAudio, int format, int sampleRate) {
public SoundType(
String id,
ShortBuffer rawAudio,
int format,
int sampleRate
) {
super(id);
this.rawAudio = rawAudio;
this.sampleRate = sampleRate;

View File

@ -24,7 +24,9 @@ import static org.lwjgl.openal.AL11.*;
public class Speaker {
public enum State {
NOT_PLAYING, PLAYING, PLAYING_LOOP
NOT_PLAYING,
PLAYING,
PLAYING_LOOP
}
// Buffers
@ -47,7 +49,13 @@ public class Speaker {
setAudioData(audioData);
}
public Speaker(int audioData, Vec3 position, Vec3 velocity, float pitch, float gain) {
public Speaker(
int audioData,
Vec3 position,
Vec3 velocity,
float pitch,
float gain
) {
setAudioData(audioData);
setPosition(position);
setVelocity(velocity);
@ -55,7 +63,12 @@ public class Speaker {
setGain(gain);
}
public Speaker(Vec3 position, Vec3 velocity, float pitch, float gain) {
public Speaker(
Vec3 position,
Vec3 velocity,
float pitch,
float gain
) {
setPosition(position);
setVelocity(velocity);
setPitch(pitch);

View File

@ -39,7 +39,9 @@ public class DefaultClientCommsListener implements CommsListener {
@Override
public void onPacketReceived(Packet packet) {
if (packet instanceof PacketAffectWorld) {
((PacketAffectWorld) packet).apply(getClient().getWorld().getData());
((PacketAffectWorld) packet).apply(
getClient().getWorld().getData()
);
} else if (packet instanceof PacketSetLocalPlayer) {
setLocalPlayer((PacketSetLocalPlayer) packet);
}

View File

@ -33,12 +33,17 @@ public class ControlTriggerLambda extends ControlTriggerInputBased {
private final Predicate<InputEvent> predicate;
private final BiConsumer<InputEvent, ControlData> dataWriter;
public ControlTriggerLambda(String id, Predicate<InputEvent> predicate,
BiConsumer<InputEvent, ControlData> dataWriter) {
public ControlTriggerLambda(
String id,
Predicate<InputEvent> predicate,
BiConsumer<InputEvent, ControlData> dataWriter
) {
super(id);
this.packetId = NamespacedUtil.getId(NamespacedUtil.getNamespace(id),
"ControlKeyPress" + NamespacedUtil.getName(id));
this.packetId = NamespacedUtil.getId(
NamespacedUtil.getNamespace(id),
"ControlKeyPress" + NamespacedUtil.getName(id)
);
this.predicate = predicate;
this.dataWriter = dataWriter;
@ -49,7 +54,10 @@ public class ControlTriggerLambda extends ControlTriggerInputBased {
if (!predicate.test(event))
return null;
PacketControl packet = new PacketControl(packetId, ControlDataRegistry.getInstance().create(getId()));
PacketControl packet = new PacketControl(
packetId,
ControlDataRegistry.getInstance().create(getId())
);
dataWriter.accept(event, packet.getControl());

View File

@ -29,7 +29,11 @@ public class ControlTriggerLocalLambda extends ControlTriggerInputBased {
private final Predicate<InputEvent> predicate;
private final Consumer<InputEvent> action;
public ControlTriggerLocalLambda(String id, Predicate<InputEvent> predicate, Consumer<InputEvent> action) {
public ControlTriggerLocalLambda(
String id,
Predicate<InputEvent> predicate,
Consumer<InputEvent> action
) {
super(id);
this.predicate = predicate;

View File

@ -27,119 +27,216 @@ import ru.windcorp.progressia.common.comms.controls.ControlData;
public class ControlTriggers {
public static ControlTriggerInputBased of(String id, BiConsumer<InputEvent, ControlData> dataWriter,
Predicate<InputEvent> predicate) {
public static ControlTriggerInputBased of(
String id,
BiConsumer<InputEvent, ControlData> dataWriter,
Predicate<InputEvent> predicate
) {
return new ControlTriggerLambda(id, predicate, dataWriter);
}
public static ControlTriggerInputBased of(String id, Consumer<ControlData> dataWriter,
Predicate<InputEvent> predicate) {
return of(id, (input, control) -> dataWriter.accept(control), predicate);
public static ControlTriggerInputBased of(
String id,
Consumer<ControlData> dataWriter,
Predicate<InputEvent> predicate
) {
return of(
id,
(input, control) -> dataWriter.accept(control),
predicate
);
}
public static ControlTriggerInputBased of(String id, Predicate<InputEvent> predicate) {
return of(id, (input, control) -> {
}, predicate);
public static ControlTriggerInputBased of(
String id,
Predicate<InputEvent> predicate
) {
return of(
id,
(input, control) -> {
},
predicate
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased of(String id, Class<I> inputType,
BiConsumer<I, ControlData> dataWriter, Predicate<I>... predicates) {
return of(id, createCheckedDataWriter(inputType, dataWriter),
createCheckedCompoundPredicate(inputType, predicates));
public static <I extends InputEvent> ControlTriggerInputBased of(
String id,
Class<I> inputType,
BiConsumer<I, ControlData> dataWriter,
Predicate<I>... predicates
) {
return of(
id,
createCheckedDataWriter(inputType, dataWriter),
createCheckedCompoundPredicate(inputType, predicates)
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased of(String id, Class<I> inputType,
Consumer<ControlData> dataWriter, Predicate<I>... predicates) {
return of(id, inputType, (input, control) -> dataWriter.accept(control), predicates);
public static <I extends InputEvent> ControlTriggerInputBased of(
String id,
Class<I> inputType,
Consumer<ControlData> dataWriter,
Predicate<I>... predicates
) {
return of(
id,
inputType,
(input, control) -> dataWriter.accept(control),
predicates
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased of(String id, Class<I> inputType,
Predicate<I>... predicates) {
return of(id, (input, control) -> {
}, createCheckedCompoundPredicate(inputType, predicates));
public static <I extends InputEvent> ControlTriggerInputBased of(
String id,
Class<I> inputType,
Predicate<I>... predicates
) {
return of(
id,
(input, control) -> {
},
createCheckedCompoundPredicate(inputType, predicates)
);
}
@SafeVarargs
public static ControlTriggerInputBased of(String id, BiConsumer<InputEvent, ControlData> dataWriter,
Predicate<InputEvent>... predicates) {
return of(id, InputEvent.class, dataWriter, predicates);
public static ControlTriggerInputBased of(
String id,
BiConsumer<InputEvent, ControlData> dataWriter,
Predicate<InputEvent>... predicates
) {
return of(
id,
InputEvent.class,
dataWriter,
predicates
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased of(String id, Consumer<ControlData> dataWriter,
Predicate<InputEvent>... predicates) {
return of(id, (input, control) -> dataWriter.accept(control), predicates);
public static <I extends InputEvent> ControlTriggerInputBased of(
String id,
Consumer<ControlData> dataWriter,
Predicate<InputEvent>... predicates
) {
return of(
id,
(input, control) -> dataWriter.accept(control),
predicates
);
}
@SafeVarargs
public static ControlTriggerInputBased of(String id, Predicate<InputEvent>... predicates) {
return of(id, InputEvent.class, (input, control) -> {
}, predicates);
public static ControlTriggerInputBased of(
String id,
Predicate<InputEvent>... predicates
) {
return of(
id,
InputEvent.class,
(input, control) -> {
},
predicates
);
}
//
//
///
///
//
//
//
//
//
//
//
//
//
public static ControlTriggerInputBased localOf(String id, Consumer<InputEvent> action,
Predicate<InputEvent> predicate) {
public static ControlTriggerInputBased localOf(
String id,
Consumer<InputEvent> action,
Predicate<InputEvent> predicate
) {
return new ControlTriggerLocalLambda(id, predicate, action);
}
public static ControlTriggerInputBased localOf(String id, Runnable action, Predicate<InputEvent> predicate) {
return localOf(id, input -> action.run(), predicate);
public static ControlTriggerInputBased localOf(
String id,
Runnable action,
Predicate<InputEvent> predicate
) {
return localOf(
id,
input -> action.run(),
predicate
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased localOf(String id, Class<I> inputType,
Consumer<I> action, Predicate<I>... predicates) {
return localOf(id, createCheckedAction(inputType, action),
createCheckedCompoundPredicate(inputType, predicates));
public static <I extends InputEvent> ControlTriggerInputBased localOf(
String id,
Class<I> inputType,
Consumer<I> action,
Predicate<I>... predicates
) {
return localOf(
id,
createCheckedAction(inputType, action),
createCheckedCompoundPredicate(inputType, predicates)
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased localOf(String id, Class<I> inputType,
Runnable action, Predicate<I>... predicates) {
return localOf(id, inputType, input -> action.run(), predicates);
public static <I extends InputEvent> ControlTriggerInputBased localOf(
String id,
Class<I> inputType,
Runnable action,
Predicate<I>... predicates
) {
return localOf(
id,
inputType,
input -> action.run(),
predicates
);
}
@SafeVarargs
public static ControlTriggerInputBased localOf(String id, Consumer<InputEvent> action,
Predicate<InputEvent>... predicates) {
return localOf(id, InputEvent.class, action, predicates);
public static ControlTriggerInputBased localOf(
String id,
Consumer<InputEvent> action,
Predicate<InputEvent>... predicates
) {
return localOf(
id,
InputEvent.class,
action,
predicates
);
}
@SafeVarargs
public static <I extends InputEvent> ControlTriggerInputBased localOf(String id, Runnable action,
Predicate<InputEvent>... predicates) {
return of(id, input -> action.run(), predicates);
public static <I extends InputEvent> ControlTriggerInputBased localOf(
String id,
Runnable action,
Predicate<InputEvent>... predicates
) {
return of(
id,
input -> action.run(),
predicates
);
}
private static <I extends InputEvent> BiConsumer<InputEvent, ControlData> createCheckedDataWriter(
Class<I> inputType, BiConsumer<I, ControlData> dataWriter) {
Class<I> inputType,
BiConsumer<I, ControlData> dataWriter
) {
return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control);
}
private static <I extends InputEvent> Consumer<InputEvent> createCheckedAction(Class<I> inputType,
Consumer<I> action) {
private static <I extends InputEvent> Consumer<InputEvent> createCheckedAction(
Class<I> inputType,
Consumer<I> action
) {
return inputEvent -> action.accept(inputType.cast(inputEvent));
}
private static <I extends InputEvent> Predicate<InputEvent> createCheckedCompoundPredicate(Class<I> inputType,
Predicate<I>[] predicates) {
private static <I extends InputEvent> Predicate<InputEvent> createCheckedCompoundPredicate(
Class<I> inputType,
Predicate<I>[] predicates
) {
return new CompoundCastPredicate<>(inputType, predicates);
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.comms.controls;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.common.comms.packets.Packet;
public class InputBasedControls {
@ -32,15 +32,16 @@ public class InputBasedControls {
this.client = client;
this.controls = ControlTriggerRegistry.getInstance().values().stream()
.filter(ControlTriggerInputBased.class::isInstance).toArray(ControlTriggerInputBased[]::new);
.filter(ControlTriggerInputBased.class::isInstance)
.toArray(ControlTriggerInputBased[]::new);
}
public void handleInput(Input input) {
public void handleInput(InputEvent event) {
for (ControlTriggerInputBased c : controls) {
Packet packet = c.onInputEvent(input.getEvent());
Packet packet = c.onInputEvent(event);
if (packet != null) {
input.consume();
event.consume();
client.getComms().sendPacket(packet);
break;
}

View File

@ -21,6 +21,7 @@ package ru.windcorp.progressia.client.comms.localhost;
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
public class LocalServerCommsChannel extends ServerCommsChannel {
@ -34,7 +35,11 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
public void connect(String login) {
setState(State.CONNECTED);
this.localClient = new LocalClient(server.getClientManager().grabClientId(), login, this);
this.localClient = new LocalClient(
server.getClientManager().grabClientId(),
login,
this
);
server.getClientManager().addClient(localClient);
}
@ -50,7 +55,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
@Override
public void disconnect() {
// Do nothing
ServerState.getInstance().getClientManager().disconnectClient(localClient);
}
}

View File

@ -0,0 +1,68 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.events;
import ru.windcorp.progressia.client.Client;
/**
* An interface for all events issued by a {@link Client}.
*/
public interface ClientEvent {
/**
* Returns the client instance that this event happened on.
*
* @return the client
*/
Client getClient();
/**
* Sets the client instance that the event is posted on. The value provided
* to this method must be returned by subsequent calls to
* {@link #getClient()}. Do not call this method when handling the event.
*
* @param client the client dispatching the event or {@code null} to unbind
* any previously bound client
*/
void setClient(Client client);
/**
* A default implementation of {@link ClientEvent}. This is not necessarily
* extended by client events.
*/
public static abstract class Default implements ClientEvent {
private Client client;
public Default(Client client) {
this.client = client;
}
@Override
public Client getClient() {
return client;
}
@Override
public void setClient(Client client) {
this.client = client;
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.events;
import ru.windcorp.progressia.client.Client;
import ru.windcorp.progressia.common.world.entity.EntityDataPlayer;
public interface NewLocalEntityEvent extends ClientEvent {
EntityDataPlayer getNewEntity();
EntityDataPlayer getPreviousEntity();
public class Immutable extends ClientEvent.Default implements NewLocalEntityEvent {
private final EntityDataPlayer newEntity;
private final EntityDataPlayer previousEntity;
public Immutable(Client client, EntityDataPlayer newEntity, EntityDataPlayer previousEntity) {
super(client);
this.newEntity = newEntity;
this.previousEntity = previousEntity;
}
@Override
public EntityDataPlayer getNewEntity() {
return newEntity;
}
@Override
public EntityDataPlayer getPreviousEntity() {
return previousEntity;
}
}
}

View File

@ -56,6 +56,25 @@ public class Colors {
return color.mul(multiplier, multiplier, multiplier, 1, output);
}
public static Vec4 mix(Vec4 zero, Vec4 one, float t, Vec4 output) {
if (output == null) {
output = new Vec4();
}
if (t <= 0) {
return output.set(zero);
} else if (t >= 1) {
return output.set(one);
}
return output.set(
zero.x * (1 - t) + one.x * t,
zero.y * (1 - t) + one.y * t,
zero.z * (1 - t) + one.z * t,
zero.w * (1 - t) + one.w * t
);
}
public static Vec4 toVector(int argb, Vec4 output) {
output.w = ((argb & 0xFF000000) >>> 24) / (float) 0xFF; // Alpha
output.x = ((argb & 0x00FF0000) >>> 16) / (float) 0xFF; // Red

View File

@ -0,0 +1,61 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.graphics;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
public class ExponentAnimation {
private final float speed;
private float value;
public ExponentAnimation(float speed, float value) {
this.speed = speed;
this.value = value;
}
public float getSpeed() {
return speed;
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
public float update(float target, double timeStep) {
float difference = value - target;
value += difference * (1 - Math.exp(speed * timeStep));
float newDifference = value - target;
if (difference * newDifference < 0) {
// Whoops, we've overshot
value = target;
}
return value;
}
public float updateForFrame(float target) {
return update(target, GraphicsInterface.getFrameLength());
}
}

View File

@ -21,16 +21,10 @@ package ru.windcorp.progressia.client.graphics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.common.eventbus.Subscribe;
import java.util.Objects;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
public class GUI {
@ -45,19 +39,11 @@ public class GUI {
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
.synchronizedList(new ArrayList<>());
private static class ModifiableInput extends Input {
@Override
public void initialize(InputEvent event, Target target) {
super.initialize(event, target);
}
}
private static final ModifiableInput THE_INPUT = new ModifiableInput();
private GUI() {
}
public static void addBottomLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(layer);
layer.onAdded();
@ -65,6 +51,7 @@ public class GUI {
}
public static void addTopLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(0, layer);
layer.onAdded();
@ -72,12 +59,19 @@ public class GUI {
}
public static void removeLayer(Layer layer) {
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.remove(layer);
layer.onRemoved();
});
}
public static void updateLayer(Layer layer) {
modify(layers -> {
// Do nothing
});
}
private static void modify(LayerStackModification mod) {
MODIFICATION_QUEUE.add(mod);
}
@ -122,43 +116,12 @@ public class GUI {
LAYERS.forEach(Layer::invalidate);
}
private static void dispatchInputEvent(InputEvent event) {
Input.Target target;
if (event instanceof KeyEvent) {
if (((KeyEvent) event).isMouse()) {
target = Input.Target.HOVERED;
} else {
target = Input.Target.FOCUSED;
public static void dispatchInput(InputEvent event) {
synchronized (LAYERS) {
for (int i = 0; i < LAYERS.size(); ++i) {
LAYERS.get(i).handleInput(event);
}
} else if (event instanceof CursorEvent) {
target = Input.Target.HOVERED;
} else if (event instanceof WheelEvent) {
target = Input.Target.HOVERED;
} else if (event instanceof FrameResizeEvent) {
return;
} else {
target = Input.Target.ALL;
}
THE_INPUT.initialize(event, target);
LAYERS.forEach(l -> l.handleInput(THE_INPUT));
}
public static Object getEventSubscriber() {
return new Object() {
@Subscribe
public void onFrameResized(FrameResizeEvent event) {
GUI.invalidateEverything();
}
@Subscribe
public void onInput(InputEvent event) {
dispatchInputEvent(event);
}
};
}
}

View File

@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics;
import java.util.concurrent.atomic.AtomicBoolean;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
public abstract class Layer {
@ -106,7 +106,7 @@ public abstract class Layer {
protected abstract void doRender();
protected abstract void handleInput(Input input);
public abstract void handleInput(InputEvent input);
protected int getWidth() {
return GraphicsInterface.getFrameWidth();

View File

@ -19,6 +19,7 @@
package ru.windcorp.progressia.client.graphics.backend;
import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFWVidMode;
import static org.lwjgl.glfw.GLFW.*;
@ -43,6 +44,13 @@ public class GraphicsBackend {
private static boolean isGLFWInitialized = false;
private static boolean isOpenGLInitialized = false;
private static boolean allowDisablingCursor;
static {
String key = GraphicsBackend.class.getName() + ".allowDisablingCursor";
allowDisablingCursor = Boolean.parseBoolean(System.getProperty(key, "true"));
}
private static boolean forceCursorToCenter = false;
private GraphicsBackend() {
}
@ -114,6 +122,10 @@ public class GraphicsBackend {
frameLength = now - frameStart;
frameStart = now;
}
if (forceCursorToCenter) {
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
}
}
static void endFrame() {
@ -159,14 +171,27 @@ public class GraphicsBackend {
public static void setFullscreen() {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(getWindowHandle(), glfwGetPrimaryMonitor(), 0, 0, vidmode.width(), vidmode.height(), 0);
glfwSetWindowMonitor(
getWindowHandle(),
glfwGetPrimaryMonitor(),
0,
0,
vidmode.width(),
vidmode.height(),
0);
isFullscreen = true;
}
public static void setWindowed() {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(getWindowHandle(), 0, (vidmode.width() - getFrameWidth()) / 2,
(vidmode.height() - getFrameHeight()) / 2, getFrameWidth(), getFrameHeight(), 0);
glfwSetWindowMonitor(
getWindowHandle(),
0,
(vidmode.width() - getFrameWidth()) / 2,
(vidmode.height() - getFrameHeight()) / 2,
getFrameWidth(),
getFrameHeight(),
0);
isFullscreen = false;
}
@ -181,10 +206,18 @@ public class GraphicsBackend {
}
public static boolean isMouseCaptured() {
if (!allowDisablingCursor) {
return forceCursorToCenter;
}
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
}
public static void setMouseCaptured(boolean capture) {
if (!allowDisablingCursor) {
forceCursorToCenter = capture;
return;
}
int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);

View File

@ -69,6 +69,10 @@ public class GraphicsInterface {
InputHandler.register(listener);
}
public static void unsubscribeFromInputEvents(Object listener) {
InputHandler.unregister(listener);
}
public static void startNextLayer() {
GraphicsBackend.startNextLayer();
}

View File

@ -39,6 +39,7 @@ public class InputHandler {
public void initialize(int key, int scancode, int action, int mods) {
this.setTime(GraphicsInterface.getTime());
this.setConsumed(false);
this.key = key;
this.scancode = scancode;
this.action = action;
@ -49,11 +50,17 @@ public class InputHandler {
private static final ModifiableKeyEvent THE_KEY_EVENT = new ModifiableKeyEvent();
static void handleKeyInput(long window, int key, int scancode, int action, int mods) {
static void handleKeyInput(
long window,
int key,
int scancode,
int action,
int mods
) {
if (GraphicsBackend.getWindowHandle() != window)
return;
THE_KEY_EVENT.initialize(key, scancode, action, mods);
dispatch(THE_KEY_EVENT);
INPUT_EVENT_BUS.post(THE_KEY_EVENT);
switch (action) {
case GLFW.GLFW_PRESS:
@ -65,7 +72,12 @@ public class InputHandler {
}
}
static void handleMouseButtonInput(long window, int key, int action, int mods) {
static void handleMouseButtonInput(
long window,
int key,
int action,
int mods
) {
handleKeyInput(window, key, Integer.MAX_VALUE - key, action, mods);
}
@ -79,6 +91,7 @@ public class InputHandler {
public void initialize(double x, double y) {
this.setTime(GraphicsInterface.getTime());
this.setConsumed(false);
getNewPosition().set(x, y);
}
@ -86,7 +99,11 @@ public class InputHandler {
private static final ModifiableCursorMoveEvent THE_CURSOR_MOVE_EVENT = new ModifiableCursorMoveEvent();
static void handleMouseMoveInput(long window, double x, double y) {
static void handleMouseMoveInput(
long window,
double x,
double y
) {
if (GraphicsBackend.getWindowHandle() != window)
return;
y = GraphicsInterface.getFrameHeight() - y; // Flip y axis
@ -94,7 +111,7 @@ public class InputHandler {
InputTracker.initializeCursorPosition(x, y);
THE_CURSOR_MOVE_EVENT.initialize(x, y);
dispatch(THE_CURSOR_MOVE_EVENT);
INPUT_EVENT_BUS.post(THE_CURSOR_MOVE_EVENT);
InputTracker.getCursorPosition().set(x, y);
}
@ -109,6 +126,7 @@ public class InputHandler {
public void initialize(double xOffset, double yOffset) {
this.setTime(GraphicsInterface.getTime());
this.setConsumed(false);
this.getOffset().set(xOffset, yOffset);
}
@ -116,11 +134,15 @@ public class InputHandler {
private static final ModifiableWheelScrollEvent THE_WHEEL_SCROLL_EVENT = new ModifiableWheelScrollEvent();
static void handleWheelScroll(long window, double xoffset, double yoffset) {
static void handleWheelScroll(
long window,
double xoffset,
double yoffset
) {
if (GraphicsBackend.getWindowHandle() != window)
return;
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
dispatch(THE_WHEEL_SCROLL_EVENT);
INPUT_EVENT_BUS.post(THE_WHEEL_SCROLL_EVENT);
}
// FrameResizeEvent
@ -133,6 +155,7 @@ public class InputHandler {
public void initialize(int width, int height) {
this.setTime(GraphicsInterface.getTime());
this.setConsumed(false);
this.getNewSize().set(width, height);
}
@ -143,19 +166,22 @@ public class InputHandler {
/*
* NB: this is NOT a GLFW callback, the raw callback is in GraphicsBackend
*/
static void handleFrameResize(int width, int height) {
static void handleFrameResize(
int width,
int height
) {
THE_FRAME_RESIZE_EVENT.initialize(width, height);
dispatch(THE_FRAME_RESIZE_EVENT);
INPUT_EVENT_BUS.post(THE_FRAME_RESIZE_EVENT);
}
// Misc
private static void dispatch(InputEvent event) {
INPUT_EVENT_BUS.post(event);
}
public static void register(Object listener) {
INPUT_EVENT_BUS.register(listener);
}
public static void unregister(Object listener) {
INPUT_EVENT_BUS.unregister(listener);
}
}

View File

@ -24,7 +24,10 @@ import gnu.trove.set.hash.TIntHashSet;
public class InputTracker {
private static final Vec2d CURSOR_POSITION = new Vec2d(Double.NaN, Double.NaN);
private static final Vec2d CURSOR_POSITION = new Vec2d(
Double.NaN,
Double.NaN
);
private static final TIntSet PRESSED_KEYS = new TIntHashSet(256);

View File

@ -24,7 +24,11 @@ import static org.lwjgl.system.MemoryUtil.*;
import org.lwjgl.opengl.GL;
import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
class LWJGLInitializer {
@ -92,16 +96,35 @@ class LWJGLInitializer {
private static void setupWindowCallbacks() {
long handle = GraphicsBackend.getWindowHandle();
glfwSetFramebufferSizeCallback(handle, GraphicsBackend::onFrameResized);
glfwSetFramebufferSizeCallback(
handle,
GraphicsBackend::onFrameResized
);
glfwSetKeyCallback(handle, InputHandler::handleKeyInput);
glfwSetMouseButtonCallback(handle, InputHandler::handleMouseButtonInput);
glfwSetMouseButtonCallback(
handle,
InputHandler::handleMouseButtonInput
);
glfwSetCursorPosCallback(handle, InputHandler::handleMouseMoveInput);
glfwSetScrollCallback(handle, InputHandler::handleWheelScroll);
GraphicsInterface.subscribeToInputEvents(GUI.getEventSubscriber());
GraphicsInterface.subscribeToInputEvents(new Object() {
@Subscribe
public void onFrameResized(FrameResizeEvent event) {
GUI.invalidateEverything();
}
@Subscribe
public void onInputEvent(InputEvent event) {
GUI.dispatchInput(event);
}
});
}
}

View File

@ -34,13 +34,19 @@ public class OpenGLObjectTracker {
private static final ReferenceQueue<OpenGLDeletable> DELETE_QUEUE = new ReferenceQueue<>();
public synchronized static void register(OpenGLDeletable object, IntConsumer glDeleter) {
GLPhantomReference<OpenGLDeletable> glRef = new GLPhantomReference<>(object, DELETE_QUEUE, object.getHandle(),
glDeleter);
GLPhantomReference<OpenGLDeletable> glRef = new GLPhantomReference<>(
object,
DELETE_QUEUE,
object.getHandle(),
glDeleter
);
TO_DELETE.add(glRef);
}
public static void deleteAllObjects() {
for (GLPhantomReference<OpenGLDeletable> glRef : TO_DELETE) {
for (
GLPhantomReference<OpenGLDeletable> glRef : TO_DELETE
) {
glRef.clear();
}
}
@ -69,16 +75,20 @@ public class OpenGLObjectTracker {
* It is possible to create a phantom reference with a {@code null}
* queue, but such a reference is completely useless: Its {@code get}
* method will always return {@code null} and, since it does not have a
* queue, it will never be enqueued.
* queue,
* it will never be enqueued.
*
* @param referent
* the object the new phantom reference will refer to
* @param q
* the queue with which the reference is to be registered, or
* {@code null} if registration is not required
* @param referent the object the new phantom reference will refer to
* @param q the queue with which the reference is to be
* registered,
* or {@code null} if registration is not required
*/
public GLPhantomReference(T referent, ReferenceQueue<? super T> q, int referentGLhandle,
IntConsumer GLDeleter) {
public GLPhantomReference(
T referent,
ReferenceQueue<? super T> q,
int referentGLhandle,
IntConsumer GLDeleter
) {
super(referent, q);
this.referentGLhandle = referentGLhandle;
this.GLDeleter = GLDeleter;

View File

@ -41,7 +41,11 @@ public class RenderTaskQueue {
HANDLER.invokeNow(task);
}
public static <E extends Exception> void waitAndInvoke(ThrowingRunnable<E> task) throws InterruptedException, E {
public static <E extends Exception> void waitAndInvoke(
ThrowingRunnable<E> task
)
throws InterruptedException,
E {
HANDLER.waitAndInvoke(task);
}

View File

@ -23,7 +23,9 @@ import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.GL_STREAM_DRAW;
public enum Usage { // TODO add _COPY and _READ, pref. as another enum
STATIC(GL_STATIC_DRAW), DYNAMIC(GL_DYNAMIC_DRAW), STREAM(GL_STREAM_DRAW);
STATIC(GL_STATIC_DRAW),
DYNAMIC(GL_DYNAMIC_DRAW),
STREAM(GL_STREAM_DRAW);
private final int glCode;

View File

@ -28,7 +28,8 @@ import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGL
public class VertexBufferObject implements OpenGLDeletable {
public static enum BindTarget {
ARRAY(GL_ARRAY_BUFFER), ELEMENT_ARRAY(GL_ELEMENT_ARRAY_BUFFER);
ARRAY(GL_ARRAY_BUFFER),
ELEMENT_ARRAY(GL_ELEMENT_ARRAY_BUFFER);
private final int glCode;

View File

@ -32,7 +32,12 @@ public class CombinedShader extends Shader {
for (int i = 1; i < resources.length; ++i) {
if (ShaderType.guessByResourceName(resources[i]) != first) {
throw new IllegalArgumentException(
"Deduced shader types of " + resources[0] + " and " + resources[i] + " differ");
"Deduced shader types of "
+ resources[0]
+ " and "
+ resources[i]
+ " differ"
);
}
}
@ -66,8 +71,19 @@ public class CombinedShader extends Shader {
if (contents.codePointAt(versionIndex) == '#') {
final String versionAnnotation = "#version ";
if (contents.regionMatches(versionIndex, versionAnnotation, 0, versionAnnotation.length())) {
contents = contents.substring(versionIndex + versionAnnotation.length() + "120".length());
if (
contents.regionMatches(
versionIndex,
versionAnnotation,
0,
versionAnnotation.length()
)
) {
contents = contents.substring(
versionIndex
+ versionAnnotation.length()
+ "120".length()
);
}
}

View File

@ -57,7 +57,10 @@ public class Shader implements OpenGLDeletable {
if (resource.contains("fsh"))
return FRAGMENT;
throw new IllegalArgumentException("Cannot deduce shader type from resource name \"" + resource + "\"");
throw new IllegalArgumentException(
"Cannot deduce shader type from resource name \"" +
resource + "\""
);
}
}
@ -87,7 +90,10 @@ public class Shader implements OpenGLDeletable {
}
public Shader(String resource) {
this(ShaderType.guessByResourceName(resource), getShaderResource(resource).readAsString());
this(
ShaderType.guessByResourceName(resource),
getShaderResource(resource).readAsString()
);
}
@Override

View File

@ -51,29 +51,104 @@ public class AttributeVertexArray extends Attribute {
}
}
public void set(int size, boolean normalized, int stride, ByteBuffer pointer) {
glVertexAttribPointer(handle, size, GL_BYTE, normalized, stride, pointer);
public void set(
int size,
boolean normalized,
int stride,
ByteBuffer pointer
) {
glVertexAttribPointer(
handle,
size,
GL_BYTE,
normalized,
stride,
pointer
);
}
public void set(int size, boolean normalized, int stride, FloatBuffer pointer) {
glVertexAttribPointer(handle, size, GL_FLOAT, normalized, stride, pointer);
public void set(
int size,
boolean normalized,
int stride,
FloatBuffer pointer
) {
glVertexAttribPointer(
handle,
size,
GL_FLOAT,
normalized,
stride,
pointer
);
}
public void set(int size, boolean normalized, int stride, IntBuffer pointer) {
glVertexAttribPointer(handle, size, GL_INT, normalized, stride, pointer);
public void set(
int size,
boolean normalized,
int stride,
IntBuffer pointer
) {
glVertexAttribPointer(
handle,
size,
GL_INT,
normalized,
stride,
pointer
);
}
public void set(int size, boolean normalized, int stride, ShortBuffer pointer) {
glVertexAttribPointer(handle, size, GL_SHORT, normalized, stride, pointer);
public void set(
int size,
boolean normalized,
int stride,
ShortBuffer pointer
) {
glVertexAttribPointer(
handle,
size,
GL_SHORT,
normalized,
stride,
pointer
);
}
public void set(int size, int type, boolean normalized, int stride, long pointer) {
glVertexAttribPointer(handle, size, type, normalized, stride, pointer);
public void set(
int size,
int type,
boolean normalized,
int stride,
long pointer
) {
glVertexAttribPointer(
handle,
size,
type,
normalized,
stride,
pointer
);
}
public void set(int size, int type, boolean normalized, int stride, VertexBufferObject vbo, long offset) {
public void set(
int size,
int type,
boolean normalized,
int stride,
VertexBufferObject vbo,
long offset
) {
glBindBuffer(GL_ARRAY_BUFFER, vbo.getHandle());
glVertexAttribPointer(handle, size, type, normalized, stride, offset);
glVertexAttribPointer(
handle,
size,
type,
normalized,
stride,
offset
);
}
}

View File

@ -37,7 +37,8 @@ public abstract class FlatRenderHelper extends ShapeRenderHelper {
float width = GraphicsInterface.getFrameWidth();
float height = GraphicsInterface.getFrameHeight();
return finalTransform.identity().translate(-1, -1, 0).scale(2 / width, 2 / height, 1 / MAX_DEPTH)
return finalTransform.identity().translate(-1, -1, 0)
.scale(2 / width, 2 / height, 1 / MAX_DEPTH)
.mul(getTransform());
}

View File

@ -33,8 +33,10 @@ public class FlatRenderProgram extends ShapeRenderProgram {
private static FlatRenderProgram def = null;
public static void init() {
def = new FlatRenderProgram(new String[] { "FlatDefault.vertex.glsl" },
new String[] { "FlatDefault.fragment.glsl" });
def = new FlatRenderProgram(
new String[] { "FlatDefault.vertex.glsl" },
new String[] { "FlatDefault.fragment.glsl" }
);
}
public static FlatRenderProgram getDefault() {
@ -46,13 +48,20 @@ public class FlatRenderProgram extends ShapeRenderProgram {
private static final String FLAT_VERTEX_SHADER_RESOURCE = "Flat.vertex.glsl";
private static final String FLAT_FRAGMENT_SHADER_RESOURCE = "Flat.fragment.glsl";
private static final String MASK_COUNT_UNIFORM_NAME = "maskCount", MASKS_UNIFORM_NAME = "masks";
private static final String MASK_COUNT_UNIFORM_NAME = "maskCount",
MASKS_UNIFORM_NAME = "masks";
private final Uniform1Int maskCountUniform;
private final Uniform2Float masksUniform;
public FlatRenderProgram(String[] vertexShaderResources, String[] fragmentShaderResources) {
super(attachVertexShader(vertexShaderResources), attachFragmentShader(fragmentShaderResources));
public FlatRenderProgram(
String[] vertexShaderResources,
String[] fragmentShaderResources
) {
super(
attachVertexShader(vertexShaderResources),
attachFragmentShader(fragmentShaderResources)
);
this.maskCountUniform = getUniform(MASK_COUNT_UNIFORM_NAME).as1Int();
this.masksUniform = getUniform(MASKS_UNIFORM_NAME).as2Float();

View File

@ -88,7 +88,8 @@ public class Mask {
@Override
public String toString() {
return "(" + getStartX() + "; " + getStartY() + ") -> (" + getEndX() + "; " + getEndY() + ")";
return "(" + getStartX() + "; " + getStartY() +
") -> (" + getEndX() + "; " + getEndY() + ")";
}
}

View File

@ -24,8 +24,9 @@ import org.lwjgl.BufferUtils;
public class MaskStack {
private final FloatBuffer buffer = BufferUtils
.createFloatBuffer(FlatRenderProgram.MASK_STACK_SIZE * TransformedMask.SIZE_IN_FLOATS);
private final FloatBuffer buffer = BufferUtils.createFloatBuffer(
FlatRenderProgram.MASK_STACK_SIZE * TransformedMask.SIZE_IN_FLOATS
);
public void pushMask(TransformedMask mask) {
mask.writeToBuffer(buffer);

View File

@ -191,7 +191,8 @@ public class RenderTarget {
assembleCurrentClipFromFaces();
float depth = this.depth--;
Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
final float kostyl = 1e-2f;
Mat4 transform = new Mat4().translate(0, 0, depth).scale(1, 1, kostyl).mul(getTransform());
assembled.add(new Clip(maskStack, transform, renderable));
}

View File

@ -42,8 +42,14 @@ public class TransformedMask {
private Vec4 endXstartY = null;
private Vec4 endXendY = null;
public TransformedMask(Vec2 origin, Vec2 width, Vec2 height, Vec2 counterOrigin, Vec2 counterWidth,
Vec2 counterHeight) {
public TransformedMask(
Vec2 origin,
Vec2 width,
Vec2 height,
Vec2 counterOrigin,
Vec2 counterWidth,
Vec2 counterHeight
) {
set(origin, width, height, counterOrigin, counterWidth, counterHeight);
}
@ -55,8 +61,14 @@ public class TransformedMask {
// Do nothing
}
public TransformedMask set(Vec2 origin, Vec2 width, Vec2 height, Vec2 counterOrigin, Vec2 counterWidth,
Vec2 counterHeight) {
public TransformedMask set(
Vec2 origin,
Vec2 width,
Vec2 height,
Vec2 counterOrigin,
Vec2 counterWidth,
Vec2 counterHeight
) {
this.origin.set(origin.x, origin.y);
this.width.set(width.x, width.y);
this.height.set(height.x, height.y);
@ -100,17 +112,35 @@ public class TransformedMask {
}
private void setFields() {
origin.set(startXstartY.x, startXstartY.y);
origin.set(
startXstartY.x,
startXstartY.y
);
width.set(endXstartY.x - startXstartY.x, endXstartY.y - startXstartY.y);
width.set(
endXstartY.x - startXstartY.x,
endXstartY.y - startXstartY.y
);
height.set(startXendY.x - startXstartY.x, startXendY.y - startXstartY.y);
height.set(
startXendY.x - startXstartY.x,
startXendY.y - startXstartY.y
);
counterOrigin.set(endXendY.x, endXendY.y);
counterOrigin.set(
endXendY.x,
endXendY.y
);
counterWidth.set(startXendY.x - endXendY.x, startXendY.y - endXendY.y);
counterWidth.set(
startXendY.x - endXendY.x,
startXendY.y - endXendY.y
);
counterHeight.set(endXstartY.x - endXendY.x, endXstartY.y - endXendY.y);
counterHeight.set(
endXstartY.x - endXendY.x,
endXstartY.y - endXendY.y
);
}
public void writeToBuffer(FloatBuffer output) {

View File

@ -33,19 +33,22 @@ public class Font {
private final float align;
private final Vec4 color;
public Font(Typeface typeface, int style, float align, Vec4 color) {
private final int scale;
public Font(Typeface typeface, int style, float align, Vec4 color, int scale) {
this.typeface = typeface;
this.style = style;
this.align = align;
this.color = color;
this.scale = scale;
}
public Font(Typeface typeface, int style, float align, int color) {
this(typeface, style, align, Colors.toVector(color));
public Font(Typeface typeface, int style, float align, int color, int scale) {
this(typeface, style, align, Colors.toVector(color), scale);
}
public Font(Typeface typeface) {
this(typeface, Typeface.Style.PLAIN, Typeface.ALIGN_LEFT, Colors.WHITE);
this(typeface, Typeface.Style.PLAIN, Typeface.ALIGN_LEFT, Colors.WHITE, 2);
}
public Font() {
@ -68,24 +71,62 @@ public class Font {
return color;
}
public int getScale() {
return scale;
}
private Renderable applyScale(Renderable unscaled) {
if (scale == 1) {
return unscaled;
}
return renderer -> {
renderer.pushTransform().scale(scale);
unscaled.render(renderer);
renderer.popTransform();
};
}
public Renderable assemble(CharSequence chars, float maxWidth) {
return typeface.assembleStatic(chars, style, align, maxWidth, color);
return applyScale(typeface.assembleStatic(chars, style, align, maxWidth, color));
}
public Renderable assembleDynamic(Supplier<CharSequence> supplier, float maxWidth) {
return typeface.assembleDynamic(supplier, style, align, maxWidth, color);
return applyScale(typeface.assembleDynamic(supplier, style, align, maxWidth, color));
}
public Renderable assemble(CharSequence chars) {
return assemble(chars, Float.POSITIVE_INFINITY);
}
public Renderable assembleDynamic(Supplier<CharSequence> supplier) {
return assembleDynamic(supplier, Float.POSITIVE_INFINITY);
}
public int getWidth(CharSequence chars, float maxWidth) {
return typeface.getWidth(chars, style, align, maxWidth);
return scale * typeface.getWidth(chars, style, align, maxWidth);
}
public int getHeight(CharSequence chars, float maxWidth) {
return typeface.getHeight(chars, style, align, maxWidth);
return scale * typeface.getHeight(chars, style, align, maxWidth);
}
public Vec2i getSize(CharSequence chars, float maxWidth, Vec2i result) {
return typeface.getSize(chars, style, align, maxWidth, result);
result = typeface.getSize(chars, style, align, maxWidth, result);
result.mul(scale);
return result;
}
public int getWidth(CharSequence chars) {
return getWidth(chars, Float.POSITIVE_INFINITY);
}
public int getHeight(CharSequence chars) {
return getHeight(chars, Float.POSITIVE_INFINITY);
}
public Vec2i getSize(CharSequence chars, Vec2i result) {
return getSize(chars, Float.POSITIVE_INFINITY, result);
}
public boolean supports(char c) {
@ -96,12 +137,11 @@ public class Font {
* Creates a new {@link Font} with the specified {@code style} exactly. This
* object's style is ignored.
*
* @param style
* the new style
* @param style the new style
* @return the new font
*/
public Font withStyle(int style) {
return new Font(getTypeface(), style, getAlign(), getColor());
return new Font(getTypeface(), style, getAlign(), getColor(), getScale());
}
public Font deriveBold() {
@ -153,15 +193,19 @@ public class Font {
}
public Font withAlign(float align) {
return new Font(getTypeface(), getStyle(), align, getColor());
return new Font(getTypeface(), getStyle(), align, getColor(), getScale());
}
public Font withColor(Vec4 color) {
return new Font(getTypeface(), getStyle(), getAlign(), color);
return new Font(getTypeface(), getStyle(), getAlign(), color, getScale());
}
public Font withColor(int color) {
return new Font(getTypeface(), getStyle(), getAlign(), color);
return new Font(getTypeface(), getStyle(), getAlign(), color, getScale());
}
public Font withScale(int scale) {
return new Font(getTypeface(), getStyle(), getAlign(), getColor(), scale);
}
}

View File

@ -81,7 +81,8 @@ public class GNUUnifontLoader {
private static BufferedReader createReader(Resource resource) throws IOException {
return new BufferedReader(
new InputStreamReader(new GZIPInputStream(resource.getInputStream()), StandardCharsets.UTF_8));
new InputStreamReader(new GZIPInputStream(resource.getInputStream()), StandardCharsets.UTF_8)
);
}
private static Stream<String> createStream(BufferedReader reader) {
@ -96,8 +97,13 @@ public class GNUUnifontLoader {
char c = getChar(declar);
TextureDataEditor editor = new TextureDataEditor(width, GNUUnifont.HEIGHT, width, GNUUnifont.HEIGHT,
TEXTURE_SETTINGS);
TextureDataEditor editor = new TextureDataEditor(
width,
GNUUnifont.HEIGHT,
width,
GNUUnifont.HEIGHT,
TEXTURE_SETTINGS
);
for (int y = 0; y < GNUUnifont.HEIGHT; ++y) {
for (int x = 0; x < width; ++x) {
@ -159,7 +165,8 @@ public class GNUUnifontLoader {
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))) {
throw new IOException(
"Illegal char in declar \"" + declar + "\" at index " + i + "; expected 0-9A-F");
"Illegal char in declar \"" + declar + "\" at index " + i + "; expected 0-9A-F"
);
}
}
}
@ -183,7 +190,8 @@ public class GNUUnifontLoader {
}
private static Collector<AtlasGlyph, ?, TCharObjectMap<Texture>> createMapper() {
return Collector.of(TCharObjectHashMap<Texture>::new,
return Collector.of(
TCharObjectHashMap<Texture>::new,
(map, glyph) -> map.put(glyph.c, glyph.texture),
@ -192,7 +200,8 @@ public class GNUUnifontLoader {
return a;
},
Characteristics.UNORDERED);
Characteristics.UNORDERED
);
}
private GNUUnifontLoader() {

View File

@ -29,8 +29,12 @@ import ru.windcorp.progressia.common.util.Vectors;
public abstract class Typeface extends Named {
public static class Style {
public static final int BOLD = 1 << 0, ITALIC = 1 << 1, UNDERLINED = 1 << 2, STRIKETHRU = 1 << 3,
SHADOW = 1 << 4, OUTLINED = 1 << 5;
public static final int BOLD = 1 << 0,
ITALIC = 1 << 1,
UNDERLINED = 1 << 2,
STRIKETHRU = 1 << 3,
SHADOW = 1 << 4,
OUTLINED = 1 << 5;
public static final int PLAIN = 0;
@ -67,19 +71,40 @@ public abstract class Typeface extends Named {
super(name);
}
public abstract Renderable assembleStatic(CharSequence chars, int style, float align, float maxWidth, Vec4 color);
public abstract Renderable assembleStatic(
CharSequence chars,
int style,
float align,
float maxWidth,
Vec4 color
);
public abstract Renderable assembleDynamic(Supplier<CharSequence> supplier, int style, float align, float maxWidth,
Vec4 color);
public abstract Renderable assembleDynamic(
Supplier<CharSequence> supplier,
int style,
float align,
float maxWidth,
Vec4 color
);
public int getWidth(CharSequence chars, int style, float align, float maxWidth) {
public int getWidth(
CharSequence chars,
int style,
float align,
float maxWidth
) {
Vec2i v = Vectors.grab2i();
v = getSize(chars, style, align, maxWidth, v);
Vectors.release(v);
return v.x;
}
public int getHeight(CharSequence chars, int style, float align, float maxWidth) {
public int getHeight(
CharSequence chars,
int style,
float align,
float maxWidth
) {
Vec2i v = Vectors.grab2i();
v = getSize(chars, style, align, maxWidth, v);
Vectors.release(v);
@ -88,7 +113,13 @@ public abstract class Typeface extends Named {
public abstract int getLineHeight();
public abstract Vec2i getSize(CharSequence chars, int style, float align, float maxWidth, Vec2i result);
public abstract Vec2i getSize(
CharSequence chars,
int style,
float align,
float maxWidth,
Vec2i result
);
public abstract boolean supports(char c);

View File

@ -43,29 +43,30 @@ public abstract class BasicButton extends Component {
private boolean isPressed = false;
private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>());
public BasicButton(String name, String label, Font labelFont) {
public BasicButton(String name, Label label) {
super(name);
this.label = new Label(name + ".Label", labelFont, label);
this.label = label;
setLayout(new LayoutAlign(10));
if (label != null) {
addChild(this.label);
}
setFocusable(true);
reassembleAt(ARTrigger.HOVER, ARTrigger.FOCUS, ARTrigger.ENABLE);
// Click triggers
addListener(KeyEvent.class, e -> {
if (e.isRepeat()) {
return false;
} else if (
addInputListener(KeyEvent.class, e -> {
if (e.isRepeat())
return;
if (
e.isLeftMouseButton() ||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
e.getKey() == GLFW.GLFW_KEY_ENTER
) {
setPressed(e.isPress());
return true;
} else {
return false;
e.consume();
}
});
@ -104,6 +105,10 @@ public abstract class BasicButton extends Component {
});
}
public BasicButton(String name, String label, Font labelFont) {
this(name, new Label(name + ".Label", labelFont, label));
}
public BasicButton(String name, String label) {
this(name, label, new Font());
}
@ -120,6 +125,7 @@ public abstract class BasicButton extends Component {
public void setPressed(boolean isPressed) {
if (this.isPressed != isPressed) {
this.isPressed = isPressed;
requestReassembly();
if (isPressed) {
takeFocus();
@ -148,4 +154,8 @@ public abstract class BasicButton extends Component {
return label;
}
public boolean hasLabel() {
return label != null;
}
}

View File

@ -25,10 +25,17 @@ import ru.windcorp.progressia.client.graphics.Colors;
public class Button extends BasicButton {
public static final int MARGIN = 2;
public static final int BORDER = 2;
public Button(String name, String label, Font labelFont) {
super(name, label, labelFont);
}
public Button(String name, Label label) {
super(name, label);
}
public Button(String name, String label) {
this(name, label, new Font());
}
@ -56,17 +63,25 @@ public class Button extends BasicButton {
} else {
backgroundColor = Colors.WHITE;
}
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, backgroundColor);
target.fill(
getX() + MARGIN,
getY() + MARGIN,
getWidth() - 2 * MARGIN,
getHeight() - 2 * MARGIN,
backgroundColor
);
}
// Change label font color
if (hasLabel()) {
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {

View File

@ -25,25 +25,27 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatRenderHelper;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget.Clip;
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
import ru.windcorp.progressia.client.graphics.input.bus.InputListener;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.util.Named;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.util.crash.ReportingEventBus;
@ -55,7 +57,7 @@ public class Component extends Named {
private Component parent = null;
private EventBus eventBus = null;
private InputBus inputBus = null;
private final InputBus inputBus = new InputBus(this);
private int x, y;
private int width, height;
@ -76,6 +78,9 @@ public class Component extends Named {
public Component(String name) {
super(name);
// Update hover flag when cursor moves
addInputListener(CursorMoveEvent.class, this::updateHoverFlag, InputBus.Option.ALWAYS);
}
public Component getParent() {
@ -522,6 +527,10 @@ public class Component extends Named {
}
}
private void updateHoverFlag(CursorMoveEvent e) {
setHovered(contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY()));
}
public void addListener(Object listener) {
if (eventBus == null) {
eventBus = ReportingEventBus.create(getName());
@ -542,118 +551,30 @@ public class Component extends Named {
eventBus.post(event);
}
public <T extends InputEvent> void addListener(Class<? extends T> type, boolean handlesConsumed,
InputListener<T> listener) {
if (inputBus == null) {
inputBus = new InputBus();
public <T extends InputEvent> void addInputListener(Class<? extends T> type, InputListener<T> listener, InputBus.Option... options) {
inputBus.register(type, listener, options);
}
inputBus.register(type, handlesConsumed, listener);
public void addKeyListener(KeyMatcher matcher, InputListener<? super KeyEvent> listener, InputBus.Option... options) {
inputBus.register(matcher, listener, options);
}
public <T extends InputEvent> void addListener(Class<? extends T> type, InputListener<T> listener) {
if (inputBus == null) {
inputBus = new InputBus();
}
inputBus.register(type, listener);
}
public void removeListener(InputListener<?> listener) {
if (inputBus != null) {
public void removeInputListener(InputListener<?> listener) {
inputBus.unregister(listener);
}
}
protected void handleInput(Input input) {
if (inputBus != null && isEnabled()) {
inputBus.dispatch(input);
}
}
public void dispatchInput(Input input) {
try {
switch (input.getTarget()) {
case FOCUSED:
dispatchInputToFocused(input);
break;
case HOVERED:
dispatchInputToHovered(input);
break;
case ALL:
default:
dispatchInputToAll(input);
break;
}
} catch (Exception e) {
throw CrashReports.report(e, "Could not dispatch input to Component %s", this);
}
}
private void dispatchInputToFocused(Input input) {
Component c = findFocused();
if (c == null)
return;
if (attemptFocusTransfer(input, c))
return;
while (c != null) {
c.handleInput(input);
c = c.getParent();
}
}
private void dispatchInputToHovered(Input input) {
getChildren().forEach(child -> {
if (child.containsCursor()) {
child.setHovered(true);
if (!input.isConsumed()) {
child.dispatchInput(input);
}
} else {
child.setHovered(false);
}
});
handleInput(input);
}
private void dispatchInputToAll(Input input) {
getChildren().forEach(c -> c.dispatchInput(input));
handleInput(input);
}
private boolean attemptFocusTransfer(Input input, Component focused) {
if (input.isConsumed())
return false;
if (!(input.getEvent() instanceof KeyEvent))
return false;
KeyEvent keyInput = (KeyEvent) input.getEvent();
if (keyInput.getKey() == GLFW.GLFW_KEY_TAB && !keyInput.isRelease()) {
input.consume();
if (keyInput.hasShift()) {
focused.focusPrevious();
} else {
focused.focusNext();
}
protected boolean passInputToChildren(InputEvent e) {
return true;
}
return false;
InputBus getInputBus() {
return inputBus;
}
public synchronized boolean contains(int x, int y) {
return x >= getX() && x < getX() + getWidth() && y >= getY() && y < getY() + getHeight();
}
public boolean containsCursor() {
return contains((int) InputTracker.getCursorX(), (int) InputTracker.getCursorY());
}
public void requestReassembly() {
if (parent != null) {
parent.requestReassembly();
@ -804,7 +725,7 @@ public class Component extends Named {
for (ARTrigger trigger : triggers) {
if (!autoReassemblyTriggerObjects.containsKey(trigger)) {
Object triggerObject = createTriggerObject(trigger);
addListener(trigger);
addListener(triggerObject);
autoReassemblyTriggerObjects.put(trigger, triggerObject);
}
}
@ -842,6 +763,20 @@ public class Component extends Named {
}
public Renderable assembleToRenderable() {
RenderTarget target = new RenderTarget();
assemble(target);
Clip[] clips = target.assemble();
return renderer -> {
for (Clip clip : clips) {
clip.render((AssembledFlatRenderHelper) renderer);
}
};
}
// /**
// * Returns a component that displays this component in its center.
// * @return a {@link Aligner} initialized to center this component

View File

@ -0,0 +1,37 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import java.util.function.BooleanSupplier;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
public class Components {
public static Component center(Component c) {
return new Group(c.getName() + ".Centerer", new LayoutAlign(), c);
}
public static Component hide(Component c, BooleanSupplier shouldHide) {
return new Hider(c.getName() + ".Hider", c, shouldHide);
}
private Components() {
}
}

View File

@ -0,0 +1,77 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import java.util.Objects;
import glm.vec._2.d.Vec2d;
import ru.windcorp.progressia.client.graphics.gui.event.DragEvent;
import ru.windcorp.progressia.client.graphics.gui.event.DragStartEvent;
import ru.windcorp.progressia.client.graphics.gui.event.DragStopEvent;
import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
public class DragManager {
private Component component;
private boolean isDragged = false;
private final Vec2d change = new Vec2d();
public void install(Component c) {
Objects.requireNonNull(c, "c");
if (c == component) {
return;
}
if (component != null) {
throw new IllegalStateException("Already installed on " + component + "; attempted to install on " + c);
}
component = c;
c.addInputListener(CursorMoveEvent.class, this::onCursorMove, InputBus.Option.ALWAYS);
c.addKeyListener(KeyMatcher.LMB, this::onLMB, InputBus.Option.ALWAYS, InputBus.Option.IGNORE_ACTION);
}
private void onCursorMove(CursorMoveEvent e) {
if (isDragged) {
Vec2d currentChange = e.getChange(null);
change.add(currentChange);
component.dispatchEvent(new DragEvent(component, currentChange, change));
}
}
private void onLMB(KeyEvent e) {
if (isDragged && e.isRelease()) {
isDragged = false;
component.dispatchEvent(new DragStopEvent(component, change));
} else if (!isDragged && !e.isConsumed() && e.isPress() && component.isHovered()) {
isDragged = true;
change.set(0, 0);
component.dispatchEvent(new DragStartEvent(component));
e.consume();
}
}
}

View File

@ -33,7 +33,7 @@ public class DynamicLabel extends Component {
super(name);
this.font = font;
this.contents = contents;
setPreferredSize(width, font.getHeight("", Float.POSITIVE_INFINITY) * 2);
setPreferredSize(width, font.getHeight("", Float.POSITIVE_INFINITY));
}
public Font getFont() {
@ -46,7 +46,7 @@ public class DynamicLabel extends Component {
@Override
protected void assembleSelf(RenderTarget target) {
target.pushTransform(new Mat4().identity().translate(getX(), getY(), -1000).scale(2));
target.pushTransform(new Mat4().identity().translate(getX(), getY(), -1000));
target.addCustomRenderer(font.assembleDynamic(getContentSupplier(), Float.POSITIVE_INFINITY));
target.popTransform();
}

View File

@ -18,9 +18,17 @@
package ru.windcorp.progressia.client.graphics.gui;
import java.util.Iterator;
import org.lwjgl.glfw.GLFW;
import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.InputBus;
import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.util.StashingStack;
public abstract class GUILayer extends AssembledFlatLayer {
@ -33,7 +41,9 @@ public abstract class GUILayer extends AssembledFlatLayer {
public GUILayer(String name, Layout layout) {
super(name);
getRoot().setLayout(layout);
getRoot().addInputListener(KeyEvent.class, this::attemptFocusTransfer, InputBus.Option.IGNORE_FOCUS);
}
public Component getRoot() {
@ -47,9 +57,85 @@ public abstract class GUILayer extends AssembledFlatLayer {
getRoot().assemble(target);
}
/**
* Stack frame for {@link #handleInput(InputEvent)}.
*/
private static class EventHandlingFrame {
Component component;
Iterator<Component> children;
void init(Component c) {
component = c;
children = c.getChildren().iterator();
}
void reset() {
component = null;
children = null;
}
}
/**
* Stacks for {@link #handleInput(InputEvent)}.
*/
private final LowOverheadCache<StashingStack<EventHandlingFrame>> pathCache = new LowOverheadCache<>(
() -> new StashingStack<>(64, EventHandlingFrame::new)
);
/*
* This is essentially a depth-first iteration of the component tree. The
* recursive procedure has been unrolled to reduce call stack length.
*/
@Override
protected void handleInput(Input input) {
getRoot().dispatchInput(input);
public void handleInput(InputEvent event) {
StashingStack<EventHandlingFrame> path = pathCache.grab();
if (!path.isEmpty()) {
throw new IllegalStateException("path is not empty: " + path);
}
path.push().init(root);
while (!path.isEmpty()) {
Iterator<Component> it = path.peek().children;
if (it.hasNext()) {
Component c = it.next();
if (c.isEnabled()) {
if (c.getChildren().isEmpty() || !c.passInputToChildren(event)) {
c.getInputBus().dispatch(event);
} else {
path.push().init(c);
}
}
} else {
path.peek().component.getInputBus().dispatch(event);
path.pop().reset();
}
}
pathCache.release(path);
}
private void attemptFocusTransfer(KeyEvent e) {
Component focused = getRoot().findFocused();
if (focused == null) {
return;
}
if (e.getKey() == GLFW.GLFW_KEY_TAB && !e.isRelease()) {
e.consume();
if (e.hasShift()) {
focused.focusPrevious();
} else {
focused.focusNext();
}
}
}
@Override

View File

@ -25,4 +25,12 @@ public class Group extends Component {
setLayout(layout);
}
public Group(String name, Layout layout, Component... children) {
this(name, layout);
for (Component child : children) {
addChild(child);
}
}
}

View File

@ -0,0 +1,65 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.graphics.gui;
import java.util.function.BooleanSupplier;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.model.Renderable;
public class Hider extends Component {
private final BooleanSupplier shouldHide;
private final Component contents;
public Hider(String name, Component contents, BooleanSupplier shouldHide) {
super(name);
this.contents = contents;
this.shouldHide = shouldHide;
setLayout(new LayoutFill());
addChild(contents);
}
@Override
protected boolean passInputToChildren(InputEvent e) {
return !shouldHide.getAsBoolean();
}
@Override
public synchronized Component findFocused() {
if (shouldHide.getAsBoolean()) {
return null;
}
return super.findFocused();
}
@Override
protected void assembleChildren(RenderTarget target) {
Renderable renderable = contents.assembleToRenderable();
target.addCustomRenderer(renderer -> {
if (!shouldHide.getAsBoolean()) {
renderable.render(renderer);
}
});
}
}

View File

@ -70,7 +70,7 @@ public class Label extends Component {
public void update() {
currentText = contents.get();
currentSize = font.getSize(currentText, maxWidth, null).mul(2);
currentSize = font.getSize(currentText, maxWidth, null);
requestReassembly();
}
@ -85,7 +85,6 @@ public class Label extends Component {
public void setFont(Font font) {
this.font = font;
requestReassembly();
}
public String getCurrentText() {
@ -100,12 +99,8 @@ public class Label extends Component {
protected void assembleSelf(RenderTarget target) {
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
target.pushTransform(
new Mat4().identity().translate(startX, getY(), 0).scale(2)
);
target.pushTransform(new Mat4().identity().translate(startX, getY(), 0));
target.addCustomRenderer(font.assemble(currentText, maxWidth));
target.popTransform();
}

View File

View File

@ -104,25 +104,22 @@ public class RadioButton extends BasicButton {
group.addChild(basicChild);
addChild(group);
addListener(KeyEvent.class, e -> {
if (e.isRelease()) return false;
addInputListener(KeyEvent.class, e -> {
if (e.isRelease()) return;
if (e.getKey() == GLFW.GLFW_KEY_LEFT || e.getKey() == GLFW.GLFW_KEY_UP) {
if (this.group != null) {
this.group.selectPrevious();
this.group.getSelected().takeFocus();
}
return true;
e.consume();
} else if (e.getKey() == GLFW.GLFW_KEY_RIGHT || e.getKey() == GLFW.GLFW_KEY_DOWN) {
if (this.group != null) {
this.group.selectNext();
this.group.getSelected().takeFocus();
}
return true;
e.consume();
}
return false;
});
addAction(b -> setChecked(true));
@ -149,7 +146,8 @@ public class RadioButton extends BasicButton {
group.selectNext();
removeAction(group.listener);
group.buttons.remove(this);
group.getSelected(); // Clear reference if this was the only button in the group
group.getSelected(); // Clear reference if this was the only button
// in the group
}
this.group = group;
@ -178,7 +176,8 @@ public class RadioButton extends BasicButton {
this.checked = checked;
if (group != null) {
group.listener.accept(this); // Failsafe for manual invocations of setChecked()
group.listener.accept(this); // Failsafe for manual invocations of
// setChecked()
}
}

View File

@ -0,0 +1,58 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.graphics.gui.event;
import glm.vec._2.d.Vec2d;
import ru.windcorp.progressia.client.graphics.gui.Component;
public class DragEvent extends ComponentEvent {
private final Vec2d currentChange = new Vec2d();
private final Vec2d totalChange = new Vec2d();
public DragEvent(Component component, Vec2d currentChange, Vec2d totalChange) {
super(component);
this.currentChange.set(currentChange.x, currentChange.y);
this.totalChange.set(totalChange.x, totalChange.y);
}
public Vec2d getCurrentChange() {
return currentChange;
}
public double getCurrentChangeX() {
return currentChange.x;
}
public double getCurrentChangeY() {
return currentChange.y;
}
public Vec2d getTotalChange() {
return totalChange;
}
public double getTotalChangeX() {
return totalChange.x;
}
public double getTotalChangeY() {
return totalChange.y;
}
}

View File

@ -0,0 +1,28 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.graphics.gui.event;
import ru.windcorp.progressia.client.graphics.gui.Component;
public class DragStartEvent extends ComponentEvent {
public DragStartEvent(Component component) {
super(component);
}
}

View File

@ -0,0 +1,44 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.graphics.gui.event;
import glm.vec._2.d.Vec2d;
import ru.windcorp.progressia.client.graphics.gui.Component;
public class DragStopEvent extends ComponentEvent {
private final Vec2d totalChange = new Vec2d();
public DragStopEvent(Component component, Vec2d totalChange) {
super(component);
this.totalChange.set(totalChange.x, totalChange.y);
}
public Vec2d getTotalChange() {
return totalChange;
}
public double getTotalChangeX() {
return totalChange.x;
}
public double getTotalChangeY() {
return totalChange.y;
}
}

View File

@ -56,8 +56,13 @@ public class LayoutAlign implements Layout {
size.x = min(size.x, cWidth);
size.y = min(size.y, cHeight);
child.setBounds(c.getX() + (int) ((cWidth - size.x) * alignX) + margin,
c.getY() + (int) ((cHeight - size.y) * alignY) + margin, size);
child.setBounds(
c.getX() +
(int) ((cWidth - size.x) * alignX) + margin,
c.getY() +
(int) ((cHeight - size.y) * alignY) + margin,
size
);
});
}
@ -66,7 +71,9 @@ public class LayoutAlign implements Layout {
public Vec2i calculatePreferredSize(Component c) {
Vec2i result = new Vec2i(0, 0);
c.getChildren().stream().map(child -> child.getPreferredSize()).forEach(size -> {
c.getChildren().stream()
.map(child -> child.getPreferredSize())
.forEach(size -> {
result.x = max(size.x, result.x);
result.y = max(size.y, result.y);
});

View File

@ -26,7 +26,9 @@ import ru.windcorp.progressia.client.graphics.gui.Layout;
public class LayoutBorderHorizontal implements Layout {
public static final String CENTER = "Center", LEFT = "Left", RIGHT = "Right";
public static final String CENTER = "Center",
LEFT = "Left",
RIGHT = "Right";
private final int margin;
@ -49,17 +51,32 @@ public class LayoutBorderHorizontal implements Layout {
if (child.getLayoutHint() == LEFT) {
childSize = child.getPreferredSize();
left = childSize.x + margin;
child.setBounds(c.getX(), c.getY(), childSize.x, c.getHeight());
child.setBounds(
c.getX(),
c.getY(),
childSize.x,
c.getHeight()
);
} else if (child.getLayoutHint() == RIGHT) {
childSize = child.getPreferredSize();
right = childSize.x + margin;
child.setBounds(c.getX() + c.getWidth() - childSize.x, c.getY(), childSize.x, c.getHeight());
child.setBounds(
c.getX() + c.getWidth() - childSize.x,
c.getY(),
childSize.x,
c.getHeight()
);
}
}
for (Component child : c.getChildren()) {
if (child.getLayoutHint() == CENTER) {
child.setBounds(c.getX() + left, c.getY(), c.getWidth() - left - right, c.getHeight());
child.setBounds(
c.getX() + left,
c.getY(),
c.getWidth() - left - right,
c.getHeight()
);
}
}

View File

@ -26,7 +26,9 @@ import ru.windcorp.progressia.client.graphics.gui.Layout;
public class LayoutBorderVertical implements Layout {
public static final String CENTER = "Center", UP = "Up", DOWN = "Down";
public static final String CENTER = "Center",
UP = "Up",
DOWN = "Down";
private final int margin;
@ -49,17 +51,32 @@ public class LayoutBorderVertical implements Layout {
if (child.getLayoutHint() == UP) {
childSize = child.getPreferredSize();
top = childSize.y + margin;
child.setBounds(c.getX(), c.getY(), c.getWidth(), childSize.y);
child.setBounds(
c.getX(),
c.getY(),
c.getWidth(),
childSize.y
);
} else if (child.getLayoutHint() == DOWN) {
childSize = child.getPreferredSize();
bottom = childSize.y + margin;
child.setBounds(c.getX(), c.getY() + c.getHeight() - childSize.y, c.getWidth(), childSize.y);
child.setBounds(
c.getX(),
c.getY() + c.getHeight() - childSize.y,
c.getWidth(),
childSize.y
);
}
}
for (Component child : c.getChildren()) {
if (child.getLayoutHint() == CENTER) {
child.setBounds(c.getX(), c.getY() + top, c.getWidth(), c.getHeight() - top - bottom);
child.setBounds(
c.getX(),
c.getY() + top,
c.getWidth(),
c.getHeight() - top - bottom
);
}
}

View File

@ -43,7 +43,8 @@ public class LayoutHorizontal implements Layout {
@Override
public void layout(Component c) {
int x = c.getX() + margin, y = c.getY() + margin;
int x = c.getX() + margin,
y = c.getY() + margin;
int width;

View File

@ -43,7 +43,8 @@ public class LayoutVertical implements Layout {
@Override
public void layout(Component c) {
int x = c.getX() + margin, y = c.getY() + c.getHeight();
int x = c.getX() + margin,
y = c.getY() + c.getHeight();
synchronized (c.getChildren()) {
for (Component child : c.getChildren()) {

View File

@ -33,7 +33,6 @@ import ru.windcorp.progressia.client.graphics.gui.layout.LayoutFill;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutVertical;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.localization.MutableString;
import ru.windcorp.progressia.client.localization.MutableStringLocalized;
@ -97,11 +96,9 @@ public class MenuLayer extends GUILayer {
}
@Override
protected void handleInput(Input input) {
if (!input.isConsumed()) {
InputEvent event = input.getEvent();
public void handleInput(InputEvent event) {
if (!event.isConsumed()) {
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
@ -110,8 +107,8 @@ public class MenuLayer extends GUILayer {
}
}
super.handleInput(input);
input.consume();
super.handleInput(event);
event.consume();
}
}

View File

@ -18,7 +18,6 @@
package ru.windcorp.progressia.client.graphics.input;
import glm.vec._2.Vec2;
import glm.vec._2.d.Vec2d;
public class CursorMoveEvent extends CursorEvent {
@ -81,20 +80,31 @@ public class CursorMoveEvent extends CursorEvent {
return getNewY() - getPreviousY();
}
public Vec2 getChange(Vec2 result) {
public Vec2d getChange(Vec2d result) {
if (result == null) {
result = new Vec2d();
}
return result.set(getChangeX(), getChangeY());
}
@Override
public CursorMoveEvent snapshot() {
return new StaticMouseMoveEvent(getPreviousPosition(), getNewPosition(), getTime());
return new StaticMouseMoveEvent(
getPreviousPosition(),
getNewPosition(),
getTime()
);
}
private class StaticMouseMoveEvent extends CursorMoveEvent {
private final Vec2d previousPosition = new Vec2d();
public StaticMouseMoveEvent(Vec2d previousPosition, Vec2d newPosition, double time) {
public StaticMouseMoveEvent(
Vec2d previousPosition,
Vec2d newPosition,
double time
) {
super(newPosition, time);
this.previousPosition.set(previousPosition.x, previousPosition.y);
}

View File

@ -67,7 +67,11 @@ public class FrameResizeEvent extends InputEvent {
private final Vec2i previousSize;
public StaticFrameResizeEvent(Vec2i newSize, Vec2i previousSize, double time) {
public StaticFrameResizeEvent(
Vec2i newSize,
Vec2i previousSize,
double time
) {
super(newSize, time);
this.previousSize = previousSize;
}

View File

@ -18,10 +18,32 @@
package ru.windcorp.progressia.client.graphics.input;
import ru.windcorp.progressia.client.graphics.gui.Component;
/**
* An instance of user input.
* <p>
* User input events are typically generated by graphics backend between frames
* and passed to the graphics layers from top to bottom. Layers that use
* {@link Component}s will forward this event through the Component hierarchy.
* <p>
* Events have a {@code consumed} flag. A freshly-generated event will have this
* flag set to {@code false}. Event listeners that process the event will
* usually choose to raise the flag ("consume the event") to ask future
* listeners to ignore this event. This is done to avoid multiple UI interfaces
* reacting to single input. By default, listeners will not receive consumed
* events; however, some listeners may choose to receive, handle and even
* un-consume the event.
* <p>
* {@code InputEvent} objects may be reused for future input events after their
* processing is complete; to obtain a static copy, use {@link #snapshot()}.
*/
public abstract class InputEvent {
private double time;
private boolean isConsumed = false;
public InputEvent(double time) {
this.time = time;
}
@ -36,4 +58,16 @@ public abstract class InputEvent {
public abstract InputEvent snapshot();
public boolean isConsumed() {
return isConsumed;
}
public void setConsumed(boolean isConsumed) {
this.isConsumed = isConsumed;
}
public void consume() {
setConsumed(true);
}
}

View File

@ -27,7 +27,13 @@ public class KeyEvent extends InputEvent {
protected int action;
protected int mods;
protected KeyEvent(int key, int scancode, int action, int mods, double time) {
protected KeyEvent(
int key,
int scancode,
int action,
int mods,
double time
) {
super(time);
this.key = key;
this.scancode = scancode;

View File

@ -18,16 +18,75 @@
package ru.windcorp.progressia.client.graphics.input;
import java.util.function.Predicate;
import java.util.Map;
import java.util.regex.Pattern;
import org.lwjgl.glfw.GLFW;
import com.google.common.collect.ImmutableMap;
public class KeyMatcher {
private static final Pattern DECLAR_SPLIT_REGEX = Pattern.compile("\\s*\\+\\s*");
private static final Map<String, Integer> MOD_TOKENS = ImmutableMap.of(
"SHIFT", GLFW.GLFW_MOD_SHIFT,
"CONTROL", GLFW.GLFW_MOD_CONTROL,
"ALT", GLFW.GLFW_MOD_ALT,
"SUPER", GLFW.GLFW_MOD_SUPER
);
public static final KeyMatcher LMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_LEFT);
public static final KeyMatcher RMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_RIGHT);
public static final KeyMatcher MMB = new KeyMatcher(GLFW.GLFW_MOUSE_BUTTON_MIDDLE);
private final int key;
private final int mods;
protected KeyMatcher(int key, int mods) {
public KeyMatcher(int key, int mods) {
this.key = key;
this.mods = mods;
}
public KeyMatcher(int key) {
this.key = key;
this.mods = 0;
}
public KeyMatcher(String declar) {
String[] tokens = DECLAR_SPLIT_REGEX.split(declar);
if (tokens.length == 0) {
throw new IllegalArgumentException("No tokens found in \"" + declar + "\"");
}
int key = -1;
int mods = 0;
for (String token : tokens) {
token = token.toUpperCase();
if (MOD_TOKENS.containsKey(token)) {
int mod = MOD_TOKENS.get(token);
if ((mods & mod) != 0) {
throw new IllegalArgumentException("Duplicate modifier \"" + token + "\" in \"" + declar + "\"");
}
mods |= mod;
} else if (key != -1) {
throw new IllegalArgumentException("Too many non-modifier tokens in \"" + declar + "\": maximum one key, first offender: \"" + token + "\"");
} else {
token = token.replace(' ', '_');
if (token.startsWith("KEYPAD_")) {
token = "KP_" + token.substring("KEYPAD_".length());
}
key = Keys.getCode(token);
if (key == -1) {
throw new IllegalArgumentException("Unknown token \"" + token + "\" in \"" + declar + "\"");
}
}
}
this.key = key;
this.mods = mods;
}
@ -43,6 +102,15 @@ public class KeyMatcher {
return true;
}
public boolean matchesIgnoringAction(KeyEvent event) {
if (event.getKey() != getKey())
return false;
if ((event.getMods() & getMods()) != getMods())
return false;
return true;
}
public int getKey() {
return key;
}
@ -51,48 +119,24 @@ public class KeyMatcher {
return mods;
}
public static KeyMatcher.Builder of(int key) {
return new KeyMatcher.Builder(key);
public KeyMatcher with(int modifier) {
return new KeyMatcher(key, mods | modifier);
}
public static class Builder {
private final int key;
private int mods = 0;
public Builder(int key) {
this.key = key;
}
public Builder with(int modifier) {
this.mods += modifier;
return this;
}
public Builder withShift() {
public KeyMatcher withShift() {
return with(GLFW.GLFW_MOD_SHIFT);
}
public Builder withCtrl() {
public KeyMatcher withCtrl() {
return with(GLFW.GLFW_MOD_CONTROL);
}
public Builder withAlt() {
public KeyMatcher withAlt() {
return with(GLFW.GLFW_MOD_ALT);
}
public Builder withSuper() {
public KeyMatcher withSuper() {
return with(GLFW.GLFW_MOD_SUPER);
}
public KeyMatcher build() {
return new KeyMatcher(key, mods);
}
public Predicate<KeyEvent> matcher() {
return build()::matches;
}
}
}

View File

@ -46,12 +46,17 @@ public class Keys {
private static final String MOUSE_BUTTON_PREFIX = "GLFW_MOUSE_BUTTON_";
private static final Set<String> IGNORE_FIELDS = new HashSet<>(
Arrays.asList("GLFW_KEY_UNKNOWN", "GLFW_KEY_LAST", "GLFW_MOUSE_BUTTON_LAST", "GLFW_MOUSE_BUTTON_1", // Alias
Arrays.asList(
"GLFW_KEY_UNKNOWN",
"GLFW_KEY_LAST",
"GLFW_MOUSE_BUTTON_LAST",
"GLFW_MOUSE_BUTTON_1", // Alias
// for
// LEFT
"GLFW_MOUSE_BUTTON_2", // Alias for RIGHT
"GLFW_MOUSE_BUTTON_3" // Alias for MIDDLE
));
)
);
static {
initializeDictionary();
@ -95,8 +100,14 @@ public class Keys {
}
if (CODES_TO_NAMES.containsKey(value)) {
throw CrashReports.report(null, "Duplicate keys: %s and %s both map to %d(0x%s)", CODES_TO_NAMES.get(value),
name, value, Integer.toHexString(value));
throw CrashReports.report(
null,
"Duplicate keys: %s and %s both map to %d(0x%s)",
CODES_TO_NAMES.get(value),
name,
value,
Integer.toHexString(value)
);
}
CODES_TO_NAMES.put(value, name);
@ -128,7 +139,7 @@ public class Keys {
}
public static int getCode(String internalName) {
if (NAMES_TO_CODES.containsKey(internalName)) {
if (!NAMES_TO_CODES.containsKey(internalName)) {
return -1;
} else {
return NAMES_TO_CODES.get(internalName);

View File

@ -20,55 +20,396 @@ package ru.windcorp.progressia.client.graphics.input.bus;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import ru.windcorp.jputil.ArrayUtil;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.input.CursorEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.input.WheelEvent;
import ru.windcorp.progressia.common.util.crash.CrashReports;
/**
* An event bus optionally related to a {@link Component} that delivers input
* events to input listeners. This bus may skip listeners based on circumstance;
* behavior can be customized for each listener with {@link Option}s.
* <p>
* By default, events are filtered by four checks before being delivered to each
* listener:
* <ol>
* <li><em>Consumption check</em>: unless {@link Option#RECEIVE_CONSUMED
* RECEIVE_CONSUMED} is set, events that are consumed will not be
* delivered.</li>
* <li><em>Hover check</em>: for certain event types (for example,
* {@link WheelEvent} or {@link KeyEvent} that {@link KeyEvent#isMouse()
* isMouse()}), the event will only be delivered if the component is hovered.
* This check may be bypassed with option {@link Option#IGNORE_HOVER
* IGNORE_HOVER} or made mandatory for all events with
* {@link Option#REQUIRE_HOVER REQUIRE_HOVER}. Hover check automatically
* succeeds if no component is provided.</li>
* <li><em>Focus check</em>: for certain event types (for example,
* {@link KeyEvent} that {@code !isMouse()}), the event will only be delivered
* if the component has focus. This check may be bypassed with option
* {@link Option#IGNORE_FOCUS IGNORE_FOCUS} or made mandatory for all events
* with {@link Option#REQUIRE_FOCUS REQUIRE_FOCUS}. Focus check automatically
* succeeds if no component is provided.</li>
* <li><em>Type check</em>: events of type {@code E} are only delivered to
* listeners registered with event type {@code T} if objects of type {@code E}
* can be cast to {@code T}.</li>
* </ol>
* Checks 1-3 are bypassed when option {@link Option#ALWAYS ALWAYS} is
* specified.
*/
public class InputBus {
private static class WrappedListener {
/**
* Options that allow customization of checks for listeners.
*/
public enum Option {
/**
* Ignore checks for consumed events, hover and focus; deliver event if
* at all possible. This is shorthand for {@link #RECEIVE_CONSUMED},
* {@link #IGNORE_HOVER} and {@link #IGNORE_FOCUS}.
*/
ALWAYS,
/**
* Receive events that were previously consumed.
*/
RECEIVE_CONSUMED,
/**
* Do not process events if the listener is registered with a component
* and the component is not hovered.
*/
REQUIRE_HOVER,
/**
* Deliver events even if the event is limited to hovered components by
* default.
*/
IGNORE_HOVER,
/**
* Do not process events if the listener is registered with a component
* and the component is not focused.
*/
REQUIRE_FOCUS,
/**
* Deliver events even if the event is limited to focused components by
* default.
*/
IGNORE_FOCUS,
/**
* Deliver events according to
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
* {@link KeyMatcher#matches(KeyEvent)} when a {@link KeyMatcher} is
* specified.
*/
IGNORE_ACTION;
}
private enum YesNoDefault {
YES, NO, DEFAULT;
}
/**
* A listener with check preferences resolved and type specified.
*/
private class WrappedListener {
private final Class<?> type;
private final boolean handleConsumed;
private final boolean dropIfConsumed;
private final YesNoDefault dropIfNotHovered;
private final YesNoDefault dropIfNotFocused;
private final InputListener<?> listener;
public WrappedListener(Class<?> type, boolean handleConsumed, InputListener<?> listener) {
public WrappedListener(
Class<?> type,
boolean dropIfConsumed,
YesNoDefault dropIfNotHovered,
YesNoDefault dropIfNotFocused,
InputListener<?> listener
) {
this.type = type;
this.handleConsumed = handleConsumed;
this.dropIfConsumed = dropIfConsumed;
this.dropIfNotHovered = dropIfNotHovered;
this.dropIfNotFocused = dropIfNotFocused;
this.listener = listener;
}
private boolean handles(Input input) {
return (!input.isConsumed() || handleConsumed) && type.isInstance(input.getEvent());
private boolean handles(InputEvent input) {
if (dropIfConsumed && input.isConsumed())
return false;
switch (dropIfNotHovered) {
case YES:
if (!isHovered())
return false;
break;
case NO:
break;
default:
if (isHovered())
break;
if (input instanceof KeyEvent && ((KeyEvent) input).isMouse())
return false;
if (input instanceof CursorEvent)
return false;
if (input instanceof WheelEvent)
return false;
break;
}
switch (dropIfNotFocused) {
case YES:
if (!isFocused())
return false;
break;
case NO:
break;
default:
if (isFocused())
break;
if (input instanceof KeyEvent && !((KeyEvent) input).isMouse())
return false;
break;
}
if (!type.isInstance(input))
return false;
return true;
}
/**
* Invokes the listener if the event is deemed appropriate by the four
* checks.
*
* @param event the event to deliver
*/
@SuppressWarnings("unchecked")
public void handle(Input input) {
if (handles(input)) {
boolean consumed = ((InputListener<InputEvent>) listener)
.handle((InputEvent) type.cast(input.getEvent()));
public void handle(InputEvent event) {
if (handles(event)) {
// A runtime check of types has been performed; this is safe.
InputListener<InputEvent> castListener = (InputListener<InputEvent>) listener;
input.setConsumed(consumed);
try {
castListener.handle(event);
} catch (Exception e) {
throw CrashReports.report(
e,
"InputListener %s for component %s has failed to receive event %s",
listener,
owner,
event
);
}
}
}
}
/**
* The component queried for focus and hover. May be {@code null}.
*/
private final Component owner;
/**
* Registered listeners.
*/
private final Collection<WrappedListener> listeners = new ArrayList<>(4);
public void dispatch(Input input) {
listeners.forEach(l -> l.handle(input));
/**
* Creates a new input bus that consults the specified {@link Component} to
* determine hover and focus.
*
* @param owner the component to use for hover and focus tests
* @see #InputBus()
*/
public InputBus(Component owner) {
this.owner = Objects.requireNonNull(owner, "owner");
}
public <T extends InputEvent> void register(Class<? extends T> type, boolean handlesConsumed,
InputListener<T> listener) {
listeners.add(new WrappedListener(type, handlesConsumed, listener));
/**
* Creates a new input bus that assumes all hover and focus checks are
* successful.
*
* @see #InputBus(Component)
*/
public InputBus() {
this.owner = null;
}
public <T extends InputEvent> void register(Class<? extends T> type, InputListener<T> listener) {
register(type, false, listener);
/**
* Determines whether hover should be assumed for this event bus.
*
* @return {@code true} iff no component is linked or the linked component
* is hovered
*/
private boolean isHovered() {
return owner == null ? true : owner.isHovered();
}
/**
* Determines whether focus should be assumed for this event bus.
*
* @return {@code true} iff no component is linked or the linked component
* is focused
*/
private boolean isFocused() {
return owner == null ? true : owner.isFocused();
}
/**
* Dispatches (delivers) the provided event to all appropriate listeners.
*
* @param event the event to process
*/
public void dispatch(InputEvent event) {
Objects.requireNonNull(event, "event");
for (WrappedListener listener : listeners) {
listener.handle(event);
}
}
/**
* Registers a listener on this bus.
* <p>
* {@code type} specifies the class of events that should be passed to this
* listener. Only events of types that extend, implement or equal
* {@code type} are processed.
* <p>
* Zero or more {@link Option}s may be specified to enable or disable the
* processing of certain events in certain circumstances. See
* {@linkplain InputBus class description} for a detailed breakdown of the
* checks performed and the effects of various options. When providing
* options to this method, later options override the effects of previous
* options.
* <p>
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} is ignored silently.
*
* @param type the event class to deliver
* @param listener the listener
* @param options the options for this listener
*/
public <T extends InputEvent> void register(
Class<? extends T> type,
InputListener<T> listener,
Option... options
) {
Objects.requireNonNull(type, "type");
Objects.requireNonNull(listener, "listener");
boolean dropIfConsumed = true;
YesNoDefault dropIfNotHovered = YesNoDefault.DEFAULT;
YesNoDefault dropIfNotFocused = YesNoDefault.DEFAULT;
if (options != null) {
for (Option option : options) {
switch (option) {
case ALWAYS:
dropIfConsumed = false;
dropIfNotHovered = YesNoDefault.NO;
dropIfNotFocused = YesNoDefault.NO;
break;
case RECEIVE_CONSUMED:
dropIfConsumed = false;
break;
case REQUIRE_HOVER:
dropIfNotHovered = YesNoDefault.YES;
break;
case IGNORE_HOVER:
dropIfNotFocused = YesNoDefault.NO;
break;
case REQUIRE_FOCUS:
dropIfNotHovered = YesNoDefault.YES;
break;
case IGNORE_FOCUS:
dropIfNotFocused = YesNoDefault.NO;
break;
case IGNORE_ACTION:
// Ignore
break;
default:
throw new IllegalArgumentException("Unexpected option " + option);
}
}
}
listeners.add(new WrappedListener(type, dropIfConsumed, dropIfNotHovered, dropIfNotFocused, listener));
}
/**
* Registers a {@link KeyEvent} listener on this bus. An event has to match
* the provided {@link KeyMatcher} to be delivered to the listener.
* <p>
* Zero or more {@link Option}s may be specified to enable or disable the
* processing of certain events in certain circumstances. See
* {@linkplain InputBus class description} for a detailed breakdown of the
* checks performed and the effects of various options. When providing
* options to this method, later options override the effects of previous
* options.
* <p>
* Option {@link Option#IGNORE_ACTION IGNORE_ACTION} requests that events
* are delivered according to
* {@link KeyMatcher#matchesIgnoringAction(KeyEvent)} rather than
* {@link KeyMatcher#matches(KeyEvent)}.
* specified.
*
* @param matcher an event filter
* @param listener the listener
* @param options the options for this listener
*/
public void register(KeyMatcher matcher, InputListener<? super KeyEvent> listener, Option... options) {
Objects.requireNonNull(matcher, "matcher");
Objects.requireNonNull(listener, "listener");
InputListener<KeyEvent> filteringListener;
if (ArrayUtil.firstIndexOf(options, Option.IGNORE_ACTION) != -1) {
filteringListener = e -> {
if (matcher.matchesIgnoringAction(e)) {
listener.handle(e);
}
};
} else {
filteringListener = e -> {
if (matcher.matches(e)) {
listener.handle(e);
}
};
}
register(KeyEvent.class, filteringListener, options);
}
/**
* Removes all occurrences of the provided listener from this bus.
*
* @param listener the listener to unregister
*/
public void unregister(InputListener<?> listener) {
if (listener == null) {
return;
}
listeners.removeIf(l -> l.listener == listener);
}

View File

@ -23,6 +23,6 @@ import ru.windcorp.progressia.client.graphics.input.InputEvent;
@FunctionalInterface
public interface InputListener<T extends InputEvent> {
boolean handle(T event);
void handle(T event);
}

View File

@ -33,14 +33,22 @@ public abstract class DynamicModel extends Model {
private final Mat4[] transforms;
private final boolean[] dynamics;
public DynamicModel(Renderable[] parts, Mat4[] transforms, boolean[] dynamic) {
public DynamicModel(
Renderable[] parts,
Mat4[] transforms,
boolean[] dynamic
) {
super(parts);
this.transforms = transforms;
this.dynamics = dynamic;
}
public DynamicModel(Builder builder) {
this(builder.getParts(), builder.getTransforms(), builder.getDynamics());
this(
builder.getParts(),
builder.getTransforms(),
builder.getDynamics()
);
}
@Override
@ -70,7 +78,11 @@ public abstract class DynamicModel extends Model {
protected Builder() {
}
private Builder addPart(Renderable part, Mat4 transform, boolean isDynamic) {
private Builder addPart(
Renderable part,
Mat4 transform,
boolean isDynamic
) {
parts.add(Objects.requireNonNull(part, "part"));
transforms.add(Objects.requireNonNull(transform, "transform"));
dynamics.add(isDynamic);
@ -78,15 +90,22 @@ public abstract class DynamicModel extends Model {
return this;
}
public Builder addStaticPart(Renderable part, Mat4 transform) {
public Builder addStaticPart(
Renderable part,
Mat4 transform
) {
return addPart(part, new Mat4(transform), false);
}
public Builder addDynamicPart(Renderable part) {
public Builder addDynamicPart(
Renderable part
) {
return addPart(part, new Mat4(), true);
}
public Builder addStaticPart(Renderable part) {
public Builder addStaticPart(
Renderable part
) {
return addStaticPart(part, IDENTITY);
}

View File

@ -37,13 +37,23 @@ public class LambdaModel extends DynamicModel {
private final TransformGetter[] getters;
public LambdaModel(Renderable[] parts, Mat4[] transforms, boolean[] dynamic, TransformGetter[] getters) {
public LambdaModel(
Renderable[] parts,
Mat4[] transforms,
boolean[] dynamic,
TransformGetter[] getters
) {
super(parts, transforms, dynamic);
this.getters = getters;
}
public LambdaModel(Builder builder) {
this(builder.getParts(), builder.getTransforms(), builder.getDynamics(), builder.getGetters());
this(
builder.getParts(),
builder.getTransforms(),
builder.getDynamics(),
builder.getGetters()
);
}
@Override
@ -65,7 +75,11 @@ public class LambdaModel extends DynamicModel {
protected Builder() {
}
private Builder addPart(Renderable part, Mat4 transform, TransformGetter getter) {
private Builder addPart(
Renderable part,
Mat4 transform,
TransformGetter getter
) {
parts.add(Objects.requireNonNull(part, "part"));
transforms.add(Objects.requireNonNull(transform, "transform"));
dynamics.add(getter != null);
@ -74,15 +88,23 @@ public class LambdaModel extends DynamicModel {
return this;
}
public Builder addStaticPart(Renderable part, Mat4 transform) {
public Builder addStaticPart(
Renderable part,
Mat4 transform
) {
return addPart(part, new Mat4(transform), null);
}
public Builder addDynamicPart(Renderable part, TransformGetter getter) {
public Builder addDynamicPart(
Renderable part,
TransformGetter getter
) {
return addPart(part, new Mat4(), getter);
}
public Builder addStaticPart(Renderable part) {
public Builder addStaticPart(
Renderable part
) {
return addStaticPart(part, IDENTITY);
}
@ -105,8 +127,12 @@ public class LambdaModel extends DynamicModel {
}
public static LambdaModel animate(Renderable model, TransformGetter transform) {
return new LambdaModel(new Renderable[] { model }, new Mat4[] { new Mat4() }, new boolean[] { true },
new TransformGetter[] { transform });
return new LambdaModel(
new Renderable[] { model },
new Mat4[] { new Mat4() },
new boolean[] { true },
new TransformGetter[] { transform }
);
}
}

View File

@ -38,7 +38,8 @@ public class ShapePartGroup {
for (int i = start; i < end; ++i) {
ShapePart face = faces[i];
assert this.texture == null ? (face.getTexture() == null)
assert this.texture == null
? (face.getTexture() == null)
: (face.getTexture().getSprite().getPrimitive() == this.texture);
indexCount += face.getIndexCount();

View File

@ -25,6 +25,7 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class ShapeParts {
@ -40,9 +41,48 @@ public class ShapeParts {
Vec3 width,
Vec3 height,
boolean flip
) {
return createRectangle(program, texture, colorMultiplier, origin, width, height, flip, null);
}
public static ShapePart createRectangle(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
Vec3 origin,
Vec3 width,
Vec3 height,
boolean flip,
Vec3 forcedNormals
) {
VertexBuilder builder = program.getVertexBuilder();
if (forcedNormals != null && builder instanceof WRPVertexBuilder) {
((WRPVertexBuilder) builder).addVertex(
origin,
colorMultiplier,
new Vec2(0, 0),
forcedNormals
);
((WRPVertexBuilder) builder).addVertex(
origin.add_(height),
colorMultiplier,
new Vec2(0, 1),
forcedNormals
);
((WRPVertexBuilder) builder).addVertex(
origin.add_(width),
colorMultiplier,
new Vec2(1, 0),
forcedNormals
);
((WRPVertexBuilder) builder).addVertex(
origin.add_(width).add(height),
colorMultiplier,
new Vec2(1, 1),
forcedNormals
);
} else {
builder.addVertex(
origin,
colorMultiplier,
@ -60,6 +100,7 @@ public class ShapeParts {
colorMultiplier,
new Vec2(1, 1)
);
}
ShortBuffer buffer = flip ? ShortBuffer.wrap(
new short[] {

View File

@ -0,0 +1,248 @@
/*
* Progressia
* Copyright (C) 2020-2021 Wind Corporation and contributors
*
* 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/>.
*/
package ru.windcorp.progressia.client.graphics.model;
import java.nio.ShortBuffer;
import java.util.Objects;
import glm.mat._3.Mat3;
import glm.mat._4.Mat4;
import glm.vec._2.Vec2;
import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram.WRPVertexBuilder;
import ru.windcorp.progressia.common.util.StashingStack;
import ru.windcorp.progressia.common.util.VectorUtil;
public class ShapePrototype {
private final ShapeRenderProgram program;
private Texture texture;
private final Vec3[] positions;
private final Vec4[] colorMultipliers;
private final Vec2[] textureCoords;
private final Vec3[] forcedNormals;
private ShortBuffer indices;
private ShortBuffer flippedIndices = null;
protected static final int TRANSFORM_STACK_SIZE = 64;
private final StashingStack<Mat4> transformStack = new StashingStack<>(
TRANSFORM_STACK_SIZE,
Mat4::new
);
public ShapePrototype(
ShapeRenderProgram program,
Texture texture,
Vec3[] positions,
Vec4[] colorMultipliers,
Vec2[] textureCoords,
Vec3[] forcedNormals,
ShortBuffer indices
) {
this.program = Objects.requireNonNull(program, "program");
this.texture = texture;
this.indices = Objects.requireNonNull(indices, "indices");
Objects.requireNonNull(positions, "positions");
Objects.requireNonNull(colorMultipliers, "colorMultipliers");
Objects.requireNonNull(textureCoords, "textureCoords");
if (forcedNormals != null && !(program instanceof WorldRenderProgram)) {
throw new IllegalArgumentException("Cannot force normals on non-WorldRenderPrograms cuz javahorse stupiddd");
}
if (forcedNormals == null) {
forcedNormals = new Vec3[positions.length];
}
this.positions = positions;
this.colorMultipliers = colorMultipliers;
this.textureCoords = textureCoords;
this.forcedNormals = forcedNormals;
if (positions.length != colorMultipliers.length) {
throw new IllegalArgumentException("positions.length (" + positions.length + ") != colorMultipliers.length (" + colorMultipliers + ")");
}
if (positions.length != textureCoords.length) {
throw new IllegalArgumentException("positions.length (" + positions.length + ") != textureCoords.length (" + textureCoords + ")");
}
if (positions.length != forcedNormals.length) {
throw new IllegalArgumentException("positions.length (" + positions.length + ") != forcedNormals.length (" + forcedNormals + ")");
}
transformStack.push().identity();
}
public ShapePart build() {
VertexBuilder builder = program.getVertexBuilder();
Vec3 transformedPosition = new Vec3();
for (int i = 0; i < positions.length; ++i) {
transformedPosition.set(positions[i]);
if (transformStack.getSize() > 1) {
VectorUtil.applyMat4(transformedPosition, transformStack.getHead());
}
if (forcedNormals[i] != null && builder instanceof WRPVertexBuilder) {
((WRPVertexBuilder) builder).addVertex(
transformedPosition, colorMultipliers[i], textureCoords[i], forcedNormals[i]
);
} else {
builder.addVertex(transformedPosition, colorMultipliers[i], textureCoords[i]);
}
}
return new ShapePart(texture, builder.assemble(), indices);
}
public ShapePrototype apply(Mat4 transform) {
for (Vec3 vector : positions) {
VectorUtil.applyMat4(vector, transform);
}
return this;
}
public ShapePrototype apply(Mat3 transform) {
for (Vec3 vector : positions) {
transform.mul(vector);
}
return this;
}
public Mat4 push() {
Mat4 previous = transformStack.getHead();
return transformStack.push().set(previous);
}
public ShapePrototype pop() {
transformStack.pop();
return this;
}
public ShapePrototype setTexture(Texture texture) {
this.texture = texture;
return this;
}
public ShapePrototype deleteTexture() {
this.texture = null;
return this;
}
public ShapePrototype resetColorMultiplier() {
for (Vec4 color : colorMultipliers) {
color.set(Colors.WHITE);
}
return this;
}
public ShapePrototype addColorMultiplier(Vec4 color) {
for (Vec4 c : colorMultipliers) {
c.mul(color);
}
return this;
}
public ShapePrototype flip() {
ShortBuffer tmp = indices;
indices = flippedIndices;
flippedIndices = tmp;
if (indices == null) {
int length = flippedIndices.limit();
indices = ShortBuffer.allocate(length);
for (int i = 0; i < length; ++i) {
indices.put(i, flippedIndices.get(length - i - 1));
}
}
return this;
}
public ShapePrototype makeDoubleSided() {
int length = indices.limit();
ShortBuffer newIndices = ShortBuffer.allocate(length * 2);
for (int i = 0; i < length; ++i) {
newIndices.put(i, indices.get(i));
}
for (int i = 0; i < length; ++i) {
newIndices.put(i + length, indices.get(length - i - 1));
}
indices = flippedIndices = newIndices;
return this;
}
public static ShapePrototype unitSquare(Texture texture, Vec4 color, ShapeRenderProgram program) {
return new ShapePrototype(
program,
texture,
new Vec3[] {
new Vec3(0, 0, 0),
new Vec3(0, 1, 0),
new Vec3(1, 0, 0),
new Vec3(1, 1, 0)
},
new Vec4[] {
new Vec4(color),
new Vec4(color),
new Vec4(color),
new Vec4(color)
},
new Vec2[] {
new Vec2(0, 0),
new Vec2(0, 1),
new Vec2(1, 0),
new Vec2(1, 1)
},
null,
ShortBuffer.wrap(new short[] {3, 1, 0, 2, 3, 0})
);
}
public static ShapePrototype unitSquare(Texture texture, Vec4 color) {
return unitSquare(texture, color, WorldRenderProgram.getDefault());
}
public static ShapePrototype unitSquare(Texture texture) {
return unitSquare(texture, Colors.WHITE, WorldRenderProgram.getDefault());
}
public static ShapePrototype unitSquare(Vec4 color) {
return unitSquare(null, color, WorldRenderProgram.getDefault());
}
public static ShapePrototype unitSquare() {
return unitSquare(null, Colors.WHITE, WorldRenderProgram.getDefault());
}
}

View File

@ -28,10 +28,15 @@ public class ShapeRenderHelper {
protected static final int TRANSFORM_STACK_SIZE = 64;
protected static final int COLOR_MULTIPLIER_STACK_SIZE = TRANSFORM_STACK_SIZE;
protected final StashingStack<Mat4> transformStack = new StashingStack<>(TRANSFORM_STACK_SIZE, Mat4::new);
protected final StashingStack<Mat4> transformStack = new StashingStack<>(
TRANSFORM_STACK_SIZE,
Mat4::new
);
protected final StashingStack<Vec4> colorMultiplierStack = new StashingStack<>(COLOR_MULTIPLIER_STACK_SIZE,
Vec4::new);
protected final StashingStack<Vec4> colorMultiplierStack = new StashingStack<>(
COLOR_MULTIPLIER_STACK_SIZE,
Vec4::new
);
{
transformStack.push().identity();

View File

@ -53,7 +53,8 @@ public class ShapeRenderProgram extends Program {
POSITIONS_ATTRIBUTE_NAME = "inputPositions",
UNIFORM_COLOR_MULTIPLER_ATTRIBUTE_NAME = "uniformColorMultiplier",
ATTRIBUTE_COLOR_MULTIPLER_ATTRIBUTE_NAME = "inputColorMultiplier",
TEXTURE_COORDS_ATTRIBUTE_NAME = "inputTextureCoords", USE_TEXTURE_UNIFORM_NAME = "useTexture",
TEXTURE_COORDS_ATTRIBUTE_NAME = "inputTextureCoords",
USE_TEXTURE_UNIFORM_NAME = "useTexture",
TEXTURE_SLOT_UNIFORM_NAME = "textureSlot";
private final Uniform4Matrix finalTransformUniform;
@ -64,11 +65,21 @@ public class ShapeRenderProgram extends Program {
private final Uniform1Int useTextureUniform;
private final Uniform1Int textureSlotUniform;
public ShapeRenderProgram(String[] vertexShaderResources, String[] fragmentShaderResources) {
super(new CombinedShader(attachVertexShader(vertexShaderResources)),
new CombinedShader(attachFragmentShader(fragmentShaderResources)));
public ShapeRenderProgram(
String[] vertexShaderResources,
String[] fragmentShaderResources
) {
super(
new CombinedShader(
attachVertexShader(vertexShaderResources)
),
new CombinedShader(
attachFragmentShader(fragmentShaderResources)
)
);
this.finalTransformUniform = getUniform(FINAL_TRANSFORM_UNIFORM_NAME).as4Matrix();
this.finalTransformUniform = getUniform(FINAL_TRANSFORM_UNIFORM_NAME)
.as4Matrix();
this.positionsAttribute = getAttribute(POSITIONS_ATTRIBUTE_NAME).asVertexArray();
@ -78,9 +89,11 @@ public class ShapeRenderProgram extends Program {
this.textureCoordsAttribute = getAttribute(TEXTURE_COORDS_ATTRIBUTE_NAME).asVertexArray();
this.useTextureUniform = getUniform(USE_TEXTURE_UNIFORM_NAME).as1Int();
this.useTextureUniform = getUniform(USE_TEXTURE_UNIFORM_NAME)
.as1Int();
this.textureSlotUniform = getUniform(TEXTURE_SLOT_UNIFORM_NAME).as1Int();
this.textureSlotUniform = getUniform(TEXTURE_SLOT_UNIFORM_NAME)
.as1Int();
}
private static String[] attachVertexShader(String[] others) {
@ -91,7 +104,10 @@ public class ShapeRenderProgram extends Program {
return ObjectArrays.concat(SHAPE_FRAGMENT_SHADER_RESOURCE, others);
}
public void render(ShapeRenderHelper helper, Shape shape) {
public void render(
ShapeRenderHelper helper,
Shape shape
) {
use();
configure(helper);
@ -129,13 +145,34 @@ public class ShapeRenderProgram extends Program {
int vertexStride = getBytesPerVertex();
int offset = 0;
positionsAttribute.set(3, GL11.GL_FLOAT, false, vertexStride, vertices, offset);
positionsAttribute.set(
3,
GL11.GL_FLOAT,
false,
vertexStride,
vertices,
offset
);
offset += 3 * Float.BYTES;
colorsAttribute.set(4, GL11.GL_FLOAT, false, vertexStride, vertices, offset);
colorsAttribute.set(
4,
GL11.GL_FLOAT,
false,
vertexStride,
vertices,
offset
);
offset += 4 * Float.BYTES;
textureCoordsAttribute.set(2, GL11.GL_FLOAT, false, vertexStride, vertices, offset);
textureCoordsAttribute.set(
2,
GL11.GL_FLOAT,
false,
vertexStride,
vertices,
offset
);
offset += 2 * Float.BYTES;
return offset;
@ -156,8 +193,12 @@ public class ShapeRenderProgram extends Program {
useTextureUniform.set(0);
}
GL11.glDrawElements(GL11.GL_TRIANGLES, group.getIndexCount(), GL11.GL_UNSIGNED_SHORT,
group.getByteOffsetOfIndices());
GL11.glDrawElements(
GL11.GL_TRIANGLES,
group.getIndexCount(),
GL11.GL_UNSIGNED_SHORT,
group.getByteOffsetOfIndices()
);
}
public int getBytesPerVertex() {
@ -179,9 +220,13 @@ public class ShapeRenderProgram extends Program {
Sprite sprite = face.getTexture().getSprite();
for (int i = 0; i < face.getVertexCount(); i++) {
int offset = vertices.position() + i * getBytesPerVertex() + (3 * Float.BYTES + 4 * Float.BYTES);
int offset = vertices.position() + i * getBytesPerVertex() + (3 * Float.BYTES +
4 * Float.BYTES);
v.set(vertices.getFloat(offset + 0 * Float.BYTES), vertices.getFloat(offset + 1 * Float.BYTES));
v.set(
vertices.getFloat(offset + 0 * Float.BYTES),
vertices.getFloat(offset + 1 * Float.BYTES)
);
v.mul(sprite.getSize()).add(sprite.getStart());
@ -199,11 +244,34 @@ public class ShapeRenderProgram extends Program {
}
public static interface VertexBuilder {
VertexBuilder addVertex(float x, float y, float z, float r, float g, float b, float tx, float ty);
VertexBuilder addVertex(
float x,
float y,
float z,
float r,
float g,
float b,
float tx,
float ty
);
VertexBuilder addVertex(float x, float y, float z, float r, float g, float b, float a, float tx, float ty);
VertexBuilder addVertex(
float x,
float y,
float z,
float r,
float g,
float b,
float a,
float tx,
float ty
);
VertexBuilder addVertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords);
VertexBuilder addVertex(
Vec3 position,
Vec4 colorMultiplier,
Vec2 textureCoords
);
ByteBuffer assemble();
}
@ -225,35 +293,84 @@ public class ShapeRenderProgram extends Program {
private final List<Vertex> vertices = new ArrayList<>();
@Override
public VertexBuilder addVertex(float x, float y, float z, float r, float g, float b, float a, float tx,
float ty) {
vertices.add(new Vertex(new Vec3(x, y, z), new Vec4(r, g, b, a), new Vec2(tx, ty)));
public VertexBuilder addVertex(
float x,
float y,
float z,
float r,
float g,
float b,
float a,
float tx,
float ty
) {
vertices.add(
new Vertex(
new Vec3(x, y, z),
new Vec4(r, g, b, a),
new Vec2(tx, ty)
)
);
return this;
}
@Override
public VertexBuilder addVertex(float x, float y, float z, float r, float g, float b, float tx, float ty) {
vertices.add(new Vertex(new Vec3(x, y, z), new Vec4(r, g, b, 1f), new Vec2(tx, ty)));
public VertexBuilder addVertex(
float x,
float y,
float z,
float r,
float g,
float b,
float tx,
float ty
) {
vertices.add(
new Vertex(
new Vec3(x, y, z),
new Vec4(r, g, b, 1f),
new Vec2(tx, ty)
)
);
return this;
}
@Override
public VertexBuilder addVertex(Vec3 position, Vec4 colorMultiplier, Vec2 textureCoords) {
vertices.add(new Vertex(new Vec3(position), new Vec4(colorMultiplier), new Vec2(textureCoords)));
public VertexBuilder addVertex(
Vec3 position,
Vec4 colorMultiplier,
Vec2 textureCoords
) {
vertices.add(
new Vertex(
new Vec3(position),
new Vec4(colorMultiplier),
new Vec2(textureCoords)
)
);
return this;
}
@Override
public ByteBuffer assemble() {
ByteBuffer result = BufferUtils.createByteBuffer(DEFAULT_BYTES_PER_VERTEX * vertices.size());
ByteBuffer result = BufferUtils.createByteBuffer(
DEFAULT_BYTES_PER_VERTEX * vertices.size()
);
for (Vertex v : vertices) {
result.putFloat(v.position.x).putFloat(v.position.y).putFloat(v.position.z)
.putFloat(v.colorMultiplier.x).putFloat(v.colorMultiplier.y).putFloat(v.colorMultiplier.z)
.putFloat(v.colorMultiplier.w).putFloat(v.textureCoords.x).putFloat(v.textureCoords.y);
result
.putFloat(v.position.x)
.putFloat(v.position.y)
.putFloat(v.position.z)
.putFloat(v.colorMultiplier.x)
.putFloat(v.colorMultiplier.y)
.putFloat(v.colorMultiplier.z)
.putFloat(v.colorMultiplier.w)
.putFloat(v.textureCoords.x)
.putFloat(v.textureCoords.y);
}
result.flip();

View File

@ -20,10 +20,12 @@ package ru.windcorp.progressia.client.graphics.model;
import java.util.Map;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Shapes {
@ -124,6 +126,36 @@ public class Shapes {
return result;
}
public static Shape createParallelogram(
// Try saying that 10 times fast
ShapeRenderProgram program,
Vec3 origin,
Vec3 width,
Vec3 height,
Vec4 colorMultiplier,
Texture texture
) {
Shape result = new Shape(
Usage.STATIC,
program,
ShapeParts.createRectangle(
program,
texture,
colorMultiplier,
origin,
width,
height,
false
)
);
return result;
}
public static class PppBuilder {
private final ShapeRenderProgram program;
@ -260,6 +292,34 @@ public class Shapes {
return this.setSize(size, size, size);
}
public PppBuilder centerAt(float x, float y, float z) {
origin.set(x, y, z);
origin.mul(2);
origin.sub(width);
origin.sub(height);
origin.sub(depth);
origin.div(2);
return this;
}
public PppBuilder apply(Mat4 transform) {
VectorUtil.applyMat4(origin, transform);
VectorUtil.rotateOnly(width, transform);
VectorUtil.rotateOnly(height, transform);
VectorUtil.rotateOnly(depth, transform);
return this;
}
public PppBuilder scale(float factor) {
origin.mul(factor);
width.mul(factor);
height.mul(factor);
depth.mul(factor);
return this;
}
public PppBuilder flip() {
this.flip = true;
return this;
@ -285,4 +345,108 @@ public class Shapes {
}
public static class PgmBuilder {
private final ShapeRenderProgram program;
private final Vec3 origin = new Vec3(0, 0, 0);
private final Vec3 width = new Vec3(1, 0, 0);
private final Vec3 height = new Vec3(0, 1, 0);
private final Vec4 colorMultiplier = new Vec4(1, 1, 1, 1);
private final Texture texture;
public PgmBuilder(ShapeRenderProgram program, Texture texture) {
this.program = program;
this.texture = texture;
}
public PgmBuilder setOrigin(Vec3 origin) {
this.origin.set(origin);
return this;
}
public PgmBuilder setOrigin(float x, float y, float z) {
this.origin.set(x, y, z);
return this;
}
public PgmBuilder setColorMultiplier(Vec4 colorMultiplier) {
this.colorMultiplier.set(colorMultiplier);
return this;
}
public PgmBuilder setColorMultiplier(float r, float g, float b) {
this.colorMultiplier.set(r, g, b, 1);
return this;
}
public PgmBuilder setColorMultiplier(float r, float g, float b, float a) {
this.colorMultiplier.set(r, g, b, a);
return this;
}
public PgmBuilder setWidth(Vec3 vector) {
this.width.set(vector);
return this;
}
public PgmBuilder setWidth(float x, float y, float z) {
this.width.set(x, y, z);
return this;
}
public PgmBuilder setWidth(float x) {
this.width.set(x, 0, 0);
return this;
}
public PgmBuilder setHeight(Vec3 vector) {
this.height.set(vector);
return this;
}
public PgmBuilder setHeight(float x, float y, float z) {
this.height.set(x, y, z);
return this;
}
public PgmBuilder setHeight(float y) {
this.height.set(0, y, 0);
return this;
}
public PgmBuilder setSize(float x, float y) {
return this.setWidth(x).setHeight(y);
}
public PgmBuilder setSize(float size) {
return this.setSize(size, size);
}
public PgmBuilder centerAt(float x, float y, float z) {
origin.set(x, y, z);
origin.mul(2);
origin.sub(width);
origin.div(2);
return this;
}
public Shape create() {
return createParallelogram(
program,
origin,
width,
height,
colorMultiplier,
texture
);
}
}
}

Some files were not shown because too many files have changed in this diff Show More