TPL refactor, unified line endings, JavaDoc and full switch to SmartSeverity

This commit is contained in:
Tefek 2020-09-08 23:32:20 +02:00
parent 60fae86237
commit 2b065249ee
64 changed files with 4471 additions and 4306 deletions

View File

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

View File

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

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

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

View File

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

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -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 : "");
}

View File

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

View File

@ -1,10 +1,10 @@
package cz.tefek.pluto.eventsystem;
/**
* @author 493msi
*
*/
public class EventData
{
}
package cz.tefek.pluto.eventsystem;
/**
* @author 493msi
*
*/
public class EventData
{
}

View File

@ -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())
{

View File

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

View File

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

View File

@ -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());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.");
}
}
}

View File

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

View File

@ -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();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>();
}

View File

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

View File

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

View File

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

View File

@ -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];
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

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

View File

@ -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);
}
}
}

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}
}

View File

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

View File

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