Skip to content

Commit

Permalink
Add shadow casting component for aircraft
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraq committed Jul 21, 2024
1 parent 89fabd5 commit ecaa708
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2024, Emanuel Rabina (http://www.ultraq.net.nz/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package nz.net.ultraq.redhorizon.classic.resources

import nz.net.ultraq.redhorizon.engine.graphics.SpriteMaterial

/**
* A material for drawing a sprite silhouette.
*
* @author Emanuel Rabina
*/
class ShadowMaterial extends SpriteMaterial {
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ package nz.net.ultraq.redhorizon.classic.shaders
class Shaders {

static final PalettedSpriteShader palettedSpriteShader = new PalettedSpriteShader()
static final ShadowShader shadowShader = new ShadowShader()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#version 410 core

in vec4 v_vertexColour;
in vec2 v_textureUVs;

out vec4 fragmentColour;

uniform sampler2D indexTexture;

/**
* Fragment shader main function, emits only a shadow colour for any pixel that
* is not using the 0th palette index.
*/
void main() {

int index = int(texture(indexTexture, v_textureUVs).x * 256);
vec4 colour = vec4(0, 0, 0, index == 0 ? 0 : 0.5);

fragmentColour = colour * v_vertexColour;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#version 410 core

in vec4 position;
in vec4 colour;
in vec2 textureUVs;

out vec4 v_vertexColour;
out vec2 v_textureUVs;

layout (std140) uniform Camera {
mat4 projection;
mat4 view;
};
uniform mat4 model;
layout (std140) uniform SpriteMetadata {
float frameStepX;
float frameStepY;
int framesHorizontal;
int framesVertical;
};
uniform int frame;

/**
* Vertex shader main function, mostly passes geometry information along to the
* fragment shader.
*/
void main() {

gl_Position = projection * view * model * position;
v_vertexColour = colour;

// Adjust textureUVs to the location of the selected frame in the spritesheet
float textureU = (frame % framesHorizontal) * frameStepX;
float textureV = floor(frame / framesHorizontal) * frameStepY;
v_textureUVs = textureUVs + vec2(textureU, textureV);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2024, Emanuel Rabina (http://www.ultraq.net.nz/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package nz.net.ultraq.redhorizon.classic.shaders

import nz.net.ultraq.redhorizon.classic.resources.ShadowMaterial
import nz.net.ultraq.redhorizon.engine.graphics.Attribute
import nz.net.ultraq.redhorizon.engine.graphics.Shader
import nz.net.ultraq.redhorizon.engine.graphics.ShaderConfig
import nz.net.ultraq.redhorizon.engine.graphics.Uniform

/**
* A shader to draw a silhouette of an existing sprite object.
*
* @author Emanuel Rabina
*/
class ShadowShader extends ShaderConfig {

final String name = 'Shadow'
final String vertexShaderSource = getResourceAsText('nz/net/ultraq/redhorizon/classic/shaders/Shadow.vert.glsl')
final String fragmentShaderSource = getResourceAsText('nz/net/ultraq/redhorizon/classic/shaders/Shadow.frag.glsl')
final Attribute[] attributes = [Attribute.POSITION, Attribute.COLOUR, Attribute.TEXTURE_UVS]
final Uniform[] uniforms = [
{ Shader shader, ShadowMaterial material, window ->
shader.setUniformTexture('indexTexture', 0, material.texture)
},
{ Shader shader, ShadowMaterial material, window ->
if (material.spriteMetadataBuffer) {
material.spriteMetadataBuffer.bind()
}
else {
shader.setUniform('framesHorizontal', material.framesHorizontal)
shader.setUniform('framesVertical', material.framesVertical)
shader.setUniform('frameStepX', material.frameStepX)
shader.setUniform('frameStepY', material.frameStepY)
}
shader.setUniform('frame', material.frame)
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ import nz.net.ultraq.redhorizon.classic.Faction
import nz.net.ultraq.redhorizon.classic.nodes.FactionColours
import nz.net.ultraq.redhorizon.classic.nodes.PalettedSprite
import nz.net.ultraq.redhorizon.classic.nodes.Rotatable
import nz.net.ultraq.redhorizon.classic.resources.PalettedSpriteMaterial
import nz.net.ultraq.redhorizon.classic.resources.ShadowMaterial
import nz.net.ultraq.redhorizon.classic.shaders.Shaders
import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRequests.ShaderRequest
import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRequests.SpriteMeshRequest
import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRequests.SpriteSheetRequest
import nz.net.ultraq.redhorizon.engine.graphics.Mesh
import nz.net.ultraq.redhorizon.engine.graphics.Shader
import nz.net.ultraq.redhorizon.engine.graphics.SpriteSheet
import nz.net.ultraq.redhorizon.engine.scenegraph.GraphicsElement
import nz.net.ultraq.redhorizon.engine.scenegraph.Node
Expand All @@ -32,6 +39,9 @@ import nz.net.ultraq.redhorizon.filetypes.ImagesFile
import static nz.net.ultraq.redhorizon.classic.maps.Map.TILE_HEIGHT
import static nz.net.ultraq.redhorizon.classic.maps.Map.TILE_WIDTH

import org.joml.Matrix4f
import org.joml.Vector3f

import java.util.concurrent.CompletableFuture

/**
Expand Down Expand Up @@ -59,6 +69,7 @@ class Unit extends Node<Unit> implements FactionColours, GraphicsElement, Rotata
final UnitTurret turret
UnitBody body2
PalettedSprite bib
UnitShadow shadow

private int stateIndex = 0
private long animationStartTime
Expand Down Expand Up @@ -122,6 +133,17 @@ class Unit extends Node<Unit> implements FactionColours, GraphicsElement, Rotata
addChild(body2)
}

/**
* Add a generated shadow to this unit.
*
* TODO: This is a good candidate for a component in an ECS.
*/
void addShadow() {

shadow = new UnitShadow()
addChild(shadow)
}

/**
* Return the number of degrees it takes to rotate the unit left/right in
* either direction for the current state of the unit.
Expand Down Expand Up @@ -165,8 +187,10 @@ class Unit extends Node<Unit> implements FactionColours, GraphicsElement, Rotata
var bodyRenderCommand = body.renderCommand()
var body2RenderCommand = body2?.renderCommand()
var turretRenderCommand = turret?.renderCommand()
var shadowRenderCommand = shadow?.renderCommand()

return { renderer ->
shadowRenderCommand?.render(renderer)
bibRenderCommand?.render(renderer)
bodyRenderCommand.render(renderer)
body2RenderCommand?.render(renderer)
Expand Down Expand Up @@ -242,6 +266,12 @@ class Unit extends Node<Unit> implements FactionColours, GraphicsElement, Rotata
this.unitData = unitData
}

@Override
PalettedSpriteMaterial getMaterial() {

return super.getMaterial()
}

@Override
void update(float delta) {

Expand Down Expand Up @@ -289,4 +319,65 @@ class Unit extends Node<Unit> implements FactionColours, GraphicsElement, Rotata
super.update(delta)
}
}

/**
* A generated unit shadow. Used mainly for aircraft which draw a silhouette
* of the unit on the ground beneath them.
*/
private class UnitShadow extends Node<UnitShadow> implements GraphicsElement {

private static final Vector3f offset = new Vector3f(0f, -20f, 0f)

final String name = 'Shadow'

private Mesh mesh
private Shader shader

private final Matrix4f transformCopy = new Matrix4f()
private final materialCopy = new ShadowMaterial()

UnitShadow() {

bounds { ->
set(body.bounds)
}
transform { ->
translate(offset)
}
}

@Override
CompletableFuture<Void> onSceneAddedAsync(Scene scene) {

return CompletableFuture.allOf(
scene.requestCreateOrGet(new SpriteMeshRequest(bounds, spriteSheet.textureRegion))
.thenAcceptAsync { newMesh ->
mesh = newMesh
},
scene.requestCreateOrGet(new ShaderRequest(Shaders.shadowShader))
.thenAcceptAsync { shadowShader ->
shader = shadowShader
}
)
}

@Override
CompletableFuture<Void> onSceneRemovedAsync(Scene scene) {

return scene.requestDelete(mesh)
}

@Override
RenderCommand renderCommand() {

transformCopy.set(globalTransform)
materialCopy.copy(body.material)

return { renderer ->
if (mesh && shader) {
renderer.draw(mesh, transformCopy, shader, materialCopy)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class Player extends Node<Player> implements Rotatable, Temporal {
translate(0, 2)
}

unit.addShadow()
addChild(unit)

traverse { Node node ->
Expand Down Expand Up @@ -177,8 +178,13 @@ class Player extends Node<Player> implements Rotatable, Temporal {
// Helicopter bobbing
Executors.newVirtualThreadPerTaskExecutor().execute { ->
while (bobbing) {
var bob = 0.0625 * Math.sin(currentTimeMs / 750)
unit.setPosition(unit.position.x(), unit.position.y() + bob as float, unit.position.z())
var bob = 0.0625 * Math.sin(currentTimeMs / 750) as float
unit.body.transform { ->
translate(0f, bob, 0f)
}
unit.body2.transform { ->
translate(0f, bob, 0f)
}
Thread.sleep(10)
}
}
Expand Down

0 comments on commit ecaa708

Please sign in to comment.