init
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
Sable's sub-levels each contain their own lighting sections, and lighting data.
|
||||
|
||||
We need Flywheel shaders to be aware of this, so we change and override the lighting storage, LUT, and shaders to respect an additional "scene ID".
|
||||
|
||||
I'm not happy with the large amounts of duplicated shader code in these overrides, but it's the route we are going with for now.
|
||||
|
||||
Reference https://github.com/Engine-Room/Flywheel/tree/1.21.1/dev for the original shaders and lighting code that these overrides are based on.
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "flywheel:internal/material.glsl"
|
||||
#include "flywheel:internal/api_impl.glsl"
|
||||
#include "flywheel:internal/uniforms/uniforms.glsl"
|
||||
|
||||
in vec4 flw_vertexPos;
|
||||
in vec4 flw_vertexColor;
|
||||
in vec2 flw_vertexTexCoord;
|
||||
flat in ivec2 flw_vertexOverlay;
|
||||
in vec2 flw_vertexLight;
|
||||
in vec3 flw_vertexNormal;
|
||||
|
||||
in float flw_distance;
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
flat in float flw_skyLightScale;
|
||||
flat in uint flw_vertexLightingSceneId;
|
||||
in vec4 flw_vertexLightingPos;
|
||||
#endif
|
||||
|
||||
vec4 flw_sampleColor;
|
||||
|
||||
FlwMaterial flw_material;
|
||||
|
||||
bool flw_fragDiffuse;
|
||||
vec4 flw_fragColor;
|
||||
ivec2 flw_fragOverlay;
|
||||
vec2 flw_fragLight;
|
||||
|
||||
uniform sampler2D flw_diffuseTex;
|
||||
uniform sampler2D flw_overlayTex;
|
||||
uniform sampler2D flw_lightTex;
|
||||
@@ -0,0 +1,26 @@
|
||||
struct FlwLightAo {
|
||||
vec2 light;
|
||||
float ao;
|
||||
};
|
||||
|
||||
/// Get the light at the given world position relative to flw_renderOrigin from the given normal.
|
||||
/// This may be interpolated for smooth lighting.
|
||||
bool flw_light(uint scene, vec3 worldPos, vec3 normal, ivec3 renderOrigin, out FlwLightAo light);
|
||||
|
||||
/// Fetches the light value at the given block position.
|
||||
/// Returns false if the light for the given block is not available.
|
||||
bool flw_lightFetch(uint scene, ivec3 blockPos, out vec2 light);
|
||||
|
||||
// Backwards compatible overloads
|
||||
|
||||
/// Get the light at the given world position relative to flw_renderOrigin from the given normal.
|
||||
/// This may be interpolated for smooth lighting.
|
||||
bool flw_light(vec3 worldPos, vec3 normal, ivec3 renderOrigin, out FlwLightAo light) {
|
||||
return flw_light(0u, worldPos, normal, renderOrigin, light);
|
||||
}
|
||||
|
||||
/// Fetches the light value at the given block position.
|
||||
/// Returns false if the light for the given block is not available.
|
||||
bool flw_lightFetch(ivec3 blockPos, out vec2 light) {
|
||||
return flw_lightFetch(0u, blockPos, light);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "flywheel:internal/fog_distance.glsl"
|
||||
|
||||
#ifdef _FLW_CRUMBLING
|
||||
out vec2 _flw_crumblingTexCoord;
|
||||
|
||||
const int DOWN = 0;
|
||||
const int UP = 1;
|
||||
const int NORTH = 2;
|
||||
const int SOUTH = 3;
|
||||
const int WEST = 4;
|
||||
const int EAST = 5;
|
||||
|
||||
// based on net.minecraftforge.client.ForgeHooksClient.getNearestStable
|
||||
int getNearestFacing(vec3 normal) {
|
||||
float maxAlignment = -2;
|
||||
int face = 2;
|
||||
|
||||
// Calculate the alignment of the normal vector with each axis.
|
||||
// Note that `-dot(normal, axis) == dot(normal, -axis)`.
|
||||
vec3 alignment = vec3(
|
||||
dot(normal, vec3(1., 0., 0.)),
|
||||
dot(normal, vec3(0., 1., 0.)),
|
||||
dot(normal, vec3(0., 0., 1.))
|
||||
);
|
||||
|
||||
if (-alignment.y > maxAlignment) {
|
||||
maxAlignment = -alignment.y;
|
||||
face = DOWN;
|
||||
}
|
||||
if (alignment.y > maxAlignment) {
|
||||
maxAlignment = alignment.y;
|
||||
face = UP;
|
||||
}
|
||||
if (-alignment.z > maxAlignment) {
|
||||
maxAlignment = -alignment.z;
|
||||
face = NORTH;
|
||||
}
|
||||
if (alignment.z > maxAlignment) {
|
||||
maxAlignment = alignment.z;
|
||||
face = SOUTH;
|
||||
}
|
||||
if (-alignment.x > maxAlignment) {
|
||||
maxAlignment = -alignment.x;
|
||||
face = WEST;
|
||||
}
|
||||
if (alignment.x > maxAlignment) {
|
||||
maxAlignment = alignment.x;
|
||||
face = EAST;
|
||||
}
|
||||
|
||||
return face;
|
||||
}
|
||||
|
||||
vec2 getCrumblingTexCoord() {
|
||||
switch (getNearestFacing(flw_vertexNormal)) {
|
||||
case DOWN: return vec2(flw_vertexPos.x, -flw_vertexPos.z);
|
||||
case UP: return vec2(flw_vertexPos.x, flw_vertexPos.z);
|
||||
case NORTH: return vec2(-flw_vertexPos.x, -flw_vertexPos.y);
|
||||
case SOUTH: return vec2(flw_vertexPos.x, -flw_vertexPos.y);
|
||||
case WEST: return vec2(-flw_vertexPos.z, -flw_vertexPos.y);
|
||||
case EAST: return vec2(flw_vertexPos.z, -flw_vertexPos.y);
|
||||
}
|
||||
|
||||
// default to north
|
||||
return vec2(-flw_vertexPos.x, -flw_vertexPos.y);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
mat4 _flw_modelMatrix;
|
||||
mat3 _flw_normalMatrix;
|
||||
uint _flw_lightingSceneId;
|
||||
float _flw_skyLightScale;
|
||||
mat4 _flw_lightingSceneMatrix;
|
||||
flat out uint flw_vertexLightingSceneId;
|
||||
flat out float flw_skyLightScale;
|
||||
out vec4 flw_vertexLightingPos;
|
||||
#endif
|
||||
|
||||
#ifdef _FLW_DEBUG
|
||||
flat out uvec2 _flw_ids;
|
||||
#endif
|
||||
|
||||
void _flw_main(in FlwInstance instance, in uint stableInstanceID, in uint baseVertex) {
|
||||
flw_vertexId = gl_VertexID - baseVertex;
|
||||
|
||||
_flw_layoutVertex();
|
||||
flw_instanceVertex(instance);
|
||||
flw_materialVertex();
|
||||
|
||||
#ifdef _FLW_CRUMBLING
|
||||
_flw_crumblingTexCoord = getCrumblingTexCoord();
|
||||
#endif
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
flw_vertexLightingPos = _flw_lightingSceneMatrix * flw_vertexPos;
|
||||
flw_vertexPos = _flw_modelMatrix * flw_vertexPos;
|
||||
flw_vertexNormal = _flw_normalMatrix * flw_vertexNormal;
|
||||
flw_vertexLightingSceneId = _flw_lightingSceneId;
|
||||
flw_skyLightScale = _flw_skyLightScale;
|
||||
#endif
|
||||
|
||||
flw_vertexNormal = normalize(flw_vertexNormal);
|
||||
|
||||
flw_distance = fogDistance(flw_vertexPos.xyz, flw_cameraPos, flw_fogShape);
|
||||
|
||||
gl_Position = flw_viewProjection * flw_vertexPos;
|
||||
|
||||
#ifdef _FLW_DEBUG
|
||||
_flw_ids = uvec2(stableInstanceID, baseVertex);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "flywheel:internal/common.vert"
|
||||
#include "flywheel:internal/packed_material.glsl"
|
||||
#include "flywheel:internal/indirect/buffer_bindings.glsl"
|
||||
#include "flywheel:internal/indirect/draw_command.glsl"
|
||||
#include "flywheel:internal/indirect/light.glsl"
|
||||
#include "flywheel:internal/indirect/matrices.glsl"
|
||||
|
||||
layout(std430, binding = _FLW_DRAW_INSTANCE_INDEX_BUFFER_BINDING) restrict readonly buffer TargetBuffer {
|
||||
uint _flw_instanceIndices[];
|
||||
};
|
||||
|
||||
layout(std430, binding = _FLW_DRAW_BUFFER_BINDING) restrict readonly buffer DrawBuffer {
|
||||
MeshDrawCommand _flw_drawCommands[];
|
||||
};
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
layout(std430, binding = _FLW_MATRIX_BUFFER_BINDING) restrict buffer MatrixBuffer {
|
||||
Matrices _flw_matrices[];
|
||||
};
|
||||
#endif
|
||||
|
||||
uniform uint _flw_baseDraw;
|
||||
|
||||
flat out uvec2 _flw_packedMaterial;
|
||||
|
||||
#if __VERSION__ < 460
|
||||
#define flw_baseInstance gl_BaseInstanceARB
|
||||
#define flw_drawId gl_DrawIDARB
|
||||
#else
|
||||
#define flw_baseInstance gl_BaseInstance
|
||||
#define flw_drawId gl_DrawID
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
uint drawIndex = flw_drawId + _flw_baseDraw;
|
||||
MeshDrawCommand draw = _flw_drawCommands[drawIndex];
|
||||
|
||||
uint packedMaterialProperties = draw.packedMaterialProperties;
|
||||
_flw_unpackMaterialProperties(packedMaterialProperties, flw_material);
|
||||
_flw_packedMaterial = uvec2(draw.packedFogAndCutout, packedMaterialProperties);
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
_flw_unpackMatrices(_flw_matrices[draw.matrixIndex], _flw_modelMatrix, _flw_normalMatrix, _flw_lightingSceneId, _flw_skyLightScale, _flw_lightingSceneMatrix);
|
||||
#endif
|
||||
|
||||
#ifdef _FLW_CRUMBLING
|
||||
uint instanceIndex = flw_baseInstance;
|
||||
#else
|
||||
uint instanceIndex = _flw_instanceIndices[flw_baseInstance + gl_InstanceID];
|
||||
#endif
|
||||
|
||||
FlwInstance instance = _flw_unpackInstance(instanceIndex);
|
||||
|
||||
_flw_main(instance, instanceIndex, draw.vertexOffset);
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
struct Matrices {
|
||||
mat4 pose;
|
||||
vec4 normalA;
|
||||
vec4 normalB;
|
||||
vec4 normalC;
|
||||
float skyLightScale;
|
||||
uint sceneID;
|
||||
float _padding1;
|
||||
float _padding2;
|
||||
mat4 lightingSceneMatrix;
|
||||
};
|
||||
|
||||
void _flw_unpackMatrices(in Matrices mats, out mat4 pose, out mat3 normal, out uint lightingSceneId, out float skyLightScale, out mat4 lightingSceneMatrix) {
|
||||
pose = mats.pose;
|
||||
normal = mat3(mats.normalA.xyz, mats.normalB.xyz, mats.normalC.xyz);
|
||||
lightingSceneId = mats.sceneID;
|
||||
skyLightScale = mats.skyLightScale;
|
||||
lightingSceneMatrix = mats.lightingSceneMatrix;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "flywheel:internal/common.vert"
|
||||
#include "flywheel:internal/packed_material.glsl"
|
||||
#include "flywheel:internal/instancing/light.glsl"
|
||||
|
||||
uniform uvec2 _flw_packedMaterial;
|
||||
uniform int _flw_baseInstance = 0;
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
uniform mat4 _flw_modelMatrixUniform;
|
||||
uniform mat3 _flw_normalMatrixUniform;
|
||||
uniform uint _flw_lightingSceneUniform;
|
||||
uniform float _flw_lightingSkyLightScaleUniform;
|
||||
uniform mat4 _flw_lightingSceneMatrixUniform;
|
||||
#endif
|
||||
|
||||
uniform uint _flw_baseVertex;
|
||||
|
||||
void main() {
|
||||
_flw_unpackMaterialProperties(_flw_packedMaterial.y, flw_material);
|
||||
|
||||
FlwInstance instance = _flw_unpackInstance(_flw_baseInstance + gl_InstanceID);
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
_flw_modelMatrix = _flw_modelMatrixUniform;
|
||||
_flw_normalMatrix = _flw_normalMatrixUniform;
|
||||
_flw_lightingSceneMatrix = _flw_lightingSceneMatrixUniform;
|
||||
_flw_lightingSceneId = _flw_lightingSceneUniform;
|
||||
_flw_skyLightScale = _flw_lightingSkyLightScaleUniform;
|
||||
#endif
|
||||
|
||||
_flw_main(instance, uint(gl_InstanceID), _flw_baseVertex);
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
const uint _FLW_BLOCKS_PER_SECTION = 18u * 18u * 18u;
|
||||
const uint _FLW_LIGHT_SIZE_BYTES = _FLW_BLOCKS_PER_SECTION;
|
||||
const uint _FLW_SOLID_SIZE_BYTES = ((_FLW_BLOCKS_PER_SECTION + 31u) / 32u) * 4u;
|
||||
const uint _FLW_LIGHT_START_BYTES = _FLW_SOLID_SIZE_BYTES;
|
||||
const uint _FLW_LIGHT_SECTION_SIZE_BYTES = _FLW_SOLID_SIZE_BYTES + _FLW_LIGHT_SIZE_BYTES;
|
||||
|
||||
const uint _FLW_SOLID_START_INTS = 0u;
|
||||
const uint _FLW_LIGHT_START_INTS = _FLW_SOLID_SIZE_BYTES / 4u;
|
||||
const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4u;
|
||||
|
||||
const uint _FLW_COMPLETELY_SOLID = 0x7FFFFFFu;
|
||||
const float _FLW_EPSILON = 1e-5;
|
||||
|
||||
const uint _FLW_LOWER_10_BITS = 0x3FFu;
|
||||
const uint _FLW_UPPER_10_BITS = 0xFFF00000u;
|
||||
|
||||
const float _FLW_LIGHT_NORMALIZER = 1. / 16.;
|
||||
|
||||
uint _flw_indexLut(uint index);
|
||||
|
||||
uint _flw_indexLight(uint index);
|
||||
|
||||
/// Find the index for the next step in the LUT.
|
||||
/// @param base The base index in the LUT, should point to the start of a coordinate span.
|
||||
/// @param coord The coordinate to look for.
|
||||
/// @param next Output. The index of the next step in the LUT.
|
||||
/// @return true if the coordinate is not in the span.
|
||||
bool _flw_nextLut(uint base, int coord, out uint next) {
|
||||
// The base coordinate.
|
||||
int start = int(_flw_indexLut(base));
|
||||
// The width of the coordinate span.
|
||||
uint size = _flw_indexLut(base + 1u);
|
||||
|
||||
// Index of the coordinate in the span.
|
||||
int i = coord - start;
|
||||
|
||||
if (i < 0 || i >= int(size)) {
|
||||
// We missed.
|
||||
return true;
|
||||
}
|
||||
|
||||
next = _flw_indexLut(base + 2u + uint(i));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _flw_chunkCoordToSectionIndex(uint sceneId, ivec3 sectionPos, out uint index) {
|
||||
uint scene;
|
||||
if (_flw_nextLut(0u, int(sceneId), scene) || scene == 0u) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint first;
|
||||
if (_flw_nextLut(scene, sectionPos.y, first) || first == 0u) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint second;
|
||||
if (_flw_nextLut(first, sectionPos.x, second) || second == 0u) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint sectionIndex;
|
||||
if (_flw_nextLut(second, sectionPos.z, sectionIndex) || sectionIndex == 0u) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The index is written as 1-based so we can properly detect missing sections.
|
||||
index = sectionIndex - 1u;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uvec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) {
|
||||
uint byteOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u;
|
||||
|
||||
uint uintOffset = byteOffset >> 2u;
|
||||
uint bitOffset = (byteOffset & 3u) << 3;
|
||||
|
||||
uint raw = _flw_indexLight(sectionOffset + _FLW_LIGHT_START_INTS + uintOffset);
|
||||
uint block = (raw >> bitOffset) & 0xFu;
|
||||
uint sky = (raw >> (bitOffset + 4u)) & 0xFu;
|
||||
|
||||
return uvec2(block, sky);
|
||||
}
|
||||
|
||||
bool _flw_isSolid(uint sectionOffset, uvec3 blockInSectionPos) {
|
||||
uint bitOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u;
|
||||
|
||||
uint uintOffset = bitOffset >> 5u;
|
||||
uint bitInWordOffset = bitOffset & 31u;
|
||||
|
||||
uint word = _flw_indexLight(sectionOffset + _FLW_SOLID_START_INTS + uintOffset);
|
||||
|
||||
return (word & (1u << bitInWordOffset)) != 0u;
|
||||
}
|
||||
|
||||
bool flw_lightFetch(uint scene, ivec3 blockPos, out vec2 lightCoord) {
|
||||
uint lightSectionIndex;
|
||||
if (_flw_chunkCoordToSectionIndex(scene, blockPos >> 4, lightSectionIndex)) {
|
||||
return false;
|
||||
}
|
||||
// The offset of the section in the light buffer.
|
||||
uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS;
|
||||
|
||||
uvec3 blockInSectionPos = uvec3((blockPos & 0xF) + 1);
|
||||
|
||||
lightCoord = vec2(_flw_lightAt(sectionOffset, blockInSectionPos)) * _FLW_LIGHT_NORMALIZER;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
uint _flw_fetchSolid3x3x3(uint sectionOffset, ivec3 blockInSectionPos) {
|
||||
uint ret = 0u;
|
||||
|
||||
// The formatter does NOT like these macros
|
||||
// @formatter:off
|
||||
|
||||
#define _FLW_FETCH_SOLID(x, y, z, i) { \
|
||||
bool flag = _flw_isSolid(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z))); \
|
||||
ret |= uint(flag) << i; \
|
||||
}
|
||||
|
||||
/// fori y, z, x: unrolled
|
||||
_FLW_FETCH_SOLID(-1, -1, -1, 0)
|
||||
_FLW_FETCH_SOLID(0, -1, -1, 1)
|
||||
_FLW_FETCH_SOLID(1, -1, -1, 2)
|
||||
|
||||
_FLW_FETCH_SOLID(-1, -1, 0, 3)
|
||||
_FLW_FETCH_SOLID(0, -1, 0, 4)
|
||||
_FLW_FETCH_SOLID(1, -1, 0, 5)
|
||||
|
||||
_FLW_FETCH_SOLID(-1, -1, 1, 6)
|
||||
_FLW_FETCH_SOLID(0, -1, 1, 7)
|
||||
_FLW_FETCH_SOLID(1, -1, 1, 8)
|
||||
|
||||
_FLW_FETCH_SOLID(-1, 0, -1, 9)
|
||||
_FLW_FETCH_SOLID(0, 0, -1, 10)
|
||||
_FLW_FETCH_SOLID(1, 0, -1, 11)
|
||||
|
||||
_FLW_FETCH_SOLID(-1, 0, 0, 12)
|
||||
_FLW_FETCH_SOLID(0, 0, 0, 13)
|
||||
_FLW_FETCH_SOLID(1, 0, 0, 14)
|
||||
|
||||
_FLW_FETCH_SOLID(-1, 0, 1, 15)
|
||||
_FLW_FETCH_SOLID(0, 0, 1, 16)
|
||||
_FLW_FETCH_SOLID(1, 0, 1, 17)
|
||||
|
||||
_FLW_FETCH_SOLID(-1, 1, -1, 18)
|
||||
_FLW_FETCH_SOLID(0, 1, -1, 19)
|
||||
_FLW_FETCH_SOLID(1, 1, -1, 20)
|
||||
|
||||
_FLW_FETCH_SOLID(-1, 1, 0, 21)
|
||||
_FLW_FETCH_SOLID(0, 1, 0, 22)
|
||||
_FLW_FETCH_SOLID(1, 1, 0, 23)
|
||||
|
||||
_FLW_FETCH_SOLID(-1, 1, 1, 24)
|
||||
_FLW_FETCH_SOLID(0, 1, 1, 25)
|
||||
_FLW_FETCH_SOLID(1, 1, 1, 26)
|
||||
|
||||
// @formatter:on
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Premtively collect all light in a 3x3x3 area centered on our block.
|
||||
/// Depending on the normal, we won't use all the data, but fetching on demand will have many duplicated fetches.
|
||||
/// Only fetching what we'll actually use using a bitmask turned out significantly slower, but perhaps a less
|
||||
/// granular approach could see wins.
|
||||
///
|
||||
/// The output is a 3-component vector <blockLight, skyLight, valid ? 1 : 0> packed into a single uint to save
|
||||
/// memory and ALU ops later on. 10 bits are used for each component. This allows 4 such packed ints to be added
|
||||
/// together with room to spare before overflowing into the next component.
|
||||
uint[27] _flw_fetchLight3x3x3(uint sectionOffset, ivec3 blockInSectionPos, uint solidMask) {
|
||||
uint[27] lights;
|
||||
|
||||
// @formatter:off
|
||||
#define _FLW_FETCH_LIGHT(_x, _y, _z, i) { \
|
||||
uvec2 light = _flw_lightAt(sectionOffset, uvec3(blockInSectionPos + ivec3(_x, _y, _z))); \
|
||||
lights[i] = (light.x) | ((light.y) << 10) | (uint((solidMask & (1u << i)) == 0u) << 20); \
|
||||
}
|
||||
|
||||
/// fori y, z, x: unrolled
|
||||
_FLW_FETCH_LIGHT(-1, -1, -1, 0)
|
||||
_FLW_FETCH_LIGHT(0, -1, -1, 1)
|
||||
_FLW_FETCH_LIGHT(1, -1, -1, 2)
|
||||
|
||||
_FLW_FETCH_LIGHT(-1, -1, 0, 3)
|
||||
_FLW_FETCH_LIGHT(0, -1, 0, 4)
|
||||
_FLW_FETCH_LIGHT(1, -1, 0, 5)
|
||||
|
||||
_FLW_FETCH_LIGHT(-1, -1, 1, 6)
|
||||
_FLW_FETCH_LIGHT(0, -1, 1, 7)
|
||||
_FLW_FETCH_LIGHT(1, -1, 1, 8)
|
||||
|
||||
_FLW_FETCH_LIGHT(-1, 0, -1, 9)
|
||||
_FLW_FETCH_LIGHT(0, 0, -1, 10)
|
||||
_FLW_FETCH_LIGHT(1, 0, -1, 11)
|
||||
|
||||
_FLW_FETCH_LIGHT(-1, 0, 0, 12)
|
||||
_FLW_FETCH_LIGHT(0, 0, 0, 13)
|
||||
_FLW_FETCH_LIGHT(1, 0, 0, 14)
|
||||
|
||||
_FLW_FETCH_LIGHT(-1, 0, 1, 15)
|
||||
_FLW_FETCH_LIGHT(0, 0, 1, 16)
|
||||
_FLW_FETCH_LIGHT(1, 0, 1, 17)
|
||||
|
||||
_FLW_FETCH_LIGHT(-1, 1, -1, 18)
|
||||
_FLW_FETCH_LIGHT(0, 1, -1, 19)
|
||||
_FLW_FETCH_LIGHT(1, 1, -1, 20)
|
||||
|
||||
_FLW_FETCH_LIGHT(-1, 1, 0, 21)
|
||||
_FLW_FETCH_LIGHT(0, 1, 0, 22)
|
||||
_FLW_FETCH_LIGHT(1, 1, 0, 23)
|
||||
|
||||
_FLW_FETCH_LIGHT(-1, 1, 1, 24)
|
||||
_FLW_FETCH_LIGHT(0, 1, 1, 25)
|
||||
_FLW_FETCH_LIGHT(1, 1, 1, 26)
|
||||
|
||||
// @formatter:on
|
||||
|
||||
return lights;
|
||||
}
|
||||
|
||||
#define _flw_index3x3x3(x, y, z) ((x) + (z) * 3u + (y) * 9u)
|
||||
#define _flw_validCountToAo(validCount) (1. - (4. - (validCount)) * 0.2)
|
||||
|
||||
/// Calculate the light for a direction by averaging the light at the corners of the block.
|
||||
///
|
||||
/// To make this reusable across directions, c00..c11 choose what values relative to each corner to use.
|
||||
/// e.g. (0, 0, 0) (0, 0, 1) (0, 1, 0) (0, 1, 1) would give you the light coming from -x at each corner.
|
||||
/// In general, to get the light for a particular direction, you fix the x, y, or z coordinate of the c values, and permutate 0 and 1 for the other two.
|
||||
/// Fixing the x coordinate to 0 gives you the light from -x, 1 gives you the light from +x.
|
||||
///
|
||||
/// @param lights The light data for the 3x3x3 area.
|
||||
/// @param interpolant The position within the center block.
|
||||
/// @param c00..c11 4 offsets to determine which "direction" we are averaging.
|
||||
/// @param oppositeMask A bitmask telling this function which bit to flip to get the opposite index for a given corner
|
||||
vec3 _flw_lightForDirection(uint[27] lights, vec3 interpolant, uint c00, uint c01, uint c10, uint c11, uint oppositeMask) {
|
||||
// Sum up the light and number of valid blocks in each corner for this direction
|
||||
uint[8] summed;
|
||||
|
||||
// @formatter:off
|
||||
|
||||
#define _FLW_SUM_CORNER(_x, _y, _z, i) { \
|
||||
const uint corner = _flw_index3x3x3(_x, _y, _z); \
|
||||
summed[i] = lights[c00 + corner] + lights[c01 + corner] + lights[c10 + corner] + lights[c11 + corner]; \
|
||||
}
|
||||
|
||||
_FLW_SUM_CORNER(0u, 0u, 0u, 0)
|
||||
_FLW_SUM_CORNER(1u, 0u, 0u, 1)
|
||||
_FLW_SUM_CORNER(0u, 0u, 1u, 2)
|
||||
_FLW_SUM_CORNER(1u, 0u, 1u, 3)
|
||||
_FLW_SUM_CORNER(0u, 1u, 0u, 4)
|
||||
_FLW_SUM_CORNER(1u, 1u, 0u, 5)
|
||||
_FLW_SUM_CORNER(0u, 1u, 1u, 6)
|
||||
_FLW_SUM_CORNER(1u, 1u, 1u, 7)
|
||||
|
||||
// @formatter:on
|
||||
|
||||
// The final light and number of valid blocks for each corner.
|
||||
vec3[8] adjusted;
|
||||
|
||||
#ifdef _FLW_INNER_FACE_CORRECTION
|
||||
// If the current corner has no valid blocks, use the opposite
|
||||
// corner's light based on which direction we're evaluating.
|
||||
// Because of how our corners are indexed, moving along one axis is the same as flipping a bit.
|
||||
#define _FLW_CORNER_INDEX(i) ((summed[i] & _FLW_UPPER_10_BITS) == 0u ? i ^ oppositeMask : i)
|
||||
#else
|
||||
#define _FLW_CORNER_INDEX(i) i
|
||||
#endif
|
||||
|
||||
// Division and branching (to avoid dividing by zero) are both kinda expensive, so use this table for the valid block normalization
|
||||
const float[5] normalizers = float[](0., 1., 1. / 2., 1. / 3., 1. / 4.);
|
||||
|
||||
// @formatter:off
|
||||
|
||||
#define _FLW_ADJUST_CORNER(i) { \
|
||||
uint corner = summed[_FLW_CORNER_INDEX(i)]; \
|
||||
uint validCount = corner >> 20u; \
|
||||
adjusted[i].xy = vec2(corner & _FLW_LOWER_10_BITS, (corner >> 10u) & _FLW_LOWER_10_BITS) * normalizers[validCount]; \
|
||||
adjusted[i].z = float(validCount); \
|
||||
}
|
||||
|
||||
_FLW_ADJUST_CORNER(0)
|
||||
_FLW_ADJUST_CORNER(1)
|
||||
_FLW_ADJUST_CORNER(2)
|
||||
_FLW_ADJUST_CORNER(3)
|
||||
_FLW_ADJUST_CORNER(4)
|
||||
_FLW_ADJUST_CORNER(5)
|
||||
_FLW_ADJUST_CORNER(6)
|
||||
_FLW_ADJUST_CORNER(7)
|
||||
|
||||
// @formatter:on
|
||||
|
||||
// Trilinear interpolation, including valid count
|
||||
vec3 light00 = mix(adjusted[0], adjusted[1], interpolant.x);
|
||||
vec3 light01 = mix(adjusted[2], adjusted[3], interpolant.x);
|
||||
vec3 light10 = mix(adjusted[4], adjusted[5], interpolant.x);
|
||||
vec3 light11 = mix(adjusted[6], adjusted[7], interpolant.x);
|
||||
|
||||
vec3 light0 = mix(light00, light01, interpolant.z);
|
||||
vec3 light1 = mix(light10, light11, interpolant.z);
|
||||
|
||||
vec3 light = mix(light0, light1, interpolant.y);
|
||||
|
||||
// Normalize the light coords
|
||||
light.xy *= _FLW_LIGHT_NORMALIZER;
|
||||
// Calculate the AO multiplier from the number of valid blocks
|
||||
light.z = _flw_validCountToAo(light.z);
|
||||
|
||||
return light;
|
||||
}
|
||||
|
||||
bool flw_light(uint scene, vec3 worldPos, vec3 normal, ivec3 renderOrigin, out FlwLightAo light) {
|
||||
// Always use the section of the block we are contained in to ensure accuracy.
|
||||
// We don't want to interpolate between sections, but also we might not be able
|
||||
// to rely on the existence neighboring sections, so don't do any extra rounding here.
|
||||
ivec3 blockPos = ivec3(floor(worldPos)) + renderOrigin;
|
||||
|
||||
uint lightSectionIndex;
|
||||
if (_flw_chunkCoordToSectionIndex(scene, blockPos >> 4, lightSectionIndex)) {
|
||||
return false;
|
||||
}
|
||||
// The offset of the section in the light buffer.
|
||||
uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS;
|
||||
|
||||
// The block's position in the section adjusted into 18x18x18 space
|
||||
ivec3 blockInSectionPos = (blockPos & 0xF) + 1;
|
||||
|
||||
// Directly trilerp as if sampling a texture
|
||||
#if _FLW_LIGHT_SMOOTHNESS == 1
|
||||
|
||||
// The lowest corner of the 2x2x2 area we'll be trilinear interpolating.
|
||||
// The ugly bit on the end evaluates to -1 or 0 depending on which side of 0.5 we are.
|
||||
uvec3 lowestCorner = blockInSectionPos + ivec3(floor(fract(worldPos) - 0.5));
|
||||
|
||||
// The distance our fragment is from the center of the lowest corner.
|
||||
vec3 interpolant = fract(worldPos - 0.5);
|
||||
|
||||
// Fetch everything for trilinear interpolation
|
||||
// Hypothetically we could re-order these and do some calculations in-between fetches
|
||||
// to help with latency hiding, but the compiler should be able to do that for us.
|
||||
vec2 light000 = vec2(_flw_lightAt(sectionOffset, lowestCorner));
|
||||
vec2 light100 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 0)));
|
||||
vec2 light001 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 0, 1)));
|
||||
vec2 light101 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 1)));
|
||||
vec2 light010 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 0)));
|
||||
vec2 light110 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 0)));
|
||||
vec2 light011 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 1)));
|
||||
vec2 light111 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 1)));
|
||||
|
||||
vec2 light00 = mix(light000, light001, interpolant.z);
|
||||
vec2 light01 = mix(light010, light011, interpolant.z);
|
||||
vec2 light10 = mix(light100, light101, interpolant.z);
|
||||
vec2 light11 = mix(light110, light111, interpolant.z);
|
||||
|
||||
vec2 light0 = mix(light00, light01, interpolant.y);
|
||||
vec2 light1 = mix(light10, light11, interpolant.y);
|
||||
|
||||
light.light = mix(light0, light1, interpolant.x) * _FLW_LIGHT_NORMALIZER;
|
||||
light.ao = 1.;
|
||||
|
||||
// Lighting and AO accurate to chunk baking
|
||||
#elif _FLW_LIGHT_SMOOTHNESS == 2
|
||||
|
||||
uint solid = _flw_fetchSolid3x3x3(sectionOffset, blockInSectionPos);
|
||||
|
||||
if (solid == _FLW_COMPLETELY_SOLID) {
|
||||
// No point in doing any work if the entire 3x3x3 volume around us is filled.
|
||||
// Kinda rare but this may happen if our fragment is in the middle of a lot of tinted glass
|
||||
light.light = vec2(0.);
|
||||
light.ao = _flw_validCountToAo(0.);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fetch everything in a 3x3x3 area centered around the block.
|
||||
uint[27] lights = _flw_fetchLight3x3x3(sectionOffset, blockInSectionPos, solid);
|
||||
|
||||
vec3 interpolant = fract(worldPos);
|
||||
|
||||
// Average the light in relevant directions at each corner, skipping directions that would have no influence
|
||||
|
||||
vec3 lightX;
|
||||
if (normal.x > _FLW_EPSILON) {
|
||||
lightX = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(1u, 0u, 0u), _flw_index3x3x3(1u, 0u, 1u), _flw_index3x3x3(1u, 1u, 0u), _flw_index3x3x3(1u, 1u, 1u), 1u);
|
||||
} else if (normal.x < -_FLW_EPSILON) {
|
||||
lightX = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 0u), _flw_index3x3x3(0u, 0u, 1u), _flw_index3x3x3(0u, 1u, 0u), _flw_index3x3x3(0u, 1u, 1u), 1u);
|
||||
} else {
|
||||
lightX = vec3(0.);
|
||||
}
|
||||
|
||||
vec3 lightZ;
|
||||
if (normal.z > _FLW_EPSILON) {
|
||||
lightZ = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 1u), _flw_index3x3x3(0u, 1u, 1u), _flw_index3x3x3(1u, 0u, 1u), _flw_index3x3x3(1u, 1u, 1u), 2u);
|
||||
} else if (normal.z < -_FLW_EPSILON) {
|
||||
lightZ = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 0u), _flw_index3x3x3(0u, 1u, 0u), _flw_index3x3x3(1u, 0u, 0u), _flw_index3x3x3(1u, 1u, 0u), 2u);
|
||||
} else {
|
||||
lightZ = vec3(0.);
|
||||
}
|
||||
|
||||
vec3 lightY;
|
||||
if (normal.y > _FLW_EPSILON) {
|
||||
lightY = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 1u, 0u), _flw_index3x3x3(0u, 1u, 1u), _flw_index3x3x3(1u, 1u, 0u), _flw_index3x3x3(1u, 1u, 1u), 4u);
|
||||
} else if (normal.y < -_FLW_EPSILON) {
|
||||
lightY = _flw_lightForDirection(lights, interpolant, _flw_index3x3x3(0u, 0u, 0u), _flw_index3x3x3(0u, 0u, 1u), _flw_index3x3x3(1u, 0u, 0u), _flw_index3x3x3(1u, 0u, 1u), 4u);
|
||||
} else {
|
||||
lightY = vec3(0.);
|
||||
}
|
||||
|
||||
vec3 n2 = normal * normal;
|
||||
vec3 lightAo = lightX * n2.x + lightY * n2.y + lightZ * n2.z;
|
||||
|
||||
light.light = lightAo.xy;
|
||||
light.ao = lightAo.z;
|
||||
|
||||
// Entirely flat lighting, the lowest setting and a fallback in case an invalid option is set
|
||||
#else
|
||||
|
||||
light.light = vec2(_flw_lightAt(sectionOffset, blockInSectionPos)) * _FLW_LIGHT_NORMALIZER;
|
||||
light.ao = 1.;
|
||||
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
void flw_shaderLight() {
|
||||
vec2 embeddedLight;
|
||||
|
||||
uint sceneId = 0;
|
||||
vec4 vertexLightingPos;
|
||||
ivec3 renderOrigin;
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
renderOrigin = flw_renderOrigin;
|
||||
sceneId = flw_vertexLightingSceneId;
|
||||
vertexLightingPos = flw_vertexLightingPos;
|
||||
|
||||
if (sceneId != 0) {
|
||||
renderOrigin = ivec3(0);
|
||||
}
|
||||
#else
|
||||
renderOrigin = flw_renderOrigin;
|
||||
vertexLightingPos = flw_vertexPos;
|
||||
#endif
|
||||
|
||||
if (flw_lightFetch(sceneId, ivec3(floor(vertexLightingPos.xyz)) + renderOrigin, embeddedLight)) {
|
||||
flw_fragLight = max(flw_fragLight, embeddedLight);
|
||||
}
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
flw_fragLight.y *= flw_skyLightScale;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
void flw_shaderLight() {
|
||||
uint sceneId = 0;
|
||||
vec4 vertexLightingPos;
|
||||
ivec3 renderOrigin;
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
renderOrigin = flw_renderOrigin;
|
||||
sceneId = flw_vertexLightingSceneId;
|
||||
vertexLightingPos = flw_vertexLightingPos;
|
||||
|
||||
if (sceneId != 0) {
|
||||
renderOrigin = ivec3(0);
|
||||
}
|
||||
#else
|
||||
renderOrigin = flw_renderOrigin;
|
||||
vertexLightingPos = flw_vertexPos;
|
||||
#endif
|
||||
|
||||
FlwLightAo light;
|
||||
if (flw_light(sceneId, vertexLightingPos.xyz, flw_vertexNormal, renderOrigin, light)) {
|
||||
flw_fragLight = max(flw_fragLight, light.light);
|
||||
|
||||
if (flw_material.ambientOcclusion) {
|
||||
flw_fragColor.rgb *= light.ao;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef FLW_EMBEDDED
|
||||
flw_fragLight.y *= flw_skyLightScale;
|
||||
#endif
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
void flw_shaderLight() {
|
||||
#ifdef FLW_EMBEDDED
|
||||
ivec3 renderOrigin = flw_renderOrigin;
|
||||
|
||||
if (flw_vertexLightingSceneId != 0) {
|
||||
renderOrigin = ivec3(0);
|
||||
}
|
||||
|
||||
FlwLightAo light;
|
||||
if (flw_light(flw_vertexLightingSceneId, flw_vertexLightingPos.xyz, flw_vertexNormal, renderOrigin, light)) {
|
||||
flw_fragLight = max(flw_fragLight, light.light);
|
||||
|
||||
if (flw_material.ambientOcclusion) {
|
||||
flw_fragColor.rgb *= light.ao;
|
||||
}
|
||||
}
|
||||
|
||||
flw_fragLight.y *= flw_skyLightScale;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"menu.savingSubLevels": "Saving sub-levels",
|
||||
|
||||
"commands.sable.helper.missing_sub_level_container": "Couldn't find sub-level container for this level!",
|
||||
"commands.sable.helper.missing_physics_system": "Couldn't find sub-level physics system for this level!",
|
||||
|
||||
"commands.sable.sub_level": "sub-level",
|
||||
"commands.sable.sub_levels": "%s sub-levels",
|
||||
|
||||
"commands.sable.physics.global": "global",
|
||||
"commands.sable.physics.local": "local",
|
||||
|
||||
"commands.sable.spawn.success": "Spawned %s",
|
||||
"commands.sable.spawn.clone.success": "Cloned sublevel",
|
||||
|
||||
"commands.sable.physics.impulse.angular.success": "Applied %s angular impulse to %s of %s",
|
||||
"commands.sable.physics.impulse.linear.success": "Applied %s linear impulse to %s of %s",
|
||||
"commands.sable.physics.rotation.add.success": "Added %s rotation to %s of %s",
|
||||
"commands.sable.physics.rotation.set.success": "Set rotation of %s to %s",
|
||||
"commands.sable.physics.translation.add.success": "Added %s translation to %s of %s",
|
||||
"commands.sable.physics.translation.set.success": "Set translation of %s to %s",
|
||||
|
||||
"commands.sable.joint.missing_sublevel_target": "Failed to find sub-level for joint",
|
||||
"commands.sable.joint.success": "Successfully created joint",
|
||||
|
||||
"commands.sable.sub_level.set_name.success_singular": "Set name of sub-level to %s",
|
||||
"commands.sable.sub_level.set_name.success_multiple": "Set name of %s sub-levels to %s",
|
||||
|
||||
"commands.sable.sub_level.get_name.success": "Name of sub-level is %s",
|
||||
"commands.sable.sub_level.get_name.failure_unnamed": "Sub-level has no name",
|
||||
|
||||
"commands.sable.sub_level.clear_name.success_singular": "Cleared name of sub-level",
|
||||
"commands.sable.sub_level.clear_name.success_multiple": "Cleared name of %s sub-levels",
|
||||
|
||||
"commands.sable.sub_level.teleport_with_orientation.success": "Teleported %s to %.2f, %.2f, %.2f facing %.2f, %.2f",
|
||||
"commands.sable.sub_level.teleport.success": "Teleported %s to %.2f, %.2f, %.2f",
|
||||
"commands.sable.sub_level.remove.success": "Removed %s",
|
||||
"commands.sable.sub_level.assemble.no_blocks": "Couldn't assemble sub-level, no valid blocks found",
|
||||
"commands.sable.sub_level.assemble.connected.too_many_blocks": "Couldn't assemble sub-level, too many blocks (maximum %s)",
|
||||
"commands.sable.sub_level.shatter.no_blocks": "Couldn't shatter into sub-levels, no valid blocks found",
|
||||
"commands.sable.sub_level.shatter.connected.too_many_blocks": "Couldn't shatter into sub-levels, too many blocks (maximum %s)",
|
||||
"commands.sable.sub_level.shatter.region.success": "Shattered a region with %s blocks into new sub-levels",
|
||||
"commands.sable.sub_level.shatter.connected.success": "Shattered %s connected blocks into new sub-levels",
|
||||
"commands.sable.sub_level.shatter.range.success": "Shattered a range with %s blocks into new sub-levels",
|
||||
"commands.sable.sub_level.shatter.radius.success": "Shattered a radius with %s blocks into new sub-levels",
|
||||
"commands.sable.sub_level.shatter.sub_level.success": "Shattered %s into %s new sub-levels",
|
||||
"commands.sable.sub_level.shatter.sub_level.only_single_block": "Can't shatter single-block sub-levels",
|
||||
"commands.sable.sub_level.assemble.region.success": "Assembled a region %s blocks to a new sub-level",
|
||||
"commands.sable.sub_level.assemble.connected.success": "Assembled %s connected blocks to a new sub-level",
|
||||
"commands.sable.sub_level.assemble.radius.success": "Assembled a radius with %s blocks to a new sub-level",
|
||||
"commands.sable.sub_level.assemble.range.success": "Assembled a range with %s blocks to a new sub-level",
|
||||
|
||||
"commands.sable.physics.paused.success": "Set physics paused to be %s",
|
||||
"commands.sable.physics.paused_toggled.success": "Toggled physics paused to be %s",
|
||||
|
||||
"commands.sable.place_schematic.failure": "Couldn't find schematic!",
|
||||
"commands.sable.place_schematic.success": "Placed schematic!",
|
||||
|
||||
"commands.sable.fail.not_inside_sub_level": "Position outside of sub-level plot",
|
||||
"commands.sable.fail.no_sub_levels": "No sub-levels found",
|
||||
"commands.sable.fail.unmodified": "No sub-levels were modified",
|
||||
"commands.sable.fail.no_axis_for_rotation": "No axis for rotation",
|
||||
|
||||
"commands.sable.info.count": "Found %s sub-levels:",
|
||||
"commands.sable.info.name": "%s:",
|
||||
"commands.sable.info.name.tooltip": "Serialization Pointer: %s",
|
||||
"commands.sable.info.position": " Position: %.2f %.2f %.2f",
|
||||
"commands.sable.info.orientation": " Orientation: %.2f %.2f %.2f %.2f",
|
||||
"commands.sable.info.linear_velocity": " Linear Velocity: %.2f %.2f %.2f",
|
||||
"commands.sable.info.angular_velocity": " Angular Velocity: %.2f %.2f %.2f",
|
||||
"commands.sable.info.mass": " Mass: %.2f",
|
||||
"commands.sable.info.world_bounds": " World Bounds: %.2f x %.2f x %.2f",
|
||||
|
||||
"commands.data.sub_level.get": "%s on sub-level %s after scale factor of %s is %s",
|
||||
"commands.data.sub_level.modified": "Modified sub-level auxiliary data of %s",
|
||||
"commands.data.sub_level.query": "%s has the following auxiliary sub-level data: %s",
|
||||
|
||||
"argument.sable.body.selector.all": "All sub-levels",
|
||||
"argument.sable.body.selector.nearest": "Nearest sub-level",
|
||||
"argument.sable.body.selector.random": "Random sub-level",
|
||||
"argument.sable.body.selector.viewed": "Viewed sub-level",
|
||||
"argument.sable.body.selector.latest": "Latest sub-level",
|
||||
"argument.sable.body.selector.tracking": "Tracking sub-level",
|
||||
"argument.sable.body.selector.inside": "Inside sub-level",
|
||||
"argument.sable.body.static_world": "The static world",
|
||||
|
||||
"argument.sable.sub_level.modifier.distance": "Distance to sub-level",
|
||||
"argument.sable.sub_level.modifier.x": "x position",
|
||||
"argument.sable.sub_level.modifier.y": "y position",
|
||||
"argument.sable.sub_level.modifier.z": "z position",
|
||||
"argument.sable.sub_level.modifier.dx": "Sub-levels between x and x + dx",
|
||||
"argument.sable.sub_level.modifier.dy": "Sub-levels between y and y + dy",
|
||||
"argument.sable.sub_level.modifier.dz": "Sub-levels between z and z + dz",
|
||||
"argument.sable.sub_level.modifier.vx": "x velocity",
|
||||
"argument.sable.sub_level.modifier.vy": "y velocity",
|
||||
"argument.sable.sub_level.modifier.vz": "z velocity",
|
||||
"argument.sable.sub_level.modifier.speed": "Sub-level speed",
|
||||
"argument.sable.sub_level.modifier.mass": "Sub-level weight",
|
||||
"argument.sable.sub_level.modifier.volume": "Volume of sub-level bounding box",
|
||||
"argument.sable.sub_level.modifier.width": "X axis size of sub-level bounding box",
|
||||
"argument.sable.sub_level.modifier.height": "Y axis size of sub-level bounding box",
|
||||
"argument.sable.sub_level.modifier.length": "Z axis size of sub-level bounding box",
|
||||
"argument.sable.sub_level.modifier.limit": "Maximum number of sub-levels to return",
|
||||
"argument.sable.sub_level.modifier.name": "Sub-level name",
|
||||
"argument.sable.sub_level.modifier.sort": "Sort the sub-levels by distance",
|
||||
"argument.sable.unexpected_end_of_input": "Unexpected end of input",
|
||||
"argument.sable.single_sub_level_required": "Only one sub-level is allowed, but the provided selector allows more than one",
|
||||
"argument.sable.sub_level.invalid": "Invalid sub-level selector",
|
||||
"argument.sable.sub_level.expected_end_of_modifier": "Expected end of modifier",
|
||||
"argument.sable.sub_level.expected_positive_integer": "Expected a positive integer",
|
||||
"argument.sable.sub_level.expected_positive_decimal": "Expected a positive decimal",
|
||||
"argument.sable.sub_level.expected_positive_range": "Expected a positive range",
|
||||
"argument.sable.sub_level.expected_sorting": "Expected sorting type of either nearest or furthest",
|
||||
|
||||
"inspector.sable.sub_level_container.title": "Sub-level Container",
|
||||
|
||||
"attribute.name.player.sub_level_punch_strength": "Push Strength",
|
||||
"attribute.name.player.sub_level_punch_cooldown": "Push Cooldown",
|
||||
|
||||
"options.sable_menu": "Sub-Level Settings... ",
|
||||
"options.physics_steps": "Physics Steps",
|
||||
"options.physics_steps_template": "%s steps / second",
|
||||
"options.physics_steps.tooltip": "How many times the physics simulation is stepped in every second. Higher values will be significantly more performance intensive, but will have higher accuracy.",
|
||||
|
||||
"sub_level.toast.checkLog": "See log for more details",
|
||||
"sub_level.toast.loadFailure": "Failed to load sub-level at %s",
|
||||
"sub_level.toast.saveFailure": "Failed to save sub-level at %s",
|
||||
"sub_level.toast.physicsFailure": "Physics failure for sub-level at %s",
|
||||
"sub_level.toast.attemptingRecovery": "Attempting recovery",
|
||||
|
||||
"camera_type.sub_level_view": "Entering Contraption Camera",
|
||||
"camera_type.sub_level_view_unlocked": "Entering Unlocked Contraption Camera",
|
||||
|
||||
"force_group.sable.gravity": "Gravity",
|
||||
"force_group.sable.drag": "Drag",
|
||||
"force_group.sable.levitation": "Levitation",
|
||||
"force_group.sable.balloon_lift": "Balloon Lift",
|
||||
"force_group.sable.propulsion": "Propulsion",
|
||||
"force_group.sable.lift": "Lift",
|
||||
"force_group.sable.magnetic_force": "Magnetic",
|
||||
|
||||
"schematic.sable.mirror_not_supported": "Cannot mirror schematics containing sub-levels!",
|
||||
|
||||
"death.attack.fall.from_sublevel": "%1$s fell from %2$s",
|
||||
|
||||
"sable.create.mechanical_arm.points_removed_sublevel_and_range": "%1$s selected interaction point(s) removed due to range limitations or not being anchored.",
|
||||
"sable.create.remove.points_removed_sublevel" : "%1$s selected interaction point(s) removed due to not being anchored"
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
layout(location = 0) in vec3 QuadPosition;
|
||||
layout(location = 1) in vec3 SableNormal;
|
||||
layout(location = 2) in uvec2 SableData;
|
||||
|
||||
layout(std140) uniform SableSprites {
|
||||
vec4 sableSprites[2 * SABLE_TEXTURE_CACHE_SIZE];
|
||||
};
|
||||
|
||||
uniform mat4 SableTransform;
|
||||
|
||||
vec3 Position;
|
||||
vec3 Normal;
|
||||
vec4 Color;
|
||||
vec2 UV0;
|
||||
ivec2 UV2;
|
||||
|
||||
void _sable_unpack() {
|
||||
uint vertexIndex = uint(gl_VertexID) & 0x3u;
|
||||
|
||||
// Packed data format:
|
||||
// TTTTTTTTTTTTLLLLLLLLZZZZYYYYXXXX
|
||||
// T = Texture ID
|
||||
// L = Packed Light
|
||||
// Z = Relative Z position
|
||||
// Y = Relative Y position
|
||||
// X = Relative X position
|
||||
uint posX = SableData.x & 15u;
|
||||
uint posY = (SableData.x >> 4) & 15u;
|
||||
uint posZ = (SableData.x >> 8) & 15u;
|
||||
uint packedLight = (SableData.x >> 12) & 255u;
|
||||
uint textureId = SableData.x >> 20u;
|
||||
|
||||
// Packed data format:
|
||||
// AAAAAAAAYYYYYYYYZZZZZZZZXXXXXXXX
|
||||
// A = Ambient Occlusion
|
||||
// Y = Section Y
|
||||
// Z = Section Z
|
||||
// X = Section X
|
||||
uint xOffset = (SableData.y) & 0xFFu;
|
||||
uint yOffset = (SableData.y >> 8) & 0xFFu;
|
||||
uint zOffset = (SableData.y >> 16) & 0xFFu;
|
||||
uint ambientOcclusion = (SableData.y >> (24u + (vertexIndex << 1u))) & 0x3u;
|
||||
|
||||
// 0,0 == 0b00
|
||||
// 0,1 == 0b01
|
||||
// 1,1 == 0b10
|
||||
// 1,0 == 0b11
|
||||
uint lower = uint(gl_VertexID) & 1u;
|
||||
uint upper = (uint(gl_VertexID) >> 1) & 1u;
|
||||
vec2 uv = vec2(float(upper), float(lower ^ upper));
|
||||
uint textureOffset = vertexIndex << 3u;
|
||||
|
||||
vec4 textureU = sableSprites[(textureId << 1u)];
|
||||
vec4 textureV = sableSprites[(textureId << 1u) + 1u];
|
||||
|
||||
Position = (SableTransform * vec4(QuadPosition + vec3(float((xOffset << 4u) + posX), float((yOffset << 4u) + posY), float((zOffset << 4u) + posZ)), 1.0)).xyz;
|
||||
Normal = (SableTransform * vec4(SableNormal, 0.0)).xyz;
|
||||
Color = vec4(1.0, 1.0, 1.0, 1.0) * vec4(vec3(1.0 - 0.2 * float(ambientOcclusion)), 1.0);
|
||||
UV0 = vec2(textureU[vertexIndex], textureV[vertexIndex]);
|
||||
UV2 = ivec2(packedLight & 0xF0u, (packedLight << 4) & 0xF0u);
|
||||
}
|
||||
Reference in New Issue
Block a user