TPL refactor, unified line endings, JavaDoc and full switch to SmartSeverity
This commit is contained in:
parent
60fae86237
commit
2b065249ee
|
@ -51,6 +51,7 @@ version numbers.*
|
|||
|
||||
### Normal priority
|
||||
* The collision system for PlutoStatic
|
||||
* Improve image loading capabilities, possibly rewrite PlutoLib#TPL
|
||||
|
||||
### Low priority
|
||||
* Polishing PlutoLib
|
||||
|
|
|
@ -6,6 +6,22 @@
|
|||
`OutputSplitStream`
|
||||
* `Logger`'s output filenames now look cleaner with `log--YYYY-MM-DD--HH-MM-SS.txt`
|
||||
* `[Logger#setup]` can now throw `IOException`
|
||||
* `[PlutoCore]` As a result, `[PlutoApplication#run]` can now throw `Exception`
|
||||
* `[PlutoCore]` As a result, `[PlutoApplication#run]` can now throw `Exception`
|
||||
* `[PlutoLib]` Updated JavaDoc in `ResourceAddress`, `TPL`, `TPNImage`
|
||||
* `[PlutoLib]` Added `ResourceAddress#openRead` and `ResourceAddress#openWrite`
|
||||
* `[PlutoLib]` Code cleanup in `MiniTime`, `TPL`
|
||||
* `[PlutoLib]` Deprecated `TPL#load(String)` in favor of `TPL#load(ResourceAddress)`,
|
||||
`TPL#load(File)` and `TPL#load(Path)`
|
||||
* `[PlutoTexturing]` Deprecated the `String` variant of `Texture#load`
|
||||
to reflect this change
|
||||
* `[PlutoSpritesheet]` Removed the usage of this method
|
||||
in `DisposablePlaceholderSprite`
|
||||
* `[PlutoLib]` Added an option to flip loaded images with `TPL#loadImageSpecial`
|
||||
and added respective `TPL#loadSpecial` for every `TPL#load`
|
||||
* `[PlutoLib]` *Removed* `TPJImage`
|
||||
* `[PlutoLib]` Removed `TPL#loadPixels`
|
||||
* `[PlutoCore]` Updated `GLFWImageUtil` to remove the usage of `TPJImage`
|
||||
* `[PlutoCore]` `[PlutoApplication]` now properly closes the `Logger` on exit
|
||||
* `[PlutoLib]` Various typo fixes
|
||||
* `[PlutoLib]` Various typo fixes
|
||||
* `[Pluto*]` Deprecated `Severity` for `SmartSeverity` and replaced all usages
|
||||
* `[Pluto*]` Deprecated `CRLF` with `LF` in all Java source files
|
|
@ -1,275 +1,275 @@
|
|||
package cz.tefek.pluto.engine.audio;
|
||||
|
||||
import org.lwjgl.stb.STBVorbis;
|
||||
import org.lwjgl.stb.STBVorbisInfo;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import cz.tefek.pluto.engine.buffer.BufferHelper;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public class AudioLoader
|
||||
{
|
||||
/**
|
||||
* Loads an audio track denoted by this {@link ResourceAddress} into memory
|
||||
* for from-memory Vorbis decoding and streaming. Good for frequently used
|
||||
* medium sized audio files, however it is discouraged to use such a track
|
||||
* in multiple audio sources at once due to the cost of seeking.
|
||||
*/
|
||||
public static ISeekableAudioTrack loadMemoryDecoded(ResourceAddress address)
|
||||
{
|
||||
|
||||
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", address.toString());
|
||||
|
||||
try
|
||||
{
|
||||
return new MemoryDecodedVorbisTrack(address);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", address.toString());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an audio track denoted by this {@link ResourceAddress} into memory
|
||||
* for from-memory PCM streaming. Good for frequently used small audio
|
||||
* files.
|
||||
*/
|
||||
public static ISeekableAudioTrack loadMemoryPCM(ResourceAddress address)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", address.toString());
|
||||
|
||||
try
|
||||
{
|
||||
return new MemoryPCMTrack(address);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", address.toString());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer loadIntoMemory(ResourceAddress addr) throws IOException
|
||||
{
|
||||
var path = addr.toNIOPath();
|
||||
var size = Files.size(path);
|
||||
|
||||
if (size > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new IOException("File '" + addr.toString() + "' is too big to be loaded!");
|
||||
}
|
||||
|
||||
var readData = MemoryUtil.memAlloc((int) size);
|
||||
|
||||
return BufferHelper.readToByteBuffer(path, readData);
|
||||
}
|
||||
|
||||
private abstract static class StreamableTrack implements IAudioStream
|
||||
{
|
||||
protected int channels;
|
||||
protected int sampleRate;
|
||||
|
||||
@Override
|
||||
public int getChannels()
|
||||
{
|
||||
return this.channels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleRate()
|
||||
{
|
||||
return this.sampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class SeekableTrack extends StreamableTrack implements ISeekableAudioTrack
|
||||
{
|
||||
protected int samplesLength;
|
||||
|
||||
@Override
|
||||
public int getLengthInSamples()
|
||||
{
|
||||
return this.samplesLength;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MemoryPCMTrack extends SeekableTrack
|
||||
{
|
||||
private ShortBuffer pcmAudio = null;
|
||||
|
||||
private int sampleOffset = 0;
|
||||
|
||||
private MemoryPCMTrack(ResourceAddress address) throws IOException
|
||||
{
|
||||
long handle = MemoryUtil.NULL;
|
||||
ByteBuffer audioBytes = null;
|
||||
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
audioBytes = loadIntoMemory(address);
|
||||
|
||||
IntBuffer error = stack.mallocInt(1);
|
||||
handle = STBVorbis.stb_vorbis_open_memory(audioBytes, error, null);
|
||||
|
||||
if (handle == MemoryUtil.NULL)
|
||||
{
|
||||
this.close();
|
||||
throw new IOException(String.format("Failed to load '%s', error code %d.\n", address.toString(), error.get(0)));
|
||||
}
|
||||
|
||||
STBVorbisInfo info = STBVorbisInfo.mallocStack(stack);
|
||||
STBVorbis.stb_vorbis_get_info(handle, info);
|
||||
|
||||
this.channels = info.channels();
|
||||
this.sampleRate = info.sample_rate();
|
||||
|
||||
// Downmix to mono, SOUNDS HORRIBLE
|
||||
//
|
||||
// this.channels = 1;
|
||||
// this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
|
||||
// this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
|
||||
// var ptr = stack.pointers(this.pcmAudio);
|
||||
// STBVorbis.stb_vorbis_get_samples_short(handle, ptr, this.samplesLength);
|
||||
|
||||
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
|
||||
this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
|
||||
STBVorbis.stb_vorbis_get_samples_short_interleaved(handle, this.channels, this.pcmAudio);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
this.close();
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemoryUtil.memFree(audioBytes);
|
||||
|
||||
if (handle != MemoryUtil.NULL)
|
||||
{
|
||||
STBVorbis.stb_vorbis_close(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(int sampleIndex)
|
||||
{
|
||||
this.sampleOffset = sampleIndex * this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getSamples(ShortBuffer pcm)
|
||||
{
|
||||
this.pcmAudio.limit(Math.min(this.sampleOffset + pcm.remaining(), this.pcmAudio.capacity()));
|
||||
int read = this.pcmAudio.remaining();
|
||||
pcm.put(this.pcmAudio);
|
||||
this.sampleOffset += read;
|
||||
pcm.clear();
|
||||
|
||||
return read / this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleOffset()
|
||||
{
|
||||
return this.sampleOffset / this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
MemoryUtil.memFree(this.pcmAudio);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MemoryDecodedVorbisTrack extends SeekableTrack
|
||||
{
|
||||
protected long handle;
|
||||
|
||||
private final ByteBuffer encodedAudio;
|
||||
|
||||
private MemoryDecodedVorbisTrack(ResourceAddress address) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
this.encodedAudio = loadIntoMemory(address);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
this.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
IntBuffer error = stack.mallocInt(1);
|
||||
this.handle = STBVorbis.stb_vorbis_open_memory(this.encodedAudio, error, null);
|
||||
|
||||
if (this.handle == MemoryUtil.NULL)
|
||||
{
|
||||
this.close();
|
||||
throw new IOException(String.format("Failed to load '%s', error code %d.\n", address.toString(), error.get(0)));
|
||||
}
|
||||
|
||||
STBVorbisInfo info = STBVorbisInfo.mallocStack(stack);
|
||||
STBVorbis.stb_vorbis_get_info(this.handle, info);
|
||||
|
||||
this.channels = info.channels();
|
||||
this.sampleRate = info.sample_rate();
|
||||
|
||||
}
|
||||
|
||||
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(this.handle);
|
||||
|
||||
Logger.logf(SmartSeverity.AUDIO, "\tSample rate:\t%d\n\t\tChannels:\t%d\n\t\tSamples:\t%d\n", this.sampleRate, this.channels, this.samplesLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getSamples(ShortBuffer pcm)
|
||||
{
|
||||
if (this.handle == MemoryUtil.NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return STBVorbis.stb_vorbis_get_samples_short_interleaved(this.handle, this.getChannels(), pcm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
MemoryUtil.memFree(this.encodedAudio);
|
||||
|
||||
if (this.handle != MemoryUtil.NULL)
|
||||
{
|
||||
STBVorbis.stb_vorbis_close(this.handle);
|
||||
this.handle = MemoryUtil.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void seek(int sampleIndex)
|
||||
{
|
||||
STBVorbis.stb_vorbis_seek(this.handle, sampleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleOffset()
|
||||
{
|
||||
return STBVorbis.stb_vorbis_get_sample_offset(this.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.audio;
|
||||
|
||||
import org.lwjgl.stb.STBVorbis;
|
||||
import org.lwjgl.stb.STBVorbisInfo;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import cz.tefek.pluto.engine.buffer.BufferHelper;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public class AudioLoader
|
||||
{
|
||||
/**
|
||||
* Loads an audio track denoted by this {@link ResourceAddress} into memory
|
||||
* for from-memory Vorbis decoding and streaming. Good for frequently used
|
||||
* medium sized audio files, however it is discouraged to use such a track
|
||||
* in multiple audio sources at once due to the cost of seeking.
|
||||
*/
|
||||
public static ISeekableAudioTrack loadMemoryDecoded(ResourceAddress address)
|
||||
{
|
||||
|
||||
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", address.toString());
|
||||
|
||||
try
|
||||
{
|
||||
return new MemoryDecodedVorbisTrack(address);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", address.toString());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an audio track denoted by this {@link ResourceAddress} into memory
|
||||
* for from-memory PCM streaming. Good for frequently used small audio
|
||||
* files.
|
||||
*/
|
||||
public static ISeekableAudioTrack loadMemoryPCM(ResourceAddress address)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO_PLUS, "Loading audio file: %s\n", address.toString());
|
||||
|
||||
try
|
||||
{
|
||||
return new MemoryPCMTrack(address);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO_ERROR, "Failed to load an audio file: '%s'\n", address.toString());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer loadIntoMemory(ResourceAddress addr) throws IOException
|
||||
{
|
||||
var path = addr.toNIOPath();
|
||||
var size = Files.size(path);
|
||||
|
||||
if (size > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new IOException("File '" + addr.toString() + "' is too big to be loaded!");
|
||||
}
|
||||
|
||||
var readData = MemoryUtil.memAlloc((int) size);
|
||||
|
||||
return BufferHelper.readToByteBuffer(path, readData);
|
||||
}
|
||||
|
||||
private abstract static class StreamableTrack implements IAudioStream
|
||||
{
|
||||
protected int channels;
|
||||
protected int sampleRate;
|
||||
|
||||
@Override
|
||||
public int getChannels()
|
||||
{
|
||||
return this.channels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleRate()
|
||||
{
|
||||
return this.sampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class SeekableTrack extends StreamableTrack implements ISeekableAudioTrack
|
||||
{
|
||||
protected int samplesLength;
|
||||
|
||||
@Override
|
||||
public int getLengthInSamples()
|
||||
{
|
||||
return this.samplesLength;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MemoryPCMTrack extends SeekableTrack
|
||||
{
|
||||
private ShortBuffer pcmAudio = null;
|
||||
|
||||
private int sampleOffset = 0;
|
||||
|
||||
private MemoryPCMTrack(ResourceAddress address) throws IOException
|
||||
{
|
||||
long handle = MemoryUtil.NULL;
|
||||
ByteBuffer audioBytes = null;
|
||||
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
audioBytes = loadIntoMemory(address);
|
||||
|
||||
IntBuffer error = stack.mallocInt(1);
|
||||
handle = STBVorbis.stb_vorbis_open_memory(audioBytes, error, null);
|
||||
|
||||
if (handle == MemoryUtil.NULL)
|
||||
{
|
||||
this.close();
|
||||
throw new IOException(String.format("Failed to load '%s', error code %d.\n", address.toString(), error.get(0)));
|
||||
}
|
||||
|
||||
STBVorbisInfo info = STBVorbisInfo.mallocStack(stack);
|
||||
STBVorbis.stb_vorbis_get_info(handle, info);
|
||||
|
||||
this.channels = info.channels();
|
||||
this.sampleRate = info.sample_rate();
|
||||
|
||||
// Downmix to mono, SOUNDS HORRIBLE
|
||||
//
|
||||
// this.channels = 1;
|
||||
// this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
|
||||
// this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
|
||||
// var ptr = stack.pointers(this.pcmAudio);
|
||||
// STBVorbis.stb_vorbis_get_samples_short(handle, ptr, this.samplesLength);
|
||||
|
||||
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(handle) * this.channels;
|
||||
this.pcmAudio = MemoryUtil.memAllocShort(this.samplesLength);
|
||||
STBVorbis.stb_vorbis_get_samples_short_interleaved(handle, this.channels, this.pcmAudio);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
this.close();
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemoryUtil.memFree(audioBytes);
|
||||
|
||||
if (handle != MemoryUtil.NULL)
|
||||
{
|
||||
STBVorbis.stb_vorbis_close(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(int sampleIndex)
|
||||
{
|
||||
this.sampleOffset = sampleIndex * this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getSamples(ShortBuffer pcm)
|
||||
{
|
||||
this.pcmAudio.limit(Math.min(this.sampleOffset + pcm.remaining(), this.pcmAudio.capacity()));
|
||||
int read = this.pcmAudio.remaining();
|
||||
pcm.put(this.pcmAudio);
|
||||
this.sampleOffset += read;
|
||||
pcm.clear();
|
||||
|
||||
return read / this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleOffset()
|
||||
{
|
||||
return this.sampleOffset / this.getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
MemoryUtil.memFree(this.pcmAudio);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MemoryDecodedVorbisTrack extends SeekableTrack
|
||||
{
|
||||
protected long handle;
|
||||
|
||||
private final ByteBuffer encodedAudio;
|
||||
|
||||
private MemoryDecodedVorbisTrack(ResourceAddress address) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
this.encodedAudio = loadIntoMemory(address);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
this.close();
|
||||
throw e;
|
||||
}
|
||||
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
IntBuffer error = stack.mallocInt(1);
|
||||
this.handle = STBVorbis.stb_vorbis_open_memory(this.encodedAudio, error, null);
|
||||
|
||||
if (this.handle == MemoryUtil.NULL)
|
||||
{
|
||||
this.close();
|
||||
throw new IOException(String.format("Failed to load '%s', error code %d.\n", address.toString(), error.get(0)));
|
||||
}
|
||||
|
||||
STBVorbisInfo info = STBVorbisInfo.mallocStack(stack);
|
||||
STBVorbis.stb_vorbis_get_info(this.handle, info);
|
||||
|
||||
this.channels = info.channels();
|
||||
this.sampleRate = info.sample_rate();
|
||||
|
||||
}
|
||||
|
||||
this.samplesLength = STBVorbis.stb_vorbis_stream_length_in_samples(this.handle);
|
||||
|
||||
Logger.logf(SmartSeverity.AUDIO, "\tSample rate:\t%d\n\t\tChannels:\t%d\n\t\tSamples:\t%d\n", this.sampleRate, this.channels, this.samplesLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getSamples(ShortBuffer pcm)
|
||||
{
|
||||
if (this.handle == MemoryUtil.NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return STBVorbis.stb_vorbis_get_samples_short_interleaved(this.handle, this.getChannels(), pcm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
MemoryUtil.memFree(this.encodedAudio);
|
||||
|
||||
if (this.handle != MemoryUtil.NULL)
|
||||
{
|
||||
STBVorbis.stb_vorbis_close(this.handle);
|
||||
this.handle = MemoryUtil.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void seek(int sampleIndex)
|
||||
{
|
||||
STBVorbis.stb_vorbis_seek(this.handle, sampleIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSampleOffset()
|
||||
{
|
||||
return STBVorbis.stb_vorbis_get_sample_offset(this.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,128 +1,128 @@
|
|||
package cz.tefek.pluto.engine.audio.al;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.openal.AL;
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.ALC;
|
||||
import org.lwjgl.openal.ALC10;
|
||||
import org.lwjgl.openal.ALCCapabilities;
|
||||
import org.lwjgl.openal.ALCapabilities;
|
||||
import org.lwjgl.openal.EXTThreadLocalContext;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import cz.tefek.pluto.Pluto;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class AudioEngine
|
||||
{
|
||||
private static ThreadLocal<Long> device = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected Long initialValue()
|
||||
{
|
||||
return MemoryUtil.NULL;
|
||||
}
|
||||
};
|
||||
|
||||
private static ThreadLocal<Long> context = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected Long initialValue()
|
||||
{
|
||||
return MemoryUtil.NULL;
|
||||
}
|
||||
};
|
||||
|
||||
private static ThreadLocal<ALCapabilities> capabilities = new ThreadLocal<>();
|
||||
|
||||
public static void initialize()
|
||||
{
|
||||
var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null);
|
||||
if (devicePtr == MemoryUtil.NULL)
|
||||
{
|
||||
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to open the default audio device.");
|
||||
|
||||
// No audio device found, but the game should not crash just because we have no audio
|
||||
return;
|
||||
}
|
||||
|
||||
device.set(devicePtr);
|
||||
|
||||
var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null);
|
||||
if (contextPtr == MemoryUtil.NULL)
|
||||
{
|
||||
ALC10.alcCloseDevice(devicePtr);
|
||||
|
||||
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to create an OpenAL context.");
|
||||
|
||||
// The game should not crash just because we have no audio
|
||||
return;
|
||||
}
|
||||
|
||||
context.set(contextPtr);
|
||||
|
||||
EXTThreadLocalContext.alcSetThreadContext(contextPtr);
|
||||
|
||||
ALCCapabilities deviceCaps = ALC.createCapabilities(devicePtr);
|
||||
var alCapabilities = AL.createCapabilities(deviceCaps);
|
||||
|
||||
if (Pluto.DEBUG_MODE)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO, "OpenAL10: %b\n", alCapabilities.OpenAL10);
|
||||
Logger.logf(SmartSeverity.AUDIO, "OpenAL11: %b\n", alCapabilities.OpenAL11);
|
||||
}
|
||||
|
||||
capabilities.set(alCapabilities);
|
||||
}
|
||||
|
||||
public static void setSpeed(Vector3f speed)
|
||||
{
|
||||
AL10.alListener3f(AL10.AL_VELOCITY, speed.x, speed.y, speed.z);
|
||||
}
|
||||
|
||||
public static void setPosition(Vector3f position)
|
||||
{
|
||||
AL10.alListener3f(AL10.AL_POSITION, position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
public static void setVolume(float volume)
|
||||
{
|
||||
AL10.alListenerf(AL10.AL_GAIN, volume);
|
||||
}
|
||||
|
||||
public static void setOrientation(Vector3f at, Vector3f up)
|
||||
{
|
||||
float[] data = new float[6];
|
||||
data[0] = at.x;
|
||||
data[1] = at.y;
|
||||
data[2] = at.z;
|
||||
data[3] = up.x;
|
||||
data[4] = up.y;
|
||||
data[5] = up.z;
|
||||
AL10.alListenerfv(AL10.AL_ORIENTATION, data);
|
||||
}
|
||||
|
||||
public static boolean isReady()
|
||||
{
|
||||
return capabilities.get() != null;
|
||||
}
|
||||
|
||||
public static void exit()
|
||||
{
|
||||
EXTThreadLocalContext.alcSetThreadContext(MemoryUtil.NULL);
|
||||
ALC10.alcDestroyContext(context.get());
|
||||
ALC10.alcCloseDevice(device.get());
|
||||
|
||||
context.remove();
|
||||
device.remove();
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.audio.al;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.openal.AL;
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.ALC;
|
||||
import org.lwjgl.openal.ALC10;
|
||||
import org.lwjgl.openal.ALCCapabilities;
|
||||
import org.lwjgl.openal.ALCapabilities;
|
||||
import org.lwjgl.openal.EXTThreadLocalContext;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import cz.tefek.pluto.Pluto;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class AudioEngine
|
||||
{
|
||||
private static ThreadLocal<Long> device = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected Long initialValue()
|
||||
{
|
||||
return MemoryUtil.NULL;
|
||||
}
|
||||
};
|
||||
|
||||
private static ThreadLocal<Long> context = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected Long initialValue()
|
||||
{
|
||||
return MemoryUtil.NULL;
|
||||
}
|
||||
};
|
||||
|
||||
private static ThreadLocal<ALCapabilities> capabilities = new ThreadLocal<>();
|
||||
|
||||
public static void initialize()
|
||||
{
|
||||
var devicePtr = ALC10.alcOpenDevice((ByteBuffer) null);
|
||||
if (devicePtr == MemoryUtil.NULL)
|
||||
{
|
||||
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to open the default audio device.");
|
||||
|
||||
// No audio device found, but the game should not crash just because we have no audio
|
||||
return;
|
||||
}
|
||||
|
||||
device.set(devicePtr);
|
||||
|
||||
var contextPtr = ALC10.alcCreateContext(devicePtr, (IntBuffer) null);
|
||||
if (contextPtr == MemoryUtil.NULL)
|
||||
{
|
||||
ALC10.alcCloseDevice(devicePtr);
|
||||
|
||||
Logger.log(SmartSeverity.AUDIO_ERROR, "Failed to create an OpenAL context.");
|
||||
|
||||
// The game should not crash just because we have no audio
|
||||
return;
|
||||
}
|
||||
|
||||
context.set(contextPtr);
|
||||
|
||||
EXTThreadLocalContext.alcSetThreadContext(contextPtr);
|
||||
|
||||
ALCCapabilities deviceCaps = ALC.createCapabilities(devicePtr);
|
||||
var alCapabilities = AL.createCapabilities(deviceCaps);
|
||||
|
||||
if (Pluto.DEBUG_MODE)
|
||||
{
|
||||
Logger.logf(SmartSeverity.AUDIO, "OpenAL10: %b\n", alCapabilities.OpenAL10);
|
||||
Logger.logf(SmartSeverity.AUDIO, "OpenAL11: %b\n", alCapabilities.OpenAL11);
|
||||
}
|
||||
|
||||
capabilities.set(alCapabilities);
|
||||
}
|
||||
|
||||
public static void setSpeed(Vector3f speed)
|
||||
{
|
||||
AL10.alListener3f(AL10.AL_VELOCITY, speed.x, speed.y, speed.z);
|
||||
}
|
||||
|
||||
public static void setPosition(Vector3f position)
|
||||
{
|
||||
AL10.alListener3f(AL10.AL_POSITION, position.x, position.y, position.z);
|
||||
}
|
||||
|
||||
public static void setVolume(float volume)
|
||||
{
|
||||
AL10.alListenerf(AL10.AL_GAIN, volume);
|
||||
}
|
||||
|
||||
public static void setOrientation(Vector3f at, Vector3f up)
|
||||
{
|
||||
float[] data = new float[6];
|
||||
data[0] = at.x;
|
||||
data[1] = at.y;
|
||||
data[2] = at.z;
|
||||
data[3] = up.x;
|
||||
data[4] = up.y;
|
||||
data[5] = up.z;
|
||||
AL10.alListenerfv(AL10.AL_ORIENTATION, data);
|
||||
}
|
||||
|
||||
public static boolean isReady()
|
||||
{
|
||||
return capabilities.get() != null;
|
||||
}
|
||||
|
||||
public static void exit()
|
||||
{
|
||||
EXTThreadLocalContext.alcSetThreadContext(MemoryUtil.NULL);
|
||||
ALC10.alcDestroyContext(context.get());
|
||||
ALC10.alcCloseDevice(device.get());
|
||||
|
||||
context.remove();
|
||||
device.remove();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,124 +1,124 @@
|
|||
package cz.tefek.pluto.engine.audio.al;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.SOFTDirectChannels;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import cz.tefek.pluto.engine.audio.IAudioStream;
|
||||
import cz.tefek.pluto.engine.audio.ISeekableAudioTrack;
|
||||
|
||||
public class AudioTrack extends AudioSource
|
||||
{
|
||||
private static final int BUFFER_SIZE_PER_CHANNEL = 16384;
|
||||
|
||||
private final IAudioStream track;
|
||||
|
||||
private final int format;
|
||||
|
||||
private static final int DOUBLE_BUFFER = 2;
|
||||
|
||||
private final int[] buffers;
|
||||
|
||||
private boolean closeOnFinish;
|
||||
|
||||
private final ShortBuffer pcm;
|
||||
|
||||
public AudioTrack(IAudioStream track)
|
||||
{
|
||||
this.track = track;
|
||||
|
||||
switch (track.getChannels())
|
||||
{
|
||||
case 1:
|
||||
this.format = AL10.AL_FORMAT_MONO16;
|
||||
break;
|
||||
case 2:
|
||||
this.format = AL10.AL_FORMAT_STEREO16;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported number of channels: " + track.getChannels());
|
||||
}
|
||||
|
||||
int bufferSize = track.getChannels() * BUFFER_SIZE_PER_CHANNEL;
|
||||
|
||||
this.pcm = MemoryUtil.memAllocShort(bufferSize);
|
||||
|
||||
this.buffers = new int[DOUBLE_BUFFER];
|
||||
AL10.alGenBuffers(this.buffers);
|
||||
|
||||
AL10.alSourcei(this.source, SOFTDirectChannels.AL_DIRECT_CHANNELS_SOFT, AL10.AL_TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
AL10.alDeleteBuffers(this.buffers);
|
||||
AL10.alDeleteSources(this.source);
|
||||
|
||||
MemoryUtil.memFree(this.pcm);
|
||||
}
|
||||
|
||||
public boolean play()
|
||||
{
|
||||
if (this.track instanceof ISeekableAudioTrack)
|
||||
{
|
||||
((ISeekableAudioTrack) this.track).rewind();
|
||||
}
|
||||
|
||||
for (int buf : this.buffers)
|
||||
{
|
||||
this.stream(buf);
|
||||
}
|
||||
|
||||
AL10.alSourcePlay(this.source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setCloseOnFinish()
|
||||
{
|
||||
this.closeOnFinish = true;
|
||||
}
|
||||
|
||||
private void stream(int buffer)
|
||||
{
|
||||
this.pcm.clear();
|
||||
int samplesPerChannel = this.track.getSamples(this.pcm);
|
||||
|
||||
if (samplesPerChannel == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var samples = samplesPerChannel * this.track.getChannels();
|
||||
this.pcm.limit(samples);
|
||||
AL10.alBufferData(buffer, this.format, this.pcm, this.track.getSampleRate());
|
||||
AL10.alSourceQueueBuffers(this.source, buffer);
|
||||
}
|
||||
|
||||
public boolean update()
|
||||
{
|
||||
int processed = AL10.alGetSourcei(this.source, AL10.AL_BUFFERS_PROCESSED);
|
||||
|
||||
for (int i = 0; i < processed; i++)
|
||||
{
|
||||
int buffer = AL10.alSourceUnqueueBuffers(this.source);
|
||||
this.stream(buffer);
|
||||
}
|
||||
|
||||
if (AL10.alGetSourcei(this.source, AL10.AL_SOURCE_STATE) == AL10.AL_STOPPED)
|
||||
{
|
||||
if (this.closeOnFinish)
|
||||
{
|
||||
this.close();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
package cz.tefek.pluto.engine.audio.al;
|
||||
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.SOFTDirectChannels;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import cz.tefek.pluto.engine.audio.IAudioStream;
|
||||
import cz.tefek.pluto.engine.audio.ISeekableAudioTrack;
|
||||
|
||||
public class AudioTrack extends AudioSource
|
||||
{
|
||||
private static final int BUFFER_SIZE_PER_CHANNEL = 16384;
|
||||
|
||||
private final IAudioStream track;
|
||||
|
||||
private final int format;
|
||||
|
||||
private static final int DOUBLE_BUFFER = 2;
|
||||
|
||||
private final int[] buffers;
|
||||
|
||||
private boolean closeOnFinish;
|
||||
|
||||
private final ShortBuffer pcm;
|
||||
|
||||
public AudioTrack(IAudioStream track)
|
||||
{
|
||||
this.track = track;
|
||||
|
||||
switch (track.getChannels())
|
||||
{
|
||||
case 1:
|
||||
this.format = AL10.AL_FORMAT_MONO16;
|
||||
break;
|
||||
case 2:
|
||||
this.format = AL10.AL_FORMAT_STEREO16;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported number of channels: " + track.getChannels());
|
||||
}
|
||||
|
||||
int bufferSize = track.getChannels() * BUFFER_SIZE_PER_CHANNEL;
|
||||
|
||||
this.pcm = MemoryUtil.memAllocShort(bufferSize);
|
||||
|
||||
this.buffers = new int[DOUBLE_BUFFER];
|
||||
AL10.alGenBuffers(this.buffers);
|
||||
|
||||
AL10.alSourcei(this.source, SOFTDirectChannels.AL_DIRECT_CHANNELS_SOFT, AL10.AL_TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
AL10.alDeleteBuffers(this.buffers);
|
||||
AL10.alDeleteSources(this.source);
|
||||
|
||||
MemoryUtil.memFree(this.pcm);
|
||||
}
|
||||
|
||||
public boolean play()
|
||||
{
|
||||
if (this.track instanceof ISeekableAudioTrack)
|
||||
{
|
||||
((ISeekableAudioTrack) this.track).rewind();
|
||||
}
|
||||
|
||||
for (int buf : this.buffers)
|
||||
{
|
||||
this.stream(buf);
|
||||
}
|
||||
|
||||
AL10.alSourcePlay(this.source);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setCloseOnFinish()
|
||||
{
|
||||
this.closeOnFinish = true;
|
||||
}
|
||||
|
||||
private void stream(int buffer)
|
||||
{
|
||||
this.pcm.clear();
|
||||
int samplesPerChannel = this.track.getSamples(this.pcm);
|
||||
|
||||
if (samplesPerChannel == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var samples = samplesPerChannel * this.track.getChannels();
|
||||
this.pcm.limit(samples);
|
||||
AL10.alBufferData(buffer, this.format, this.pcm, this.track.getSampleRate());
|
||||
AL10.alSourceQueueBuffers(this.source, buffer);
|
||||
}
|
||||
|
||||
public boolean update()
|
||||
{
|
||||
int processed = AL10.alGetSourcei(this.source, AL10.AL_BUFFERS_PROCESSED);
|
||||
|
||||
for (int i = 0; i < processed; i++)
|
||||
{
|
||||
int buffer = AL10.alSourceUnqueueBuffers(this.source);
|
||||
this.stream(buffer);
|
||||
}
|
||||
|
||||
if (AL10.alGetSourcei(this.source, AL10.AL_SOURCE_STATE) == AL10.AL_STOPPED)
|
||||
{
|
||||
if (this.closeOnFinish)
|
||||
{
|
||||
this.close();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,55 +1,53 @@
|
|||
package cz.tefek.pluto.engine.input;
|
||||
|
||||
import org.lwjgl.glfw.GLFWCursorPosCallback;
|
||||
|
||||
public class CursorPositionCallback extends GLFWCursorPosCallback
|
||||
{
|
||||
private double posX;
|
||||
private double posY;
|
||||
|
||||
private double deltaX;
|
||||
private double deltaY;
|
||||
|
||||
@Override
|
||||
public void invoke(long window, double xpos, double ypos)
|
||||
{
|
||||
this.deltaX = this.posX - xpos;
|
||||
this.deltaY = this.posY - ypos;
|
||||
|
||||
this.posX = xpos;
|
||||
this.posY = ypos;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
this.deltaX = 0;
|
||||
this.deltaY = 0;
|
||||
}
|
||||
|
||||
public double getX()
|
||||
{
|
||||
return this.posX;
|
||||
}
|
||||
|
||||
public double getY()
|
||||
{
|
||||
return this.posY;
|
||||
}
|
||||
|
||||
public double getDeltaX()
|
||||
{
|
||||
return this.deltaX;
|
||||
}
|
||||
|
||||
public double getDeltaY()
|
||||
{
|
||||
return this.deltaY;
|
||||
}
|
||||
|
||||
public boolean isInside(int x, int y, int x2, int y2)
|
||||
{
|
||||
boolean inside = this.getX() > x && this.getX() < x2 && this.getY() > y && this.getY() < y2;
|
||||
|
||||
return inside;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.input;
|
||||
|
||||
import org.lwjgl.glfw.GLFWCursorPosCallback;
|
||||
|
||||
public class CursorPositionCallback extends GLFWCursorPosCallback
|
||||
{
|
||||
private double posX;
|
||||
private double posY;
|
||||
|
||||
private double deltaX;
|
||||
private double deltaY;
|
||||
|
||||
@Override
|
||||
public void invoke(long window, double xpos, double ypos)
|
||||
{
|
||||
this.deltaX = this.posX - xpos;
|
||||
this.deltaY = this.posY - ypos;
|
||||
|
||||
this.posX = xpos;
|
||||
this.posY = ypos;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
this.deltaX = 0;
|
||||
this.deltaY = 0;
|
||||
}
|
||||
|
||||
public double getX()
|
||||
{
|
||||
return this.posX;
|
||||
}
|
||||
|
||||
public double getY()
|
||||
{
|
||||
return this.posY;
|
||||
}
|
||||
|
||||
public double getDeltaX()
|
||||
{
|
||||
return this.deltaX;
|
||||
}
|
||||
|
||||
public double getDeltaY()
|
||||
{
|
||||
return this.deltaY;
|
||||
}
|
||||
|
||||
public boolean isInside(int x, int y, int x2, int y2)
|
||||
{
|
||||
return this.getX() > x && this.getX() < x2 && this.getY() > y && this.getY() < y2;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,58 +1,58 @@
|
|||
package cz.tefek.pluto.engine.input;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_PRESS;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_RELEASE;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.lwjgl.glfw.GLFWKeyCallback;
|
||||
|
||||
public class KeyboardInputCallback extends GLFWKeyCallback
|
||||
{
|
||||
private Set<Integer> keyPressed = new HashSet<>();
|
||||
private Set<Integer> keyDown = new HashSet<>();
|
||||
private Set<Integer> keyReleased = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void invoke(long window, int key, int scancode, int action, int mods)
|
||||
{
|
||||
if (key < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
{
|
||||
this.keyDown.add(key);
|
||||
this.keyPressed.add(key);
|
||||
}
|
||||
|
||||
if (action == GLFW_RELEASE)
|
||||
{
|
||||
this.keyDown.remove(key);
|
||||
this.keyReleased.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetPressed()
|
||||
{
|
||||
this.keyPressed.clear();
|
||||
this.keyReleased.clear();
|
||||
}
|
||||
|
||||
public boolean hasBeenPressed(int key)
|
||||
{
|
||||
return this.keyPressed.contains(key);
|
||||
}
|
||||
|
||||
public boolean hasBeenReleased(int key)
|
||||
{
|
||||
return this.keyReleased.contains(key);
|
||||
}
|
||||
|
||||
public boolean isKeyDown(int key)
|
||||
{
|
||||
return this.keyDown.contains(key);
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.input;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_PRESS;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_RELEASE;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.lwjgl.glfw.GLFWKeyCallback;
|
||||
|
||||
public class KeyboardInputCallback extends GLFWKeyCallback
|
||||
{
|
||||
private Set<Integer> keyPressed = new HashSet<>();
|
||||
private Set<Integer> keyDown = new HashSet<>();
|
||||
private Set<Integer> keyReleased = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void invoke(long window, int key, int scancode, int action, int mods)
|
||||
{
|
||||
if (key < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
{
|
||||
this.keyDown.add(key);
|
||||
this.keyPressed.add(key);
|
||||
}
|
||||
|
||||
if (action == GLFW_RELEASE)
|
||||
{
|
||||
this.keyDown.remove(key);
|
||||
this.keyReleased.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetPressed()
|
||||
{
|
||||
this.keyPressed.clear();
|
||||
this.keyReleased.clear();
|
||||
}
|
||||
|
||||
public boolean hasBeenPressed(int key)
|
||||
{
|
||||
return this.keyPressed.contains(key);
|
||||
}
|
||||
|
||||
public boolean hasBeenReleased(int key)
|
||||
{
|
||||
return this.keyReleased.contains(key);
|
||||
}
|
||||
|
||||
public boolean isKeyDown(int key)
|
||||
{
|
||||
return this.keyDown.contains(key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
package cz.tefek.pluto.engine.input;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWMouseButtonCallback;
|
||||
|
||||
public class MouseButtonCallback extends GLFWMouseButtonCallback
|
||||
{
|
||||
public boolean[] buttonClicked = new boolean[32];
|
||||
public boolean[] buttonDown = new boolean[32];
|
||||
public boolean[] buttonReleased = new boolean[32];
|
||||
|
||||
@Override
|
||||
public void invoke(long window, int button, int action, int mods)
|
||||
{
|
||||
this.buttonClicked[button] = action == GLFW.GLFW_PRESS;
|
||||
this.buttonDown[button] = action != GLFW.GLFW_RELEASE;
|
||||
this.buttonReleased[button] = action == GLFW.GLFW_RELEASE;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
for (int i = 0; i < this.buttonClicked.length; i++)
|
||||
{
|
||||
this.buttonClicked[i] = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.buttonClicked.length; i++)
|
||||
{
|
||||
this.buttonReleased[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package cz.tefek.pluto.engine.input;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWMouseButtonCallback;
|
||||
|
||||
public class MouseButtonCallback extends GLFWMouseButtonCallback
|
||||
{
|
||||
public boolean[] buttonClicked = new boolean[32];
|
||||
public boolean[] buttonDown = new boolean[32];
|
||||
public boolean[] buttonReleased = new boolean[32];
|
||||
|
||||
@Override
|
||||
public void invoke(long window, int button, int action, int mods)
|
||||
{
|
||||
this.buttonClicked[button] = action == GLFW.GLFW_PRESS;
|
||||
this.buttonDown[button] = action != GLFW.GLFW_RELEASE;
|
||||
this.buttonReleased[button] = action == GLFW.GLFW_RELEASE;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
for (int i = 0; i < this.buttonClicked.length; i++)
|
||||
{
|
||||
this.buttonClicked[i] = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.buttonClicked.length; i++)
|
||||
{
|
||||
this.buttonReleased[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
package cz.tefek.pluto.engine.input;
|
||||
|
||||
import org.lwjgl.glfw.GLFWScrollCallback;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class ScrollInputCallback extends GLFWScrollCallback
|
||||
{
|
||||
private double xScroll;
|
||||
private double yScroll;
|
||||
|
||||
@Override
|
||||
public void invoke(long window, double xoffset, double yoffset)
|
||||
{
|
||||
xScroll = xoffset;
|
||||
yScroll = yoffset;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
xScroll = 0;
|
||||
yScroll = 0;
|
||||
}
|
||||
|
||||
public double getYScroll()
|
||||
{
|
||||
return yScroll;
|
||||
}
|
||||
|
||||
public void setYScroll(double yScroll)
|
||||
{
|
||||
this.yScroll = yScroll;
|
||||
}
|
||||
|
||||
public double getXScroll()
|
||||
{
|
||||
return xScroll;
|
||||
}
|
||||
|
||||
public void setXScroll(double xScroll)
|
||||
{
|
||||
this.xScroll = xScroll;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.input;
|
||||
|
||||
import org.lwjgl.glfw.GLFWScrollCallback;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class ScrollInputCallback extends GLFWScrollCallback
|
||||
{
|
||||
private double xScroll;
|
||||
private double yScroll;
|
||||
|
||||
@Override
|
||||
public void invoke(long window, double xoffset, double yoffset)
|
||||
{
|
||||
xScroll = xoffset;
|
||||
yScroll = yoffset;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
xScroll = 0;
|
||||
yScroll = 0;
|
||||
}
|
||||
|
||||
public double getYScroll()
|
||||
{
|
||||
return yScroll;
|
||||
}
|
||||
|
||||
public void setYScroll(double yScroll)
|
||||
{
|
||||
this.yScroll = yScroll;
|
||||
}
|
||||
|
||||
public double getXScroll()
|
||||
{
|
||||
return xScroll;
|
||||
}
|
||||
|
||||
public void setXScroll(double xScroll)
|
||||
{
|
||||
this.xScroll = xScroll;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,102 +1,102 @@
|
|||
package cz.tefek.pluto.engine.graphics.font;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.texture.MagFilter;
|
||||
import cz.tefek.pluto.engine.graphics.texture.MinFilter;
|
||||
import cz.tefek.pluto.engine.graphics.texture.Texture;
|
||||
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
|
||||
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
|
||||
import cz.tefek.pluto.engine.gui.font.CharacterInfo;
|
||||
import cz.tefek.pluto.engine.gui.font.Font;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public class FontManager
|
||||
{
|
||||
private static Map<String, Font> fonts = new HashMap<>();
|
||||
|
||||
public static void loadFont(ResourceAddress address)
|
||||
{
|
||||
String fontname = null;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
var def = new HashMap<Character, CharacterInfo>();
|
||||
|
||||
int row = 0;
|
||||
|
||||
try (BufferedReader br = Files.newBufferedReader(address.copy().branch("definitions").fileExtension("txt").toNIOPath()))
|
||||
{
|
||||
String line;
|
||||
while ((line = br.readLine()) != null)
|
||||
{
|
||||
if (line.startsWith("//"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row == 0)
|
||||
{
|
||||
String[] fontinfo;
|
||||
fontinfo = line.split(",");
|
||||
|
||||
fontname = fontinfo[0];
|
||||
|
||||
String[] dim = fontinfo[1].split("x");
|
||||
|
||||
width = Integer.parseInt(dim[0]);
|
||||
height = Integer.parseInt(dim[1]);
|
||||
}
|
||||
|
||||
if (row > 0)
|
||||
{
|
||||
String[] offs = null;
|
||||
|
||||
offs = line.split(" ")[1].split(";");
|
||||
|
||||
def.put(line.charAt(0), new CharacterInfo(row - 1, Integer.parseInt(offs[0]), Integer.parseInt(offs[1])));
|
||||
}
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
br.close();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "Could not load font: " + address.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Font font = new Font(fontname, width, height, def);
|
||||
RectangleTexture texture = new RectangleTexture();
|
||||
texture.load(address.copy().branch("tex").fileExtension("png"), MagFilter.NEAREST, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
|
||||
font.setTexture(texture);
|
||||
|
||||
fonts.put(fontname, font);
|
||||
}
|
||||
|
||||
public static void unloadAll()
|
||||
{
|
||||
fonts.values().stream().map(Font::getTexture).forEach(Texture::delete);
|
||||
fonts.clear();
|
||||
}
|
||||
|
||||
public static Font getFontByName(String fontname)
|
||||
{
|
||||
var font = fonts.get(fontname);
|
||||
|
||||
if (font == null)
|
||||
{
|
||||
Logger.log(SmartSeverity.WARNING, "Font with name " + fontname + " could not be found, using the default one instead (if there is one).");
|
||||
return fonts.get("default");
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.font;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.texture.MagFilter;
|
||||
import cz.tefek.pluto.engine.graphics.texture.MinFilter;
|
||||
import cz.tefek.pluto.engine.graphics.texture.Texture;
|
||||
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
|
||||
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
|
||||
import cz.tefek.pluto.engine.gui.font.CharacterInfo;
|
||||
import cz.tefek.pluto.engine.gui.font.Font;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public class FontManager
|
||||
{
|
||||
private static Map<String, Font> fonts = new HashMap<>();
|
||||
|
||||
public static void loadFont(ResourceAddress address)
|
||||
{
|
||||
String fontname = null;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
var def = new HashMap<Character, CharacterInfo>();
|
||||
|
||||
int row = 0;
|
||||
|
||||
try (BufferedReader br = Files.newBufferedReader(address.copy().branch("definitions").fileExtension("txt").toNIOPath()))
|
||||
{
|
||||
String line;
|
||||
while ((line = br.readLine()) != null)
|
||||
{
|
||||
if (line.startsWith("//"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row == 0)
|
||||
{
|
||||
String[] fontinfo;
|
||||
fontinfo = line.split(",");
|
||||
|
||||
fontname = fontinfo[0];
|
||||
|
||||
String[] dim = fontinfo[1].split("x");
|
||||
|
||||
width = Integer.parseInt(dim[0]);
|
||||
height = Integer.parseInt(dim[1]);
|
||||
}
|
||||
|
||||
if (row > 0)
|
||||
{
|
||||
String[] offs = null;
|
||||
|
||||
offs = line.split(" ")[1].split(";");
|
||||
|
||||
def.put(line.charAt(0), new CharacterInfo(row - 1, Integer.parseInt(offs[0]), Integer.parseInt(offs[1])));
|
||||
}
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
br.close();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "Could not load font: " + address.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Font font = new Font(fontname, width, height, def);
|
||||
RectangleTexture texture = new RectangleTexture();
|
||||
texture.load(address.copy().branch("tex").fileExtension("png"), MagFilter.NEAREST, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
|
||||
font.setTexture(texture);
|
||||
|
||||
fonts.put(fontname, font);
|
||||
}
|
||||
|
||||
public static void unloadAll()
|
||||
{
|
||||
fonts.values().stream().map(Font::getTexture).forEach(Texture::delete);
|
||||
fonts.clear();
|
||||
}
|
||||
|
||||
public static Font getFontByName(String fontname)
|
||||
{
|
||||
var font = fonts.get(fontname);
|
||||
|
||||
if (font == null)
|
||||
{
|
||||
Logger.log(SmartSeverity.WARNING, "Font with name " + fontname + " could not be found, using the default one instead (if there is one).");
|
||||
return fonts.get("default");
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
package cz.tefek.pluto.engine.graphics.font;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
|
||||
import cz.tefek.pluto.engine.shader.ShaderBase;
|
||||
import cz.tefek.pluto.engine.shader.ShaderProgram;
|
||||
import cz.tefek.pluto.engine.shader.VertexArrayAttribute;
|
||||
import cz.tefek.pluto.engine.shader.uniform.Uniform;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformBoolean;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection;
|
||||
|
||||
@ShaderProgram
|
||||
public final class FontShader extends ShaderBase
|
||||
{
|
||||
@AutoViewportProjection
|
||||
@Uniform(name = "projection")
|
||||
public UniformMat4 projectionMatrix;
|
||||
|
||||
@Uniform(name = "transformation")
|
||||
public UniformMat4 transformationMatrix;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvBase;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvDelta;
|
||||
|
||||
@Uniform
|
||||
public UniformVec4 recolor;
|
||||
|
||||
@Uniform
|
||||
public UniformBoolean italic;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.POSITION)
|
||||
public int position;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.UV)
|
||||
public int uvCoords;
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.font;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
|
||||
import cz.tefek.pluto.engine.shader.ShaderBase;
|
||||
import cz.tefek.pluto.engine.shader.ShaderProgram;
|
||||
import cz.tefek.pluto.engine.shader.VertexArrayAttribute;
|
||||
import cz.tefek.pluto.engine.shader.uniform.Uniform;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformBoolean;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection;
|
||||
|
||||
@ShaderProgram
|
||||
public final class FontShader extends ShaderBase
|
||||
{
|
||||
@AutoViewportProjection
|
||||
@Uniform(name = "projection")
|
||||
public UniformMat4 projectionMatrix;
|
||||
|
||||
@Uniform(name = "transformation")
|
||||
public UniformMat4 transformationMatrix;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvBase;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvDelta;
|
||||
|
||||
@Uniform
|
||||
public UniformVec4 recolor;
|
||||
|
||||
@Uniform
|
||||
public UniformBoolean italic;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.POSITION)
|
||||
public int position;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.UV)
|
||||
public int uvCoords;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
package cz.tefek.pluto.engine.gui.font;
|
||||
|
||||
public class CharacterInfo
|
||||
{
|
||||
int leftOffs;
|
||||
private int rightOffs;
|
||||
int number;
|
||||
|
||||
public CharacterInfo(int number, int leftOffs, int rightOffs)
|
||||
{
|
||||
this.number = number;
|
||||
this.leftOffs = leftOffs;
|
||||
this.rightOffs = rightOffs;
|
||||
}
|
||||
|
||||
public int getLeftOffset()
|
||||
{
|
||||
return this.leftOffs;
|
||||
}
|
||||
|
||||
public int getNumber()
|
||||
{
|
||||
return this.number;
|
||||
}
|
||||
|
||||
public int getRightOffset()
|
||||
{
|
||||
return this.rightOffs;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.gui.font;
|
||||
|
||||
public class CharacterInfo
|
||||
{
|
||||
int leftOffs;
|
||||
private int rightOffs;
|
||||
int number;
|
||||
|
||||
public CharacterInfo(int number, int leftOffs, int rightOffs)
|
||||
{
|
||||
this.number = number;
|
||||
this.leftOffs = leftOffs;
|
||||
this.rightOffs = rightOffs;
|
||||
}
|
||||
|
||||
public int getLeftOffset()
|
||||
{
|
||||
return this.leftOffs;
|
||||
}
|
||||
|
||||
public int getNumber()
|
||||
{
|
||||
return this.number;
|
||||
}
|
||||
|
||||
public int getRightOffset()
|
||||
{
|
||||
return this.rightOffs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +1,59 @@
|
|||
package cz.tefek.pluto.engine.gui.font;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
|
||||
|
||||
public class Font
|
||||
{
|
||||
private String name;
|
||||
private int width;
|
||||
private int height;
|
||||
private HashMap<Character, CharacterInfo> definitions;
|
||||
private RectangleTexture texture;
|
||||
|
||||
public Font(String name, int width, int height, HashMap<Character, CharacterInfo> def)
|
||||
{
|
||||
this.name = name;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.definitions = def;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getTextureWidth()
|
||||
{
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public int getTextureHeight()
|
||||
{
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public RectangleTexture getTexture()
|
||||
{
|
||||
return this.texture;
|
||||
}
|
||||
|
||||
public void setTexture(RectangleTexture texture)
|
||||
{
|
||||
this.texture = texture;
|
||||
this.width = texture.getWidth();
|
||||
this.height = texture.getHeight();
|
||||
}
|
||||
|
||||
public HashMap<Character, CharacterInfo> getDefinitions()
|
||||
{
|
||||
return this.definitions;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.gui.font;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.texture.texture2d.RectangleTexture;
|
||||
|
||||
public class Font
|
||||
{
|
||||
private String name;
|
||||
private int width;
|
||||
private int height;
|
||||
private HashMap<Character, CharacterInfo> definitions;
|
||||
private RectangleTexture texture;
|
||||
|
||||
public Font(String name, int width, int height, HashMap<Character, CharacterInfo> def)
|
||||
{
|
||||
this.name = name;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.definitions = def;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getTextureWidth()
|
||||
{
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public int getTextureHeight()
|
||||
{
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public RectangleTexture getTexture()
|
||||
{
|
||||
return this.texture;
|
||||
}
|
||||
|
||||
public void setTexture(RectangleTexture texture)
|
||||
{
|
||||
this.texture = texture;
|
||||
this.width = texture.getWidth();
|
||||
this.height = texture.getHeight();
|
||||
}
|
||||
|
||||
public HashMap<Character, CharacterInfo> getDefinitions()
|
||||
{
|
||||
return this.definitions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,311 +1,311 @@
|
|||
package cz.tefek.pluto.engine.gui.font;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.font.FontManager;
|
||||
import cz.tefek.pluto.engine.graphics.font.FontShader;
|
||||
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.QuadPresets;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.VertexArray;
|
||||
import cz.tefek.pluto.engine.math.TransformationMatrix;
|
||||
|
||||
public class FontRenderer
|
||||
{
|
||||
private static final int LINE_HEIGHT = 30;
|
||||
private static final int SPACE_WIDTH = 12;
|
||||
|
||||
private static final int CHAR_WIDTH = 16;
|
||||
private static final int CHAR_HEIGHT = 24;
|
||||
|
||||
private static VertexArray charVAO;
|
||||
private static FontShader fontShader;
|
||||
|
||||
public static void load(FontShader defaultFontShaderIn)
|
||||
{
|
||||
charVAO = QuadPresets.basicNoNeg();
|
||||
fontShader = defaultFontShaderIn;
|
||||
}
|
||||
|
||||
public static void unload()
|
||||
{
|
||||
if (charVAO != null)
|
||||
{
|
||||
charVAO.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public static void prepareInstance(Font font)
|
||||
{
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
fontShader.start();
|
||||
charVAO.bind();
|
||||
charVAO.enableAllAttributes();
|
||||
font.getTexture().bind();
|
||||
}
|
||||
|
||||
public static void drawString(float xPos, float yPos, Object string, Object color, float relativeSize, String fontname, boolean isShadow)
|
||||
{
|
||||
Font font = FontManager.getFontByName(fontname);
|
||||
|
||||
if (font == null)
|
||||
{
|
||||
System.err.println("Font doesn't exist: " + fontname);
|
||||
return;
|
||||
}
|
||||
|
||||
float charWidth = CHAR_WIDTH * relativeSize;
|
||||
float charHeight = CHAR_HEIGHT * relativeSize;
|
||||
|
||||
float lineHeight = LINE_HEIGHT * relativeSize;
|
||||
float spaceWidth = SPACE_WIDTH * relativeSize;
|
||||
|
||||
prepareInstance(font);
|
||||
|
||||
color(color);
|
||||
|
||||
String text = String.valueOf(string);
|
||||
|
||||
float drawX = xPos;
|
||||
float drawY = yPos;
|
||||
|
||||
for (int characterIndex = 0; characterIndex < text.length(); characterIndex++)
|
||||
{
|
||||
int column = 0;
|
||||
int row = 0;
|
||||
|
||||
var currentChar = text.charAt(characterIndex);
|
||||
|
||||
if (text.length() > characterIndex + 1)
|
||||
{
|
||||
var nextChar = text.charAt(characterIndex + 1);
|
||||
|
||||
if (currentChar == '\\' && nextChar == '&')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Inline coloring (tm) :) -> &c[0xff770077]
|
||||
if (text.length() > characterIndex + 13)
|
||||
{
|
||||
if (currentChar == '&' && nextChar == 'c' && text.charAt(characterIndex + 2) == '[' && text.charAt(characterIndex + 13) == ']')
|
||||
{
|
||||
char c = '0';
|
||||
char cBef = '0';
|
||||
|
||||
if (characterIndex > 0)
|
||||
{
|
||||
c = text.charAt(characterIndex - 1);
|
||||
}
|
||||
|
||||
if (characterIndex > 1)
|
||||
{
|
||||
cBef = text.charAt(characterIndex - 2);
|
||||
}
|
||||
|
||||
if (c != '\\' || cBef == '\\' && c == '\\')
|
||||
{
|
||||
if (!isShadow)
|
||||
{
|
||||
char[] col = new char[10];
|
||||
|
||||
text.getChars(characterIndex + 3, characterIndex + 13, col, 0);
|
||||
|
||||
String clr = String.valueOf(col);
|
||||
|
||||
color(clr);
|
||||
}
|
||||
|
||||
characterIndex += 13;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (text.length() > characterIndex + 2)
|
||||
{
|
||||
if (currentChar == '&' && nextChar == 'i' && (text.charAt(characterIndex + 2) == '1' || text.charAt(characterIndex + 2) == '0'))
|
||||
{
|
||||
char c = '0';
|
||||
char cBef = '0';
|
||||
|
||||
if (characterIndex > 0)
|
||||
{
|
||||
c = text.charAt(characterIndex - 1);
|
||||
}
|
||||
|
||||
if (characterIndex > 1)
|
||||
{
|
||||
cBef = text.charAt(characterIndex - 2);
|
||||
}
|
||||
|
||||
if (c != '\\' || cBef == '\\' && c == '\\')
|
||||
{
|
||||
if (text.charAt(characterIndex + 2) == '1')
|
||||
{
|
||||
italic(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
italic(false);
|
||||
}
|
||||
|
||||
characterIndex += 2;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float shift = 0;
|
||||
|
||||
switch (currentChar)
|
||||
{
|
||||
case '\n':
|
||||
color(color);
|
||||
drawX = xPos;
|
||||
drawY += lineHeight;
|
||||
continue;
|
||||
|
||||
case ' ':
|
||||
drawX += spaceWidth;
|
||||
continue;
|
||||
|
||||
case 'g':
|
||||
case 'y':
|
||||
case 'p':
|
||||
case 'j':
|
||||
shift = 6 * relativeSize;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var fontDefs = font.getDefinitions();
|
||||
var charInf = fontDefs.get(currentChar);
|
||||
|
||||
if (charInf == null)
|
||||
{
|
||||
charInf = fontDefs.get('?');
|
||||
}
|
||||
|
||||
var atlasIndex = charInf.getNumber();
|
||||
|
||||
row = atlasIndex / CHAR_WIDTH;
|
||||
column = atlasIndex % CHAR_WIDTH;
|
||||
|
||||
// Position of the current character in the texture atlas in pixels
|
||||
float u = column * CHAR_WIDTH;
|
||||
float v = row * CHAR_HEIGHT;
|
||||
|
||||
// Offset from the left
|
||||
drawX -= charInf.getLeftOffset() * relativeSize;
|
||||
|
||||
float posY = shift + drawY;
|
||||
|
||||
fontShader.uvBase.load(u, font.getTextureHeight() - v - CHAR_HEIGHT);
|
||||
fontShader.uvDelta.load(CHAR_WIDTH, CHAR_HEIGHT);
|
||||
|
||||
Matrix4f transformation = TransformationMatrix.create(new Vector3f(drawX, posY, 0), new Vector3f(0, 0, 0), new Vector3f(charWidth, charHeight, 0));
|
||||
|
||||
fontShader.transformationMatrix.load(transformation);
|
||||
|
||||
charVAO.draw(DrawMode.TRIANGLES);
|
||||
|
||||
drawX += charWidth;
|
||||
drawX -= charInf.getRightOffset() * relativeSize;
|
||||
drawX += relativeSize;
|
||||
}
|
||||
|
||||
italic(false);
|
||||
}
|
||||
|
||||
public static void color(Object color)
|
||||
{
|
||||
color(color, false);
|
||||
}
|
||||
|
||||
public static void color(Object color, boolean darker)
|
||||
{
|
||||
float dark = 0;
|
||||
|
||||
if (darker)
|
||||
{
|
||||
dark = 0.35f;
|
||||
}
|
||||
|
||||
if (color instanceof float[])
|
||||
{
|
||||
float[] c = (float[]) color;
|
||||
|
||||
if (c.length == 4)
|
||||
{
|
||||
recolor(c[0] - dark, c[1] - dark, c[2] - dark, c[3]);
|
||||
}
|
||||
else if (c.length == 3)
|
||||
{
|
||||
recolor(c[0] - dark, c[1] - dark, c[2] - dark, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (color instanceof String)
|
||||
{
|
||||
String col = (String) color;
|
||||
|
||||
if (col.length() == 7)
|
||||
{
|
||||
recolor(Integer.valueOf(col.substring(1, 3), 16) / 256f - dark, Integer.valueOf(col.substring(3, 5), 16) / 256f - dark, Integer.valueOf(col.substring(5, 7), 16) / 256f - dark, 1);
|
||||
}
|
||||
|
||||
if (col.length() == 10)
|
||||
{
|
||||
recolor(Integer.valueOf(col.substring(4, 6), 16) / 256f - dark, Integer.valueOf(col.substring(6, 8), 16) / 256f - dark, Integer.valueOf(col.substring(8, 10), 16) / 256f - dark, Integer.valueOf(col.substring(2, 4), 16) / 256f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void recolor(float r, float g, float b, float a)
|
||||
{
|
||||
fontShader.recolor.load(r, g, b, a);
|
||||
}
|
||||
|
||||
private static void italic(boolean useItalic)
|
||||
{
|
||||
fontShader.italic.load(useItalic);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size)
|
||||
{
|
||||
drawString(x, y, text, new float[] { r, g, b, a }, size, "default", false);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text)
|
||||
{
|
||||
drawString(x, y, text, new float[] { 0, 0, 0, 1 }, 1, "default", false);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, boolean isShadow)
|
||||
{
|
||||
drawString(x, y, text, new float[] { r, g, b, a }, size, "default", isShadow);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, String fontname, boolean isShadow)
|
||||
{
|
||||
drawString(x, y, text, new float[] { r, g, b, a }, size, fontname, isShadow);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, String fontname)
|
||||
{
|
||||
drawString(x, y, text, new float[] { r, g, b, a }, size, fontname, false);
|
||||
}
|
||||
|
||||
public static void drawString(float xPos, float yPos, Object string, Object color, float relativeSize, String fontname)
|
||||
{
|
||||
drawString(xPos, yPos, string, color, relativeSize, fontname, false);
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.gui.font;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.font.FontManager;
|
||||
import cz.tefek.pluto.engine.graphics.font.FontShader;
|
||||
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.QuadPresets;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.VertexArray;
|
||||
import cz.tefek.pluto.engine.math.TransformationMatrix;
|
||||
|
||||
public class FontRenderer
|
||||
{
|
||||
private static final int LINE_HEIGHT = 30;
|
||||
private static final int SPACE_WIDTH = 12;
|
||||
|
||||
private static final int CHAR_WIDTH = 16;
|
||||
private static final int CHAR_HEIGHT = 24;
|
||||
|
||||
private static VertexArray charVAO;
|
||||
private static FontShader fontShader;
|
||||
|
||||
public static void load(FontShader defaultFontShaderIn)
|
||||
{
|
||||
charVAO = QuadPresets.basicNoNeg();
|
||||
fontShader = defaultFontShaderIn;
|
||||
}
|
||||
|
||||
public static void unload()
|
||||
{
|
||||
if (charVAO != null)
|
||||
{
|
||||
charVAO.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public static void prepareInstance(Font font)
|
||||
{
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
fontShader.start();
|
||||
charVAO.bind();
|
||||
charVAO.enableAllAttributes();
|
||||
font.getTexture().bind();
|
||||
}
|
||||
|
||||
public static void drawString(float xPos, float yPos, Object string, Object color, float relativeSize, String fontname, boolean isShadow)
|
||||
{
|
||||
Font font = FontManager.getFontByName(fontname);
|
||||
|
||||
if (font == null)
|
||||
{
|
||||
System.err.println("Font doesn't exist: " + fontname);
|
||||
return;
|
||||
}
|
||||
|
||||
float charWidth = CHAR_WIDTH * relativeSize;
|
||||
float charHeight = CHAR_HEIGHT * relativeSize;
|
||||
|
||||
float lineHeight = LINE_HEIGHT * relativeSize;
|
||||
float spaceWidth = SPACE_WIDTH * relativeSize;
|
||||
|
||||
prepareInstance(font);
|
||||
|
||||
color(color);
|
||||
|
||||
String text = String.valueOf(string);
|
||||
|
||||
float drawX = xPos;
|
||||
float drawY = yPos;
|
||||
|
||||
for (int characterIndex = 0; characterIndex < text.length(); characterIndex++)
|
||||
{
|
||||
int column = 0;
|
||||
int row = 0;
|
||||
|
||||
var currentChar = text.charAt(characterIndex);
|
||||
|
||||
if (text.length() > characterIndex + 1)
|
||||
{
|
||||
var nextChar = text.charAt(characterIndex + 1);
|
||||
|
||||
if (currentChar == '\\' && nextChar == '&')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Inline coloring (tm) :) -> &c[0xff770077]
|
||||
if (text.length() > characterIndex + 13)
|
||||
{
|
||||
if (currentChar == '&' && nextChar == 'c' && text.charAt(characterIndex + 2) == '[' && text.charAt(characterIndex + 13) == ']')
|
||||
{
|
||||
char c = '0';
|
||||
char cBef = '0';
|
||||
|
||||
if (characterIndex > 0)
|
||||
{
|
||||
c = text.charAt(characterIndex - 1);
|
||||
}
|
||||
|
||||
if (characterIndex > 1)
|
||||
{
|
||||
cBef = text.charAt(characterIndex - 2);
|
||||
}
|
||||
|
||||
if (c != '\\' || cBef == '\\' && c == '\\')
|
||||
{
|
||||
if (!isShadow)
|
||||
{
|
||||
char[] col = new char[10];
|
||||
|
||||
text.getChars(characterIndex + 3, characterIndex + 13, col, 0);
|
||||
|
||||
String clr = String.valueOf(col);
|
||||
|
||||
color(clr);
|
||||
}
|
||||
|
||||
characterIndex += 13;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (text.length() > characterIndex + 2)
|
||||
{
|
||||
if (currentChar == '&' && nextChar == 'i' && (text.charAt(characterIndex + 2) == '1' || text.charAt(characterIndex + 2) == '0'))
|
||||
{
|
||||
char c = '0';
|
||||
char cBef = '0';
|
||||
|
||||
if (characterIndex > 0)
|
||||
{
|
||||
c = text.charAt(characterIndex - 1);
|
||||
}
|
||||
|
||||
if (characterIndex > 1)
|
||||
{
|
||||
cBef = text.charAt(characterIndex - 2);
|
||||
}
|
||||
|
||||
if (c != '\\' || cBef == '\\' && c == '\\')
|
||||
{
|
||||
if (text.charAt(characterIndex + 2) == '1')
|
||||
{
|
||||
italic(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
italic(false);
|
||||
}
|
||||
|
||||
characterIndex += 2;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float shift = 0;
|
||||
|
||||
switch (currentChar)
|
||||
{
|
||||
case '\n':
|
||||
color(color);
|
||||
drawX = xPos;
|
||||
drawY += lineHeight;
|
||||
continue;
|
||||
|
||||
case ' ':
|
||||
drawX += spaceWidth;
|
||||
continue;
|
||||
|
||||
case 'g':
|
||||
case 'y':
|
||||
case 'p':
|
||||
case 'j':
|
||||
shift = 6 * relativeSize;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var fontDefs = font.getDefinitions();
|
||||
var charInf = fontDefs.get(currentChar);
|
||||
|
||||
if (charInf == null)
|
||||
{
|
||||
charInf = fontDefs.get('?');
|
||||
}
|
||||
|
||||
var atlasIndex = charInf.getNumber();
|
||||
|
||||
row = atlasIndex / CHAR_WIDTH;
|
||||
column = atlasIndex % CHAR_WIDTH;
|
||||
|
||||
// Position of the current character in the texture atlas in pixels
|
||||
float u = column * CHAR_WIDTH;
|
||||
float v = row * CHAR_HEIGHT;
|
||||
|
||||
// Offset from the left
|
||||
drawX -= charInf.getLeftOffset() * relativeSize;
|
||||
|
||||
float posY = shift + drawY;
|
||||
|
||||
fontShader.uvBase.load(u, font.getTextureHeight() - v - CHAR_HEIGHT);
|
||||
fontShader.uvDelta.load(CHAR_WIDTH, CHAR_HEIGHT);
|
||||
|
||||
Matrix4f transformation = TransformationMatrix.create(new Vector3f(drawX, posY, 0), new Vector3f(0, 0, 0), new Vector3f(charWidth, charHeight, 0));
|
||||
|
||||
fontShader.transformationMatrix.load(transformation);
|
||||
|
||||
charVAO.draw(DrawMode.TRIANGLES);
|
||||
|
||||
drawX += charWidth;
|
||||
drawX -= charInf.getRightOffset() * relativeSize;
|
||||
drawX += relativeSize;
|
||||
}
|
||||
|
||||
italic(false);
|
||||
}
|
||||
|
||||
public static void color(Object color)
|
||||
{
|
||||
color(color, false);
|
||||
}
|
||||
|
||||
public static void color(Object color, boolean darker)
|
||||
{
|
||||
float dark = 0;
|
||||
|
||||
if (darker)
|
||||
{
|
||||
dark = 0.35f;
|
||||
}
|
||||
|
||||
if (color instanceof float[])
|
||||
{
|
||||
float[] c = (float[]) color;
|
||||
|
||||
if (c.length == 4)
|
||||
{
|
||||
recolor(c[0] - dark, c[1] - dark, c[2] - dark, c[3]);
|
||||
}
|
||||
else if (c.length == 3)
|
||||
{
|
||||
recolor(c[0] - dark, c[1] - dark, c[2] - dark, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (color instanceof String)
|
||||
{
|
||||
String col = (String) color;
|
||||
|
||||
if (col.length() == 7)
|
||||
{
|
||||
recolor(Integer.valueOf(col.substring(1, 3), 16) / 256f - dark, Integer.valueOf(col.substring(3, 5), 16) / 256f - dark, Integer.valueOf(col.substring(5, 7), 16) / 256f - dark, 1);
|
||||
}
|
||||
|
||||
if (col.length() == 10)
|
||||
{
|
||||
recolor(Integer.valueOf(col.substring(4, 6), 16) / 256f - dark, Integer.valueOf(col.substring(6, 8), 16) / 256f - dark, Integer.valueOf(col.substring(8, 10), 16) / 256f - dark, Integer.valueOf(col.substring(2, 4), 16) / 256f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void recolor(float r, float g, float b, float a)
|
||||
{
|
||||
fontShader.recolor.load(r, g, b, a);
|
||||
}
|
||||
|
||||
private static void italic(boolean useItalic)
|
||||
{
|
||||
fontShader.italic.load(useItalic);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size)
|
||||
{
|
||||
drawString(x, y, text, new float[] { r, g, b, a }, size, "default", false);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text)
|
||||
{
|
||||
drawString(x, y, text, new float[] { 0, 0, 0, 1 }, 1, "default", false);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, boolean isShadow)
|
||||
{
|
||||
drawString(x, y, text, new float[] { r, g, b, a }, size, "default", isShadow);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, String fontname, boolean isShadow)
|
||||
{
|
||||
drawString(x, y, text, new float[] { r, g, b, a }, size, fontname, isShadow);
|
||||
}
|
||||
|
||||
public static void drawString(float x, float y, Object text, float r, float g, float b, float a, float size, String fontname)
|
||||
{
|
||||
drawString(x, y, text, new float[] { r, g, b, a }, size, fontname, false);
|
||||
}
|
||||
|
||||
public static void drawString(float xPos, float yPos, Object string, Object color, float relativeSize, String fontname)
|
||||
{
|
||||
drawString(xPos, yPos, string, color, relativeSize, fontname, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
package cz.tefek.pluto;
|
||||
|
||||
/**
|
||||
* Constants shared by all Pluto libraries.
|
||||
*
|
||||
* @since pre-alpha
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class Pluto
|
||||
{
|
||||
public static final boolean DEBUG_MODE = Boolean.valueOf(System.getProperty("cz.tefek.pluto.debug"));
|
||||
|
@ -8,41 +15,57 @@ public class Pluto
|
|||
|
||||
/**
|
||||
* The year version number, changes with breaking API changes.
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static final int VERSION_YEAR = 20;
|
||||
|
||||
/**
|
||||
* The major version number, changes with breaking API changes.
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static final int VERSION_MAJOR = 2;
|
||||
|
||||
/**
|
||||
* The minor version number, changes with backwards-compatible API changes.
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static final int VERSION_MINOR = 0;
|
||||
|
||||
/**
|
||||
* The patch number, changes with backwards-compatible fixes and patches.
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static final int VERSION_PATCH = 0;
|
||||
|
||||
/**
|
||||
* Denotes whether this build is a pre-release build.
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static final boolean PRERELEASE = true;
|
||||
|
||||
/**
|
||||
* The name of this pre-release, e.g. alpha, beta, RC and similar.
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static final String PRERELEASE_NAME = "alpha";
|
||||
|
||||
/**
|
||||
* The pre-release patch number, incremented by 1 with *any* pre-release change.
|
||||
* The pre-release patch number, incremented by 1 with *any* pre-release update.
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static final int PRERELEASE_PATCH = 1;
|
||||
|
||||
/**
|
||||
* The combined version string.
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static final String VERSION = VERSION_YEAR + "." + VERSION_MAJOR + "." + VERSION_MINOR + "." + VERSION_PATCH + (PRERELEASE ? "-" + PRERELEASE_NAME + "." + PRERELEASE_PATCH : "");
|
||||
}
|
||||
|
|
|
@ -1,315 +1,319 @@
|
|||
package cz.tefek.pluto.chrono;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A helper class to convert from a time span in milliseconds to a simplified
|
||||
* time span {@link String} format and vice versa. Note this action is fully
|
||||
* reversible at the cost of losing millisecond precision.
|
||||
*
|
||||
* <h3>MiniTime format specification:</h3>
|
||||
*
|
||||
* <pre>
|
||||
* [Nw][Nd][Nh][Nm][Ns]
|
||||
*
|
||||
* w - weeks
|
||||
* d - days
|
||||
* h - hours
|
||||
* m - minutes
|
||||
* s - seconds
|
||||
*
|
||||
* N - a decimal integer
|
||||
* </pre>
|
||||
* <ul>
|
||||
* <li>At least one value is required.</li>
|
||||
* <li>At least time unit is required.</li>
|
||||
* <li>Time units are case insensitive.</li>
|
||||
* <li>String cannot start with a letter or end with a number.</li>
|
||||
* <li>All values must be valid <i>positive</i> 32-bit signed integers.</li>
|
||||
* <li>All values except the first one must be less than 1 of the previous time
|
||||
* unit.</li>
|
||||
* <li>Skipping a time unit assumes zero of that time unit.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>Permitted values:</h3>
|
||||
* <ul>
|
||||
* <li>standard MiniTime scheme, from <code>0s</code> to
|
||||
* <code>2147483647w6d23h59m59s</code></li>
|
||||
* <li>string <code>"forever"</code> (parses as {@link Long#MAX_VALUE})</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public class MiniTime
|
||||
{
|
||||
private static class MiniTimeCouldNotBeParsedException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -5403949842120041373L;
|
||||
|
||||
public MiniTimeCouldNotBeParsedException()
|
||||
{
|
||||
super("Time period could not be parsed. Correct format: \\_w\\_d\\_h\\_m\\_s **without spaces** between the units. You can skip a time unit. Example: 1h15m");
|
||||
}
|
||||
}
|
||||
|
||||
private static final TimeUnit miliseconds = TimeUnit.MILLISECONDS;
|
||||
|
||||
private static final int DAYS_IN_WEEK = 7;
|
||||
private static final int HOURS_IN_DAY = 24;
|
||||
private static final int MINUTES_IN_HOUR = 60;
|
||||
private static final int SECONDS_IN_MINUTE = 60;
|
||||
private static final int MILLIS_IN_MINUTE = 1000;
|
||||
|
||||
/**
|
||||
* Converts a MiniTime spec string to the respective time duration in
|
||||
* milliseconds.
|
||||
*
|
||||
* @param input The source MiniTime non-null string
|
||||
* @return The resulting time span in milliseconds
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public static long parse(String input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new IllegalArgumentException("MiniTime string cannot be null!");
|
||||
}
|
||||
|
||||
// Nothing to parse
|
||||
if (input.isEmpty())
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
if (input.equalsIgnoreCase("forever"))
|
||||
{
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
// Follow the scheme
|
||||
if (!input.matches("[0-9]*[wW]?[0-9]*[dD]?[0-9]*[hH]?[0-9]*[mM]?[0-9]*[sS]?"))
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
// 4584 of what? Potatoes?
|
||||
if (input.matches("[0-9]+"))
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
// Where are the numbers?
|
||||
if (input.matches("[a-zA-Z]+"))
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
// It shouldn't start with a letter
|
||||
if (input.matches("^[a-zA-Z].+"))
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
var nrs = input.split("[a-zA-Z]");
|
||||
var letters = input.split("[0-9]+");
|
||||
|
||||
if (nrs.length != letters.length)
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
long time = 0;
|
||||
|
||||
for (int i = 1; i < nrs.length; i++)
|
||||
{
|
||||
var type = letters[i - 1];
|
||||
int number = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// The only time this fails is when the number is too long
|
||||
number = Integer.parseUnsignedInt(nrs[i]);
|
||||
}
|
||||
catch (NumberFormatException nfe)
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
var allow = 0L;
|
||||
var multiplier = 0;
|
||||
|
||||
switch (type.toLowerCase())
|
||||
{
|
||||
case "w":
|
||||
case "W":
|
||||
allow = Integer.MAX_VALUE;
|
||||
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * DAYS_IN_WEEK * MILLIS_IN_MINUTE;
|
||||
break;
|
||||
case "d":
|
||||
case "D":
|
||||
allow = DAYS_IN_WEEK;
|
||||
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * MILLIS_IN_MINUTE;
|
||||
break;
|
||||
case "h":
|
||||
case "H":
|
||||
allow = HOURS_IN_DAY;
|
||||
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * MILLIS_IN_MINUTE;
|
||||
break;
|
||||
case "m":
|
||||
case "M":
|
||||
allow = MINUTES_IN_HOUR;
|
||||
multiplier = SECONDS_IN_MINUTE * MILLIS_IN_MINUTE;
|
||||
break;
|
||||
case "s":
|
||||
case "S":
|
||||
allow = SECONDS_IN_MINUTE;
|
||||
multiplier = MILLIS_IN_MINUTE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// The top one can be more than it normally could have, for example you can
|
||||
// issue a ban for 48h but not 46h120m (it looks dumb)
|
||||
if (i == 1)
|
||||
{
|
||||
allow = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (number > allow)
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
time += multiplier * number;
|
||||
}
|
||||
|
||||
return System.currentTimeMillis() + time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time span between two Unix-epoch millisecond time points to a
|
||||
* MiniTime string. <i>Note ALL time spans larger or equal than
|
||||
* {@link Integer#MAX_VALUE} will be permanently converted to "forever".</i>
|
||||
*
|
||||
* @param before The first time point in Unix-time milliseconds
|
||||
* @param after The first time point in Unix-time milliseconds
|
||||
* @return The resulting MiniTime string
|
||||
*
|
||||
* @throws IllegalArgumentException on a negative time duration
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public static String fromMillisDiffNow(long after, long before)
|
||||
{
|
||||
if (after == Long.MAX_VALUE)
|
||||
{
|
||||
return "forever";
|
||||
}
|
||||
|
||||
return formatDiff(after - before);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time span between now and a future time point in Unix-epoch
|
||||
* milliseconds to a MiniTime string. <i>Note ALL time spans larger or equal
|
||||
* than {@link Integer#MAX_VALUE} will be permanently converted to
|
||||
* "forever".</i>
|
||||
*
|
||||
* @param future The source time span in milliseconds
|
||||
* @return The resulting MiniTime string
|
||||
*
|
||||
* @throws IllegalArgumentException on a negative time duration
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public static String fromMillisDiffNow(long future)
|
||||
{
|
||||
if (future == Long.MAX_VALUE)
|
||||
{
|
||||
return "forever";
|
||||
}
|
||||
|
||||
var diff = future - System.currentTimeMillis();
|
||||
|
||||
return formatDiff(diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time span milliseconds to a MiniTime string. <i>Note ALL time
|
||||
* spans larger or equal than {@link Integer#MAX_VALUE} will be permanently
|
||||
* converted to "forever".</i>
|
||||
*
|
||||
* @param diff The source time span in milliseconds
|
||||
* @return The resulting MiniTime string
|
||||
*
|
||||
* @throws IllegalArgumentException on a negative time duration
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public static String formatDiff(long diff)
|
||||
{
|
||||
if (diff < 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Negative time span cannot be converted to MiniTime.");
|
||||
}
|
||||
|
||||
var xweeks = miliseconds.toDays(diff) / DAYS_IN_WEEK;
|
||||
|
||||
if (xweeks > Integer.MAX_VALUE)
|
||||
{
|
||||
return "forever";
|
||||
}
|
||||
|
||||
var xdays = miliseconds.toDays(diff) % DAYS_IN_WEEK;
|
||||
var xhours = miliseconds.toHours(diff) % HOURS_IN_DAY;
|
||||
var xminutes = miliseconds.toMinutes(diff) % MINUTES_IN_HOUR;
|
||||
var xseconds = miliseconds.toSeconds(diff) % SECONDS_IN_MINUTE;
|
||||
|
||||
return formatTime(xweeks, xdays, xhours, xminutes, xseconds);
|
||||
}
|
||||
|
||||
private static String formatTime(long weeks, long days, long hours, long minutes, long seconds)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (weeks > 0)
|
||||
{
|
||||
sb.append(weeks);
|
||||
sb.append('w');
|
||||
}
|
||||
if (days > 0)
|
||||
{
|
||||
sb.append(days);
|
||||
sb.append('d');
|
||||
}
|
||||
if (hours > 0)
|
||||
{
|
||||
sb.append(hours);
|
||||
sb.append('h');
|
||||
}
|
||||
if (minutes > 0)
|
||||
{
|
||||
sb.append(minutes);
|
||||
sb.append('m');
|
||||
}
|
||||
if (seconds > 0)
|
||||
{
|
||||
sb.append(seconds);
|
||||
sb.append('s');
|
||||
}
|
||||
|
||||
var timeStr = sb.toString();
|
||||
|
||||
return timeStr.isEmpty() ? "0s" : timeStr;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.chrono;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A helper class to convert from a time span in milliseconds to a simplified
|
||||
* time span {@link String} format and vice versa. Note this action is fully
|
||||
* reversible at the cost of losing millisecond precision.
|
||||
*
|
||||
* <h3>MiniTime format specification:</h3>
|
||||
*
|
||||
* <pre>
|
||||
* [Nw][Nd][Nh][Nm][Ns]
|
||||
*
|
||||
* w - weeks
|
||||
* d - days
|
||||
* h - hours
|
||||
* m - minutes
|
||||
* s - seconds
|
||||
*
|
||||
* N - a decimal integer
|
||||
* </pre>
|
||||
* <ul>
|
||||
* <li>At least one value is required.</li>
|
||||
* <li>At least time unit is required.</li>
|
||||
* <li>Time units are case insensitive.</li>
|
||||
* <li>String cannot start with a letter or end with a number.</li>
|
||||
* <li>All values must be valid <i>positive</i> 32-bit signed integers.</li>
|
||||
* <li>All values except the first one must be less than 1 of the previous time
|
||||
* unit.</li>
|
||||
* <li>Skipping a time unit assumes zero of that time unit.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>Permitted values:</h3>
|
||||
* <ul>
|
||||
* <li>standard MiniTime scheme, from <code>0s</code> to
|
||||
* <code>2147483647w6d23h59m59s</code></li>
|
||||
* <li>string <code>"forever"</code> (parses as {@link Long#MAX_VALUE})</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public class MiniTime
|
||||
{
|
||||
private static class MiniTimeCouldNotBeParsedException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -5403949842120041373L;
|
||||
|
||||
public MiniTimeCouldNotBeParsedException()
|
||||
{
|
||||
super("Time period could not be parsed. Correct format: \\_w\\_d\\_h\\_m\\_s **without spaces** between the units. You can skip a time unit. Example: 1h15m");
|
||||
}
|
||||
}
|
||||
|
||||
private static final TimeUnit miliseconds = TimeUnit.MILLISECONDS;
|
||||
|
||||
private static final int DAYS_IN_WEEK = 7;
|
||||
private static final int HOURS_IN_DAY = 24;
|
||||
private static final int MINUTES_IN_HOUR = 60;
|
||||
private static final int SECONDS_IN_MINUTE = 60;
|
||||
private static final int MILLIS_IN_MINUTE = 1000;
|
||||
|
||||
/**
|
||||
* Converts a MiniTime spec string to the respective time duration in
|
||||
* milliseconds.
|
||||
*
|
||||
* @param input The source MiniTime non-null string
|
||||
* @return The resulting time span in milliseconds
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public static long parse(String input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new IllegalArgumentException("MiniTime string cannot be null!");
|
||||
}
|
||||
|
||||
// Nothing to parse
|
||||
if (input.isEmpty())
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
if (input.equalsIgnoreCase("forever"))
|
||||
{
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
// Follow the scheme
|
||||
if (!input.matches("[0-9]*[wW]?[0-9]*[dD]?[0-9]*[hH]?[0-9]*[mM]?[0-9]*[sS]?"))
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
// 4584 of what? Potatoes?
|
||||
if (input.matches("[0-9]+"))
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
// Where are the numbers?
|
||||
if (input.matches("[a-zA-Z]+"))
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
// It shouldn't start with a letter
|
||||
if (input.matches("^[a-zA-Z].+"))
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
var nrs = input.split("[a-zA-Z]");
|
||||
var letters = input.split("[0-9]+");
|
||||
|
||||
if (nrs.length != letters.length)
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
long time = 0;
|
||||
|
||||
for (int i = 1; i < nrs.length; i++)
|
||||
{
|
||||
var type = letters[i - 1];
|
||||
int number;
|
||||
|
||||
try
|
||||
{
|
||||
// The only time this fails is when the number is too long
|
||||
number = Integer.parseUnsignedInt(nrs[i]);
|
||||
}
|
||||
catch (NumberFormatException nfe)
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
var allow = 0L;
|
||||
var multiplier = 0;
|
||||
|
||||
switch (type.toLowerCase())
|
||||
{
|
||||
case "w":
|
||||
case "W":
|
||||
allow = Integer.MAX_VALUE;
|
||||
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * DAYS_IN_WEEK * MILLIS_IN_MINUTE;
|
||||
break;
|
||||
case "d":
|
||||
case "D":
|
||||
allow = DAYS_IN_WEEK;
|
||||
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY * MILLIS_IN_MINUTE;
|
||||
break;
|
||||
case "h":
|
||||
case "H":
|
||||
allow = HOURS_IN_DAY;
|
||||
multiplier = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * MILLIS_IN_MINUTE;
|
||||
break;
|
||||
case "m":
|
||||
case "M":
|
||||
allow = MINUTES_IN_HOUR;
|
||||
multiplier = SECONDS_IN_MINUTE * MILLIS_IN_MINUTE;
|
||||
break;
|
||||
case "s":
|
||||
case "S":
|
||||
allow = SECONDS_IN_MINUTE;
|
||||
multiplier = MILLIS_IN_MINUTE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// The top one can be more than it normally could have, for example you can
|
||||
// issue a ban for 48h but not 46h120m (it looks dumb)
|
||||
if (i == 1)
|
||||
{
|
||||
allow = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (number > allow)
|
||||
{
|
||||
throw new MiniTimeCouldNotBeParsedException();
|
||||
}
|
||||
|
||||
time += multiplier * number;
|
||||
}
|
||||
|
||||
return System.currentTimeMillis() + time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time span between two Unix-epoch millisecond time points to a
|
||||
* MiniTime string. <i>Note ALL time spans larger or equal than
|
||||
* {@link Integer#MAX_VALUE} weeks will be permanently converted to "forever".
|
||||
* Inputting {@link Long#MAX_VALUE} for the future time point has the same effect.</i>
|
||||
*
|
||||
* @param before The first time point in Unix-time milliseconds
|
||||
* @param after The first time point in Unix-time milliseconds
|
||||
*
|
||||
* @return The resulting MiniTime string
|
||||
*
|
||||
* @throws IllegalArgumentException on a negative time duration
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public static String fromMillisDiffNow(long after, long before)
|
||||
{
|
||||
if (after == Long.MAX_VALUE)
|
||||
{
|
||||
return "forever";
|
||||
}
|
||||
|
||||
return formatDiff(after - before);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time span between now and a future time point in Unix-epoch
|
||||
* milliseconds to a MiniTime string. <i>Note ALL time spans larger or equal
|
||||
* than {@link Integer#MAX_VALUE} weeks will be permanently converted to
|
||||
* "forever". Inputting {@link Long#MAX_VALUE} for the future time point
|
||||
* has the same effect.</i>
|
||||
*
|
||||
* @param future The future time point in Unix time milliseconds
|
||||
*
|
||||
* @return The resulting MiniTime string
|
||||
*
|
||||
* @throws IllegalArgumentException on a negative time duration
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public static String fromMillisDiffNow(long future)
|
||||
{
|
||||
if (future == Long.MAX_VALUE)
|
||||
{
|
||||
return "forever";
|
||||
}
|
||||
|
||||
var diff = future - System.currentTimeMillis();
|
||||
|
||||
return formatDiff(diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a time span milliseconds to a MiniTime string. <i>Note ALL time
|
||||
* spans larger or equal than {@link Integer#MAX_VALUE} weeks will be permanently
|
||||
* converted to "forever".</i>
|
||||
*
|
||||
* @param diff The source time span in milliseconds
|
||||
* @return The resulting MiniTime string
|
||||
*
|
||||
* @throws IllegalArgumentException on a negative time duration
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.2
|
||||
*/
|
||||
public static String formatDiff(long diff)
|
||||
{
|
||||
if (diff < 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Negative time span cannot be converted to MiniTime.");
|
||||
}
|
||||
|
||||
var xweeks = miliseconds.toDays(diff) / DAYS_IN_WEEK;
|
||||
|
||||
if (xweeks > Integer.MAX_VALUE)
|
||||
{
|
||||
return "forever";
|
||||
}
|
||||
|
||||
var xdays = miliseconds.toDays(diff) % DAYS_IN_WEEK;
|
||||
var xhours = miliseconds.toHours(diff) % HOURS_IN_DAY;
|
||||
var xminutes = miliseconds.toMinutes(diff) % MINUTES_IN_HOUR;
|
||||
var xseconds = miliseconds.toSeconds(diff) % SECONDS_IN_MINUTE;
|
||||
|
||||
return formatTime(xweeks, xdays, xhours, xminutes, xseconds);
|
||||
}
|
||||
|
||||
private static String formatTime(long weeks, long days, long hours, long minutes, long seconds)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (weeks > 0)
|
||||
{
|
||||
sb.append(weeks);
|
||||
sb.append('w');
|
||||
}
|
||||
if (days > 0)
|
||||
{
|
||||
sb.append(days);
|
||||
sb.append('d');
|
||||
}
|
||||
if (hours > 0)
|
||||
{
|
||||
sb.append(hours);
|
||||
sb.append('h');
|
||||
}
|
||||
if (minutes > 0)
|
||||
{
|
||||
sb.append(minutes);
|
||||
sb.append('m');
|
||||
}
|
||||
if (seconds > 0)
|
||||
{
|
||||
sb.append(seconds);
|
||||
sb.append('s');
|
||||
}
|
||||
|
||||
var timeStr = sb.toString();
|
||||
|
||||
return timeStr.isEmpty() ? "0s" : timeStr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package cz.tefek.pluto.eventsystem;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class EventData
|
||||
{
|
||||
|
||||
}
|
||||
package cz.tefek.pluto.eventsystem;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class EventData
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
package cz.tefek.pluto.io.asl.resource;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.Severity;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
import cz.tefek.pluto.modloader.ModLoaderCore;
|
||||
|
||||
/**
|
||||
|
@ -94,13 +93,11 @@ public class ResourceAddress
|
|||
|
||||
if (as.length == 1)
|
||||
{
|
||||
Logger.log(Severity.WARNING, "Please do not use tier 1 addresses, so it doesn't conflict with core assets.");
|
||||
Logger.log(SmartSeverity.WARNING, "Please do not use tier 1 addresses, so it doesn't conflict with core assets.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < as.length; i++)
|
||||
for (String branch : as)
|
||||
{
|
||||
var branch = as[i];
|
||||
|
||||
if (branch.length() < 1 || branch.length() > MAX_BRANCH_STRING_LENGTH)
|
||||
{
|
||||
throw new IllegalArgumentException("Length of branch must be higher than 0 and lower than 33, this is not an essay.");
|
||||
|
@ -151,9 +148,9 @@ public class ResourceAddress
|
|||
return raddress;
|
||||
}
|
||||
|
||||
public ResourceAddress fileExtension(String ext)
|
||||
public ResourceAddress fileExtension(@Nullable String ext)
|
||||
{
|
||||
if (ext == null || ext == "")
|
||||
if (ext == null || "".equals(ext))
|
||||
{
|
||||
this.fileExtension = null;
|
||||
return this;
|
||||
|
@ -213,15 +210,7 @@ public class ResourceAddress
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sbPath = new StringBuilder(this.resSubscriber.getMod().getModID());
|
||||
|
||||
sbPath.append("$");
|
||||
|
||||
sbPath.append(this.subAddressToString());
|
||||
|
||||
String path = sbPath.toString();
|
||||
|
||||
return path;
|
||||
return this.resSubscriber.getMod().getModID() + "$" + this.subAddressToString();
|
||||
}
|
||||
|
||||
public ResourceAddress copy()
|
||||
|
@ -246,12 +235,10 @@ public class ResourceAddress
|
|||
|
||||
if (this.hasFileExtension())
|
||||
{
|
||||
sbPath.append("." + this.fileExtension);
|
||||
sbPath.append(".").append(this.fileExtension);
|
||||
}
|
||||
|
||||
String path = sbPath.toString();
|
||||
|
||||
return path;
|
||||
return sbPath.toString();
|
||||
}
|
||||
|
||||
public String subAddressToString()
|
||||
|
@ -293,7 +280,7 @@ public class ResourceAddress
|
|||
var pathBuilder = new StringBuilder(this.resSubscriber.getRootPath());
|
||||
final var separator = FileSystems.getDefault().getSeparator();
|
||||
pathBuilder.append(separator);
|
||||
pathBuilder.append(this.subAddress.stream().collect(Collectors.joining(separator)));
|
||||
pathBuilder.append(String.join(separator, this.subAddress));
|
||||
|
||||
if (this.hasFileExtension())
|
||||
{
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
package cz.tefek.pluto.io.asl.resource.type;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.Resource;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceHelper;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.Severity;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* {@link ResourceAddress} in, {@link BufferedImage} out.
|
||||
|
@ -38,7 +37,7 @@ public class ResourceImage extends Resource<BufferedImage>
|
|||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.log(Severity.ERROR, "Could not load BufferedImage: " + this.address.toString() + ", will load placeholder.");
|
||||
Logger.log(SmartSeverity.ERROR, "Could not load BufferedImage: " + this.address.toString() + ", will load placeholder.");
|
||||
Logger.log(e);
|
||||
|
||||
try
|
||||
|
@ -47,7 +46,7 @@ public class ResourceImage extends Resource<BufferedImage>
|
|||
}
|
||||
catch (IOException e1)
|
||||
{
|
||||
Logger.log(Severity.ERROR, "Placeholder BufferedImage not found: " + ResourceHelper.GLOBAL_ROOT + "data/assets/err/missingTex.png");
|
||||
Logger.log(SmartSeverity.ERROR, "Placeholder BufferedImage not found: " + ResourceHelper.GLOBAL_ROOT + "data/assets/err/missingTex.png");
|
||||
Logger.log("This is not good! :C");
|
||||
|
||||
Logger.log(e1);
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
package cz.tefek.pluto.io.asl.resource.type;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.Resource;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.Severity;
|
||||
|
||||
/**
|
||||
* {@link ResourceAddress} in, {@link InputStream} out.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class ResourceInputStream extends Resource<InputStream>
|
||||
{
|
||||
public ResourceInputStream(ResourceAddress raddress)
|
||||
{
|
||||
super(raddress, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream loadFromFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new FileInputStream(this.address.toPath());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.log(Severity.EXCEPTION, "Failed to open " + this.address + "!");
|
||||
Logger.log(Severity.EXCEPTION, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
package cz.tefek.pluto.io.asl.resource.type;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.Resource;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* {@link ResourceAddress} in, {@link InputStream} out.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class ResourceInputStream extends Resource<InputStream>
|
||||
{
|
||||
public ResourceInputStream(ResourceAddress raddress)
|
||||
{
|
||||
super(raddress, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream loadFromFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Files.newInputStream(this.address.toNIOPath());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "Failed to open " + this.address + "!");
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
package cz.tefek.pluto.io.asl.textio;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
|
||||
/**
|
||||
* A simple text file reader. Apart from generic methods of loading, you can use
|
||||
* a {@link ResourceAddress}. For writing use {@link TextOut}.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class TextIn
|
||||
{
|
||||
public static String load(URL url)
|
||||
{
|
||||
try
|
||||
{
|
||||
load(url.toURI());
|
||||
}
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String load(URI uri)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Files.readString(Paths.get(uri));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String load(Path path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Files.readString(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String loadInternal(String filename)
|
||||
{
|
||||
return load(TextIn.class.getResource("/" + filename));
|
||||
}
|
||||
|
||||
public static String loadExternal(String filename)
|
||||
{
|
||||
return load(new File(filename).toURI());
|
||||
}
|
||||
|
||||
public static String fromAddress(ResourceAddress address)
|
||||
{
|
||||
return load(address.toNIOPath());
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.io.asl.textio;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
|
||||
/**
|
||||
* A simple text file reader. Apart from generic methods of loading, you can use
|
||||
* a {@link ResourceAddress}. For writing use {@link TextOut}.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class TextIn
|
||||
{
|
||||
public static String load(URL url)
|
||||
{
|
||||
try
|
||||
{
|
||||
load(url.toURI());
|
||||
}
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String load(URI uri)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Files.readString(Paths.get(uri));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String load(Path path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Files.readString(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String loadInternal(String filename)
|
||||
{
|
||||
return load(TextIn.class.getResource("/" + filename));
|
||||
}
|
||||
|
||||
public static String loadExternal(String filename)
|
||||
{
|
||||
return load(new File(filename).toURI());
|
||||
}
|
||||
|
||||
public static String fromAddress(ResourceAddress address)
|
||||
{
|
||||
return load(address.toNIOPath());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
package cz.tefek.pluto.io.asl.textio;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
/**
|
||||
* Simplifies text writer creation. For reading use {@link TextIn}.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
|
||||
public class TextOut
|
||||
{
|
||||
public static PrintStream createPrintStream(String filePath) throws IOException
|
||||
{
|
||||
PrintStream printstream = new PrintStream(createFOStream(filePath));
|
||||
|
||||
return printstream;
|
||||
}
|
||||
|
||||
public static FileOutputStream createFOStream(String filePath) throws IOException
|
||||
{
|
||||
FileOutputStream fos = new FileOutputStream(filePath);
|
||||
|
||||
return fos;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.io.asl.textio;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
/**
|
||||
* Simplifies text writer creation. For reading use {@link TextIn}.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
|
||||
public class TextOut
|
||||
{
|
||||
public static PrintStream createPrintStream(String filePath) throws IOException
|
||||
{
|
||||
PrintStream printstream = new PrintStream(createFOStream(filePath));
|
||||
|
||||
return printstream;
|
||||
}
|
||||
|
||||
public static FileOutputStream createFOStream(String filePath) throws IOException
|
||||
{
|
||||
FileOutputStream fos = new FileOutputStream(filePath);
|
||||
|
||||
return fos;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public class Logger
|
|||
private static OutputStream fileLog = null;
|
||||
|
||||
/**
|
||||
* Initializes up the logger and replaces the standard output and standard error output with the logger methods.
|
||||
* Initializes the logger and replaces the standard output and standard error output with the logger methods.
|
||||
*
|
||||
* <p>
|
||||
* <em>
|
||||
|
|
|
@ -1,35 +1,39 @@
|
|||
package cz.tefek.pluto.io.logger;
|
||||
|
||||
/**
|
||||
* Message severity.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public enum Severity implements ISeverity
|
||||
{
|
||||
INFO("[INFO] ", false),
|
||||
WARNING("[WARNING] ", true),
|
||||
ERROR("[ERROR] ", true),
|
||||
EXCEPTION("[EXCEPTION] ", true),
|
||||
NONE("", false);
|
||||
|
||||
private String displayName;
|
||||
private boolean usesStdErr;
|
||||
|
||||
Severity(String name, boolean usesStdErr)
|
||||
{
|
||||
this.displayName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName()
|
||||
{
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStdErr()
|
||||
{
|
||||
return this.usesStdErr;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.io.logger;
|
||||
|
||||
/**
|
||||
* Message severity.
|
||||
*
|
||||
* @author 493msi
|
||||
*
|
||||
* @deprecated Use {@link SmartSeverity} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public enum Severity implements ISeverity
|
||||
{
|
||||
INFO("[INFO] ", false),
|
||||
WARNING("[WARNING] ", true),
|
||||
ERROR("[ERROR] ", true),
|
||||
EXCEPTION("[EXCEPTION] ", true),
|
||||
NONE("", false);
|
||||
|
||||
private String displayName;
|
||||
private boolean usesStdErr;
|
||||
|
||||
Severity(String name, boolean usesStdErr)
|
||||
{
|
||||
this.displayName = name;
|
||||
this.usesStdErr = usesStdErr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName()
|
||||
{
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStdErr()
|
||||
{
|
||||
return this.usesStdErr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,84 +1,84 @@
|
|||
package cz.tefek.pluto.math;
|
||||
|
||||
/**
|
||||
* A class to generate a cubic bezier interpolation function. Not very
|
||||
* optimized, so use it sparsely.
|
||||
*
|
||||
* @since 0.2
|
||||
* @author 493msi
|
||||
*/
|
||||
public class CubicBezier
|
||||
{
|
||||
private static final int iterations = 16;
|
||||
private double a, b, c, d;
|
||||
|
||||
/**
|
||||
* Creates a new {@code CubicBezier} from the given parameters.
|
||||
*
|
||||
* @param cpx1 the X position of the direction the function "steers towards"
|
||||
* @param cpy1 the Y position of the direction the function "steers towards"
|
||||
* @param cpx2 the X position of the direction the function "arrives from"
|
||||
* @param cpy2 the Y position of the direction the function "arrives from"
|
||||
*
|
||||
* @since 0.3
|
||||
* @author 493msi
|
||||
*/
|
||||
public CubicBezier(double cpx1, double cpy1, double cpx2, double cpy2)
|
||||
{
|
||||
if (cpx1 < 0 || cpx1 > 1 || cpx2 < 0 || cpx2 > 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Parameter out of range, only 0..1 is supported (only one Y value in 0..1 for each X in 0..1).");
|
||||
}
|
||||
|
||||
this.a = cpx1;
|
||||
this.b = cpy1;
|
||||
this.c = cpx2;
|
||||
this.d = cpy2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solves this {@code CubicBezier} for the given x and the supplied
|
||||
* parameters in the constructor.
|
||||
*
|
||||
* @param xIn the input X position
|
||||
*
|
||||
* @return the approximate Y position for the given X position
|
||||
*
|
||||
* @since 0.3
|
||||
* @author 493msi
|
||||
*/
|
||||
public double forX(double xIn)
|
||||
{
|
||||
if (xIn < 0)
|
||||
{
|
||||
return this.forX(0);
|
||||
}
|
||||
|
||||
if (xIn > 1)
|
||||
{
|
||||
return this.forX(1);
|
||||
}
|
||||
|
||||
double t = 0.5;
|
||||
|
||||
double x;
|
||||
double y = 3 * (1 - t) * (1 - t) * t * this.b + 3 * (1 - t) * t * t * this.d + t * t * t;
|
||||
|
||||
double delta = 0.25;
|
||||
boolean uh;
|
||||
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
x = 3 * (1 - t) * (1 - t) * t * this.a + 3 * (1 - t) * t * t * this.c + t * t * t;
|
||||
y = 3 * (1 - t) * (1 - t) * t * this.b + 3 * (1 - t) * t * t * this.d + t * t * t;
|
||||
|
||||
uh = x > xIn;
|
||||
|
||||
t += uh ? -delta : delta;
|
||||
|
||||
delta /= 2;
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.math;
|
||||
|
||||
/**
|
||||
* A class to generate a cubic bezier interpolation function. Not very
|
||||
* optimized, so use it sparsely.
|
||||
*
|
||||
* @since 0.2
|
||||
* @author 493msi
|
||||
*/
|
||||
public class CubicBezier
|
||||
{
|
||||
private static final int iterations = 16;
|
||||
private double a, b, c, d;
|
||||
|
||||
/**
|
||||
* Creates a new {@code CubicBezier} from the given parameters.
|
||||
*
|
||||
* @param cpx1 the X position of the direction the function "steers towards"
|
||||
* @param cpy1 the Y position of the direction the function "steers towards"
|
||||
* @param cpx2 the X position of the direction the function "arrives from"
|
||||
* @param cpy2 the Y position of the direction the function "arrives from"
|
||||
*
|
||||
* @since 0.3
|
||||
* @author 493msi
|
||||
*/
|
||||
public CubicBezier(double cpx1, double cpy1, double cpx2, double cpy2)
|
||||
{
|
||||
if (cpx1 < 0 || cpx1 > 1 || cpx2 < 0 || cpx2 > 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Parameter out of range, only 0..1 is supported (only one Y value in 0..1 for each X in 0..1).");
|
||||
}
|
||||
|
||||
this.a = cpx1;
|
||||
this.b = cpy1;
|
||||
this.c = cpx2;
|
||||
this.d = cpy2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solves this {@code CubicBezier} for the given x and the supplied
|
||||
* parameters in the constructor.
|
||||
*
|
||||
* @param xIn the input X position
|
||||
*
|
||||
* @return the approximate Y position for the given X position
|
||||
*
|
||||
* @since 0.3
|
||||
* @author 493msi
|
||||
*/
|
||||
public double forX(double xIn)
|
||||
{
|
||||
if (xIn < 0)
|
||||
{
|
||||
return this.forX(0);
|
||||
}
|
||||
|
||||
if (xIn > 1)
|
||||
{
|
||||
return this.forX(1);
|
||||
}
|
||||
|
||||
double t = 0.5;
|
||||
|
||||
double x;
|
||||
double y = 3 * (1 - t) * (1 - t) * t * this.b + 3 * (1 - t) * t * t * this.d + t * t * t;
|
||||
|
||||
double delta = 0.25;
|
||||
boolean uh;
|
||||
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
x = 3 * (1 - t) * (1 - t) * t * this.a + 3 * (1 - t) * t * t * this.c + t * t * t;
|
||||
y = 3 * (1 - t) * (1 - t) * t * this.b + 3 * (1 - t) * t * t * this.d + t * t * t;
|
||||
|
||||
uh = x > xIn;
|
||||
|
||||
t += uh ? -delta : delta;
|
||||
|
||||
delta /= 2;
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,141 +1,141 @@
|
|||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceSubscriber;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.pluto.pp.PlutoPackage;
|
||||
|
||||
/**
|
||||
* Mod object. Can be used to create a {@link ResourceSubscriber}.
|
||||
* {@link ModLoaderCore} automatically creates a Mod object for each class
|
||||
* annotated by {@link ModEntry @ModEntry} (assuming it is registered or class
|
||||
* loaded by ModClassLoader, which auto-detects and registers {@link ModEntry
|
||||
* ModEntries}).
|
||||
*
|
||||
* @see <a href="http://pluto.tefek.cz/mod/dev/">PlutoModLoader tutorial</a> for
|
||||
* more information.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class Mod extends PlutoPackage
|
||||
{
|
||||
private Class<?> mainClass;
|
||||
private Object instance;
|
||||
|
||||
private boolean clientSupport;
|
||||
private boolean serverSupport;
|
||||
|
||||
private ResourceSubscriber defaultResourceSubscriber;
|
||||
|
||||
private String rootDataFolder;
|
||||
|
||||
Mod(Class<?> mainClass, String rootDataFolder)
|
||||
{
|
||||
super(extractModID(mainClass));
|
||||
|
||||
if (mainClass != null)
|
||||
{
|
||||
ModEntry modInterface = mainClass.getDeclaredAnnotation(ModEntry.class);
|
||||
|
||||
if (modInterface != null)
|
||||
{
|
||||
this.mainClass = mainClass;
|
||||
this.name = modInterface.displayName().isBlank() ? modInterface.modid() : modInterface.displayName();
|
||||
this.author = modInterface.author();
|
||||
this.version = modInterface.version();
|
||||
this.build = modInterface.build();
|
||||
this.earliestCompatibleBuild = modInterface.earliestCompatibleBuild();
|
||||
this.dependencies = modInterface.dependencies();
|
||||
this.description = modInterface.description();
|
||||
this.iconURL = modInterface.iconURL();
|
||||
|
||||
this.rootDataFolder = rootDataFolder;
|
||||
this.defaultResourceSubscriber = new ResourceSubscriber(this, rootDataFolder);
|
||||
|
||||
this.clientSupport = modInterface.clientSupport();
|
||||
this.serverSupport = modInterface.serverSupport();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractModID(Class<?> mainClass)
|
||||
{
|
||||
if (mainClass != null)
|
||||
{
|
||||
ModEntry modInterface = mainClass.getDeclaredAnnotation(ModEntry.class);
|
||||
|
||||
if (modInterface != null)
|
||||
{
|
||||
return modInterface.modid();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> getMainClass()
|
||||
{
|
||||
return this.mainClass;
|
||||
}
|
||||
|
||||
Object getClassInstance() throws ReflectiveOperationException
|
||||
{
|
||||
if (this.instance == null)
|
||||
{
|
||||
Logger.log("|Pluto Mod Loader| Loading mod instance: " + this.name);
|
||||
this.instance = this.mainClass.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
public String getModID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getModName()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getModAuthor()
|
||||
{
|
||||
return this.author;
|
||||
}
|
||||
|
||||
public String getModVersion()
|
||||
{
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public int getModBuild()
|
||||
{
|
||||
return this.build;
|
||||
}
|
||||
|
||||
public boolean isClientSupported()
|
||||
{
|
||||
return this.clientSupport;
|
||||
}
|
||||
|
||||
public boolean isServerSupported()
|
||||
{
|
||||
return this.serverSupport;
|
||||
}
|
||||
|
||||
public ResourceSubscriber getDefaultResourceSubscriber()
|
||||
{
|
||||
return this.defaultResourceSubscriber;
|
||||
}
|
||||
|
||||
public void setRootDataFolder(String rootDataFolder)
|
||||
{
|
||||
this.rootDataFolder = rootDataFolder;
|
||||
this.defaultResourceSubscriber = new ResourceSubscriber(this, rootDataFolder);
|
||||
}
|
||||
|
||||
public String getRootDataFolder()
|
||||
{
|
||||
return this.rootDataFolder;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceSubscriber;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.pluto.pp.PlutoPackage;
|
||||
|
||||
/**
|
||||
* Mod object. Can be used to create a {@link ResourceSubscriber}.
|
||||
* {@link ModLoaderCore} automatically creates a Mod object for each class
|
||||
* annotated by {@link ModEntry @ModEntry} (assuming it is registered or class
|
||||
* loaded by ModClassLoader, which auto-detects and registers {@link ModEntry
|
||||
* ModEntries}).
|
||||
*
|
||||
* @see <a href="http://pluto.tefek.cz/mod/dev/">PlutoModLoader tutorial</a> for
|
||||
* more information.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class Mod extends PlutoPackage
|
||||
{
|
||||
private Class<?> mainClass;
|
||||
private Object instance;
|
||||
|
||||
private boolean clientSupport;
|
||||
private boolean serverSupport;
|
||||
|
||||
private ResourceSubscriber defaultResourceSubscriber;
|
||||
|
||||
private String rootDataFolder;
|
||||
|
||||
Mod(Class<?> mainClass, String rootDataFolder)
|
||||
{
|
||||
super(extractModID(mainClass));
|
||||
|
||||
if (mainClass != null)
|
||||
{
|
||||
ModEntry modInterface = mainClass.getDeclaredAnnotation(ModEntry.class);
|
||||
|
||||
if (modInterface != null)
|
||||
{
|
||||
this.mainClass = mainClass;
|
||||
this.name = modInterface.displayName().isBlank() ? modInterface.modid() : modInterface.displayName();
|
||||
this.author = modInterface.author();
|
||||
this.version = modInterface.version();
|
||||
this.build = modInterface.build();
|
||||
this.earliestCompatibleBuild = modInterface.earliestCompatibleBuild();
|
||||
this.dependencies = modInterface.dependencies();
|
||||
this.description = modInterface.description();
|
||||
this.iconURL = modInterface.iconURL();
|
||||
|
||||
this.rootDataFolder = rootDataFolder;
|
||||
this.defaultResourceSubscriber = new ResourceSubscriber(this, rootDataFolder);
|
||||
|
||||
this.clientSupport = modInterface.clientSupport();
|
||||
this.serverSupport = modInterface.serverSupport();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractModID(Class<?> mainClass)
|
||||
{
|
||||
if (mainClass != null)
|
||||
{
|
||||
ModEntry modInterface = mainClass.getDeclaredAnnotation(ModEntry.class);
|
||||
|
||||
if (modInterface != null)
|
||||
{
|
||||
return modInterface.modid();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> getMainClass()
|
||||
{
|
||||
return this.mainClass;
|
||||
}
|
||||
|
||||
Object getClassInstance() throws ReflectiveOperationException
|
||||
{
|
||||
if (this.instance == null)
|
||||
{
|
||||
Logger.log("|Pluto Mod Loader| Loading mod instance: " + this.name);
|
||||
this.instance = this.mainClass.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
public String getModID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getModName()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getModAuthor()
|
||||
{
|
||||
return this.author;
|
||||
}
|
||||
|
||||
public String getModVersion()
|
||||
{
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public int getModBuild()
|
||||
{
|
||||
return this.build;
|
||||
}
|
||||
|
||||
public boolean isClientSupported()
|
||||
{
|
||||
return this.clientSupport;
|
||||
}
|
||||
|
||||
public boolean isServerSupported()
|
||||
{
|
||||
return this.serverSupport;
|
||||
}
|
||||
|
||||
public ResourceSubscriber getDefaultResourceSubscriber()
|
||||
{
|
||||
return this.defaultResourceSubscriber;
|
||||
}
|
||||
|
||||
public void setRootDataFolder(String rootDataFolder)
|
||||
{
|
||||
this.rootDataFolder = rootDataFolder;
|
||||
this.defaultResourceSubscriber = new ResourceSubscriber(this, rootDataFolder);
|
||||
}
|
||||
|
||||
public String getRootDataFolder()
|
||||
{
|
||||
return this.rootDataFolder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,154 +1,154 @@
|
|||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceHelper;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* Class-loads all valid mods. The only requirement for the mod is to have a
|
||||
* mod.jar file inside the base folder (for example
|
||||
* <i>mods/spaghetti/mod.jar</i>).
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class ModClassLoader
|
||||
{
|
||||
public static ArrayList<String> mods;
|
||||
|
||||
public static void loadJars()
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Looking for installed mods.");
|
||||
|
||||
File modDir = new File(ResourceHelper.GLOBAL_ROOT + "mods/");
|
||||
|
||||
if (!modDir.exists())
|
||||
{
|
||||
modDir.mkdir();
|
||||
|
||||
Logger.log(SmartSeverity.MODULE, " No mod found.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mods = new ArrayList<>(Arrays.asList(modDir.list()));
|
||||
|
||||
if (mods.size() == 0)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "No mod found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Found " + mods.size() + " mod(s) to load.");
|
||||
|
||||
try
|
||||
{
|
||||
loadAll();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadAll() throws Exception
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (mods.size() > 0)
|
||||
{
|
||||
if (mods.size() == 1)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "There is one mod to load.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "There are " + mods.size() + " mods to load.");
|
||||
}
|
||||
|
||||
for (String modname : mods)
|
||||
{
|
||||
var modFolder = ResourceHelper.GLOBAL_ROOT + "mods/" + modname;
|
||||
var dataDir = modFolder + "/data";
|
||||
|
||||
if (new File(modFolder + "/mod.jar").exists())
|
||||
{
|
||||
var dataDirFile = new File(dataDir);
|
||||
|
||||
if (!dataDirFile.isDirectory())
|
||||
{
|
||||
dataDirFile.mkdirs();
|
||||
}
|
||||
|
||||
String pathToJar = modFolder + "/mod.jar";
|
||||
|
||||
JarFile jarFile = new JarFile(pathToJar);
|
||||
Enumeration<JarEntry> e = jarFile.entries();
|
||||
|
||||
URL[] urls = { new URL("jar:file:" + pathToJar + "!/") };
|
||||
|
||||
URLClassLoader sysLoader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader());
|
||||
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
JarEntry je = e.nextElement();
|
||||
|
||||
if (je.isDirectory())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not sure what to do with non-java files.
|
||||
// They are ignored (for now).
|
||||
if (!je.getName().endsWith(".class"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
String className = je.getName().replaceAll("\\.class$", "").replace('/', '.');
|
||||
|
||||
Class<?> modClass = sysLoader.loadClass(className);
|
||||
|
||||
if (modClass.getDeclaredAnnotation(ModEntry.class) != null)
|
||||
{
|
||||
ModLoaderCore.registerMod(modClass, dataDir);
|
||||
}
|
||||
}
|
||||
|
||||
jarFile.close();
|
||||
|
||||
Logger.log(SmartSeverity.MODULE_PLUS, "Loaded mod jar file: " + modname + "/mod.jar");
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Failed to load mod jar file: " + modname + ".");
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Reason: Missing mod.jar file.");
|
||||
}
|
||||
}
|
||||
|
||||
if (i < 1 || i == 0)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Loading mods complete, loaded " + i + " mods.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Loading mods complete, loaded " + i + " mod.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "There aren't any mods, skipping initialising phase.");
|
||||
}
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceHelper;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* Class-loads all valid mods. The only requirement for the mod is to have a
|
||||
* mod.jar file inside the base folder (for example
|
||||
* <i>mods/spaghetti/mod.jar</i>).
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class ModClassLoader
|
||||
{
|
||||
public static ArrayList<String> mods;
|
||||
|
||||
public static void loadJars()
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Looking for installed mods.");
|
||||
|
||||
File modDir = new File(ResourceHelper.GLOBAL_ROOT + "mods/");
|
||||
|
||||
if (!modDir.exists())
|
||||
{
|
||||
modDir.mkdir();
|
||||
|
||||
Logger.log(SmartSeverity.MODULE, " No mod found.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mods = new ArrayList<>(Arrays.asList(modDir.list()));
|
||||
|
||||
if (mods.size() == 0)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "No mod found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Found " + mods.size() + " mod(s) to load.");
|
||||
|
||||
try
|
||||
{
|
||||
loadAll();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadAll() throws Exception
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (mods.size() > 0)
|
||||
{
|
||||
if (mods.size() == 1)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "There is one mod to load.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "There are " + mods.size() + " mods to load.");
|
||||
}
|
||||
|
||||
for (String modname : mods)
|
||||
{
|
||||
var modFolder = ResourceHelper.GLOBAL_ROOT + "mods/" + modname;
|
||||
var dataDir = modFolder + "/data";
|
||||
|
||||
if (new File(modFolder + "/mod.jar").exists())
|
||||
{
|
||||
var dataDirFile = new File(dataDir);
|
||||
|
||||
if (!dataDirFile.isDirectory())
|
||||
{
|
||||
dataDirFile.mkdirs();
|
||||
}
|
||||
|
||||
String pathToJar = modFolder + "/mod.jar";
|
||||
|
||||
JarFile jarFile = new JarFile(pathToJar);
|
||||
Enumeration<JarEntry> e = jarFile.entries();
|
||||
|
||||
URL[] urls = { new URL("jar:file:" + pathToJar + "!/") };
|
||||
|
||||
URLClassLoader sysLoader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader());
|
||||
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
JarEntry je = e.nextElement();
|
||||
|
||||
if (je.isDirectory())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not sure what to do with non-java files.
|
||||
// They are ignored (for now).
|
||||
if (!je.getName().endsWith(".class"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
String className = je.getName().replaceAll("\\.class$", "").replace('/', '.');
|
||||
|
||||
Class<?> modClass = sysLoader.loadClass(className);
|
||||
|
||||
if (modClass.getDeclaredAnnotation(ModEntry.class) != null)
|
||||
{
|
||||
ModLoaderCore.registerMod(modClass, dataDir);
|
||||
}
|
||||
}
|
||||
|
||||
jarFile.close();
|
||||
|
||||
Logger.log(SmartSeverity.MODULE_PLUS, "Loaded mod jar file: " + modname + "/mod.jar");
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Failed to load mod jar file: " + modname + ".");
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Reason: Missing mod.jar file.");
|
||||
}
|
||||
}
|
||||
|
||||
if (i < 1 || i == 0)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Loading mods complete, loaded " + i + " mods.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Loading mods complete, loaded " + i + " mod.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "There aren't any mods, skipping initialising phase.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* The heart of any Pluto mod. Annotate your class with this, so the
|
||||
* PlutoModLoader can load it. The class must be directly registered or
|
||||
* processed by {@link ModClassLoader} (as an external mod).
|
||||
*
|
||||
* @see <a href="http://pluto.tefek.cz/mod/dev/">PlutoModLoader tutorial</a> for
|
||||
* more information.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ModEntry {
|
||||
String modid();
|
||||
|
||||
String displayName() default "";
|
||||
|
||||
String author() default "anonymous author";
|
||||
|
||||
String version() default "unknown version";
|
||||
|
||||
Class<?>[] dependencies() default {};
|
||||
|
||||
String iconURL() default "";
|
||||
|
||||
String description() default "No description available";
|
||||
|
||||
int build() default 0;
|
||||
|
||||
int earliestCompatibleBuild() default 0;
|
||||
|
||||
boolean clientSupport() default true;
|
||||
|
||||
boolean serverSupport() default false;
|
||||
}
|
||||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* The heart of any Pluto mod. Annotate your class with this, so the
|
||||
* PlutoModLoader can load it. The class must be directly registered or
|
||||
* processed by {@link ModClassLoader} (as an external mod).
|
||||
*
|
||||
* @see <a href="http://pluto.tefek.cz/mod/dev/">PlutoModLoader tutorial</a> for
|
||||
* more information.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ModEntry {
|
||||
String modid();
|
||||
|
||||
String displayName() default "";
|
||||
|
||||
String author() default "anonymous author";
|
||||
|
||||
String version() default "unknown version";
|
||||
|
||||
Class<?>[] dependencies() default {};
|
||||
|
||||
String iconURL() default "";
|
||||
|
||||
String description() default "No description available";
|
||||
|
||||
int build() default 0;
|
||||
|
||||
int earliestCompatibleBuild() default 0;
|
||||
|
||||
boolean clientSupport() default true;
|
||||
|
||||
boolean serverSupport() default false;
|
||||
}
|
||||
|
|
|
@ -1,119 +1,119 @@
|
|||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceHelper;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* Unzips mod packages from the packages folder into the mods folder. WIP
|
||||
*
|
||||
* 2020 Update: Still WIP
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class ModInstaller
|
||||
{
|
||||
public static void unpackNewMods()
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Looking for new mod packages to install.");
|
||||
|
||||
File f = new File(ResourceHelper.GLOBAL_ROOT + "packages/");
|
||||
if (!f.exists())
|
||||
{
|
||||
f.mkdir();
|
||||
|
||||
Logger.log(SmartSeverity.MODULE, "Package folder does not exist, creating it and aborting unpacking.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<String> files = new ArrayList<String>(Arrays.asList(f.list()));
|
||||
|
||||
if (files.size() == 0)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "No mod package found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_PLUS, "Found " + files.size() + " mod packages.");
|
||||
|
||||
for (String file : files)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_PLUS, "Mod package found: " + file + ", installing it.");
|
||||
|
||||
try
|
||||
{
|
||||
extract(file);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Unpacking of " + file + " failed!");
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
new File(ResourceHelper.GLOBAL_ROOT + "packages/" + file).delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void extract(String filepath) throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[2048];
|
||||
|
||||
InputStream fileInput = null;
|
||||
fileInput = new FileInputStream(ResourceHelper.GLOBAL_ROOT + "packages/" + filepath);
|
||||
|
||||
ZipInputStream stream = new ZipInputStream(fileInput);
|
||||
String outdir = ResourceHelper.GLOBAL_ROOT + "mods/";
|
||||
|
||||
if (!new File(outdir).exists())
|
||||
{
|
||||
new File(outdir).mkdir();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
String filename = filepath.replaceAll("\\.zip$", "");
|
||||
|
||||
new File(outdir + filename).mkdir();
|
||||
|
||||
ZipEntry entry;
|
||||
while ((entry = stream.getNextEntry()) != null)
|
||||
{
|
||||
String outpath = outdir + filename + "/" + entry.getName();
|
||||
FileOutputStream output = null;
|
||||
try
|
||||
{
|
||||
output = new FileOutputStream(outpath);
|
||||
int len = 0;
|
||||
while ((len = stream.read(buffer)) > 0)
|
||||
{
|
||||
output.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (output != null)
|
||||
{
|
||||
output.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceHelper;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* Unzips mod packages from the packages folder into the mods folder. WIP
|
||||
*
|
||||
* 2020 Update: Still WIP
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class ModInstaller
|
||||
{
|
||||
public static void unpackNewMods()
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Looking for new mod packages to install.");
|
||||
|
||||
File f = new File(ResourceHelper.GLOBAL_ROOT + "packages/");
|
||||
if (!f.exists())
|
||||
{
|
||||
f.mkdir();
|
||||
|
||||
Logger.log(SmartSeverity.MODULE, "Package folder does not exist, creating it and aborting unpacking.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<String> files = new ArrayList<String>(Arrays.asList(f.list()));
|
||||
|
||||
if (files.size() == 0)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "No mod package found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_PLUS, "Found " + files.size() + " mod packages.");
|
||||
|
||||
for (String file : files)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_PLUS, "Mod package found: " + file + ", installing it.");
|
||||
|
||||
try
|
||||
{
|
||||
extract(file);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Unpacking of " + file + " failed!");
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
new File(ResourceHelper.GLOBAL_ROOT + "packages/" + file).delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void extract(String filepath) throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[2048];
|
||||
|
||||
InputStream fileInput = null;
|
||||
fileInput = new FileInputStream(ResourceHelper.GLOBAL_ROOT + "packages/" + filepath);
|
||||
|
||||
ZipInputStream stream = new ZipInputStream(fileInput);
|
||||
String outdir = ResourceHelper.GLOBAL_ROOT + "mods/";
|
||||
|
||||
if (!new File(outdir).exists())
|
||||
{
|
||||
new File(outdir).mkdir();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
String filename = filepath.replaceAll("\\.zip$", "");
|
||||
|
||||
new File(outdir + filename).mkdir();
|
||||
|
||||
ZipEntry entry;
|
||||
while ((entry = stream.getNextEntry()) != null)
|
||||
{
|
||||
String outpath = outdir + filename + "/" + entry.getName();
|
||||
FileOutputStream output = null;
|
||||
try
|
||||
{
|
||||
output = new FileOutputStream(outpath);
|
||||
int len = 0;
|
||||
while ((len = stream.read(buffer)) > 0)
|
||||
{
|
||||
output.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (output != null)
|
||||
{
|
||||
output.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,271 +1,271 @@
|
|||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEventManager;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceHelper;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
import cz.tefek.pluto.modloader.event.ModLoad;
|
||||
import cz.tefek.pluto.modloader.event.ModLoadEvent;
|
||||
import cz.tefek.pluto.modloader.event.ModPostLoad;
|
||||
import cz.tefek.pluto.modloader.event.ModPostLoadEvent;
|
||||
import cz.tefek.pluto.modloader.event.ModPreLoad;
|
||||
import cz.tefek.pluto.modloader.event.ModPreLoadEvent;
|
||||
import cz.tefek.pluto.modloader.event.ModUnload;
|
||||
import cz.tefek.pluto.modloader.event.ModUnloadEvent;
|
||||
|
||||
public class ModLoaderCore
|
||||
{
|
||||
public static final String MOD_ID_STRING_PATTERN = "[a-z0-9]+[a-z0-9_]*";
|
||||
public static final String FULL_MOD_ID_STRING_PATTERN = "^" + MOD_ID_STRING_PATTERN + "$";
|
||||
|
||||
static ModLoadingPhase loadingPhase = ModLoadingPhase.WAITING;
|
||||
|
||||
private static final List<Mod> modArchive = new ArrayList<>();
|
||||
|
||||
private static final Queue<Mod> loadBuffer = new LinkedList<>();
|
||||
|
||||
public static void registerMod(Class<?> modClass, String modDataRoot)
|
||||
{
|
||||
if (loadingPhase != ModLoadingPhase.WAITING && loadingPhase != ModLoadingPhase.CLASSLOADING)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Cannot register mod during loading phase " + loadingPhase.name() + "!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (getModByMainClass(modClass) != null)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Mod class " + modClass.getCanonicalName() + " is already registered, skipping it.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (modDataRoot == null)
|
||||
{
|
||||
modDataRoot = ResourceHelper.DEFAULT_RESOURCE_ROOT;
|
||||
}
|
||||
|
||||
var registeredMod = loadBuffer.stream().filter(presentMod -> presentMod.getMainClass().equals(modClass)).findAny();
|
||||
|
||||
if (registeredMod.isPresent())
|
||||
{
|
||||
if (modDataRoot != null)
|
||||
{
|
||||
var mod = registeredMod.get();
|
||||
|
||||
mod.setRootDataFolder(modDataRoot);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Mod mod = new Mod(modClass, modDataRoot);
|
||||
|
||||
if (!mod.getModID().matches(FULL_MOD_ID_STRING_PATTERN))
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Mod id " + mod.getModID() + " contains invalid characters (or none at all), mod will not be loaded.");
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Only lowercase letters (a to z) and numbers (0 to 9) are allowed characters.");
|
||||
return;
|
||||
}
|
||||
|
||||
var deps = mod.getDependencies();
|
||||
|
||||
for (var dependency : deps)
|
||||
{
|
||||
var registeredDependency = loadBuffer.stream().filter(presentMod -> presentMod.getMainClass().equals(dependency)).findAny();
|
||||
|
||||
if (registeredDependency.isPresent())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
registerMod(dependency);
|
||||
}
|
||||
|
||||
loadBuffer.add(mod);
|
||||
}
|
||||
|
||||
public static void registerMod(Class<?> modClass)
|
||||
{
|
||||
registerMod(modClass, null);
|
||||
}
|
||||
|
||||
public static List<Mod> getAllMods()
|
||||
{
|
||||
return Collections.unmodifiableList(modArchive);
|
||||
}
|
||||
|
||||
public static Mod getModByID(String id)
|
||||
{
|
||||
for (Mod mod : modArchive)
|
||||
{
|
||||
if (mod.getModID().equals(id))
|
||||
{
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Mod getModByMainClass(Class<?> modClass)
|
||||
{
|
||||
for (Mod mod : modArchive)
|
||||
{
|
||||
if (modClass.equals(mod.getMainClass()))
|
||||
{
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void loadProcedure()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.PREPARING;
|
||||
|
||||
StaticPlutoEventManager.registerEvent(ModPreLoad.class);
|
||||
StaticPlutoEventManager.registerEvent(ModLoad.class);
|
||||
StaticPlutoEventManager.registerEvent(ModPostLoad.class);
|
||||
StaticPlutoEventManager.registerEvent(ModUnload.class);
|
||||
|
||||
Logger.log("[Pluto Mod Loader] Number of manually added mods: " + modArchive.size());
|
||||
loadingPhase = ModLoadingPhase.UPACKING;
|
||||
ModInstaller.unpackNewMods();
|
||||
loadingPhase = ModLoadingPhase.CLASSLOADING;
|
||||
ModClassLoader.loadJars();
|
||||
loadingPhase = ModLoadingPhase.INITIALIZING;
|
||||
|
||||
while (loadBuffer.peek() != null)
|
||||
{
|
||||
var mod = loadBuffer.remove();
|
||||
StaticPlutoEventManager.registerEventHandler(mod.getMainClass());
|
||||
modArchive.add(mod);
|
||||
}
|
||||
|
||||
if (!modArchive.isEmpty())
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Initializing mod(s)...");
|
||||
initMods();
|
||||
|
||||
if (loadingPhase == ModLoadingPhase.FINISHING)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Initializing mod(s) finished.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Initializing mod(s) was canceled due to error(s).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void initMods()
|
||||
{
|
||||
while (loadingPhase != ModLoadingPhase.CANCELED && loadingPhase != ModLoadingPhase.FINISHING)
|
||||
{
|
||||
switch (loadingPhase)
|
||||
{
|
||||
case INITIALIZING:
|
||||
preLoadMods();
|
||||
break;
|
||||
case PRELOADING:
|
||||
loadMods();
|
||||
break;
|
||||
case LOADING:
|
||||
postLoadMods();
|
||||
break;
|
||||
case POSTLOADING:
|
||||
complete();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void unloadProcedure()
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_MINUS, "Unloading all mods.");
|
||||
StaticPlutoEventManager.fireEvent(ModUnload.class, new ModUnloadEvent());
|
||||
modArchive.clear();
|
||||
}
|
||||
|
||||
private static void preLoadMods()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.PRELOADING;
|
||||
|
||||
try
|
||||
{
|
||||
ModPreLoadEvent preLoadData = new ModPreLoadEvent();
|
||||
preLoadData.mods = modArchive;
|
||||
|
||||
StaticPlutoEventManager.fireEvent(ModPreLoad.class, preLoadData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Problem encountered while pre-loading mods.");
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Mod loading stopped.");
|
||||
|
||||
Logger.log(e);
|
||||
|
||||
cancelLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadMods()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.LOADING;
|
||||
|
||||
try
|
||||
{
|
||||
ModLoadEvent loadData = new ModLoadEvent();
|
||||
|
||||
StaticPlutoEventManager.fireEvent(ModLoad.class, loadData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Problem encountered while loading mods.");
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Mod loading stopped.");
|
||||
|
||||
Logger.log(e);
|
||||
|
||||
cancelLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private static void postLoadMods()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.POSTLOADING;
|
||||
|
||||
try
|
||||
{
|
||||
ModPostLoadEvent postLoadData = new ModPostLoadEvent();
|
||||
|
||||
StaticPlutoEventManager.fireEvent(ModPostLoad.class, postLoadData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Problem encountered while post-loading mods.");
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Mod loading stopped.");
|
||||
|
||||
Logger.log(e);
|
||||
|
||||
cancelLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private static void complete()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.FINISHING;
|
||||
}
|
||||
|
||||
private static void cancelLoading()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.CANCELED;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.modloader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEventManager;
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceHelper;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
import cz.tefek.pluto.modloader.event.ModLoad;
|
||||
import cz.tefek.pluto.modloader.event.ModLoadEvent;
|
||||
import cz.tefek.pluto.modloader.event.ModPostLoad;
|
||||
import cz.tefek.pluto.modloader.event.ModPostLoadEvent;
|
||||
import cz.tefek.pluto.modloader.event.ModPreLoad;
|
||||
import cz.tefek.pluto.modloader.event.ModPreLoadEvent;
|
||||
import cz.tefek.pluto.modloader.event.ModUnload;
|
||||
import cz.tefek.pluto.modloader.event.ModUnloadEvent;
|
||||
|
||||
public class ModLoaderCore
|
||||
{
|
||||
public static final String MOD_ID_STRING_PATTERN = "[a-z0-9]+[a-z0-9_]*";
|
||||
public static final String FULL_MOD_ID_STRING_PATTERN = "^" + MOD_ID_STRING_PATTERN + "$";
|
||||
|
||||
static ModLoadingPhase loadingPhase = ModLoadingPhase.WAITING;
|
||||
|
||||
private static final List<Mod> modArchive = new ArrayList<>();
|
||||
|
||||
private static final Queue<Mod> loadBuffer = new LinkedList<>();
|
||||
|
||||
public static void registerMod(Class<?> modClass, String modDataRoot)
|
||||
{
|
||||
if (loadingPhase != ModLoadingPhase.WAITING && loadingPhase != ModLoadingPhase.CLASSLOADING)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Cannot register mod during loading phase " + loadingPhase.name() + "!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (getModByMainClass(modClass) != null)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Mod class " + modClass.getCanonicalName() + " is already registered, skipping it.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (modDataRoot == null)
|
||||
{
|
||||
modDataRoot = ResourceHelper.DEFAULT_RESOURCE_ROOT;
|
||||
}
|
||||
|
||||
var registeredMod = loadBuffer.stream().filter(presentMod -> presentMod.getMainClass().equals(modClass)).findAny();
|
||||
|
||||
if (registeredMod.isPresent())
|
||||
{
|
||||
if (modDataRoot != null)
|
||||
{
|
||||
var mod = registeredMod.get();
|
||||
|
||||
mod.setRootDataFolder(modDataRoot);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Mod mod = new Mod(modClass, modDataRoot);
|
||||
|
||||
if (!mod.getModID().matches(FULL_MOD_ID_STRING_PATTERN))
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Mod id " + mod.getModID() + " contains invalid characters (or none at all), mod will not be loaded.");
|
||||
Logger.log(SmartSeverity.MODULE_WARNING, "Only lowercase letters (a to z) and numbers (0 to 9) are allowed characters.");
|
||||
return;
|
||||
}
|
||||
|
||||
var deps = mod.getDependencies();
|
||||
|
||||
for (var dependency : deps)
|
||||
{
|
||||
var registeredDependency = loadBuffer.stream().filter(presentMod -> presentMod.getMainClass().equals(dependency)).findAny();
|
||||
|
||||
if (registeredDependency.isPresent())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
registerMod(dependency);
|
||||
}
|
||||
|
||||
loadBuffer.add(mod);
|
||||
}
|
||||
|
||||
public static void registerMod(Class<?> modClass)
|
||||
{
|
||||
registerMod(modClass, null);
|
||||
}
|
||||
|
||||
public static List<Mod> getAllMods()
|
||||
{
|
||||
return Collections.unmodifiableList(modArchive);
|
||||
}
|
||||
|
||||
public static Mod getModByID(String id)
|
||||
{
|
||||
for (Mod mod : modArchive)
|
||||
{
|
||||
if (mod.getModID().equals(id))
|
||||
{
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Mod getModByMainClass(Class<?> modClass)
|
||||
{
|
||||
for (Mod mod : modArchive)
|
||||
{
|
||||
if (modClass.equals(mod.getMainClass()))
|
||||
{
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void loadProcedure()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.PREPARING;
|
||||
|
||||
StaticPlutoEventManager.registerEvent(ModPreLoad.class);
|
||||
StaticPlutoEventManager.registerEvent(ModLoad.class);
|
||||
StaticPlutoEventManager.registerEvent(ModPostLoad.class);
|
||||
StaticPlutoEventManager.registerEvent(ModUnload.class);
|
||||
|
||||
Logger.log("[Pluto Mod Loader] Number of manually added mods: " + modArchive.size());
|
||||
loadingPhase = ModLoadingPhase.UPACKING;
|
||||
ModInstaller.unpackNewMods();
|
||||
loadingPhase = ModLoadingPhase.CLASSLOADING;
|
||||
ModClassLoader.loadJars();
|
||||
loadingPhase = ModLoadingPhase.INITIALIZING;
|
||||
|
||||
while (loadBuffer.peek() != null)
|
||||
{
|
||||
var mod = loadBuffer.remove();
|
||||
StaticPlutoEventManager.registerEventHandler(mod.getMainClass());
|
||||
modArchive.add(mod);
|
||||
}
|
||||
|
||||
if (!modArchive.isEmpty())
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Initializing mod(s)...");
|
||||
initMods();
|
||||
|
||||
if (loadingPhase == ModLoadingPhase.FINISHING)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE, "Initializing mod(s) finished.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Initializing mod(s) was canceled due to error(s).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void initMods()
|
||||
{
|
||||
while (loadingPhase != ModLoadingPhase.CANCELED && loadingPhase != ModLoadingPhase.FINISHING)
|
||||
{
|
||||
switch (loadingPhase)
|
||||
{
|
||||
case INITIALIZING:
|
||||
preLoadMods();
|
||||
break;
|
||||
case PRELOADING:
|
||||
loadMods();
|
||||
break;
|
||||
case LOADING:
|
||||
postLoadMods();
|
||||
break;
|
||||
case POSTLOADING:
|
||||
complete();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void unloadProcedure()
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_MINUS, "Unloading all mods.");
|
||||
StaticPlutoEventManager.fireEvent(ModUnload.class, new ModUnloadEvent());
|
||||
modArchive.clear();
|
||||
}
|
||||
|
||||
private static void preLoadMods()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.PRELOADING;
|
||||
|
||||
try
|
||||
{
|
||||
ModPreLoadEvent preLoadData = new ModPreLoadEvent();
|
||||
preLoadData.mods = modArchive;
|
||||
|
||||
StaticPlutoEventManager.fireEvent(ModPreLoad.class, preLoadData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Problem encountered while pre-loading mods.");
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Mod loading stopped.");
|
||||
|
||||
Logger.log(e);
|
||||
|
||||
cancelLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadMods()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.LOADING;
|
||||
|
||||
try
|
||||
{
|
||||
ModLoadEvent loadData = new ModLoadEvent();
|
||||
|
||||
StaticPlutoEventManager.fireEvent(ModLoad.class, loadData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Problem encountered while loading mods.");
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Mod loading stopped.");
|
||||
|
||||
Logger.log(e);
|
||||
|
||||
cancelLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private static void postLoadMods()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.POSTLOADING;
|
||||
|
||||
try
|
||||
{
|
||||
ModPostLoadEvent postLoadData = new ModPostLoadEvent();
|
||||
|
||||
StaticPlutoEventManager.fireEvent(ModPostLoad.class, postLoadData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Problem encountered while post-loading mods.");
|
||||
Logger.log(SmartSeverity.MODULE_ERROR, "Mod loading stopped.");
|
||||
|
||||
Logger.log(e);
|
||||
|
||||
cancelLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private static void complete()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.FINISHING;
|
||||
}
|
||||
|
||||
private static void cancelLoading()
|
||||
{
|
||||
loadingPhase = ModLoadingPhase.CANCELED;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
package cz.tefek.pluto.modloader;
|
||||
|
||||
public enum ModLoadingPhase
|
||||
{
|
||||
UPACKING,
|
||||
PREPARING,
|
||||
INITIALIZING,
|
||||
WAITING,
|
||||
PRELOADING,
|
||||
LOADING,
|
||||
POSTLOADING,
|
||||
FINISHING,
|
||||
CANCELED,
|
||||
INSTANTIATING,
|
||||
CLASSLOADING,
|
||||
UNLOADING;
|
||||
}
|
||||
package cz.tefek.pluto.modloader;
|
||||
|
||||
public enum ModLoadingPhase
|
||||
{
|
||||
UPACKING,
|
||||
PREPARING,
|
||||
INITIALIZING,
|
||||
WAITING,
|
||||
PRELOADING,
|
||||
LOADING,
|
||||
POSTLOADING,
|
||||
FINISHING,
|
||||
CANCELED,
|
||||
INSTANTIATING,
|
||||
CLASSLOADING,
|
||||
UNLOADING;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
@StaticPlutoEvent(passingParamClass = ModLoadEvent.class)
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public @interface ModLoad
|
||||
{
|
||||
|
||||
}
|
||||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
@StaticPlutoEvent(passingParamClass = ModLoadEvent.class)
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public @interface ModLoad
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.EventData;
|
||||
import cz.tefek.pluto.modloader.ModEntry;
|
||||
|
||||
/**
|
||||
* Instances are passed to {@link ModEntry ModEntries} on load. Currently does
|
||||
* nothing.
|
||||
*
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class ModLoadEvent extends EventData
|
||||
{
|
||||
}
|
||||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.EventData;
|
||||
import cz.tefek.pluto.modloader.ModEntry;
|
||||
|
||||
/**
|
||||
* Instances are passed to {@link ModEntry ModEntries} on load. Currently does
|
||||
* nothing.
|
||||
*
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class ModLoadEvent extends EventData
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
@StaticPlutoEvent(passingParamClass = ModPostLoadEvent.class)
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public @interface ModPostLoad
|
||||
{
|
||||
|
||||
}
|
||||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
@StaticPlutoEvent(passingParamClass = ModPostLoadEvent.class)
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public @interface ModPostLoad
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.EventData;
|
||||
import cz.tefek.pluto.modloader.ModEntry;
|
||||
|
||||
/**
|
||||
* Instances are passed to {@link ModEntry ModEntries} on post-load.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class ModPostLoadEvent extends EventData
|
||||
{
|
||||
// TODO Cross-mod messaging
|
||||
public final List<String> crossMessages = new ArrayList<String>();
|
||||
}
|
||||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.EventData;
|
||||
import cz.tefek.pluto.modloader.ModEntry;
|
||||
|
||||
/**
|
||||
* Instances are passed to {@link ModEntry ModEntries} on post-load.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class ModPostLoadEvent extends EventData
|
||||
{
|
||||
// TODO Cross-mod messaging
|
||||
public final List<String> crossMessages = new ArrayList<String>();
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
@StaticPlutoEvent(passingParamClass = ModPreLoadEvent.class)
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public @interface ModPreLoad
|
||||
{
|
||||
|
||||
}
|
||||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
@StaticPlutoEvent(passingParamClass = ModPreLoadEvent.class)
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public @interface ModPreLoad
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.EventData;
|
||||
import cz.tefek.pluto.modloader.Mod;
|
||||
import cz.tefek.pluto.modloader.ModEntry;
|
||||
|
||||
/**
|
||||
* Instances are passed to {@link ModEntry ModEntries} on load. Carries a list
|
||||
* of all {@link Mod} objects.
|
||||
*
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class ModPreLoadEvent extends EventData
|
||||
{
|
||||
public List<Mod> mods;
|
||||
}
|
||||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.EventData;
|
||||
import cz.tefek.pluto.modloader.Mod;
|
||||
import cz.tefek.pluto.modloader.ModEntry;
|
||||
|
||||
/**
|
||||
* Instances are passed to {@link ModEntry ModEntries} on load. Carries a list
|
||||
* of all {@link Mod} objects.
|
||||
*
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class ModPreLoadEvent extends EventData
|
||||
{
|
||||
public List<Mod> mods;
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
@StaticPlutoEvent(passingParamClass = ModUnloadEvent.class)
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public @interface ModUnload {
|
||||
|
||||
}
|
||||
package cz.tefek.pluto.modloader.event;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import cz.tefek.pluto.eventsystem.staticmode.StaticPlutoEvent;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target(METHOD)
|
||||
@StaticPlutoEvent(passingParamClass = ModUnloadEvent.class)
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public @interface ModUnload {
|
||||
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
package cz.tefek.pluto.tpl;
|
||||
|
||||
public class TPJImage
|
||||
{
|
||||
int[] data;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
public TPJImage(int[] pixels, int width, int height)
|
||||
{
|
||||
this.data = pixels;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public int getHeight()
|
||||
{
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public int[] getData()
|
||||
{
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public int pixelAt(int x, int y)
|
||||
{
|
||||
return this.data[x + y * this.width];
|
||||
}
|
||||
}
|
|
@ -1,187 +1,312 @@
|
|||
package cz.tefek.pluto.tpl;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.Raster;
|
||||
import java.io.File;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* Quick ABGR (8-bit per channel, 32 bits per pixel) texture loader for OpenGL
|
||||
* use. Color component swizzling may be needed.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class TPL
|
||||
{
|
||||
private static final int PLACEHOLDER_SIZE = 16;
|
||||
private static final int PLACEHOLDER_CHECKEDBOARD = 8;
|
||||
private static final int PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE = PLACEHOLDER_SIZE / PLACEHOLDER_CHECKEDBOARD;
|
||||
|
||||
public static TPNImage load(ResourceAddress file)
|
||||
{
|
||||
return file == null ? loadImage(null) : load(file.toPath());
|
||||
}
|
||||
|
||||
public static TPNImage load(String file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
return loadImage(null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return loadImage(ImageIO.read(new File(file)));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "[TPL] Image could not be loaded: " + file);
|
||||
Logger.log(e);
|
||||
|
||||
return loadImage(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static TPNImage loadImage(BufferedImage image)
|
||||
{
|
||||
boolean remake = false;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
Logger.log(SmartSeverity.WARNING, "[TPL] Null BufferedImage supplied, generating a placeholder.");
|
||||
|
||||
remake = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = image.getWidth();
|
||||
height = image.getHeight();
|
||||
|
||||
if (width > 16384 || height > 16384 || width < 1 || height < 1)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "[TPL] BufferedImage size is invalid (< 1 or > 16384), generating a placeholder.");
|
||||
|
||||
remake = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (remake)
|
||||
{
|
||||
width = PLACEHOLDER_SIZE;
|
||||
height = PLACEHOLDER_SIZE;
|
||||
|
||||
Logger.log(SmartSeverity.INFO, "[TPL] Generating a substitute image...");
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4);
|
||||
buffer.order(ByteOrder.nativeOrder());
|
||||
|
||||
for (int i = 0; i < width * height; i++)
|
||||
{
|
||||
int x = i % width;
|
||||
int y = i / width;
|
||||
boolean checker = x / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2 == y / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2;
|
||||
|
||||
buffer.put((byte) 0xff); // A
|
||||
buffer.put((byte) 0x00); // B
|
||||
buffer.put((byte) 0x00); // G
|
||||
buffer.put((byte) (checker ? 0xff : 0x00)); // R
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
return new TPNImage(buffer, width, height);
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.nativeOrder());
|
||||
|
||||
BufferedImage copy = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
|
||||
Graphics2D imgGraphics = copy.createGraphics();
|
||||
imgGraphics.drawImage(image, 0, copy.getHeight(), copy.getWidth(), 0, 0, 0, image.getWidth(), image.getHeight(), null); // I wonder if this is pixel-perfect
|
||||
imgGraphics.dispose();
|
||||
|
||||
Raster data = copy.getData();
|
||||
DataBuffer dataBuffer = data.getDataBuffer();
|
||||
DataBufferByte byteBuffer = (DataBufferByte) dataBuffer;
|
||||
byte[] byteData = byteBuffer.getData();
|
||||
buffer.put(byteData);
|
||||
buffer.flip();
|
||||
|
||||
return new TPNImage(buffer, width, height);
|
||||
}
|
||||
|
||||
public static TPJImage loadPixels(String file)
|
||||
{
|
||||
TPJImage tImg = null;
|
||||
BufferedImage image = null;
|
||||
|
||||
boolean remake = false;
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
try
|
||||
{
|
||||
image = ImageIO.read(new File(file));
|
||||
width = image.getWidth();
|
||||
height = image.getHeight();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "[TPL] Image could not be loaded: " + file);
|
||||
Logger.log(e);
|
||||
|
||||
remake = true;
|
||||
}
|
||||
|
||||
if (width > 16384 || height > 16384 || width < 1 || height < 1)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "[TPL] Image size is invalid (< 1 or > 16384): " + file);
|
||||
Logger.log(SmartSeverity.ERROR, "[TPL] A replacement will be generated.");
|
||||
|
||||
remake = true;
|
||||
}
|
||||
|
||||
if (remake)
|
||||
{
|
||||
width = PLACEHOLDER_SIZE;
|
||||
height = PLACEHOLDER_SIZE;
|
||||
|
||||
tImg = new TPJImage(new int[width * height], width, height);
|
||||
|
||||
Logger.log(SmartSeverity.INFO, "[TPL] Generating a substitute image...");
|
||||
|
||||
for (int i = 0; i < width * height; i++)
|
||||
{
|
||||
int x = i % width;
|
||||
int y = i / width;
|
||||
boolean checker = x / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2 == y / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2;
|
||||
|
||||
tImg.data[i] = checker ? 0xffff0000 : 0xff000000;
|
||||
}
|
||||
|
||||
return tImg;
|
||||
}
|
||||
|
||||
tImg = new TPJImage(new int[width * height], width, height);
|
||||
|
||||
for (int i = 0; i < width * height; i++)
|
||||
{
|
||||
int pixel = image.getRGB(i % width, i / width);
|
||||
|
||||
tImg.data[i] = pixel;
|
||||
}
|
||||
|
||||
return tImg;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.tpl;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
* Quick ABGR (8-bit per channel, 32 bits per pixel) image loader for OpenGL textures.
|
||||
* Color component swizzling may be needed.
|
||||
*
|
||||
* FIXME: Refactor {@link TPL#loadBufferedImage}
|
||||
*
|
||||
* @author 493msi
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since pre-alpha
|
||||
*/
|
||||
public class TPL
|
||||
{
|
||||
private static final int PLACEHOLDER_SIZE = 16;
|
||||
|
||||
private static final int PLACEHOLDER_CHECKEDBOARD = 8;
|
||||
private static final int PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE = PLACEHOLDER_SIZE / PLACEHOLDER_CHECKEDBOARD;
|
||||
|
||||
private static final BufferedImage placeholder;
|
||||
|
||||
static
|
||||
{
|
||||
placeholder = new BufferedImage(PLACEHOLDER_SIZE, PLACEHOLDER_SIZE, BufferedImage.TYPE_INT_ARGB);
|
||||
var data = placeholder.getData();
|
||||
var dataBuffer = (DataBufferInt) data.getDataBuffer();
|
||||
|
||||
for (int i = 0; i < PLACEHOLDER_SIZE * PLACEHOLDER_SIZE; i++)
|
||||
{
|
||||
int x = i % PLACEHOLDER_SIZE;
|
||||
int y = i / PLACEHOLDER_SIZE;
|
||||
boolean checker = x / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2 == y / PLACEHOLDER_CHECKEDBOARD_SQUARE_SIZE % 2;
|
||||
|
||||
dataBuffer.setElem(i, checker ? 0xFFFF0000 : 0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix this mess
|
||||
private static BufferedImage loadBufferedImage(@Nullable Object source)
|
||||
{
|
||||
var inputStream = (InputStream) null;
|
||||
|
||||
try
|
||||
{
|
||||
if (source instanceof ResourceAddress)
|
||||
{
|
||||
inputStream = Files.newInputStream(((ResourceAddress) source).toNIOPath());
|
||||
}
|
||||
else if (source instanceof String)
|
||||
{
|
||||
inputStream = new FileInputStream((String) source);
|
||||
}
|
||||
else if (source instanceof File)
|
||||
{
|
||||
inputStream = new FileInputStream((File) source);
|
||||
}
|
||||
else if (source instanceof Path)
|
||||
{
|
||||
inputStream = Files.newInputStream((Path) source);
|
||||
}
|
||||
|
||||
if (inputStream != null)
|
||||
{
|
||||
return ImageIO.read(inputStream);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "[TPL] Image could not be loaded: " + source);
|
||||
Logger.log(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (inputStream != null)
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "[TPL] Failed to close: " + source);
|
||||
Logger.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an image from a file the denoted by the input {@link ResourceAddress} into a {@link TPNImage} buffer.
|
||||
*
|
||||
* If the input {@link ResourceAddress} is null, a placeholder will be generated.
|
||||
*
|
||||
* @param address The source {@link ResourceAddress}, from which the image will be loaded
|
||||
*
|
||||
* @return The output {@link TPNImage}, never null
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static TPNImage load(@Nullable ResourceAddress address)
|
||||
{
|
||||
return loadImage(loadBufferedImage(address));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an image from the denoted filename to a {@link TPNImage} buffer.
|
||||
*
|
||||
* If the input filename is null, a placeholder will be generated.
|
||||
*
|
||||
* @deprecated Use the {@link TPL#load(ResourceAddress)} or {@link TPL#load(File)} variants.
|
||||
*
|
||||
* @param filename The source filename, from which the image will be loaded
|
||||
*
|
||||
* @return The output {@link TPNImage}, never null
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
@Deprecated
|
||||
public static TPNImage load(@Nullable String filename)
|
||||
{
|
||||
return loadImage(loadBufferedImage(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an image from the input {@link File} into a {@link TPNImage} buffer.
|
||||
*
|
||||
* If the input {@link File} is null, a placeholder will be generated.
|
||||
*
|
||||
* @param file The source {@link File}, from which the image will be loaded
|
||||
*
|
||||
* @return The output {@link TPNImage}, never null
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since 20.2.0.0-alpha.1
|
||||
* */
|
||||
public static TPNImage load(@Nullable File file)
|
||||
{
|
||||
return loadImage(loadBufferedImage(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an image from a file the denoted by the input {@link Path} into a {@link TPNImage} buffer.
|
||||
*
|
||||
* If the input {@link Path} is null, a placeholder will be generated.
|
||||
*
|
||||
* @param path The source {@link Path}, from which the image will be loaded
|
||||
*
|
||||
* @return The output {@link TPNImage}, never null
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since 20.2.0.0-alpha.1
|
||||
* */
|
||||
public static TPNImage load(@Nullable Path path)
|
||||
{
|
||||
return loadImage(loadBufferedImage(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an image from a file the denoted by the input {@link ResourceAddress} into a {@link TPNImage} buffer.
|
||||
*
|
||||
* If the input {@link ResourceAddress} is null, a placeholder will be generated.
|
||||
*
|
||||
* @param address The source {@link ResourceAddress}, from which the image will be loaded
|
||||
* @param flipY Whether the image should flipped vertically (for OpenGL uses)
|
||||
*
|
||||
* @return The output {@link TPNImage}, never null
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since 20.2.0.0-alpha.1
|
||||
* */
|
||||
public static TPNImage loadSpecial(@Nullable ResourceAddress address, boolean flipY)
|
||||
{
|
||||
return loadImageSpecial(loadBufferedImage(address), flipY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads an image from the input {@link File} into a {@link TPNImage} buffer.
|
||||
*
|
||||
* If the input {@link File} is null, a placeholder will be generated.
|
||||
*
|
||||
* @param file The source {@link File}, from which the image will be loaded
|
||||
* @param flipY Whether the image should flipped vertically (for OpenGL uses)
|
||||
*
|
||||
* @return The output {@link TPNImage}, never null
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since 20.2.0.0-alpha.1
|
||||
* */
|
||||
public static TPNImage load(@Nullable File file, boolean flipY)
|
||||
{
|
||||
return loadImageSpecial(loadBufferedImage(file), flipY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an image from a file the denoted by the input {@link Path} into a {@link TPNImage} buffer.
|
||||
*
|
||||
* If the input {@link Path} is null, a placeholder will be generated.
|
||||
*
|
||||
* @param path The source {@link Path}, from which the image will be loaded
|
||||
* @param flipY Whether the image should flipped vertically (for OpenGL uses)
|
||||
*
|
||||
* @return The output {@link TPNImage}, never null
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since 20.2.0.0-alpha.1
|
||||
* */
|
||||
public static TPNImage loadSpecial(@Nullable Path path, boolean flipY)
|
||||
{
|
||||
return loadImageSpecial(loadBufferedImage(path), flipY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@link BufferedImage} into a {@link TPNImage} buffer.
|
||||
*
|
||||
* If the input {@link Path} is null, a placeholder will be generated.
|
||||
*
|
||||
* @param image The source {@link BufferedImage}
|
||||
* @param flipY Whether the image should flipped vertically (for OpenGL uses)
|
||||
*
|
||||
* @return The output {@link TPNImage}, never null
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since 20.2.0.0-alpha.1
|
||||
* */
|
||||
public static TPNImage loadImageSpecial(@Nullable BufferedImage image, boolean flipY)
|
||||
{
|
||||
if (image == null)
|
||||
{
|
||||
Logger.log(SmartSeverity.WARNING, "[TPL] Null BufferedImage supplied, generating a placeholder.");
|
||||
|
||||
return loadImageSpecial(placeholder, flipY);
|
||||
}
|
||||
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
if (width > 16384 || height > 16384 || width < 1 || height < 1)
|
||||
{
|
||||
Logger.log(SmartSeverity.ERROR, "[TPL] BufferedImage size is invalid (< 1 or > 16384), generating a placeholder.");
|
||||
|
||||
return loadImageSpecial(placeholder, flipY);
|
||||
}
|
||||
|
||||
BufferedImage copy = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
|
||||
Graphics2D imgGraphics = copy.createGraphics();
|
||||
imgGraphics.drawImage(image,
|
||||
0, flipY ? copy.getHeight() : 0, copy.getWidth(), flipY ? 0 : copy.getHeight(),
|
||||
0, 0, image.getWidth(), image.getHeight(),
|
||||
null); // I wonder if this is pixel-perfect
|
||||
imgGraphics.dispose();
|
||||
|
||||
Raster data = copy.getRaster();
|
||||
DataBuffer dataBuffer = data.getDataBuffer();
|
||||
DataBufferByte byteBuffer = (DataBufferByte) dataBuffer;
|
||||
byte[] byteData = byteBuffer.getData();
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.nativeOrder());
|
||||
buffer.put(byteData);
|
||||
buffer.flip();
|
||||
|
||||
return new TPNImage(buffer, width, height);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes a {@link BufferedImage} into a {@link TPNImage} buffer.
|
||||
*
|
||||
* If the input {@link Path} is null, a placeholder will be generated.
|
||||
*
|
||||
* @param image The source {@link BufferedImage}
|
||||
*
|
||||
* @return The output {@link TPNImage}, never null
|
||||
*
|
||||
* @see TPNImage
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public static TPNImage loadImage(@Nullable BufferedImage image)
|
||||
{
|
||||
return loadImageSpecial(image, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,73 @@
|
|||
package cz.tefek.pluto.tpl;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class TPNImage
|
||||
{
|
||||
ByteBuffer data;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
public TPNImage(ByteBuffer bfr, int width, int height)
|
||||
{
|
||||
this.data = bfr;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public int getHeight()
|
||||
{
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public ByteBuffer getData()
|
||||
{
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.tpl;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A wrapper around a native color buffer for easier handling
|
||||
* by various APIs, such as OpenGL and GLFW.
|
||||
*
|
||||
* @implNote TPNImage is <em>always</em> ABGR due to image format
|
||||
* limitations of {@link java.awt.image.BufferedImage}
|
||||
*
|
||||
* @author 493msi
|
||||
*
|
||||
* @since pre-alpha
|
||||
*/
|
||||
public class TPNImage
|
||||
{
|
||||
private final ByteBuffer data;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TPNImage} from the specified buffer, width and height.
|
||||
*
|
||||
* @param bfr The input {@link ByteBuffer}
|
||||
* @param width This image's width
|
||||
* @param height This image's height
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public TPNImage(ByteBuffer bfr, int width, int height)
|
||||
{
|
||||
this.data = bfr;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of the color buffer.
|
||||
*
|
||||
* @return The width of this {@link TPNImage}
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public int getWidth()
|
||||
{
|
||||
return this.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the color buffer.
|
||||
*
|
||||
* @return The height of this {@link TPNImage}
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public int getHeight()
|
||||
{
|
||||
return this.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a read-only view of the color buffer.
|
||||
*
|
||||
* @return This image's color {@link ByteBuffer}
|
||||
*
|
||||
* @since pre-alpha
|
||||
* */
|
||||
public ByteBuffer getData()
|
||||
{
|
||||
return this.data.asReadOnlyBuffer();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,57 +1,57 @@
|
|||
package cz.tefek.pluto.engine.graphics.gl.vao;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class QuadPresets
|
||||
{
|
||||
public static VertexArray basicQuad()
|
||||
{
|
||||
float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 };
|
||||
|
||||
float[] positions = { -1, 1, 1, 1, 1, -1, -1, -1 };
|
||||
|
||||
int[] indices = { 0, 1, 2, 0, 2, 3 };
|
||||
|
||||
VertexArrayBuilder vab = new VertexArrayBuilder();
|
||||
vab.vertices(new VecArray<>(positions, 2));
|
||||
vab.uvs(new VecArray<>(uvs, 2));
|
||||
vab.indices(new VecArray<>(indices, 1));
|
||||
|
||||
return vab.export();
|
||||
}
|
||||
|
||||
public static VertexArray halvedSize()
|
||||
{
|
||||
float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 };
|
||||
|
||||
float[] positions = { -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f };
|
||||
|
||||
int[] indices = { 0, 1, 2, 0, 2, 3 };
|
||||
|
||||
VertexArrayBuilder vab = new VertexArrayBuilder();
|
||||
vab.vertices(new VecArray<>(positions, 2));
|
||||
vab.uvs(new VecArray<>(uvs, 2));
|
||||
vab.indices(new VecArray<>(indices, 1));
|
||||
return vab.export();
|
||||
}
|
||||
|
||||
public static VertexArray basicNoNeg()
|
||||
{
|
||||
float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 };
|
||||
|
||||
float[] positions = { 0, 1, 1, 1, 1, 0, 0, 0 };
|
||||
|
||||
int[] indices = { 0, 1, 2, 0, 2, 3 };
|
||||
|
||||
VertexArrayBuilder vab = new VertexArrayBuilder();
|
||||
vab.vertices(new VecArray<>(positions, 2));
|
||||
vab.uvs(new VecArray<>(uvs, 2));
|
||||
vab.indices(new VecArray<>(indices, 1));
|
||||
|
||||
return vab.export();
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.gl.vao;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class QuadPresets
|
||||
{
|
||||
public static VertexArray basicQuad()
|
||||
{
|
||||
float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 };
|
||||
|
||||
float[] positions = { -1, 1, 1, 1, 1, -1, -1, -1 };
|
||||
|
||||
int[] indices = { 0, 1, 2, 0, 2, 3 };
|
||||
|
||||
VertexArrayBuilder vab = new VertexArrayBuilder();
|
||||
vab.vertices(new VecArray<>(positions, 2));
|
||||
vab.uvs(new VecArray<>(uvs, 2));
|
||||
vab.indices(new VecArray<>(indices, 1));
|
||||
|
||||
return vab.export();
|
||||
}
|
||||
|
||||
public static VertexArray halvedSize()
|
||||
{
|
||||
float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 };
|
||||
|
||||
float[] positions = { -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f };
|
||||
|
||||
int[] indices = { 0, 1, 2, 0, 2, 3 };
|
||||
|
||||
VertexArrayBuilder vab = new VertexArrayBuilder();
|
||||
vab.vertices(new VecArray<>(positions, 2));
|
||||
vab.uvs(new VecArray<>(uvs, 2));
|
||||
vab.indices(new VecArray<>(indices, 1));
|
||||
return vab.export();
|
||||
}
|
||||
|
||||
public static VertexArray basicNoNeg()
|
||||
{
|
||||
float[] uvs = { 0, 0, 1, 0, 1, 1, 0, 1 };
|
||||
|
||||
float[] positions = { 0, 1, 1, 1, 1, 0, 0, 0 };
|
||||
|
||||
int[] indices = { 0, 1, 2, 0, 2, 3 };
|
||||
|
||||
VertexArrayBuilder vab = new VertexArrayBuilder();
|
||||
vab.vertices(new VecArray<>(positions, 2));
|
||||
vab.uvs(new VecArray<>(uvs, 2));
|
||||
vab.indices(new VecArray<>(indices, 1));
|
||||
|
||||
return vab.export();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,145 +1,145 @@
|
|||
package cz.tefek.pluto.engine.graphics.gl.vao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vbo.ArrayBuffer;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public class VertexArray
|
||||
{
|
||||
protected final List<Integer> usedAttribs;
|
||||
protected final Vector<ArrayBuffer<?>> vertexAttribs;
|
||||
|
||||
protected IndexArrayBuffer indices;
|
||||
|
||||
private int vertexCount;
|
||||
protected int glID = 0;
|
||||
|
||||
public VertexArray()
|
||||
{
|
||||
int maxAttribs = GL33.glGetInteger(GL33.GL_MAX_VERTEX_ATTRIBS);
|
||||
|
||||
this.usedAttribs = new ArrayList<>(maxAttribs);
|
||||
this.vertexAttribs = new Vector<ArrayBuffer<?>>(maxAttribs);
|
||||
this.vertexAttribs.setSize(maxAttribs);
|
||||
|
||||
this.glID = GL33.glGenVertexArrays();
|
||||
|
||||
Logger.logf(SmartSeverity.ADDED, "Vertex array ID %d created...\n", this.glID);
|
||||
}
|
||||
|
||||
public void createArrayAttrib(ArrayBuffer<?> buffer, int attribID)
|
||||
{
|
||||
this.bind();
|
||||
buffer.bind();
|
||||
GL33.glVertexAttribPointer(attribID, buffer.getVertexDimensions(), buffer.getType().getGLID(), false, 0, 0);
|
||||
|
||||
this.vertexAttribs.set(attribID, buffer);
|
||||
this.usedAttribs.add(attribID);
|
||||
|
||||
if (!this.hasIndices())
|
||||
{
|
||||
this.vertexCount = buffer.getVertexCount();
|
||||
}
|
||||
}
|
||||
|
||||
public List<ArrayBuffer<?>> getVertexAttribs()
|
||||
{
|
||||
return Collections.unmodifiableList(this.vertexAttribs);
|
||||
}
|
||||
|
||||
public int getVertexCount()
|
||||
{
|
||||
return this.vertexCount;
|
||||
}
|
||||
|
||||
public void enableAllAttributes()
|
||||
{
|
||||
this.usedAttribs.stream().forEach(GL33::glEnableVertexAttribArray);
|
||||
}
|
||||
|
||||
public void bindIndices(IndexArrayBuffer buffer)
|
||||
{
|
||||
this.bind();
|
||||
buffer.bind();
|
||||
this.indices = buffer;
|
||||
this.vertexCount = buffer.getVertexCount();
|
||||
}
|
||||
|
||||
public void bind()
|
||||
{
|
||||
GL33.glBindVertexArray(this.glID);
|
||||
}
|
||||
|
||||
public void unbind()
|
||||
{
|
||||
GL33.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public void draw(DrawMode mode)
|
||||
{
|
||||
if (this.hasIndices())
|
||||
{
|
||||
GL33.glDrawElements(mode.getGLID(), this.vertexCount, this.indices.getType().getGLID(), MemoryUtil.NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL33.glDrawArrays(mode.getGLID(), 0, this.vertexCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawInstanced(DrawMode mode, int count)
|
||||
{
|
||||
if (this.hasIndices())
|
||||
{
|
||||
GL33.glDrawElementsInstanced(mode.getGLID(), this.vertexCount, this.indices.getType().getGLID(), MemoryUtil.NULL, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL33.glDrawArraysInstanced(mode.getGLID(), 0, this.vertexCount, count);
|
||||
}
|
||||
}
|
||||
|
||||
public IndexArrayBuffer getIndices()
|
||||
{
|
||||
return this.indices;
|
||||
}
|
||||
|
||||
public boolean hasIndices()
|
||||
{
|
||||
return this.indices != null;
|
||||
}
|
||||
|
||||
public void delete()
|
||||
{
|
||||
this.usedAttribs.stream().map(this.vertexAttribs::get).forEach(ArrayBuffer::delete);
|
||||
this.vertexAttribs.clear();
|
||||
this.usedAttribs.clear();
|
||||
|
||||
if (this.indices != null)
|
||||
{
|
||||
this.indices.delete();
|
||||
this.indices = null;
|
||||
}
|
||||
|
||||
Logger.logf(SmartSeverity.REMOVED, "Vertex array ID %d deleted...\n", this.glID);
|
||||
|
||||
GL33.glDeleteVertexArrays(this.glID);
|
||||
|
||||
this.glID = 0;
|
||||
}
|
||||
|
||||
public int getID()
|
||||
{
|
||||
return this.glID;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.gl.vao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vbo.ArrayBuffer;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public class VertexArray
|
||||
{
|
||||
protected final List<Integer> usedAttribs;
|
||||
protected final Vector<ArrayBuffer<?>> vertexAttribs;
|
||||
|
||||
protected IndexArrayBuffer indices;
|
||||
|
||||
private int vertexCount;
|
||||
protected int glID = 0;
|
||||
|
||||
public VertexArray()
|
||||
{
|
||||
int maxAttribs = GL33.glGetInteger(GL33.GL_MAX_VERTEX_ATTRIBS);
|
||||
|
||||
this.usedAttribs = new ArrayList<>(maxAttribs);
|
||||
this.vertexAttribs = new Vector<ArrayBuffer<?>>(maxAttribs);
|
||||
this.vertexAttribs.setSize(maxAttribs);
|
||||
|
||||
this.glID = GL33.glGenVertexArrays();
|
||||
|
||||
Logger.logf(SmartSeverity.ADDED, "Vertex array ID %d created...\n", this.glID);
|
||||
}
|
||||
|
||||
public void createArrayAttrib(ArrayBuffer<?> buffer, int attribID)
|
||||
{
|
||||
this.bind();
|
||||
buffer.bind();
|
||||
GL33.glVertexAttribPointer(attribID, buffer.getVertexDimensions(), buffer.getType().getGLID(), false, 0, 0);
|
||||
|
||||
this.vertexAttribs.set(attribID, buffer);
|
||||
this.usedAttribs.add(attribID);
|
||||
|
||||
if (!this.hasIndices())
|
||||
{
|
||||
this.vertexCount = buffer.getVertexCount();
|
||||
}
|
||||
}
|
||||
|
||||
public List<ArrayBuffer<?>> getVertexAttribs()
|
||||
{
|
||||
return Collections.unmodifiableList(this.vertexAttribs);
|
||||
}
|
||||
|
||||
public int getVertexCount()
|
||||
{
|
||||
return this.vertexCount;
|
||||
}
|
||||
|
||||
public void enableAllAttributes()
|
||||
{
|
||||
this.usedAttribs.stream().forEach(GL33::glEnableVertexAttribArray);
|
||||
}
|
||||
|
||||
public void bindIndices(IndexArrayBuffer buffer)
|
||||
{
|
||||
this.bind();
|
||||
buffer.bind();
|
||||
this.indices = buffer;
|
||||
this.vertexCount = buffer.getVertexCount();
|
||||
}
|
||||
|
||||
public void bind()
|
||||
{
|
||||
GL33.glBindVertexArray(this.glID);
|
||||
}
|
||||
|
||||
public void unbind()
|
||||
{
|
||||
GL33.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public void draw(DrawMode mode)
|
||||
{
|
||||
if (this.hasIndices())
|
||||
{
|
||||
GL33.glDrawElements(mode.getGLID(), this.vertexCount, this.indices.getType().getGLID(), MemoryUtil.NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL33.glDrawArrays(mode.getGLID(), 0, this.vertexCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawInstanced(DrawMode mode, int count)
|
||||
{
|
||||
if (this.hasIndices())
|
||||
{
|
||||
GL33.glDrawElementsInstanced(mode.getGLID(), this.vertexCount, this.indices.getType().getGLID(), MemoryUtil.NULL, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL33.glDrawArraysInstanced(mode.getGLID(), 0, this.vertexCount, count);
|
||||
}
|
||||
}
|
||||
|
||||
public IndexArrayBuffer getIndices()
|
||||
{
|
||||
return this.indices;
|
||||
}
|
||||
|
||||
public boolean hasIndices()
|
||||
{
|
||||
return this.indices != null;
|
||||
}
|
||||
|
||||
public void delete()
|
||||
{
|
||||
this.usedAttribs.stream().map(this.vertexAttribs::get).forEach(ArrayBuffer::delete);
|
||||
this.vertexAttribs.clear();
|
||||
this.usedAttribs.clear();
|
||||
|
||||
if (this.indices != null)
|
||||
{
|
||||
this.indices.delete();
|
||||
this.indices = null;
|
||||
}
|
||||
|
||||
Logger.logf(SmartSeverity.REMOVED, "Vertex array ID %d deleted...\n", this.glID);
|
||||
|
||||
GL33.glDeleteVertexArrays(this.glID);
|
||||
|
||||
this.glID = 0;
|
||||
}
|
||||
|
||||
public int getID()
|
||||
{
|
||||
return this.glID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
package cz.tefek.pluto.engine.graphics.gl.vao;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vbo.FloatArrayBuffer;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer;
|
||||
|
||||
public class VertexArrayBuilder
|
||||
{
|
||||
protected VertexArray va;
|
||||
|
||||
public VertexArrayBuilder()
|
||||
{
|
||||
this.va = new VertexArray();
|
||||
}
|
||||
|
||||
public VertexArrayBuilder vertices(VecArray<float[]> vertices)
|
||||
{
|
||||
this.va.createArrayAttrib(new FloatArrayBuffer(vertices), ReservedAttributes.POSITION);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public VertexArrayBuilder uvs(VecArray<float[]> uvs)
|
||||
{
|
||||
this.va.createArrayAttrib(new FloatArrayBuffer(uvs), ReservedAttributes.UV);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public VertexArrayBuilder indices(VecArray<int[]> indices)
|
||||
{
|
||||
this.va.bindIndices(new IndexArrayBuffer(indices));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public VertexArray export()
|
||||
{
|
||||
return this.va;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.gl.vao;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vbo.FloatArrayBuffer;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vbo.IndexArrayBuffer;
|
||||
|
||||
public class VertexArrayBuilder
|
||||
{
|
||||
protected VertexArray va;
|
||||
|
||||
public VertexArrayBuilder()
|
||||
{
|
||||
this.va = new VertexArray();
|
||||
}
|
||||
|
||||
public VertexArrayBuilder vertices(VecArray<float[]> vertices)
|
||||
{
|
||||
this.va.createArrayAttrib(new FloatArrayBuffer(vertices), ReservedAttributes.POSITION);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public VertexArrayBuilder uvs(VecArray<float[]> uvs)
|
||||
{
|
||||
this.va.createArrayAttrib(new FloatArrayBuffer(uvs), ReservedAttributes.UV);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public VertexArrayBuilder indices(VecArray<int[]> indices)
|
||||
{
|
||||
this.va.bindIndices(new IndexArrayBuffer(indices));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public VertexArray export()
|
||||
{
|
||||
return this.va;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +1,62 @@
|
|||
package cz.tefek.pluto.engine.graphics.gl.vbo;
|
||||
|
||||
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
|
||||
import static org.lwjgl.opengl.GL15.glGenBuffers;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
|
||||
public abstract class ArrayBuffer<T extends VecArray<?>>
|
||||
{
|
||||
protected int glID = 0;
|
||||
|
||||
private final int vertexDimensions;
|
||||
private final int vertexCount;
|
||||
|
||||
public ArrayBuffer(T data)
|
||||
{
|
||||
this.glID = glGenBuffers();
|
||||
this.bind();
|
||||
this.bindData(data);
|
||||
|
||||
this.vertexDimensions = data.getVecDimensions();
|
||||
this.vertexCount = data.getVertexCount();
|
||||
}
|
||||
|
||||
public abstract EnumArrayBufferType getType();
|
||||
|
||||
protected abstract void bindData(T data);
|
||||
|
||||
public void bind()
|
||||
{
|
||||
glBindBuffer(GL_ARRAY_BUFFER, this.glID);
|
||||
}
|
||||
|
||||
public void unbind()
|
||||
{
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
public void delete()
|
||||
{
|
||||
glDeleteBuffers(this.glID);
|
||||
|
||||
this.glID = 0;
|
||||
}
|
||||
|
||||
public int getID()
|
||||
{
|
||||
return this.glID;
|
||||
}
|
||||
|
||||
public int getVertexDimensions()
|
||||
{
|
||||
return this.vertexDimensions;
|
||||
}
|
||||
|
||||
public int getVertexCount()
|
||||
{
|
||||
return this.vertexCount;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.gl.vbo;
|
||||
|
||||
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
|
||||
import static org.lwjgl.opengl.GL15.glGenBuffers;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
|
||||
public abstract class ArrayBuffer<T extends VecArray<?>>
|
||||
{
|
||||
protected int glID = 0;
|
||||
|
||||
private final int vertexDimensions;
|
||||
private final int vertexCount;
|
||||
|
||||
public ArrayBuffer(T data)
|
||||
{
|
||||
this.glID = glGenBuffers();
|
||||
this.bind();
|
||||
this.bindData(data);
|
||||
|
||||
this.vertexDimensions = data.getVecDimensions();
|
||||
this.vertexCount = data.getVertexCount();
|
||||
}
|
||||
|
||||
public abstract EnumArrayBufferType getType();
|
||||
|
||||
protected abstract void bindData(T data);
|
||||
|
||||
public void bind()
|
||||
{
|
||||
glBindBuffer(GL_ARRAY_BUFFER, this.glID);
|
||||
}
|
||||
|
||||
public void unbind()
|
||||
{
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
public void delete()
|
||||
{
|
||||
glDeleteBuffers(this.glID);
|
||||
|
||||
this.glID = 0;
|
||||
}
|
||||
|
||||
public int getID()
|
||||
{
|
||||
return this.glID;
|
||||
}
|
||||
|
||||
public int getVertexDimensions()
|
||||
{
|
||||
return this.vertexDimensions;
|
||||
}
|
||||
|
||||
public int getVertexCount()
|
||||
{
|
||||
return this.vertexCount;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
package cz.tefek.pluto.engine.graphics.gl.vbo;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
|
||||
public class FloatArrayBuffer extends ArrayBuffer<VecArray<float[]>>
|
||||
{
|
||||
public FloatArrayBuffer(VecArray<float[]> data)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bindData(VecArray<float[]> vertexData)
|
||||
{
|
||||
GL33.glBufferData(GL33.GL_ARRAY_BUFFER, vertexData.getData(), GL33.GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumArrayBufferType getType()
|
||||
{
|
||||
return EnumArrayBufferType.FLOAT;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.gl.vbo;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
|
||||
public class FloatArrayBuffer extends ArrayBuffer<VecArray<float[]>>
|
||||
{
|
||||
public FloatArrayBuffer(VecArray<float[]> data)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bindData(VecArray<float[]> vertexData)
|
||||
{
|
||||
GL33.glBufferData(GL33.GL_ARRAY_BUFFER, vertexData.getData(), GL33.GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumArrayBufferType getType()
|
||||
{
|
||||
return EnumArrayBufferType.FLOAT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
package cz.tefek.pluto.engine.graphics.gl.vbo;
|
||||
|
||||
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL15.glBufferData;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
|
||||
public class IndexArrayBuffer extends ArrayBuffer<VecArray<int[]>>
|
||||
{
|
||||
public IndexArrayBuffer(int[] data)
|
||||
{
|
||||
super(new VecArray<>(data, 1));
|
||||
}
|
||||
|
||||
public IndexArrayBuffer(VecArray<int[]> data)
|
||||
{
|
||||
super(data);
|
||||
|
||||
if (data.getVecDimensions() != 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Index buffers must have exactly one vertex dimension!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind()
|
||||
{
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.glID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bindData(VecArray<int[]> data)
|
||||
{
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.getData(), GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind()
|
||||
{
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumArrayBufferType getType()
|
||||
{
|
||||
return EnumArrayBufferType.UNSIGNED_INT;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.gl.vbo;
|
||||
|
||||
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
|
||||
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||
import static org.lwjgl.opengl.GL15.glBufferData;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.data.VecArray;
|
||||
|
||||
public class IndexArrayBuffer extends ArrayBuffer<VecArray<int[]>>
|
||||
{
|
||||
public IndexArrayBuffer(int[] data)
|
||||
{
|
||||
super(new VecArray<>(data, 1));
|
||||
}
|
||||
|
||||
public IndexArrayBuffer(VecArray<int[]> data)
|
||||
{
|
||||
super(data);
|
||||
|
||||
if (data.getVecDimensions() != 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Index buffers must have exactly one vertex dimension!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind()
|
||||
{
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.glID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bindData(VecArray<int[]> data)
|
||||
{
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.getData(), GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind()
|
||||
{
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumArrayBufferType getType()
|
||||
{
|
||||
return EnumArrayBufferType.UNSIGNED_INT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
package cz.tefek.pluto.engine.shader;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public abstract class ShaderBase implements IShaderProgram
|
||||
{
|
||||
private int programID;
|
||||
|
||||
protected int getUniform(String name)
|
||||
{
|
||||
return GL33.glGetUniformLocation(this.programID, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
Logger.logf(SmartSeverity.REMOVED, "Disposing of shader ID %d of type %s...\n", this.getID(), this.getClass().getCanonicalName());
|
||||
|
||||
this.stop();
|
||||
GL33.glDeleteProgram(this.programID);
|
||||
}
|
||||
|
||||
protected void bindAttribute(int attribute, String name)
|
||||
{
|
||||
GL33.glBindAttribLocation(this.programID, attribute, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getID()
|
||||
{
|
||||
return this.programID;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.shader;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public abstract class ShaderBase implements IShaderProgram
|
||||
{
|
||||
private int programID;
|
||||
|
||||
protected int getUniform(String name)
|
||||
{
|
||||
return GL33.glGetUniformLocation(this.programID, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
Logger.logf(SmartSeverity.REMOVED, "Disposing of shader ID %d of type %s...\n", this.getID(), this.getClass().getCanonicalName());
|
||||
|
||||
this.stop();
|
||||
GL33.glDeleteProgram(this.programID);
|
||||
}
|
||||
|
||||
protected void bindAttribute(int attribute, String name)
|
||||
{
|
||||
GL33.glBindAttribLocation(this.programID, attribute, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getID()
|
||||
{
|
||||
return this.programID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,242 +1,242 @@
|
|||
package cz.tefek.pluto.engine.graphics;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.joml.Matrix3x2f;
|
||||
import org.joml.Matrix3x2fc;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.QuadPresets;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.VertexArray;
|
||||
import cz.tefek.pluto.engine.graphics.texture.texture2d.Texture2D;
|
||||
|
||||
/**
|
||||
* A builder-like renderer for 2D rectangles. Note that the internal state is
|
||||
* not monitored for performance reasons and outside events may affect active
|
||||
* instances, such as changing the active shader or texture. In order to restore
|
||||
* the internal state to the default.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class Renderer2D
|
||||
{
|
||||
public static VertexArray standardQuad;
|
||||
public static VertexArray centeredQuad;
|
||||
|
||||
private static IShader2D defaultShader;
|
||||
|
||||
public static final Renderer2D INSTANCE = new Renderer2D();
|
||||
|
||||
protected Matrix3x2f transformation = new Matrix3x2f();
|
||||
|
||||
protected IShader2D customShader;
|
||||
|
||||
protected VertexArray activeVA;
|
||||
|
||||
protected Texture2D activeTexture;
|
||||
|
||||
protected boolean modifiedTransformation = false;
|
||||
|
||||
private static Stack<IShader2D> customShaderStack = new Stack<>();
|
||||
|
||||
public static void load(IShader2D defaultShaderIn)
|
||||
{
|
||||
standardQuad = QuadPresets.basicNoNeg();
|
||||
centeredQuad = QuadPresets.halvedSize();
|
||||
defaultShader = defaultShaderIn;
|
||||
}
|
||||
|
||||
public static void unload()
|
||||
{
|
||||
if (standardQuad != null)
|
||||
{
|
||||
standardQuad.delete();
|
||||
}
|
||||
|
||||
if (centeredQuad != null)
|
||||
{
|
||||
centeredQuad.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a custom {@link IShader2D shader} to be used in place of the
|
||||
* default one.
|
||||
*/
|
||||
public static void pushCustomShader(IShader2D shader)
|
||||
{
|
||||
customShaderStack.push(shader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the top {@link IShader2D shader} from the custom shader stack.
|
||||
*/
|
||||
public static IShader2D popCustomShader()
|
||||
{
|
||||
return customShaderStack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the renderer is currently supplied with a custom
|
||||
* {@link IShader2D shader}.
|
||||
*/
|
||||
public static boolean hasCustomShader()
|
||||
{
|
||||
return !customShaderStack.empty();
|
||||
}
|
||||
|
||||
public static Renderer2D draw(VertexArray va, IShader2D shader)
|
||||
{
|
||||
|
||||
GL33.glEnable(GL33.GL_BLEND);
|
||||
|
||||
INSTANCE.customShader = shader;
|
||||
|
||||
INSTANCE.customShader.start();
|
||||
|
||||
INSTANCE.customShader.loadRecolor(1, 1, 1, 1);
|
||||
|
||||
INSTANCE.identity();
|
||||
|
||||
INSTANCE.switchVertexArray(va);
|
||||
|
||||
INSTANCE.activeTexture = null;
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static Renderer2D draw(VertexArray va)
|
||||
{
|
||||
return draw(va, hasCustomShader() ? customShaderStack.peek() : defaultShader);
|
||||
}
|
||||
|
||||
public static Renderer2D draw()
|
||||
{
|
||||
return draw(standardQuad);
|
||||
}
|
||||
|
||||
public Renderer2D identity()
|
||||
{
|
||||
this.transformation.m00 = 1;
|
||||
this.transformation.m01 = 0;
|
||||
this.transformation.m10 = 0;
|
||||
this.transformation.m11 = 1;
|
||||
this.transformation.m20 = 0;
|
||||
this.transformation.m21 = 0;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D switchVertexArray(VertexArray va)
|
||||
{
|
||||
va.bind();
|
||||
va.enableAllAttributes();
|
||||
|
||||
this.activeVA = va;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D rotate(float rotation)
|
||||
{
|
||||
this.transformation.rotate(rotation);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D at(float x, float y, float width, float height)
|
||||
{
|
||||
this.identity();
|
||||
this.transformation.translate(x, y);
|
||||
this.transformation.scale(width, height);
|
||||
|
||||
this.modifiedTransformation = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D translate(float x, float y)
|
||||
{
|
||||
this.transformation.translate(x, y);
|
||||
|
||||
this.modifiedTransformation = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D scale(float width, float height)
|
||||
{
|
||||
this.transformation.scale(width, height);
|
||||
|
||||
this.modifiedTransformation = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D transformation(Matrix3x2fc transformationMatrix)
|
||||
{
|
||||
this.transformation.set(transformationMatrix);
|
||||
|
||||
this.modifiedTransformation = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private Renderer2D writeTransformation()
|
||||
{
|
||||
this.customShader.loadTransformationMatrix(this.transformation);
|
||||
|
||||
this.modifiedTransformation = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D texturef(Texture2D texture, float u, float v, float width, float height)
|
||||
{
|
||||
if (this.activeTexture != texture)
|
||||
{
|
||||
this.activeTexture = texture;
|
||||
texture.bind();
|
||||
}
|
||||
|
||||
this.customShader.loadUV(u, 1 - v - height, width, height);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D texture(Texture2D texture, int u, int v, int width, int height)
|
||||
{
|
||||
return this.texturef(texture, u / (float) texture.getWidth(), v / (float) texture.getHeight(), width / (float) texture.getWidth(), height / (float) texture.getHeight());
|
||||
}
|
||||
|
||||
public Renderer2D texture(Texture2D texture)
|
||||
{
|
||||
return this.texturef(texture, 0.0f, 0.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
public Renderer2D recolor(float r, float g, float b, float a)
|
||||
{
|
||||
this.customShader.loadRecolor(r, g, b, a);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D recolor(Vector4f recolor)
|
||||
{
|
||||
this.customShader.loadRecolor(recolor);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void flush()
|
||||
{
|
||||
if (this.modifiedTransformation)
|
||||
{
|
||||
this.writeTransformation();
|
||||
this.modifiedTransformation = false;
|
||||
}
|
||||
|
||||
this.activeVA.draw(DrawMode.TRIANGLES);
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.joml.Matrix3x2f;
|
||||
import org.joml.Matrix3x2fc;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.DrawMode;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.QuadPresets;
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.VertexArray;
|
||||
import cz.tefek.pluto.engine.graphics.texture.texture2d.Texture2D;
|
||||
|
||||
/**
|
||||
* A builder-like renderer for 2D rectangles. Note that the internal state is
|
||||
* not monitored for performance reasons and outside events may affect active
|
||||
* instances, such as changing the active shader or texture. In order to restore
|
||||
* the internal state to the default.
|
||||
*
|
||||
* @author 493msi
|
||||
*/
|
||||
public class Renderer2D
|
||||
{
|
||||
public static VertexArray standardQuad;
|
||||
public static VertexArray centeredQuad;
|
||||
|
||||
private static IShader2D defaultShader;
|
||||
|
||||
public static final Renderer2D INSTANCE = new Renderer2D();
|
||||
|
||||
protected Matrix3x2f transformation = new Matrix3x2f();
|
||||
|
||||
protected IShader2D customShader;
|
||||
|
||||
protected VertexArray activeVA;
|
||||
|
||||
protected Texture2D activeTexture;
|
||||
|
||||
protected boolean modifiedTransformation = false;
|
||||
|
||||
private static Stack<IShader2D> customShaderStack = new Stack<>();
|
||||
|
||||
public static void load(IShader2D defaultShaderIn)
|
||||
{
|
||||
standardQuad = QuadPresets.basicNoNeg();
|
||||
centeredQuad = QuadPresets.halvedSize();
|
||||
defaultShader = defaultShaderIn;
|
||||
}
|
||||
|
||||
public static void unload()
|
||||
{
|
||||
if (standardQuad != null)
|
||||
{
|
||||
standardQuad.delete();
|
||||
}
|
||||
|
||||
if (centeredQuad != null)
|
||||
{
|
||||
centeredQuad.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a custom {@link IShader2D shader} to be used in place of the
|
||||
* default one.
|
||||
*/
|
||||
public static void pushCustomShader(IShader2D shader)
|
||||
{
|
||||
customShaderStack.push(shader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the top {@link IShader2D shader} from the custom shader stack.
|
||||
*/
|
||||
public static IShader2D popCustomShader()
|
||||
{
|
||||
return customShaderStack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the renderer is currently supplied with a custom
|
||||
* {@link IShader2D shader}.
|
||||
*/
|
||||
public static boolean hasCustomShader()
|
||||
{
|
||||
return !customShaderStack.empty();
|
||||
}
|
||||
|
||||
public static Renderer2D draw(VertexArray va, IShader2D shader)
|
||||
{
|
||||
|
||||
GL33.glEnable(GL33.GL_BLEND);
|
||||
|
||||
INSTANCE.customShader = shader;
|
||||
|
||||
INSTANCE.customShader.start();
|
||||
|
||||
INSTANCE.customShader.loadRecolor(1, 1, 1, 1);
|
||||
|
||||
INSTANCE.identity();
|
||||
|
||||
INSTANCE.switchVertexArray(va);
|
||||
|
||||
INSTANCE.activeTexture = null;
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static Renderer2D draw(VertexArray va)
|
||||
{
|
||||
return draw(va, hasCustomShader() ? customShaderStack.peek() : defaultShader);
|
||||
}
|
||||
|
||||
public static Renderer2D draw()
|
||||
{
|
||||
return draw(standardQuad);
|
||||
}
|
||||
|
||||
public Renderer2D identity()
|
||||
{
|
||||
this.transformation.m00 = 1;
|
||||
this.transformation.m01 = 0;
|
||||
this.transformation.m10 = 0;
|
||||
this.transformation.m11 = 1;
|
||||
this.transformation.m20 = 0;
|
||||
this.transformation.m21 = 0;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D switchVertexArray(VertexArray va)
|
||||
{
|
||||
va.bind();
|
||||
va.enableAllAttributes();
|
||||
|
||||
this.activeVA = va;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D rotate(float rotation)
|
||||
{
|
||||
this.transformation.rotate(rotation);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D at(float x, float y, float width, float height)
|
||||
{
|
||||
this.identity();
|
||||
this.transformation.translate(x, y);
|
||||
this.transformation.scale(width, height);
|
||||
|
||||
this.modifiedTransformation = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D translate(float x, float y)
|
||||
{
|
||||
this.transformation.translate(x, y);
|
||||
|
||||
this.modifiedTransformation = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D scale(float width, float height)
|
||||
{
|
||||
this.transformation.scale(width, height);
|
||||
|
||||
this.modifiedTransformation = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D transformation(Matrix3x2fc transformationMatrix)
|
||||
{
|
||||
this.transformation.set(transformationMatrix);
|
||||
|
||||
this.modifiedTransformation = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private Renderer2D writeTransformation()
|
||||
{
|
||||
this.customShader.loadTransformationMatrix(this.transformation);
|
||||
|
||||
this.modifiedTransformation = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D texturef(Texture2D texture, float u, float v, float width, float height)
|
||||
{
|
||||
if (this.activeTexture != texture)
|
||||
{
|
||||
this.activeTexture = texture;
|
||||
texture.bind();
|
||||
}
|
||||
|
||||
this.customShader.loadUV(u, 1 - v - height, width, height);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D texture(Texture2D texture, int u, int v, int width, int height)
|
||||
{
|
||||
return this.texturef(texture, u / (float) texture.getWidth(), v / (float) texture.getHeight(), width / (float) texture.getWidth(), height / (float) texture.getHeight());
|
||||
}
|
||||
|
||||
public Renderer2D texture(Texture2D texture)
|
||||
{
|
||||
return this.texturef(texture, 0.0f, 0.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
public Renderer2D recolor(float r, float g, float b, float a)
|
||||
{
|
||||
this.customShader.loadRecolor(r, g, b, a);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Renderer2D recolor(Vector4f recolor)
|
||||
{
|
||||
this.customShader.loadRecolor(recolor);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void flush()
|
||||
{
|
||||
if (this.modifiedTransformation)
|
||||
{
|
||||
this.writeTransformation();
|
||||
this.modifiedTransformation = false;
|
||||
}
|
||||
|
||||
this.activeVA.draw(DrawMode.TRIANGLES);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +1,70 @@
|
|||
package cz.tefek.pluto.engine.graphics;
|
||||
|
||||
import org.joml.Matrix3x2fc;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
|
||||
import cz.tefek.pluto.engine.shader.ShaderBase;
|
||||
import cz.tefek.pluto.engine.shader.ShaderProgram;
|
||||
import cz.tefek.pluto.engine.shader.VertexArrayAttribute;
|
||||
import cz.tefek.pluto.engine.shader.uniform.Uniform;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat3x2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
@ShaderProgram
|
||||
public final class Shader2D extends ShaderBase implements IShader2D
|
||||
{
|
||||
@AutoViewportProjection
|
||||
@Uniform(name = "projection")
|
||||
public UniformMat4 projectionMatrix;
|
||||
|
||||
@Uniform(name = "transformation")
|
||||
public UniformMat3x2 transformationMatrix;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvBase;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvDelta;
|
||||
|
||||
@Uniform
|
||||
public UniformVec4 recolor;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.POSITION)
|
||||
public int position;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.UV)
|
||||
public int uvCoords;
|
||||
|
||||
@Override
|
||||
public void loadUV(float uvStartX, float uvStartY, float uWidth, float vHeight)
|
||||
{
|
||||
this.uvBase.load(uvStartX, uvStartY);
|
||||
this.uvDelta.load(uWidth, vHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRecolor(float r, float g, float b, float a)
|
||||
{
|
||||
this.recolor.load(r, g, b, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadProjectionMatrix(Matrix4fc matrix)
|
||||
{
|
||||
this.projectionMatrix.load(matrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadTransformationMatrix(Matrix3x2fc matrix)
|
||||
{
|
||||
this.transformationMatrix.load(matrix);
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics;
|
||||
|
||||
import org.joml.Matrix3x2fc;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
|
||||
import cz.tefek.pluto.engine.shader.ShaderBase;
|
||||
import cz.tefek.pluto.engine.shader.ShaderProgram;
|
||||
import cz.tefek.pluto.engine.shader.VertexArrayAttribute;
|
||||
import cz.tefek.pluto.engine.shader.uniform.Uniform;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat3x2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
@ShaderProgram
|
||||
public final class Shader2D extends ShaderBase implements IShader2D
|
||||
{
|
||||
@AutoViewportProjection
|
||||
@Uniform(name = "projection")
|
||||
public UniformMat4 projectionMatrix;
|
||||
|
||||
@Uniform(name = "transformation")
|
||||
public UniformMat3x2 transformationMatrix;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvBase;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvDelta;
|
||||
|
||||
@Uniform
|
||||
public UniformVec4 recolor;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.POSITION)
|
||||
public int position;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.UV)
|
||||
public int uvCoords;
|
||||
|
||||
@Override
|
||||
public void loadUV(float uvStartX, float uvStartY, float uWidth, float vHeight)
|
||||
{
|
||||
this.uvBase.load(uvStartX, uvStartY);
|
||||
this.uvDelta.load(uWidth, vHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRecolor(float r, float g, float b, float a)
|
||||
{
|
||||
this.recolor.load(r, g, b, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadProjectionMatrix(Matrix4fc matrix)
|
||||
{
|
||||
this.projectionMatrix.load(matrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadTransformationMatrix(Matrix3x2fc matrix)
|
||||
{
|
||||
this.transformationMatrix.load(matrix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +1,70 @@
|
|||
package cz.tefek.pluto.engine.graphics;
|
||||
|
||||
import org.joml.Matrix3x2fc;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
|
||||
import cz.tefek.pluto.engine.shader.ShaderBase;
|
||||
import cz.tefek.pluto.engine.shader.ShaderProgram;
|
||||
import cz.tefek.pluto.engine.shader.VertexArrayAttribute;
|
||||
import cz.tefek.pluto.engine.shader.uniform.Uniform;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat3x2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
@ShaderProgram
|
||||
public final class ShaderRectangle2D extends ShaderBase implements IRectangleShader2D
|
||||
{
|
||||
@AutoViewportProjection
|
||||
@Uniform(name = "projection")
|
||||
public UniformMat4 projectionMatrix;
|
||||
|
||||
@Uniform(name = "transformation")
|
||||
public UniformMat3x2 transformationMatrix;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvBase;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvDelta;
|
||||
|
||||
@Uniform
|
||||
public UniformVec4 recolor;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.POSITION)
|
||||
public int position;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.UV)
|
||||
public int uvCoords;
|
||||
|
||||
@Override
|
||||
public void loadUV(float uvStartX, float uvStartY, float uWidth, float vHeight)
|
||||
{
|
||||
this.uvBase.load(uvStartX, uvStartY);
|
||||
this.uvDelta.load(uWidth, vHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRecolor(float r, float g, float b, float a)
|
||||
{
|
||||
this.recolor.load(r, g, b, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadProjectionMatrix(Matrix4fc matrix)
|
||||
{
|
||||
this.projectionMatrix.load(matrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadTransformationMatrix(Matrix3x2fc matrix)
|
||||
{
|
||||
this.transformationMatrix.load(matrix);
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics;
|
||||
|
||||
import org.joml.Matrix3x2fc;
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.gl.vao.attrib.ReservedAttributes;
|
||||
import cz.tefek.pluto.engine.shader.ShaderBase;
|
||||
import cz.tefek.pluto.engine.shader.ShaderProgram;
|
||||
import cz.tefek.pluto.engine.shader.VertexArrayAttribute;
|
||||
import cz.tefek.pluto.engine.shader.uniform.Uniform;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat3x2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformMat4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec2;
|
||||
import cz.tefek.pluto.engine.shader.uniform.UniformVec4;
|
||||
import cz.tefek.pluto.engine.shader.uniform.auto.AutoViewportProjection;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
@ShaderProgram
|
||||
public final class ShaderRectangle2D extends ShaderBase implements IRectangleShader2D
|
||||
{
|
||||
@AutoViewportProjection
|
||||
@Uniform(name = "projection")
|
||||
public UniformMat4 projectionMatrix;
|
||||
|
||||
@Uniform(name = "transformation")
|
||||
public UniformMat3x2 transformationMatrix;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvBase;
|
||||
|
||||
@Uniform
|
||||
public UniformVec2 uvDelta;
|
||||
|
||||
@Uniform
|
||||
public UniformVec4 recolor;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.POSITION)
|
||||
public int position;
|
||||
|
||||
@VertexArrayAttribute(ReservedAttributes.UV)
|
||||
public int uvCoords;
|
||||
|
||||
@Override
|
||||
public void loadUV(float uvStartX, float uvStartY, float uWidth, float vHeight)
|
||||
{
|
||||
this.uvBase.load(uvStartX, uvStartY);
|
||||
this.uvDelta.load(uWidth, vHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadRecolor(float r, float g, float b, float a)
|
||||
{
|
||||
this.recolor.load(r, g, b, a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadProjectionMatrix(Matrix4fc matrix)
|
||||
{
|
||||
this.projectionMatrix.load(matrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadTransformationMatrix(Matrix3x2fc matrix)
|
||||
{
|
||||
this.transformationMatrix.load(matrix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package cz.tefek.pluto.engine.graphics.sprite;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.texture.MagFilter;
|
||||
import cz.tefek.pluto.engine.graphics.texture.MinFilter;
|
||||
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
|
||||
|
@ -11,7 +13,7 @@ public class DisposablePlaceholderSprite extends DisposableTextureSprite
|
|||
{
|
||||
super(new RectangleTexture());
|
||||
|
||||
this.spriteTexture.load((String) null, MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
|
||||
this.spriteTexture.load((BufferedImage) null, MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
|
||||
this.width = this.spriteTexture.getWidth();
|
||||
this.height = this.spriteTexture.getHeight();
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import cz.tefek.pluto.engine.graphics.sprite.Sprite;
|
|||
import cz.tefek.pluto.engine.graphics.sprite.SpriteDisposable;
|
||||
import cz.tefek.pluto.engine.graphics.sprite.TileSprite;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.Severity;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public abstract class TiledSpriteSheet<T> extends SpriteSheet<T>
|
||||
{
|
||||
|
@ -65,7 +65,7 @@ public abstract class TiledSpriteSheet<T> extends SpriteSheet<T>
|
|||
|
||||
protected void expand()
|
||||
{
|
||||
Logger.logf(Severity.INFO, "Spritesheet #%d: Expanding from %dx%d to ", this.id, this.spriteSheetWidth, this.spriteSheetHeight);
|
||||
Logger.logf(SmartSeverity.INFO, "Spritesheet #%d: Expanding from %dx%d to ", this.id, this.spriteSheetWidth, this.spriteSheetHeight);
|
||||
|
||||
this.spriteSheetWidth *= 2;
|
||||
this.spriteSheetHeight *= 2;
|
||||
|
@ -79,7 +79,7 @@ public abstract class TiledSpriteSheet<T> extends SpriteSheet<T>
|
|||
|
||||
protected void upscale(int factor)
|
||||
{
|
||||
Logger.logf(Severity.INFO, "Spritesheet #%d: Upscaling from %dx%d to ", this.id, this.tileWidth, this.tileHeight);
|
||||
Logger.logf(SmartSeverity.INFO, "Spritesheet #%d: Upscaling from %dx%d to ", this.id, this.tileWidth, this.tileHeight);
|
||||
|
||||
this.tileWidth *= factor;
|
||||
this.tileHeight *= factor;
|
||||
|
|
|
@ -1,197 +1,197 @@
|
|||
package cz.tefek.pluto.engine.buffer;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
|
||||
/**
|
||||
* A utility class to handle primitive native buffers.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*
|
||||
*/
|
||||
public final class BufferHelper
|
||||
{
|
||||
/**
|
||||
* Creates an {@link IntBuffer} from the input <code>int</code> array.
|
||||
*
|
||||
* @param data The input <code>int</code> array.
|
||||
* @return The created {@link IntBuffer}
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static IntBuffer flippedIntBuffer(int[] data)
|
||||
{
|
||||
IntBuffer buffer = BufferUtils.createIntBuffer(data.length);
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link FloatBuffer} from the input <code>float</code> array.
|
||||
*
|
||||
* @param data The input <code>float</code> array.
|
||||
* @return The created {@link FloatBuffer}
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static FloatBuffer flippedFloatBuffer(float[] data)
|
||||
{
|
||||
FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ByteBuffer} from the input <code>byte</code> array.
|
||||
*
|
||||
* @param data The input <code>byte</code> array.
|
||||
* @return The created {@link ByteBuffer}
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static ByteBuffer flippedByteBuffer(byte[] data)
|
||||
{
|
||||
ByteBuffer buffer = BufferUtils.createByteBuffer(data.length);
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reallocates a new, bigger {@link ByteBuffer} and copies the data from the
|
||||
* old one.
|
||||
*
|
||||
* @param buffer The input {@link ByteBuffer}.
|
||||
* @param newCapacity The new buffer's capacity, can't be smaller than the
|
||||
* current one.
|
||||
* @return The new, bigger {@link ByteBuffer}.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static ByteBuffer resizeBuffer(ByteBuffer buffer, int newCapacity)
|
||||
{
|
||||
if (buffer.capacity() > newCapacity)
|
||||
{
|
||||
throw new IllegalArgumentException("New capacity is smaller than the previous one.");
|
||||
}
|
||||
|
||||
ByteBuffer newBuffer = BufferUtils.createByteBuffer(newCapacity);
|
||||
buffer.flip();
|
||||
newBuffer.put(buffer);
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file denoted by the specified path and returns a
|
||||
* {@link ByteBuffer} containing the read bytes.
|
||||
*
|
||||
* @param path The file's path.
|
||||
* @return A {@link ByteBuffer} containing the file's contents.
|
||||
* @throws IOException Upon standard I/O errors.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static ByteBuffer readToFlippedByteBuffer(String path) throws IOException
|
||||
{
|
||||
try (var fc = FileChannel.open(Path.of(path)))
|
||||
{
|
||||
var size = fc.size();
|
||||
|
||||
if (size > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new IOException("File ' + pah + ' is too big to be read into a ByteBuffer!");
|
||||
}
|
||||
|
||||
ByteBuffer buf = BufferUtils.createByteBuffer((int) size);
|
||||
fc.read(buf);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file denoted by the specified {@link Path} and fills the input
|
||||
* {@link ByteBuffer} with the read bytes.
|
||||
*
|
||||
* <p>
|
||||
* <em>Make sure the buffer can hold the entire file.</em>
|
||||
* </p>
|
||||
*
|
||||
* @param path The file's path.
|
||||
* @param buf The input buffer to be filled with data.
|
||||
*
|
||||
* @return The input {@link ByteBuffer}.
|
||||
* @throws IOException Upon standard I/O errors.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.3
|
||||
*/
|
||||
public static ByteBuffer readToByteBuffer(Path path, ByteBuffer buf) throws IOException
|
||||
{
|
||||
try (var fc = FileChannel.open(path))
|
||||
{
|
||||
fc.read(buf);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ResourceAddress} version of
|
||||
* {@link BufferHelper#readToByteBuffer(Path path, ByteBuffer buf)}.
|
||||
*
|
||||
* @param addr The file's {@link ResourceAddress}.
|
||||
* @param buf The input buffer to be filled with data.
|
||||
*
|
||||
* @return The input {@link ByteBuffer}.
|
||||
* @throws IOException Upon standard I/O errors.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.3
|
||||
*/
|
||||
public static ByteBuffer readToByteBuffer(ResourceAddress addr, ByteBuffer buf) throws IOException
|
||||
{
|
||||
return readToByteBuffer(addr.toNIOPath(), buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file denoted by the specified {@link ResourceAddress} and returns
|
||||
* a {@link ByteBuffer} containing the read bytes.
|
||||
*
|
||||
* @param path The file's path.
|
||||
* @return A {@link ByteBuffer} containing the file's contents.
|
||||
* @throws IOException Upon standard I/O errors.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.3
|
||||
*/
|
||||
public static ByteBuffer readToFlippedByteBuffer(ResourceAddress path) throws IOException
|
||||
{
|
||||
try (var is = Files.newInputStream(path.toNIOPath()))
|
||||
{
|
||||
var ba = IOUtils.toByteArray(is);
|
||||
return flippedByteBuffer(ba);
|
||||
}
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.buffer;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
|
||||
/**
|
||||
* A utility class to handle primitive native buffers.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*
|
||||
*/
|
||||
public final class BufferHelper
|
||||
{
|
||||
/**
|
||||
* Creates an {@link IntBuffer} from the input <code>int</code> array.
|
||||
*
|
||||
* @param data The input <code>int</code> array.
|
||||
* @return The created {@link IntBuffer}
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static IntBuffer flippedIntBuffer(int[] data)
|
||||
{
|
||||
IntBuffer buffer = BufferUtils.createIntBuffer(data.length);
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link FloatBuffer} from the input <code>float</code> array.
|
||||
*
|
||||
* @param data The input <code>float</code> array.
|
||||
* @return The created {@link FloatBuffer}
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static FloatBuffer flippedFloatBuffer(float[] data)
|
||||
{
|
||||
FloatBuffer buffer = BufferUtils.createFloatBuffer(data.length);
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ByteBuffer} from the input <code>byte</code> array.
|
||||
*
|
||||
* @param data The input <code>byte</code> array.
|
||||
* @return The created {@link ByteBuffer}
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static ByteBuffer flippedByteBuffer(byte[] data)
|
||||
{
|
||||
ByteBuffer buffer = BufferUtils.createByteBuffer(data.length);
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reallocates a new, bigger {@link ByteBuffer} and copies the data from the
|
||||
* old one.
|
||||
*
|
||||
* @param buffer The input {@link ByteBuffer}.
|
||||
* @param newCapacity The new buffer's capacity, can't be smaller than the
|
||||
* current one.
|
||||
* @return The new, bigger {@link ByteBuffer}.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static ByteBuffer resizeBuffer(ByteBuffer buffer, int newCapacity)
|
||||
{
|
||||
if (buffer.capacity() > newCapacity)
|
||||
{
|
||||
throw new IllegalArgumentException("New capacity is smaller than the previous one.");
|
||||
}
|
||||
|
||||
ByteBuffer newBuffer = BufferUtils.createByteBuffer(newCapacity);
|
||||
buffer.flip();
|
||||
newBuffer.put(buffer);
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file denoted by the specified path and returns a
|
||||
* {@link ByteBuffer} containing the read bytes.
|
||||
*
|
||||
* @param path The file's path.
|
||||
* @return A {@link ByteBuffer} containing the file's contents.
|
||||
* @throws IOException Upon standard I/O errors.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static ByteBuffer readToFlippedByteBuffer(String path) throws IOException
|
||||
{
|
||||
try (var fc = FileChannel.open(Path.of(path)))
|
||||
{
|
||||
var size = fc.size();
|
||||
|
||||
if (size > Integer.MAX_VALUE)
|
||||
{
|
||||
throw new IOException("File ' + pah + ' is too big to be read into a ByteBuffer!");
|
||||
}
|
||||
|
||||
ByteBuffer buf = BufferUtils.createByteBuffer((int) size);
|
||||
fc.read(buf);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file denoted by the specified {@link Path} and fills the input
|
||||
* {@link ByteBuffer} with the read bytes.
|
||||
*
|
||||
* <p>
|
||||
* <em>Make sure the buffer can hold the entire file.</em>
|
||||
* </p>
|
||||
*
|
||||
* @param path The file's path.
|
||||
* @param buf The input buffer to be filled with data.
|
||||
*
|
||||
* @return The input {@link ByteBuffer}.
|
||||
* @throws IOException Upon standard I/O errors.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.3
|
||||
*/
|
||||
public static ByteBuffer readToByteBuffer(Path path, ByteBuffer buf) throws IOException
|
||||
{
|
||||
try (var fc = FileChannel.open(path))
|
||||
{
|
||||
fc.read(buf);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ResourceAddress} version of
|
||||
* {@link BufferHelper#readToByteBuffer(Path path, ByteBuffer buf)}.
|
||||
*
|
||||
* @param addr The file's {@link ResourceAddress}.
|
||||
* @param buf The input buffer to be filled with data.
|
||||
*
|
||||
* @return The input {@link ByteBuffer}.
|
||||
* @throws IOException Upon standard I/O errors.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.3
|
||||
*/
|
||||
public static ByteBuffer readToByteBuffer(ResourceAddress addr, ByteBuffer buf) throws IOException
|
||||
{
|
||||
return readToByteBuffer(addr.toNIOPath(), buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a file denoted by the specified {@link ResourceAddress} and returns
|
||||
* a {@link ByteBuffer} containing the read bytes.
|
||||
*
|
||||
* @param path The file's path.
|
||||
* @return A {@link ByteBuffer} containing the file's contents.
|
||||
* @throws IOException Upon standard I/O errors.
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.3
|
||||
*/
|
||||
public static ByteBuffer readToFlippedByteBuffer(ResourceAddress path) throws IOException
|
||||
{
|
||||
try (var is = Files.newInputStream(path.toNIOPath()))
|
||||
{
|
||||
var ba = IOUtils.toByteArray(is);
|
||||
return flippedByteBuffer(ba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package cz.tefek.pluto.engine.buffer;
|
|||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.glfw.GLFWImage;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import cz.tefek.pluto.tpl.TPL;
|
||||
|
||||
/**
|
||||
|
@ -28,23 +30,27 @@ public class GLFWImageUtil
|
|||
{
|
||||
var icon = GLFWImage.create(icons.length);
|
||||
|
||||
for (int iconIndex = 0; iconIndex < icons.length; iconIndex++)
|
||||
for (String iconPath : icons)
|
||||
{
|
||||
var img = TPL.loadPixels(icons[iconIndex]);
|
||||
var img = TPL.loadSpecial(Path.of(iconPath), false);
|
||||
var imgData = img.getData();
|
||||
var imgWidth = img.getWidth();
|
||||
var imgHeight = img.getHeight();
|
||||
int imgWidth = img.getWidth();
|
||||
int imgHeight = img.getHeight();
|
||||
|
||||
var byteBuf = BufferUtils.createByteBuffer(imgWidth * imgHeight * 4);
|
||||
int pixelCount = imgWidth * imgHeight;
|
||||
int bytesPerPixel = 4;
|
||||
var byteBuf = BufferUtils.createByteBuffer(pixelCount * bytesPerPixel);
|
||||
|
||||
for (int i = 0; i < imgHeight * imgWidth; i++)
|
||||
byte[] px = new byte[bytesPerPixel];
|
||||
|
||||
for (int i = 0; i < pixelCount; i++)
|
||||
{
|
||||
var data = imgData[i];
|
||||
px[3] = imgData.get(); // A
|
||||
px[2] = imgData.get(); // B
|
||||
px[1] = imgData.get(); // G
|
||||
px[0] = imgData.get(); // R
|
||||
|
||||
byteBuf.put((byte) ((data & 0x00ff0000) >> 16));
|
||||
byteBuf.put((byte) ((data & 0x0000ff00) >> 8));
|
||||
byteBuf.put((byte) (data & 0x000000ff));
|
||||
byteBuf.put((byte) ((data & 0xff000000) >> 24));
|
||||
byteBuf.put(px);
|
||||
}
|
||||
|
||||
byteBuf.flip();
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
package cz.tefek.pluto.engine.display;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
import org.lwjgl.glfw.GLFWImage;
|
||||
import org.lwjgl.glfw.GLFWVidMode;
|
||||
import org.lwjgl.glfw.GLFWWindowSizeCallback;
|
||||
import org.lwjgl.glfw.*;
|
||||
import org.lwjgl.opengl.ARBDebugOutput;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
@ -13,7 +9,6 @@ import org.lwjgl.system.MemoryUtil;
|
|||
|
||||
import cz.tefek.pluto.engine.gl.GLDebugInfo;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.Severity;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
/**
|
||||
|
@ -72,7 +67,7 @@ public class Display
|
|||
{
|
||||
if (Display.this.debugMode)
|
||||
{
|
||||
Logger.logf(Severity.INFO, "Resized to %dx%d.\n", width, height);
|
||||
Logger.logf(SmartSeverity.INFO, "Resized to %dx%d.\n", width, height);
|
||||
}
|
||||
|
||||
Display.this.width = width;
|
||||
|
|
|
@ -1,75 +1,75 @@
|
|||
package cz.tefek.pluto.engine.display;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class Framerate
|
||||
{
|
||||
private static long lastDraw = 0;
|
||||
|
||||
private static double frameTime = Double.NaN;
|
||||
|
||||
private static double FPS = Double.NaN;
|
||||
|
||||
private static int interpolatedFPS;
|
||||
|
||||
private static boolean firstRemoved = false;
|
||||
|
||||
private static Deque<Long> drawTimestamps = new LinkedBlockingDeque<>();
|
||||
|
||||
public static double getFrameTime()
|
||||
{
|
||||
return frameTime;
|
||||
}
|
||||
|
||||
public static double getFPS()
|
||||
{
|
||||
return FPS;
|
||||
}
|
||||
|
||||
public static int getInterpolatedFPS()
|
||||
{
|
||||
return interpolatedFPS;
|
||||
}
|
||||
|
||||
public static void tick()
|
||||
{
|
||||
var now = System.nanoTime();
|
||||
|
||||
if (lastDraw > 0)
|
||||
{
|
||||
var frameTimeNs = now - lastDraw;
|
||||
frameTime = frameTimeNs / (double) TimeUnit.MILLISECONDS.toNanos(1);
|
||||
FPS = TimeUnit.SECONDS.toMillis(1) / frameTime;
|
||||
}
|
||||
|
||||
var nowMs = System.currentTimeMillis();
|
||||
|
||||
drawTimestamps.add(nowMs);
|
||||
|
||||
Long oldestDraw;
|
||||
long oneSecondAgo = nowMs - 1000;
|
||||
|
||||
while ((oldestDraw = drawTimestamps.peek()) != null && oldestDraw < oneSecondAgo)
|
||||
{
|
||||
drawTimestamps.remove();
|
||||
firstRemoved = true;
|
||||
}
|
||||
|
||||
if (firstRemoved)
|
||||
{
|
||||
interpolatedFPS = drawTimestamps.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
interpolatedFPS = (int) Math.round(FPS);
|
||||
}
|
||||
|
||||
lastDraw = now;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.display;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class Framerate
|
||||
{
|
||||
private static long lastDraw = 0;
|
||||
|
||||
private static double frameTime = Double.NaN;
|
||||
|
||||
private static double FPS = Double.NaN;
|
||||
|
||||
private static int interpolatedFPS;
|
||||
|
||||
private static boolean firstRemoved = false;
|
||||
|
||||
private static Deque<Long> drawTimestamps = new LinkedBlockingDeque<>();
|
||||
|
||||
public static double getFrameTime()
|
||||
{
|
||||
return frameTime;
|
||||
}
|
||||
|
||||
public static double getFPS()
|
||||
{
|
||||
return FPS;
|
||||
}
|
||||
|
||||
public static int getInterpolatedFPS()
|
||||
{
|
||||
return interpolatedFPS;
|
||||
}
|
||||
|
||||
public static void tick()
|
||||
{
|
||||
var now = System.nanoTime();
|
||||
|
||||
if (lastDraw > 0)
|
||||
{
|
||||
var frameTimeNs = now - lastDraw;
|
||||
frameTime = frameTimeNs / (double) TimeUnit.MILLISECONDS.toNanos(1);
|
||||
FPS = TimeUnit.SECONDS.toMillis(1) / frameTime;
|
||||
}
|
||||
|
||||
var nowMs = System.currentTimeMillis();
|
||||
|
||||
drawTimestamps.add(nowMs);
|
||||
|
||||
Long oldestDraw;
|
||||
long oneSecondAgo = nowMs - 1000;
|
||||
|
||||
while ((oldestDraw = drawTimestamps.peek()) != null && oldestDraw < oneSecondAgo)
|
||||
{
|
||||
drawTimestamps.remove();
|
||||
firstRemoved = true;
|
||||
}
|
||||
|
||||
if (firstRemoved)
|
||||
{
|
||||
interpolatedFPS = drawTimestamps.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
interpolatedFPS = (int) Math.round(FPS);
|
||||
}
|
||||
|
||||
lastDraw = now;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,66 @@
|
|||
package cz.tefek.pluto.engine.math;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
public class ProjectionMatrix
|
||||
{
|
||||
/**
|
||||
* Create a 2D orthogonal projection Matrix4f based on the width and height.
|
||||
*
|
||||
* @param width The ortho width
|
||||
* @param height The ortho height
|
||||
*
|
||||
* @return the matrix
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static Matrix4f createOrtho2D(int width, int height)
|
||||
{
|
||||
var orthoMatrix = new Matrix4f();
|
||||
orthoMatrix.setOrtho2D(0, width, height, 0);
|
||||
|
||||
return orthoMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a centered 2D orthogonal projection Matrix3x2f based on the width and
|
||||
* height.
|
||||
*
|
||||
* @param width The ortho width
|
||||
* @param height The ortho height
|
||||
*
|
||||
* @return the matrix
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.3
|
||||
*/
|
||||
public static Matrix4f createOrtho2DCentered(int width, int height)
|
||||
{
|
||||
var orthoMatrix = new Matrix4f();
|
||||
orthoMatrix.setOrtho2D(width / 2.0f, width / 2.0f, height / 2.0f, height / 2.0f);
|
||||
|
||||
return orthoMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a perspective frustum based on the parameters.
|
||||
*
|
||||
* @param aspectRatio The aspect ratio of the frustum
|
||||
* @param fov The fov of the frustum
|
||||
* @param zNear The distance of the zNear clipping plane
|
||||
* @param zFar The distance of the zFar clipping plane
|
||||
*
|
||||
* @return the perspective matrix
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static Matrix4f createPerspective(float aspectRatio, float fov, float zNear, float zFar)
|
||||
{
|
||||
var perspective = new Matrix4f();
|
||||
perspective.setPerspective(fov, aspectRatio, zNear, zFar);
|
||||
|
||||
return perspective;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.math;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
public class ProjectionMatrix
|
||||
{
|
||||
/**
|
||||
* Create a 2D orthogonal projection Matrix4f based on the width and height.
|
||||
*
|
||||
* @param width The ortho width
|
||||
* @param height The ortho height
|
||||
*
|
||||
* @return the matrix
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static Matrix4f createOrtho2D(int width, int height)
|
||||
{
|
||||
var orthoMatrix = new Matrix4f();
|
||||
orthoMatrix.setOrtho2D(0, width, height, 0);
|
||||
|
||||
return orthoMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a centered 2D orthogonal projection Matrix3x2f based on the width and
|
||||
* height.
|
||||
*
|
||||
* @param width The ortho width
|
||||
* @param height The ortho height
|
||||
*
|
||||
* @return the matrix
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.3
|
||||
*/
|
||||
public static Matrix4f createOrtho2DCentered(int width, int height)
|
||||
{
|
||||
var orthoMatrix = new Matrix4f();
|
||||
orthoMatrix.setOrtho2D(width / 2.0f, width / 2.0f, height / 2.0f, height / 2.0f);
|
||||
|
||||
return orthoMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a perspective frustum based on the parameters.
|
||||
*
|
||||
* @param aspectRatio The aspect ratio of the frustum
|
||||
* @param fov The fov of the frustum
|
||||
* @param zNear The distance of the zNear clipping plane
|
||||
* @param zFar The distance of the zFar clipping plane
|
||||
*
|
||||
* @return the perspective matrix
|
||||
*
|
||||
* @author 493msi
|
||||
* @since 0.1
|
||||
*/
|
||||
public static Matrix4f createPerspective(float aspectRatio, float fov, float zNear, float zFar)
|
||||
{
|
||||
var perspective = new Matrix4f();
|
||||
perspective.setPerspective(fov, aspectRatio, zNear, zFar);
|
||||
|
||||
return perspective;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
package cz.tefek.pluto.engine.graphics.texture;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.gl.IOpenGLEnum;
|
||||
|
||||
public enum MagFilter implements IOpenGLEnum
|
||||
{
|
||||
NEAREST(GL33.GL_NEAREST),
|
||||
LINEAR(GL33.GL_LINEAR);
|
||||
|
||||
MagFilter(int id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
private int id;
|
||||
|
||||
@Override
|
||||
public int getGLID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.texture;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.gl.IOpenGLEnum;
|
||||
|
||||
public enum MagFilter implements IOpenGLEnum
|
||||
{
|
||||
NEAREST(GL33.GL_NEAREST),
|
||||
LINEAR(GL33.GL_LINEAR);
|
||||
|
||||
MagFilter(int id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
private int id;
|
||||
|
||||
@Override
|
||||
public int getGLID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
package cz.tefek.pluto.engine.graphics.texture;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.gl.IOpenGLEnum;
|
||||
|
||||
public enum MinFilter implements IOpenGLEnum
|
||||
{
|
||||
NEAREST(GL33.GL_NEAREST, false),
|
||||
LINEAR(GL33.GL_LINEAR, false),
|
||||
NEAREST_MIPMAP_NEAREST(GL33.GL_NEAREST_MIPMAP_NEAREST, true),
|
||||
LINEAR_MIPMAP_NEAREST(GL33.GL_LINEAR_MIPMAP_NEAREST, true),
|
||||
NEAREST_MIPMAP_LINEAR(GL33.GL_NEAREST_MIPMAP_LINEAR, true),
|
||||
LINEAR_MIPMAP_LINEAR(GL33.GL_LINEAR_MIPMAP_LINEAR, true);
|
||||
|
||||
MinFilter(int id, boolean isMipMapped)
|
||||
{
|
||||
this.id = id;
|
||||
this.mipMapped = isMipMapped;
|
||||
}
|
||||
|
||||
private int id;
|
||||
private boolean mipMapped;
|
||||
|
||||
@Override
|
||||
public int getGLID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public boolean isMipMapped()
|
||||
{
|
||||
return this.mipMapped;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.texture;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.gl.IOpenGLEnum;
|
||||
|
||||
public enum MinFilter implements IOpenGLEnum
|
||||
{
|
||||
NEAREST(GL33.GL_NEAREST, false),
|
||||
LINEAR(GL33.GL_LINEAR, false),
|
||||
NEAREST_MIPMAP_NEAREST(GL33.GL_NEAREST_MIPMAP_NEAREST, true),
|
||||
LINEAR_MIPMAP_NEAREST(GL33.GL_LINEAR_MIPMAP_NEAREST, true),
|
||||
NEAREST_MIPMAP_LINEAR(GL33.GL_NEAREST_MIPMAP_LINEAR, true),
|
||||
LINEAR_MIPMAP_LINEAR(GL33.GL_LINEAR_MIPMAP_LINEAR, true);
|
||||
|
||||
MinFilter(int id, boolean isMipMapped)
|
||||
{
|
||||
this.id = id;
|
||||
this.mipMapped = isMipMapped;
|
||||
}
|
||||
|
||||
private int id;
|
||||
private boolean mipMapped;
|
||||
|
||||
@Override
|
||||
public int getGLID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public boolean isMipMapped()
|
||||
{
|
||||
return this.mipMapped;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
package cz.tefek.pluto.engine.graphics.texture;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import cz.tefek.pluto.io.asl.resource.ResourceAddress;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.Severity;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
import cz.tefek.pluto.tpl.TPL;
|
||||
import cz.tefek.pluto.tpl.TPNImage;
|
||||
|
||||
public abstract class Texture
|
||||
{
|
||||
protected int glID = 0;
|
||||
protected int glID;
|
||||
protected final int type;
|
||||
protected final int dimensions;
|
||||
|
||||
|
@ -138,7 +136,7 @@ public abstract class Texture
|
|||
{
|
||||
if (wrapOptions.length != this.dimensions)
|
||||
{
|
||||
Logger.log(Severity.ERROR, "Error: WrapMode option count does not match texture's dimensions.");
|
||||
Logger.log(SmartSeverity.ERROR, "Error: WrapMode option count does not match texture's dimensions.");
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -198,6 +196,7 @@ public abstract class Texture
|
|||
this.load(file.toPath(), magFilter, minFilter, wrap);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void load(String file, MagFilter magFilter, MinFilter minFilter, WrapMode... wrap)
|
||||
{
|
||||
TPNImage image = TPL.load(file);
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
package cz.tefek.pluto.engine.graphics.texture;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.opengl.GL44;
|
||||
|
||||
import cz.tefek.pluto.engine.gl.IOpenGLEnum;
|
||||
|
||||
public enum WrapMode implements IOpenGLEnum
|
||||
{
|
||||
REPEAT(GL33.GL_REPEAT),
|
||||
CLAMP_TO_EDGE(GL33.GL_CLAMP_TO_EDGE),
|
||||
CLAMP_TO_BORDER(GL33.GL_CLAMP_TO_BORDER),
|
||||
MIRROR_CLAMP_TO_EDGE(GL44.GL_MIRROR_CLAMP_TO_EDGE),
|
||||
MIRRORED_REPEAT(GL33.GL_MIRRORED_REPEAT);
|
||||
|
||||
WrapMode(int id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static final EnumSet<WrapMode> repeatModes = EnumSet.of(WrapMode.MIRRORED_REPEAT, WrapMode.REPEAT);
|
||||
public static final EnumSet<WrapMode> clampModes = EnumSet.of(WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_BORDER, MIRROR_CLAMP_TO_EDGE);
|
||||
public static final EnumSet<WrapMode> mirrorModes = EnumSet.of(WrapMode.MIRROR_CLAMP_TO_EDGE, WrapMode.MIRRORED_REPEAT);
|
||||
|
||||
private int id;
|
||||
|
||||
@Override
|
||||
public int getGLID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.texture;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.opengl.GL44;
|
||||
|
||||
import cz.tefek.pluto.engine.gl.IOpenGLEnum;
|
||||
|
||||
public enum WrapMode implements IOpenGLEnum
|
||||
{
|
||||
REPEAT(GL33.GL_REPEAT),
|
||||
CLAMP_TO_EDGE(GL33.GL_CLAMP_TO_EDGE),
|
||||
CLAMP_TO_BORDER(GL33.GL_CLAMP_TO_BORDER),
|
||||
MIRROR_CLAMP_TO_EDGE(GL44.GL_MIRROR_CLAMP_TO_EDGE),
|
||||
MIRRORED_REPEAT(GL33.GL_MIRRORED_REPEAT);
|
||||
|
||||
WrapMode(int id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static final EnumSet<WrapMode> repeatModes = EnumSet.of(WrapMode.MIRRORED_REPEAT, WrapMode.REPEAT);
|
||||
public static final EnumSet<WrapMode> clampModes = EnumSet.of(WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_BORDER, MIRROR_CLAMP_TO_EDGE);
|
||||
public static final EnumSet<WrapMode> mirrorModes = EnumSet.of(WrapMode.MIRROR_CLAMP_TO_EDGE, WrapMode.MIRRORED_REPEAT);
|
||||
|
||||
private int id;
|
||||
|
||||
@Override
|
||||
public int getGLID()
|
||||
{
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package cz.tefek.pluto.engine.graphics.texture.texture2d;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.texture.Texture;
|
||||
import cz.tefek.pluto.engine.graphics.texture.WrapMode;
|
||||
import cz.tefek.pluto.io.logger.Logger;
|
||||
import cz.tefek.pluto.io.logger.Severity;
|
||||
import cz.tefek.pluto.io.logger.SmartSeverity;
|
||||
|
||||
public class RectangleTexture extends Texture
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ public class RectangleTexture extends Texture
|
|||
{
|
||||
if (Arrays.stream(wrapOptions).anyMatch(WrapMode.repeatModes::contains))
|
||||
{
|
||||
Logger.log(Severity.ERROR, "Error: Rectangle textures do not support repeat wrap modes!");
|
||||
Logger.log(SmartSeverity.ERROR, "Error: Rectangle textures do not support repeat wrap modes!");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
package cz.tefek.pluto.engine.graphics.texture.texture2d;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.texture.Texture;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class Texture2D extends Texture
|
||||
{
|
||||
public Texture2D()
|
||||
{
|
||||
super(GL33.GL_TEXTURE_2D, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeData(long address)
|
||||
{
|
||||
GL33.glTexImage2D(this.type, 0, GL33.GL_RGBA8, this.width, this.height, 0, GL33.GL_RGBA, GL33.GL_UNSIGNED_BYTE, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMipMapping()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
package cz.tefek.pluto.engine.graphics.texture.texture2d;
|
||||
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import cz.tefek.pluto.engine.graphics.texture.Texture;
|
||||
|
||||
/**
|
||||
* @author 493msi
|
||||
*
|
||||
*/
|
||||
public class Texture2D extends Texture
|
||||
{
|
||||
public Texture2D()
|
||||
{
|
||||
super(GL33.GL_TEXTURE_2D, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeData(long address)
|
||||
{
|
||||
GL33.glTexImage2D(this.type, 0, GL33.GL_RGBA8, this.width, this.height, 0, GL33.GL_RGBA, GL33.GL_UNSIGNED_BYTE, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMipMapping()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue