118 Commits

Author SHA1 Message Date
eb5aa59941 Removed plain sand block and tile; incremented version
To be honest, I kinda forgot to increment version in last commit, so I
decided to make a pointless change to justify an entire commit =\
2021-09-11 21:12:42 +03:00
409bbdb680 Merge branch 'save-world' 2021-09-11 19:42:36 +03:00
0c41350ae7 Added file identification header
-All files now start with a 16 byte header consisting of "PROG" followed
by the integer position of the region in region coordinates.
	-regionCoords is passed through some functions to allow for this to be
confirmed.
2021-09-10 22:19:18 -04:00
a633c8324e Added Reset World button and fixed some translations 2021-09-11 00:47:28 +03:00
6b33f231b4 Moved region implementation to .server.world.io.region 2021-09-10 23:59:03 +03:00
e2308b825d Removed warnings and formatted code 2021-09-10 23:31:14 +03:00
e0f6a08740 Bug fixes
-Now offset technically starts at 1, so any chunks with offset 0 are
ignored.
-Reduced number of unused chunks, storage efficiency is at about 99% (if
null sectors are counted as useful)
2021-09-10 14:14:18 -04:00
2820e01974 Finished Partition Logic
Note: it still does waste a lot of space, I will work on that next
-Added back the confirmHeaderHealth logic
-Checks to make sure it will not overwrite important chunks
	-Uses PartitionLink chunks to move to a different part of the file
-Added allocateEmptySector() to allow for the file size to be increased
without moving the origin point of the chunk.
2021-09-10 13:51:37 -04:00
46bcb85044 Region File better????
-Moved most of the file accessing of Region to RegionFile
-Disabled most of the header check except the length check(it will be
back soon)
-Max chunk size arbitrarily raised to 4MiB because I wanted sectors
longer than 16B
-Sectors now have a mandatory 1B header that identifies it
	-0 is a null sector, it ends every chunk
	-1 is a normal data sector, it has a "parity" byte that makes sure it
is reading chunks in linear order(fun fact: it isnt at the moment)
	-2 is a jump to a different location. this isnt implemented well yet
	-3 will be a "bulk data" sector. Multiple chunks with identical data
can point here. Probably only useful when it is easily identifiable,
like multiple chunks being one entire block, like air.
-Removed all chunk length references as I think they do not make sense
when it can use different sectors for non-data purposes.
2021-09-09 19:58:44 -04:00
c5dfe3d0b7 Saving Modifications
-Safer saving, it waits for the file to stop being used to close(still
has holes)
-If a chunk is too large, it is moved to the end of the file.
	-Calculates the efficiency of each region file in confirmHeaderHealth
-Fixed import warning
2021-09-09 16:52:55 -04:00
a85fc27f8b Added _UNFINISHED_ water, beaches and mantle
- Refactored terrain generation
- Added PiecewiseLinearFunction
- Added some placeholder content
  - Added Test:Water, the solid, opaque water
  - Added beaches
  - Added Test:Mantle
- Tweaked rock distribution parameters
2021-08-31 17:27:08 +03:00
711e4a2bb4 Added formatting templates guide for Intellij IDEA 2021-08-30 19:51:28 +03:00
0100c8791d Added player saving and loading from disk 2021-08-30 18:23:42 +03:00
e967a64401 More Compact region files
-Sector length increased to a short, the minimum sector size is now 16
bytes
2021-08-30 10:52:56 -04:00
d2ffe1fe0e Fixed the bug that opfromthestart found, reloading now works 2021-08-29 12:04:02 +03:00
f4300558d5 Formatted and broke the saving mechanism. I'm too tired to bugfix
- Refactored and formatted TestWorldDiskIO
- Removed HashableVec3i
- Added Coordinates methods for custom bit count
- Properly reverted commit 98250cd
- Known bugs:
  - Server shutdown close()s regions too early
  - Re-entering a world does not show saved changes
2021-08-29 02:08:19 +03:00
cd16334db8 Moved TestWorldDiskIO to a subpackage and introduced some abstractions 2021-08-28 23:31:50 +03:00
41a2909f7c Reverted last two commits because no one wants to fix the bugs 2021-08-28 21:31:34 +03:00
a222ea8f67 Fixed multithread chunk IO 2021-08-28 21:14:35 +03:00
98250cd524 Some changes for efficiency(not yet)
-Tried to use threads/executors in ChunkRequestDaemon, it just hangs.
-Added isEmpty and isOpaque attributes to DefaultChunkData (should this
just be in ChunkData?)
	-Added compute and getter functions to access(for everything after
loading)
	-Doesn't render empty chunks(not yet used)
-Using format 65537 allows the empty and opaqueness to be saved. They do
not do anything yet and there is no way to set them yet
	-Added loadRegionX and ".progressia_chunkx" file
-removed formats 0 and 1, which use individual chunk files.
2021-08-27 16:59:05 -04:00
f74c731a3d Fixed GUI buttons and a rare crash on startup 2021-08-27 12:20:58 +03:00
98c383bf7d Fixed a stupid typo in previous commit 2021-08-26 12:11:21 +03:00
dd80df2cf2 Fixed a GUI reassembly issue and added cursor disabling suppression
- Buttons no longer reassemble every frame
- Label.setFont() no longer requests reassembly
- Component.reassembleAt now actually works
- Using a very long flag, cursor capturing can be disabled to facilitate
GUI debugging
2021-08-26 12:09:22 +03:00
9dcb3a7748 -Better HashableVec3i compare
-Added RandomFileMapped class
	-Made it a wrapper for RandomAccessFile and contains maps of locations
and lengths
-Added confirmHeaderHealth method to made sure the file isnt corrupted
-Changed everything from MappedByteBuffers to RandomFileMappeds.
2021-08-25 16:15:38 -04:00
1727a2a4a1 Added Object fields to StatefulObjects and added some utility methods
- Added ObjectStateField
- Added WorldGenericContextRO.findClosestEntity, .forEachEntity
- Added VectorUtil.lookAt, .distance{,Sq}
2021-08-25 16:47:56 +03:00
20fb8f0597 Fixed missing popTrasnform in Statie render 2021-08-24 15:12:35 +03:00
1d28f32865 Implemented entity spawning and despawning and changed some stuff
- Non-player entities can now be added and removed properly
  - WorldLogic.spawnEntity can be used to add entity and create an
entity ID
- Statie is back, more beautiful than ever!
  - Place Test:StatieSpawner block and wait to make her spawn
- TPS display now features a visual tick indicator
- Updated Fern texture
2021-08-24 13:59:28 +03:00
0ccc108ddd Merge branch 'save-world'
Third time's the charm!
2021-08-24 01:38:22 +03:00
c7e7d3bdac Added ferns, fixed herb render and cleaned up TestContent
- Added Test:Fern
- Herbs are no longer stretched horizontally
- Formatted and cleaned up TestContent
2021-08-23 18:36:15 +03:00
62729f5873 Added packet buffering and fix crash when placing flowers on leaves
- Packets are now buffered before processing to reduce stack depth
- Attempts to place tiles on invalid locations get rejected earlier
2021-08-23 17:37:25 +03:00
84864f8947 Added more grass variants
- Added flat grass tiles with varying opaqueness
- Renamed Test:Grass to Test:GrassOpaque
- Added Chernozem
2021-08-23 15:18:37 +03:00
d01ef3654f Added grasses and flowers
- Added CROSimple to optimize simple non-Surface textures
- Added TileRenderCross
- Added Low, Medium and Tall grass
- Added Blue, Purple and White flat flowers
- Added Bushes
- Added tiny Dandelions and tiny Lavanders
- Improved grass and log textures
2021-08-22 22:18:42 +03:00
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
fae09edb16 Fixes n Speed n Stuff
-Better comparison for HashableVec3i
-Changed RandomAccessFile to MappedByteBuffer
-natFromInt now works properly
-better region selection
-Changes to make different strategies work
2021-08-19 10:47:16 -04:00
e4ced6507e Hash Things
-Removed unused imports
-Using HashMap to keep track of RandomAccessFile instances
-Using those instances to do stuff
-Made new HashableVec3i class that allows for hashing of 3d vectors.
2021-08-18 18:50:48 -04:00
b7dcbb0f30 Fixing stuff
-Got rid of some annoying logging
-Format
2021-08-18 13:21:54 -04:00
9c26418354 Well, it works now ig
-Better file lengthening
-Made temp files
    -Exchanged temp files for ByteArrayIn/OutputBuffers
-Fixed loadRegion to actually read sector sizes
2021-08-18 13:15:30 -04:00
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
6891d3a095 Trying to get it to work
-Uses many streams to keep track of things
-Fixed some bad things, still are a ton.
2021-08-06 12:56:21 -04:00
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
8167c40f64 IDK these errors are weird
-Changed the loading to fit the byte changes
2021-08-05 14:12:37 -04:00
8bc23acb61 idk changes so I can show OLEGSHA
-changed number of index bytes from 4 to 3
-exposed some variables for help with debugger
2021-08-05 13:39:42 -04:00
80541eafc3 Renamed BlockFace contexts into TileStack contexts 2021-08-05 19:39:39 +03:00
254faca0a5 Better deletion
-Makes new TestPlayerControls instance each time you enter a world
-Some dynamic sizing of chunk data within region files.
2021-08-05 12:29:29 -04:00
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
0c66f1751e Made outline of region file
-Improved loading screen loading(so it doesnt crash)
-Implemented region file related things, but it doesnt quite work
    -Uses file header to try to look up location of data
    -Writes the data at the end of the file(I need info to how much it writes)
2021-08-04 19:17:49 -04:00
c88dea6030 Fixing Bugs mostly
-Cleaning up imports
-Better error detection
-Actual thread deletion(still needs a bit of work to delete all conected objects)
-Starting making format file and inplementing it
2021-08-04 16:57:21 -04:00
6521cb5749 Better Listeners
-Specific listeners for the start and stop of loading
-Better(?) layer deletion after exiting a world
-Actual server thread shutdown
2021-08-04 12:52:37 -04:00
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
94db44e443 Im tired i need sleep
-Added listeners for saving and loading chunks
-Made loading screens for between title and game(they dont work yet)
-Added localized text(some)
-Safeish deletion and saving of chunks
    -It still keeps them in memory I think so this needs work too
2021-08-03 22:42:46 -04:00
53f72b068a Merge branch 'title-screen' into save-world 2021-08-03 20:08:39 -04:00
a9ca5f6b17 Everything Excpeti polishing and options
-Added LayerTitle class that is the title menu
-Edited the launcher and proxy so it only starts the game when it needs to
-Made buttons work with MutableString objects
2021-08-03 19:42:04 -04:00
4ab7cb738e Testing some stuff
-Made functions to convert integers into natural numbers (cause why not)
-Ideas/plans of dynamic/custom region file sizes
2021-08-03 17:53:49 -04:00
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
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
427 changed files with 22314 additions and 5603 deletions

2
.gitignore vendored
View File

@ -30,6 +30,8 @@ build_packages/*
!build_packages/NSIS
build_packages/NSIS/*
!build_packages/NSIS/ProgressiaInstaller.nsi
!build_packages/NSIS/logo.ico
!build_packages/NSIS/left_side.bmp
# ... and except build_packages/DEB/template
!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)
- Java 8 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
See [Build Guide](docs/building/BuildGuide.md) for compilation requirements.

View File

@ -3,17 +3,17 @@
#
# 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/>.
#
@ -24,24 +24,24 @@ buildDebianPackage() {
# Commands that must be available to execute this action
requiredCommands='dpkg-deb fakeroot'
# Package name. Sync with control file manually!
name='progressia-techdemo'
# Version that the package will receive. Sync with control file manually!
version='1.0_all'
# This directory will be copied into $tmpDir
templateDirectory="build_packages/DEB/template"
# Files that must be present
requiredFiles="$templateDirectory/DEBIAN/control"
nameAndVersion="$name-$version"
tmpDir="build_packages/DEB/$nameAndVersion"
outputFile="build_packages/DEB/$nameAndVersion.deb"
echo "Checking environment to build Debian package"
for item in $requiredCommands; do
if command -v "$item" &> /dev/null; then
echo "- $item found"
@ -50,7 +50,7 @@ buildDebianPackage() {
exit 100
fi
done
for file in $requiredFiles; do
if ! [ -r "$file" ]; then
echoerr "$file is missing or not readable, cannot package"
@ -59,13 +59,13 @@ buildDebianPackage() {
echo "- $file is present and readable"
fi
done
echo "Environment OK; packaging Debian package"
exitCode=0
{
shareDir="$tmpDir/usr/share/progressia"
mkdir -p "$tmpDir" &&
mkdir -p "$shareDir" &&
cp -r "$templateDirectory"/* "$tmpDir" &&
@ -79,7 +79,7 @@ buildDebianPackage() {
echoerr "Could not create Debian package"
exitCode=1
}
{
if [ -d "$tmpDir" ]; then
rm -r "$tmpDir"
@ -89,7 +89,7 @@ buildDebianPackage() {
echoerr "Could not clean up after packaging Debian package"
exitCode=2
}
exit "$exitCode"
}
@ -97,15 +97,15 @@ buildWindowsInstaller() {
# Commands that must be available to execute this action
requiredCommands='makensis'
# NSIS configuration file that must be present
configurationFile='build_packages/NSIS/ProgressiaInstaller.nsi'
# File that will be output
outputFile='build_packages/NSIS/ProgressiaInstaller.exe'
echo "Checking environment to build Windows installer"
for item in $requiredCommands; do
if command -v "$item" &> /dev/null; then
echo "- $item found"
@ -114,20 +114,21 @@ buildWindowsInstaller() {
exit 100
fi
done
if ! [ -r "$configurationFile" ]; then
echoerr "$configurationFile is missing or not readable, cannot build"
exit 101
else
echo "- $configurationFile is present and readable"
fi
echo "Environment OK; building Windows installer"
exitCode=0
{
cp -r 'build/libs/lib' 'build_packages/NSIS/lib' &&
cp 'build/libs/Progressia.jar' 'build_packages/NSIS/Progressia.jar' &&
cp 'LICENSE' 'build_packages/NSIS/LICENSE.txt' &&
echo "------ NSIS ------" &&
makensis "$configurationFile" &&
echo "---- NSIS END ----" &&
@ -136,7 +137,7 @@ buildWindowsInstaller() {
echoerr "Could not build Windows installer"
exitCode=1
}
{
if [ -d 'build_packages/NSIS/lib' ]; then
rm -r 'build_packages/NSIS/lib'
@ -144,12 +145,15 @@ buildWindowsInstaller() {
if [ -e 'build_packages/NSIS/Progressia.jar' ]; then
rm 'build_packages/NSIS/Progressia.jar'
fi
if [ -e 'build_packages/NSIS/LICENSE.txt' ]; then
rm 'build_packages/NSIS/LICENSE.txt'
fi
echo "Cleaned up"
} || {
echoerr "Could not clean up after building Windows installer"
exitCode=2
}
exit "$exitCode"
}

View File

@ -10,16 +10,32 @@
;--------------------------------
;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 "Progressia"
OutFile "ProgressiaInstaller.exe"
Name "${PROJECT_NAME}"
OutFile "${PROJECT_NAME}Installer.exe"
Unicode True
;Default installation folder
InstallDir "$PROGRAMFILES\Progressia"
InstallDir "$PROGRAMFILES\${PROJECT_NAME}"
;Get installation folder from registry if available
InstallDirRegKey HKLM "Software\Progressia" "Install_Dir"
InstallDirRegKey HKLM "Software\${PROJECT_NAME}" ""
;Request application privileges for Windows Vista
RequestExecutionLevel admin
@ -33,14 +49,18 @@
;Pages
!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_DIRECTORY
!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_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
@ -52,35 +72,40 @@
;--------------------------------
;Installer Sections
Section "Install Progressia" SecDummy
Section "Install ${PROJECT_NAME}" SEC0000
SectionIn RO ;Make it read-only
SetOutPath "$INSTDIR"
SetOverwrite on
;Files
File Progressia.jar
File logo.ico
File /r lib
;Store installation folder
WriteRegStr HKLM SOFTWARE\Progressia "Install_Dir" "$INSTDIR"
;Create uninstaller
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "DisplayName" "Progressia (remove only)"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "DisplayName" "${PROJECT_NAME} (remove only)"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROJECT_NAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteUninstaller "$INSTDIR\Uninstall.exe"
SectionEnd
;--------------------------------
;Descriptions
Section "Create Desktop Shortcut" SEC0001
SetOutPath "$APPDATA\${PROJECT_NAME}"
CreateShortCut "$DESKTOP\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
SectionEnd
;Language strings
LangString DESC_SecDummy ${LANG_ENGLISH} "A test section."
Section "Start Menu Shortcuts" SEC0002
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
CreateDirectory "$SMPROGRAMS\${PROJECT_NAME}"
CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
CreateShortcut "$SMPROGRAMS\${PROJECT_NAME}\${PROJECT_NAME}.lnk" "$INSTDIR\${PROJECT_NAME}.jar" "" "$INSTDIR\logo.ico"
SectionEnd
;--------------------------------
;Uninstaller Section
@ -92,9 +117,45 @@ Section "Uninstall"
Delete $INSTDIR\Uninstall.exe
Delete $INSTDIR\Progressia.jar
Delete $INSTDIR\lib\*.*
Delete $INSTDIR\logo.ico
RMDir /r "$INSTDIR"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Progressia"
DeleteRegKey HKLM "Software\Progressia"
RMDir $INSTDIR\lib
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
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

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

View File

@ -15,21 +15,31 @@
* 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;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.common.util.crash.analyzers.OutOfMemoryAnalyzer;
import ru.windcorp.progressia.common.util.crash.providers.*;
import ru.windcorp.progressia.test.LayerTitle;
public class ProgressiaLauncher {
public static String[] arguments;
private static Proxy proxy;
public static void launch(String[] args, Proxy proxy) {
arguments = args.clone();
setupCrashReports();
proxy.initialize();
ProgressiaLauncher.proxy = proxy;
GUI.addTopLayer(new LayerTitle("Title"));
}
public static Proxy getProxy() {
return proxy;
}
private static void setupCrashReports() {
@ -40,6 +50,7 @@ public class ProgressiaLauncher {
CrashReports.registerProvider(new OpenALContextProvider());
CrashReports.registerProvider(new ArgsContextProvider());
CrashReports.registerProvider(new LanguageContextProvider());
CrashReports.registerProvider(new ScreenContextProvider());
// Analyzers
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.LocalPlayer;
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;
public class Client {
@ -36,7 +36,7 @@ public class Client {
private final ServerCommsChannel comms;
public Client(WorldData world, ServerCommsChannel comms) {
public Client(DefaultWorldData world, ServerCommsChannel comms) {
this.world = new WorldRender(world, this);
this.comms = comms;

View File

@ -15,7 +15,7 @@
* 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;
import ru.windcorp.progressia.Proxy;
@ -30,14 +30,16 @@ import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.localization.Localizer;
import ru.windcorp.progressia.common.resource.ResourceManager;
import ru.windcorp.progressia.common.util.crash.CrashReports;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.TestContent;
import ru.windcorp.progressia.test.TestMusicPlayer;
public class ClientProxy implements Proxy {
@Override
public void initialize() {
GraphicsBackend.initialize();
try {
RenderTaskQueue.waitAndInvoke(FlatRenderProgram::init);
RenderTaskQueue.waitAndInvoke(WorldRenderProgram::init);
@ -57,8 +59,6 @@ public class ClientProxy implements Proxy {
AudioSystem.initialize();
ServerState.startServer();
ClientState.connectToLocalServer();
TestMusicPlayer.start();
}
}

View File

@ -15,15 +15,18 @@
* 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;
import ru.windcorp.progressia.client.comms.localhost.LocalServerCommsChannel;
import ru.windcorp.progressia.client.graphics.GUI;
import ru.windcorp.progressia.client.graphics.Layer;
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.client.localization.MutableStringLocalized;
import ru.windcorp.progressia.server.ServerState;
import ru.windcorp.progressia.test.LayerAbout;
import ru.windcorp.progressia.test.LayerTestText;
import ru.windcorp.progressia.test.LayerTestUI;
import ru.windcorp.progressia.test.TestContent;
@ -41,7 +44,7 @@ public class ClientState {
public static void connectToLocalServer() {
WorldData world = new WorldData();
DefaultWorldData world = new DefaultWorldData();
LocalServerCommsChannel channel = new LocalServerCommsChannel(
ServerState.getInstance()
@ -52,11 +55,39 @@ public class ClientState {
channel.connect(TestContent.PLAYER_LOGIN);
setInstance(client);
displayLoadingScreen();
GUI.addBottomLayer(new LayerWorld(client));
GUI.addTopLayer(new LayerTestUI());
GUI.addTopLayer(new LayerAbout());
}
private static void displayLoadingScreen() {
GUI.addTopLayer(new LayerTestText("Text", new MutableStringLocalized("LayerText.Load"), layer -> {
Client client = ClientState.getInstance();
// TODO refacetor and remove
if (client != null) {
client.getComms().processPackets();
}
if (client != null && client.getLocalPlayer().hasEntity()) {
GUI.removeLayer(layer);
// TODO refactor, this shouldn't be here
LayerWorld layerWorld = new LayerWorld(client);
LayerTestUI layerUI = new LayerTestUI();
LayerAbout layerAbout = new LayerAbout();
GUI.addBottomLayer(layerWorld);
GUI.addTopLayer(layerUI);
GUI.addTopLayer(layerAbout);
}
}));
}
public static void disconnectFromLocalServer() {
getInstance().getComms().disconnect();
for (Layer layer : GUI.getLayers()) {
GUI.removeLayer(layer);
}
}
private ClientState() {

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.SoundType;
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.ALC10.*;
@ -40,7 +41,6 @@ public class AudioManager {
private static List<Speaker> soundSpeakers = new ArrayList<>(SOUNDS_NUM);
private static Speaker musicSpeaker;
private static ArrayList<SoundType> soundsBuffer = new ArrayList<>();
public static void initAL() {
String defaultDeviceName = alcGetString(
@ -82,31 +82,19 @@ public class AudioManager {
return speaker;
}
private static SoundType findSoundType(String soundID) throws Exception {
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) {
public static Speaker initSpeaker(SoundType st) {
Speaker speaker = getLastSpeaker();
try {
findSoundType(soundID).initSpeaker(speaker);
st.initSpeaker(speaker);
} catch (Exception ex) {
throw new RuntimeException();
}
return speaker;
}
public static Speaker initMusicSpeaker(String soundID) {
public static Speaker initMusicSpeaker(SoundType st) {
try {
findSoundType(soundID).initSpeaker(musicSpeaker);
st.initSpeaker(musicSpeaker);
} catch (Exception ex) {
throw new RuntimeException();
}
@ -120,11 +108,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) {
soundsBuffer.add(AudioReader.readAsMono(path, id));
AudioRegistry.getInstance().register(AudioReader.readAsMono(resource, id));
} 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;
import ru.windcorp.progressia.common.resource.ResourceManager;
public class AudioSystem {
static public void initialize() {
AudioManager.initAL();
@ -28,7 +30,7 @@ public class AudioSystem {
static void loadAudioData() {
AudioManager.loadSound(
"assets/sounds/block_destroy_clap.ogg",
ResourceManager.getResource("assets/sounds/block_destroy_clap.ogg"),
"Progressia:BlockDestroy",
AudioFormat.MONO
);

View File

@ -19,72 +19,37 @@
package ru.windcorp.progressia.client.audio;
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.common.util.namespaces.Namespaced;
public class Music extends Namespaced {
private Vec3 position = new Vec3();
private Vec3 velocity = new Vec3();
private float pitch = 1.0f;
private float gain = 1.0f;
public class Music
extends Sound {
public Music(SoundType soundType, int timeLength, float pitch, float gain) {
super(soundType, timeLength, new Vec3(), new Vec3(), pitch, gain);
}
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) {
super(id);
}
public Music(
String id,
Vec3 position,
Vec3 velocity,
float pitch,
float gain
) {
this(id);
this.position = position;
this.velocity = velocity;
this.pitch = pitch;
this.gain = gain;
@Override
protected Speaker initSpeaker() {
return AudioManager.initMusicSpeaker(soundType);
}
public void play(boolean loop) {
Speaker speaker = AudioManager.initMusicSpeaker(this.getId());
speaker.setGain(gain);
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;
@Override
public void setPosition(Vec3 position) {
throw new UnsupportedOperationException();
}
}

View File

@ -19,23 +19,30 @@
package ru.windcorp.progressia.client.audio;
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.common.util.namespaces.Namespaced;
public class SoundEffect
extends Namespaced {
public class Sound {
private Vec3 position = new Vec3();
private Vec3 velocity = new Vec3();
private float pitch = 1.0f;
private float gain = 1.0f;
public SoundEffect(String id) {
super(id);
protected Vec3 position = new Vec3(0f, 0f, 0f);
protected Vec3 velocity = new Vec3(0f, 0f, 0f);
protected float pitch = 1.0f;
protected float gain = 1.0f;
protected int timeLength = 0;
protected SoundType soundType;
public Sound(SoundType soundType) {
this.soundType = soundType;
}
public SoundEffect(
public Sound(String id) {
this(AudioRegistry.getInstance().get(id));
}
public Sound(
String id,
int timeLength,
Vec3 position,
Vec3 velocity,
float pitch,
@ -47,9 +54,28 @@ public class SoundEffect
this.pitch = pitch;
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) {
Speaker speaker = AudioManager.initSpeaker(this.getId());
Speaker speaker = initSpeaker();
speaker.setGain(gain);
speaker.setPitch(pitch);
speaker.setPosition(position);
@ -93,4 +119,9 @@ public class SoundEffect
public float getPitch() {
return pitch;
}
public double getDuration() {
return soundType.getDuration();
}
}

View File

@ -33,13 +33,11 @@ public class AudioReader {
}
// 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 rateBuffer = BufferUtils.createIntBuffer(1);
Resource res = ResourceManager.getResource(path);
ShortBuffer rawAudio = decodeVorbis(res, channelBuffer, rateBuffer);
ShortBuffer rawAudio = decodeVorbis(resource, channelBuffer, rateBuffer);
return new SoundType(
id,
@ -49,12 +47,12 @@ public class AudioReader {
);
}
public static SoundType readAsMono(String path, String id) {
return readAsSpecified(path, id, AL_FORMAT_MONO16);
public static SoundType readAsMono(Resource resource, String id) {
return readAsSpecified(resource, id, AL_FORMAT_MONO16);
}
public static SoundType readAsStereo(String path, String id) {
return readAsSpecified(path, id, AL_FORMAT_STEREO16);
public static SoundType readAsStereo(Resource resource, String id) {
return readAsSpecified(resource, id, AL_FORMAT_STEREO16);
}
private static ShortBuffer decodeVorbis(

View File

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

View File

@ -120,6 +120,7 @@ public class Speaker {
}
public void setAudioData(int audioData) {
stop();
this.audioData = audioData;
alSourcei(this.sourceData, AL_BUFFER, audioData);
}

View File

@ -0,0 +1,53 @@
/*
* 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

@ -142,6 +142,96 @@ public class ControlTriggers {
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(
Class<I> inputType,
@ -149,6 +239,13 @@ public class ControlTriggers {
) {
return (inputEvent, control) -> dataWriter.accept(inputType.cast(inputEvent), control);
}
private static <I extends InputEvent> Consumer<InputEvent> createCheckedAction(
Class<I> inputType,
Consumer<I> action
) {
return inputEvent -> action.accept(inputType.cast(inputEvent));
}
private static <I extends InputEvent> Predicate<InputEvent> createCheckedCompoundPredicate(
Class<I> inputType,

View File

@ -15,7 +15,7 @@
* 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.localhost;
import java.io.IOException;

View File

@ -15,12 +15,13 @@
* 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.localhost;
import ru.windcorp.progressia.client.comms.ServerCommsChannel;
import ru.windcorp.progressia.common.comms.packets.Packet;
import ru.windcorp.progressia.server.Server;
import ru.windcorp.progressia.server.ServerState;
public class LocalServerCommsChannel extends ServerCommsChannel {
@ -54,7 +55,7 @@ public class LocalServerCommsChannel extends ServerCommsChannel {
@Override
public void disconnect() {
// Do nothing
ServerState.getInstance().getClientManager().disconnectClient(localClient);
}
}

View File

@ -34,7 +34,13 @@ public class Colors {
DEBUG_BLUE = toVector(0xFF0000FF),
DEBUG_CYAN = toVector(0xFF00FFFF),
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) {
return toVector(argb, new Vec4());

View File

@ -15,15 +15,17 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ru.windcorp.progressia.client.graphics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
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.FrameResizeEvent;
import ru.windcorp.progressia.client.graphics.input.InputEvent;
@ -57,15 +59,27 @@ public class GUI {
}
public static void addBottomLayer(Layer layer) {
modify(layers -> layers.add(layer));
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(layer);
layer.onAdded();
});
}
public static void addTopLayer(Layer layer) {
modify(layers -> layers.add(0, layer));
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.add(0, layer);
layer.onAdded();
});
}
public static void removeLayer(Layer layer) {
modify(layers -> layers.remove(layer));
Objects.requireNonNull(layer, "layer");
modify(layers -> {
layers.remove(layer);
layer.onRemoved();
});
}
private static void modify(LayerStackModification mod) {
@ -78,12 +92,33 @@ public class GUI {
public static void render() {
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) {
LAYERS.get(i).render();
}
}
}

View File

@ -30,15 +30,52 @@ public abstract class Layer {
private boolean hasInitialized = 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) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Layer " + name;
}
public CursorPolicy getCursorPolicy() {
return cursorPolicy;
}
public void setCursorPolicy(CursorPolicy cursorPolicy) {
this.cursorPolicy = cursorPolicy;
}
void render() {
GraphicsInterface.startNextLayer();
@ -78,5 +115,13 @@ public abstract class Layer {
protected int getHeight() {
return GraphicsInterface.getFrameHeight();
}
protected void onAdded() {
// Do nothing
}
protected void onRemoved() {
// Do nothing
}
}

View File

@ -18,11 +18,12 @@
package ru.windcorp.progressia.client.graphics.backend;
import static org.lwjgl.opengl.GL11.*;
import glm.vec._2.i.Vec2i;
import org.lwjgl.glfw.GLFWVidMode;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
public class GraphicsBackend {
@ -38,9 +39,37 @@ public class GraphicsBackend {
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 static boolean allowDisablingCursor;
static {
String key = GraphicsBackend.class.getName() + ".allowDisablingCursor";
allowDisablingCursor = Boolean.parseBoolean(System.getProperty(key, "true"));
}
private static boolean forceCursorToCenter = false;
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() {
startRenderThread();
}
@ -93,6 +122,10 @@ public class GraphicsBackend {
frameLength = now - frameStart;
frameStart = now;
}
if (forceCursorToCenter) {
glfwSetCursorPos(windowHandle, FRAME_SIZE.x / 2.0, FRAME_SIZE.y / 2.0);
}
}
static void endFrame() {
@ -128,4 +161,69 @@ public class GraphicsBackend {
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() {
if (!allowDisablingCursor) {
return forceCursorToCenter;
}
return glfwGetInputMode(windowHandle, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
}
public static void setMouseCaptured(boolean capture) {
if (!allowDisablingCursor) {
forceCursorToCenter = capture;
return;
}
int mode = capture ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
glfwSetInputMode(windowHandle, GLFW_CURSOR, mode);
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();
}
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

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

View File

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

View File

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

View File

@ -0,0 +1,156 @@
/*
* 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, Label label) {
super(name);
this.label = 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, Font labelFont) {
this(name, new Label(name + ".Label", labelFont, label));
}
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;
requestReassembly();
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,83 @@
/*
* 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, Label label) {
super(name, label);
}
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;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import glm.vec._2.i.Vec2i;
import ru.windcorp.progressia.client.graphics.backend.InputTracker;
import ru.windcorp.progressia.client.graphics.flat.RenderTarget;
import ru.windcorp.progressia.client.graphics.gui.event.ChildAddedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ChildRemovedEvent;
import ru.windcorp.progressia.client.graphics.gui.event.EnableEvent;
import ru.windcorp.progressia.client.graphics.gui.event.FocusEvent;
import ru.windcorp.progressia.client.graphics.gui.event.HoverEvent;
import ru.windcorp.progressia.client.graphics.gui.event.ParentChangedEvent;
@ -61,6 +66,8 @@ public class Component extends Named {
private Object layoutHint = null;
private Layout layout = null;
private boolean isEnabled = true;
private boolean isFocusable = false;
private boolean isFocused = false;
@ -285,9 +292,30 @@ public class Component extends Named {
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() {
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) {
this.isFocusable = focusable;
@ -337,7 +365,7 @@ public class Component extends Named {
return;
}
if (component.isFocusable()) {
if (component.canGainFocusNow()) {
setFocused(false);
component.setFocused(true);
return;
@ -379,7 +407,7 @@ public class Component extends Named {
return;
}
if (component.isFocusable()) {
if (component.canGainFocusNow()) {
setFocused(false);
component.setFocused(true);
return;
@ -432,13 +460,52 @@ public class Component extends Named {
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() {
return isHovered;
}
protected void setHovered(boolean isHovered) {
if (this.isHovered != isHovered) {
if (this.isHovered != isHovered && isEnabled()) {
this.isHovered = isHovered;
if (!isHovered && !getChildren().isEmpty()) {
@ -502,7 +569,7 @@ public class Component extends Named {
}
protected void handleInput(Input input) {
if (inputBus != null) {
if (inputBus != null && isEnabled()) {
inputBus.dispatch(input);
}
}
@ -598,6 +665,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() {
// To be overridden
}
@ -637,6 +715,135 @@ public class Component extends Named {
protected void assembleChildren(RenderTarget 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(triggerObject);
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.

View File

@ -16,18 +16,13 @@
* 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();
int getIndex();
TileDataStack getStack();
default boolean isValid() {
return get() != null;
public Group(String name, Layout layout) {
super(name);
setLayout(layout);
}
}

View File

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

View File

@ -15,14 +15,66 @@
* 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;
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) {
super(name);
setLayout(layout);
this(name, layout, Colors.WHITE, Colors.LIGHT_GRAY);
}
/**
* @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,208 @@
/*
* 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

@ -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

@ -97,16 +97,27 @@ public class LayoutGrid implements Layout {
void setBounds(int column, int row, Component child, Component parent) {
if (!isSummed)
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(
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)
: (parent.getWidth() - margin - columns[column])),
(row != (rows.length - 1) ? (rows[row + 1] - rows[row] - gap)
: (parent.getHeight() - margin - rows[row]))
width,
height
);
}
}
@ -132,10 +143,9 @@ public class LayoutGrid implements Layout {
GridDimensions grid = calculateGrid(c);
grid.sum();
int[] coords;
for (Component child : c.getChildren()) {
coords = (int[]) child.getLayoutHint();
grid.setBounds(coords[0], coords[1], child, c);
Vec2i coords = (Vec2i) child.getLayoutHint();
grid.setBounds(coords.x, coords.y, child, c);
}
}
}
@ -149,11 +159,10 @@ public class LayoutGrid implements Layout {
private GridDimensions calculateGrid(Component parent) {
GridDimensions result = new GridDimensions();
int[] coords;
for (Component child : parent.getChildren()) {
coords = (int[]) child.getLayoutHint();
result.add(coords[0], coords[1], child.getPreferredSize());
Vec2i coords = (Vec2i) child.getLayoutHint();
result.add(coords.x, coords.y, child.getPreferredSize());
}
return result;

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

@ -18,23 +18,23 @@
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 glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
class BlockFaceVectors {
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 height = outer.getHeight(face);
@ -59,36 +59,36 @@ class BlockFaceVectors {
static {
OUTER = new BlockFaceVectors(
ImmutableMap.<BlockFace, Vec3>builder()
ImmutableMap.<AbsFace, Vec3>builder()
.put(TOP, new Vec3(-0.5f, +0.5f, +0.5f))
.put(BOTTOM, new Vec3(-0.5f, -0.5f, -0.5f))
.put(NORTH, new Vec3(+0.5f, -0.5f, -0.5f))
.put(SOUTH, new Vec3(-0.5f, +0.5f, -0.5f))
.put(WEST, new Vec3(+0.5f, +0.5f, -0.5f))
.put(EAST, new Vec3(-0.5f, -0.5f, -0.5f))
.put(POS_Z, new Vec3(-0.5f, +0.5f, +0.5f))
.put(NEG_Z, new Vec3(-0.5f, -0.5f, -0.5f))
.put(POS_X, new Vec3(+0.5f, -0.5f, -0.5f))
.put(NEG_X, new Vec3(-0.5f, +0.5f, -0.5f))
.put(POS_Y, new Vec3(+0.5f, +0.5f, -0.5f))
.put(NEG_Y, new Vec3(-0.5f, -0.5f, -0.5f))
.build(),
ImmutableMap.<BlockFace, Vec3>builder()
ImmutableMap.<AbsFace, Vec3>builder()
.put(TOP, new Vec3(0, -1, 0))
.put(BOTTOM, new Vec3(0, +1, 0))
.put(NORTH, new Vec3(0, +1, 0))
.put(SOUTH, new Vec3(0, -1, 0))
.put(WEST, new Vec3(-1, 0, 0))
.put(EAST, new Vec3(+1, 0, 0))
.put(POS_Z, new Vec3(0, -1, 0))
.put(NEG_Z, new Vec3(0, +1, 0))
.put(POS_X, new Vec3(0, +1, 0))
.put(NEG_X, new Vec3(0, -1, 0))
.put(POS_Y, new Vec3(-1, 0, 0))
.put(NEG_Y, new Vec3(+1, 0, 0))
.build(),
ImmutableMap.<BlockFace, Vec3>builder()
ImmutableMap.<AbsFace, Vec3>builder()
.put(TOP, new Vec3(+1, 0, 0))
.put(BOTTOM, new Vec3(+1, 0, 0))
.put(NORTH, new Vec3(0, 0, +1))
.put(SOUTH, new Vec3(0, 0, +1))
.put(WEST, new Vec3(0, 0, +1))
.put(EAST, new Vec3(0, 0, +1))
.put(POS_Z, new Vec3(+1, 0, 0))
.put(NEG_Z, new Vec3(+1, 0, 0))
.put(POS_X, new Vec3(0, 0, +1))
.put(NEG_X, new Vec3(0, 0, +1))
.put(POS_Y, new Vec3(0, 0, +1))
.put(NEG_Y, new Vec3(0, 0, +1))
.build()
);
@ -100,29 +100,29 @@ class BlockFaceVectors {
return inner ? INNER : OUTER;
}
private final ImmutableMap<BlockFace, Vec3> origins;
private final ImmutableMap<BlockFace, Vec3> widths;
private final ImmutableMap<BlockFace, Vec3> heights;
private final ImmutableMap<AbsFace, Vec3> origins;
private final ImmutableMap<AbsFace, Vec3> widths;
private final ImmutableMap<AbsFace, Vec3> heights;
public BlockFaceVectors(
ImmutableMap<BlockFace, Vec3> origins,
ImmutableMap<BlockFace, Vec3> widths,
ImmutableMap<BlockFace, Vec3> heights
ImmutableMap<AbsFace, Vec3> origins,
ImmutableMap<AbsFace, Vec3> widths,
ImmutableMap<AbsFace, Vec3> heights
) {
this.origins = origins;
this.widths = widths;
this.heights = heights;
}
public Vec3 getOrigin(BlockFace face) {
public Vec3 getOrigin(AbsFace face) {
return origins.get(face);
}
public Vec3 getWidth(BlockFace face) {
public Vec3 getWidth(AbsFace face) {
return widths.get(face);
}
public Vec3 getHeight(BlockFace face) {
public Vec3 getHeight(AbsFace face) {
return heights.get(face);
}
}

View File

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

View File

@ -24,7 +24,7 @@ import java.util.Objects;
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;
@ -40,7 +40,7 @@ public class Face implements Comparable<Face> {
private ShortBuffer userIndices;
private boolean userIndicesUpdated = true;
public Face(
public ShapePart(
Texture texture,
ByteBuffer vertices,
ShortBuffer indices
@ -50,7 +50,7 @@ public class Face implements Comparable<Face> {
setIndices(indices);
}
public Face(
public ShapePart(
Texture texture,
ByteBuffer vertices
) {
@ -155,7 +155,7 @@ public class Face implements Comparable<Face> {
return vertices;
}
public Face setVertices(ByteBuffer vertices) {
public ShapePart setVertices(ByteBuffer vertices) {
this.vertices = Objects.requireNonNull(vertices, "vertices");
markForVertexUpdate();
return this;
@ -202,7 +202,7 @@ public class Face implements Comparable<Face> {
return userIndices.remaining();
}
public Face setIndices(ShortBuffer indices) {
public ShapePart setIndices(ShortBuffer indices) {
if (indices == null) {
indices = GENERATE_SUCCESSIVE_LATER;
}
@ -245,7 +245,7 @@ public class Face implements Comparable<Face> {
}
@Override
public int compareTo(Face o) {
public int compareTo(ShapePart o) {
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.TexturePrimitive;
public class FaceGroup {
public class ShapePartGroup {
private final TexturePrimitive texture;
private final int indexCount;
private final int byteOffsetOfIndices;
FaceGroup(Face[] faces, int start, int end) {
ShapePartGroup(ShapePart[] faces, int start, int end) {
Texture t = faces[start].getTexture();
this.texture = t == null ? null : t.getSprite().getPrimitive();
@ -36,7 +36,7 @@ public class FaceGroup {
int indexCount = 0;
for (int i = start; i < end; ++i) {
Face face = faces[i];
ShapePart face = faces[i];
assert this.texture == null
? (face.getTexture() == null)

View File

@ -25,14 +25,14 @@ import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram.VertexBuilder;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.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,
Texture texture,
Vec4 colorMultiplier,
@ -82,19 +82,19 @@ public class Faces {
}
);
return new Face(
return new ShapePart(
texture,
builder.assemble(),
buffer
);
}
public static Face createBlockFace(
public static ShapePart createBlockFace(
ShapeRenderProgram program,
Texture texture,
Vec4 colorMultiplier,
Vec3 blockCenter,
BlockFace face,
AbsFace face,
boolean inner
) {
BlockFaceVectors vectors = BlockFaceVectors.get(inner);

View File

@ -116,7 +116,7 @@ public class ShapeRenderProgram extends Program {
try {
enableAttributes();
for (FaceGroup group : shape.getGroups()) {
for (ShapePartGroup group : shape.getGroups()) {
renderFaceGroup(group);
}
} finally {
@ -182,7 +182,7 @@ public class ShapeRenderProgram extends Program {
indices.bind(BindTarget.ELEMENT_ARRAY);
}
protected void renderFaceGroup(FaceGroup group) {
protected void renderFaceGroup(ShapePartGroup group) {
TexturePrimitive texture = group.getTexture();
if (texture != null) {
@ -206,12 +206,12 @@ public class ShapeRenderProgram extends Program {
}
public void preprocess(Shape shape) {
for (Face face : shape.getFaces()) {
for (ShapePart face : shape.getParts()) {
applySprites(face);
}
}
private void applySprites(Face face) {
private void applySprites(ShapePart face) {
if (face.getTexture() == null)
return;

View File

@ -20,11 +20,13 @@ package ru.windcorp.progressia.client.graphics.model;
import java.util.Map;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Shapes {
@ -50,7 +52,7 @@ public class Shapes {
boolean flip
) {
Face top = Faces.createRectangle(
ShapePart top = ShapeParts.createRectangle(
program,
topTexture,
colorMultiplier,
@ -60,7 +62,7 @@ public class Shapes {
flip
);
Face bottom = Faces.createRectangle(
ShapePart bottom = ShapeParts.createRectangle(
program,
bottomTexture,
colorMultiplier,
@ -70,7 +72,7 @@ public class Shapes {
flip
);
Face north = Faces.createRectangle(
ShapePart north = ShapeParts.createRectangle(
program,
northTexture,
colorMultiplier,
@ -80,7 +82,7 @@ public class Shapes {
flip
);
Face south = Faces.createRectangle(
ShapePart south = ShapeParts.createRectangle(
program,
southTexture,
colorMultiplier,
@ -90,7 +92,7 @@ public class Shapes {
flip
);
Face east = Faces.createRectangle(
ShapePart east = ShapeParts.createRectangle(
program,
eastTexture,
colorMultiplier,
@ -100,7 +102,7 @@ public class Shapes {
flip
);
Face west = Faces.createRectangle(
ShapePart west = ShapeParts.createRectangle(
program,
westTexture,
colorMultiplier,
@ -165,16 +167,16 @@ public class Shapes {
public PppBuilder(
ShapeRenderProgram program,
Map<BlockFace, Texture> textureMap
Map<AbsFace, Texture> textureMap
) {
this(
program,
textureMap.get(BlockFace.TOP),
textureMap.get(BlockFace.BOTTOM),
textureMap.get(BlockFace.NORTH),
textureMap.get(BlockFace.SOUTH),
textureMap.get(BlockFace.EAST),
textureMap.get(BlockFace.WEST)
textureMap.get(AbsFace.POS_Z),
textureMap.get(AbsFace.NEG_Z),
textureMap.get(AbsFace.POS_X),
textureMap.get(AbsFace.NEG_X),
textureMap.get(AbsFace.NEG_Y),
textureMap.get(AbsFace.POS_Y)
);
}
@ -259,6 +261,34 @@ public class Shapes {
public PppBuilder setSize(float size) {
return this.setSize(size, size, size);
}
public PppBuilder centerAt(float x, float y, float z) {
origin.set(x, y, z);
origin.mul(2);
origin.sub(width);
origin.sub(height);
origin.sub(depth);
origin.div(2);
return this;
}
public PppBuilder apply(Mat4 transform) {
VectorUtil.applyMat4(origin, transform);
VectorUtil.rotateOnly(width, transform);
VectorUtil.rotateOnly(height, transform);
VectorUtil.rotateOnly(depth, transform);
return this;
}
public PppBuilder scale(float factor) {
origin.mul(factor);
width.mul(factor);
height.mul(factor);
depth.mul(factor);
return this;
}
public PppBuilder flip() {
this.flip = true;

View File

@ -38,10 +38,6 @@ public class StaticModel extends Model {
this.transforms = transforms;
}
public StaticModel(Builder builder) {
this(builder.getParts(), builder.getTransforms());
}
@Override
protected Mat4 getTransform(int partIndex) {
return transforms[partIndex];
@ -82,6 +78,10 @@ public class StaticModel extends Model {
private Mat4[] getTransforms() {
return transforms.toArray(new Mat4[transforms.size()]);
}
public StaticModel build() {
return new StaticModel(getParts(), getTransforms());
}
}

View File

@ -21,7 +21,7 @@ package ru.windcorp.progressia.client.graphics.texture;
import java.util.Map;
import glm.vec._2.Vec2;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class ComplexTexture {
@ -54,14 +54,14 @@ public class ComplexTexture {
);
}
public Map<BlockFace, Texture> getCuboidTextures(
public Map<AbsFace, Texture> getCuboidTextures(
int x,
int y,
int width,
int height,
int depth
) {
return BlockFace.mapToFaces(
return AbsFace.mapToFaces(
get(
x + depth + width,
y + height + depth,
@ -86,7 +86,7 @@ public class ComplexTexture {
);
}
public Map<BlockFace, Texture> getCuboidTextures(
public Map<AbsFace, Texture> getCuboidTextures(
int x,
int y,
int size

View File

@ -29,6 +29,9 @@ import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode;
import ru.windcorp.progressia.client.world.entity.NPedModel;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
public class Camera {
@ -60,13 +63,13 @@ public class Camera {
}
}
void getCameraPosition(Vec3 output);
Vec3 getCameraPosition(Vec3 output);
void getCameraVelocity(Vec3 output);
Vec3 getCameraVelocity(Vec3 output);
float getCameraYaw();
float getCameraPitch();
Vec3 getLookingAt(Vec3 output);
Vec3 getUpVector(Vec3 output);
Collection<Mode> getCameraModes();
@ -84,14 +87,11 @@ public class Camera {
*/
private final Vec3 lastAnchorPosition = new Vec3();
private float lastAnchorYaw;
private float lastAnchorPitch;
private final Vec3 lastAnchorLookingAt = new Vec3();
private final Vec3 lastAnchorUpVector = new Vec3();
private final Mat4 lastCameraMatrix = new Mat4();
private final Vec3 lastAnchorLookingAt = new Vec3();
private final Vec3 lastAnchorUp = new Vec3();
{
invalidateCache();
}
@ -108,6 +108,9 @@ public class Camera {
*/
public void apply(WorldRenderHelper helper) {
if (NPedModel.flag) {
// System.out.println("Camera.apply()");
}
applyPerspective(helper);
rotateCoordinateSystem(helper);
@ -149,26 +152,34 @@ public class Camera {
}
private void applyDirection(WorldRenderHelper helper) {
float pitch = anchor.getCameraPitch();
float yaw = anchor.getCameraYaw();
anchor.getLookingAt(lastAnchorLookingAt);
anchor.getUpVector(lastAnchorUpVector);
helper.pushViewTransform()
.rotateY(-pitch)
.rotateZ(-yaw);
lookAt(helper.pushViewTransform());
}
this.lastAnchorYaw = yaw;
this.lastAnchorPitch = pitch;
this.lastAnchorLookingAt.set(
cos(pitch) * cos(yaw),
cos(pitch) * sin(yaw),
sin(pitch)
);
this.lastAnchorUp.set(
cos(pitch + PI_F / 2) * cos(yaw),
cos(pitch + PI_F / 2) * sin(yaw),
sin(pitch + PI_F / 2)
private void lookAt(Mat4 result) {
Vec3 f = this.lastAnchorLookingAt;
Vec3 s = Vectors.grab3();
Vec3 u = Vectors.grab3();
f.cross(this.lastAnchorUpVector, s);
s.normalize();
s.cross(f, u);
Mat4 workspace = Matrices.grab4();
workspace.set(
+f.x, -s.x, +u.x, 0,
+f.y, -s.y, +u.y, 0,
+f.z, -s.z, +u.z, 0,
0, 0, 0, 1
);
result.mul(workspace);
Matrices.release(workspace);
Vectors.release(s);
Vectors.release(u);
}
private void applyPosition(WorldRenderHelper helper) {
@ -247,8 +258,6 @@ public class Camera {
private void invalidateCache() {
this.lastAnchorPosition.set(Float.NaN);
this.lastAnchorYaw = Float.NaN;
this.lastAnchorPitch = Float.NaN;
this.lastCameraMatrix.set(
Float.NaN,
@ -270,7 +279,7 @@ public class Camera {
);
this.lastAnchorLookingAt.set(Float.NaN);
this.lastAnchorUp.set(Float.NaN);
this.lastAnchorUpVector.set(Float.NaN);
}
public Anchor.Mode getMode() {
@ -289,14 +298,6 @@ public class Camera {
return currentModeIndex;
}
public float getLastAnchorYaw() {
return lastAnchorYaw;
}
public float getLastAnchorPitch() {
return lastAnchorPitch;
}
public Vec3 getLastAnchorPosition() {
return lastAnchorPosition;
}
@ -310,7 +311,7 @@ public class Camera {
}
public Vec3 getLastAnchorUp() {
return lastAnchorUp;
return lastAnchorUpVector;
}
}

View File

@ -59,24 +59,32 @@ public class EntityAnchor implements Anchor {
}
@Override
public void getCameraPosition(Vec3 output) {
public Vec3 getCameraPosition(Vec3 output) {
if (output == null) output = new Vec3();
model.getViewPoint(output);
output.add(entity.getPosition());
output.add(model.getPosition());
return output;
}
@Override
public void getCameraVelocity(Vec3 output) {
public Vec3 getCameraVelocity(Vec3 output) {
if (output == null) output = new Vec3();
output.set(entity.getVelocity());
return output;
}
@Override
public float getCameraYaw() {
return entity.getYaw();
public Vec3 getLookingAt(Vec3 output) {
if (output == null) output = new Vec3();
model.getLookingAt(output);
return output;
}
@Override
public float getCameraPitch() {
return entity.getPitch();
public Vec3 getUpVector(Vec3 output) {
if (output == null) output = new Vec3();
model.getUpVector(output);
return output;
}
@Override

View File

@ -41,6 +41,8 @@ import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.collision.Collideable;
import ru.windcorp.progressia.common.collision.colliders.Collider;
import ru.windcorp.progressia.common.util.FloatMathUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.GravityModel;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.test.CollisionModelRenderer;
import ru.windcorp.progressia.test.TestPlayerControls;
@ -57,6 +59,8 @@ public class LayerWorld extends Layer {
super("World");
this.client = client;
this.inputBasedControls = new InputBasedControls(client);
setCursorPolicy(CursorPolicy.FORBID);
}
@Override
@ -72,6 +76,8 @@ public class LayerWorld extends Layer {
@Override
protected void doRender() {
client.getComms().processPackets();
Camera camera = client.getCamera();
if (camera.hasAnchor()) {
renderWorld();
@ -188,7 +194,7 @@ public class LayerWorld extends Layer {
);
}
return new StaticModel(b);
return b.build();
}
private static final float FRICTION_COEFF = Units.get("1e-5f kg/s");
@ -197,16 +203,25 @@ public class LayerWorld extends Layer {
entity.getVelocity().mul((float) Math.exp(-FRICTION_COEFF / entity.getCollisionMass() * tickLength));
}
private static final float MC_g = Units.get("32 m/s^2");
private static final float IRL_g = Units.get("9.8 m/s^2");
private void tmp_applyGravity(EntityData entity, float tickLength) {
GravityModel gm = ClientState.getInstance().getWorld().getData().getGravityModel();
Vec3 upVector = Vectors.grab3();
gm.getUp(entity.getPosition(), upVector);
entity.changeUpVector(upVector);
Vectors.release(upVector);
if (ClientState.getInstance().getLocalPlayer().getEntity() == entity && tmp_testControls.isFlying()) {
return;
}
final float gravitationalAcceleration = tmp_testControls.useMinecraftGravity() ? MC_g : IRL_g;
entity.getVelocity().add(0, 0, -gravitationalAcceleration * tickLength);
Vec3 gravitationalAcceleration = Vectors.grab3();
gm.getGravity(entity.getPosition(), gravitationalAcceleration);
gravitationalAcceleration.mul(tickLength);
entity.getVelocity().add(gravitationalAcceleration);
Vectors.release(gravitationalAcceleration);
}
@Override

View File

@ -23,13 +23,13 @@ import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.world.WorldRender;
import ru.windcorp.progressia.common.world.BlockRay;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class Selection {
private final Vec3i block = new Vec3i();
private BlockFace surface = null;
private AbsFace surface = null;
private final Vec2 pointOnSurface = new Vec2(0.5f, 0.5f);
private final Vec3 point = new Vec3();
@ -38,10 +38,9 @@ public class Selection {
private BlockRay ray = new BlockRay();
public void update(WorldRender world, EntityData player) {
Vec3 direction = new Vec3();
Vec3 start = new Vec3();
player.getLookingAtVector(direction);
Vec3 direction = player.getLookingAt();
world.getEntityRenderable(player).getViewPoint(start);
start.add(player.getPosition());
@ -71,7 +70,7 @@ public class Selection {
return exists ? point : null;
}
public BlockFace getSurface() {
public AbsFace getSurface() {
return exists ? surface : null;
}

View File

@ -33,7 +33,7 @@ import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.backend.VertexBufferObject;
import ru.windcorp.progressia.client.graphics.backend.shaders.attributes.*;
import ru.windcorp.progressia.client.graphics.backend.shaders.uniforms.*;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
@ -138,12 +138,12 @@ public class WorldRenderProgram extends ShapeRenderProgram {
public void preprocess(Shape shape) {
super.preprocess(shape);
for (Face face : shape.getFaces()) {
for (ShapePart face : shape.getParts()) {
computeNormals(face);
}
}
private void computeNormals(Face face) {
private void computeNormals(ShapePart face) {
Vec3 a = Vectors.grab3();
Vec3 b = Vectors.grab3();
Vec3 c = Vectors.grab3();
@ -183,7 +183,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
normal.normalize();
}
private void loadVertexPosition(Face face, int index, Vec3 result) {
private void loadVertexPosition(ShapePart face, int index, Vec3 result) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex();
@ -194,7 +194,7 @@ public class WorldRenderProgram extends ShapeRenderProgram {
);
}
private void saveVertexNormal(Face face, int index, Vec3 normal) {
private void saveVertexNormal(ShapePart face, int index, Vec3 normal) {
ByteBuffer vertices = face.getVertices();
int offset = vertices.position() + index * getBytesPerVertex() + (3 * Float.BYTES +
4 * Float.BYTES +

View File

@ -18,57 +18,52 @@
package ru.windcorp.progressia.client.world;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Model;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.StaticModel;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.StaticModel.Builder;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.block.BlockRenderNone;
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSupplier;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizers;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.TileDataReference;
import ru.windcorp.progressia.common.world.TileDataStack;
import ru.windcorp.progressia.common.world.generic.ChunkGenericRO;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRender
implements GenericChunk<ChunkRender, BlockRender, TileRender, TileRenderStack> {
implements ChunkGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
private final WorldRender world;
private final ChunkData data;
private final DefaultChunkData data;
private Model model = null;
private final ChunkRenderModel model;
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
.synchronizedMap(new WeakHashMap<>());
public ChunkRender(WorldRender world, ChunkData data) {
public ChunkRender(WorldRender world, DefaultChunkData data) {
this.world = world;
this.data = data;
this.model = new ChunkRenderModel(this);
}
@Override
public Vec3i getPosition() {
return getData().getPosition();
}
@Override
public AbsFace getUp() {
return getData().getUp();
}
@Override
public BlockRender getBlock(Vec3i posInChunk) {
@ -98,177 +93,45 @@ public class ChunkRender
return world;
}
public ChunkData getData() {
public DefaultChunkData getData() {
return data;
}
public synchronized void markForUpdate() {
public void markForUpdate() {
getWorld().markChunkForUpdate(getPosition());
}
public synchronized void render(ShapeRenderHelper renderer) {
if (model == null) {
return;
}
renderer.pushTransform().translate(
data.getX() * ChunkData.BLOCKS_PER_CHUNK,
data.getY() * ChunkData.BLOCKS_PER_CHUNK,
data.getZ() * ChunkData.BLOCKS_PER_CHUNK
);
model.render(renderer);
renderer.popTransform();
}
public synchronized void update() {
Collection<ChunkRenderOptimizer> optimizers = ChunkRenderOptimizers.getAllSuppliers().stream()
.map(ChunkRenderOptimizerSupplier::createOptimizer)
.collect(Collectors.toList());
optimizers.forEach(bro -> bro.startRender(this));
StaticModel.Builder builder = StaticModel.builder();
Vec3i cursor = new Vec3i();
for (int x = 0; x < ChunkData.BLOCKS_PER_CHUNK; ++x) {
for (int y = 0; y < ChunkData.BLOCKS_PER_CHUNK; ++y) {
for (int z = 0; z < ChunkData.BLOCKS_PER_CHUNK; ++z) {
cursor.set(x, y, z);
buildBlock(cursor, optimizers, builder);
buildBlockTiles(cursor, optimizers, builder);
}
}
}
optimizers.stream()
.map(ChunkRenderOptimizer::endRender)
.filter(Objects::nonNull)
.forEach(builder::addPart);
model = new StaticModel(builder);
}
private void buildBlock(
Vec3i cursor,
Collection<ChunkRenderOptimizer> optimizers,
Builder builder
) {
BlockRender block = getBlock(cursor);
if (block instanceof BlockRenderNone) {
return;
}
forwardBlockToOptimizers(block, cursor, optimizers);
if (!block.needsOwnRenderable()) {
return;
}
addBlockRenderable(block, cursor, builder);
}
private void forwardBlockToOptimizers(
BlockRender block,
Vec3i cursor,
Collection<ChunkRenderOptimizer> optimizers
) {
optimizers.forEach(cro -> cro.processBlock(block, cursor));
}
private void addBlockRenderable(
BlockRender block,
Vec3i cursor,
Builder builder
) {
Renderable renderable = block.createRenderable();
if (renderable == null) {
renderable = block::render;
}
builder.addPart(
renderable,
new Mat4().identity().translate(cursor.x, cursor.y, cursor.z)
);
}
private void buildBlockTiles(
Vec3i cursor,
Collection<ChunkRenderOptimizer> optimizers,
Builder builder
) {
for (BlockFace face : BlockFace.getFaces()) {
buildFaceTiles(cursor, face, optimizers, builder);
}
}
private void buildFaceTiles(
Vec3i cursor,
BlockFace face,
Collection<ChunkRenderOptimizer> optimizers,
Builder builder
) {
List<TileData> tiles = getData().getTilesOrNull(cursor, face);
if (tiles == null) {
return;
}
for (int layer = 0; layer < tiles.size(); ++layer) {
if (tiles.get(layer) == null) {
System.out.println(tiles.get(layer).getId());
}
buildTile(
cursor,
face,
TileRenderRegistry.getInstance().get(
tiles.get(layer).getId()
),
layer,
optimizers,
builder
);
}
}
private void buildTile(
Vec3i cursor,
BlockFace face,
TileRender tile,
int layer,
Collection<ChunkRenderOptimizer> optimizers,
Builder builder
) {
// TODO implement
Vec3 pos = new Vec3(cursor.x, cursor.y, cursor.z);
optimizers.forEach(cro -> cro.processTile(tile, cursor, face));
if (!tile.needsOwnRenderable())
return;
Vec3 offset = new Vec3(
face.getVector().x,
face.getVector().y,
face.getVector().z
);
pos.add(offset.mul(1f / 64));
builder.addPart(
tile.createRenderable(face),
new Mat4().identity().translate(pos)
);
model.update();
}
private class TileRenderStackImpl extends TileRenderStack {
private class TileRenderReferenceImpl implements TileRenderReference {
private final TileDataReference parent;
public TileRenderReferenceImpl(TileDataReference parent) {
this.parent = parent;
}
@Override
public TileRender get() {
return TileRenderRegistry.getInstance().get(parent.get().getId());
}
@Override
public int getIndex() {
return parent.getIndex();
}
@Override
public TileRenderStack getStack() {
return TileRenderStackImpl.this;
}
}
private final TileDataStack parent;
@ -287,9 +150,24 @@ public class ChunkRender
}
@Override
public BlockFace getFace() {
public RelFace getFace() {
return parent.getFace();
}
@Override
public TileRenderReference getReference(int index) {
return new TileRenderReferenceImpl(parent.getReference(index));
}
@Override
public int getIndexByTag(int tag) {
return parent.getIndexByTag(tag);
}
@Override
public int getTagByIndex(int index) {
return parent.getTagByIndex(index);
}
@Override
public TileRender get(int index) {

View File

@ -0,0 +1,167 @@
/*
* 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.world;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import glm.mat._4.Mat4;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Model;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.StaticModel;
import ru.windcorp.progressia.client.graphics.model.StaticModel.Builder;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.block.BlockRenderNone;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderNone;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.GenericChunks;
import ru.windcorp.progressia.common.world.rels.AxisRotations;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRenderModel implements Renderable {
private final ChunkRender chunk;
private final Collection<ChunkRenderOptimizer> optimizers = new ArrayList<>();
private Model model = null;
public ChunkRenderModel(ChunkRender chunk) {
this.chunk = chunk;
}
@Override
public void render(ShapeRenderHelper renderer) {
if (model == null) return;
float offset = DefaultChunkData.BLOCKS_PER_CHUNK / 2 - 0.5f;
renderer.pushTransform().translate(
chunk.getX() * DefaultChunkData.BLOCKS_PER_CHUNK,
chunk.getY() * DefaultChunkData.BLOCKS_PER_CHUNK,
chunk.getZ() * DefaultChunkData.BLOCKS_PER_CHUNK
).translate(offset, offset, offset)
.mul(AxisRotations.getResolutionMatrix4(chunk.getUp()))
.translate(-offset, -offset, -offset);
model.render(renderer);
renderer.popTransform();
}
public void update() {
setupCROs();
StaticModel.Builder sink = StaticModel.builder();
optimizers.forEach(ChunkRenderOptimizer::startRender);
GenericChunks.forEachBiC(relBlockInChunk -> {
processBlockAndTiles(relBlockInChunk, sink);
});
for (ChunkRenderOptimizer optimizer : optimizers) {
Renderable renderable = optimizer.endRender();
if (renderable != null) {
sink.addPart(renderable);
}
}
this.model = sink.build();
this.optimizers.clear();
}
private void setupCROs() {
Set<String> ids = ChunkRenderOptimizerRegistry.getInstance().keySet();
for (String id : ids) {
ChunkRenderOptimizer optimizer = ChunkRenderOptimizerRegistry.getInstance().create(id);
optimizer.setup(chunk);
this.optimizers.add(optimizer);
}
}
private void processBlockAndTiles(Vec3i relBlockInChunk, Builder sink) {
processBlock(relBlockInChunk, sink);
for (RelFace face : RelFace.getFaces()) {
processTileStack(relBlockInChunk, face, sink);
}
}
private void processBlock(Vec3i relBlockInChunk, Builder sink) {
BlockRender block = chunk.getBlockRel(relBlockInChunk);
if (block instanceof BlockRenderNone) {
return;
}
if (block.needsOwnRenderable()) {
sink.addPart(
block.createRenderable(chunk.getData(), relBlockInChunk),
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
);
}
processBlockWithCROs(block, relBlockInChunk);
}
private void processBlockWithCROs(BlockRender block, Vec3i relBlockInChunk) {
for (ChunkRenderOptimizer optimizer : optimizers) {
optimizer.addBlock(block, relBlockInChunk);
}
}
private void processTileStack(Vec3i relBlockInChunk, RelFace face, Builder sink) {
TileRenderStack trs = chunk.getTilesOrNullRel(relBlockInChunk, face);
if (trs == null || trs.isEmpty()) {
return;
}
trs.forEach(tile -> processTile(tile, relBlockInChunk, face, sink));
}
private void processTile(TileRender tile, Vec3i relBlockInChunk, RelFace face, Builder sink) {
if (tile instanceof TileRenderNone) {
return;
}
if (tile.needsOwnRenderable()) {
sink.addPart(
tile.createRenderable(chunk.getData(), relBlockInChunk, face),
new Mat4().identity().translate(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z)
);
}
processTileWithCROs(tile, relBlockInChunk, face);
}
private void processTileWithCROs(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
for (ChunkRenderOptimizer optimizer : optimizers) {
optimizer.addTile(tile, relBlockInChunk, face);
}
}
}

View File

@ -18,8 +18,15 @@
package ru.windcorp.progressia.client.world;
import ru.windcorp.progressia.common.world.ChunkData;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListener;
import ru.windcorp.progressia.common.world.block.BlockData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
import ru.windcorp.progressia.common.world.tile.TileData;
class ChunkUpdateListener implements ChunkDataListener {
@ -30,8 +37,64 @@ class ChunkUpdateListener implements ChunkDataListener {
}
@Override
public void onChunkChanged(ChunkData chunk) {
public void onChunkChanged(DefaultChunkData chunk) {
world.getChunk(chunk).markForUpdate();
}
@Override
public void onChunkLoaded(DefaultChunkData chunk) {
Vec3i cursor = new Vec3i();
for (AbsFace face : AbsFace.getFaces()) {
cursor.set(chunk.getX(), chunk.getY(), chunk.getZ());
cursor.add(face.getVector());
world.markChunkForUpdate(cursor);
}
}
@Override
public void onChunkBlockChanged(DefaultChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) {
onLocationChanged(chunk, blockInChunk);
}
@Override
public void onChunkTilesChanged(
DefaultChunkData chunk,
Vec3i blockInChunk,
RelFace face,
TileData tile,
boolean wasAdded
) {
onLocationChanged(chunk, blockInChunk);
}
private void onLocationChanged(DefaultChunkData chunk, Vec3i blockInChunk) {
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.X);
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Y);
checkCoordinate(blockInChunk, chunkPos, VectorUtil.Axis.Z);
Vectors.release(chunkPos);
}
private void checkCoordinate(Vec3i blockInChunk, Vec3i chunkPos, VectorUtil.Axis axis) {
int block = VectorUtil.get(blockInChunk, axis);
int diff = 0;
if (block == 0) {
diff = -1;
} else if (block == DefaultChunkData.BLOCKS_PER_CHUNK - 1) {
diff = +1;
} else {
return;
}
int previousChunkPos = VectorUtil.get(chunkPos, axis);
VectorUtil.set(chunkPos, axis, previousChunkPos + diff);
world.markChunkForUpdate(chunkPos);
VectorUtil.set(chunkPos, axis, previousChunkPos);
}
}

View File

@ -34,57 +34,58 @@ import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry;
import ru.windcorp.progressia.client.world.entity.EntityRenderable;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderReference;
import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListeners;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
import ru.windcorp.progressia.common.world.WorldDataListener;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.ChunkSet;
import ru.windcorp.progressia.common.world.generic.ChunkSets;
import ru.windcorp.progressia.common.world.generic.GenericWorld;
import ru.windcorp.progressia.common.world.generic.WorldGenericRO;
public class WorldRender
implements GenericWorld<BlockRender, TileRender, TileRenderStack, ChunkRender, EntityRenderable> {
implements WorldGenericRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender, EntityRenderable> {
private final WorldData data;
private final DefaultWorldData data;
private final Client client;
private final Map<ChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
private final Map<DefaultChunkData, ChunkRender> chunks = Collections.synchronizedMap(new HashMap<>());
private final Map<EntityData, EntityRenderable> entityModels = Collections.synchronizedMap(new WeakHashMap<>());
private final ChunkSet chunksToUpdate = ChunkSets.newSyncHashSet();
public WorldRender(WorldData data, Client client) {
public WorldRender(DefaultWorldData data, Client client) {
this.data = data;
this.client = client;
data.addListener(ChunkDataListeners.createAdder(new ChunkUpdateListener(this)));
data.addListener(new WorldDataListener() {
@Override
public void onChunkLoaded(WorldData world, ChunkData chunk) {
public void onChunkLoaded(DefaultWorldData world, DefaultChunkData chunk) {
addChunk(chunk);
}
@Override
public void beforeChunkUnloaded(WorldData world, ChunkData chunk) {
public void beforeChunkUnloaded(DefaultWorldData world, DefaultChunkData chunk) {
removeChunk(chunk);
}
});
}
protected void addChunk(ChunkData chunk) {
protected void addChunk(DefaultChunkData chunk) {
chunks.put(chunk, new ChunkRender(WorldRender.this, chunk));
markChunkForUpdate(chunk.getPosition());
}
protected void removeChunk(ChunkData chunk) {
protected void removeChunk(DefaultChunkData chunk) {
chunks.remove(chunk);
}
public WorldData getData() {
public DefaultWorldData getData() {
return data;
}
@ -92,7 +93,7 @@ public class WorldRender
return client;
}
public ChunkRender getChunk(ChunkData chunkData) {
public ChunkRender getChunk(DefaultChunkData chunkData) {
return chunks.get(chunkData);
}
@ -110,6 +111,13 @@ public class WorldRender
public Collection<EntityRenderable> getEntities() {
return entityModels.values();
}
@Override
public EntityRenderable getEntity(long entityId) {
EntityData entityData = getData().getEntity(entityId);
if (entityData == null) return null;
return getEntityRenderable(entityData);
}
public void render(ShapeRenderHelper renderer) {
updateChunks();

View File

@ -18,24 +18,19 @@
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.generic.GenericBlock;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.BlockGeneric;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable;
public abstract class BlockRender extends Namespaced implements GenericBlock {
public abstract class BlockRender extends Namespaced implements BlockGeneric {
public BlockRender(String id) {
super(id);
}
public void render(ShapeRenderHelper renderer) {
throw new UnsupportedOperationException(
"BlockRender.render() not implemented in " + this
);
}
public Renderable createRenderable() {
public Renderable createRenderable(DefaultChunkData chunk, Vec3i relBlockInChunk) {
return null;
}

View File

@ -18,8 +18,10 @@
package ru.windcorp.progressia.client.world.block;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.world.DefaultChunkData;
public class BlockRenderNone extends BlockRender {
@ -28,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
}
@Override
public Renderable createRenderable() {
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
return EmptyModel.getInstance();
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
@ -29,8 +29,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture eastTexture,
Texture westTexture
Texture westTexture,
Texture eastTexture
) {
super(
id,
@ -38,8 +38,8 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
bottomTexture,
northTexture,
southTexture,
eastTexture,
westTexture
westTexture,
eastTexture
);
}
@ -54,9 +54,21 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
texture
);
}
public BlockRenderOpaqueCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
this(
id,
topTexture,
bottomTexture,
sideTexture,
sideTexture,
sideTexture,
sideTexture
);
}
@Override
public boolean isOpaque(BlockFace face) {
public boolean isOpaque(RelFace face) {
return true;
}
@ -65,9 +77,4 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
return true;
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -18,23 +18,33 @@
package ru.windcorp.progressia.client.world.block;
import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import static ru.windcorp.progressia.common.world.rels.AbsFace.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import ru.windcorp.progressia.client.graphics.model.Shapes;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerCube.OpaqueCube;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.BlockOptimizedSurface;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public abstract class BlockRenderTexturedCube
extends BlockRender
implements OpaqueCube {
implements BlockOptimizedSurface {
private final Map<BlockFace, Texture> textures = new HashMap<>();
private final Map<RelFace, Texture> textures;
public BlockRenderTexturedCube(
String id,
@ -42,35 +52,68 @@ public abstract class BlockRenderTexturedCube
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture eastTexture,
Texture westTexture
Texture westTexture,
Texture eastTexture
) {
super(id);
this.textures = RelFace.mapToFaces(topTexture, bottomTexture, northTexture, southTexture, westTexture, eastTexture);
}
textures.put(TOP, topTexture);
textures.put(BOTTOM, bottomTexture);
textures.put(NORTH, northTexture);
textures.put(SOUTH, southTexture);
textures.put(EAST, eastTexture);
textures.put(WEST, westTexture);
public Texture getTexture(RelFace blockFace) {
return textures.get(blockFace);
}
public Vec4 getColorMultiplier(RelFace blockFace) {
return Colors.WHITE;
}
@Override
public Texture getTexture(BlockFace face) {
return textures.get(face);
public final void getShapeParts(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Vec3 offset
) {
output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset));
}
@Override
public Renderable createRenderable() {
return new Shapes.PppBuilder(
private ShapePart createFace(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
boolean inner,
Vec3 offset
) {
return ShapeParts.createBlockFace(
WorldRenderProgram.getDefault(),
getTexture(TOP),
getTexture(BOTTOM),
getTexture(NORTH),
getTexture(SOUTH),
getTexture(EAST),
getTexture(WEST)
).create();
getTexture(blockFace),
getColorMultiplier(blockFace),
offset,
blockFace.resolve(AbsFace.POS_Z),
inner
);
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk) {
boolean opaque = isBlockOpaque();
ShapePart[] faces = new ShapePart[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)];
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
faces[i] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), false, Vectors.ZERO_3);
}
if (!opaque) {
for (int i = 0; i < BLOCK_FACE_COUNT; ++i) {
faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, RelFace.getFaces().get(i), true, Vectors.ZERO_3);
}
}
return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(), faces);
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
@ -29,8 +29,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
Texture bottomTexture,
Texture northTexture,
Texture southTexture,
Texture eastTexture,
Texture westTexture
Texture westTexture,
Texture eastTexture
) {
super(
id,
@ -38,8 +38,8 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
bottomTexture,
northTexture,
southTexture,
eastTexture,
westTexture
westTexture,
eastTexture
);
}
@ -54,9 +54,21 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
texture
);
}
public BlockRenderTransparentCube(String id, Texture topTexture, Texture bottomTexture, Texture sideTexture) {
this(
id,
topTexture,
bottomTexture,
sideTexture,
sideTexture,
sideTexture,
sideTexture
);
}
@Override
public boolean isOpaque(BlockFace face) {
public boolean isOpaque(RelFace face) {
return false;
}
@ -65,9 +77,4 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
return false;
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -15,31 +15,123 @@
* 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.world.cro;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.rels.RelFace;
public abstract class ChunkRenderOptimizer {
/**
* Chunk render optimizer (CRO) is an object that produces optimized models for
* chunks. CROs are sequentially given information about the blocks and tiles of
* a particular chunk, after which they are expected to produce a set of
* {@link Renderable}s. As the name suggests, CROs are primarily expected to
* output models that are optimized compared to models of individual blocks and
* tiles. An example of a CRO is {@link ChunkRenderOptimizerSurface}: it removes
* block surfaces and tiles that it knows cannot be seen, thus significantly
* reducing total polygon count.
* <p>
* As with everything related to rendering chunks, CROs are interacted with
* using the relative local chunk coordinate system. In this coordinate system,
* the coordinates are the chunk coordinates relativized using the chunks's up
* direction. In simpler terms, coordinates are {@code [0; BLOCKS_PER_CHUNK)}
* and Z is always up.
* <h3>CRO lifecycle</h3>
* A CRO instance is created by {@link ChunkRenderOptimizerRegistry}. It may
* then be used to work on multiple chunks sequentially. Each chunk is processed
* in the following way:
* <ol>
* <li>{@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender}
* instance.</li>
* <li>{@link #startRender()} is invoked. The CRO must reset its state.</li>
* <li>{@link #addBlock(BlockRender, Vec3i)} and
* {@link #addTile(TileRender, Vec3i, RelFace)} are invoked for each block and
* tile that this CRO should optimize. {@code addTile} specifies tiles in order
* of ascension within a tile stack.</li>
* <li>{@link #endRender()} is invoked. The CRO may perform any pending
* calculations. The result of the optimization is returned.</li>
* </ol>
* <p>
* Each CRO instance is accessed by a single thread.
*/
public abstract class ChunkRenderOptimizer extends Namespaced {
public abstract void startRender(ChunkRender chunk);
/**
* The chunk that this CRO is currently working on.
*/
protected ChunkRender chunk = null;
public abstract void processBlock(
BlockRender block,
Vec3i posInChunk
);
/**
* Creates a new CRO instance with the specified ID.
*
* @param id the ID of this CRO
*/
public ChunkRenderOptimizer(String id) {
super(id);
}
public abstract void processTile(
TileRender tile,
Vec3i posInChunk,
BlockFace face
);
/**
* This method is invoked before a new chunk processing cycle begins to
* specify the chunk. When overriding, {@code super.setup(chunk)} must be
* invoked.
*
* @param chunk the chunk that will be processed next
*/
public void setup(ChunkRender chunk) {
this.chunk = chunk;
}
public abstract Shape endRender();
/**
* @return the chunk that this CRO is currently working on
*/
public ChunkRender getChunk() {
return chunk;
}
/**
* Resets this CRO to a state in which a new chunk may be processed.
*/
public abstract void startRender();
/**
* Requests that this CRO processes the provided block. This method may only
* be invoked between {@link #startRender()} and {@link #endRender()}. This
* method is only invoked once per block. This method is not necessarily
* invoked for each block.
*
* @param block a {@link BlockRender} instance describing the
* block.
* It corresponds to
* {@code getChunk().getBlock(blockInChunk)}.
* @param relBlockInChunk the relative position of the block
*/
public abstract void addBlock(BlockRender block, Vec3i relBlockInChunk);
/**
* Requests that this CRO processes the provided tile. This method may only
* be invoked between {@link #startRender()} and {@link #endRender()}. This
* method is only invoked once per tile. This method is not necessarily
* invoked for each tile. When multiple tiles in a tile stack are requested,
* this method is invoked for lower tiles first.
*
* @param tile a {@link BlockRender} instance describing the tile
* @param relBlockInChunk the relative position of the block that the tile
* belongs to
* @param blockFace the face that the tile belongs to
*/
public abstract void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace);
/**
* Requests that the CRO assembles and outputs its model. This method may
* only be invoked after {@link #startRender()}.
*
* @return the assembled {@link Renderable}.
*/
public abstract Renderable endRender();
}

View File

@ -1,283 +0,0 @@
/*
* 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.world.cro;
import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT;
import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Face;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
public static interface OpaqueCube {
public Texture getTexture(BlockFace face);
public boolean isOpaque(BlockFace face);
public boolean isBlockOpaque();
}
public static interface OpaqueSurface {
public Texture getTexture(BlockFace face);
public boolean isOpaque(BlockFace face);
}
private static class BlockInfo {
OpaqueCube block;
final FaceInfo[] faces = new FaceInfo[BLOCK_FACE_COUNT];
{
for (int i = 0; i < faces.length; ++i) {
faces[i] = new FaceInfo();
}
}
}
private static class FaceInfo {
static final int NO_OPAQUE_TILES = -1;
int topOpaqueTile = NO_OPAQUE_TILES;
final OpaqueSurface[] tiles = new OpaqueSurface[TILES_PER_FACE];
int tileCount = 0;
}
private final BlockInfo[][][] data = new BlockInfo[BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK];
{
for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) {
for (int y = 0; y < BLOCKS_PER_CHUNK; ++y) {
for (int z = 0; z < BLOCKS_PER_CHUNK; ++z) {
data[x][y][z] = new BlockInfo();
}
}
}
}
@Override
public void startRender(ChunkRender chunk) {
// Do nothing
}
@Override
public void processBlock(BlockRender block, Vec3i pos) {
if (!(block instanceof OpaqueCube))
return;
OpaqueCube opaqueCube = (OpaqueCube) block;
addBlock(pos, opaqueCube);
}
@Override
public void processTile(TileRender tile, Vec3i pos, BlockFace face) {
if (!(tile instanceof OpaqueSurface))
return;
OpaqueSurface opaqueTile = (OpaqueSurface) tile;
addTile(pos, face, opaqueTile);
}
protected void addBlock(Vec3i pos, OpaqueCube cube) {
getBlock(pos).block = cube;
}
private void addTile(Vec3i pos, BlockFace face, OpaqueSurface opaqueTile) {
FaceInfo faceInfo = getFace(pos, face);
int index = faceInfo.tileCount;
faceInfo.tileCount++;
faceInfo.tiles[index] = opaqueTile;
if (opaqueTile.isOpaque(face)) {
faceInfo.topOpaqueTile = index;
}
}
protected BlockInfo getBlock(Vec3i cursor) {
return data[cursor.x][cursor.y][cursor.z];
}
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
return getBlock(cursor).faces[face.getId()];
}
@Override
public Shape endRender() {
Collection<Face> shapeFaces = new ArrayList<>(
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
);
Vec3i cursor = new Vec3i();
for (cursor.x = 0; cursor.x < BLOCKS_PER_CHUNK; ++cursor.x) {
for (cursor.y = 0; cursor.y < BLOCKS_PER_CHUNK; ++cursor.y) {
for (cursor.z = 0; cursor.z < BLOCKS_PER_CHUNK; ++cursor.z) {
processInnerFaces(cursor, shapeFaces::add);
processOuterFaces(cursor, shapeFaces::add);
}
}
}
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
shapeFaces.toArray(new Face[shapeFaces.size()])
);
}
private void processOuterFaces(
Vec3i cursor,
Consumer<Face> output
) {
for (BlockFace face : BlockFace.getFaces()) {
if (!shouldRenderOuterFace(cursor, face))
continue;
Vec3 faceOrigin = new Vec3(cursor.x, cursor.y, cursor.z);
Vec3 offset = new Vec3(face.getVector().x, face.getVector().y, face.getVector().z).mul(1f / 128);
FaceInfo info = getFace(cursor, face);
if (info.topOpaqueTile == FaceInfo.NO_OPAQUE_TILES) {
OpaqueCube block = getBlock(cursor).block;
if (block != null) {
addFace(
faceOrigin,
face,
getBlock(cursor).block.getTexture(face),
output
);
faceOrigin.add(offset);
}
}
int startLayer = info.topOpaqueTile;
if (startLayer == FaceInfo.NO_OPAQUE_TILES) {
startLayer = 0;
}
for (int layer = startLayer; layer < info.tileCount; ++layer) {
addFace(
faceOrigin,
face,
info.tiles[layer].getTexture(face),
output
);
faceOrigin.add(offset);
}
}
}
private void addFace(
Vec3 cursor,
BlockFace face,
Texture texture,
Consumer<Face> output
) {
if (texture == null)
return;
output.accept(
Faces.createBlockFace(
WorldRenderProgram.getDefault(),
texture,
Colors.WHITE,
new Vec3(cursor),
face,
false
)
);
}
private boolean shouldRenderOuterFace(Vec3i cursor, BlockFace face) {
cursor.add(face.getVector());
try {
// TODO handle neighboring chunks properly
if (!isInBounds(cursor))
return true;
OpaqueCube adjacent = getBlock(cursor).block;
if (adjacent == null)
return true;
if (adjacent.isOpaque(face))
return false;
return true;
} finally {
cursor.sub(face.getVector());
}
}
private void processInnerFaces(
Vec3i cursor,
Consumer<Face> output
) {
// if (block.isBlockOpaque()) return;
//
// for (BlockFace face : BlockFace.getFaces()) {
//
// Texture texture = block.getTexture(face);
// if (texture == null) continue;
//
// output.accept(Faces.createBlockFace(
// WorldRenderProgram.getDefault(),
// texture,
// COLOR_MULTIPLIER,
// new Vec3(cursor.x, cursor.y, cursor.z),
// face,
// true
// ));
//
// }
}
private boolean isInBounds(Vec3i cursor) {
return isInBounds(cursor.x) &&
isInBounds(cursor.y) &&
isInBounds(cursor.z);
}
private boolean isInBounds(int c) {
return c >= 0 && c < BLOCKS_PER_CHUNK;
}
}

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.world.cro;
import ru.windcorp.progressia.common.util.namespaces.NamespacedFactoryRegistry;
public class ChunkRenderOptimizerRegistry extends NamespacedFactoryRegistry<ChunkRenderOptimizer> {
private static final ChunkRenderOptimizerRegistry INSTANCE = new ChunkRenderOptimizerRegistry();
/**
* @return the instance
*/
public static ChunkRenderOptimizerRegistry getInstance() {
return INSTANCE;
}
}

View File

@ -0,0 +1,99 @@
/*
* 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.world.cro;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRenderOptimizerSimple extends ChunkRenderOptimizer {
public interface BlockOptimizedSimple {
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
Consumer<ShapePart> output
);
}
public interface TileOptimizedCustom {
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
Consumer<ShapePart> output
);
}
private final Collection<ShapePart> parts = new ArrayList<>();
private final Consumer<ShapePart> partAdder = parts::add;
public ChunkRenderOptimizerSimple(String id) {
super(id);
}
@Override
public void startRender() {
parts.clear();
}
@Override
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
if (block instanceof BlockOptimizedSimple) {
((BlockOptimizedSimple) block).getShapeParts(chunk.getData(), relBlockInChunk, partAdder);
}
}
@Override
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace blockFace) {
if (tile instanceof TileOptimizedCustom) {
((TileOptimizedCustom) tile).getShapeParts(chunk.getData(), relBlockInChunk, blockFace, partAdder);
}
}
@Override
public Renderable endRender() {
if (parts.isEmpty()) {
return null;
}
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
parts.toArray(new ShapePart[parts.size()])
);
}
}

View File

@ -0,0 +1,396 @@
/*
* 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.world.cro;
import static ru.windcorp.progressia.common.world.DefaultChunkData.BLOCKS_PER_CHUNK;
import static ru.windcorp.progressia.common.world.generic.TileGenericStackRO.TILES_PER_FACE;
import static ru.windcorp.progressia.common.world.rels.AbsFace.BLOCK_FACE_COUNT;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.GenericChunks;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer {
private static final float OVERLAY_OFFSET = 1 / 128f;
/**
* A common interface to objects that can provide optimizeable surfaces.
* This is an internal interface; use {@link BlockOptimizedSurface} or
* {@link TileOptimizedSurface} instead.
*/
private static interface OptimizedSurface {
/**
* Creates and outputs a set of shape parts that correspond to this
* surface. The coordinates of the face vertices must be in chunk
* coordinate system.
*
* @param chunk the chunk that contains the requested face
* @param relBlockInChunk the relative block in chunk
* @param blockFace the requested face
* @param inner whether this face should be visible from
* inside
* ({@code true}) or outside ({@code false})
* @param output a consumer that the created shape parts must
* be
* given to
* @param offset an additional offset that must be applied to
* all
* vertices
*/
void getShapeParts(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Vec3 offset /* kostyl 156% */
);
/**
* Returns the opacity of the surface identified by the provided
* {@link RelFace}.
* Opaque surfaces prevent surfaces behind them from being included in
* chunk models.
*
* @param blockFace the face to query
* @return {@code true} iff the surface is opaque.
*/
boolean isOpaque(RelFace blockFace);
}
/**
* A block that can be optimized by {@link ChunkRenderOptimizerSurface}.
*/
public static interface BlockOptimizedSurface extends OptimizedSurface {
/**
* Returns the opacity of the block. Opaque blocks do not expect that
* the camera can be inside them. Opaque blocks prevent surfaces that
* face them
* from being included in chunk models.
*
* @return {@code true} iff the block is opaque.
*/
boolean isBlockOpaque();
}
/**
* A tile that can be optimized by {@link ChunkRenderOptimizerSurface}.
*/
public static interface TileOptimizedSurface extends OptimizedSurface {
// Empty for now
}
private static class BlockInfo {
BlockOptimizedSurface block;
final FaceInfo[] faces = new FaceInfo[BLOCK_FACE_COUNT];
{
for (int i = 0; i < faces.length; ++i) {
faces[i] = new FaceInfo(this);
}
}
}
private static class FaceInfo {
static final int BLOCK_LAYER = -1;
final BlockInfo block;
int topOpaqueSurface = BLOCK_LAYER;
int bottomOpaqueSurface = Integer.MAX_VALUE;
final TileOptimizedSurface[] tiles = new TileOptimizedSurface[TILES_PER_FACE];
int tileCount = 0;
FaceInfo(BlockInfo block) {
this.block = block;
}
OptimizedSurface getSurface(int layer) {
return layer == BLOCK_LAYER ? block.block : tiles[layer];
}
}
private final BlockInfo[][][] data = new BlockInfo[BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK];
public ChunkRenderOptimizerSurface(String id) {
super(id);
}
@Override
public void startRender() {
for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) {
for (int y = 0; y < BLOCKS_PER_CHUNK; ++y) {
for (int z = 0; z < BLOCKS_PER_CHUNK; ++z) {
data[x][y][z] = new BlockInfo();
}
}
}
}
@Override
public void addBlock(BlockRender block, Vec3i relBlockInChunk) {
if (!(block instanceof BlockOptimizedSurface))
return;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
addBlock(relBlockInChunk, bos);
}
@Override
public void addTile(TileRender tile, Vec3i relBlockInChunk, RelFace face) {
if (!(tile instanceof TileOptimizedSurface))
return;
TileOptimizedSurface tos = (TileOptimizedSurface) tile;
addTile(relBlockInChunk, face, tos);
}
private void addBlock(Vec3i relBlockInChunk, BlockOptimizedSurface block) {
getBlock(relBlockInChunk).block = block;
}
private void addTile(Vec3i relBlockInChunk, RelFace face, TileOptimizedSurface tile) {
FaceInfo faceInfo = getFace(relBlockInChunk, face);
int index = faceInfo.tileCount;
faceInfo.tileCount++;
faceInfo.tiles[index] = tile;
if (tile.isOpaque(face)) {
faceInfo.topOpaqueSurface = index;
if (faceInfo.bottomOpaqueSurface == FaceInfo.BLOCK_LAYER) {
faceInfo.bottomOpaqueSurface = index;
}
}
}
protected BlockInfo getBlock(Vec3i relBlockInChunk) {
return data[relBlockInChunk.x][relBlockInChunk.y][relBlockInChunk.z];
}
protected FaceInfo getFace(Vec3i relBlockInChunk, RelFace face) {
return getBlock(relBlockInChunk).faces[face.getId()];
}
@Override
public Renderable endRender() {
Collection<ShapePart> shapeParts = new ArrayList<>(
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
);
Consumer<ShapePart> consumer = shapeParts::add;
GenericChunks.forEachBiC(relBlockInChunk -> {
processInnerFaces(relBlockInChunk, consumer);
processOuterFaces(relBlockInChunk, consumer);
});
if (shapeParts.isEmpty()) {
return null;
}
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
shapeParts.toArray(new ShapePart[shapeParts.size()])
);
}
private void processOuterFaces(
Vec3i relBlockInChunk,
Consumer<ShapePart> output
) {
for (RelFace blockFace : RelFace.getFaces()) {
processOuterFace(relBlockInChunk, blockFace, output);
}
}
private void processOuterFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
if (!shouldRenderOuterFace(relBlockInChunk, blockFace))
return;
FaceInfo info = getFace(relBlockInChunk, blockFace);
if (info.tileCount == 0 && info.block.block == null)
return;
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
for (
int layer = info.topOpaqueSurface;
layer < info.tileCount;
++layer
) {
OptimizedSurface surface = info.getSurface(layer);
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, false, output, faceOrigin);
faceOrigin.add(offset);
}
}
private void processInnerFaces(Vec3i relBlockInChunk, Consumer<ShapePart> output) {
for (RelFace blockFace : RelFace.getFaces()) {
processInnerFace(relBlockInChunk, blockFace, output);
}
}
private void processInnerFace(Vec3i relBlockInChunk, RelFace blockFace, Consumer<ShapePart> output) {
if (!shouldRenderInnerFace(relBlockInChunk, blockFace))
return;
FaceInfo info = getFace(relBlockInChunk, blockFace);
if (info.tileCount == 0 && info.block.block == null)
return;
Vec3 faceOrigin = new Vec3(relBlockInChunk.x, relBlockInChunk.y, relBlockInChunk.z);
Vec3 offset = new Vec3(blockFace.getRelFloatVector()).mul(OVERLAY_OFFSET);
for (
int layer = FaceInfo.BLOCK_LAYER;
layer <= info.bottomOpaqueSurface && layer < info.tileCount;
++layer
) {
OptimizedSurface surface = info.getSurface(layer);
if (surface == null)
continue; // layer may be BLOCK_LAYER, then block may be null
surface.getShapeParts(chunk.getData(), relBlockInChunk, blockFace, true, output, faceOrigin);
faceOrigin.add(offset);
}
}
private boolean shouldRenderOuterFace(Vec3i relBlockInChunk, RelFace face) {
relBlockInChunk.add(face.getRelVector());
try {
return shouldRenderWhenFacing(relBlockInChunk, face);
} finally {
relBlockInChunk.sub(face.getRelVector());
}
}
private boolean shouldRenderInnerFace(Vec3i relBlockInChunk, RelFace face) {
return shouldRenderWhenFacing(relBlockInChunk, face);
}
private boolean shouldRenderWhenFacing(Vec3i relBlockInChunk, RelFace face) {
if (GenericChunks.containsBiC(relBlockInChunk)) {
return shouldRenderWhenFacingLocal(relBlockInChunk, face);
} else {
return shouldRenderWhenFacingNeighbor(relBlockInChunk, face);
}
}
private boolean shouldRenderWhenFacingLocal(Vec3i relBlockInChunk, RelFace face) {
BlockOptimizedSurface block = getBlock(relBlockInChunk).block;
if (block == null) {
return true;
}
if (block.isOpaque(face)) {
return false;
}
return true;
}
private boolean shouldRenderWhenFacingNeighbor(Vec3i relBlockInLocalChunk, RelFace face) {
Vec3i blockInChunk = Vectors.grab3i();
chunk.resolve(relBlockInLocalChunk, blockInChunk);
Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ());
try {
// Determine blockInChunk and chunkPos
if (blockInChunk.x == -1) {
blockInChunk.x = BLOCKS_PER_CHUNK - 1;
chunkPos.x -= 1;
} else if (blockInChunk.x == BLOCKS_PER_CHUNK) {
blockInChunk.x = 0;
chunkPos.x += 1;
} else if (blockInChunk.y == -1) {
blockInChunk.y = BLOCKS_PER_CHUNK - 1;
chunkPos.y -= 1;
} else if (blockInChunk.y == BLOCKS_PER_CHUNK) {
blockInChunk.y = 0;
chunkPos.y += 1;
} else if (blockInChunk.z == -1) {
blockInChunk.z = BLOCKS_PER_CHUNK - 1;
chunkPos.z -= 1;
} else if (blockInChunk.z == BLOCKS_PER_CHUNK) {
blockInChunk.z = 0;
chunkPos.z += 1;
} else {
throw new AssertionError(
"Requested incorrent neighbor ("
+ relBlockInLocalChunk.x + "; "
+ relBlockInLocalChunk.y + "; "
+ relBlockInLocalChunk.z + ")"
);
}
ChunkRender chunk = this.chunk.getWorld().getChunk(chunkPos);
if (chunk == null)
return false;
BlockRender block = chunk.getBlock(blockInChunk);
if (!(block instanceof BlockOptimizedSurface))
return true;
BlockOptimizedSurface bos = (BlockOptimizedSurface) block;
RelFace rotatedFace = face.rotate(this.chunk.getUp(), chunk.getUp());
if (!bos.isOpaque(rotatedFace)) {
return true;
}
return false;
} finally {
Vectors.release(blockInChunk);
Vectors.release(chunkPos);
}
}
}

View File

@ -15,22 +15,49 @@
* 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.world.entity;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.common.world.generic.GenericEntity;
import ru.windcorp.progressia.common.world.generic.EntityGeneric;
public abstract class EntityRenderable implements Renderable, GenericEntity {
public abstract class EntityRenderable implements Renderable, EntityGeneric {
private final EntityData data;
private long stateComputedForFrame = -1;
public EntityRenderable(EntityData data) {
this.data = data;
}
/**
* Updates the state of this model. This method is invoked exactly once per
* renderable per frame before this entity is queried for the first time.
*/
protected void update() {
// Do nothing
}
private void updateIfNecessary() {
if (stateComputedForFrame != GraphicsInterface.getFramesRendered()) {
update();
stateComputedForFrame = GraphicsInterface.getFramesRendered();
}
}
@Override
public final void render(ShapeRenderHelper renderer) {
updateIfNecessary();
doRender(renderer);
}
protected abstract void doRender(ShapeRenderHelper renderer);
public EntityData getData() {
return data;
}
@ -44,8 +71,42 @@ public abstract class EntityRenderable implements Renderable, GenericEntity {
public String getId() {
return getData().getId();
}
@Override
public long getEntityId() {
return getData().getEntityId();
}
public void getViewPoint(Vec3 output) {
public final Vec3 getLookingAt(Vec3 output) {
if (output == null) output = new Vec3();
updateIfNecessary();
doGetLookingAt(output);
return output;
}
protected void doGetLookingAt(Vec3 output) {
output.set(getData().getLookingAt());
}
public final Vec3 getUpVector(Vec3 output) {
if (output == null) output = new Vec3();
updateIfNecessary();
doGetUpVector(output);
return output;
}
protected void doGetUpVector(Vec3 output) {
output.set(getData().getUpVector());
}
public final Vec3 getViewPoint(Vec3 output) {
if (output == null) output = new Vec3();
updateIfNecessary();
doGetViewPoint(output);
return output;
}
protected void doGetViewPoint(Vec3 output) {
output.set(0, 0, 0);
}

View File

@ -43,6 +43,7 @@ public class HumanoidModel extends NPedModel {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
super.applyTransform(mat, model);
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
float value = sin(phase);
float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter();

View File

@ -18,11 +18,8 @@
package ru.windcorp.progressia.client.world.entity;
import static java.lang.Math.atan2;
import static java.lang.Math.min;
import static java.lang.Math.pow;
import static java.lang.Math.toRadians;
import static ru.windcorp.progressia.common.util.FloatMathUtil.normalizeAngle;
import glm.Glm;
import glm.mat._4.Mat4;
@ -32,6 +29,9 @@ import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.Units;
import ru.windcorp.progressia.common.util.FloatMathUtil;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData;
public abstract class NPedModel extends EntityRenderable {
@ -51,29 +51,30 @@ public abstract class NPedModel extends EntityRenderable {
ShapeRenderHelper renderer,
NPedModel model
) {
renderer.pushTransform().translate(translation);
applyTransform(renderer.pushTransform(), model);
renderable.render(renderer);
renderer.popTransform();
renderer.popTransform();
}
protected abstract void applyTransform(Mat4 mat, NPedModel model);
protected void applyTransform(Mat4 mat, NPedModel model) {
mat.translate(getTranslation());
}
public Vec3 getTranslation() {
return translation;
}
public Mat4 getTransform(Mat4 output, NPedModel model) {
if (output == null) output = new Mat4().identity();
applyTransform(output, model);
return output;
}
}
public static class Body extends BodyPart {
public Body(Renderable renderable) {
super(renderable, null);
}
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
// Do nothing
}
}
public static class Head extends BodyPart {
@ -97,7 +98,8 @@ public abstract class NPedModel extends EntityRenderable {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch());
super.applyTransform(mat, model);
mat.rotateZ(-model.getHeadYaw()).rotateY(-model.getHeadPitch());
}
public Vec3 getViewPoint() {
@ -105,6 +107,8 @@ public abstract class NPedModel extends EntityRenderable {
}
}
public static boolean flag;
protected final Body body;
protected final Head head;
@ -128,7 +132,9 @@ public abstract class NPedModel extends EntityRenderable {
private float walkingFrequency;
private float bodyYaw = Float.NaN;
private final Vec3 bodyLookingAt = new Vec3().set(0);
private final Mat4 bodyTransform = new Mat4();
private float headYaw;
private float headPitch;
@ -138,68 +144,121 @@ public abstract class NPedModel extends EntityRenderable {
this.head = head;
this.scale = scale;
evaluateAngles();
computeRotations();
}
@Override
public void render(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).rotateZ(bodyYaw);
protected void doRender(ShapeRenderHelper renderer) {
renderer.pushTransform().scale(scale).mul(bodyTransform);
renderBodyParts(renderer);
renderer.popTransform();
accountForVelocity();
evaluateAngles();
}
protected void renderBodyParts(ShapeRenderHelper renderer) {
body.render(renderer, this);
head.render(renderer, this);
}
private void evaluateAngles() {
float globalYaw = normalizeAngle(getData().getYaw());
if (Float.isNaN(bodyYaw)) {
bodyYaw = globalYaw;
headYaw = 0;
} else {
headYaw = normalizeAngle(globalYaw - bodyYaw);
if (headYaw > +head.maxYaw) {
bodyYaw += headYaw - +head.maxYaw;
headYaw = +head.maxYaw;
} else if (headYaw < -head.maxYaw) {
bodyYaw += headYaw - -head.maxYaw;
headYaw = -head.maxYaw;
}
}
bodyYaw = normalizeAngle(bodyYaw);
headPitch = Glm.clamp(
getData().getPitch(),
-head.maxPitch,
head.maxPitch
);
@Override
protected void update() {
advanceTime();
computeRotations();
}
private void accountForVelocity() {
Vec3 horizontal = new Vec3(getData().getVelocity());
horizontal.z = 0;
private void computeRotations() {
if (!bodyLookingAt.any()) {
getData().getForwardVector(bodyLookingAt);
headYaw = 0;
} else {
ensureBodyLookingAtIsPerpendicularToUpVector();
computeDesiredHeadYaw();
clampHeadYawAndChangeBodyLookingAt();
}
recomputeBodyTransform();
setHeadPitch();
}
private void ensureBodyLookingAtIsPerpendicularToUpVector() {
Vec3 up = getData().getUpVector();
if (up.dot(bodyLookingAt) > 1 - 1e-4) return;
Vec3 tmp = Vectors.grab3();
tmp.set(up).mul(-up.dot(bodyLookingAt)).add(bodyLookingAt);
float tmpLength = tmp.length();
if (tmpLength > 1e-4) {
bodyLookingAt.set(tmp).div(tmpLength);
} else {
// bodyLookingAt is suddenly parallel to up vector -- PANIC! ENTERING RESCUE MODE!
getData().getForwardVector(bodyLookingAt);
}
Vectors.release(tmp);
}
private void computeDesiredHeadYaw() {
Vec3 newDirection = getData().getForwardVector(null);
Vec3 oldDirection = bodyLookingAt;
Vec3 up = getData().getUpVector();
headYaw = (float) VectorUtil.getAngle(oldDirection, newDirection, up);
}
private void clampHeadYawAndChangeBodyLookingAt() {
float bodyYawChange = 0;
if (headYaw > +head.maxYaw) {
bodyYawChange = headYaw - +head.maxYaw;
headYaw = +head.maxYaw;
} else if (headYaw < -head.maxYaw) {
bodyYawChange = headYaw - -head.maxYaw;
headYaw = -head.maxYaw;
}
if (bodyYawChange != 0) {
VectorUtil.rotate(bodyLookingAt, getData().getUpVector(), bodyYawChange);
}
}
private void recomputeBodyTransform() {
Vec3 u = getData().getUpVector();
Vec3 f = bodyLookingAt;
Vec3 s = Vectors.grab3();
s.set(u).cross(f);
bodyTransform.identity().set(
+f.x, +f.y, +f.z, 0,
-s.x, -s.y, -s.z, 0,
+u.x, +u.y, +u.z, 0,
0, 0, 0, 1
);
Vectors.release(s);
}
private void setHeadPitch() {
headPitch = Glm.clamp((float) getData().getPitch(), -head.maxPitch, +head.maxPitch);
}
private void advanceTime() {
Vec3 horizontal = getData().getUpVector()
.mul_(-getData().getUpVector().dot(getData().getVelocity()))
.add(getData().getVelocity());
velocity = horizontal.length();
evaluateVelocityCoeff();
computeVelocityParameter();
// TODO switch to world time
walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000;
bodyYaw += velocityParameter * normalizeAngle(
(float) (atan2(horizontal.y, horizontal.x) - bodyYaw)
) * min(1, GraphicsInterface.getFrameLength() * 10);
rotateBodyWithMovement(horizontal);
}
private void evaluateVelocityCoeff() {
private void computeVelocityParameter() {
if (velocity > maxEffectiveVelocity) {
velocityParameter = 1;
} else {
@ -207,17 +266,39 @@ public abstract class NPedModel extends EntityRenderable {
}
}
private void rotateBodyWithMovement(Vec3 target) {
if (velocityParameter == 0 || !target.any() || Glm.equals(target, bodyLookingAt)) {
return;
}
Vec3 axis = getData().getUpVector();
float yawDifference = FloatMathUtil.normalizeAngle(
(float) VectorUtil.getAngle(
bodyLookingAt,
target.normalize_(),
axis
)
);
float bodyYawChange =
velocityParameter *
yawDifference *
(float) Math.expm1(GraphicsInterface.getFrameLength() * 10);
VectorUtil.rotate(bodyLookingAt, axis, bodyYawChange);
}
@Override
public void getViewPoint(Vec3 output) {
protected void doGetViewPoint(Vec3 output) {
Mat4 m = new Mat4();
Vec4 v = new Vec4();
m.identity()
.scale(scale)
.rotateZ(bodyYaw)
.translate(head.getTranslation())
.rotateZ(headYaw)
.rotateY(headPitch);
.mul(bodyTransform);
head.getTransform(m, this);
v.set(head.getViewPoint(), 1);
m.mul(v);
@ -232,9 +313,9 @@ public abstract class NPedModel extends EntityRenderable {
public Head getHead() {
return head;
}
public float getBodyYaw() {
return bodyYaw;
public Vec3 getBodyLookingAt() {
return bodyLookingAt;
}
public float getHeadYaw() {

View File

@ -44,6 +44,7 @@ public class QuadripedModel extends NPedModel {
@Override
protected void applyTransform(Mat4 mat, NPedModel model) {
super.applyTransform(mat, model);
float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset;
float value = sin(phase);
float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter();

View File

@ -18,26 +18,21 @@
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericTile;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.generic.TileGeneric;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRender extends Namespaced implements GenericTile {
public class TileRender extends Namespaced implements TileGeneric {
public TileRender(String id) {
super(id);
}
public void render(ShapeRenderHelper renderer, BlockFace face) {
throw new UnsupportedOperationException(
"TileRender.render() not implemented in " + this
);
}
public Renderable createRenderable(BlockFace face) {
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
return null;
}

View File

@ -0,0 +1,170 @@
/*
* 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.world.tile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import glm.mat._3.Mat3;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSimple.TileOptimizedCustom;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.VectorUtil;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.AxisRotations;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderCross extends TileRender implements TileOptimizedCustom {
private static final float SQRT_2_OVER_2 = (float) Math.sqrt(2) / 2;
private static final float[] ONE_AND_NEGATIVE_ONE = new float[] { 1, -1 };
private final Texture texture;
private final float width;
public TileRenderCross(String id, Texture texture, boolean allowStretching) {
super(id);
this.texture = texture;
this.width = allowStretching ? 1 : SQRT_2_OVER_2;
}
public Texture getTexture(RelFace blockFace) {
return texture;
}
public Vec4 getColorMultiplier(RelFace blockFace) {
return Colors.WHITE;
}
@Override
public void getShapeParts(
DefaultChunkData chunk,
Vec3i bic,
RelFace blockFace,
Consumer<ShapePart> output
) {
Mat4 transform = Matrices.grab4();
Vec3 origin = Vectors.grab3();
Vec3 width = Vectors.grab3();
Vec3 height = Vectors.grab3();
Mat3 resolutionMatrix = AxisRotations.getResolutionMatrix3(blockFace.resolve(AbsFace.POS_Z));
Vec4 color = getColorMultiplier(blockFace);
Texture texture = getTexture(blockFace);
float originOffset = (1 - this.width) / 2;
WorldRenderProgram program = WorldRenderProgram.getDefault();
for (int i = 0; getTransform(chunk, bic, blockFace, i, transform); i++) {
for (float flip : ONE_AND_NEGATIVE_ONE) {
origin.set(flip * (originOffset - 0.5f), originOffset - 0.5f, 0);
width.set(flip * this.width, this.width, 0);
height.set(0, 0, 1);
VectorUtil.applyMat4(origin, transform);
VectorUtil.rotateOnly(width, transform);
VectorUtil.rotateOnly(height, transform);
origin.z += 1 - 0.5f;
if (blockFace != RelFace.UP) {
resolutionMatrix.mul(origin);
resolutionMatrix.mul(width);
resolutionMatrix.mul(height);
}
origin.add(bic.x, bic.y, bic.z);
output.accept(
ShapeParts.createRectangle(
program,
texture,
color,
origin,
width,
height,
false
)
);
output.accept(
ShapeParts.createRectangle(
program,
texture,
color,
origin,
width,
height,
true
)
);
}
}
Matrices.release(transform);
Vectors.release(origin);
Vectors.release(width);
Vectors.release(height);
}
protected boolean getTransform(
DefaultChunkData chunk,
Vec3i relBlockInChunk,
RelFace blockFace,
int count,
Mat4 output
) {
output.identity();
return count == 0;
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace) {
Collection<ShapePart> parts = new ArrayList<>(4);
getShapeParts(chunk, blockInChunk, blockFace, parts::add);
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
parts.toArray(new ShapePart[parts.size()])
);
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -1,81 +0,0 @@
/*
* 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.world.tile;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerCube.OpaqueSurface;
import ru.windcorp.progressia.common.world.block.BlockFace;
public class TileRenderGrass extends TileRender implements OpaqueSurface {
private final Texture topTexture;
private final Texture sideTexture;
public TileRenderGrass(
String id,
Texture top,
Texture side
) {
super(id);
this.topTexture = top;
this.sideTexture = side;
}
@Override
public Texture getTexture(BlockFace face) {
return (face == BlockFace.TOP) ? topTexture : sideTexture;
}
@Override
public boolean isOpaque(BlockFace face) {
return face == BlockFace.TOP;
}
@Override
public Renderable createRenderable(BlockFace face) {
ShapeRenderProgram program = WorldRenderProgram.getDefault();
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
Faces.createBlockFace(
program,
getTexture(face),
Colors.WHITE,
new Vec3(0, 0, 0),
face,
false
)
);
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.world.tile;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.EmptyModel;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderNone extends TileRender {
public TileRenderNone(String id) {
super(id);
}
@Override
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace face) {
return EmptyModel.getInstance();
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderOpaqueSurface extends TileRenderSurface {
@ -28,7 +28,7 @@ public class TileRenderOpaqueSurface extends TileRenderSurface {
}
@Override
public boolean isOpaque(BlockFace face) {
public boolean isOpaque(RelFace face) {
return true;
}

View File

@ -0,0 +1,27 @@
/*
* 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.world.tile;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.common.world.generic.TileGenericReferenceRO;
public interface TileRenderReference
extends TileGenericReferenceRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
}

View File

@ -18,12 +18,15 @@
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import java.util.AbstractList;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.common.world.TileDataStack;
import ru.windcorp.progressia.common.world.generic.TileGenericStackRO;
public abstract class TileRenderStack
extends GenericTileStack<TileRenderStack, TileRender, ChunkRender> {
extends AbstractList<TileRender>
implements TileGenericStackRO<BlockRender, TileRender, TileRenderStack, TileRenderReference, ChunkRender> {
public abstract TileDataStack getData();

View File

@ -18,19 +18,26 @@
package ru.windcorp.progressia.client.world.tile;
import java.util.function.Consumer;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4;
import ru.windcorp.progressia.client.graphics.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.ShapePart;
import ru.windcorp.progressia.client.graphics.model.ShapeParts;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerCube.OpaqueSurface;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.TileOptimizedSurface;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.DefaultChunkData;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public abstract class TileRenderSurface extends TileRender implements OpaqueSurface {
public abstract class TileRenderSurface extends TileRender implements TileOptimizedSurface {
private final Texture texture;
@ -38,27 +45,52 @@ public abstract class TileRenderSurface extends TileRender implements OpaqueSurf
super(id);
this.texture = texture;
}
public TileRenderSurface(String id) {
this(id, null);
}
@Override
public Texture getTexture(BlockFace face) {
public Texture getTexture(RelFace blockFace) {
return texture;
}
public Vec4 getColorMultiplier(RelFace blockFace) {
return Colors.WHITE;
}
@Override
public final void getShapeParts(
DefaultChunkData chunk, Vec3i relBlockInChunk, RelFace blockFace,
boolean inner,
Consumer<ShapePart> output,
Vec3 offset
) {
output.accept(createFace(chunk, relBlockInChunk, blockFace, inner, offset));
}
private ShapePart createFace(
DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace,
boolean inner,
Vec3 offset
) {
return ShapeParts.createBlockFace(
WorldRenderProgram.getDefault(),
getTexture(blockFace),
getColorMultiplier(blockFace),
offset,
blockFace.resolve(AbsFace.POS_Z),
inner
);
}
@Override
public Renderable createRenderable(BlockFace face) {
ShapeRenderProgram program = WorldRenderProgram.getDefault();
public Renderable createRenderable(DefaultChunkData chunk, Vec3i blockInChunk, RelFace blockFace) {
return new Shape(
Usage.STATIC,
WorldRenderProgram.getDefault(),
Faces.createBlockFace(
program,
getTexture(face),
Colors.WHITE,
new Vec3(0, 0, 0),
face,
false
)
createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3)
);
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.RelFace;
public class TileRenderTransparentSurface extends TileRenderSurface {
@ -28,7 +28,7 @@ public class TileRenderTransparentSurface extends TileRenderSurface {
}
@Override
public boolean isOpaque(BlockFace face) {
public boolean isOpaque(RelFace face) {
return false;
}

View File

@ -0,0 +1,134 @@
/*
* 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.common.collision;
import java.util.function.Supplier;
import com.google.common.collect.ImmutableList;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.rels.AbsFace;
import ru.windcorp.progressia.common.world.rels.AxisRotations;
public class AABBRotator implements AABBoid {
private class AABBRotatorWall implements Wall {
private final int id;
public AABBRotatorWall(int id) {
this.id = id;
}
@Override
public void getOrigin(Vec3 output) {
parent.getWall(id).getOrigin(output);
AxisRotations.resolve(output, upSupplier.get(), output);
}
@Override
public void getWidth(Vec3 output) {
parent.getWall(id).getWidth(output);
AxisRotations.resolve(output, upSupplier.get(), output);
}
@Override
public void getHeight(Vec3 output) {
parent.getWall(id).getHeight(output);
AxisRotations.resolve(output, upSupplier.get(), output);
}
}
private final Supplier<AbsFace> upSupplier;
private final Supplier<Vec3> hingeSupplier;
private final AABBoid parent;
private final AABBRotatorWall[] walls = new AABBRotatorWall[AbsFace.BLOCK_FACE_COUNT];
{
for (int id = 0; id < walls.length; ++id) {
walls[id] = new AABBRotatorWall(id);
}
}
public AABBRotator(Supplier<AbsFace> upSupplier, Supplier<Vec3> hingeSupplier, AABBoid parent) {
this.upSupplier = upSupplier;
this.hingeSupplier = hingeSupplier;
this.parent = parent;
}
@Override
public void setOrigin(Vec3 origin) {
Vec3 relativeOrigin = Vectors.grab3();
Vec3 hinge = hingeSupplier.get();
origin.sub(hinge, relativeOrigin);
AxisRotations.relativize(relativeOrigin, upSupplier.get(), relativeOrigin);
relativeOrigin.add(hinge);
parent.setOrigin(relativeOrigin);
Vectors.release(relativeOrigin);
}
@Override
public void moveOrigin(Vec3 displacement) {
parent.moveOrigin(displacement);
}
@Override
public void getOrigin(Vec3 output) {
parent.getOrigin(output);
Vec3 hinge = hingeSupplier.get();
output.sub(hinge);
AxisRotations.resolve(output, upSupplier.get(), output);
output.add(hinge);
}
@Override
public void getSize(Vec3 output) {
parent.getSize(output);
AxisRotations.resolve(output, upSupplier.get(), output);
output.abs();
}
@Override
public Wall getWall(int faceId) {
return walls[faceId];
}
public static CollisionModel rotate(Supplier<AbsFace> upSupplier, Supplier<Vec3> hingeSupplier, CollisionModel parent) {
if (parent instanceof AABBoid) {
return new AABBRotator(upSupplier, hingeSupplier, (AABBoid) parent);
} else if (parent instanceof CompoundCollisionModel) {
ImmutableList.Builder<CollisionModel> models = ImmutableList.builder();
for (CollisionModel original : ((CompoundCollisionModel) parent).getModels()) {
models.add(rotate(upSupplier, hingeSupplier, original));
}
return new CompoundCollisionModel(models.build());
} else {
throw new RuntimeException("not supported");
}
}
}

View File

@ -19,7 +19,7 @@
package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public interface AABBoid extends CollisionModel {
@ -27,7 +27,7 @@ public interface AABBoid extends CollisionModel {
void getSize(Vec3 output);
default Wall getWall(BlockFace face) {
default Wall getWall(AbsFace face) {
return getWall(face.getId());
}

View File

@ -20,7 +20,7 @@ package ru.windcorp.progressia.common.collision;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
public class TranslatedAABB implements AABBoid {
@ -51,7 +51,7 @@ public class TranslatedAABB implements AABBoid {
private AABBoid parent;
private final Vec3 translation = new Vec3();
private final TranslatedAABBWall[] walls = new TranslatedAABBWall[BlockFace.BLOCK_FACE_COUNT];
private final TranslatedAABBWall[] walls = new TranslatedAABBWall[AbsFace.BLOCK_FACE_COUNT];
{
for (int id = 0; id < walls.length; ++id) {

View File

@ -24,7 +24,7 @@ import java.util.Collection;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
public class WorldCollisionHelper {
@ -79,7 +79,7 @@ public class WorldCollisionHelper {
* checked against
* @param maxTime maximum collision time
*/
public void tuneToCollideable(WorldData world, Collideable collideable, float maxTime) {
public void tuneToCollideable(DefaultWorldData world, Collideable collideable, float maxTime) {
activeBlockModels.forEach(blockModelCache::release);
activeBlockModels.clear();
CollisionPathComputer.forEveryBlockInCollisionPath(

View File

@ -25,7 +25,7 @@ import ru.windcorp.progressia.common.collision.colliders.Collider.ColliderWorksp
import ru.windcorp.progressia.common.collision.colliders.Collider.Collision;
import ru.windcorp.progressia.common.util.Matrices;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.rels.AbsFace;
class AABBoidCollider {
@ -50,7 +50,7 @@ class AABBoidCollider {
computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody);
// For every wall of collision space
for (int i = 0; i < BlockFace.BLOCK_FACE_COUNT; ++i) {
for (int i = 0; i < AbsFace.BLOCK_FACE_COUNT; ++i) {
Wall wall = originCollisionSpace.getWall(i);
Collision collision = computeWallCollision(
@ -122,46 +122,51 @@ class AABBoidCollider {
return output;
}
// @formatter:off
/*
* Here we determine whether a collision has actually happened, and if it
* did, at what moment.
* The basic idea is to compute the moment of collision and impact
* coordinates in wall coordinate space.
* Then, we can check impact coordinates to determine if we actually hit the
* wall or flew by and then
* check time to make sure the collision is not too far in the future and
* not in the past.
* Here we determine whether a collision has actually happened, and if it did, at what moment.
*
* The basic idea is to compute the moment of collision and impact coordinates in wall coordinate space.
* Then, we can check impact coordinates to determine if we actually hit the wall or flew by and then
* check time to make sure the collision is not too far in the future and not in the past.
*
* DETAILED EXPLANATION:
* Consider a surface defined by an origin r_wall and two noncollinear
* nonzero vectors w and h.
*
* Consider a surface defined by an origin r_wall and two noncollinear nonzero vectors w and h.
* Consider a line defined by an origin r_line and a nonzero vector v.
*
* Then, a collision occurs if there exist x, y and t such that
* ______ _
* r_line + v * t
* ______ _
* r_line + v * t
*
* and
* ______ _ _
* r_wall + w * x + h * y
* describe the same location (indeed, this corresponds to a collision at
* moment t0 + t
* with a point on the wall with coordinates (x; y) in (w; h) coordinate
* system).
* ______ _ _
* r_wall + w * x + h * y
*
* describe the same location (indeed, this corresponds to a collision at moment t0 + t
* with a point on the wall with coordinates (x; y) in (w; h) coordinate system).
*
* Therefore,
* ______ _ ______ _ _
* r_line + v*t = r_wall + w*x + h*y;
* _ ⎡w_x h_x -v_x⎤ ⎡x⎤ _ ______ ______
* r = ⎢w_y h_y -v_y⎥ * ⎢y⎥, where r = r_line - r_wall;
* ⎣w_z h_z -v_z⎦ ⎣t⎦
* ⎡x⎤ ⎡w_x h_x -v_x⎤ -1 _
* ⎢y⎥ = ⎢w_y h_y -v_y⎥ * r, if the matrix is invertible.
* ⎣t⎦ ⎣w_z h_z -v_z⎦
* ______ _ ______ _ _
* r_line + v*t = r_wall + w*x + h*y;
*
* _ ⎡w_x h_x -v_x⎤ ⎡x⎤ _ ______ ______
* r = ⎢w_y h_y -v_y⎥ * ⎢y⎥, where r = r_line - r_wall;
* ⎣w_z h_z -v_z⎦ ⎣t⎦
*
* ⎡x⎤ ⎡w_x h_x -v_x⎤ -1 _
* ⎢y⎥ = ⎢w_y h_y -v_y⎥ * r, if the matrix is invertible.
* ⎣t⎦ ⎣w_z h_z -v_z⎦
*
* Then, one only needs to ensure that:
* 0 < x < 1,
* 0 < y < 1, and
* 0 < t < T, where T is remaining tick time.
* If the matrix is not invertible or any of the conditions are not met, no
* collision happened.
* 0 < x < 1,
* 0 < y < 1, and
* 0 < t < T, where T is remaining tick time.
*
* If the matrix is not invertible or any of the conditions are not met, no collision happened.
* If all conditions are satisfied, then the moment of impact is t0 + t.
*/
// @formatter:on
private static Collision computeWallCollision(
Wall obstacleWall,
AABBoid colliderModel,

View File

@ -27,7 +27,7 @@ import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.collision.*;
import ru.windcorp.progressia.common.util.LowOverheadCache;
import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.WorldData;
import ru.windcorp.progressia.common.world.DefaultWorldData;
public class Collider {
@ -36,7 +36,7 @@ public class Collider {
/**
* Dear Princess Celestia,
* <p>
* When {@linkplain #advanceTime(Collection, Collision, WorldData, float)
* When {@linkplain #advanceTime(Collection, Collision, DefaultWorldData, float)
* advancing time},
* time step for all entities <em>except</em> currently colliding bodies is
* the current
@ -61,7 +61,7 @@ public class Collider {
public static void performCollisions(
List<? extends Collideable> colls,
WorldData world,
DefaultWorldData world,
float tickLength,
ColliderWorkspace workspace
) {
@ -96,7 +96,7 @@ public class Collider {
private static Collision getFirstCollision(
List<? extends Collideable> colls,
float tickLength,
WorldData world,
DefaultWorldData world,
ColliderWorkspace workspace
) {
Collision result = null;
@ -126,7 +126,7 @@ public class Collider {
private static void tuneWorldCollisionHelper(
Collideable coll,
float tickLength,
WorldData world,
DefaultWorldData world,
ColliderWorkspace workspace
) {
WorldCollisionHelper wch = workspace.worldCollisionHelper;
@ -194,7 +194,7 @@ public class Collider {
Collision collision,
Collection<? extends Collideable> colls,
WorldData world,
DefaultWorldData world,
float tickLength,
ColliderWorkspace workspace
) {
@ -212,66 +212,72 @@ public class Collider {
handlePhysics(collision);
}
// @formatter:off
/*
* Here we compute the change in body velocities due to a collision.
*
* We make the following simplifications:
* 1) The bodies are perfectly rigid;
* 2) The collision is perfectly inelastic
* (no bouncing);
* 3) The bodies are spherical;
* 4) No tangential friction exists
* (bodies do not experience friction when sliding against each other);
* 5) Velocities are not relativistic.
* 1) The bodies are perfectly rigid;
* 2) The collision is perfectly inelastic
* (no bouncing);
* 3) The bodies are spherical;
* 4) No tangential friction exists
* (bodies do not experience friction when sliding against each other);
* 5) Velocities are not relativistic.
*
* Angular momentum is ignored per 3) and 4),
* e.g. when something pushes an end of a long stick, the stick does not
* rotate.
* e.g. when something pushes an end of a long stick, the stick does not rotate.
*
* DETAILED EXPLANATION:
* Two spherical (sic) bodies, a and b, experience a perfectly inelastic
* collision
*
* Two spherical (sic) bodies, a and b, experience a perfectly inelastic collision
* along a unit vector
* _ _ _ _ _
* n = (w h) / (|w h|),
* _ _
* _ _ _ _ _
* n = (w h) / (|w h|),
* _ _
* where w and h are two noncollinear nonzero vectors on the dividing plane.
* ___ ___
* ___ ___
* Body masses and velocities are M_a, M_b and v_a, v_b, respectively.
* ___ ___
* ___ ___
* After the collision desired velocities are u_a and u_b, respectively.
* _
* (Notation convention: suffix 'n' denotes a vector projection onto vector
* n,
* _
* (Notation convention: suffix 'n' denotes a vector projection onto vector n,
* and suffix 't' denotes a vector projection onto the dividing plane.)
* Consider the law of conservation of momentum for axis n and the dividing
* plane:
* ____________ ____________ ________________
* n: ⎧ p_a_before_n + p_b_before_n = p_common_after_n;
* ⎨ ___________ ____________
* t: ⎩ p_i_after_t = p_i_before_t for any i in {a, b}.
*
* Consider the law of conservation of momentum for axis n and the dividing plane:
* ____________ ____________ ________________
* n: ⎧ p_a_before_n + p_b_before_n = p_common_after_n;
* ⎨ ___________ ____________
* t: ⎩ p_i_after_t = p_i_before_t for any i in {a, b}.
*
* Expressing all p_* in given terms:
* ___ _ ___ _ ___ ___ ____ ____
* n: ⎧ M_a * (v_a ⋅ n) + M_b * (v_b ⋅ n) = (M_a + M_b) * u_n, where u_n ≡
* u_an = u_bn;
* ⎨ ____ ___ _ ___ _
* t: ⎩ u_it = v_i - n * (v_i ⋅ n) for any i in {a, b}.
* ___ _ ___ _ ___ ___ ____ ____
* n: ⎧ M_a * (v_a ⋅ n) + M_b * (v_b ⋅ n) = (M_a + M_b) * u_n, where u_n ≡ u_an = u_bn;
* ⎨ ____ ___ _ ___ _
* t: ⎩ u_it = v_i - n * (v_i ⋅ n) for any i in {a, b}.
*
* Therefore:
* ___ _ ___ _ ___ _
* u_n = n * ( M_a/(M_a + M_b) * v_a ⋅ n + M_b/(M_a + M_b) * v_b ⋅ n );
* ___ _ ___ _ ___ _
* u_n = n * ( M_a/(M_a + M_b) * v_a ⋅ n + M_b/(M_a + M_b) * v_b ⋅ n );
*
* or, equivalently,
* ___ _ ___ _ ___ _
* u_n = n * ( m_a * v_a ⋅ n + m_b * v_b ⋅ n ),
* ___ _ ___ _ ___ _
* u_n = n * ( m_a * v_a ⋅ n + m_b * v_b ⋅ n ),
*
* where m_a and m_b are relative masses (see below).
*
* Finally,
* ___ ____ ___
* u_i = u_it + u_n for any i in {a, b}.
* The usage of relative masses m_i permits a convenient generalization of
* the algorithm
* for infinite masses, signifying masses "significantly greater" than
* finite masses:
* 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b) for any i
* in {a, b}.
* 2) If M_i is finite but M_j is infinite, let m_i = 0 and m_j = 1.
* 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i in {a, b}.
* ___ ____ ___
* u_i = u_it + u_n for any i in {a, b}.
*
* The usage of relative masses m_i permits a convenient generalization of the algorithm
* for infinite masses, signifying masses "significantly greater" than finite masses:
*
* 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b) for any i in {a, b}.
* 2) If M_i is finite but M_j is infinite, let m_i = 0 and m_j = 1.
* 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i in {a, b}.
*/
// @formatter:on
private static void handlePhysics(Collision collision) {
// Fuck JGLM
Vec3 n = Vectors.grab3();
@ -355,7 +361,7 @@ public class Collider {
private static void advanceTime(
Collection<? extends Collideable> colls,
Collision exceptions,
WorldData world,
DefaultWorldData world,
float step
) {
world.advanceTime(step);

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import ru.windcorp.progressia.common.comms.packets.Packet;
@ -53,6 +54,8 @@ public abstract class CommsChannel {
private State state = State.CONNECTING;
private final Collection<CommsListener> listeners = Collections.synchronizedCollection(new ArrayList<>());
private final List<Packet> pendingPackets = Collections.synchronizedList(new ArrayList<>());
protected abstract void doSendPacket(Packet packet) throws IOException;
@ -101,8 +104,19 @@ public abstract class CommsChannel {
public abstract void disconnect();
protected void onPacketReceived(Packet packet) {
pendingPackets.add(packet);
}
protected void forwardPacketToListeners(Packet packet) {
listeners.forEach(l -> l.onPacketReceived(packet));
}
public void processPackets() {
synchronized (pendingPackets) {
pendingPackets.forEach(this::forwardPacketToListeners);
pendingPackets.clear();
}
}
public void addListener(CommsListener listener) {
listeners.add(listener);

View File

@ -15,7 +15,7 @@
* 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.common.hacks;
import java.lang.reflect.Constructor;
@ -29,15 +29,14 @@ import com.google.common.util.concurrent.MoreExecutors;
import ru.windcorp.progressia.common.util.crash.CrashReports;
/**
* This class had to be written because there is not legal way to instantiate a
* non-async
* {@link EventBus} with both a custom identifier and a custom exception
* handler. Which
* is a shame. Guava maintainers know about the issue but have rejected
* solutions multiple
* times <em>without a clearly stated reason</em>; looks like some dirty
* reflection will
* have to do.
* This class had to be written because there is no legal way to instantiate a
* non-async {@link EventBus} with both a custom identifier and a custom
* exception handler. Which is a shame. Guava maintainers know about the issue
* but have rejected solutions multiple times <em>without a clearly stated
* reason</em>; looks like some dirty reflection will have to do.
* <p>
* When explicitly referencing this class, please mention its usage in
* implementation notes because it is unreliable long-term.
*
* @author javapony
*/

View File

@ -15,16 +15,17 @@
* 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.server.world.tile;
package ru.windcorp.progressia.common.resource;
import ru.windcorp.progressia.common.world.generic.GenericTileStack;
import ru.windcorp.progressia.common.world.tile.TileDataStack;
import ru.windcorp.progressia.server.world.ChunkLogic;
import java.io.InputStream;
public abstract class TileLogicStack
extends GenericTileStack<TileLogicStack, TileLogic, ChunkLogic> {
import ru.windcorp.progressia.Progressia;
public abstract TileDataStack getData();
public class ClasspathResourceReader implements ResourceReader {
@Override
public InputStream read(String name) {
return Progressia.class.getClassLoader().getResourceAsStream(name);
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.common.resource;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FilesystemResourceReader implements ResourceReader {
@Override
public InputStream read(String name) {
try {
return Files.newInputStream(Paths.get(name));
} catch (IOException e) {
return null;
}
}
}

View File

@ -30,19 +30,24 @@ import org.lwjgl.BufferUtils;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import ru.windcorp.progressia.Progressia;
import ru.windcorp.progressia.common.util.Named;
import ru.windcorp.progressia.common.util.crash.CrashReports;
public class Resource extends Named {
private final ResourceReader resourceReader;
public Resource(String name) {
public Resource(String name, ResourceReader resourceReader) {
super(name);
this.resourceReader = resourceReader;
}
public InputStream getInputStream() {
// TODO Do proper resource lookup
return Progressia.class.getClassLoader().getResourceAsStream(getName());
return getResourceReader().read(getName());
}
public ResourceReader getResourceReader() {
return resourceReader;
}
public Reader getReader() {

View File

@ -19,9 +19,16 @@
package ru.windcorp.progressia.common.resource;
public class ResourceManager {
private static final ResourceReader CLASSPATH_READER = new ClasspathResourceReader();
private static final ResourceReader FILESYSTEM_READER = new FilesystemResourceReader();
public static Resource getResource(String name) {
return new Resource(name);
return new Resource(name, CLASSPATH_READER);
}
public static Resource getFileResource(String name) {
return new Resource(name, FILESYSTEM_READER);
}
public static Resource getTextureResource(String name) {

View File

@ -0,0 +1,26 @@
/*
* 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.common.resource;
import java.io.InputStream;
public interface ResourceReader {
InputStream read(String name);
}

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