86 Commits

Author SHA1 Message Date
71250104ea Better 3d falling
-Sand now can fall from and to the direction of gravity
-Registers every FallingBlock block to a different value. It doesnt
quite work though
-Removed unused code
2021-09-10 16:20:09 -04:00
51752f95f9 Merge pull request #15 from opfromthestart/falling-block
Falling block
2021-08-23 13:35:58 -04:00
175f092673 Fixed some stuff
-Added default gravity block check in server
-Some things I had to copy since i didnt merge right
-Adds all gravity blocks to FallingBlock list
-LogTop debug thing to check for good textures
-Some texture rendering(see above)
-Some control changes since I need to get to the edge quickly
2021-08-23 13:33:18 -04:00
59129f95c9 Why
-Idk this has to be "Sand" and not "Test:Sand"
2021-08-22 14:04:42 -04:00
efff41a6db Merge branch 'master' into falling-block 2021-08-22 14:03:31 -04:00
30879a1241 Merge pull request #1 from OLEGSHA/master
Uhhh pull
2021-08-22 13:47:54 -04:00
9fc1a21191 Added rock DB and worldgen
- Added Rocks container
- Added DiscreteNoise and a DIY Worley generator
- Added RockLayer
  - Used to generate rock strata
- Reworked SurfaceTerrainGenerator to use contexts
2021-08-21 23:05:54 +03:00
4f620b7261 Added 6 more types of rocks
- Added black granite, dolomite, eclogite, gabbro, limestone and marble
  - Monolith, cracked, gravel and sand variants
- Renamed Test:Granite to Test:RedGranite
- Added red granite sand
2021-08-21 17:44:01 +03:00
6f90bf345b Incremented version and made some small changes
- Version is now pre-alpha 2. Yay?
- Removed compass display in the lower-left
- The cross can now be hidden by pressing F1
  - Does not hide LayerAbout though
- Generator spawns the player in the middle of a surface rather than at
its edge
2021-08-20 21:37:49 +03:00
2328f2ae3a Replaced placeholder worldgen with a passable one
- Removed old (pre-planet) worldgen
  - TestGravityModel remains
- Moved surface generator to .logic.world.generation.surface
- Split planet generator into generator logic and config
  - Logic moved to .logic.world.generation.planet
  - Config extracted into TestGenerationConfig & others in .test.gen
  - GravityModel renamed to Test:PlanetGravityModel
- TestTerrainGenerator utilities moved and made public thru Fields
- Reconfigured planet generator to be a passable
  - Increased planet size to R=0.5 km
  - Added noise-based heightmaps (fabulous cliffs included)
  - Added noise-based forest density map
  - Reworked all SurfaceFeatures
    - TestGrassFeature now also places scatter and flowers
    - TestTreeFeature and TestBushFeature:
      - Common code exctracted to MultiblockVegetationFeature
      - Made prettier
  - gud muscle flex yeeeeeeeeeeee
- Fixed a bug in the gravity model
- A lot of other changes that I already forgot about
2021-08-20 18:07:41 +03:00
15b5d367b4 Chunk loading region around the player is now compressed vertically 2021-08-18 10:58:18 +03:00
ca2014802a Resolved a random deadlock that became way too frequent to ignore
There was a deadlock that sometimes occurred when passing PacketSetBlock
to client: PacketSetBlock first acquired monitor of the chunk it
modified, than attempted to lock the visible chunks set. In parallel, a
chunk update first acquires the visible chunks, than the individual
chunk. Turns out, markForUpdate() doesn't need to be synchronized, so
PacketSetBlock can now acquire the locks sequentially, avoiding the
deadlock.
2021-08-18 09:30:29 +03:00
539a61e854 Almost resolved feature generation issue, removed dead code
- SurfaceFeatures that change neighboring chunks no longer suffer from
tearing if world saving is enabled
  - World saving is still disabled by default
    - wontfix until we get a new, more optimized world IO system
  - See GitHub issue #13 for details
- Removed WorldAccessor getter from Server. This is an implementation
detail.
- Removed SurfaceWorld, SurfaceFeature.Request (see previous commit)
2021-08-18 00:26:45 +03:00
a3760d7425 Removed erroneous RelFace resolution by WorldAccessor 2021-08-17 19:21:57 +03:00
d33b48578d Added SurfaceContexts to replace SurfaceWorld+Request. WIP
There is a problem with features when up != POS_Z
2021-08-17 16:05:44 +03:00
a6fd81ba1e Contexts now only accept RelFace; fixed tile placement crash 2021-08-16 13:05:57 +03:00
82872c7cf3 Fixed RotatingServerContext
- RotatingServerContext now rotates coordinates, too
- Fixed a bug caused by the implementation of push methods by
TransformingServerContext
- Fixed unbounded recursion in WorldGenericRO.hasTile(Vec3i, BlockFace,
int)
2021-08-15 23:54:25 +03:00
54c66d28d6 Added TransformingServerContext and RotatingServerContext. WIP
There is a problem with faces in contexts when up != POS_Z, world
crashes soon after startup

- Added TransformingServerContext - a common basis for context wrappers
that alter the coordinate space
- Added RotatingServerContext - a context wrapper that rotates the
coordinate space
  - Used to ensure positive Z is up
- PacketAffectTile now checks the provided tile tag for validity
  - This causes a crash when the invalid action is requested, not
executed
- TickChunk task reuses contexts
2021-08-13 16:11:46 +03:00
78a1c25554 Grass tiles have regained the ability to disappear under blocks
- Test:Grass now (again) randomly disappears under opaque blocks
- Fixed a truly egregious bug in AbstractContextRO.pop()
- Fixed BlockContext.pushRelative(AbsRelation)
- Some changes in toString methods in contexts
2021-08-11 13:45:47 +03:00
a03c783fc9 Fixing bugs introduced in previous commit
- Fixed AbstractContextRO.isSubcontexting()
- Fixed push(...) overrides in FilterServerContext
- Fixed DefaultChunkLogic.tmp_generateTickLists()
- Debug screen now also lists visible and loaded chunks
- AbstractContextRO.Frame now has a toString()
2021-08-11 13:02:18 +03:00
0a45613e45 Began work on integrating the new Contexts. Compiles but does not work
- All TickContexts including TickContextMutable are deleted
- Previous occurrences of TickContexts are replaced by appropriate
ServerContexts
- Added Context.popAndReturn methods for convenience

Current known problems:
- World does not generate properly on startup
- No bulk methods in the new API (the likes of forEachTile, etc.)
- AbsFace/RelFace ambiguity in the new API (see
TestTileLogicGrass.java:68)
- TestTileLogicGrass.java:53 is disabled for some reason
2021-08-09 20:32:15 +03:00
5fb4c601ff Added ReportingServerContext
- Added ReportingServerContext to listen for write events in contexts
- Fixed method WorldGenericContextWO.setBlock(Vec3i, B)
2021-08-08 14:07:07 +03:00
020802a89c Added FilterServerContext and DefaultServerContextLogic
- Added DefaultServerContextLogic
  - A standard implementation of ServerTileContext.Logic that delegates
all methods to a ServerTileContext instance
  - Now used by DefaultServerContextImpl
- Added FilterServerContext
  - A base for creating context wrappers
2021-08-08 12:19:31 +03:00
0f909039fe Renamed ReusableServerContext to DefaultServerContext 2021-08-06 11:02:43 +03:00
15f741bc04 Added subcontexting. Context#subcontexting. 2021-08-06 10:49:40 +03:00
80541eafc3 Renamed BlockFace contexts into TileStack contexts 2021-08-05 19:39:39 +03:00
0f60d45ffa Contexts no longer expose to World, Chunks, TileStacks or TileReferences
The intention is to bottleneck all read queries and write requests
through context objects without the need to create practically useless
Chunk, TileStack and TileRef wrappers.

- WorldGenericContext{RO,WO} no longer extend WorldGeneric{RO,WO}
- Added tag access for tiles to contexts
- Documented almost all context methods
- Renamed isBlockLoaded() to isLocationLoaded()
- I found some inner peace
2021-08-05 16:42:21 +03:00
fbc803d6e2 Laid some groundwork for context implementation; rewrite incoming
- ReusableServerContext will be the default context implementation. It
is sort of implemented, but not really
- WorldLogic{,RO} now declare getData() method
- WorldGenericContextWO.removeEntity(EntityGeneric) received a default
implementation
- TileDataContext.getTag() received a default implementation
2021-08-04 18:42:16 +03:00
dccfcc9419 Better /Stuff
-Uses Coordinates a little in TestWorldGenerator. Ill do more eventually I think
-Better sprite getting for FallingBlocks
2021-08-03 15:05:05 -04:00
c2a2cc074a Some Stuff to Fix Bugs
-Improved debug info of Fallingblock ticking
-Fixed unsynchronized access as per OLEGSHA
-Maybe better server side deletion of blocks
2021-08-03 14:45:36 -04:00
1ee9a55d19 Defined Data and Server context interfaces
Server read-write (not RO) interfaces do not extend the complimentary
*GenericContextWO interfaces by design. It makes no sense to set a
BlockLogic, but it makes plenty of sense to refer to a ChunkData rather
than a ChunkDataRO.
2021-07-31 20:47:23 +03:00
a338a00f1d A change to the class hierarchy of WorldLogic similar to prev commit
- Renamed ChunkLogic -> DefaultChunkLogic, WorldLogic ->
DefaultWorldLogic
- Created/rewritten TileLogicReference{,RO}, TileLogicStack{,RO},
ChunkLogic{,RO}
- Drafted up something for ServerWorldContext & friends, WIP

(see commit 9a32660 for more details)
2021-07-31 16:01:25 +03:00
9a326603cd Still working on Contexts. Introduced a billion interfaces. WIP.
*takes a deep breath
- Renamed Generic world structure interfaces to the following scheme:

      {Block,Tile,Chunk,World}Generic{,Stack,Reference}{RO,WO}
      (e.g. GenericWritableChunk -> ChunkGenericWO)

    - RO is Read Only, WO is Write Only
- Generic writable interfaces no longer extend their read-only
counterparts (thus Write Only)
- TileGenericStack{RO,WO} are now interfaces; AbstractList is only
introduced by final implementations
- TileGenericReferenceRO now has a WO counterpart
- Fixed compilation issues with the previous commit
- Declared some additional functionality for Generic interfaces
- Old ChunkData and WorldData renamed to DefaultChunkData and
DefaultWorldData
  - Now considered to be an implementation detail; references will be
minimized
- Introduced TileDataStack{,RO}, TileDataReference{,RO}, ChunkData{,RO},
WorldData{,RO} interfaces
  - Suffix -RO indicates Read Only, no suffix means read-write
  - To be used in place of DefaultChunk and DefaultWorld
  - Designed to support wrappers and "fake" implementations
  - May need some refinement (fix return/parameter types, ...)
- Surface world generator is now implemented poorly (WIP)
- Should compile. May work fine. Unless Java inheritance rules have
screwed me over.
2021-07-23 22:46:49 +03:00
d7afe39f00 Added more generic Contexts. WIP. 2021-07-15 22:26:20 +03:00
0264e512ab Merge branch 'master' into addPlanet
Conflicts:
	src/main/java/ru/windcorp/progressia/test/LayerTestGUI.java
	src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java
2021-07-12 16:20:15 +03:00
e47fb3c4bd Added surface features. Tree generation is currently broken!
- Added SurfaceFeature
  - Used to generate chunk features
- Added SurfaceTopLayerFeature
  - A superclass for features that are only concerned with editing the
surface
- Added grass, temporary bushes and temporary trees
  - Bushes and trees do not generate properly due to bugs
  - Added Test:TemporaryLeaves
- Added SurfaceWorld (a GenericWritableWorld wrapper)
- Added some unit tests for rotation utilities
- Fixed a whole lot of bugs
2021-07-07 17:37:08 +03:00
eace6733ce Added Menus and cursor visibility management
- Layers now have a CursorPolicy
  - Used to enable/disable cursor based on top layer
- Added a default menu layer implementation
2021-06-28 17:45:49 +03:00
085f602427 Panel now has decorations; functionality moved to new superclass Group 2021-06-25 17:33:46 +03:00
737b495fc4 Ported GUI improvements from opfromthestart/Progressia
These changes were originally implemented by opfromthestart. OLEGSHA
then patched a whole bunch of stuff

- Components can now be disabled
- Added BasicButton
- Added Button, Checkbox and RadioButton (with RadioButtonGroup)
- Added some new colors to Colors
- Pressing Esc in game pops up a menu (WIP)
- Fixed text z-ordering
- Fixed LayoutGrid Y-direction
2021-06-25 14:35:36 +03:00
e58007ea11 Format
Format
2021-06-10 17:09:24 -04:00
ae2980c9de Bug Fixing and Performance
-Multiple null checks(It doesn't work without them)
-Checks to make sure entities are within loaded chunks
-Actual entity removal
2021-06-10 17:00:09 -04:00
3879e5ffac Sand Towers Fall
-The listener in TestWorldGenerator updates any block above it if that block is a block that can be affected by gravity.
-Format happened(I forgot)
2021-06-10 15:56:15 -04:00
47eb9fa5af Conservation of Matter
-Placing a Test:Sand block now deletes the block and summons a Test:FallingBlock in its place.
2021-06-10 14:08:35 -04:00
bf49687ab6 Better performance maybe?
-Changed as many of the ClientState statements to context as I could.
-Only looks for a sand based update once per block update(I think it was twice before)
2021-06-10 12:50:51 -04:00
b3ae829383 Almost All Works
-Added Set in TestEntityLogicFallingBlock to keep track of gravity blocks
  -Used this set in listener in TestWorldGenerator
-setInvisible and isDone() methods in TestEntityDataFallingBlock
  -Used to determine if the entity should be rendered/cared about in TestEntityRenderFallingBlock and TestEntityLogicFallingBlock
-Added more accurate Vec3i mod and div methods in TestEntityLogicFallingBlock
  *Change this to a better place
-TestEntityLogicFallingBlock actually places the block when the entity lands
-Better air block detection in TestWorldGenerator
*Still cannot erase the original placed block, not sure why.
2021-05-09 15:57:38 -04:00
b374e9a736 Added FallingBlock Classes
-Added TestEntityDataFallingBlock class
  -Right now only supports Test:Sand
  -Kinda just copied from Statie
-Added TestEntityRenderFallingBlock
  -Used BlockRenderTexturedCube and TestEntityRenderStatie for this one
  -Has a setTexture method, but it needs improving
-Added TestEntityLogicFallingBlock class
  -Allows for a falling block to return to being a block.
  -The coordinates of the entity never move, even if it does ingame.
-In TestContent
  -Registers Test:FallingBlock
-In TestWorldGenerator
  -Adds listener for a sand block being placed
    -Listener replaces the block with a FallingBlock entity
    -It should also replace the block with air but it hasn't been working for me.
2021-05-04 20:29:06 -04:00
531a8c99c3 Added CONTRIBUTING.md. Discussion is welcome. 2021-04-27 23:22:48 +03:00
6fb7e7fc04 Added SurfaceWorld to facilitate surface feature generation 2021-04-13 15:18:15 +03:00
20dccf3d12 Some more refactoring of generic world-related classes. May not compile.
- Added GenericWritableWorld
- Moved static methods from GenericChunk to GenericChunks
- GenericEntity now declares getEntityId()
- GenericWorld now declares getEntity(long)
- Added a lambda-based mapToFaces variations for AbsFace and RelFace
2021-04-09 23:16:08 +03:00
32851b8fb0 Added a serverside uptime tick counter 2021-04-09 20:34:47 +03:00
a95bdf1efe Moved .setBlockRel(...) implementation to GenericWritableChunk 2021-04-09 20:15:07 +03:00
e0a03cad1d More refactoring of GenericChunk and pals
- Genericized TileReference
- Unified template arguments
2021-04-06 00:36:38 +03:00
2532e80f6a Moved some methods from ChunkData to GenericChunk
- Moved some method definitions from ChunkData to GenericChunk
- Moved some method definitions from ChunkData to GenericWritableChunk
- Refactored GenericChunk including changing some method signatures
- Added some documentation for GenericChunk
2021-04-05 17:30:37 +03:00
3c3f3816df Fixed a generation issue and refactored some code around WorldGenerators
- ChunkRequestDaemon now attempts to generate requested loaded non-ready
chunks upon discovery
- The server is now only specified once for WorldGenerator
- WorldLogic now actively checks the generation contract
2021-04-02 21:51:52 +03:00
2d3d250f92 Renamed Scatter to Feature and added a generation feature interface
- Renamed SurfaceScatterGenerator to SurfaceFeatureGenerator
- Renamed PlanetScatterGenerator to PlanetFeatureGenerator
- Added a very basic interface for adding generation features
- Removed debug leftovers from PlayerManager and VisionManager
2021-04-02 21:29:09 +03:00
c49fdfa5ff Added a crude music player and fixed a lot of OpenAL stuff
- Game will now play all Vorbis files in music/ directory (at runtime)
  - Random order with random pauses (15 to 60 sec)
  - Force start by pressing M
- Added AudioRegistry for performance
- Speakers now can change data correctly
- Added ResourceReaders
  - Classpath and Filesystem for now
- Added local controls. Use with ControlTriggers.localOf(...)

NB: No actual music files included (yet). Place them in music/ directory
of the game directory (e.g. in <project>/run/music).
2021-03-26 23:51:51 +03:00
9d7f69892b Removed unused import and renamed VSYNC to VSync 2021-03-26 21:57:54 +03:00
7ecdfdfb4d Added scatter generation logic to TestPlanetGenerator
- Scatter generation is now triggered properly in TestPlanetGenerator
- WorldGenerators are now required to call addChunk() themselves (again)
- ChunkManager now generates loaded chunks that are not ready
- Chunks scheduled for unloading no longer unload if they become
requested while in queue
2021-03-26 21:26:05 +03:00
4332a78221 Refactored ChunkManager and EntityManager, added server event bus 2021-03-26 20:26:12 +03:00
ef572c43c7 Updated documentation for GuavaEventBusHijacker and ReportingEventBus 2021-03-25 17:15:03 +03:00
f28c765e3f Made Gravity Models configurable with packets 2021-03-15 21:02:33 +03:00
f4311fb27c Created a bare-bones implementation of the final planet generator
- Added Planet generator
  - Uses temporary generation algorithms
- Added Surface generator
- Added FloatRangeMap
2021-03-15 18:54:53 +03:00
abd8d9eebb Moved some functionality into WorldGenerator
- WorldGenerators now suggest a spawn location
- WorldGenerators are no longer responsible for adding chunks
2021-02-28 23:31:57 +03:00
a9a21ce664 Moved planet generation code to its own package 2021-02-28 23:03:23 +03:00
bd5a1fa04e Added rotating AABBs through lots of pain and suffering
- Collision models now rotate to match entity's general up direction
- Extracted rotation logic from RelRelation into AxisRotations
- Test:PlanetGravityModel is now properly centered
- Fixed some small bugs
2021-02-28 22:55:51 +03:00
2d55d4db51 Added a cubic gravity model and fixed some stuff
- Added TestPlanetGenerator and a corresponding gravity model
- Fixed gravity-triggered camera rotation
2021-02-22 15:38:14 +03:00
d438d2aa14 Linked GravityModel to a WorldGenerator and added GM comms transfer
- WorldData no longer acquires a GravityModel automatically
- On the server, GravityModel is specified by WorldGenerator
- On the client, GravityModel is received from the server via a
PacketSetGravityModel
2021-02-07 01:01:37 +03:00
d3c5011063 Replaced AbsFace with RelFace or BlockFace where appropriate
- Added BlockFace - a *Face superclass
- Refactored and optimized Rel{Relation, Face}
- Replaced most AbsFace references with BlockFace or RelFace
- Chunks now have an up direction
  - Determined by GravityModel's discrete up
  - Static; cannot change unless chunk is reloaded
  - Chunk models are now rendered rotated accordingly
- Fixed some minor bugs that were somehow revealed by these changes
- Moved TileLogicGrass to .test, where it belongs
- Disabled grass despawn until a new worldgen is implemented
2021-02-07 00:45:43 +03:00
10d271059c Added RelRelation and RelFace; added discrete up vector to GravityModel 2021-02-02 18:49:55 +03:00
acef9d32df Changed packages for relations, renamed Face to ShapePart
- Added BlockRelation as an abstract superclass to existing relations
  - Must be given an absolute "up" direction before use
- Moved AbsFace, AbsRelation and BlockRelation to .world.rels
- Renamed Face to ShapePart to reduce confusion with AbsFace
2021-02-01 19:14:49 +03:00
848178b343 Renamed BlockFace and BlockRelation to AbsFace and AbsRelation
- Renamed BlockFace to AbsFace
- Renamed BlockRelation to AbsRelation
- Renamed AbsFace constants using the following scheme:
  POS_X, NEG_Y, etc.
2021-02-01 17:25:07 +03:00
b1666fa4b9 Fixed a bunch of issues with gravity and implemented gravity changes
Also added DebugGraphics and made VectorUtil comply with the general Vec
contract
2021-01-31 23:34:24 +03:00
73d24d36f4 Added support VSYNC and fixed crashreporter formatter 2021-01-31 13:29:10 +03:00
bdb3bff570 Merge branch 'master' of git@github.com:OLEGSHA/Progressia.git 2021-01-31 13:23:05 +03:00
6997bb1104 Fixed compilation issue 2021-01-31 13:12:10 +03:00
eac0a34516 Clarified RAM requirement in README.md 2021-01-30 22:59:01 +03:00
f9717be412 Switched to using looking-at vectors instead of Euler angles
Also fixed camera jittering and added some vector functions
2021-01-29 23:19:22 +03:00
127d1c3d87 Refactored Sound Engine
- Renamed SoundEffect to Sound
- Now Music inherits Sound
- Now every sound has parameter timeLength
2021-01-29 18:35:05 +03:00
26a35f306c Fixed chunk model updates 2021-01-26 22:21:03 +03:00
52f3f653d8 Added Fullscreen mode 2021-01-26 21:23:33 +03:00
553837f207 GravityModels now take position into account
- Also documented GravityModel
2021-01-25 22:06:34 +03:00
8c5493f78e Added GravityModels, removed gravity switch
- Added GravityModel
  - can specify gravity varying by location and time
  - Added GravityModelRegistry
  - Stored in WorldData
- Removed Minecraft gravity mode
2021-01-25 21:35:46 +03:00
fc85eb5658 Removed accidentally committed local build.gradle 2021-01-25 10:46:02 +03:00
260562310a Refactored CROs, removed internal chunk borders. Documented CROs.
- Refactored CROs
- Renamed CROOpaqueCube to CROSurface (including relevant interfaces)
- Optimized CROS
  - CROS now takes neighbor chunks into account
- Refactored default OptimizedSurface implementations
- Documented some CRO code
2021-01-24 20:48:38 +03:00
85edc07c75 Incremented version and corrected whitespace in buildPackages.sh 2021-01-21 18:15:50 +03:00
fcf225f9c7 Updated NSIS installer
Added:
- game icon
- left side image
- desktop shortcut
- main menu shortcut
- working directory in AppData
2021-01-20 18:09:59 +03:00
609 changed files with 20590 additions and 8630 deletions

2
.gitignore vendored
View File

@ -30,6 +30,8 @@ build_packages/*
!build_packages/NSIS !build_packages/NSIS
build_packages/NSIS/* build_packages/NSIS/*
!build_packages/NSIS/ProgressiaInstaller.nsi !build_packages/NSIS/ProgressiaInstaller.nsi
!build_packages/NSIS/logo.ico
!build_packages/NSIS/left_side.bmp
# ... and except build_packages/DEB/template # ... and except build_packages/DEB/template
!build_packages/DEB !build_packages/DEB

View File

@ -16,7 +16,7 @@ temperature mechanics and a parallelism-capable server.
- GNU/Linux (x64, arm32 or arm64), Windows XP or later (x64 or x86) or MacOS (x64) - GNU/Linux (x64, arm32 or arm64), Windows XP or later (x64 or x86) or MacOS (x64)
- Java 8 or later - Java 8 or later
- OpenGL 2.1 or later - OpenGL 2.1 or later
- Probably at least 4 GiB RAM - Probably about 0.5 GiB RAM
- Less than 1 GiB of storage space - Less than 1 GiB of storage space
See [Build Guide](docs/building/BuildGuide.md) for compilation requirements. See [Build Guide](docs/building/BuildGuide.md) for compilation requirements.

View File

@ -128,6 +128,7 @@ buildWindowsInstaller() {
{ {
cp -r 'build/libs/lib' 'build_packages/NSIS/lib' && cp -r 'build/libs/lib' 'build_packages/NSIS/lib' &&
cp 'build/libs/Progressia.jar' 'build_packages/NSIS/Progressia.jar' && cp 'build/libs/Progressia.jar' 'build_packages/NSIS/Progressia.jar' &&
cp 'LICENSE' 'build_packages/NSIS/LICENSE.txt' &&
echo "------ NSIS ------" && echo "------ NSIS ------" &&
makensis "$configurationFile" && makensis "$configurationFile" &&
echo "---- NSIS END ----" && echo "---- NSIS END ----" &&
@ -144,6 +145,9 @@ buildWindowsInstaller() {
if [ -e 'build_packages/NSIS/Progressia.jar' ]; then if [ -e 'build_packages/NSIS/Progressia.jar' ]; then
rm 'build_packages/NSIS/Progressia.jar' rm 'build_packages/NSIS/Progressia.jar'
fi fi
if [ -e 'build_packages/NSIS/LICENSE.txt' ]; then
rm 'build_packages/NSIS/LICENSE.txt'
fi
echo "Cleaned up" echo "Cleaned up"
} || { } || {
echoerr "Could not clean up after building Windows installer" echoerr "Could not clean up after building Windows installer"

View File

@ -10,16 +10,32 @@
;-------------------------------- ;--------------------------------
;General ;General
!define PROJECT_NAME "Progressia"
; MUI Settings / Icons
!define MUI_ICON "logo.ico"
;!define MUI_UNICON ;Uninstall icon
; MUI Settings / Header
; !define MUI_HEADERIMAGE
; !define MUI_HEADERIMAGE_RIGHT
; !define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\orange-r-nsis.bmp"
; !define MUI_HEADERIMAGE_UNBITMAP "${NSISDIR}\Contrib\Graphics\Header\orange-uninstall-r-nsis.bmp"
; MUI Settings / Wizard
!define MUI_WELCOMEFINISHPAGE_BITMAP "left_side.bmp"
!define MUI_UNWELCOMEFINISHPAGE_BITMAP "left_side.bmp"
;Name and file ;Name and file
Name "Progressia" Name "${PROJECT_NAME}"
OutFile "ProgressiaInstaller.exe" OutFile "${PROJECT_NAME}Installer.exe"
Unicode True Unicode True
;Default installation folder ;Default installation folder
InstallDir "$PROGRAMFILES\Progressia" InstallDir "$PROGRAMFILES\${PROJECT_NAME}"
;Get installation folder from registry if available ;Get installation folder from registry if available
InstallDirRegKey HKLM "Software\Progressia" "Install_Dir" InstallDirRegKey HKLM "Software\${PROJECT_NAME}" ""
;Request application privileges for Windows Vista ;Request application privileges for Windows Vista
RequestExecutionLevel admin RequestExecutionLevel admin
@ -33,14 +49,18 @@
;Pages ;Pages
!insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_WELCOME
;!insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt" !insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
!insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "Start ${PROJECT_NAME}"
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink"
!insertmacro MUI_PAGE_FINISH !insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME !insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH !insertmacro MUI_UNPAGE_FINISH
@ -52,12 +72,15 @@
;-------------------------------- ;--------------------------------
;Installer Sections ;Installer Sections
Section "Install Progressia" SecDummy Section "Install ${PROJECT_NAME}" SEC0000
SectionIn RO ;Make it read-only
SetOutPath "$INSTDIR" SetOutPath "$INSTDIR"
SetOverwrite on
;Files ;Files
File Progressia.jar File Progressia.jar
File logo.ico
File /r lib File /r lib
;Store installation folder ;Store installation folder
@ -65,22 +88,24 @@ Section "Install Progressia" SecDummy
;Create uninstaller ;Create uninstaller
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "DisplayName" "Progressia (remove only)" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "DisplayName" "${PROJECT_NAME} (remove only)"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "UninstallString" "$INSTDIR\Uninstall.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteUninstaller "$INSTDIR\Uninstall.exe" WriteUninstaller "$INSTDIR\Uninstall.exe"
SectionEnd SectionEnd
;-------------------------------- Section "Create Desktop Shortcut" SEC0001
;Descriptions SetOutPath "$APPDATA\${PROJECT_NAME}"
CreateShortCut "$DESKTOP\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
SectionEnd
;Language strings Section "Start Menu Shortcuts" SEC0002
LangString DESC_SecDummy ${LANG_ENGLISH} "A test section."
;Assign language strings to sections CreateDirectory "$SMPROGRAMS\${PROJECT_NAME}"
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
!insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy) CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
!insertmacro MUI_FUNCTION_DESCRIPTION_END
SectionEnd
;-------------------------------- ;--------------------------------
;Uninstaller Section ;Uninstaller Section
@ -92,9 +117,45 @@ Section "Uninstall"
Delete $INSTDIR\Uninstall.exe Delete $INSTDIR\Uninstall.exe
Delete $INSTDIR\Progressia.jar Delete $INSTDIR\Progressia.jar
Delete $INSTDIR\lib\*.* Delete $INSTDIR\lib\*.*
Delete $INSTDIR\logo.ico
RMDir /r "$INSTDIR" RMDir $INSTDIR\lib
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia"
DeleteRegKey HKLM "Software\Progressia" Delete $DESKTOP\${PROJECT_NAME}.lnk
Delete $SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk
Delete $SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk
RMDir $INSTDIR
RMDir /r $SMPROGRAMS\${PROJECT_NAME}
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}"
DeleteRegKey HKLM "Software\${PROJECT_NAME}"
SectionEnd SectionEnd
Section "un.Remove user data"
RMDir /r "$APPDATA\${PROJECT_NAME}"
SectionEnd
;--------------------------------
;Functions
Function LaunchLink
SetOutPath "$APPDATA\${PROJECT_NAME}"
ExecShell "" "$INSTDIR\${PROJECT_NAME}.jar"
FunctionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecDummy ${LANG_ENGLISH} "Install ${PROJECT_NAME}."
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC0000} $(DESC_SecDummy)
!insertmacro MUI_FUNCTION_DESCRIPTION_END

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

69
docs/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,69 @@
# Contributing Guidelines
This document lists conventions adopted by Progressia developers.
## git
### Branches
Progressia repository contains a `master` branch and several "feature" branches.
`master` is expected to contain a version of the game suitable for demonstration and forking/branching. Do not commit directly to `master` without OLEGSHA's approval.
- `master` must always correctly build without compiler warnings (see below).
- `master` must always pass all unit tests.
- `master` must always be able to launch successfully.
- `master` must always only contain working features.
- `master` should not contain excessive debug code.
- `master` must always have its code and filenames formatted (see below).
"Feature" branches are branches dedicated to the development of a single feature. When the feature reaches completion the branch is merged into `master` and removed. Intermediate merges into `master` may occur when some fitting milestone is reached. Intermediate merges from `master` may be done as necessary. Merges between "feature" branches should generally be avoided.
When beginning work on a new feature, create a new branch based on `master` (or on another "feature" branch if absolutely necessary). Use `all-small-with-dashes` to name the branch: `add-trees` or `rebalance-plastics` are good names. Do not fix unrelated bugs or work on unrelated features in a "feature" branch - create a new one, switch to an existing one or commit directly to `master` if the changes are small enough.
"Feature" branches may not be formatted properly. Formatting is required before merging into `master` or other branches.
### Commits
- Commits must leave the branch in a state that builds without compiler warnings (see below).
- Changes should be grouped in commits semantically. Avoid committing many small related changes in sequence; if necessary, wait and accumulate them. Avoid committing unrelated changes together; if necessary, split staged changes into several commits. This should normally result in about 1-2 commits for a day's work.
- Commit bulk changes (renaming, formatting, ...) separately. Don't ever commit whitespace changes outside formatting commits.
- Message format:
```
Short description of changes
<empty line>
- Enumeration of changes
- Nest when appropriate
- Use dashes only
- List not needed for small commits
```
Example:
```
Changed packages for relations, renamed Face to ShapePart
- Added BlockRelation as an abstract superclass to existing relations
- Must be given an absolute "up" direction before use
- Moved AbsFace, AbsRelation and BlockRelation to .world.rels
- Renamed Face to ShapePart to reduce confusion with AbsFace
```
- Only commit changes described in the commit message. Please double-check staged files before committing.
- Avoid merge conflicts. Pull before committing.
- Better sign commits than not. See: [git](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work), [GitHub](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification).
## Code
### Warnings
Make sure that all committed code contains no compiler warnings. This specifically includes unused imports, unused private members, missing `@Override`s and warnings related to generics.
Warnings about unknown tokens in `@SuppressWarnings` are temporarily ignored. Please disable them in your IDE.
### Code Style
Formatting code is important.
- The format is specified within the files inside `/templates_and_presets/eclipse_ide`. Import the specifications into Eclipse or IntelliJ IDEA and use the IDEs' format feature. Alternatively format the code manually in accordance with existing files.
- Only use tabs for indentation. Never indent with spaces even when wrapping lines. This is to ensure that indentation does not break when tab width is different.
- Don't use `I` prefix for interfaces (not `IDoable` but `Doable`).
- Prioritize readability over compactness. Do not hesitate to use (very) long identifiers if they aid comprehension.
- Document all mathematics unless it is trivial, especially when using math notation for variable names.
- Use proper English when writing comments. Avoid boxes in comments. Use `//` for single-line comments.

View File

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

View File

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

View File

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

View File

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

View File

@ -108,7 +108,7 @@ public class CharArrayIterator implements CharacterIterator {
return pos; return pos;
} }
// @SuppressWarnings("all") Just STFU, this _is_ terrific // @SuppressWarnings("all") Just STFU, this _is_ terrific
// SonarLint: "clone" should not be overridden (java:S2975) // SonarLint: "clone" should not be overridden (java:S2975)
// And I wouldn't have done that if only CharacterIterator had not required // And I wouldn't have done that if only CharacterIterator had not required

View File

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

View File

@ -86,7 +86,7 @@ public class FancyCharacterIterator implements CharacterIterator {
return sb.toString(); return sb.toString();
} }
// @SuppressWarnings("all") Just STFU, this _is_ terrific // @SuppressWarnings("all") Just STFU, this _is_ terrific
// SonarLint: "clone" should not be overridden (java:S2975) // SonarLint: "clone" should not be overridden (java:S2975)
// And I wouldn't have done that if only CharacterIterator had not required // And I wouldn't have done that if only CharacterIterator had not required

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@ import ru.windcorp.jputil.chars.Escaper;
*/ */
// SonarLint: Constants should not be defined in interfaces (java:S1214) // SonarLint: Constants should not be defined in interfaces (java:S1214)
// DONE is an essential part of the interface // DONE is an essential part of the interface
@SuppressWarnings("squid:S1214") @SuppressWarnings("squid:S1214")
public interface CharReader { public interface CharReader {
@ -179,8 +179,7 @@ public interface CharReader {
/** /**
* Skips to the end of the current line. Both <code>"\n"</code>, * Skips to the end of the current line. Both <code>"\n"</code>,
* <code>"\r"</code> * <code>"\r"</code> and <code>"\r\n"</code> are considered line separators.
* and <code>"\r\n"</code> are considered line separators.
* *
* @return the amount of characters in the skipped line * @return the amount of characters in the skipped line
*/ */

View File

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

View File

@ -44,9 +44,16 @@ public interface ThrowingBiConsumer<T, U, E extends Exception> {
} }
public static <T, U, E extends Exception> ThrowingBiConsumer<T, U, E> concat( 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> first,
ThrowingBiConsumer<? super T, ? super U, ? extends E> second 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) {
return (t, u) -> { return (t, u) -> {
first.accept(t, u); first.accept(t, u);
second.accept(t, u); second.accept(t, u);
@ -54,19 +61,7 @@ public interface ThrowingBiConsumer<T, U, E extends Exception> {
} }
public static <T, U, E extends Exception> ThrowingBiConsumer<T, U, E> concat( public static <T, U, E extends Exception> ThrowingBiConsumer<T, U, E> concat(
BiConsumer<? super T, ? super U> first, ThrowingBiConsumer<? super T, ? super U, E> first, BiConsumer<? super T, ? super U> second) {
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) -> { return (t, u) -> {
first.accept(t, u); first.accept(t, u);
second.accept(t, u); second.accept(t, u);

View File

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

View File

@ -28,10 +28,8 @@ public interface ThrowingFunction<T, R, E extends Exception> {
R apply(T t) throws E; R apply(T t) throws E;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default Function<T, R> withHandler( default Function<T, R> withHandler(BiConsumer<? super T, ? super E> handler,
BiConsumer<? super T, ? super E> handler, Function<? super T, ? extends R> value) {
Function<? super T, ? extends R> value
) {
return t -> { return t -> {
try { try {
return apply(t); return apply(t);
@ -58,23 +56,18 @@ public interface ThrowingFunction<T, R, E extends Exception> {
} }
public static <T, R, I, E extends Exception> ThrowingFunction<T, R, E> compose( public static <T, R, I, E extends Exception> ThrowingFunction<T, R, E> compose(
ThrowingFunction<? super T, I, ? extends E> first, ThrowingFunction<? super T, I, ? extends E> first,
ThrowingFunction<? super I, ? extends R, ? extends 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(Function<? super T, I> first,
ThrowingFunction<? super I, ? extends R, E> second) {
return t -> second.apply(first.apply(t)); return t -> second.apply(first.apply(t));
} }
public static <T, R, I, E extends Exception> ThrowingFunction<T, R, E> compose( public static <T, R, I, E extends Exception> ThrowingFunction<T, R, E> compose(
Function<? super T, I> first, ThrowingFunction<? super T, I, E> first, Function<? super I, ? extends R> second) {
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)); return t -> second.apply(first.apply(t));
} }

View File

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

View File

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

View File

@ -37,7 +37,7 @@ public class SelectorSystem<T> {
private final Collection<Selector<T>> selectors = Collections.synchronizedCollection(new ArrayList<Selector<T>>()); private final Collection<Selector<T>> selectors = Collections.synchronizedCollection(new ArrayList<Selector<T>>());
private final Collection<SelectorOperator> operators = Collections private final Collection<SelectorOperator> operators = Collections
.synchronizedCollection(new ArrayList<SelectorOperator>()); .synchronizedCollection(new ArrayList<SelectorOperator>());
private String stackPrefix = null; private String stackPrefix = null;

View File

@ -40,6 +40,7 @@ public class ProgressiaLauncher {
CrashReports.registerProvider(new OpenALContextProvider()); CrashReports.registerProvider(new OpenALContextProvider());
CrashReports.registerProvider(new ArgsContextProvider()); CrashReports.registerProvider(new ArgsContextProvider());
CrashReports.registerProvider(new LanguageContextProvider()); CrashReports.registerProvider(new LanguageContextProvider());
CrashReports.registerProvider(new ScreenContextProvider());
// Analyzers // Analyzers
CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer()); CrashReports.registerAnalyzer(new OutOfMemoryAnalyzer());

View File

@ -24,7 +24,7 @@ import ru.windcorp.progressia.client.graphics.world.Camera;
import ru.windcorp.progressia.client.graphics.world.EntityAnchor; import ru.windcorp.progressia.client.graphics.world.EntityAnchor;
import ru.windcorp.progressia.client.graphics.world.LocalPlayer; import ru.windcorp.progressia.client.graphics.world.LocalPlayer;
import ru.windcorp.progressia.client.world.WorldRender; import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
public class Client { public class Client {
@ -36,7 +36,7 @@ public class Client {
private final ServerCommsChannel comms; private final ServerCommsChannel comms;
public Client(WorldData world, ServerCommsChannel comms) { public Client(DefaultWorldData world, ServerCommsChannel comms) {
this.world = new WorldRender(world, this); this.world = new WorldRender(world, this);
this.comms = comms; this.comms = comms;
@ -69,11 +69,7 @@ public class Client {
return; return;
} }
getCamera().setAnchor( getCamera().setAnchor(new EntityAnchor(getWorld().getEntityRenderable(entity)));
new EntityAnchor(
getWorld().getEntityRenderable(entity)
)
);
} }
} }

View File

@ -32,6 +32,7 @@ import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.TestContent; import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestMusicPlayer;
public class ClientProxy implements Proxy { public class ClientProxy implements Proxy {
@ -41,10 +42,8 @@ public class ClientProxy implements Proxy {
try { try {
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init); RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init); RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
RenderTaskQueue.waitAndInvoke( RenderTaskQueue.waitAndInvoke(() -> Typefaces
() -> Typefaces .setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz"))));
.setDefault(GNUUnifontLoader.load(ResourceManager.getResource("assets/unifont-13.0.03.hex.gz")))
);
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw CrashReports.report(e, "ClientProxy failed"); throw CrashReports.report(e, "ClientProxy failed");
} }
@ -59,6 +58,8 @@ public class ClientProxy implements Proxy {
ServerState.startServer(); ServerState.startServer();
ClientState.connectToLocalServer(); ClientState.connectToLocalServer();
TestMusicPlayer.start();
} }
} }

View File

@ -21,7 +21,7 @@ package ru.windcorp.progressia.client;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel; import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI; import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.world.LayerWorld; import ru.windcorp.progressia.client.graphics.world.LayerWorld;
import ru.windcorp.progressia.common.world.WorldData; import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.server.ServerState; import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerAbout; import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerTestUI; import ru.windcorp.progressia.test.LayerTestUI;
@ -41,11 +41,9 @@ public class ClientState {
public static void connectToLocalServer() { public static void connectToLocalServer() {
WorldData world = new WorldData(); DefaultWorldData world = new DefaultWorldData();
LocalServerCommsChannel channel = new LocalServerCommsChannel( LocalServerCommsChannel channel = new LocalServerCommsChannel(ServerState.getInstance());
ServerState.getInstance()
);
Client client = new Client(world, channel); Client client = new Client(world, channel);

View File

@ -23,6 +23,7 @@ import ru.windcorp.progressia.client.audio.backend.AudioReader;
import ru.windcorp.progressia.client.audio.backend.Listener; import ru.windcorp.progressia.client.audio.backend.Listener;
import ru.windcorp.progressia.client.audio.backend.SoundType; import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.client.audio.backend.Speaker; import ru.windcorp.progressia.client.audio.backend.Speaker;
import ru.windcorp.progressia.common.resource.Resource;
import static org.lwjgl.openal.AL11.*; import static org.lwjgl.openal.AL11.*;
import static org.lwjgl.openal.ALC10.*; import static org.lwjgl.openal.ALC10.*;
@ -40,13 +41,9 @@ public class AudioManager {
private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM); private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM);
private static Speaker musicSpeaker; private static Speaker musicSpeaker;
private static ArrayList<SoundType> soundsBuffer = new ArrayList<>();
public static void initAL() { public static void initAL() {
String defaultDeviceName = alcGetString( String defaultDeviceName = alcGetString(0, ALC_DEFAULT_DEVICE_SPECIFIER);
0,
ALC_DEFAULT_DEVICE_SPECIFIER
);
device = alcOpenDevice(defaultDeviceName); device = alcOpenDevice(defaultDeviceName);
@ -75,38 +72,23 @@ public class AudioManager {
lastSoundIndex = 0; lastSoundIndex = 0;
} }
speaker = soundSpeakers.get(lastSoundIndex); speaker = soundSpeakers.get(lastSoundIndex);
} while ( } while (speaker.getState().equals(Speaker.State.PLAYING_LOOP));
speaker.getState()
.equals(Speaker.State.PLAYING_LOOP)
);
return speaker; return speaker;
} }
private static SoundType findSoundType(String soundID) throws Exception { public static Speaker initSpeaker(SoundType st) {
for (SoundType s : soundsBuffer) {
if (s.getId().equals(soundID)) {
return s;
}
}
throw new Exception(
"ERROR: The selected sound is not loaded or" +
" not exists"
);
}
public static Speaker initSpeaker(String soundID) {
Speaker speaker = getLastSpeaker(); Speaker speaker = getLastSpeaker();
try { try {
findSoundType(soundID).initSpeaker(speaker); st.initSpeaker(speaker);
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException(); throw new RuntimeException();
} }
return speaker; return speaker;
} }
public static Speaker initMusicSpeaker(String soundID) { public static Speaker initMusicSpeaker(SoundType st) {
try { try {
findSoundType(soundID).initSpeaker(musicSpeaker); st.initSpeaker(musicSpeaker);
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException(); throw new RuntimeException();
} }
@ -120,11 +102,11 @@ public class AudioManager {
} }
} }
public static void loadSound(String path, String id, AudioFormat format) { public static void loadSound(Resource resource, String id, AudioFormat format) {
if (format == AudioFormat.MONO) { if (format == AudioFormat.MONO) {
soundsBuffer.add(AudioReader.readAsMono(path, id)); AudioRegistry.getInstance().register(AudioReader.readAsMono(resource, id));
} else { } else {
soundsBuffer.add(AudioReader.readAsStereo(path, id)); AudioRegistry.getInstance().register(AudioReader.readAsStereo(resource, id));
} }
} }

View File

@ -0,0 +1,34 @@
/*
* 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.audio;
import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.common.util.namespaces.NamespacedInstanceRegistry;
public class AudioRegistry extends NamespacedInstanceRegistry<SoundType> {
private static final AudioRegistry INSTANCE = new AudioRegistry();
/**
* @return the instance
*/
public static AudioRegistry getInstance() {
return INSTANCE;
}
}

View File

@ -18,6 +18,8 @@
package ru.windcorp.progressia.client.audio; package ru.windcorp.progressia.client.audio;
import ru.windcorp.progressia.common.resource.ResourceManager;
public class AudioSystem { public class AudioSystem {
static public void initialize() { static public void initialize() {
AudioManager.initAL(); AudioManager.initAL();
@ -27,10 +29,7 @@ public class AudioSystem {
} }
static void loadAudioData() { static void loadAudioData() {
AudioManager.loadSound( AudioManager.loadSound(ResourceManager.getResource("assets/sounds/block_destroy_clap.ogg"),
"assets/sounds/block_destroy_clap.ogg", "Progressia:BlockDestroy", AudioFormat.MONO);
"Progressia:BlockDestroy",
AudioFormat.MONO
);
} }
} }

View File

@ -19,72 +19,34 @@
package ru.windcorp.progressia.client.audio; package ru.windcorp.progressia.client.audio;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.client.audio.backend.Speaker; import ru.windcorp.progressia.client.audio.backend.Speaker;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
public class Music extends Namespaced { public class Music extends Sound {
private Vec3 position = new Vec3();
private Vec3 velocity = new Vec3(); public Music(SoundType soundType, int timeLength, float pitch, float gain) {
private float pitch = 1.0f; super(soundType, timeLength, new Vec3(), new Vec3(), pitch, gain);
private float gain = 1.0f; }
public Music(SoundType soundType) {
super(soundType);
}
public Music(String id, int timeLength, float pitch, float gain) {
super(id, timeLength, new Vec3(), new Vec3(), pitch, gain);
}
public Music(String id) { public Music(String id) {
super(id); super(id);
} }
public Music( @Override
String id, protected Speaker initSpeaker() {
Vec3 position, return AudioManager.initMusicSpeaker(soundType);
Vec3 velocity,
float pitch,
float gain
) {
this(id);
this.position = position;
this.velocity = velocity;
this.pitch = pitch;
this.gain = gain;
} }
public void play(boolean loop) { @Override
Speaker speaker = AudioManager.initMusicSpeaker(this.getId()); public void setPosition(Vec3 position) {
speaker.setGain(gain); throw new UnsupportedOperationException();
speaker.setPitch(pitch);
speaker.setPosition(position);
speaker.setVelocity(velocity);
if (loop) {
speaker.playLoop();
} else {
speaker.play();
}
}
public void setGain(float gain) {
this.gain = gain;
}
public void setPitch(float pitch) {
this.pitch = pitch;
}
public void setVelocity(Vec3 velocity) {
this.velocity = velocity;
}
public Vec3 getPosition() {
return position;
}
public float getGain() {
return gain;
}
public Vec3 getVelocity() {
return velocity;
}
public float getPitch() {
return pitch;
} }
} }

View File

@ -19,28 +19,28 @@
package ru.windcorp.progressia.client.audio; package ru.windcorp.progressia.client.audio;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.audio.backend.SoundType;
import ru.windcorp.progressia.client.audio.backend.Speaker; import ru.windcorp.progressia.client.audio.backend.Speaker;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
public class SoundEffect public class Sound {
extends Namespaced {
private Vec3 position = new Vec3(); protected Vec3 position = new Vec3(0f, 0f, 0f);
private Vec3 velocity = new Vec3(); protected Vec3 velocity = new Vec3(0f, 0f, 0f);
private float pitch = 1.0f; protected float pitch = 1.0f;
private float gain = 1.0f; protected float gain = 1.0f;
protected int timeLength = 0;
public SoundEffect(String id) { protected SoundType soundType;
super(id);
public Sound(SoundType soundType) {
this.soundType = soundType;
} }
public SoundEffect( public Sound(String id) {
String id, this(AudioRegistry.getInstance().get(id));
Vec3 position, }
Vec3 velocity,
float pitch, public Sound(String id, int timeLength, Vec3 position, Vec3 velocity, float pitch, float gain) {
float gain
) {
this(id); this(id);
this.position = position; this.position = position;
this.velocity = velocity; this.velocity = velocity;
@ -48,8 +48,20 @@ public class SoundEffect
this.gain = gain; this.gain = gain;
} }
public Sound(SoundType soundType, int timeLength, Vec3 position, Vec3 velocity, float pitch, float gain) {
this(soundType);
this.position = position;
this.velocity = velocity;
this.pitch = pitch;
this.gain = gain;
}
protected Speaker initSpeaker() {
return AudioManager.initSpeaker(soundType);
}
public void play(boolean loop) { public void play(boolean loop) {
Speaker speaker = AudioManager.initSpeaker(this.getId()); Speaker speaker = initSpeaker();
speaker.setGain(gain); speaker.setGain(gain);
speaker.setPitch(pitch); speaker.setPitch(pitch);
speaker.setPosition(position); speaker.setPosition(position);
@ -93,4 +105,9 @@ public class SoundEffect
public float getPitch() { public float getPitch() {
return pitch; return pitch;
} }
public double getDuration() {
return soundType.getDuration();
}
} }

View File

@ -33,39 +33,24 @@ public class AudioReader {
} }
// TODO fix converting from mono-stereo // TODO fix converting from mono-stereo
private static SoundType readAsSpecified(String path, String id, int format) { private static SoundType readAsSpecified(Resource resource, String id, int format) {
IntBuffer channelBuffer = BufferUtils.createIntBuffer(1); IntBuffer channelBuffer = BufferUtils.createIntBuffer(1);
IntBuffer rateBuffer = BufferUtils.createIntBuffer(1); IntBuffer rateBuffer = BufferUtils.createIntBuffer(1);
Resource res = ResourceManager.getResource(path); ShortBuffer rawAudio = decodeVorbis(resource, channelBuffer, rateBuffer);
ShortBuffer rawAudio = decodeVorbis(res, channelBuffer, rateBuffer); return new SoundType(id, rawAudio, format, rateBuffer.get(0));
return new SoundType(
id,
rawAudio,
format,
rateBuffer.get(0)
);
} }
public static SoundType readAsMono(String path, String id) { public static SoundType readAsMono(Resource resource, String id) {
return readAsSpecified(path, id, AL_FORMAT_MONO16); return readAsSpecified(resource, id, AL_FORMAT_MONO16);
} }
public static SoundType readAsStereo(String path, String id) { public static SoundType readAsStereo(Resource resource, String id) {
return readAsSpecified(path, id, AL_FORMAT_STEREO16); return readAsSpecified(resource, id, AL_FORMAT_STEREO16);
} }
private static ShortBuffer decodeVorbis( private static ShortBuffer decodeVorbis(Resource dataToDecode, IntBuffer channelsBuffer, IntBuffer rateBuffer) {
Resource dataToDecode, return stb_vorbis_decode_memory(dataToDecode.readAsBytes(), channelsBuffer, rateBuffer);
IntBuffer channelsBuffer,
IntBuffer rateBuffer
) {
return stb_vorbis_decode_memory(
dataToDecode.readAsBytes(),
channelsBuffer,
rateBuffer
);
} }
} }

View File

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

View File

@ -21,6 +21,9 @@ package ru.windcorp.progressia.client.audio.backend;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
import org.lwjgl.openal.AL10;
import static org.lwjgl.openal.AL11.*; import static org.lwjgl.openal.AL11.*;
public class SoundType extends Namespaced { public class SoundType extends Namespaced {
@ -29,13 +32,9 @@ public class SoundType extends Namespaced {
private int sampleRate; private int sampleRate;
private int format; private int format;
private int audioBuffer; private int audioBuffer;
private double duration;
public SoundType( public SoundType(String id, ShortBuffer rawAudio, int format, int sampleRate) {
String id,
ShortBuffer rawAudio,
int format,
int sampleRate
) {
super(id); super(id);
this.rawAudio = rawAudio; this.rawAudio = rawAudio;
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
@ -46,9 +45,14 @@ public class SoundType extends Namespaced {
private void createAudioBuffer() { private void createAudioBuffer() {
this.audioBuffer = alGenBuffers(); this.audioBuffer = alGenBuffers();
alBufferData(audioBuffer, format, rawAudio, sampleRate); alBufferData(audioBuffer, format, rawAudio, sampleRate);
duration = rawAudio.limit() / (double) sampleRate / (format == AL10.AL_FORMAT_STEREO16 ? 2 : 1);
} }
public void initSpeaker(Speaker speaker) { public void initSpeaker(Speaker speaker) {
speaker.setAudioData(audioBuffer); speaker.setAudioData(audioBuffer);
} }
public double getDuration() {
return duration;
}
} }

View File

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

View File

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

View File

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

View File

@ -0,0 +1,49 @@
/*
* 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.comms.controls;
import java.util.function.Consumer;
import java.util.function.Predicate;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
import ru.windcorp.progressia.common.comms.controls.PacketControl;
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) {
super(id);
this.predicate = predicate;
this.action = action;
}
@Override
public PacketControl onInputEvent(InputEvent event) {
if (!predicate.test(event))
return null;
action.accept(event);
return null;
}
}

View File

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

View File

@ -32,8 +32,7 @@ public class InputBasedControls {
this.client = client; this.client = client;
this.controls = ControlTriggerRegistry.getInstance().values().stream() this.controls = ControlTriggerRegistry.getInstance().values().stream()
.filter(ControlTriggerInputBased.class::isInstance) .filter(ControlTriggerInputBased.class::isInstance).toArray(ControlTriggerInputBased[]::new);
.toArray(ControlTriggerInputBased[]::new);
} }
public void handleInput(Input input) { public void handleInput(Input input) {

View File

@ -34,11 +34,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
public void connect(String login) { public void connect(String login) {
setState(State.CONNECTED); setState(State.CONNECTED);
this.localClient = new LocalClient( this.localClient = new LocalClient(server.getClientManager().grabClientId(), login, this);
server.getClientManager().grabClientId(),
login,
this
);
server.getClientManager().addClient(localClient); server.getClientManager().addClient(localClient);
} }

View File

@ -34,7 +34,13 @@ public class Colors {
DEBUG_BLUE = toVector(0xFF0000FF), DEBUG_BLUE = toVector(0xFF0000FF),
DEBUG_CYAN = toVector(0xFF00FFFF), DEBUG_CYAN = toVector(0xFF00FFFF),
DEBUG_MAGENTA = toVector(0xFFFF00FF), DEBUG_MAGENTA = toVector(0xFFFF00FF),
DEBUG_YELLOW = toVector(0xFFFFFF00); DEBUG_YELLOW = toVector(0xFFFFFF00),
LIGHT_GRAY = toVector(0xFFCBCBD0),
BLUE = toVector(0xFF37A2E6),
HOVER_BLUE = toVector(0xFFC3E4F7),
DISABLED_GRAY = toVector(0xFFE5E5E5),
DISABLED_BLUE = toVector(0xFFB2D8ED);
public static Vec4 toVector(int argb) { public static Vec4 toVector(int argb) {
return toVector(argb, new Vec4()); return toVector(argb, new Vec4());

View File

@ -24,6 +24,7 @@ import java.util.List;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.input.CursorEvent; import ru.windcorp.progressia.client.graphics.input.CursorEvent;
import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent; import ru.windcorp.progressia.client.graphics.input.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent; import ru.windcorp.progressia.client.graphics.input.InputEvent;
@ -42,7 +43,7 @@ public class GUI {
} }
private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections private static final List<LayerStackModification> MODIFICATION_QUEUE = Collections
.synchronizedList(new ArrayList<>()); .synchronizedList(new ArrayList<>());
private static class ModifiableInput extends Input { private static class ModifiableInput extends Input {
@Override @Override
@ -57,15 +58,24 @@ public class GUI {
} }
public static void addBottomLayer(Layer layer) { public static void addBottomLayer(Layer layer) {
modify(layers -> layers.add(layer)); modify(layers -> {
layers.add(layer);
layer.onAdded();
});
} }
public static void addTopLayer(Layer layer) { public static void addTopLayer(Layer layer) {
modify(layers -> layers.add(0, layer)); modify(layers -> {
layers.add(0, layer);
layer.onAdded();
});
} }
public static void removeLayer(Layer layer) { public static void removeLayer(Layer layer) {
modify(layers -> layers.remove(layer)); modify(layers -> {
layers.remove(layer);
layer.onRemoved();
});
} }
private static void modify(LayerStackModification mod) { private static void modify(LayerStackModification mod) {
@ -78,12 +88,33 @@ public class GUI {
public static void render() { public static void render() {
synchronized (LAYERS) { synchronized (LAYERS) {
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
MODIFICATION_QUEUE.clear(); if (!MODIFICATION_QUEUE.isEmpty()) {
MODIFICATION_QUEUE.forEach(action -> action.affect(LAYERS));
MODIFICATION_QUEUE.clear();
boolean isMouseCurrentlyCaptured = GraphicsInterface.isMouseCaptured();
Layer.CursorPolicy policy = Layer.CursorPolicy.REQUIRE;
for (Layer layer : LAYERS) {
Layer.CursorPolicy currentPolicy = layer.getCursorPolicy();
if (currentPolicy != Layer.CursorPolicy.INDIFFERENT) {
policy = currentPolicy;
break;
}
}
boolean shouldCaptureMouse = (policy == Layer.CursorPolicy.FORBID);
if (shouldCaptureMouse != isMouseCurrentlyCaptured) {
GraphicsInterface.setMouseCaptured(shouldCaptureMouse);
}
}
for (int i = LAYERS.size() - 1; i >= 0; --i) { for (int i = LAYERS.size() - 1; i >= 0; --i) {
LAYERS.get(i).render(); LAYERS.get(i).render();
} }
} }
} }

View File

@ -31,15 +31,52 @@ public abstract class Layer {
private final AtomicBoolean isValid = new AtomicBoolean(false); private final AtomicBoolean isValid = new AtomicBoolean(false);
/**
* Represents various requests that a {@link Layer} can make regarding the
* presence of a visible cursor. The value of the highest layer that is not
* {@link #INDIFFERENT} is used.
*/
public static enum CursorPolicy {
/**
* Require that a cursor is visible.
*/
REQUIRE,
/**
* The {@link Layer} should not affect the presence or absence of a
* visible cursor; lower layers should be consulted.
*/
INDIFFERENT,
/**
* Forbid a visible cursor.
*/
FORBID
}
private CursorPolicy cursorPolicy = CursorPolicy.INDIFFERENT;
public Layer(String name) { public Layer(String name) {
this.name = name; this.name = name;
} }
public String getName() {
return name;
}
@Override @Override
public String toString() { public String toString() {
return "Layer " + name; return "Layer " + name;
} }
public CursorPolicy getCursorPolicy() {
return cursorPolicy;
}
public void setCursorPolicy(CursorPolicy cursorPolicy) {
this.cursorPolicy = cursorPolicy;
}
void render() { void render() {
GraphicsInterface.startNextLayer(); GraphicsInterface.startNextLayer();
@ -79,4 +116,12 @@ public abstract class Layer {
return GraphicsInterface.getFrameHeight(); return GraphicsInterface.getFrameHeight();
} }
protected void onAdded() {
// Do nothing
}
protected void onRemoved() {
// Do nothing
}
} }

View File

@ -18,11 +18,11 @@
package ru.windcorp.progressia.client.graphics.backend; package ru.windcorp.progressia.client.graphics.backend;
import static org.lwjgl.opengl.GL11.*;
import glm.vec._2.i.Vec2i; import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFWVidMode;
import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
public class GraphicsBackend { public class GraphicsBackend {
@ -38,9 +38,30 @@ public class GraphicsBackend {
private static boolean faceCullingEnabled = false; private static boolean faceCullingEnabled = false;
private static boolean isFullscreen = false;
private static boolean vSyncEnabled = false;
private static boolean isGLFWInitialized = false;
private static boolean isOpenGLInitialized = false;
private GraphicsBackend() { private GraphicsBackend() {
} }
public static boolean isGLFWInitialized() {
return isGLFWInitialized;
}
static void setGLFWInitialized(boolean isGLFWInitialized) {
GraphicsBackend.isGLFWInitialized = isGLFWInitialized;
}
public static boolean isOpenGLInitialized() {
return isOpenGLInitialized;
}
static void setOpenGLInitialized(boolean isOpenGLInitialized) {
GraphicsBackend.isOpenGLInitialized = isOpenGLInitialized;
}
public static void initialize() { public static void initialize() {
startRenderThread(); startRenderThread();
} }
@ -128,4 +149,48 @@ public class GraphicsBackend {
faceCullingEnabled = useFaceCulling; faceCullingEnabled = useFaceCulling;
} }
public static boolean isFullscreen() {
return isFullscreen;
}
public static boolean isVSyncEnabled() {
return vSyncEnabled;
}
public static void setFullscreen() {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
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);
isFullscreen = false;
}
public static void setVSyncEnabled(boolean enable) {
glfwSwapInterval(enable ? 1 : 0);
vSyncEnabled = enable;
}
public static int getRefreshRate() {
GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
return vidmode.refreshRate();
}
public static boolean isMouseCaptured() {
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
}
public static void setMouseCaptured(boolean capture) {
int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);
if (!capture) {
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
}
}
} }

View File

@ -73,4 +73,21 @@ public class GraphicsInterface {
GraphicsBackend.startNextLayer(); GraphicsBackend.startNextLayer();
} }
public static void makeFullscreen(boolean state) {
if (state) {
GraphicsBackend.setFullscreen();
} else {
GraphicsBackend.setWindowed();
}
GraphicsBackend.setVSyncEnabled(GraphicsBackend.isVSyncEnabled());
}
public static boolean isMouseCaptured() {
return GraphicsBackend.isMouseCaptured();
}
public static void setMouseCaptured(boolean capture) {
GraphicsBackend.setMouseCaptured(capture);
}
} }

View File

@ -49,13 +49,7 @@ public class InputHandler {
private static final ModifiableKeyEvent THE_KEY_EVENT = new ModifiableKeyEvent(); private static final ModifiableKeyEvent THE_KEY_EVENT = new ModifiableKeyEvent();
static void handleKeyInput( static void handleKeyInput(long window, int key, int scancode, int action, int mods) {
long window,
int key,
int scancode,
int action,
int mods
) {
if (GraphicsBackend.getWindowHandle() != window) if (GraphicsBackend.getWindowHandle() != window)
return; return;
THE_KEY_EVENT.initialize(key, scancode, action, mods); THE_KEY_EVENT.initialize(key, scancode, action, mods);
@ -71,12 +65,7 @@ public class InputHandler {
} }
} }
static void handleMouseButtonInput( static void handleMouseButtonInput(long window, int key, int action, int mods) {
long window,
int key,
int action,
int mods
) {
handleKeyInput(window, key, Integer.MAX_VALUE - key, action, mods); handleKeyInput(window, key, Integer.MAX_VALUE - key, action, mods);
} }
@ -97,11 +86,7 @@ public class InputHandler {
private static final ModifiableCursorMoveEvent THE_CURSOR_MOVE_EVENT = new ModifiableCursorMoveEvent(); private static final ModifiableCursorMoveEvent THE_CURSOR_MOVE_EVENT = new ModifiableCursorMoveEvent();
static void handleMouseMoveInput( static void handleMouseMoveInput(long window, double x, double y) {
long window,
double x,
double y
) {
if (GraphicsBackend.getWindowHandle() != window) if (GraphicsBackend.getWindowHandle() != window)
return; return;
y = GraphicsInterface.getFrameHeight() - y; // Flip y axis y = GraphicsInterface.getFrameHeight() - y; // Flip y axis
@ -131,11 +116,7 @@ public class InputHandler {
private static final ModifiableWheelScrollEvent THE_WHEEL_SCROLL_EVENT = new ModifiableWheelScrollEvent(); private static final ModifiableWheelScrollEvent THE_WHEEL_SCROLL_EVENT = new ModifiableWheelScrollEvent();
static void handleWheelScroll( static void handleWheelScroll(long window, double xoffset, double yoffset) {
long window,
double xoffset,
double yoffset
) {
if (GraphicsBackend.getWindowHandle() != window) if (GraphicsBackend.getWindowHandle() != window)
return; return;
THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset); THE_WHEEL_SCROLL_EVENT.initialize(xoffset, yoffset);
@ -162,10 +143,7 @@ public class InputHandler {
/* /*
* NB: this is NOT a GLFW callback, the raw callback is in GraphicsBackend * NB: this is NOT a GLFW callback, the raw callback is in GraphicsBackend
*/ */
static void handleFrameResize( static void handleFrameResize(int width, int height) {
int width,
int height
) {
THE_FRAME_RESIZE_EVENT.initialize(width, height); THE_FRAME_RESIZE_EVENT.initialize(width, height);
dispatch(THE_FRAME_RESIZE_EVENT); dispatch(THE_FRAME_RESIZE_EVENT);
} }

View File

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

View File

@ -50,6 +50,7 @@ class LWJGLInitializer {
private static void initializeGLFW() { private static void initializeGLFW() {
// TODO Do GLFW error handling: check glfwInit, setup error callback // TODO Do GLFW error handling: check glfwInit, setup error callback
glfwInit(); glfwInit();
GraphicsBackend.setGLFWInitialized(true);
} }
private static void createWindow() { private static void createWindow() {
@ -64,10 +65,8 @@ class LWJGLInitializer {
GraphicsBackend.setWindowHandle(handle); GraphicsBackend.setWindowHandle(handle);
glfwSetInputMode(handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwMakeContextCurrent(handle); glfwMakeContextCurrent(handle);
glfwSwapInterval(0); glfwSwapInterval(0); // TODO: remove after config system is added
} }
private static void positionWindow() { private static void positionWindow() {
@ -87,21 +86,16 @@ class LWJGLInitializer {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
RenderTaskQueue.schedule(OpenGLObjectTracker::deleteEnqueuedObjects); RenderTaskQueue.schedule(OpenGLObjectTracker::deleteEnqueuedObjects);
GraphicsBackend.setOpenGLInitialized(true);
} }
private static void setupWindowCallbacks() { private static void setupWindowCallbacks() {
long handle = GraphicsBackend.getWindowHandle(); long handle = GraphicsBackend.getWindowHandle();
glfwSetFramebufferSizeCallback( glfwSetFramebufferSizeCallback(handle, GraphicsBackend::onFrameResized);
handle,
GraphicsBackend::onFrameResized
);
glfwSetKeyCallback(handle, InputHandler::handleKeyInput); glfwSetKeyCallback(handle, InputHandler::handleKeyInput);
glfwSetMouseButtonCallback( glfwSetMouseButtonCallback(handle, InputHandler::handleMouseButtonInput);
handle,
InputHandler::handleMouseButtonInput
);
glfwSetCursorPosCallback(handle, InputHandler::handleMouseMoveInput); glfwSetCursorPosCallback(handle, InputHandler::handleMouseMoveInput);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,8 +29,8 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4; import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors; import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage; import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Face; import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Faces; import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shape; import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
@ -84,7 +84,7 @@ public class RenderTarget {
private final Deque<TransformedMask> maskStack = new LinkedList<>(); private final Deque<TransformedMask> maskStack = new LinkedList<>();
private final Deque<Mat4> transformStack = new LinkedList<>(); private final Deque<Mat4> transformStack = new LinkedList<>();
private final List<Face> currentClipFaces = new ArrayList<>(); private final List<ShapePart> currentClipFaces = new ArrayList<>();
private int depth = 0; private int depth = 0;
@ -94,8 +94,8 @@ public class RenderTarget {
protected void assembleCurrentClipFromFaces() { protected void assembleCurrentClipFromFaces() {
if (!currentClipFaces.isEmpty()) { if (!currentClipFaces.isEmpty()) {
Face[] faces = currentClipFaces.toArray( ShapePart[] faces = currentClipFaces.toArray(
new Face[currentClipFaces.size()] new ShapePart[currentClipFaces.size()]
); );
currentClipFaces.clear(); currentClipFaces.clear();
@ -189,16 +189,13 @@ public class RenderTarget {
public void addCustomRenderer(Renderable renderable) { public void addCustomRenderer(Renderable renderable) {
assembleCurrentClipFromFaces(); assembleCurrentClipFromFaces();
assembled.add(
new Clip( float depth = this.depth--;
maskStack, Mat4 transform = new Mat4().translate(0, 0, depth).mul(getTransform());
getTransform(), assembled.add(new Clip(maskStack, transform, renderable));
renderable
)
);
} }
protected void addFaceToCurrentClip(Face face) { protected void addFaceToCurrentClip(ShapePart face) {
currentClipFaces.add(face); currentClipFaces.add(face);
} }
@ -270,7 +267,7 @@ public class RenderTarget {
fill(Colors.toVector(color)); fill(Colors.toVector(color));
} }
public Face createRectagleFace( public ShapePart createRectagleFace(
int x, int x,
int y, int y,
int width, int width,
@ -280,7 +277,7 @@ public class RenderTarget {
) { ) {
float depth = this.depth--; float depth = this.depth--;
return Faces.createRectangle( return ShapeParts.createRectangle(
FlatRenderProgram.getDefault(), FlatRenderProgram.getDefault(),
texture, texture,
color, color,
@ -291,7 +288,7 @@ public class RenderTarget {
); );
} }
public Face createRectagleFace( public ShapePart createRectagleFace(
int x, int x,
int y, int y,
int width, int width,

View File

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

View File

@ -68,17 +68,11 @@ public class Font {
return color; return color;
} }
public Renderable assemble( public Renderable assemble(CharSequence chars, float maxWidth) {
CharSequence chars,
float maxWidth
) {
return typeface.assembleStatic(chars, style, align, maxWidth, color); return typeface.assembleStatic(chars, style, align, maxWidth, color);
} }
public Renderable assembleDynamic( public Renderable assembleDynamic(Supplier<CharSequence> supplier, float maxWidth) {
Supplier<CharSequence> supplier,
float maxWidth
) {
return typeface.assembleDynamic(supplier, style, align, maxWidth, color); return typeface.assembleDynamic(supplier, style, align, maxWidth, color);
} }
@ -102,7 +96,8 @@ public class Font {
* Creates a new {@link Font} with the specified {@code style} exactly. This * Creates a new {@link Font} with the specified {@code style} exactly. This
* object's style is ignored. * object's style is ignored.
* *
* @param style the new style * @param style
* the new style
* @return the new font * @return the new font
*/ */
public Font withStyle(int style) { public Font withStyle(int style) {

View File

@ -73,7 +73,7 @@ public class GNUUnifontLoader {
public static GNUUnifont load(Resource resource) { public static GNUUnifont load(Resource resource) {
try (BufferedReader reader = createReader(resource)) { try (BufferedReader reader = createReader(resource)) {
return createStream(reader).map(GNUUnifontLoader::parse).map(GNUUnifontLoader::addToAtlas) return createStream(reader).map(GNUUnifontLoader::parse).map(GNUUnifontLoader::addToAtlas)
.collect(Collectors.collectingAndThen(createMapper(), GNUUnifont::new)); .collect(Collectors.collectingAndThen(createMapper(), GNUUnifont::new));
} catch (IOException | UncheckedIOException e) { } catch (IOException | UncheckedIOException e) {
throw CrashReports.report(e, "Could not load GNUUnifont"); throw CrashReports.report(e, "Could not load GNUUnifont");
} }
@ -81,8 +81,7 @@ public class GNUUnifontLoader {
private static BufferedReader createReader(Resource resource) throws IOException { private static BufferedReader createReader(Resource resource) throws IOException {
return new BufferedReader( 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) { private static Stream<String> createStream(BufferedReader reader) {
@ -97,13 +96,8 @@ public class GNUUnifontLoader {
char c = getChar(declar); char c = getChar(declar);
TextureDataEditor editor = new TextureDataEditor( TextureDataEditor editor = new TextureDataEditor(width, GNUUnifont.HEIGHT, width, GNUUnifont.HEIGHT,
width, TEXTURE_SETTINGS);
GNUUnifont.HEIGHT,
width,
GNUUnifont.HEIGHT,
TEXTURE_SETTINGS
);
for (int y = 0; y < GNUUnifont.HEIGHT; ++y) { for (int y = 0; y < GNUUnifont.HEIGHT; ++y) {
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
@ -165,8 +159,7 @@ public class GNUUnifontLoader {
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))) { if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))) {
throw new IOException( 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");
);
} }
} }
} }
@ -190,18 +183,16 @@ public class GNUUnifontLoader {
} }
private static Collector<AtlasGlyph, ?, TCharObjectMap<Texture>> createMapper() { private static Collector<AtlasGlyph, ?, TCharObjectMap<Texture>> createMapper() {
return Collector.of( return Collector.of(TCharObjectHashMap<Texture>::new,
TCharObjectHashMap<Texture>::new,
(map, glyph) -> map.put(glyph.c, glyph.texture), (map, glyph) -> map.put(glyph.c, glyph.texture),
(a, b) -> { (a, b) -> {
a.putAll(b); a.putAll(b);
return a; return a;
}, },
Characteristics.UNORDERED Characteristics.UNORDERED);
);
} }
private GNUUnifontLoader() { private GNUUnifontLoader() {

View File

@ -33,8 +33,8 @@ import gnu.trove.stack.TIntStack;
import gnu.trove.stack.array.TIntArrayStack; import gnu.trove.stack.array.TIntArrayStack;
import ru.windcorp.progressia.client.graphics.Colors; import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage; import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Face; import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Faces; import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shape; import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram; import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@ -144,7 +144,7 @@ public abstract class SpriteTypeface extends Typeface {
return new Shape( return new Shape(
Usage.STATIC, Usage.STATIC,
getProgram(), getProgram(),
Faces.createRectangle( ShapeParts.createRectangle(
getProgram(), getProgram(),
getTexture(c), getTexture(c),
Colors.WHITE, Colors.WHITE,
@ -167,7 +167,7 @@ public abstract class SpriteTypeface extends Typeface {
private final Renderable unitLine = new Shape( private final Renderable unitLine = new Shape(
Usage.STATIC, Usage.STATIC,
getProgram(), getProgram(),
Faces.createRectangle( ShapeParts.createRectangle(
getProgram(), getProgram(),
null, null,
Vectors.UNIT_4, Vectors.UNIT_4,
@ -257,7 +257,7 @@ public abstract class SpriteTypeface extends Typeface {
private class SDWorkspace extends SpriteTypeface.Workspace { private class SDWorkspace extends SpriteTypeface.Workspace {
private final Collection<Face> faces = new ArrayList<>(); private final Collection<ShapePart> faces = new ArrayList<>();
private final Vec3 origin = new Vec3(); private final Vec3 origin = new Vec3();
private final Vec3 width = new Vec3(); private final Vec3 width = new Vec3();
@ -298,7 +298,7 @@ public abstract class SpriteTypeface extends Typeface {
workspace.height.sub(workspace.origin); workspace.height.sub(workspace.origin);
workspace.faces.add( workspace.faces.add(
Faces.createRectangle( ShapeParts.createRectangle(
getProgram(), getProgram(),
texture, texture,
color, color,
@ -314,7 +314,7 @@ public abstract class SpriteTypeface extends Typeface {
return new Shape( return new Shape(
Usage.STATIC, Usage.STATIC,
getProgram(), getProgram(),
workspace.faces.toArray(new Face[workspace.faces.size()]) workspace.faces.toArray(new ShapePart[workspace.faces.size()])
); );
} }

View File

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

View File

@ -0,0 +1,151 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Consumer;
import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.Subscribe;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.event.ButtonEvent;
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.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
public abstract class BasicButton extends Component {
private final Label label;
private boolean isPressed = false;
private final Collection<Consumer<BasicButton>> actions = Collections.synchronizedCollection(new ArrayList<>());
public BasicButton(String name, String label, Font labelFont) {
super(name);
this.label = new Label(name + ".Label", labelFont, label);
setLayout(new LayoutAlign(10));
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 (
e.isLeftMouseButton() ||
e.getKey() == GLFW.GLFW_KEY_SPACE ||
e.getKey() == GLFW.GLFW_KEY_ENTER
) {
setPressed(e.isPress());
return true;
} else {
return false;
}
});
addListener(new Object() {
// Release when losing focus
@Subscribe
public void onFocusChange(FocusEvent e) {
if (!e.getNewState()) {
setPressed(false);
}
}
// Release when hover ends
@Subscribe
public void onHoverEnded(HoverEvent e) {
if (!e.isNowHovered()) {
setPressed(false);
}
}
// Release when disabled
@Subscribe
public void onDisabled(EnableEvent e) {
if (!e.getComponent().isEnabled()) {
setPressed(false);
}
}
// Trigger virtualClick when button is released
@Subscribe
public void onRelease(ButtonEvent.Release e) {
virtualClick();
}
});
}
public BasicButton(String name, String label) {
this(name, label, new Font());
}
public boolean isPressed() {
return isPressed;
}
public void click() {
setPressed(true);
setPressed(false);
}
public void setPressed(boolean isPressed) {
if (this.isPressed != isPressed) {
this.isPressed = isPressed;
if (isPressed) {
takeFocus();
}
dispatchEvent(ButtonEvent.create(this, this.isPressed));
}
}
public BasicButton addAction(Consumer<BasicButton> action) {
this.actions.add(Objects.requireNonNull(action, "action"));
return this;
}
public boolean removeAction(Consumer<BasicButton> action) {
return this.actions.remove(action);
}
public void virtualClick() {
this.actions.forEach(action -> {
action.accept(this);
});
}
public Label getLabel() {
return label;
}
}

View File

@ -0,0 +1,79 @@
/*
* 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 glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.Colors;
public class Button extends BasicButton {
public Button(String name, String label, Font labelFont) {
super(name, label, labelFont);
}
public Button(String name, String label) {
this(name, label, new Font());
}
@Override
protected void assembleSelf(RenderTarget target) {
// Border
Vec4 borderColor;
if (isPressed() || isHovered() || isFocused()) {
borderColor = Colors.BLUE;
} else {
borderColor = Colors.LIGHT_GRAY;
}
target.fill(getX(), getY(), getWidth(), getHeight(), borderColor);
// Inside area
if (isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (isHovered() && isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, backgroundColor);
}
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.WHITE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
}
}

View File

@ -0,0 +1,149 @@
/*
* 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 glm.vec._2.i.Vec2i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.font.Typefaces;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
public class Checkbox extends BasicButton {
private class Tick extends Component {
public Tick() {
super(Checkbox.this.getName() + ".Tick");
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
}
@Override
protected void assembleSelf(RenderTarget target) {
int size = getPreferredSize().x;
int x = getX();
int y = getY() + (getHeight() - size) / 2;
// Border
Vec4 borderColor;
if (Checkbox.this.isPressed() || Checkbox.this.isHovered() || Checkbox.this.isFocused()) {
borderColor = Colors.BLUE;
} else {
borderColor = Colors.LIGHT_GRAY;
}
target.fill(x, y, size, size, borderColor);
// Inside area
if (Checkbox.this.isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (Checkbox.this.isHovered() && Checkbox.this.isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
target.fill(x + 2, y + 2, size - 4, size - 4, backgroundColor);
}
// "Tick"
if (Checkbox.this.isChecked()) {
target.fill(x + 4, y + 4, size - 8, size - 8, Colors.BLUE);
}
}
}
private boolean checked;
public Checkbox(String name, String label, Font labelFont, boolean check) {
super(name, label, labelFont);
this.checked = check;
assert getChildren().size() == 1 : "Checkbox expects that BasicButton contains exactly one child";
Component basicChild = getChild(0);
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
removeChild(basicChild);
setLayout(new LayoutAlign(0, 0.5f, 10));
group.setLayoutHint(basicChild.getLayoutHint());
group.addChild(new Tick());
group.addChild(basicChild);
addChild(group);
addAction(b -> switchState());
}
public Checkbox(String name, String label, Font labelFont) {
this(name, label, labelFont, false);
}
public Checkbox(String name, String label, boolean check) {
this(name, label, new Font(), check);
}
public Checkbox(String name, String label) {
this(name, label, false);
}
public void switchState() {
setChecked(!isChecked());
}
/**
* @return the checked
*/
public boolean isChecked() {
return checked;
}
/**
* @param checked the checked to set
*/
public void setChecked(boolean checked) {
this.checked = checked;
}
@Override
protected void assembleSelf(RenderTarget target) {
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
}
}

View File

@ -19,18 +19,23 @@
package ru.windcorp.progressia.client.graphics.gui; package ru.windcorp.progressia.client.graphics.gui;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import glm.vec._2.i.Vec2i; import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.backend.InputTracker; import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget; import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent; 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.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.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent; import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent; import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
@ -62,6 +67,8 @@ public class Component extends Named {
private Object layoutHint = null; private Object layoutHint = null;
private Layout layout = null; private Layout layout = null;
private boolean isEnabled = true;
private boolean isFocusable = false; private boolean isFocusable = false;
private boolean isFocused = false; private boolean isFocused = false;
@ -285,10 +292,31 @@ public class Component extends Named {
return this; return this;
} }
/**
* Checks whether this component is focusable. A component needs to be
* focusable to become focused. A component that is focusable may not
* necessarily be ready to gain focus (see {@link #canGainFocusNow()}).
*
* @return {@code true} iff the component is focusable
* @see #canGainFocusNow()
*/
public boolean isFocusable() { public boolean isFocusable() {
return isFocusable; return isFocusable;
} }
/**
* Checks whether this component can become focused at this moment.
* <p>
* The implementation of this method in {@link Component} considers the
* component a focus candidate if it is both focusable and enabled.
*
* @return {@code true} iff the component can receive focus
* @see #isFocusable()
*/
public boolean canGainFocusNow() {
return isFocusable() && isEnabled();
}
public Component setFocusable(boolean focusable) { public Component setFocusable(boolean focusable) {
this.isFocusable = focusable; this.isFocusable = focusable;
return this; return this;
@ -337,7 +365,7 @@ public class Component extends Named {
return; return;
} }
if (component.isFocusable()) { if (component.canGainFocusNow()) {
setFocused(false); setFocused(false);
component.setFocused(true); component.setFocused(true);
return; return;
@ -379,7 +407,7 @@ public class Component extends Named {
return; return;
} }
if (component.isFocusable()) { if (component.canGainFocusNow()) {
setFocused(false); setFocused(false);
component.setFocused(true); component.setFocused(true);
return; return;
@ -433,12 +461,51 @@ public class Component extends Named {
return null; return null;
} }
public boolean isEnabled() {
return isEnabled;
}
/**
* Enables or disables this component. An {@link EnableEvent} is dispatched
* if the state changes.
*
* @param enabled {@code true} to enable the component, {@code false} to
* disable the component
* @see #setEnabledRecursively(boolean)
*/
public void setEnabled(boolean enabled) {
if (this.isEnabled != enabled) {
if (isFocused() && isEnabled()) {
focusNext();
}
if (isEnabled()) {
setHovered(false);
}
this.isEnabled = enabled;
dispatchEvent(new EnableEvent(this));
}
}
/**
* Enables or disables this component and all of its children recursively.
*
* @param enabled {@code true} to enable the components, {@code false} to
* disable the components
* @see #setEnabled(boolean)
*/
public void setEnabledRecursively(boolean enabled) {
setEnabled(enabled);
getChildren().forEach(c -> c.setEnabledRecursively(enabled));
}
public boolean isHovered() { public boolean isHovered() {
return isHovered; return isHovered;
} }
protected void setHovered(boolean isHovered) { protected void setHovered(boolean isHovered) {
if (this.isHovered != isHovered) { if (this.isHovered != isHovered && isEnabled()) {
this.isHovered = isHovered; this.isHovered = isHovered;
if (!isHovered && !getChildren().isEmpty()) { if (!isHovered && !getChildren().isEmpty()) {
@ -475,11 +542,8 @@ public class Component extends Named {
eventBus.post(event); eventBus.post(event);
} }
public <T extends InputEvent> void addListener( public <T extends InputEvent> void addListener(Class<? extends T> type, boolean handlesConsumed,
Class<? extends T> type, InputListener<T> listener) {
boolean handlesConsumed,
InputListener<T> listener
) {
if (inputBus == null) { if (inputBus == null) {
inputBus = new InputBus(); inputBus = new InputBus();
} }
@ -502,7 +566,7 @@ public class Component extends Named {
} }
protected void handleInput(Input input) { protected void handleInput(Input input) {
if (inputBus != null) { if (inputBus != null && isEnabled()) {
inputBus.dispatch(input); inputBus.dispatch(input);
} }
} }
@ -598,6 +662,17 @@ public class Component extends Named {
} }
} }
/**
* Schedules the reassembly to occur.
* <p>
* This method is invoked in root components whenever a
* {@linkplain #requestReassembly() reassembly request} is made by one of
* its children. When creating the dedicated root component, override this
* method to perform any implementation-specific actions that will cause a
* reassembly as soon as possible.
* <p>
* The default implementation of this method does nothing.
*/
protected void handleReassemblyRequest() { protected void handleReassemblyRequest() {
// To be overridden // To be overridden
} }
@ -638,6 +713,135 @@ public class Component extends Named {
getChildren().forEach(child -> child.assemble(target)); getChildren().forEach(child -> child.assemble(target));
} }
/*
* Automatic Reassembly
*/
/**
* The various kinds of changes that may be used with
* {@link Component#reassembleAt(ARTrigger...)}.
*/
protected static enum ARTrigger {
/**
* Reassemble the component whenever its hover status changes, e.g.
* whenever the pointer enters or leaves its bounds.
*/
HOVER,
/**
* Reassemble the component whenever it gains or loses focus.
* <p>
* <em>Component must be focusable to be able to gain focus.</em> The
* component will not be reassembled unless
* {@link Component#setFocusable(boolean) setFocusable(true)} has been
* invoked.
*/
FOCUS,
/**
* Reassemble the component whenever it is enabled or disabled.
*/
ENABLE
}
/**
* All trigger objects (event listeners) that are currently registered with
* {@link #eventBus}. The field is {@code null} until the first trigger is
* installed.
*/
private Map<ARTrigger, Object> autoReassemblyTriggerObjects = null;
private Object createTriggerObject(ARTrigger type) {
switch (type) {
case HOVER:
return new Object() {
@Subscribe
public void onHoverChanged(HoverEvent e) {
requestReassembly();
}
};
case FOCUS:
return new Object() {
@Subscribe
public void onFocusChanged(FocusEvent e) {
requestReassembly();
}
};
case ENABLE:
return new Object() {
@Subscribe
public void onEnabled(EnableEvent e) {
requestReassembly();
}
};
default:
throw new NullPointerException("type");
}
}
/**
* Requests that {@link #requestReassembly()} is invoked on this component
* whenever any of the specified changes occur. Duplicate attempts to
* register the same trigger are silently ignored.
* <p>
* {@code triggers} may be empty, which results in a no-op. It must not be
* {@code null}.
*
* @param triggers the {@linkplain ARTrigger triggers} to
* request reassembly with.
* @see #disableAutoReassemblyAt(ARTrigger...)
*/
protected synchronized void reassembleAt(ARTrigger... triggers) {
Objects.requireNonNull(triggers, "triggers");
if (triggers.length == 0)
return;
if (autoReassemblyTriggerObjects == null) {
autoReassemblyTriggerObjects = new EnumMap<>(ARTrigger.class);
}
for (ARTrigger trigger : triggers) {
if (!autoReassemblyTriggerObjects.containsKey(trigger)) {
Object triggerObject = createTriggerObject(trigger);
addListener(trigger);
autoReassemblyTriggerObjects.put(trigger, triggerObject);
}
}
}
/**
* Requests that {@link #requestReassembly()} is no longer invoked on this
* component whenever any of the specified changes occur. After a trigger is
* removed, it may be reinstalled with
* {@link #reassembleAt(ARTrigger...)}. Attempts to remove a
* nonexistant trigger are silently ignored.
* <p>
* {@code triggers} may be empty, which results in a no-op. It must not be
* {@code null}.
*
* @param triggers the {@linkplain ARTrigger triggers} to remove
* @see #reassemblyAt(ARTrigger...)
*/
protected synchronized void disableAutoReassemblyAt(ARTrigger... triggers) {
Objects.requireNonNull(triggers, "triggers");
if (triggers.length == 0)
return;
if (autoReassemblyTriggerObjects == null)
return;
for (ARTrigger trigger : triggers) {
Object triggerObject = autoReassemblyTriggerObjects.remove(trigger);
if (triggerObject != null) {
removeListener(trigger);
}
}
}
// /** // /**
// * Returns a component that displays this component in its center. // * Returns a component that displays this component in its center.
// * @return a {@link Aligner} initialized to center this component // * @return a {@link Aligner} initialized to center this component

View File

@ -16,18 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package ru.windcorp.progressia.common.world.tile; package ru.windcorp.progressia.client.graphics.gui;
public interface TileReference { public class Group extends Component {
TileData get(); public Group(String name, Layout layout) {
super(name);
int getIndex(); setLayout(layout);
TileDataStack getStack();
default boolean isValid() {
return get() != null;
} }
} }

View File

@ -83,6 +83,11 @@ public class Label extends Component {
return font; return font;
} }
public void setFont(Font font) {
this.font = font;
requestReassembly();
}
public String getCurrentText() { public String getCurrentText() {
return currentText; return currentText;
} }
@ -96,11 +101,7 @@ public class Label extends Component {
float startX = getX() + font.getAlign() * (getWidth() - currentSize.x); float startX = getX() + font.getAlign() * (getWidth() - currentSize.x);
target.pushTransform( target.pushTransform(
new Mat4().identity().translate(startX, getY(), -1000) // TODO wtf new Mat4().identity().translate(startX, getY(), 0).scale(2)
// is this
// magic
// <---
.scale(2)
); );
target.addCustomRenderer(font.assemble(currentText, maxWidth)); target.addCustomRenderer(font.assemble(currentText, maxWidth));

View File

@ -15,14 +15,66 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package ru.windcorp.progressia.client.graphics.gui; package ru.windcorp.progressia.client.graphics.gui;
public class Panel extends Component { import java.util.Objects;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
public class Panel extends Group {
private Vec4 fill;
private Vec4 border;
public Panel(String name, Layout layout, Vec4 fill, Vec4 border) {
super(name, layout);
this.fill = Objects.requireNonNull(fill, "fill");
this.border = border;
}
public Panel(String name, Layout layout) { public Panel(String name, Layout layout) {
super(name); this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
setLayout(layout); }
/**
* @return the fill
*/
public Vec4 getFill() {
return fill;
}
/**
* @param fill the fill to set
*/
public void setFill(Vec4 fill) {
this.fill = Objects.requireNonNull(fill, "fill");
}
/**
* @return the border
*/
public Vec4 getBorder() {
return border;
}
/**
* @param border the border to set
*/
public void setBorder(Vec4 border) {
this.border = border;
}
@Override
protected void assembleSelf(RenderTarget target) {
if (border == null) {
target.fill(getX(), getY(), getWidth(), getHeight(), fill);
} else {
target.fill(getX(), getY(), getWidth(), getHeight(), border);
target.fill(getX() + 2, getY() + 2, getWidth() - 4, getHeight() - 4, fill);
}
} }
} }

View File

@ -0,0 +1,205 @@
/*
* 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 org.lwjgl.glfw.GLFW;
import glm.vec._2.i.Vec2i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.font.Typefaces;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutHorizontal;
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
public class RadioButton extends BasicButton {
private class Tick extends Component {
public Tick() {
super(RadioButton.this.getName() + ".Tick");
setPreferredSize(new Vec2i(Typefaces.getDefault().getLineHeight() * 3 / 2));
}
private void cross(RenderTarget target, int x, int y, int size, Vec4 color) {
target.fill(x + 4, y, size - 8, size, color);
target.fill(x + 2, y + 2, size - 4, size - 4, color);
target.fill(x, y + 4, size, size - 8, color);
}
@Override
protected void assembleSelf(RenderTarget target) {
int size = getPreferredSize().x;
int x = getX();
int y = getY() + (getHeight() - size) / 2;
// Border
Vec4 borderColor;
if (RadioButton.this.isPressed() || RadioButton.this.isHovered() || RadioButton.this.isFocused()) {
borderColor = Colors.BLUE;
} else {
borderColor = Colors.LIGHT_GRAY;
}
cross(target, x, y, size, borderColor);
// Inside area
if (RadioButton.this.isPressed()) {
// Do nothing
} else {
Vec4 backgroundColor;
if (RadioButton.this.isHovered() && RadioButton.this.isEnabled()) {
backgroundColor = Colors.HOVER_BLUE;
} else {
backgroundColor = Colors.WHITE;
}
cross(target, x + 2, y + 2, size - 4, backgroundColor);
}
// "Tick"
if (RadioButton.this.isChecked()) {
cross(target, x + 4, y + 4, size - 8, Colors.BLUE);
}
}
}
private boolean checked;
private RadioButtonGroup group = null;
public RadioButton(String name, String label, Font labelFont, boolean check) {
super(name, label, labelFont);
this.checked = check;
assert getChildren().size() == 1 : "RadioButton expects that BasicButton contains exactly one child";
Component basicChild = getChild(0);
Group group = new Group(getName() + ".LabelAndTick", new LayoutHorizontal(0, 10));
removeChild(basicChild);
setLayout(new LayoutAlign(0, 0.5f, 10));
group.setLayoutHint(basicChild.getLayoutHint());
group.addChild(new Tick());
group.addChild(basicChild);
addChild(group);
addListener(KeyEvent.class, e -> {
if (e.isRelease()) return false;
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;
} 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;
}
return false;
});
addAction(b -> setChecked(true));
}
public RadioButton(String name, String label, Font labelFont) {
this(name, label, labelFont, false);
}
public RadioButton(String name, String label, boolean check) {
this(name, label, new Font(), check);
}
public RadioButton(String name, String label) {
this(name, label, false);
}
/**
* @param group the group to set
*/
public RadioButton setGroup(RadioButtonGroup group) {
if (this.group != null) {
group.selectNext();
removeAction(group.listener);
group.buttons.remove(this);
group.getSelected(); // Clear reference if this was the only button in the group
}
this.group = group;
if (this.group != null) {
group.buttons.add(this);
addAction(group.listener);
}
setChecked(false);
return this;
}
/**
* @return the checked
*/
public boolean isChecked() {
return checked;
}
/**
* @param checked the checked to set
*/
public void setChecked(boolean checked) {
this.checked = checked;
if (group != null) {
group.listener.accept(this); // Failsafe for manual invocations of setChecked()
}
}
@Override
protected void assembleSelf(RenderTarget target) {
// Change label font color
if (isPressed()) {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLUE));
} else {
getLabel().setFont(getLabel().getFont().withColor(Colors.BLACK));
}
}
@Override
protected void postAssembleSelf(RenderTarget target) {
// Apply disable tint
if (!isEnabled()) {
target.fill(getX(), getY(), getWidth(), getHeight(), Colors.toVector(0x88FFFFFF));
}
}
}

View File

@ -0,0 +1,119 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
public class RadioButtonGroup {
private final Collection<Consumer<RadioButtonGroup>> actions = Collections.synchronizedCollection(new ArrayList<>());
final List<RadioButton> buttons = Collections.synchronizedList(new ArrayList<>());
private RadioButton selected = null;
Consumer<BasicButton> listener = b -> {
if (b instanceof RadioButton && ((RadioButton) b).isChecked() && buttons.contains(b)) {
select((RadioButton) b);
}
};
public RadioButtonGroup addAction(Consumer<RadioButtonGroup> action) {
this.actions.add(Objects.requireNonNull(action, "action"));
return this;
}
public boolean removeAction(Consumer<BasicButton> action) {
return this.actions.remove(action);
}
public List<RadioButton> getButtons() {
return Collections.unmodifiableList(buttons);
}
public synchronized RadioButton getSelected() {
if (!buttons.contains(selected)) {
selected = null;
}
return selected;
}
public synchronized void select(RadioButton button) {
if (button != null && !buttons.contains(button)) {
throw new IllegalArgumentException("Button " + button + " is not in the group");
}
getSelected(); // Clear if invalid
if (selected == button) {
return; // Terminate listener-setter recursion
}
if (selected != null) {
selected.setChecked(false);
}
selected = button;
if (selected != null) {
selected.setChecked(true);
}
actions.forEach(action -> action.accept(this));
}
public void selectNext() {
selectNeighbour(+1);
}
public void selectPrevious() {
selectNeighbour(-1);
}
private synchronized void selectNeighbour(int direction) {
if (getSelected() == null) {
if (buttons.isEmpty()) {
throw new IllegalStateException("Cannot select neighbour button: group empty");
}
select(buttons.get(0));
} else {
RadioButton button;
int index = buttons.indexOf(selected);
do {
index += direction;
if (index >= buttons.size()) {
index = 0;
} else if (index < 0) {
index = buttons.size() - 1;
}
button = buttons.get(index);
} while (button != getSelected() && !button.isEnabled());
select(button);
}
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.BasicButton;
public class ButtonEvent extends ComponentEvent {
public static class Press extends ButtonEvent {
public Press(BasicButton button) {
super(button, true);
}
}
public static class Release extends ButtonEvent {
public Release(BasicButton button) {
super(button, false);
}
}
private final boolean isPress;
protected ButtonEvent(BasicButton button, boolean isPress) {
super(button);
this.isPress = isPress;
}
public static ButtonEvent create(BasicButton button, boolean isPress) {
if (isPress) {
return new Press(button);
} else {
return new Release(button);
}
}
public boolean isPress() {
return isPress;
}
public boolean isRelease() {
return !isPress;
}
}

View File

@ -0,0 +1,11 @@
package ru.windcorp.progressia.client.graphics.gui.event;
import ru.windcorp.progressia.client.graphics.gui.Component;
public class EnableEvent extends ComponentEvent {
public EnableEvent(Component component) {
super(component);
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,78 @@
/*
* 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.layout;
import static java.lang.Math.max;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.gui.Layout;
public class LayoutFill implements Layout {
private final int margin;
public LayoutFill(int margin) {
this.margin = margin;
}
public LayoutFill() {
this(0);
}
@Override
public void layout(Component c) {
c.getChildren().forEach(child -> {
int cWidth = c.getWidth() - 2 * margin;
int cHeight = c.getHeight() - 2 * margin;
child.setBounds(
c.getX() + margin,
c.getY() + margin,
cWidth,
cHeight
);
});
}
@Override
public Vec2i calculatePreferredSize(Component c) {
Vec2i result = new Vec2i(0, 0);
c.getChildren().stream()
.map(child -> child.getPreferredSize())
.forEach(size -> {
result.x = max(size.x, result.x);
result.y = max(size.y, result.y);
});
result.x += 2 * margin;
result.y += 2 * margin;
return result;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + margin + ")";
}
}

View File

@ -98,15 +98,26 @@ public class LayoutGrid implements Layout {
if (!isSummed) if (!isSummed)
throw new IllegalStateException("Not summed yet"); throw new IllegalStateException("Not summed yet");
int width, height;
if (column == columns.length - 1) {
width = parent.getWidth() - margin - columns[column];
} else {
width = columns[column + 1] - columns[column] - gap;
}
if (row == rows.length - 1) {
height = parent.getHeight() - margin - rows[row];
} else {
height = rows[row + 1] - rows[row] - gap;
}
child.setBounds( child.setBounds(
parent.getX() + columns[column], parent.getX() + columns[column],
parent.getY() + rows[row], parent.getY() + parent.getHeight() - (rows[row] + height),
(column != (columns.length - 1) ? (columns[column + 1] - columns[column] - gap) width,
: (parent.getWidth() - margin - columns[column])), height
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
: (parent.getHeight() - margin - rows[row]))
); );
} }
} }
@ -132,10 +143,9 @@ public class LayoutGrid implements Layout {
GridDimensions grid = calculateGrid(c); GridDimensions grid = calculateGrid(c);
grid.sum(); grid.sum();
int[] coords;
for (Component child : c.getChildren()) { for (Component child : c.getChildren()) {
coords = (int[]) child.getLayoutHint(); Vec2i coords = (Vec2i) child.getLayoutHint();
grid.setBounds(coords[0], coords[1], child, c); grid.setBounds(coords.x, coords.y, child, c);
} }
} }
} }
@ -149,11 +159,10 @@ public class LayoutGrid implements Layout {
private GridDimensions calculateGrid(Component parent) { private GridDimensions calculateGrid(Component parent) {
GridDimensions result = new GridDimensions(); GridDimensions result = new GridDimensions();
int[] coords;
for (Component child : parent.getChildren()) { for (Component child : parent.getChildren()) {
coords = (int[]) child.getLayoutHint(); Vec2i coords = (Vec2i) child.getLayoutHint();
result.add(coords[0], coords[1], child.getPreferredSize()); result.add(coords.x, coords.y, child.getPreferredSize());
} }
return result; return result;

View File

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

View File

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

View File

@ -0,0 +1,117 @@
/*
* 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.menu;
import org.lwjgl.glfw.GLFW;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.font.Font;
import ru.windcorp.progressia.client.graphics.gui.Component;
import ru.windcorp.progressia.client.graphics.gui.GUILayer;
import ru.windcorp.progressia.client.graphics.gui.Label;
import ru.windcorp.progressia.client.graphics.gui.Layout;
import ru.windcorp.progressia.client.graphics.gui.Panel;
import ru.windcorp.progressia.client.graphics.gui.layout.LayoutAlign;
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;
public class MenuLayer extends GUILayer {
private final Component content;
private final Component background;
private final Runnable closeAction = () -> {
GUI.removeLayer(this);
};
public MenuLayer(String name, Component content) {
super(name, new LayoutFill(0));
setCursorPolicy(CursorPolicy.REQUIRE);
this.background = new Panel(name + ".Background", new LayoutAlign(10), Colors.toVector(0x66000000), null);
this.content = content;
background.addChild(content);
getRoot().addChild(background);
}
public MenuLayer(String name, Layout contentLayout) {
this(name, new Panel(name + ".Content", contentLayout));
}
public MenuLayer(String name) {
this(name, new LayoutVertical(20, 10));
}
public Component getContent() {
return content;
}
public Component getBackground() {
return background;
}
protected void addTitle() {
String translationKey = "Layer" + getName() + ".Title";
MutableString titleText = new MutableStringLocalized(translationKey);
Font titleFont = new Font().deriveBold().withColor(Colors.BLACK).withAlign(0.5f);
Label label = new Label(getName() + ".Title", titleFont, titleText);
getContent().addChild(label);
Panel panel = new Panel(getName() + ".Title.Underscore", null, Colors.BLUE, null);
panel.setLayout(new LayoutFill() {
@Override
public Vec2i calculatePreferredSize(Component c) {
return new Vec2i(label.getPreferredSize().x + 40, 4);
}
});
getContent().addChild(panel);
}
protected Runnable getCloseAction() {
return closeAction;
}
@Override
protected void handleInput(Input input) {
if (!input.isConsumed()) {
InputEvent event = input.getEvent();
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
if (keyEvent.isPress() && keyEvent.getKey() == GLFW.GLFW_KEY_ESCAPE) {
getCloseAction().run();
}
}
}
super.handleInput(input);
input.consume();
}
}

View File

@ -87,22 +87,14 @@ public class CursorMoveEvent extends CursorEvent {
@Override @Override
public CursorMoveEvent snapshot() { public CursorMoveEvent snapshot() {
return new StaticMouseMoveEvent( return new StaticMouseMoveEvent(getPreviousPosition(), getNewPosition(), getTime());
getPreviousPosition(),
getNewPosition(),
getTime()
);
} }
private class StaticMouseMoveEvent extends CursorMoveEvent { private class StaticMouseMoveEvent extends CursorMoveEvent {
private final Vec2d previousPosition = new Vec2d(); private final Vec2d previousPosition = new Vec2d();
public StaticMouseMoveEvent( public StaticMouseMoveEvent(Vec2d previousPosition, Vec2d newPosition, double time) {
Vec2d previousPosition,
Vec2d newPosition,
double time
) {
super(newPosition, time); super(newPosition, time);
this.previousPosition.set(previousPosition.x, previousPosition.y); this.previousPosition.set(previousPosition.x, previousPosition.y);
} }

View File

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

View File

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

View File

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

View File

@ -31,28 +31,21 @@ public class InputBus {
private final boolean handleConsumed; private final boolean handleConsumed;
private final InputListener<?> listener; private final InputListener<?> listener;
public WrappedListener( public WrappedListener(Class<?> type, boolean handleConsumed, InputListener<?> listener) {
Class<?> type,
boolean handleConsumed,
InputListener<?> listener
) {
this.type = type; this.type = type;
this.handleConsumed = handleConsumed; this.handleConsumed = handleConsumed;
this.listener = listener; this.listener = listener;
} }
private boolean handles(Input input) { private boolean handles(Input input) {
return (!input.isConsumed() || handleConsumed) && return (!input.isConsumed() || handleConsumed) && type.isInstance(input.getEvent());
type.isInstance(input.getEvent());
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void handle(Input input) { public void handle(Input input) {
if (handles(input)) { if (handles(input)) {
boolean consumed = ((InputListener<InputEvent>) listener) boolean consumed = ((InputListener<InputEvent>) listener)
.handle( .handle((InputEvent) type.cast(input.getEvent()));
(InputEvent) type.cast(input.getEvent())
);
input.setConsumed(consumed); input.setConsumed(consumed);
} }
@ -66,18 +59,12 @@ public class InputBus {
listeners.forEach(l -> l.handle(input)); listeners.forEach(l -> l.handle(input));
} }
public <T extends InputEvent> void register( public <T extends InputEvent> void register(Class<? extends T> type, boolean handlesConsumed,
Class<? extends T> type, InputListener<T> listener) {
boolean handlesConsumed,
InputListener<T> listener
) {
listeners.add(new WrappedListener(type, handlesConsumed, listener)); listeners.add(new WrappedListener(type, handlesConsumed, listener));
} }
public <T extends InputEvent> void register( public <T extends InputEvent> void register(Class<? extends T> type, InputListener<T> listener) {
Class<? extends T> type,
InputListener<T> listener
) {
register(type, false, listener); register(type, false, listener);
} }

View File

@ -18,23 +18,23 @@
package ru.windcorp.progressia.client.graphics.model; package ru.windcorp.progressia.client.graphics.model;
import static ru.windcorp.progressia.common.world.block.BlockFace.*; import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.rels.AbsFace;
class BlockFaceVectors { class BlockFaceVectors {
private static BlockFaceVectors createInner(BlockFaceVectors outer) { private static BlockFaceVectors createInner(BlockFaceVectors outer) {
ImmutableMap.Builder<BlockFace, Vec3> originBuilder = ImmutableMap.builder(); ImmutableMap.Builder<AbsFace, Vec3> originBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> widthBuilder = ImmutableMap.builder(); ImmutableMap.Builder<AbsFace, Vec3> widthBuilder = ImmutableMap.builder();
ImmutableMap.Builder<BlockFace, Vec3> heightBuilder = ImmutableMap.builder(); ImmutableMap.Builder<AbsFace, Vec3> heightBuilder = ImmutableMap.builder();
for (BlockFace face : getFaces()) { for (AbsFace face : getFaces()) {
Vec3 width = outer.getWidth(face); Vec3 width = outer.getWidth(face);
Vec3 height = outer.getHeight(face); Vec3 height = outer.getHeight(face);
@ -59,36 +59,36 @@ class BlockFaceVectors {
static { static {
OUTER = new BlockFaceVectors( OUTER = new BlockFaceVectors(
ImmutableMap.<BlockFace, Vec3>builder() ImmutableMap.<AbsFace, Vec3>builder()
.put(TOP, new Vec3(-0.5f, +0.5f, +0.5f)) .put(POS_Z, new Vec3(-0.5f, +0.5f, +0.5f))
.put(BOTTOM, new Vec3(-0.5f, -0.5f, -0.5f)) .put(NEG_Z, new Vec3(-0.5f, -0.5f, -0.5f))
.put(NORTH, new Vec3(+0.5f, -0.5f, -0.5f)) .put(POS_X, new Vec3(+0.5f, -0.5f, -0.5f))
.put(SOUTH, new Vec3(-0.5f, +0.5f, -0.5f)) .put(NEG_X, new Vec3(-0.5f, +0.5f, -0.5f))
.put(WEST, new Vec3(+0.5f, +0.5f, -0.5f)) .put(POS_Y, new Vec3(+0.5f, +0.5f, -0.5f))
.put(EAST, new Vec3(-0.5f, -0.5f, -0.5f)) .put(NEG_Y, new Vec3(-0.5f, -0.5f, -0.5f))
.build(), .build(),
ImmutableMap.<BlockFace, Vec3>builder() ImmutableMap.<AbsFace, Vec3>builder()
.put(TOP, new Vec3(0, -1, 0)) .put(POS_Z, new Vec3(0, -1, 0))
.put(BOTTOM, new Vec3(0, +1, 0)) .put(NEG_Z, new Vec3(0, +1, 0))
.put(NORTH, new Vec3(0, +1, 0)) .put(POS_X, new Vec3(0, +1, 0))
.put(SOUTH, new Vec3(0, -1, 0)) .put(NEG_X, new Vec3(0, -1, 0))
.put(WEST, new Vec3(-1, 0, 0)) .put(POS_Y, new Vec3(-1, 0, 0))
.put(EAST, new Vec3(+1, 0, 0)) .put(NEG_Y, new Vec3(+1, 0, 0))
.build(), .build(),
ImmutableMap.<BlockFace, Vec3>builder() ImmutableMap.<AbsFace, Vec3>builder()
.put(TOP, new Vec3(+1, 0, 0)) .put(POS_Z, new Vec3(+1, 0, 0))
.put(BOTTOM, new Vec3(+1, 0, 0)) .put(NEG_Z, new Vec3(+1, 0, 0))
.put(NORTH, new Vec3(0, 0, +1)) .put(POS_X, new Vec3(0, 0, +1))
.put(SOUTH, new Vec3(0, 0, +1)) .put(NEG_X, new Vec3(0, 0, +1))
.put(WEST, new Vec3(0, 0, +1)) .put(POS_Y, new Vec3(0, 0, +1))
.put(EAST, new Vec3(0, 0, +1)) .put(NEG_Y, new Vec3(0, 0, +1))
.build() .build()
); );
@ -100,29 +100,29 @@ class BlockFaceVectors {
return inner ? INNER : OUTER; return inner ? INNER : OUTER;
} }
private final ImmutableMap<BlockFace, Vec3> origins; private final ImmutableMap<AbsFace, Vec3> origins;
private final ImmutableMap<BlockFace, Vec3> widths; private final ImmutableMap<AbsFace, Vec3> widths;
private final ImmutableMap<BlockFace, Vec3> heights; private final ImmutableMap<AbsFace, Vec3> heights;
public BlockFaceVectors( public BlockFaceVectors(
ImmutableMap<BlockFace, Vec3> origins, ImmutableMap<AbsFace, Vec3> origins,
ImmutableMap<BlockFace, Vec3> widths, ImmutableMap<AbsFace, Vec3> widths,
ImmutableMap<BlockFace, Vec3> heights ImmutableMap<AbsFace, Vec3> heights
) { ) {
this.origins = origins; this.origins = origins;
this.widths = widths; this.widths = widths;
this.heights = heights; this.heights = heights;
} }
public Vec3 getOrigin(BlockFace face) { public Vec3 getOrigin(AbsFace face) {
return origins.get(face); return origins.get(face);
} }
public Vec3 getWidth(BlockFace face) { public Vec3 getWidth(AbsFace face) {
return widths.get(face); return widths.get(face);
} }
public Vec3 getHeight(BlockFace face) { public Vec3 getHeight(AbsFace face) {
return heights.get(face); return heights.get(face);
} }
} }

View File

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

View File

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

View File

@ -30,10 +30,10 @@ import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
public class Shape implements Renderable { public class Shape implements Renderable {
private final ShapeRenderProgram program; private final ShapeRenderProgram program;
private final Face[] faces; private final ShapePart[] parts;
private final Usage usage; private final Usage usage;
private FaceGroup[] groups; private ShapePartGroup[] groups;
private ByteBuffer vertices; private ByteBuffer vertices;
private ShortBuffer indices; private ShortBuffer indices;
@ -45,33 +45,33 @@ public class Shape implements Renderable {
private VertexBufferObject verticesVbo; private VertexBufferObject verticesVbo;
private VertexBufferObject indicesVbo; private VertexBufferObject indicesVbo;
public Shape(Usage usage, ShapeRenderProgram program, Face... faces) { public Shape(Usage usage, ShapeRenderProgram program, ShapePart... parts) {
this.program = program; this.program = program;
this.faces = faces; this.parts = parts;
this.usage = usage; this.usage = usage;
configureFaces(); configureParts();
program.preprocess(this); program.preprocess(this);
assembleBuffers(); assembleBuffers();
} }
private void configureFaces() { private void configureParts() {
for (Face face : faces) { for (ShapePart part : parts) {
face.setShape(this); part.setShape(this);
} }
} }
private void assembleBuffers() { private void assembleBuffers() {
// TODO optimize: only update faces that requested it // TODO optimize: only update faces that requested it
sortFaces(); sortParts();
resizeBuffers(); resizeBuffers();
for (Face face : faces) { for (ShapePart part : parts) {
assembleVertices(face); assembleVertices(part);
assembleIndices(face); assembleIndices(part);
face.resetUpdateFlags(); part.resetUpdateFlags();
} }
this.vertices.flip(); this.vertices.flip();
@ -85,110 +85,110 @@ public class Shape implements Renderable {
private void resizeBuffers() { private void resizeBuffers() {
int verticesRequired = 0, indicesRequired = 0; int verticesRequired = 0, indicesRequired = 0;
for (Face face : faces) { for (ShapePart part : parts) {
verticesRequired += face.getVertices().remaining(); verticesRequired += part.getVertices().remaining();
indicesRequired += face.getIndices().remaining(); indicesRequired += part.getIndices().remaining();
} }
if (this.vertices == null || vertices.capacity() < verticesRequired) { if (vertices == null || vertices.capacity() < verticesRequired) {
this.vertices = BufferUtils.createByteBuffer(verticesRequired); this.vertices = BufferUtils.createByteBuffer(verticesRequired);
} else { } else {
this.vertices.position(0).limit(verticesRequired); vertices.position(0).limit(verticesRequired);
} }
if (this.indices == null || this.indices.capacity() < indicesRequired) { if (indices == null || indices.capacity() < indicesRequired) {
this.indices = BufferUtils.createShortBuffer(indicesRequired); this.indices = BufferUtils.createShortBuffer(indicesRequired);
} else { } else {
this.indices.position(0).limit(indicesRequired); indices.position(0).limit(indicesRequired);
} }
} }
private void assembleVertices(Face face) { private void assembleVertices(ShapePart part) {
face.locationOfVertices = this.vertices.position(); part.locationOfVertices = this.vertices.position();
insertVertices(face); insertVertices(part);
linkVerticesWith(face); linkVerticesWith(part);
} }
private void insertVertices(Face face) { private void insertVertices(ShapePart part) {
ByteBuffer faceVertices = face.getVertices(); ByteBuffer partVertices = part.getVertices();
faceVertices.mark(); partVertices.mark();
this.vertices.put(faceVertices); this.vertices.put(partVertices);
faceVertices.reset(); partVertices.reset();
} }
private void linkVerticesWith(Face face) { private void linkVerticesWith(ShapePart part) {
int limit = vertices.limit(); int limit = vertices.limit();
int position = vertices.position(); int position = vertices.position();
vertices.limit(position).position(face.getLocationOfVertices()); vertices.limit(position).position(part.getLocationOfVertices());
face.vertices = vertices.slice(); part.vertices = vertices.slice();
vertices.position(position).limit(limit); vertices.position(position).limit(limit);
} }
private void assembleIndices(Face face) { private void assembleIndices(ShapePart part) {
short vertexOffset = (short) (face.getLocationOfVertices() / program.getBytesPerVertex()); short vertexOffset = (short) (part.getLocationOfVertices() / program.getBytesPerVertex());
face.locationOfIndices = indices.position(); part.locationOfIndices = indices.position();
ShortBuffer faceIndices = face.getIndices(); ShortBuffer partIndices = part.getIndices();
if (faceIndices == null) { if (partIndices == null) {
for (int i = 0; i < face.getVertexCount(); ++i) { for (int i = 0; i < part.getVertexCount(); ++i) {
this.indices.put((short) (vertexOffset + i)); this.indices.put((short) (vertexOffset + i));
} }
} else { } else {
for (int i = faceIndices.position(); i < faceIndices.limit(); ++i) { for (int i = partIndices.position(); i < partIndices.limit(); ++i) {
short faceIndex = faceIndices.get(i); short partIndex = partIndices.get(i);
faceIndex += vertexOffset; partIndex += vertexOffset;
this.indices.put(faceIndex); this.indices.put(partIndex);
} }
} }
} }
private void sortFaces() { private void sortParts() {
Arrays.sort(faces); Arrays.sort(parts);
} }
private void assembleGroups() { private void assembleGroups() {
int unique = countUniqueFaces(); int unique = countUniqueParts();
this.groups = new FaceGroup[unique]; this.groups = new ShapePartGroup[unique];
if (faces.length == 0) if (parts.length == 0)
return; return;
int previousHandle = faces[0].getSortingIndex(); int previousHandle = parts[0].getSortingIndex();
int start = 0; int start = 0;
int groupIndex = 0; int groupIndex = 0;
for (int i = 1; i < faces.length; ++i) { for (int i = 1; i < parts.length; ++i) {
if (previousHandle != faces[i].getSortingIndex()) { if (previousHandle != parts[i].getSortingIndex()) {
groups[groupIndex] = new FaceGroup(faces, start, i); groups[groupIndex] = new ShapePartGroup(parts, start, i);
start = i; start = i;
groupIndex++; groupIndex++;
previousHandle = faces[i].getSortingIndex(); previousHandle = parts[i].getSortingIndex();
} }
} }
assert groupIndex == groups.length - 1; assert groupIndex == groups.length - 1;
groups[groupIndex] = new FaceGroup(faces, start, faces.length); groups[groupIndex] = new ShapePartGroup(parts, start, parts.length);
} }
private int countUniqueFaces() { private int countUniqueParts() {
if (faces.length == 0) if (parts.length == 0)
return 0; return 0;
int result = 1; int result = 1;
int previousHandle = faces[0].getSortingIndex(); int previousHandle = parts[0].getSortingIndex();
for (int i = 1; i < faces.length; ++i) { for (int i = 1; i < parts.length; ++i) {
if (previousHandle != faces[i].getSortingIndex()) { if (previousHandle != parts[i].getSortingIndex()) {
result++; result++;
previousHandle = faces[i].getSortingIndex(); previousHandle = parts[i].getSortingIndex();
} }
} }
@ -238,11 +238,11 @@ public class Shape implements Renderable {
return program; return program;
} }
public Face[] getFaces() { public ShapePart[] getParts() {
return faces; return parts;
} }
public FaceGroup[] getGroups() { public ShapePartGroup[] getGroups() {
return groups; return groups;
} }

View File

@ -24,7 +24,7 @@ import java.util.Objects;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
public class Face implements Comparable<Face> { public class ShapePart implements Comparable<ShapePart> {
private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null; private static final ShortBuffer GENERATE_SUCCESSIVE_LATER = null;
@ -40,7 +40,7 @@ public class Face implements Comparable<Face> {
private ShortBuffer userIndices; private ShortBuffer userIndices;
private boolean userIndicesUpdated = true; private boolean userIndicesUpdated = true;
public Face( public ShapePart(
Texture texture, Texture texture,
ByteBuffer vertices, ByteBuffer vertices,
ShortBuffer indices ShortBuffer indices
@ -50,7 +50,7 @@ public class Face implements Comparable<Face> {
setIndices(indices); setIndices(indices);
} }
public Face( public ShapePart(
Texture texture, Texture texture,
ByteBuffer vertices ByteBuffer vertices
) { ) {
@ -155,7 +155,7 @@ public class Face implements Comparable<Face> {
return vertices; return vertices;
} }
public Face setVertices(ByteBuffer vertices) { public ShapePart setVertices(ByteBuffer vertices) {
this.vertices = Objects.requireNonNull(vertices, "vertices"); this.vertices = Objects.requireNonNull(vertices, "vertices");
markForVertexUpdate(); markForVertexUpdate();
return this; return this;
@ -202,7 +202,7 @@ public class Face implements Comparable<Face> {
return userIndices.remaining(); return userIndices.remaining();
} }
public Face setIndices(ShortBuffer indices) { public ShapePart setIndices(ShortBuffer indices) {
if (indices == null) { if (indices == null) {
indices = GENERATE_SUCCESSIVE_LATER; indices = GENERATE_SUCCESSIVE_LATER;
} }
@ -245,7 +245,7 @@ public class Face implements Comparable<Face> {
} }
@Override @Override
public int compareTo(Face o) { public int compareTo(ShapePart o) {
return Integer.compare(getSortingIndex(), o.getSortingIndex()); return Integer.compare(getSortingIndex(), o.getSortingIndex());
} }

View File

@ -21,13 +21,13 @@ package ru.windcorp.progressia.client.graphics.model;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive; import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
public class FaceGroup { public class ShapePartGroup {
private final TexturePrimitive texture; private final TexturePrimitive texture;
private final int indexCount; private final int indexCount;
private final int byteOffsetOfIndices; private final int byteOffsetOfIndices;
FaceGroup(Face[] faces, int start, int end) { ShapePartGroup(ShapePart[] faces, int start, int end) {
Texture t = faces[start].getTexture(); Texture t = faces[start].getTexture();
this.texture = t == null ? null : t.getSprite().getPrimitive(); this.texture = t == null ? null : t.getSprite().getPrimitive();
@ -36,11 +36,10 @@ public class FaceGroup {
int indexCount = 0; int indexCount = 0;
for (int i = start; i < end; ++i) { for (int i = start; i < end; ++i) {
Face face = faces[i]; ShapePart face = faces[i];
assert this.texture == null assert this.texture == null ? (face.getTexture() == null)
? (face.getTexture() == null) : (face.getTexture().getSprite().getPrimitive() == this.texture);
: (face.getTexture().getSprite().getPrimitive() == this.texture);
indexCount += face.getIndexCount(); indexCount += face.getIndexCount();
} }

View File

@ -25,14 +25,14 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4; import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder; import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Faces { public class ShapeParts {
private Faces() { private ShapeParts() {
} }
public static Face createRectangle( public static ShapePart createRectangle(
ShapeRenderProgram program, ShapeRenderProgram program,
Texture texture, Texture texture,
Vec4 colorMultiplier, Vec4 colorMultiplier,
@ -82,19 +82,19 @@ public class Faces {
} }
); );
return new Face( return new ShapePart(
texture, texture,
builder.assemble(), builder.assemble(),
buffer buffer
); );
} }
public static Face createBlockFace( public static ShapePart createBlockFace(
ShapeRenderProgram program, ShapeRenderProgram program,
Texture texture, Texture texture,
Vec4 colorMultiplier, Vec4 colorMultiplier,
Vec3 blockCenter, Vec3 blockCenter,
BlockFace face, AbsFace face,
boolean inner boolean inner
) { ) {
BlockFaceVectors vectors = BlockFaceVectors.get(inner); BlockFaceVectors vectors = BlockFaceVectors.get(inner);

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