Initial commit
|
@ -0,0 +1,8 @@
|
|||
.vscode
|
||||
build/android/.gradle
|
||||
build/android/app/.cxx
|
||||
build/android/app/build
|
||||
build/android/build
|
||||
build/linux/gltest_x86_64_debug
|
||||
build/linux/gltest_x86_64_release
|
||||
build/linux/obj
|
|
@ -0,0 +1,17 @@
|
|||
A simple, cross-platform 2D game engine with OpenGL renderer written in modern
|
||||
C++. Supports Linux and Android platforms. Compiles both with gcc and clang.
|
||||
|
||||
Build for Linux:
|
||||
|
||||
cd build/linux
|
||||
make
|
||||
|
||||
Build for Android:
|
||||
|
||||
cd build/android
|
||||
./gradlew :app:assembleRelease
|
||||
|
||||
Build for Android and install (debug):
|
||||
|
||||
cd build/android
|
||||
./gradlew :app:installDebug
|
After Width: | Height: | Size: 332 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 156 KiB |
After Width: | Height: | Size: 219 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 32 KiB |
|
@ -0,0 +1,10 @@
|
|||
precision mediump float;
|
||||
|
||||
uniform vec4 color;
|
||||
uniform sampler2D texture;
|
||||
|
||||
varying vec2 tex_coord_0;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(texture, tex_coord_0) * color;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
attribute vec2 in_position;
|
||||
attribute vec2 in_tex_coord_0;
|
||||
|
||||
uniform vec2 scale;
|
||||
uniform vec2 offset;
|
||||
uniform vec2 pivot;
|
||||
uniform vec2 rotation;
|
||||
uniform vec2 tex_offset;
|
||||
uniform vec2 tex_scale;
|
||||
uniform mat4 projection;
|
||||
|
||||
varying vec2 tex_coord_0;
|
||||
|
||||
void main() {
|
||||
// Simple 2d transform.
|
||||
vec2 position = in_position;
|
||||
position *= scale;
|
||||
position += pivot;
|
||||
position = vec2(position.x * rotation.y + position.y * rotation.x,
|
||||
position.y * rotation.y - position.x * rotation.x);
|
||||
position += offset - pivot;
|
||||
|
||||
tex_coord_0 = (in_tex_coord_0 + tex_offset) * tex_scale;
|
||||
|
||||
gl_Position = projection * vec4(position, 0.0, 1.0);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// This creates a normalized unit sized quad.
|
||||
{
|
||||
"primitive": "TriangleStrip",
|
||||
"vertex_description": "p2f;t2f",
|
||||
"num_vertices": 4,
|
||||
"vertices": [-0.5, -0.5, 0.0, 1.0,
|
||||
0.5, -0.5, 1.0, 1.0,
|
||||
-0.5, 0.5, 0.0, 0.0,
|
||||
0.5, 0.5, 1.0, 0.0]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
precision mediump float;
|
||||
|
||||
uniform vec4 color;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
attribute vec2 in_position;
|
||||
attribute vec2 in_tex_coord_0;
|
||||
|
||||
uniform vec2 scale;
|
||||
uniform vec2 offset;
|
||||
uniform vec2 pivot;
|
||||
uniform vec2 rotation;
|
||||
uniform mat4 projection;
|
||||
|
||||
void main() {
|
||||
// Simple 2d transform.
|
||||
vec2 position = in_position;
|
||||
position *= scale;
|
||||
position += pivot;
|
||||
position = vec2(position.x * rotation.y + position.y * rotation.x,
|
||||
position.y * rotation.y - position.x * rotation.x);
|
||||
position += offset - pivot;
|
||||
|
||||
gl_Position = projection * vec4(position, 0.0, 1.0);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
precision mediump float;
|
||||
|
||||
uniform highp vec2 sky_offset;
|
||||
uniform vec3 nebula_color;
|
||||
|
||||
varying highp vec2 tex_coord_0;
|
||||
|
||||
float random(highp vec2 p) {
|
||||
highp float sd = sin(dot(p, vec2(54.90898, 18.233)));
|
||||
return fract(sd * 4337.5453);
|
||||
}
|
||||
|
||||
float nebula(in highp vec2 p) {
|
||||
highp vec2 i = floor(p);
|
||||
highp vec2 f = fract(p);
|
||||
|
||||
float a = random(i);
|
||||
float b = random(i + vec2(1.0, 0.0));
|
||||
float c = random(i + vec2(0.0, 1.0));
|
||||
float d = random(i + vec2(1.0, 1.0));
|
||||
|
||||
vec2 u = smoothstep(0.0, 1.0, f);
|
||||
|
||||
return mix(a, b, u.x) +
|
||||
(c - a)* u.y * (1.0 - u.x) +
|
||||
(d - b) * u.x * u.y;
|
||||
}
|
||||
|
||||
float stars(in highp vec2 p, float num_cells, float size) {
|
||||
highp vec2 n = p * num_cells;
|
||||
highp vec2 i = floor(n);
|
||||
|
||||
vec2 a = n - i - random(i);
|
||||
a /= num_cells * size;
|
||||
float e = dot(a, a);
|
||||
|
||||
return smoothstep(0.95, 1.0, (1.0 - sqrt(e)));
|
||||
}
|
||||
|
||||
void main() {
|
||||
highp vec2 layer1_coord = tex_coord_0 + sky_offset;
|
||||
highp vec2 layer2_coord = tex_coord_0 + sky_offset * 0.7;
|
||||
vec3 result = vec3(0.);
|
||||
|
||||
float c = nebula(layer2_coord * 3.0) * 0.35 - 0.05;
|
||||
result += nebula_color * floor(c * 60.0) / 60.0;
|
||||
|
||||
c = stars(layer1_coord, 8.0, 0.05);
|
||||
result += vec3(0.97, 0.74, 0.74) * c;
|
||||
|
||||
c = stars(layer2_coord, 16.0, 0.025) * 0.5;
|
||||
result += vec3(0.9, 0.9, 0.95) * c;
|
||||
|
||||
gl_FragColor = vec4(result, 1.0);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
attribute vec2 in_position;
|
||||
attribute vec2 in_tex_coord_0;
|
||||
|
||||
uniform vec2 scale;
|
||||
uniform mat4 projection;
|
||||
|
||||
varying vec2 tex_coord_0;
|
||||
|
||||
void main() {
|
||||
// Simple 2d transform.
|
||||
vec2 position = in_position;
|
||||
position *= scale;
|
||||
|
||||
tex_coord_0 = in_tex_coord_0;
|
||||
|
||||
gl_Position = projection * vec4(position, 0.0, 1.0);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
#
|
||||
# Copyright (C) The Android Open Source Project
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
# build native_app_glue as a static lib
|
||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(${CMAKE_C_FLAGS}, "${CMAKE_C_FLAGS} -D_DEBUG")
|
||||
else ()
|
||||
set(${CMAKE_C_FLAGS}, "${CMAKE_C_FLAGS}")
|
||||
endif ()
|
||||
add_library(native_app_glue STATIC
|
||||
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
|
||||
|
||||
# now build app's shared lib
|
||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall -Werror -D_DEBUG")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall -Werror")
|
||||
endif ()
|
||||
|
||||
|
||||
# Export ANativeActivity_onCreate(),
|
||||
# Refer to: https://github.com/android-ndk/ndk/issues/381.
|
||||
set(CMAKE_SHARED_LINKER_FLAGS
|
||||
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
|
||||
|
||||
add_library(native-activity SHARED
|
||||
../../../src/base/asset_file_android.cc
|
||||
../../../src/base/asset_file.cc
|
||||
../../../src/base/collusion_test.cc
|
||||
../../../src/base/log.cc
|
||||
../../../src/base/random.cc
|
||||
../../../src/base/task_runner.cc
|
||||
../../../src/base/timer.cc
|
||||
../../../src/base/vecmath.cc
|
||||
../../../src/base/worker.cc
|
||||
../../../src/demo/credits.cc
|
||||
../../../src/demo/demo.cc
|
||||
../../../src/demo/enemy.cc
|
||||
../../../src/demo/hud.cc
|
||||
../../../src/demo/menu.cc
|
||||
../../../src/demo/player.cc
|
||||
../../../src/demo/sky_quad.cc
|
||||
../../../src/engine/animatable.cc
|
||||
../../../src/engine/animator.cc
|
||||
../../../src/engine/engine.cc
|
||||
../../../src/engine/font.cc
|
||||
../../../src/engine/image_quad.cc
|
||||
../../../src/engine/image.cc
|
||||
../../../src/engine/mesh.cc
|
||||
../../../src/engine/platform/platform_android.cc
|
||||
../../../src/engine/platform/platform.cc
|
||||
../../../src/engine/renderer/geometry.cc
|
||||
../../../src/engine/renderer/render_resource.cc
|
||||
../../../src/engine/renderer/renderer_android.cc
|
||||
../../../src/engine/renderer/renderer_types.cc
|
||||
../../../src/engine/renderer/renderer.cc
|
||||
../../../src/engine/renderer/shader.cc
|
||||
../../../src/engine/renderer/texture.cc
|
||||
../../../src/engine/shader_source.cc
|
||||
../../../src/engine/solid_quad.cc
|
||||
../../../src/third_party/android/gestureDetector.cpp
|
||||
../../../src/third_party/android/gl3stub.c
|
||||
../../../src/third_party/android/GLContext.cpp
|
||||
../../../src/third_party/jsoncpp/jsoncpp.cc
|
||||
../../../src/third_party/minizip/ioapi.c
|
||||
../../../src/third_party/minizip/unzip.c
|
||||
)
|
||||
|
||||
target_include_directories(native-activity PRIVATE
|
||||
${ANDROID_NDK}/sources/android/native_app_glue
|
||||
)
|
||||
|
||||
# add lib dependencies
|
||||
target_link_libraries(native-activity
|
||||
android
|
||||
native_app_glue
|
||||
EGL
|
||||
GLESv2
|
||||
log
|
||||
z)
|
|
@ -0,0 +1,40 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
applicationId = 'com.example.native_activity'
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 28
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments '-DANDROID_STL=c++_static'
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||
'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version '3.10.2'
|
||||
path 'CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
assets.srcDirs = ['../../../assets']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- BEGIN_INCLUDE(manifest) -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.native_activity"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
<!-- This .apk has no Java code itself, so set hasCode to false. -->
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:hasCode="false">
|
||||
|
||||
<!-- Our activity is the built-in NativeActivity framework class.
|
||||
This will take care of integrating with our NDK code. -->
|
||||
<activity android:name="android.app.NativeActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="orientation|keyboardHidden">
|
||||
<!-- Tell NativeActivity the name of our .so -->
|
||||
<meta-data android:name="android.app.lib_name"
|
||||
android:value="native-activity" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
<!-- END_INCLUDE(manifest) -->
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 7.5 KiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">NativeActivity</string>
|
||||
</resources>
|
|
@ -0,0 +1,21 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#Sun Feb 05 19:39:12 IST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
|
@ -0,0 +1,8 @@
|
|||
## This file must *NOT* be checked into Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
#
|
||||
# Location of the SDK. This is only used by Gradle.
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
#Thu Apr 09 18:23:32 CEST 2020
|
||||
sdk.dir=/home/auygun/Android/Sdk
|
|
@ -0,0 +1,2 @@
|
|||
include ':app'
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
.DEFAULT_GOAL := all
|
||||
|
||||
# --- Input variables ---
|
||||
BUILD ?= release
|
||||
ifeq ($(findstring $(BUILD),debug release),)
|
||||
$(error BUILD must be set to debug or release)
|
||||
endif
|
||||
|
||||
# Build all executables by default.
|
||||
APPS ?= gltest
|
||||
|
||||
# If the VERBOSE flag isn't set, then mute superfluous output.
|
||||
ifeq ($(VERBOSE),)
|
||||
HUSH_COMPILE = @echo "Compiling $<";
|
||||
HUSH_LINK = @echo "Linking $@";
|
||||
HUSH_GENERATE = @echo "Generating $@";
|
||||
HUSH_CLEAN = @
|
||||
endif
|
||||
|
||||
# --- Internal variables ---
|
||||
ARCH := $(shell uname -p)
|
||||
SRC_ROOT := $(abspath ../../src)
|
||||
OUTPUT_DIR := $(abspath .)
|
||||
INTERMEDIATE_DIR := $(OUTPUT_DIR)/obj
|
||||
BUILD_DIR := $(INTERMEDIATE_DIR)/$(BUILD)
|
||||
|
||||
ARFLAGS = r
|
||||
LDFLAGS = -lX11 -lGL -lz -pthread
|
||||
|
||||
# Always enable debug information.
|
||||
CFLAGS += -g
|
||||
|
||||
# Flags to generate dependency information.
|
||||
CFLAGS += -MD -MP -MT $@
|
||||
|
||||
# Predefined flags.
|
||||
ifeq ($(BUILD), debug)
|
||||
CFLAGS += -D_DEBUG
|
||||
endif
|
||||
|
||||
# Enable compiler optimizations for everything except debug.
|
||||
# Note that a very aggresssive optimization level is used and it may not be
|
||||
# valid for all standard compliant programs. Reduce this level on individual
|
||||
# files or modules as needed.
|
||||
ifneq ($(BUILD), debug)
|
||||
CFLAGS += -Ofast
|
||||
endif
|
||||
|
||||
# Flag to turn on extended instruction sets for the compiler.
|
||||
CFLAGS += -msse2
|
||||
|
||||
# Let C++ inherit all C flags.
|
||||
CXXFLAGS = $(CFLAGS)
|
||||
|
||||
# Enable C++17
|
||||
CXXFLAGS += -std=c++17
|
||||
|
||||
# --- Internal functions ---
|
||||
app_exe = $(OUTPUT_DIR)/$(1)_$(ARCH)_$(BUILD)
|
||||
objs_from_src = $(patsubst $(SRC_ROOT)/%, $(BUILD_DIR)/%.o, $(basename $(1)))
|
||||
objs_from_src_in = $(call objs_from_src, $(shell find $(1) -name "*.cc" -o -name "*.c"))
|
||||
|
||||
# --- gltest application ---
|
||||
ifneq ($(filter gltest,$(APPS)),)
|
||||
|
||||
GLTEST_SRC := \
|
||||
$(SRC_ROOT)/base/asset_file_linux.cc \
|
||||
$(SRC_ROOT)/base/asset_file.cc \
|
||||
$(SRC_ROOT)/base/collusion_test.cc \
|
||||
$(SRC_ROOT)/base/log.cc \
|
||||
$(SRC_ROOT)/base/random.cc \
|
||||
$(SRC_ROOT)/base/task_runner.cc \
|
||||
$(SRC_ROOT)/base/timer.cc \
|
||||
$(SRC_ROOT)/base/vecmath.cc \
|
||||
$(SRC_ROOT)/base/worker.cc \
|
||||
$(SRC_ROOT)/demo/credits.cc \
|
||||
$(SRC_ROOT)/demo/demo.cc \
|
||||
$(SRC_ROOT)/demo/enemy.cc \
|
||||
$(SRC_ROOT)/demo/hud.cc \
|
||||
$(SRC_ROOT)/demo/menu.cc \
|
||||
$(SRC_ROOT)/demo/player.cc \
|
||||
$(SRC_ROOT)/demo/sky_quad.cc \
|
||||
$(SRC_ROOT)/engine/animatable.cc \
|
||||
$(SRC_ROOT)/engine/animator.cc \
|
||||
$(SRC_ROOT)/engine/engine.cc \
|
||||
$(SRC_ROOT)/engine/font.cc \
|
||||
$(SRC_ROOT)/engine/image_quad.cc \
|
||||
$(SRC_ROOT)/engine/image.cc \
|
||||
$(SRC_ROOT)/engine/mesh.cc \
|
||||
$(SRC_ROOT)/engine/platform/platform_linux.cc \
|
||||
$(SRC_ROOT)/engine/platform/platform.cc \
|
||||
$(SRC_ROOT)/engine/renderer/geometry.cc \
|
||||
$(SRC_ROOT)/engine/renderer/render_resource.cc \
|
||||
$(SRC_ROOT)/engine/renderer/renderer_linux.cc \
|
||||
$(SRC_ROOT)/engine/renderer/renderer_types.cc \
|
||||
$(SRC_ROOT)/engine/renderer/renderer.cc \
|
||||
$(SRC_ROOT)/engine/renderer/shader.cc \
|
||||
$(SRC_ROOT)/engine/renderer/texture.cc \
|
||||
$(SRC_ROOT)/engine/shader_source.cc \
|
||||
$(SRC_ROOT)/engine/solid_quad.cc \
|
||||
$(SRC_ROOT)/third_party/glew/glew.c \
|
||||
$(SRC_ROOT)/third_party/jsoncpp/jsoncpp.cc \
|
||||
$(SRC_ROOT)/third_party/minizip/ioapi.c \
|
||||
$(SRC_ROOT)/third_party/minizip/unzip.c
|
||||
|
||||
GLTEST_EXE := $(call app_exe,gltest)
|
||||
GLTEST_OBJS := $(call objs_from_src, $(GLTEST_SRC))
|
||||
EXES += $(GLTEST_EXE)
|
||||
OBJS += $(GLTEST_OBJS)
|
||||
|
||||
$(GLTEST_EXE): $(GLTEST_OBJS) $(LIBS)
|
||||
|
||||
endif
|
||||
|
||||
|
||||
# --- Build rules ---
|
||||
|
||||
# Dependencies.
|
||||
DEPS = $(OBJS:.o=.d)
|
||||
-include $(DEPS)
|
||||
|
||||
.PHONY: all clean cleanall help
|
||||
|
||||
all: $(EXES)
|
||||
|
||||
clean:
|
||||
@echo "Cleaning..."
|
||||
$(HUSH_CLEAN) $(RM) -r $(BUILD_DIR)
|
||||
|
||||
cleanall:
|
||||
@echo "Cleaning all..."
|
||||
$(HUSH_CLEAN) $(RM) -r $(INTERMEDIATE_DIR)
|
||||
|
||||
help:
|
||||
@echo "BUILD = Build mode. One of:"
|
||||
@echo " debug (no optimizations)"
|
||||
@echo " release (optimizations, the default)"
|
||||
@echo "APPS = Applications to build. Defaults to all."
|
||||
@echo "VERBOSE = Full output from commands if set."
|
||||
|
||||
# It's important that libraries are specified last as Ubuntu uses "ld --as-needed" by default.
|
||||
# Only the static libraries referenced by the object files will be linked into the executable.
|
||||
# Beware that circular dependencies doesn't work with this flag.
|
||||
$(EXES):
|
||||
@mkdir -p $(@D)
|
||||
$(HUSH_LINK) $(CXX) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(BUILD_DIR)/%.a:
|
||||
@mkdir -p $(@D)
|
||||
$(HUSH_GENERATE) $(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_ROOT)/%.c
|
||||
@mkdir -p $(@D)
|
||||
$(HUSH_COMPILE) $(CC) -c $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_ROOT)/%.cc
|
||||
@mkdir -p $(@D)
|
||||
$(HUSH_COMPILE) $(CXX) -c $(CXXFLAGS) -o $@ $<
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Attila Uygun
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,41 @@
|
|||
#include "asset_file.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
std::unique_ptr<char[]> AssetFile::ReadWholeFile(const std::string& file_name,
|
||||
const std::string& root_path,
|
||||
size_t* length,
|
||||
bool null_terminate) {
|
||||
AssetFile file;
|
||||
if (!file.Open(file_name, root_path))
|
||||
return nullptr;
|
||||
|
||||
int size = file.GetSize();
|
||||
if (size == 0)
|
||||
return nullptr;
|
||||
|
||||
// Allocate a new buffer and add space for a null terminator.
|
||||
std::unique_ptr<char[]> buffer =
|
||||
std::make_unique<char[]>(size + (null_terminate ? 1 : 0));
|
||||
|
||||
// Read all of it.
|
||||
int bytesRead = file.Read(buffer.get(), size);
|
||||
if (!bytesRead) {
|
||||
LOG << "Failed to read a buffer of size: " << size << " from file "
|
||||
<< file_name;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Return the buffer size if the caller is interested.
|
||||
if (length)
|
||||
*length = bytesRead;
|
||||
|
||||
// Null terminate the buffer.
|
||||
if (null_terminate)
|
||||
buffer[size] = 0;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef ASSET_FILE_H
|
||||
#define ASSET_FILE_H
|
||||
|
||||
#include "file.h"
|
||||
#if defined(__ANDROID__)
|
||||
#include <zlib.h>
|
||||
#include "../third_party/minizip/unzip.h"
|
||||
#elif defined(__linux__)
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace base {
|
||||
|
||||
class AssetFile {
|
||||
public:
|
||||
AssetFile();
|
||||
~AssetFile();
|
||||
|
||||
bool Open(const std::string& file_name, const std::string& root_path);
|
||||
void Close();
|
||||
|
||||
int GetSize();
|
||||
|
||||
int Read(char* data, size_t size);
|
||||
|
||||
static std::unique_ptr<char[]> ReadWholeFile(const std::string& file_name,
|
||||
const std::string& root_path,
|
||||
size_t* length = 0,
|
||||
bool null_terminate = false);
|
||||
|
||||
private:
|
||||
#if defined(__ANDROID__)
|
||||
unzFile archive_ = 0;
|
||||
size_t uncompressed_size_ = 0;
|
||||
#elif defined(__linux)
|
||||
ScopedFILE file_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // ASSET_FILE_H
|
|
@ -0,0 +1,74 @@
|
|||
#include <assert.h>
|
||||
#include <string>
|
||||
#include "asset_file.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
AssetFile::AssetFile() = default;
|
||||
|
||||
AssetFile::~AssetFile() {
|
||||
Close();
|
||||
}
|
||||
|
||||
bool AssetFile::Open(const std::string& file_name,
|
||||
const std::string& root_path) {
|
||||
do {
|
||||
// Try to open the zip archive.
|
||||
archive_ = unzOpen(root_path.c_str());
|
||||
if (!archive_) {
|
||||
LOG << "Failed to open zip file: " << root_path;
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to find the file.
|
||||
std::string full_name = "assets/" + file_name;
|
||||
if (UNZ_OK != unzLocateFile(archive_, full_name.c_str(), 1)) {
|
||||
LOG << "Failed to locate file in zip archive: " << file_name;
|
||||
break;
|
||||
}
|
||||
|
||||
// Need to get the uncompressed size of the file.
|
||||
unz_file_info info;
|
||||
if (UNZ_OK !=
|
||||
unzGetCurrentFileInfo(archive_, &info, NULL, 0, NULL, 0, NULL, 0)) {
|
||||
LOG << "Failed to get file info: " << file_name;
|
||||
break;
|
||||
}
|
||||
uncompressed_size_ = info.uncompressed_size;
|
||||
|
||||
// Open the current file.
|
||||
if (UNZ_OK != unzOpenCurrentFile(archive_)) {
|
||||
LOG << "Failed to open file: " << file_name;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
void AssetFile::Close() {
|
||||
if (archive_) {
|
||||
// This could potentially be called without having opened a file, but that
|
||||
// should be a harmless nop.
|
||||
unzCloseCurrentFile(archive_);
|
||||
|
||||
unzClose(archive_);
|
||||
archive_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int AssetFile::GetSize() {
|
||||
return uncompressed_size_;
|
||||
}
|
||||
|
||||
int AssetFile::Read(char* data, size_t size) {
|
||||
// Uncompress data into the buffer.
|
||||
int result = unzReadCurrentFile(archive_, data, size);
|
||||
return result < size ? 0 : result;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,41 @@
|
|||
#include <string>
|
||||
#include "asset_file.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
AssetFile::AssetFile() = default;
|
||||
|
||||
AssetFile::~AssetFile() = default;
|
||||
|
||||
bool AssetFile::Open(const std::string& file_name,
|
||||
const std::string& root_path) {
|
||||
std::string full_path = root_path + "assets/" + file_name;
|
||||
file_.reset(fopen(full_path.c_str(), "rb"));
|
||||
return !!file_;
|
||||
}
|
||||
|
||||
void AssetFile::Close() {
|
||||
file_.reset();
|
||||
}
|
||||
|
||||
int AssetFile::GetSize() {
|
||||
int size = 0;
|
||||
|
||||
if (file_) {
|
||||
if (!fseek(file_.get(), 0, SEEK_END)) {
|
||||
size = ftell(file_.get());
|
||||
rewind(file_.get());
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int AssetFile::Read(char* data, size_t size) {
|
||||
if (file_)
|
||||
return fread(data, 1, size, file_.get());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef CLOSURE_H
|
||||
#define CLOSURE_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace base {
|
||||
|
||||
using Closure = std::function<void()>;
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // CLOSURE_H
|
|
@ -0,0 +1,51 @@
|
|||
#include "collusion_test.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace base {
|
||||
|
||||
bool Intersection(const Vector2& center,
|
||||
const Vector2& size,
|
||||
const Vector2& point) {
|
||||
float dx = point.x - center.x;
|
||||
float px = size.x / 2 - fabs(dx);
|
||||
if (px <= 0)
|
||||
return false;
|
||||
|
||||
float dy = point.y - center.y;
|
||||
float py = size.y / 2 - fabs(dy);
|
||||
return py > 0;
|
||||
}
|
||||
|
||||
bool Intersection(const Vector2& center,
|
||||
const Vector2& size,
|
||||
const Vector2& origin,
|
||||
const Vector2& dir) {
|
||||
Vector2 min = center - size / 2;
|
||||
Vector2 max = center + size / 2;
|
||||
|
||||
float tmin = std::numeric_limits<float>::min();
|
||||
float tmax = std::numeric_limits<float>::max();
|
||||
|
||||
if (dir.x != 0.0) {
|
||||
float tx1 = (min.x - origin.x) / dir.x;
|
||||
float tx2 = (max.x - origin.x) / dir.x;
|
||||
|
||||
tmin = std::max(tmin, std::min(tx1, tx2));
|
||||
tmax = std::min(tmax, std::max(tx1, tx2));
|
||||
}
|
||||
|
||||
if (dir.y != 0.0) {
|
||||
float ty1 = (min.y - origin.y) / dir.y;
|
||||
float ty2 = (max.y - origin.y) / dir.y;
|
||||
|
||||
tmin = std::max(tmin, std::min(ty1, ty2));
|
||||
tmax = std::min(tmax, std::max(ty1, ty2));
|
||||
}
|
||||
|
||||
return tmax >= tmin;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef COLLUSION_TEST_H
|
||||
#define COLLUSION_TEST_H
|
||||
|
||||
#include "vecmath.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
// AABB vs point.
|
||||
bool Intersection(const Vector2& center,
|
||||
const Vector2& size,
|
||||
const Vector2& point);
|
||||
|
||||
// Ray-AABB intersection test.
|
||||
// center, size: Center and size of the box.
|
||||
// origin, dir: Origin and direction of the ray.
|
||||
bool Intersection(const Vector2& center,
|
||||
const Vector2& size,
|
||||
const Vector2& origin,
|
||||
const Vector2& dir);
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // COLLUSION_TEST_H
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef FILE_H
|
||||
#define FILE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct ScopedFILECloser {
|
||||
inline void operator()(FILE* x) const {
|
||||
if (x)
|
||||
fclose(x);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
namespace base {
|
||||
|
||||
// Automatically closes file.
|
||||
using ScopedFILE = std::unique_ptr<FILE, internal::ScopedFILECloser>;
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // FILE_H
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef HASH_H
|
||||
#define HASH_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define HHASH(x) base::HornerHash(31, x)
|
||||
|
||||
namespace base {
|
||||
|
||||
// Compile time string hashing function.
|
||||
template <size_t N>
|
||||
constexpr inline size_t HornerHash(size_t prime,
|
||||
const char (&str)[N],
|
||||
size_t Len = N - 1) {
|
||||
return (Len <= 1) ? str[0]
|
||||
: (prime * HornerHash(prime, str, Len - 1) + str[Len - 1]);
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // HASH_H
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef INTERPOLATION_H
|
||||
#define INTERPOLATION_H
|
||||
|
||||
namespace base {
|
||||
|
||||
// Round a float to int.
|
||||
inline int Round(float f) {
|
||||
return int(f + 0.5f);
|
||||
}
|
||||
|
||||
// Linearly interpolate between a and b, by fraction t.
|
||||
template <class T>
|
||||
inline T Lerp(const T& a, const T& b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int Lerp<int>(const int& a, const int& b, float t) {
|
||||
return Round(a + (b - a) * t);
|
||||
}
|
||||
|
||||
inline float SmoothStep(float t) {
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
|
||||
inline float SmootherStep(float t) {
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
// Interpolating spline defined by four control points with the curve drawn only
|
||||
// from 0 to 1 which are p1 and p2 respectively.
|
||||
inline float CatmullRom(float t, float p0, float p3) {
|
||||
return 0.5f * ((-p0 + 1) * t + (2 * p0 + 4 * 1 - p3) * t * t +
|
||||
(-p0 - 3 * 1 + p3) * t * t * t);
|
||||
}
|
||||
|
||||
inline float Acceleration(float t, float w) {
|
||||
return w * t * t + (1 - w) * t;
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // INTERPOLATION_H
|
|
@ -0,0 +1,52 @@
|
|||
#include "log.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
#else
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
|
||||
// This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have
|
||||
// an object of the correct type on the LHS of the unused part of the ternary
|
||||
// operator.
|
||||
Log* Log::swallow_stream;
|
||||
|
||||
Log::Log(const char* file, int line) : file_(file), line_(line) {}
|
||||
|
||||
Log::~Log() {
|
||||
stream_ << std::endl;
|
||||
std::string text(stream_.str());
|
||||
std::string filename(file_);
|
||||
size_t last_slash_pos = filename.find_last_of("\\/");
|
||||
if (last_slash_pos != std::string::npos)
|
||||
filename = filename.substr(last_slash_pos + 1);
|
||||
#if defined(__ANDROID__)
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kaliber", "[%s:%d] %s",
|
||||
filename.c_str(), line_, text.c_str());
|
||||
#else
|
||||
printf("[%s:%d] %s", filename.c_str(), line_, text.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
template <>
|
||||
Log& Log::operator<<<bool>(const bool& arg) {
|
||||
stream_ << (arg ? "true" : "false");
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
Log& Log::operator<<<Vector2>(const Vector2& arg) {
|
||||
stream_ << "(" << arg.x << ", " << arg.y << ")";
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <>
|
||||
Log& Log::operator<<<Vector4>(const Vector4& arg) {
|
||||
stream_ << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", " << arg.w
|
||||
<< ")";
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
|
||||
#include <sstream>
|
||||
#include "vecmath.h"
|
||||
|
||||
#define EAT_STREAM_PARAMETERS \
|
||||
true ? (void)0 : base::Log::Voidify() & (*base::Log::swallow_stream)
|
||||
|
||||
#define LOG base::Log(__FILE__, __LINE__)
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define DLOG base::Log(__FILE__, __LINE__)
|
||||
#else
|
||||
#define DLOG EAT_STREAM_PARAMETERS
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
|
||||
class Log {
|
||||
public:
|
||||
class Voidify {
|
||||
public:
|
||||
Voidify() = default;
|
||||
// This has to be an operator with a precedence lower than << but
|
||||
// higher than ?:
|
||||
void operator&(Log&) {}
|
||||
};
|
||||
|
||||
Log(const char* file, int line);
|
||||
~Log();
|
||||
|
||||
template <typename T>
|
||||
Log& operator<<(const T& arg) {
|
||||
stream_ << arg;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static Log* swallow_stream;
|
||||
|
||||
private:
|
||||
const char* file_;
|
||||
const int line_;
|
||||
std::ostringstream stream_;
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // LOG_H
|
|
@ -0,0 +1,53 @@
|
|||
#ifndef MEM_H
|
||||
#define MEM_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#define ALIGN_MEM(alignment) __attribute__((aligned(alignment)))
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct ScopedAlignedFree {
|
||||
inline void operator()(void* x) const {
|
||||
if (x)
|
||||
free(x);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
namespace base {
|
||||
|
||||
template <typename T>
|
||||
struct AlignedMem {
|
||||
using ScoppedPtr = std::unique_ptr<T, internal::ScopedAlignedFree>;
|
||||
};
|
||||
|
||||
inline void* AlignedAlloc(size_t size) {
|
||||
const size_t kAlignment = 16;
|
||||
|
||||
void* ptr = NULL;
|
||||
#if defined(__ANDROID__)
|
||||
ptr = memalign(kAlignment, size);
|
||||
#else
|
||||
if (posix_memalign(&ptr, kAlignment, size))
|
||||
ptr = NULL;
|
||||
#endif
|
||||
assert(ptr);
|
||||
// assert(((unsigned)ptr & (kAlignment - 1)) == 0);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline void AlignedFree(void* mem) {
|
||||
free(mem);
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // MEM_H
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef MISC_H
|
||||
#define MISC_H
|
||||
|
||||
#define CRASH *((int*)nullptr) = 0;
|
||||
|
||||
namespace base {
|
||||
|
||||
// ToDo: x86 has the bsr instruction.
|
||||
inline int GetHighestBitPos(int value) {
|
||||
return (0xFFFF0000 & value ? value &= 0xFFFF0000, 1 : 0) * 0x10 +
|
||||
(0xFF00FF00 & value ? value &= 0xFF00FF00, 1 : 0) * 0x08 +
|
||||
(0xF0F0F0F0 & value ? value &= 0xF0F0F0F0, 1 : 0) * 0x04 +
|
||||
(0xCCCCCCCC & value ? value &= 0xCCCCCCCC, 1 : 0) * 0x02 +
|
||||
(0xAAAAAAAA & value ? 1 : 0) * 0x01;
|
||||
}
|
||||
|
||||
// Get the highest set bit in an integer number
|
||||
inline int GetHighestBit(int value) {
|
||||
return 0x1 << GetHighestBitPos(value);
|
||||
}
|
||||
|
||||
// Check if the given integer is a power of two, ie if only one bit is set.
|
||||
inline bool IsPow2(int value) {
|
||||
return GetHighestBit(value) == value;
|
||||
// return ((value & (value - 1)) == 0);
|
||||
}
|
||||
|
||||
inline int RoundUpToPow2(int val) {
|
||||
int i = 1 << GetHighestBitPos(val);
|
||||
return val == i ? val : i << 1;
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // MISC_H
|
|
@ -0,0 +1,26 @@
|
|||
#include "random.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "interpolation.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
Random::Random() {
|
||||
std::random_device rd;
|
||||
generator_ = std::mt19937(rd());
|
||||
real_distribution_ = std::uniform_real_distribution<float>(0, 1);
|
||||
}
|
||||
|
||||
Random::Random(unsigned seed) {
|
||||
generator_ = std::mt19937(seed);
|
||||
real_distribution_ = std::uniform_real_distribution<float>(0, 1);
|
||||
}
|
||||
|
||||
Random::~Random() = default;
|
||||
|
||||
int Random::Roll(int min, int max) {
|
||||
return Lerp(min, max, GetFloat());
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef RANDOM_GENERATOR_H
|
||||
#define RANDOM_GENERATOR_H
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace base {
|
||||
|
||||
class Random {
|
||||
public:
|
||||
Random();
|
||||
Random(unsigned seed);
|
||||
~Random();
|
||||
|
||||
// Returns a random float between 0 and 1.
|
||||
float GetFloat() { return real_distribution_(generator_); }
|
||||
|
||||
// Returns a random int between min and max.
|
||||
int Roll(int min, int max);
|
||||
|
||||
private:
|
||||
std::mt19937 generator_;
|
||||
std::uniform_real_distribution<float> real_distribution_;
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // RANDOM_GENERATOR_H
|
|
@ -0,0 +1,30 @@
|
|||
#include "task_runner.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
void TaskRunner::Enqueue(base::Closure task) {
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
thread_tasks_.emplace_back(std::move(task));
|
||||
}
|
||||
|
||||
void TaskRunner::Run() {
|
||||
for (;;) {
|
||||
base::Closure task;
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
if (!thread_tasks_.empty()) {
|
||||
task.swap(thread_tasks_.front());
|
||||
thread_tasks_.pop_front();
|
||||
}
|
||||
}
|
||||
if (!task)
|
||||
break;
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskRunner::IsBoundToCurrentThread() {
|
||||
return thread_id_ == std::this_thread::get_id();
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef TASK_RUNNER_H
|
||||
#define TASK_RUNNER_H
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "closure.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
class TaskRunner {
|
||||
public:
|
||||
TaskRunner() = default;
|
||||
~TaskRunner() = default;
|
||||
|
||||
void Enqueue(base::Closure cb);
|
||||
void Run();
|
||||
|
||||
bool IsBoundToCurrentThread();
|
||||
|
||||
private:
|
||||
std::thread::id thread_id_ = std::this_thread::get_id();
|
||||
std::mutex mutex_;
|
||||
std::deque<base::Closure> thread_tasks_;
|
||||
|
||||
TaskRunner(TaskRunner const&) = delete;
|
||||
TaskRunner& operator=(TaskRunner const&) = delete;
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // TASK_RUNNER_H
|
|
@ -0,0 +1,28 @@
|
|||
#include "timer.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
Timer::Timer() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Timer::Reset() {
|
||||
gettimeofday(&last_time_, nullptr);
|
||||
|
||||
seconds_passed_ = 0.0f;
|
||||
seconds_accumulated_ = 0.0f;
|
||||
}
|
||||
|
||||
void Timer::Update() {
|
||||
timeval currentTime;
|
||||
gettimeofday(¤tTime, nullptr);
|
||||
seconds_passed_ =
|
||||
(float)(currentTime.tv_sec - last_time_.tv_sec) +
|
||||
0.000001f * (float)(currentTime.tv_usec - last_time_.tv_usec);
|
||||
|
||||
last_time_ = currentTime;
|
||||
|
||||
seconds_accumulated_ += seconds_passed_;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef TIMER_H
|
||||
#define TIMER_H
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
namespace base {
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer();
|
||||
~Timer() = default;
|
||||
|
||||
void Reset();
|
||||
|
||||
void Update();
|
||||
|
||||
float GetSecondsPassed() const { return seconds_passed_; }
|
||||
float GetSecondsAccumulated() const { return seconds_accumulated_; }
|
||||
|
||||
private:
|
||||
float seconds_passed_ = 0.0f;
|
||||
float seconds_accumulated_ = 0.0f;
|
||||
|
||||
timeval last_time_;
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // TIMER_H
|
|
@ -0,0 +1,15 @@
|
|||
#include "vecmath.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
Matrix4x4 Ortho(float left, float right, float bottom, float top) {
|
||||
Matrix4x4 m(1);
|
||||
m.c[0].x = 2.0f / (right - left);
|
||||
m.c[1].y = 2.0f / (top - bottom);
|
||||
m.c[2].z = -1.0f;
|
||||
m.c[3].x = -(right + left) / (right - left);
|
||||
m.c[3].y = -(top + bottom) / (top - bottom);
|
||||
return m;
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,163 @@
|
|||
#ifndef VEC_MATH_H
|
||||
#define VEC_MATH_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace base {
|
||||
|
||||
struct Vector2 {
|
||||
float x, y;
|
||||
|
||||
Vector2() {}
|
||||
Vector2(float _x, float _y) : x(_x), y(_y) {}
|
||||
|
||||
float Magnitude() { return sqrt(x * x + y * y); }
|
||||
|
||||
Vector2 Normalize() {
|
||||
float m = Magnitude();
|
||||
x /= m;
|
||||
y /= m;
|
||||
return *this;
|
||||
}
|
||||
|
||||
float DotProduct(const Vector2& v) { return x * v.x + y * v.y; }
|
||||
|
||||
float CrossProduct(const Vector2& v) { return x * v.y - y * v.x; }
|
||||
|
||||
Vector2 operator-() { return Vector2(x * -1.0f, y * -1.0f); }
|
||||
|
||||
Vector2 operator+=(const Vector2& v) {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2 operator-=(const Vector2& v) {
|
||||
x -= v.x;
|
||||
y -= v.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2 operator*=(const Vector2& v) {
|
||||
x *= v.x;
|
||||
y *= v.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2 operator*=(float s) {
|
||||
x *= s;
|
||||
y *= s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2 operator/=(const Vector2& v) {
|
||||
x /= v.x;
|
||||
y /= v.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2 operator/=(float s) {
|
||||
x /= s;
|
||||
y /= s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const float* GetData() const { return &x; }
|
||||
};
|
||||
|
||||
inline Vector2 operator+(const Vector2& v1, const Vector2& v2) {
|
||||
return Vector2(v1.x + v2.x, v1.y + v2.y);
|
||||
}
|
||||
|
||||
inline Vector2 operator-(const Vector2& v1, const Vector2& v2) {
|
||||
return Vector2(v1.x - v2.x, v1.y - v2.y);
|
||||
}
|
||||
|
||||
inline Vector2 operator*(const Vector2& v1, const Vector2& v2) {
|
||||
return Vector2(v1.x * v2.x, v1.y * v2.y);
|
||||
}
|
||||
|
||||
inline Vector2 operator/(const Vector2& v1, const Vector2& v2) {
|
||||
return Vector2(v1.x / v2.x, v1.y / v2.y);
|
||||
}
|
||||
|
||||
inline Vector2 operator*(const Vector2& v, float s) {
|
||||
return Vector2(v.x * s, v.y * s);
|
||||
}
|
||||
|
||||
inline Vector2 operator/(const Vector2& v, float s) {
|
||||
return Vector2(v.x / s, v.y / s);
|
||||
}
|
||||
|
||||
inline bool operator==(const Vector2& v1, const Vector2& v2) {
|
||||
return v1.x == v2.x && v1.y == v2.y;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Vector2& v1, const Vector2& v2) {
|
||||
return v1.x != v2.x || v1.y != v2.y;
|
||||
}
|
||||
|
||||
struct Vector3 {
|
||||
float x, y, z;
|
||||
|
||||
Vector3() {}
|
||||
Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
|
||||
|
||||
const float* GetData() const { return &x; }
|
||||
};
|
||||
|
||||
inline Vector3 operator+(const Vector3& v1, const Vector3& v2) {
|
||||
return Vector3(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
|
||||
}
|
||||
|
||||
struct Vector4 {
|
||||
float x, y, z, w;
|
||||
|
||||
Vector4() {}
|
||||
Vector4(float _x, float _y, float _z, float _w)
|
||||
: x(_x), y(_y), z(_z), w(_w) {}
|
||||
|
||||
Vector4 operator+=(const Vector4& v) {
|
||||
x += v.x;
|
||||
y += v.y;
|
||||
z += v.z;
|
||||
w += v.w;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const float* GetData() const { return &x; }
|
||||
};
|
||||
|
||||
inline Vector4 operator*(const Vector4& v1, const Vector4& v2) {
|
||||
return Vector4(v1.x * v2.x, v2.y * v2.y, v1.z * v2.z, v1.w * v2.w);
|
||||
}
|
||||
|
||||
inline Vector4 operator*(const Vector4& v, float s) {
|
||||
return Vector4(v.x * s, v.y * s, v.z * s, v.w * s);
|
||||
}
|
||||
|
||||
inline Vector4 operator+(const Vector4& v1, const Vector4& v2) {
|
||||
return Vector4(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z, v1.w + v2.w);
|
||||
}
|
||||
|
||||
inline Vector4 operator-(const Vector4& v1, const Vector4& v2) {
|
||||
return Vector4(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z, v1.w - v2.w);
|
||||
}
|
||||
|
||||
struct Matrix4x4 {
|
||||
Vector4 c[4];
|
||||
|
||||
Matrix4x4() {}
|
||||
Matrix4x4(float s)
|
||||
: c{Vector4(s, 0, 0, 0), Vector4(0, s, 0, 0), Vector4(0, 0, s, 0),
|
||||
Vector4(0, 0, 0, s)} {}
|
||||
|
||||
const float* GetData() const { return &c[0].x; }
|
||||
};
|
||||
|
||||
Matrix4x4 Ortho(float left, float right, float bottom, float top);
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // VEC_MATH_H
|
|
@ -0,0 +1,68 @@
|
|||
#include "worker.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
Worker::Worker(unsigned max_concurrency) : max_concurrency_(max_concurrency) {
|
||||
if (max_concurrency_ > std::thread::hardware_concurrency() ||
|
||||
max_concurrency_ == 0) {
|
||||
max_concurrency_ = std::thread::hardware_concurrency();
|
||||
if (max_concurrency_ == 0)
|
||||
max_concurrency_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
Worker::~Worker() = default;
|
||||
|
||||
void Worker::Enqueue(base::Closure task) {
|
||||
if (!active_) {
|
||||
unsigned concurrency = max_concurrency_;
|
||||
while (concurrency--)
|
||||
threads_.emplace_back(&Worker::WorkerMain, this);
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
bool notify;
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
notify = tasks_.empty();
|
||||
tasks_.emplace_back(std::move(task));
|
||||
}
|
||||
if (notify)
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
void Worker::Join() {
|
||||
if (!active_)
|
||||
return;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
quit_when_idle_ = true;
|
||||
}
|
||||
cv_.notify_all();
|
||||
for (auto& thread : threads_)
|
||||
thread.join();
|
||||
threads_.clear();
|
||||
active_ = false;
|
||||
}
|
||||
|
||||
void Worker::WorkerMain() {
|
||||
for (;;) {
|
||||
base::Closure task;
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
while (tasks_.empty()) {
|
||||
if (quit_when_idle_)
|
||||
return;
|
||||
cv_.wait(scoped_lock);
|
||||
}
|
||||
task.swap(tasks_.front());
|
||||
tasks_.pop_front();
|
||||
}
|
||||
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef WORKER_H
|
||||
#define WORKER_H
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include "closure.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
// Feed the worker tasks and they will be called on a thread from the pool.
|
||||
class Worker {
|
||||
public:
|
||||
Worker(unsigned max_concurrency = 0);
|
||||
~Worker();
|
||||
|
||||
void Enqueue(base::Closure task);
|
||||
void Join();
|
||||
|
||||
private:
|
||||
bool active_ = false;
|
||||
unsigned max_concurrency_ = 0;
|
||||
|
||||
std::condition_variable cv_;
|
||||
std::mutex mutex_;
|
||||
std::vector<std::thread> threads_;
|
||||
std::deque<base::Closure> tasks_;
|
||||
bool quit_when_idle_ = false;
|
||||
|
||||
void WorkerMain();
|
||||
|
||||
Worker(Worker const&) = delete;
|
||||
Worker& operator=(Worker const&) = delete;
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // WORKER_H
|
|
@ -0,0 +1,141 @@
|
|||
#include "credits.h"
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "../base/vecmath.h"
|
||||
#include "../base/worker.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/font.h"
|
||||
#include "../engine/image.h"
|
||||
#include "../engine/input_event.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
#include "demo.h"
|
||||
|
||||
using namespace base;
|
||||
using namespace eng;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kCreditsLines[Credits::kNumLines][15] = {
|
||||
"Credits", "Code:", "Attila Uygun", "Graphics:", "Erkan Erturk"};
|
||||
|
||||
constexpr float kLineSpaces[Credits::kNumLines - 1] = {1.5f, 0.5f, 1.5f, 0.5f};
|
||||
|
||||
const Vector4 kTextColor = {0.3f, 0.55f, 1.0f, 1};
|
||||
constexpr float kFadeSpeed = 0.2f;
|
||||
|
||||
} // namespace
|
||||
|
||||
Credits::Credits() = default;
|
||||
|
||||
Credits::~Credits() = default;
|
||||
|
||||
bool Credits::Initialize() {
|
||||
Engine& engine = Engine::Get();
|
||||
|
||||
font_ = engine.GetAsset<Font>("PixelCaps!.ttf");
|
||||
if (!font_)
|
||||
return false;
|
||||
|
||||
max_text_width_ = -1;
|
||||
for (int i = 0; i < kNumLines; ++i) {
|
||||
int width, height;
|
||||
font_->CalculateBoundingBox(kCreditsLines[i], width, height);
|
||||
if (width > max_text_width_)
|
||||
max_text_width_ = width;
|
||||
}
|
||||
|
||||
for (int i = 0; i < kNumLines; ++i)
|
||||
text_animator_.Attach(&text_[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Credits::Update(float delta_time) {
|
||||
text_animator_.Update(delta_time);
|
||||
}
|
||||
|
||||
void Credits::OnInputEvent(std::unique_ptr<InputEvent> event) {
|
||||
if ((event->GetType() == InputEvent::kTap ||
|
||||
event->GetType() == InputEvent::kDragEnd ||
|
||||
event->GetType() == InputEvent::kNavigateBack) &&
|
||||
!text_animator_.IsPlaying(Animator::kBlending)) {
|
||||
Hide();
|
||||
Engine& engine = Engine::Get();
|
||||
static_cast<Demo*>(engine.GetGame())->EnterMenuState();
|
||||
}
|
||||
}
|
||||
|
||||
void Credits::Draw() {
|
||||
for (int i = 0; i < kNumLines; ++i)
|
||||
text_[i].Draw();
|
||||
}
|
||||
|
||||
void Credits::ContextLost() {
|
||||
if (tex_)
|
||||
tex_->Update(CreateImage());
|
||||
}
|
||||
|
||||
void Credits::Show() {
|
||||
tex_ = Engine::Get().CreateRenderResource<Texture>();
|
||||
tex_->Update(CreateImage());
|
||||
|
||||
for (int i = 0; i < kNumLines; ++i) {
|
||||
text_[i].Create(tex_, {1, kNumLines});
|
||||
text_[i].SetOffset({0, 0});
|
||||
text_[i].SetScale({1, 1});
|
||||
text_[i].AutoScale();
|
||||
text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0));
|
||||
text_[i].SetFrame(i);
|
||||
|
||||
if (i > 0) {
|
||||
text_[i].PlaceToBottomOf(text_[i - 1]);
|
||||
text_[i].Translate(text_[i - 1].GetOffset() * Vector2(0, 1));
|
||||
text_[i].Translate({0, text_[i - 1].GetScale().y * -kLineSpaces[i - 1]});
|
||||
}
|
||||
}
|
||||
|
||||
float center_offset_y =
|
||||
(text_[0].GetOffset().y - text_[kNumLines - 1].GetOffset().y) / 2;
|
||||
for (int i = 0; i < kNumLines; ++i)
|
||||
text_[i].Translate({0, center_offset_y});
|
||||
|
||||
text_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
|
||||
text_animator_.SetEndCallback(Animator::kBlending, nullptr);
|
||||
});
|
||||
text_animator_.SetBlending(kTextColor, kFadeSpeed);
|
||||
text_animator_.Play(Animator::kBlending, false);
|
||||
text_animator_.SetVisible(true);
|
||||
}
|
||||
|
||||
void Credits::Hide() {
|
||||
text_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
|
||||
for (int i = 0; i < kNumLines; ++i)
|
||||
text_[i].Destory();
|
||||
tex_.reset();
|
||||
text_animator_.SetEndCallback(Animator::kBlending, nullptr);
|
||||
text_animator_.SetVisible(false);
|
||||
});
|
||||
text_animator_.SetBlending(kTextColor * Vector4(1, 1, 1, 0), kFadeSpeed);
|
||||
text_animator_.Play(Animator::kBlending, false);
|
||||
}
|
||||
|
||||
std::shared_ptr<Image> Credits::CreateImage() {
|
||||
int line_height = font_->GetLineHeight() + 1;
|
||||
auto image = std::make_shared<Image>();
|
||||
image->Create(max_text_width_, line_height * kNumLines);
|
||||
image->Clear({1, 1, 1, 0});
|
||||
|
||||
Worker worker(kNumLines);
|
||||
for (int i = 0; i < kNumLines; ++i) {
|
||||
int w, h;
|
||||
font_->CalculateBoundingBox(kCreditsLines[i], w, h);
|
||||
float x = (image->GetWidth() - w) / 2;
|
||||
float y = line_height * i;
|
||||
worker.Enqueue(std::bind(&Font::Print, font_, x, y, kCreditsLines[i],
|
||||
image->GetBuffer(), image->GetWidth()));
|
||||
}
|
||||
worker.Join();
|
||||
|
||||
image->SetImmutable();
|
||||
return image;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef CREDITS_H
|
||||
#define CREDITS_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../engine/animator.h"
|
||||
#include "../engine/image_quad.h"
|
||||
|
||||
namespace eng {
|
||||
class Image;
|
||||
class InputEvent;
|
||||
class Font;
|
||||
class Texture;
|
||||
} // namespace eng
|
||||
|
||||
class Credits {
|
||||
public:
|
||||
static constexpr int kNumLines = 5;
|
||||
|
||||
Credits();
|
||||
~Credits();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void Update(float delta_time);
|
||||
|
||||
void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
|
||||
|
||||
void Draw();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void Show();
|
||||
void Hide();
|
||||
|
||||
private:
|
||||
std::shared_ptr<eng::Texture> tex_;
|
||||
|
||||
eng::ImageQuad text_[kNumLines];
|
||||
eng::Animator text_animator_;
|
||||
|
||||
std::shared_ptr<const eng::Font> font_;
|
||||
int max_text_width_ = 0;
|
||||
|
||||
std::shared_ptr<eng::Image> CreateImage();
|
||||
};
|
||||
|
||||
#endif // CREDITS_H
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef DAMAGE_TYPE_H
|
||||
#define DAMAGE_TYPE_H
|
||||
|
||||
enum DamageType {
|
||||
kDamageType_Invalid = -1,
|
||||
kDamageType_Green,
|
||||
kDamageType_Blue,
|
||||
kDamageType_Any,
|
||||
kDamageType_Max
|
||||
};
|
||||
|
||||
enum EnemyType {
|
||||
kEnemyType_Invalid = -1,
|
||||
kEnemyType_Skull,
|
||||
kEnemyType_Bug,
|
||||
kEnemyType_Tank,
|
||||
kEnemyType_Max
|
||||
};
|
||||
|
||||
#endif // DAMAGE_TYPE_H
|
|
@ -0,0 +1,248 @@
|
|||
#include "demo.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "../base/random.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/game_factory.h"
|
||||
#include "../engine/input_event.h"
|
||||
|
||||
DECLARE_GAME_BEGIN
|
||||
DECLARE_GAME(Demo)
|
||||
DECLARE_GAME_END
|
||||
|
||||
using namespace base;
|
||||
using namespace eng;
|
||||
|
||||
bool Demo::Initialize() {
|
||||
if (!sky_.Create()) {
|
||||
LOG << "Could not create the sky.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!enemy_.Initialize()) {
|
||||
LOG << "Failed to create the enemy.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!player_.Initialize()) {
|
||||
LOG << "Failed to create the enemy.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hud_.Initialize()) {
|
||||
LOG << "Failed to create the hud.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!menu_.Initialize()) {
|
||||
LOG << "Failed to create the menu.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!credits_.Initialize()) {
|
||||
LOG << "Failed to create the credits.";
|
||||
return false;
|
||||
}
|
||||
|
||||
EnterMenuState();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Demo::Update(float delta_time) {
|
||||
Engine& engine = Engine::Get();
|
||||
|
||||
while (std::unique_ptr<InputEvent> event = engine.GetNextInputEvent()) {
|
||||
if (state_ == kMenu)
|
||||
menu_.OnInputEvent(std::move(event));
|
||||
else if (state_ == kCredits)
|
||||
credits_.OnInputEvent(std::move(event));
|
||||
else
|
||||
player_.OnInputEvent(std::move(event));
|
||||
}
|
||||
|
||||
if (delayed_work_timer_ > 0) {
|
||||
delayed_work_timer_ -= delta_time;
|
||||
if (delayed_work_timer_ <= 0) {
|
||||
base::Closure cb = std::move(delayed_work_cb_);
|
||||
delayed_work_cb_ = nullptr;
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
if (add_score_ > 0) {
|
||||
score_ += add_score_;
|
||||
add_score_ = 0;
|
||||
hud_.PrintScore(score_, true);
|
||||
}
|
||||
|
||||
hud_.Update(delta_time);
|
||||
menu_.Update(delta_time);
|
||||
credits_.Update(delta_time);
|
||||
|
||||
if (state_ == kMenu)
|
||||
UpdateMenuState(delta_time);
|
||||
else if (state_ == kGame)
|
||||
UpdateGameState(delta_time);
|
||||
}
|
||||
|
||||
void Demo::Draw(float frame_frac) {
|
||||
sky_.Draw(frame_frac);
|
||||
player_.Draw(frame_frac);
|
||||
enemy_.Draw(frame_frac);
|
||||
hud_.Draw();
|
||||
menu_.Draw();
|
||||
credits_.Draw();
|
||||
}
|
||||
|
||||
void Demo::ContextLost() {
|
||||
enemy_.ContextLost();
|
||||
player_.ContextLost();
|
||||
hud_.ContextLost();
|
||||
menu_.ContextLost();
|
||||
credits_.ContextLost();
|
||||
sky_.ContextLost();
|
||||
}
|
||||
|
||||
void Demo::LostFocus() {
|
||||
if (state_ == kGame)
|
||||
EnterMenuState();
|
||||
}
|
||||
|
||||
void Demo::GainedFocus() {}
|
||||
|
||||
void Demo::AddScore(int score) {
|
||||
add_score_ += score;
|
||||
}
|
||||
|
||||
void Demo::EnterMenuState() {
|
||||
if (state_ == kMenu)
|
||||
return;
|
||||
if (wave_ == 0) {
|
||||
menu_.SetOptionEnabled(Menu::kContinue, false);
|
||||
} else {
|
||||
menu_.SetOptionEnabled(Menu::kContinue, true);
|
||||
menu_.SetOptionEnabled(Menu::kNewGame, false);
|
||||
}
|
||||
menu_.Show();
|
||||
state_ = kMenu;
|
||||
}
|
||||
|
||||
void Demo::EnterCreditsState() {
|
||||
if (state_ == kCredits)
|
||||
return;
|
||||
credits_.Show();
|
||||
state_ = kCredits;
|
||||
}
|
||||
|
||||
void Demo::EnterGameState() {
|
||||
if (state_ == kGame)
|
||||
return;
|
||||
hud_.Show();
|
||||
state_ = kGame;
|
||||
}
|
||||
|
||||
void Demo::UpdateMenuState(float delta_time) {
|
||||
switch (menu_.selected_option()) {
|
||||
case Menu::kOption_Invalid:
|
||||
break;
|
||||
case Menu::kContinue:
|
||||
menu_.Hide();
|
||||
Continue();
|
||||
break;
|
||||
case Menu::kNewGame:
|
||||
menu_.Hide();
|
||||
StartNewGame();
|
||||
break;
|
||||
case Menu::kCredits:
|
||||
menu_.Hide();
|
||||
EnterCreditsState();
|
||||
break;
|
||||
case Menu::kExit:
|
||||
Engine::Get().Exit();
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Demo::UpdateGameState(float delta_time) {
|
||||
sky_.Update(delta_time);
|
||||
player_.Update(delta_time);
|
||||
enemy_.Update(delta_time);
|
||||
|
||||
if (waiting_for_next_wave_)
|
||||
return;
|
||||
|
||||
if (enemy_.num_enemies_killed_in_current_wave() != last_num_enemies_killed_) {
|
||||
last_num_enemies_killed_ = enemy_.num_enemies_killed_in_current_wave();
|
||||
int enemies_remaining = total_enemies_ - last_num_enemies_killed_;
|
||||
|
||||
if (enemies_remaining <= 0) {
|
||||
waiting_for_next_wave_ = true;
|
||||
hud_.SetProgress(wave_ > 0 ? 0 : 1);
|
||||
|
||||
enemy_.OnWaveFinished();
|
||||
|
||||
SetDelayedWork(1, [&]() -> void {
|
||||
Random& rnd = Engine::Get().GetRandomGenerator();
|
||||
int dominant_channel = rnd.Roll(0, 2);
|
||||
if (dominant_channel == last_dominant_channel_)
|
||||
dominant_channel = (dominant_channel + 1) % 3;
|
||||
last_dominant_channel_ = dominant_channel;
|
||||
|
||||
float weights[3] = {0, 0, 0};
|
||||
weights[dominant_channel] = 1;
|
||||
Vector4 c = {Lerp(0.75f, 0.95f, rnd.GetFloat()) * weights[0],
|
||||
Lerp(0.75f, 0.95f, rnd.GetFloat()) * weights[1],
|
||||
Lerp(0.75f, 0.95f, rnd.GetFloat()) * weights[2], 1};
|
||||
c += {Lerp(0.1f, 0.5f, rnd.GetFloat()) * (1 - weights[0]),
|
||||
Lerp(0.1f, 0.5f, rnd.GetFloat()) * (1 - weights[1]),
|
||||
Lerp(0.1f, 0.5f, rnd.GetFloat()) * (1 - weights[2]), 1};
|
||||
sky_.SwitchColor(c);
|
||||
|
||||
++wave_;
|
||||
hud_.PrintScore(score_, true);
|
||||
hud_.PrintWave(wave_, true);
|
||||
hud_.SetProgress(1);
|
||||
|
||||
float factor = 3 * (log10(5 * (float)wave_) / log10(1.2f)) - 25;
|
||||
total_enemies_ = (int)(6 * factor);
|
||||
last_num_enemies_killed_ = 0;
|
||||
DLOG << "wave: " << wave_ << " total_enemies_: " << total_enemies_;
|
||||
|
||||
enemy_.OnWaveStarted(wave_);
|
||||
|
||||
waiting_for_next_wave_ = false;
|
||||
});
|
||||
} else {
|
||||
hud_.SetProgress((float)enemies_remaining / (float)total_enemies_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Demo::Continue() {
|
||||
EnterGameState();
|
||||
}
|
||||
|
||||
void Demo::StartNewGame() {
|
||||
score_ = 0;
|
||||
add_score_ = 0;
|
||||
wave_ = 0;
|
||||
last_num_enemies_killed_ = -1;
|
||||
total_enemies_ = 0;
|
||||
waiting_for_next_wave_ = false;
|
||||
delayed_work_timer_ = 0;
|
||||
delayed_work_cb_ = nullptr;
|
||||
EnterGameState();
|
||||
}
|
||||
|
||||
void Demo::SetDelayedWork(float seconds, base::Closure cb) {
|
||||
assert(delayed_work_cb_ == nullptr);
|
||||
delayed_work_cb_ = std::move(cb);
|
||||
delayed_work_timer_ = seconds;
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
#ifndef DEMO_H
|
||||
#define DEMO_H
|
||||
|
||||
#include "../base/closure.h"
|
||||
#include "../engine/game.h"
|
||||
#include "credits.h"
|
||||
#include "enemy.h"
|
||||
#include "hud.h"
|
||||
#include "menu.h"
|
||||
#include "player.h"
|
||||
#include "sky_quad.h"
|
||||
|
||||
class Demo : public eng::Game {
|
||||
public:
|
||||
Demo() = default;
|
||||
~Demo() override = default;
|
||||
|
||||
bool Initialize() override;
|
||||
|
||||
void Update(float delta_time) override;
|
||||
|
||||
void Draw(float frame_frac) override;
|
||||
|
||||
void ContextLost() override;
|
||||
|
||||
void LostFocus() override;
|
||||
|
||||
void GainedFocus() override;
|
||||
|
||||
void AddScore(int score);
|
||||
|
||||
void EnterMenuState();
|
||||
void EnterCreditsState();
|
||||
void EnterGameState();
|
||||
|
||||
Player& GetPlayer() { return player_; }
|
||||
Enemy& GetEnemy() { return enemy_; }
|
||||
|
||||
int wave() { return wave_; }
|
||||
|
||||
private:
|
||||
enum State { kState_Invalid = -1, kMenu, kGame, kCredits, kState_Max };
|
||||
|
||||
State state_ = kState_Invalid;
|
||||
|
||||
Player player_;
|
||||
Enemy enemy_;
|
||||
Hud hud_;
|
||||
Menu menu_;
|
||||
Credits credits_;
|
||||
|
||||
SkyQuad sky_;
|
||||
int last_dominant_channel_ = -1;
|
||||
|
||||
int score_ = 0;
|
||||
int add_score_ = 0;
|
||||
|
||||
int wave_ = 0;
|
||||
|
||||
int last_num_enemies_killed_ = -1;
|
||||
int total_enemies_ = 0;
|
||||
|
||||
int waiting_for_next_wave_ = false;
|
||||
|
||||
float delayed_work_timer_ = 0;
|
||||
base::Closure delayed_work_cb_;
|
||||
|
||||
void UpdateMenuState(float delta_time);
|
||||
void UpdateGameState(float delta_time);
|
||||
|
||||
void Continue();
|
||||
void StartNewGame();
|
||||
|
||||
void SetDelayedWork(float seconds, base::Closure cb);
|
||||
};
|
||||
|
||||
#endif // DEMO_H
|
|
@ -0,0 +1,461 @@
|
|||
#include "enemy.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
#include "../base/collusion_test.h"
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/font.h"
|
||||
#include "../engine/image.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
#include "demo.h"
|
||||
|
||||
using namespace base;
|
||||
using namespace eng;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int enemy_frame_start[][3] = {{0, 50, -1},
|
||||
{13, 33, -1},
|
||||
{-1, -1, 100}};
|
||||
constexpr int enemy_frame_count[][3] = {{7, 7, -1}, {6, 6, -1}, {-1, -1, 7}};
|
||||
constexpr int enemy_frame_speed = 12;
|
||||
|
||||
constexpr int enemy_scores[] = {100, 150, 300};
|
||||
|
||||
constexpr float kSpawnPeriod[kEnemyType_Max][2] = {{2, 5},
|
||||
{15, 25},
|
||||
{110, 130}};
|
||||
|
||||
void SetupFadeOutAnim(Animator& animator, float delay) {
|
||||
animator.SetEndCallback(Animator::kTimer, [&]() -> void {
|
||||
animator.SetBlending({1, 1, 1, 0}, 0.5f,
|
||||
std::bind(Acceleration, std::placeholders::_1, -1));
|
||||
animator.Play(Animator::kBlending, false);
|
||||
});
|
||||
animator.SetEndCallback(Animator::kBlending,
|
||||
[&]() -> void { animator.SetVisible(false); });
|
||||
animator.SetTimer(delay);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Enemy::Enemy()
|
||||
: skull_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
bug_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
target_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
blast_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
score_tex_{Engine::Get().CreateRenderResource<Texture>(),
|
||||
Engine::Get().CreateRenderResource<Texture>(),
|
||||
Engine::Get().CreateRenderResource<Texture>()} {}
|
||||
|
||||
Enemy::~Enemy() = default;
|
||||
|
||||
bool Enemy::Initialize() {
|
||||
font_ = Engine::Get().GetAsset<Font>("PixelCaps!.ttf");
|
||||
if (!font_)
|
||||
return false;
|
||||
|
||||
return CreateRenderResources();
|
||||
}
|
||||
|
||||
void Enemy::ContextLost() {
|
||||
CreateRenderResources();
|
||||
}
|
||||
|
||||
void Enemy::Update(float delta_time) {
|
||||
if (!waiting_for_next_wave_) {
|
||||
if (spawn_factor_interpolator_ < 1) {
|
||||
spawn_factor_interpolator_ += delta_time * 0.1f;
|
||||
if (spawn_factor_interpolator_ > 1)
|
||||
spawn_factor_interpolator_ = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < kEnemyType_Max; ++i)
|
||||
seconds_since_last_spawn_[i] += delta_time;
|
||||
|
||||
SpawnNextEnemy();
|
||||
}
|
||||
|
||||
for (auto it = enemies_.begin(); it != enemies_.end(); ++it) {
|
||||
if (it->marked_for_removal) {
|
||||
it = enemies_.erase(it);
|
||||
continue;
|
||||
}
|
||||
it->sprite_animator.Update(delta_time);
|
||||
it->target_animator.Update(delta_time);
|
||||
it->blast_animator.Update(delta_time);
|
||||
it->health_animator.Update(delta_time);
|
||||
it->score_animator.Update(delta_time);
|
||||
it->movement_animator.Update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
void Enemy::Draw(float frame_frac) {
|
||||
for (auto& e : enemies_) {
|
||||
e.sprite.Draw();
|
||||
e.target.Draw();
|
||||
e.blast.Draw();
|
||||
e.health_base.Draw();
|
||||
e.health_bar.Draw();
|
||||
e.score.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
bool Enemy::HasTarget(DamageType damage_type) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
return GetTarget(damage_type) ? true : false;
|
||||
}
|
||||
|
||||
Vector2 Enemy::GetTargetPos(DamageType damage_type) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
EnemyUnit* target = GetTarget(damage_type);
|
||||
if (target)
|
||||
return target->sprite.GetOffset() -
|
||||
Vector2(0, target->sprite.GetScale().y / 2.5f);
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
void Enemy::SelectTarget(DamageType damage_type,
|
||||
const Vector2& origin,
|
||||
const Vector2& dir,
|
||||
float snap_factor) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
if (waiting_for_next_wave_)
|
||||
return;
|
||||
|
||||
EnemyUnit* best_enemy = nullptr;
|
||||
|
||||
float closest_dist = std::numeric_limits<float>::max();
|
||||
for (auto& e : enemies_) {
|
||||
if (e.hit_points <= 0 || e.marked_for_removal)
|
||||
continue;
|
||||
|
||||
if (e.targetted_by_weapon_ == damage_type) {
|
||||
e.targetted_by_weapon_ = kDamageType_Invalid;
|
||||
e.target.SetVisible(false);
|
||||
e.target_animator.Stop(Animator::kAllAnimations);
|
||||
}
|
||||
|
||||
if (!base::Intersection(e.sprite.GetOffset(),
|
||||
e.sprite.GetScale() * snap_factor,
|
||||
origin, dir))
|
||||
continue;
|
||||
|
||||
Vector2 weapon_enemy_dir = e.sprite.GetOffset() - origin;
|
||||
float enemy_weapon_dist = weapon_enemy_dir.Magnitude();
|
||||
if (closest_dist > enemy_weapon_dist) {
|
||||
closest_dist = enemy_weapon_dist;
|
||||
best_enemy = &e;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_enemy) {
|
||||
best_enemy->targetted_by_weapon_ = damage_type;
|
||||
best_enemy->target.SetVisible(true);
|
||||
if (damage_type == kDamageType_Green) {
|
||||
best_enemy->target.SetFrame(0);
|
||||
best_enemy->target_animator.SetFrames(6, 28);
|
||||
} else {
|
||||
best_enemy->target.SetFrame(6);
|
||||
best_enemy->target_animator.SetFrames(6, 28);
|
||||
}
|
||||
best_enemy->target_animator.Play(Animator::kFrames, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Enemy::DeselectTarget(DamageType damage_type) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
EnemyUnit* target = GetTarget(damage_type);
|
||||
if (target) {
|
||||
target->targetted_by_weapon_ = kDamageType_Invalid;
|
||||
target->target.SetVisible(false);
|
||||
target->target_animator.Stop(Animator::kAllAnimations);
|
||||
}
|
||||
}
|
||||
|
||||
void Enemy::HitTarget(DamageType damage_type) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
if (waiting_for_next_wave_)
|
||||
return;
|
||||
|
||||
EnemyUnit* target = GetTarget(damage_type);
|
||||
|
||||
if (target) {
|
||||
target->target.SetVisible(false);
|
||||
target->target_animator.Stop(Animator::kAllAnimations);
|
||||
}
|
||||
|
||||
if (!target || (target->damage_type != kDamageType_Any &&
|
||||
target->damage_type != damage_type))
|
||||
return;
|
||||
|
||||
TakeDamage(target, 1);
|
||||
}
|
||||
|
||||
void Enemy::OnWaveFinished() {
|
||||
for (auto& e : enemies_) {
|
||||
if (!e.marked_for_removal && e.hit_points > 0)
|
||||
e.movement_animator.Pause(Animator::kMovement);
|
||||
}
|
||||
waiting_for_next_wave_ = true;
|
||||
}
|
||||
|
||||
void Enemy::OnWaveStarted(int wave) {
|
||||
for (auto& e : enemies_) {
|
||||
if (!e.marked_for_removal && e.hit_points > 0) {
|
||||
if (wave == 1)
|
||||
e.marked_for_removal = true;
|
||||
else
|
||||
TakeDamage(&e, 100);
|
||||
}
|
||||
}
|
||||
num_enemies_killed_in_current_wave_ = 0;
|
||||
seconds_since_last_spawn_ = {0, 0, 0};
|
||||
seconds_to_next_spawn_ = {0, 0, 0};
|
||||
spawn_factor_ = 1 / (log10(0.25f * (wave + 4) + 1.468f) * 6);
|
||||
spawn_factor_interpolator_ = 0;
|
||||
waiting_for_next_wave_ = false;
|
||||
}
|
||||
|
||||
void Enemy::TakeDamage(EnemyUnit* target, int damage) {
|
||||
assert(!target->marked_for_removal);
|
||||
assert(target->hit_points > 0);
|
||||
|
||||
target->blast.SetVisible(true);
|
||||
target->blast_animator.Play(Animator::kFrames, false);
|
||||
|
||||
target->hit_points -= damage;
|
||||
if (target->hit_points <= 0) {
|
||||
if (!waiting_for_next_wave_)
|
||||
++num_enemies_killed_in_current_wave_;
|
||||
|
||||
target->sprite.SetVisible(false);
|
||||
target->health_base.SetVisible(false);
|
||||
target->health_bar.SetVisible(false);
|
||||
target->score.SetVisible(true);
|
||||
|
||||
target->score_animator.Play(Animator::kTimer | Animator::kMovement, false);
|
||||
target->movement_animator.Pause(Animator::kMovement);
|
||||
|
||||
Engine& engine = Engine::Get();
|
||||
Demo* game = static_cast<Demo*>(engine.GetGame());
|
||||
game->AddScore(GetScore(target->enemy_type));
|
||||
} else {
|
||||
target->targetted_by_weapon_ = kDamageType_Invalid;
|
||||
|
||||
Vector2 s = target->sprite.GetScale() * Vector2(0.6f, 0.01f);
|
||||
s.x *= (float)target->hit_points / (float)target->total_health;
|
||||
float t = (s.x - target->health_bar.GetScale().x) / 2;
|
||||
target->health_bar.SetScale(s);
|
||||
target->health_bar.Translate({t, 0});
|
||||
|
||||
target->health_base.SetVisible(true);
|
||||
target->health_bar.SetVisible(true);
|
||||
|
||||
target->health_animator.Stop(Animator::kTimer | Animator::kBlending);
|
||||
target->health_animator.Play(Animator::kTimer, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Enemy::SpawnNextEnemy() {
|
||||
Engine& engine = Engine::Get();
|
||||
Random& rnd = engine.GetRandomGenerator();
|
||||
|
||||
float factor = Lerp(1.0f, spawn_factor_, spawn_factor_interpolator_);
|
||||
EnemyType enemy_type = kEnemyType_Invalid;
|
||||
|
||||
for (int i = 0; i < kEnemyType_Max; ++i) {
|
||||
if (seconds_since_last_spawn_[i] >= seconds_to_next_spawn_[i]) {
|
||||
if (seconds_to_next_spawn_[i] > 0)
|
||||
enemy_type = (EnemyType)i;
|
||||
|
||||
seconds_since_last_spawn_[i] = 0;
|
||||
seconds_to_next_spawn_[i] =
|
||||
Lerp(kSpawnPeriod[i][0] * factor, kSpawnPeriod[i][1] * factor,
|
||||
rnd.GetFloat());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (enemy_type == kEnemyType_Invalid)
|
||||
return;
|
||||
|
||||
DamageType damage_type = enemy_type == kEnemyType_Tank
|
||||
? kDamageType_Any
|
||||
: (DamageType)(rnd.Roll(0, 1));
|
||||
|
||||
Vector2 s = engine.GetScreenSize();
|
||||
int col;
|
||||
col = rnd.Roll(0, 3);
|
||||
if (col == last_spawn_col_)
|
||||
col = (col + 1) % 4;
|
||||
last_spawn_col_ = col;
|
||||
float x = (s.x / 4) / 2 + (s.x / 4) * col - s.x / 2;
|
||||
Vector2 pos = {x, s.y / 2};
|
||||
float speed = enemy_type == kEnemyType_Tank
|
||||
? 36.0f
|
||||
: (rnd.Roll(1, 4) == 4 ? 6.0f : 10.0f);
|
||||
|
||||
Spawn(enemy_type, damage_type, pos, speed);
|
||||
}
|
||||
|
||||
void Enemy::Spawn(EnemyType enemy_type,
|
||||
DamageType damage_type,
|
||||
const Vector2& pos,
|
||||
float speed) {
|
||||
assert(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Max);
|
||||
|
||||
Engine& engine = Engine::Get();
|
||||
Demo* game = static_cast<Demo*>(engine.GetGame());
|
||||
|
||||
auto& e = enemies_.emplace_back();
|
||||
e.enemy_type = enemy_type;
|
||||
e.damage_type = damage_type;
|
||||
if (enemy_type == kEnemyType_Skull) {
|
||||
e.total_health = e.hit_points = 1;
|
||||
e.sprite.Create(skull_tex_, {10, 13}, 100, 100);
|
||||
} else if (enemy_type == kEnemyType_Bug) {
|
||||
e.total_health = e.hit_points = 2;
|
||||
e.sprite.Create(bug_tex_, {10, 4});
|
||||
} else { // kEnemyType_Tank
|
||||
e.total_health = e.hit_points = 6;
|
||||
e.sprite.Create(skull_tex_, {10, 13}, 100, 100);
|
||||
}
|
||||
e.sprite.AutoScale();
|
||||
e.sprite.SetVisible(true);
|
||||
Vector2 spawn_pos = pos + Vector2(0, e.sprite.GetScale().y / 2);
|
||||
e.sprite.SetOffset(spawn_pos);
|
||||
|
||||
e.sprite.SetFrame(enemy_frame_start[enemy_type][damage_type]);
|
||||
e.sprite_animator.SetFrames(enemy_frame_count[enemy_type][damage_type],
|
||||
enemy_frame_speed);
|
||||
|
||||
e.sprite_animator.Attach(&e.sprite);
|
||||
e.sprite_animator.Play(Animator::kFrames, true);
|
||||
|
||||
e.target.Create(target_tex_, {6, 2});
|
||||
e.target.AutoScale();
|
||||
e.target.SetOffset(spawn_pos);
|
||||
|
||||
e.blast.Create(blast_tex_, {6, 2});
|
||||
e.blast.AutoScale();
|
||||
e.blast.SetOffset(spawn_pos);
|
||||
|
||||
e.health_base.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
|
||||
e.health_base.SetOffset(spawn_pos);
|
||||
e.health_base.PlaceToBottomOf(e.sprite);
|
||||
e.health_base.SetColor({0.5f, 0.5f, 0.5f, 1});
|
||||
|
||||
e.health_bar.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
|
||||
e.health_bar.SetOffset(spawn_pos);
|
||||
e.health_bar.PlaceToBottomOf(e.sprite);
|
||||
e.health_bar.SetColor({0.161f, 0.89f, 0.322f, 1});
|
||||
|
||||
e.score.Create(score_tex_[e.enemy_type]);
|
||||
e.score.AutoScale();
|
||||
e.score.SetColor({1, 1, 1, 1});
|
||||
e.score.SetOffset(spawn_pos);
|
||||
|
||||
e.target_animator.Attach(&e.target);
|
||||
|
||||
e.blast_animator.SetEndCallback(Animator::kFrames,
|
||||
[&]() -> void { e.blast.SetVisible(false); });
|
||||
if (damage_type == kDamageType_Green) {
|
||||
e.blast.SetFrame(0);
|
||||
e.blast_animator.SetFrames(6, 28);
|
||||
} else {
|
||||
e.blast.SetFrame(6);
|
||||
e.blast_animator.SetFrames(6, 28);
|
||||
}
|
||||
e.blast_animator.Attach(&e.blast);
|
||||
|
||||
SetupFadeOutAnim(e.health_animator, 1);
|
||||
e.health_animator.Attach(&e.health_base);
|
||||
e.health_animator.Attach(&e.health_bar);
|
||||
|
||||
SetupFadeOutAnim(e.score_animator, 0.2f);
|
||||
e.score_animator.SetMovement({0, engine.GetScreenSize().y / 2}, 2.0f);
|
||||
e.score_animator.SetEndCallback(
|
||||
Animator::kMovement, [&]() -> void { e.marked_for_removal = true; });
|
||||
e.score_animator.Attach(&e.score);
|
||||
|
||||
float max_distance =
|
||||
engine.GetScreenSize().y - game->GetPlayer().GetWeaponScale().y / 2;
|
||||
|
||||
e.movement_animator.SetMovement(
|
||||
{0, -max_distance}, speed,
|
||||
std::bind(Acceleration, std::placeholders::_1, -0.15f));
|
||||
e.movement_animator.SetEndCallback(Animator::kMovement, [&]() -> void {
|
||||
e.sprite.SetVisible(false);
|
||||
e.target.SetVisible(false);
|
||||
e.blast.SetVisible(false);
|
||||
e.marked_for_removal = true;
|
||||
});
|
||||
e.movement_animator.Attach(&e.sprite);
|
||||
e.movement_animator.Attach(&e.target);
|
||||
e.movement_animator.Attach(&e.blast);
|
||||
e.movement_animator.Attach(&e.health_base);
|
||||
e.movement_animator.Attach(&e.health_bar);
|
||||
e.movement_animator.Attach(&e.score);
|
||||
e.movement_animator.Play(Animator::kMovement, false);
|
||||
}
|
||||
|
||||
Enemy::EnemyUnit* Enemy::GetTarget(DamageType damage_type) {
|
||||
for (auto& e : enemies_) {
|
||||
if (e.targetted_by_weapon_ == damage_type && e.hit_points > 0 &&
|
||||
!e.marked_for_removal)
|
||||
return &e;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Enemy::GetScore(EnemyType enemy_type) {
|
||||
assert(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
|
||||
return enemy_scores[enemy_type];
|
||||
}
|
||||
|
||||
std::shared_ptr<Image> Enemy::GetScoreImage(int score) {
|
||||
std::string text = std::to_string(score);
|
||||
int width, height;
|
||||
font_->CalculateBoundingBox(text.c_str(), width, height);
|
||||
|
||||
auto image = std::make_shared<Image>();
|
||||
image->Create(width, height);
|
||||
image->Clear({1, 1, 1, 0});
|
||||
|
||||
font_->Print(0, 0, text.c_str(), image->GetBuffer(), image->GetWidth());
|
||||
|
||||
image->SetImmutable();
|
||||
return image;
|
||||
}
|
||||
|
||||
bool Enemy::CreateRenderResources() {
|
||||
Engine& engine = Engine::Get();
|
||||
|
||||
auto skull_image = engine.GetAsset<Image>("enemy_anims_01_frames_ok.png");
|
||||
auto bug_image = engine.GetAsset<Image>("enemy_anims_02_frames_ok.png");
|
||||
auto target_image = engine.GetAsset<Image>("enemy_target_single_ok.png");
|
||||
auto blast_image = engine.GetAsset<Image>("enemy_anims_blast_ok.png");
|
||||
if (!skull_image || !bug_image || !target_image || !blast_image)
|
||||
return false;
|
||||
|
||||
skull_tex_->Update(skull_image);
|
||||
bug_tex_->Update(bug_image);
|
||||
target_tex_->Update(target_image);
|
||||
blast_tex_->Update(blast_image);
|
||||
|
||||
for (int i = 0; i < kEnemyType_Max; ++i)
|
||||
score_tex_[i]->Update(GetScoreImage(GetScore((EnemyType)i)));
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
#ifndef ENEMY_H
|
||||
#define ENEMY_H
|
||||
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "../base/vecmath.h"
|
||||
#include "../engine/animator.h"
|
||||
#include "../engine/image_quad.h"
|
||||
#include "../engine/solid_quad.h"
|
||||
#include "damage_type.h"
|
||||
|
||||
namespace eng {
|
||||
class Image;
|
||||
class Font;
|
||||
class Texture;
|
||||
} // namespace eng
|
||||
|
||||
class Enemy {
|
||||
public:
|
||||
Enemy();
|
||||
~Enemy();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void Update(float delta_time);
|
||||
|
||||
void Draw(float frame_frac);
|
||||
|
||||
bool HasTarget(DamageType damage_type);
|
||||
base::Vector2 GetTargetPos(DamageType damage_type);
|
||||
|
||||
void SelectTarget(DamageType damage_type,
|
||||
const base::Vector2& origin,
|
||||
const base::Vector2& dir,
|
||||
float snap_factor);
|
||||
void DeselectTarget(DamageType damage_type);
|
||||
|
||||
void HitTarget(DamageType damage_type);
|
||||
|
||||
void OnWaveFinished();
|
||||
void OnWaveStarted(int wave);
|
||||
|
||||
int num_enemies_killed_in_current_wave() {
|
||||
return num_enemies_killed_in_current_wave_;
|
||||
}
|
||||
|
||||
private:
|
||||
struct EnemyUnit {
|
||||
EnemyType enemy_type = kEnemyType_Invalid;
|
||||
DamageType damage_type = kDamageType_Invalid;
|
||||
|
||||
bool marked_for_removal = false;
|
||||
DamageType targetted_by_weapon_ = kDamageType_Invalid;
|
||||
int total_health = 0;
|
||||
int hit_points = 0;
|
||||
|
||||
eng::ImageQuad sprite;
|
||||
eng::ImageQuad target;
|
||||
eng::ImageQuad blast;
|
||||
eng::ImageQuad score;
|
||||
eng::SolidQuad health_base;
|
||||
eng::SolidQuad health_bar;
|
||||
|
||||
eng::Animator movement_animator;
|
||||
eng::Animator sprite_animator;
|
||||
eng::Animator target_animator;
|
||||
eng::Animator blast_animator;
|
||||
eng::Animator health_animator;
|
||||
eng::Animator score_animator;
|
||||
};
|
||||
|
||||
std::shared_ptr<eng::Texture> skull_tex_;
|
||||
std::shared_ptr<eng::Texture> bug_tex_;
|
||||
std::shared_ptr<eng::Texture> target_tex_;
|
||||
std::shared_ptr<eng::Texture> blast_tex_;
|
||||
std::shared_ptr<eng::Texture> score_tex_[kEnemyType_Max];
|
||||
|
||||
std::shared_ptr<const eng::Font> font_;
|
||||
|
||||
std::list<EnemyUnit> enemies_;
|
||||
|
||||
int num_enemies_killed_in_current_wave_ = 0;
|
||||
|
||||
std::array<float, kEnemyType_Max> seconds_since_last_spawn_ = {0, 0, 0};
|
||||
std::array<float, kEnemyType_Max> seconds_to_next_spawn_ = {0, 0, 0};
|
||||
|
||||
float spawn_factor_ = 0;
|
||||
float spawn_factor_interpolator_ = 0;
|
||||
|
||||
bool waiting_for_next_wave_ = false;
|
||||
|
||||
int last_spawn_col_ = 0;
|
||||
|
||||
void TakeDamage(EnemyUnit* target, int damage);
|
||||
|
||||
void SpawnNextEnemy();
|
||||
|
||||
void Spawn(EnemyType enemy_type,
|
||||
DamageType damage_type,
|
||||
const base::Vector2& pos,
|
||||
float speed);
|
||||
|
||||
EnemyUnit* GetTarget(DamageType damage_type);
|
||||
|
||||
int GetScore(EnemyType enemy_type);
|
||||
|
||||
std::shared_ptr<eng::Image> GetScoreImage(int score);
|
||||
|
||||
bool CreateRenderResources();
|
||||
};
|
||||
|
||||
#endif // ENEMY_H
|
|
@ -0,0 +1,164 @@
|
|||
#include "hud.h"
|
||||
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "../base/vecmath.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/font.h"
|
||||
#include "../engine/image.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
|
||||
using namespace base;
|
||||
using namespace eng;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kHorizontalMargin = 0.07f;
|
||||
constexpr float kVerticalMargin = 0.025f;
|
||||
|
||||
const Vector4 kPprogressBarColor[2] = {{0.256f, 0.434f, 0.72f, 1},
|
||||
{0.905f, 0.493f, 0.194f, 1}};
|
||||
const Vector4 kTextColor = {0.895f, 0.692f, 0.24f, 1};
|
||||
|
||||
} // namespace
|
||||
|
||||
Hud::Hud() {
|
||||
text_[0].Create(Engine::Get().CreateRenderResource<Texture>());
|
||||
text_[1].Create(Engine::Get().CreateRenderResource<Texture>());
|
||||
}
|
||||
|
||||
Hud::~Hud() = default;
|
||||
|
||||
bool Hud::Initialize() {
|
||||
Engine& engine = Engine::Get();
|
||||
|
||||
font_ = engine.GetAsset<Font>("PixelCaps!.ttf");
|
||||
if (!font_)
|
||||
return false;
|
||||
|
||||
int tmp;
|
||||
font_->CalculateBoundingBox("big_enough_text", max_text_width_, tmp);
|
||||
|
||||
auto image = CreateImage();
|
||||
image->SetImmutable();
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
text_[i].GetTexture()->Update(image);
|
||||
text_[i].AutoScale();
|
||||
text_[i].SetColor(kTextColor);
|
||||
|
||||
Vector2 pos = (engine.GetScreenSize() / 2 - text_[i].GetScale() / 2);
|
||||
pos -= engine.GetScreenSize() * Vector2(kHorizontalMargin, kVerticalMargin);
|
||||
|
||||
Vector2 scale = engine.GetScreenSize() * Vector2(1, 0);
|
||||
scale -= engine.GetScreenSize() * Vector2(kHorizontalMargin * 4, 0);
|
||||
scale += text_[0].GetScale() * Vector2(0, 0.3f);
|
||||
|
||||
progress_bar_[i].Scale(scale);
|
||||
progress_bar_[i].Translate(pos * Vector2(0, 1));
|
||||
progress_bar_[i].SetColor(kPprogressBarColor[i] * Vector4(1, 1, 1, 0));
|
||||
|
||||
pos -= progress_bar_[i].GetScale() * Vector2(0, 4);
|
||||
text_[i].Translate(pos * Vector2(i ? 1 : -1, 1));
|
||||
|
||||
progress_bar_animator_[i].Attach(&progress_bar_[i]);
|
||||
|
||||
text_animator_cb_[i] = [&, i]() -> void {
|
||||
text_animator_[i].SetEndCallback(Animator::kBlending, nullptr);
|
||||
text_animator_[i].SetBlending(
|
||||
kTextColor, 2, std::bind(Acceleration, std::placeholders::_1, -1));
|
||||
text_animator_[i].Play(Animator::kBlending, false);
|
||||
};
|
||||
text_animator_[i].Attach(&text_[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Hud::Update(float delta_time) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
text_animator_[i].Update(delta_time);
|
||||
progress_bar_animator_[i].Update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
void Hud::Draw() {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
progress_bar_[i].Draw();
|
||||
text_[i].Draw();
|
||||
}
|
||||
}
|
||||
|
||||
void Hud::ContextLost() {
|
||||
PrintScore(last_score_, false);
|
||||
PrintWave(last_wave_, false);
|
||||
}
|
||||
|
||||
void Hud::Show() {
|
||||
if (text_[0].IsVisible())
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
progress_bar_[i].SetVisible(true);
|
||||
text_[i].SetVisible(true);
|
||||
progress_bar_animator_[i].SetBlending(kPprogressBarColor[i], 0.3f);
|
||||
progress_bar_animator_[i].Play(Animator::kBlending, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Hud::PrintScore(int score, bool flash) {
|
||||
last_score_ = score;
|
||||
Print(0, std::to_string(score));
|
||||
|
||||
if (flash) {
|
||||
text_animator_[0].SetEndCallback(Animator::kBlending, text_animator_cb_[0]);
|
||||
text_animator_[0].SetBlending(
|
||||
{1, 1, 1, 1}, 0.1f, std::bind(Acceleration, std::placeholders::_1, 1));
|
||||
text_animator_[0].Play(Animator::kBlending, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Hud::PrintWave(int wave, bool flash) {
|
||||
last_wave_ = wave;
|
||||
std::string text = "wave ";
|
||||
text += std::to_string(wave);
|
||||
Print(1, text.c_str());
|
||||
|
||||
if (flash) {
|
||||
text_animator_[1].SetEndCallback(Animator::kBlending, text_animator_cb_[1]);
|
||||
text_animator_[1].SetBlending({1, 1, 1, 1}, 0.08f);
|
||||
text_animator_[1].Play(Animator::kBlending, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Hud::SetProgress(float progress) {
|
||||
progress = std::min(std::max(0.0f, progress), 1.0f);
|
||||
last_progress_ = progress;
|
||||
Vector2 s = progress_bar_[0].GetScale() * Vector2(progress, 1);
|
||||
float t = (s.x - progress_bar_[1].GetScale().x) / 2;
|
||||
progress_bar_[1].SetScale(s);
|
||||
progress_bar_[1].Translate({t, 0});
|
||||
}
|
||||
|
||||
void Hud::Print(int i, const std::string& text) {
|
||||
auto image = CreateImage();
|
||||
|
||||
float x = 0;
|
||||
if (i == 1) {
|
||||
int w, h;
|
||||
font_->CalculateBoundingBox(text.c_str(), w, h);
|
||||
x = image->GetWidth() - w;
|
||||
}
|
||||
|
||||
font_->Print(x, 0, text.c_str(), image->GetBuffer(), image->GetWidth());
|
||||
image->SetImmutable();
|
||||
|
||||
text_[i].GetTexture()->Update(image);
|
||||
}
|
||||
|
||||
std::shared_ptr<Image> Hud::CreateImage() {
|
||||
auto image = std::make_shared<Image>();
|
||||
image->Create(max_text_width_, font_->GetLineHeight());
|
||||
image->Clear({1, 1, 1, 0});
|
||||
return image;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
#ifndef HUD_H
|
||||
#define HUD_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../base/closure.h"
|
||||
#include "../engine/animator.h"
|
||||
#include "../engine/image_quad.h"
|
||||
#include "../engine/solid_quad.h"
|
||||
|
||||
namespace eng {
|
||||
class Image;
|
||||
class Font;
|
||||
} // namespace eng
|
||||
|
||||
class Hud {
|
||||
public:
|
||||
Hud();
|
||||
~Hud();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void Update(float delta_time);
|
||||
|
||||
void Draw();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void Show();
|
||||
|
||||
void PrintScore(int score, bool flash);
|
||||
void PrintWave(int wave, bool flash);
|
||||
void SetProgress(float progress);
|
||||
|
||||
private:
|
||||
eng::SolidQuad progress_bar_[2];
|
||||
eng::ImageQuad text_[2];
|
||||
|
||||
eng::Animator progress_bar_animator_[2];
|
||||
eng::Animator text_animator_[2];
|
||||
base::Closure text_animator_cb_[2];
|
||||
|
||||
std::shared_ptr<const eng::Font> font_;
|
||||
int max_text_width_ = 0;
|
||||
|
||||
int last_score_ = 0;
|
||||
int last_wave_ = 0;
|
||||
float last_progress_ = 0;
|
||||
|
||||
void Print(int i, const std::string& text);
|
||||
|
||||
std::shared_ptr<eng::Image> CreateImage();
|
||||
};
|
||||
|
||||
#endif // HUD_H
|
|
@ -0,0 +1,215 @@
|
|||
#include "menu.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "../base/collusion_test.h"
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "../base/worker.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/font.h"
|
||||
#include "../engine/image.h"
|
||||
#include "../engine/input_event.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
|
||||
using namespace base;
|
||||
using namespace eng;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kMenuOption[Menu::kOption_Max][10] = {"continue", "start",
|
||||
"credits", "exit"};
|
||||
|
||||
constexpr float kMenuOptionSpace = 1.5f;
|
||||
|
||||
const Vector4 kColorNormal = {1, 1, 1, 1};
|
||||
const Vector4 kColorHighlight = {5, 5, 5, 1};
|
||||
constexpr float kBlendingSpeed = 0.12f;
|
||||
|
||||
const Vector4 kColorFadeOut = {1, 1, 1, 0};
|
||||
constexpr float kFadeSpeed = 0.2f;
|
||||
|
||||
} // namespace
|
||||
|
||||
Menu::Menu() : tex_(Engine::Get().CreateRenderResource<Texture>()) {}
|
||||
|
||||
Menu::~Menu() = default;
|
||||
|
||||
bool Menu::Initialize() {
|
||||
Engine& engine = Engine::Get();
|
||||
|
||||
font_ = engine.GetAsset<Font>("PixelCaps!.ttf");
|
||||
if (!font_)
|
||||
return false;
|
||||
|
||||
max_text_width_ = -1;
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
int width, height;
|
||||
font_->CalculateBoundingBox(kMenuOption[i], width, height);
|
||||
if (width > max_text_width_)
|
||||
max_text_width_ = width;
|
||||
}
|
||||
|
||||
tex_->Update(CreateImage());
|
||||
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
items_[i].text.Create(tex_, {1, 4});
|
||||
items_[i].text.AutoScale();
|
||||
items_[i].text.Scale(1.5f);
|
||||
items_[i].text.SetColor(kColorFadeOut);
|
||||
items_[i].text.SetVisible(false);
|
||||
items_[i].text.SetFrame(i);
|
||||
|
||||
items_[i].select_item_cb_ = [&, i]() -> void {
|
||||
items_[i].text_animator.SetEndCallback(
|
||||
Animator::kBlending, [&, i]() -> void {
|
||||
items_[i].text_animator.SetEndCallback(Animator::kBlending,
|
||||
nullptr);
|
||||
selected_option_ = (Option)i;
|
||||
});
|
||||
items_[i].text_animator.SetBlending(kColorNormal, kBlendingSpeed);
|
||||
items_[i].text_animator.Play(Animator::kBlending, false);
|
||||
};
|
||||
items_[i].text_animator.Attach(&items_[i].text);
|
||||
}
|
||||
// Get the item positions calculated.
|
||||
SetOptionEnabled(kContinue, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Menu::Update(float delta_time) {
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
if (items_[i].hide)
|
||||
continue;
|
||||
items_[i].text_animator.Update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) {
|
||||
if (event->GetType() == InputEvent::kTap ||
|
||||
event->GetType() == InputEvent::kDragStart)
|
||||
tap_pos_[0] = tap_pos_[1] = event->GetVector(0);
|
||||
else if (event->GetType() == InputEvent::kDrag)
|
||||
tap_pos_[1] = event->GetVector(0);
|
||||
|
||||
if ((event->GetType() != InputEvent::kTap &&
|
||||
event->GetType() != InputEvent::kDragEnd) || IsAnimating())
|
||||
return;
|
||||
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
if (items_[i].hide)
|
||||
continue;
|
||||
if (!Intersection(items_[i].text.GetOffset(),
|
||||
items_[i].text.GetScale() * Vector2(1.2f, 2),
|
||||
tap_pos_[0]))
|
||||
continue;
|
||||
if (!Intersection(items_[i].text.GetOffset(),
|
||||
items_[i].text.GetScale() * Vector2(1.2f, 2),
|
||||
tap_pos_[1]))
|
||||
continue;
|
||||
|
||||
items_[i].text_animator.SetEndCallback(Animator::kBlending,
|
||||
items_[i].select_item_cb_);
|
||||
items_[i].text_animator.SetBlending(kColorHighlight, kBlendingSpeed);
|
||||
items_[i].text_animator.Play(Animator::kBlending, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::Draw() {
|
||||
for (int i = 0; i < kOption_Max; ++i)
|
||||
items_[i].text.Draw();
|
||||
}
|
||||
|
||||
void Menu::ContextLost() {
|
||||
tex_->Update(CreateImage());
|
||||
}
|
||||
|
||||
void Menu::SetOptionEnabled(Option o, bool enable) {
|
||||
int first = -1, last = -1;
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
if (i == o)
|
||||
items_[i].hide = !enable;
|
||||
if (!items_[i].hide) {
|
||||
items_[i].text.SetOffset({0, 0});
|
||||
if (last >= 0) {
|
||||
items_[i].text.PlaceToBottomOf(items_[last].text);
|
||||
items_[i].text.Translate(items_[last].text.GetOffset() * Vector2(0, 1));
|
||||
items_[i].text.Translate(
|
||||
{0, items_[last].text.GetScale().y * -kMenuOptionSpace});
|
||||
}
|
||||
if (first < 0)
|
||||
first = i;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
float center_offset_y =
|
||||
(items_[first].text.GetOffset().y - items_[last].text.GetOffset().y) / 2;
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
if (!items_[i].hide)
|
||||
items_[i].text.Translate({0, center_offset_y});
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::Show() {
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
if (items_[i].hide)
|
||||
continue;
|
||||
items_[i].text_animator.SetEndCallback(
|
||||
Animator::kBlending, [&, i]() -> void {
|
||||
items_[i].text_animator.SetEndCallback(Animator::kBlending, nullptr);
|
||||
});
|
||||
items_[i].text_animator.SetBlending(kColorNormal, kFadeSpeed);
|
||||
items_[i].text_animator.Play(Animator::kBlending, false);
|
||||
items_[i].text.SetVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::Hide() {
|
||||
selected_option_ = kOption_Invalid;
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
if (items_[i].hide)
|
||||
continue;
|
||||
items_[i].text_animator.SetEndCallback(
|
||||
Animator::kBlending, [&, i]() -> void {
|
||||
items_[i].text_animator.SetEndCallback(Animator::kBlending, nullptr);
|
||||
items_[i].text.SetVisible(false);
|
||||
});
|
||||
items_[i].text_animator.SetBlending(kColorFadeOut, kFadeSpeed);
|
||||
items_[i].text_animator.Play(Animator::kBlending, false);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Image> Menu::CreateImage() {
|
||||
int line_height = font_->GetLineHeight() + 1;
|
||||
auto image = std::make_shared<Image>();
|
||||
image->Create(max_text_width_, line_height * kOption_Max);
|
||||
|
||||
// Fill the area of each menu item with gradient.
|
||||
image->GradientV({1.0f, 1.0f, 1.0f, 0}, {.0f, .0f, 1.0f, 0}, line_height);
|
||||
|
||||
base::Worker worker(kOption_Max);
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
int w, h;
|
||||
font_->CalculateBoundingBox(kMenuOption[i], w, h);
|
||||
float x = (image->GetWidth() - w) / 2;
|
||||
float y = line_height * i;
|
||||
worker.Enqueue(std::bind(&Font::Print, font_, x, y, kMenuOption[i],
|
||||
image->GetBuffer(), image->GetWidth()));
|
||||
}
|
||||
worker.Join();
|
||||
|
||||
image->SetImmutable();
|
||||
return image;
|
||||
}
|
||||
|
||||
bool Menu::IsAnimating() {
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
if (items_[i].text_animator.IsPlaying(Animator::kBlending))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
#ifndef MENU_H
|
||||
#define MENU_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../base/closure.h"
|
||||
#include "../base/vecmath.h"
|
||||
#include "../engine/animator.h"
|
||||
#include "../engine/image_quad.h"
|
||||
|
||||
namespace eng {
|
||||
class Image;
|
||||
class InputEvent;
|
||||
class Font;
|
||||
class Texture;
|
||||
} // namespace eng
|
||||
|
||||
class Menu {
|
||||
public:
|
||||
enum Option {
|
||||
kOption_Invalid = -1,
|
||||
kContinue,
|
||||
kNewGame,
|
||||
kCredits,
|
||||
kExit,
|
||||
kOption_Max,
|
||||
};
|
||||
|
||||
Menu();
|
||||
~Menu();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void Update(float delta_time);
|
||||
|
||||
void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
|
||||
|
||||
void Draw();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void SetOptionEnabled(Option o, bool enable);
|
||||
|
||||
void Show();
|
||||
void Hide();
|
||||
|
||||
Option selected_option() const { return selected_option_; }
|
||||
|
||||
private:
|
||||
struct Item {
|
||||
eng::ImageQuad text;
|
||||
eng::Animator text_animator;
|
||||
base::Closure select_item_cb_;
|
||||
bool hide = false;
|
||||
};
|
||||
|
||||
std::shared_ptr<eng::Texture> tex_;
|
||||
|
||||
Item items_[kOption_Max];
|
||||
|
||||
std::shared_ptr<const eng::Font> font_;
|
||||
int max_text_width_ = 0;
|
||||
|
||||
Option selected_option_ = kOption_Invalid;
|
||||
|
||||
base::Vector2 tap_pos_[2] = {{0, 0}, {0, 0}};
|
||||
|
||||
std::shared_ptr<eng::Image> CreateImage();
|
||||
|
||||
bool IsAnimating();
|
||||
};
|
||||
|
||||
#endif // MENU_H
|
|
@ -0,0 +1,350 @@
|
|||
#include "player.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/image.h"
|
||||
#include "../engine/input_event.h"
|
||||
#include "demo.h"
|
||||
|
||||
using namespace base;
|
||||
using namespace eng;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int wepon_warmup_frame[] = {1, 9};
|
||||
constexpr int wepon_warmup_frame_count = 4;
|
||||
constexpr int wepon_cooldown_frame[] = {5, 13};
|
||||
constexpr int wepon_cooldown_frame_count = 3;
|
||||
constexpr int wepon_anim_speed = 48;
|
||||
|
||||
} // namespace
|
||||
|
||||
Player::Player()
|
||||
: weapon_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
beam_tex_(Engine::Get().CreateRenderResource<Texture>()) {}
|
||||
|
||||
Player::~Player() = default;
|
||||
|
||||
bool Player::Initialize() {
|
||||
if (!CreateRenderResources())
|
||||
return false;
|
||||
SetupWeapons();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player::ContextLost() {
|
||||
CreateRenderResources();
|
||||
}
|
||||
|
||||
void Player::Update(float delta_time) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
warmup_animator_[i].Update(delta_time);
|
||||
cooldown_animator_[i].Update(delta_time);
|
||||
beam_animator_[i].Update(delta_time);
|
||||
spark_animator_[i].Update(delta_time);
|
||||
}
|
||||
|
||||
if (active_weapon_ != kDamageType_Invalid)
|
||||
UpdateTarget();
|
||||
}
|
||||
|
||||
void Player::OnInputEvent(std::unique_ptr<InputEvent> event) {
|
||||
if (event->GetType() == InputEvent::kNavigateBack)
|
||||
NavigateBack();
|
||||
else if (event->GetType() == InputEvent::kDragStart)
|
||||
DragStart(event->GetVector(0));
|
||||
else if (event->GetType() == InputEvent::kDrag)
|
||||
Drag(event->GetVector(0));
|
||||
else if (event->GetType() == InputEvent::kDragEnd)
|
||||
DragEnd();
|
||||
else if (event->GetType() == InputEvent::kDragCancel)
|
||||
DragCancel();
|
||||
}
|
||||
|
||||
void Player::Draw(float frame_frac) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
drag_sign_[i].Draw();
|
||||
beam_[i].Draw();
|
||||
beam_spark_[i].Draw();
|
||||
weapon_[i].Draw();
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 Player::GetWeaponPos(DamageType type) const {
|
||||
return Engine::Get().GetScreenSize() /
|
||||
Vector2(type == kDamageType_Green ? 3.5f : -3.5f, -2) +
|
||||
Vector2(0, weapon_[type].GetScale().y * 0.7f);
|
||||
}
|
||||
|
||||
Vector2 Player::GetWeaponScale() const {
|
||||
return weapon_[0].GetScale();
|
||||
}
|
||||
|
||||
DamageType Player::GetWeaponType(const Vector2& pos) {
|
||||
DamageType closest_weapon = kDamageType_Invalid;
|
||||
float closest_dist = std::numeric_limits<float>::max();
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
float dist = (pos - weapon_[i].GetOffset()).Magnitude();
|
||||
if (dist < closest_dist) {
|
||||
closest_dist = dist;
|
||||
closest_weapon = (DamageType)i;
|
||||
}
|
||||
}
|
||||
|
||||
assert(closest_weapon != kDamageType_Invalid);
|
||||
if (closest_dist < weapon_[closest_weapon].GetScale().x * 0.9f)
|
||||
return closest_weapon;
|
||||
return kDamageType_Invalid;
|
||||
}
|
||||
|
||||
void Player::SetBeamLength(DamageType type, float len) {
|
||||
beam_[type].SetOffset({0, 0});
|
||||
beam_[type].SetScale({len, beam_[type].GetScale().y});
|
||||
beam_[type].PlaceToRightOf(weapon_[type]);
|
||||
beam_[type].Translate(weapon_[type].GetScale() * Vector2(-0.5f, 0));
|
||||
beam_[type].SetPivot(beam_[type].GetOffset());
|
||||
beam_[type].Translate(weapon_[type].GetOffset());
|
||||
}
|
||||
|
||||
void Player::WarmupWeapon(DamageType type) {
|
||||
cooldown_animator_[type].Stop(Animator::kFrames);
|
||||
warmup_animator_[type].Play(Animator::kFrames, false);
|
||||
}
|
||||
|
||||
void Player::CooldownWeapon(DamageType type) {
|
||||
warmup_animator_[type].Stop(Animator::kFrames);
|
||||
cooldown_animator_[type].Play(Animator::kFrames, false);
|
||||
}
|
||||
|
||||
void Player::Fire(DamageType type, Vector2 dir) {
|
||||
Engine& engine = Engine::Get();
|
||||
Enemy& enemy = static_cast<Demo*>(engine.GetGame())->GetEnemy();
|
||||
|
||||
if (enemy.HasTarget(type))
|
||||
dir = weapon_[type].GetOffset() - enemy.GetTargetPos(type);
|
||||
else
|
||||
dir *= engine.GetScreenSize().y * 1.3f;
|
||||
|
||||
float len = dir.Magnitude();
|
||||
SetBeamLength(type, len);
|
||||
|
||||
dir.Normalize();
|
||||
float cos_theta = dir.DotProduct(Vector2(1, 0));
|
||||
float theta = acos(cos_theta) + M_PI;
|
||||
beam_[type].SetTheta(theta);
|
||||
beam_spark_[type].SetTheta(theta);
|
||||
|
||||
beam_[type].SetColor({1, 1, 1, 1});
|
||||
beam_[type].SetVisible(true);
|
||||
beam_spark_[type].SetVisible(true);
|
||||
|
||||
float length = beam_[type].GetScale().x * 0.85f;
|
||||
Vector2 movement = dir * -length;
|
||||
// Convert from units per second to duration.
|
||||
float speed = 1.0f / (18.0f / length);
|
||||
spark_animator_[type].SetMovement(movement, speed);
|
||||
spark_animator_[type].Play(Animator::kMovement, false);
|
||||
}
|
||||
|
||||
bool Player::IsFiring(DamageType type) {
|
||||
return beam_animator_[type].IsPlaying(Animator::kBlending) ||
|
||||
spark_animator_[type].IsPlaying(Animator::kMovement);
|
||||
}
|
||||
|
||||
void Player::SetupWeapons() {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// Setup draw sign.
|
||||
drag_sign_[i].Create(weapon_tex_, {8, 2});
|
||||
drag_sign_[i].AutoScale();
|
||||
drag_sign_[i].SetFrame(i * 8);
|
||||
|
||||
// Setup weapon.
|
||||
weapon_[i].Create(weapon_tex_, {8, 2});
|
||||
weapon_[i].AutoScale();
|
||||
weapon_[i].SetVisible(true);
|
||||
weapon_[i].SetFrame(wepon_warmup_frame[i]);
|
||||
|
||||
// Setup beam.
|
||||
beam_[i].Create(beam_tex_, {1, 2});
|
||||
beam_[i].AutoScale();
|
||||
beam_[i].SetFrame(i);
|
||||
beam_[i].PlaceToRightOf(weapon_[i]);
|
||||
beam_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
|
||||
beam_[i].SetPivot(beam_[i].GetOffset());
|
||||
|
||||
// Setup beam spark.
|
||||
beam_spark_[i].Create(weapon_tex_, {8, 2});
|
||||
beam_spark_[i].AutoScale();
|
||||
beam_spark_[i].SetFrame(i * 8 + 1);
|
||||
beam_spark_[i].PlaceToRightOf(weapon_[i]);
|
||||
beam_spark_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
|
||||
beam_spark_[i].SetPivot(beam_spark_[i].GetOffset());
|
||||
|
||||
// Place parts on the screen.
|
||||
Vector2 offset = GetWeaponPos((DamageType)i);
|
||||
beam_[i].Translate(offset);
|
||||
beam_spark_[i].Translate(offset);
|
||||
weapon_[i].Translate(offset);
|
||||
|
||||
// Setup animators.
|
||||
weapon_[i].SetFrame(wepon_cooldown_frame[i]);
|
||||
cooldown_animator_[i].SetFrames(wepon_cooldown_frame_count,
|
||||
wepon_anim_speed);
|
||||
cooldown_animator_[i].SetEndCallback(Animator::kFrames, [&, i]() -> void {
|
||||
weapon_[i].SetFrame(wepon_warmup_frame[i]);
|
||||
});
|
||||
cooldown_animator_[i].Attach(&weapon_[i]);
|
||||
|
||||
weapon_[i].SetFrame(wepon_warmup_frame[i]);
|
||||
warmup_animator_[i].SetFrames(wepon_warmup_frame_count, wepon_anim_speed);
|
||||
warmup_animator_[i].SetRotation(M_PI * 2, 8.0f);
|
||||
warmup_animator_[i].Attach(&weapon_[i]);
|
||||
warmup_animator_[i].Play(Animator::kRotation, true);
|
||||
|
||||
spark_animator_[i].SetEndCallback(Animator::kMovement, [&, i]() -> void {
|
||||
beam_spark_[i].SetVisible(false);
|
||||
beam_animator_[i].Play(Animator::kBlending, false);
|
||||
static_cast<Demo*>(Engine::Get().GetGame())
|
||||
->GetEnemy()
|
||||
.HitTarget((DamageType)i);
|
||||
});
|
||||
spark_animator_[i].Attach(&beam_spark_[i]);
|
||||
|
||||
beam_animator_[i].SetEndCallback(
|
||||
Animator::kBlending, [&, i]() -> void { beam_[i].SetVisible(false); });
|
||||
beam_animator_[i].SetBlending({1, 1, 1, 0}, 0.16f);
|
||||
beam_animator_[i].Attach(&beam_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::UpdateTarget() {
|
||||
if (IsFiring(active_weapon_))
|
||||
return;
|
||||
|
||||
Engine& engine = Engine::Get();
|
||||
Demo* game = static_cast<Demo*>(engine.GetGame());
|
||||
|
||||
if (drag_valid_) {
|
||||
Vector2 dir = (drag_end_ - drag_start_).Normalize();
|
||||
game->GetEnemy().SelectTarget(active_weapon_, drag_start_, dir, 1.2f);
|
||||
if (!game->GetEnemy().HasTarget(active_weapon_))
|
||||
game->GetEnemy().SelectTarget(active_weapon_, drag_start_, dir, 2);
|
||||
} else {
|
||||
game->GetEnemy().DeselectTarget(active_weapon_);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::DragStart(const Vector2& pos) {
|
||||
active_weapon_ = GetWeaponType(pos);
|
||||
if (active_weapon_ == kDamageType_Invalid)
|
||||
return;
|
||||
|
||||
drag_start_ = drag_end_ = pos;
|
||||
|
||||
drag_sign_[active_weapon_].SetOffset(drag_start_);
|
||||
drag_sign_[active_weapon_].SetVisible(true);
|
||||
}
|
||||
|
||||
void Player::Drag(const Vector2& pos) {
|
||||
if (active_weapon_ == kDamageType_Invalid)
|
||||
return;
|
||||
|
||||
drag_end_ = pos;
|
||||
drag_sign_[active_weapon_].SetOffset(drag_end_);
|
||||
|
||||
if (ValidateDrag()) {
|
||||
if (!drag_valid_ && !IsFiring(active_weapon_))
|
||||
WarmupWeapon(active_weapon_);
|
||||
drag_valid_ = true;
|
||||
} else {
|
||||
if (drag_valid_ && !IsFiring(active_weapon_))
|
||||
CooldownWeapon(active_weapon_);
|
||||
drag_valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Player::DragEnd() {
|
||||
if (active_weapon_ == kDamageType_Invalid)
|
||||
return;
|
||||
|
||||
UpdateTarget();
|
||||
|
||||
DamageType type = active_weapon_;
|
||||
active_weapon_ = kDamageType_Invalid;
|
||||
drag_sign_[type].SetVisible(false);
|
||||
|
||||
Vector2 fire_dir = (drag_start_ - drag_end_).Normalize();
|
||||
|
||||
if (drag_valid_ && !IsFiring(type)) {
|
||||
if (warmup_animator_[type].IsPlaying(Animator::kFrames)) {
|
||||
warmup_animator_[type].SetEndCallback(
|
||||
Animator::kFrames, [&, type, fire_dir]() -> void {
|
||||
warmup_animator_[type].SetEndCallback(Animator::kFrames, nullptr);
|
||||
CooldownWeapon(type);
|
||||
Fire(type, fire_dir);
|
||||
});
|
||||
} else {
|
||||
CooldownWeapon(type);
|
||||
Fire(type, fire_dir);
|
||||
}
|
||||
}
|
||||
|
||||
drag_valid_ = false;
|
||||
drag_start_ = drag_end_ = {0, 0};
|
||||
}
|
||||
|
||||
void Player::DragCancel() {
|
||||
if (active_weapon_ == kDamageType_Invalid)
|
||||
return;
|
||||
|
||||
DamageType type = active_weapon_;
|
||||
active_weapon_ = kDamageType_Invalid;
|
||||
drag_sign_[type].SetVisible(false);
|
||||
|
||||
if (drag_valid_ && !IsFiring(type)) {
|
||||
if (warmup_animator_[type].IsPlaying(Animator::kFrames)) {
|
||||
warmup_animator_[type].SetEndCallback(
|
||||
Animator::kFrames, [&, type]() -> void {
|
||||
warmup_animator_[type].SetEndCallback(Animator::kFrames, nullptr);
|
||||
CooldownWeapon(type);
|
||||
});
|
||||
} else {
|
||||
CooldownWeapon(type);
|
||||
}
|
||||
}
|
||||
|
||||
drag_valid_ = false;
|
||||
drag_start_ = drag_end_ = {0, 0};
|
||||
}
|
||||
|
||||
bool Player::ValidateDrag() {
|
||||
Vector2 dir = drag_end_ - drag_start_;
|
||||
float len = dir.Magnitude();
|
||||
dir.Normalize();
|
||||
if (len < weapon_[active_weapon_].GetScale().y / 4)
|
||||
return false;
|
||||
if (dir.DotProduct(Vector2(0, 1)) < 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player::NavigateBack() {
|
||||
DragCancel();
|
||||
Engine& engine = Engine::Get();
|
||||
static_cast<Demo*>(engine.GetGame())->EnterMenuState();
|
||||
}
|
||||
|
||||
bool Player::CreateRenderResources() {
|
||||
Engine& engine = Engine::Get();
|
||||
|
||||
auto weapon_image = engine.GetAsset<Image>("enemy_anims_flare_ok.png");
|
||||
auto beam_image = engine.GetAsset<Image>("enemy_ray_ok.png");
|
||||
if (!weapon_image || !beam_image)
|
||||
return false;
|
||||
|
||||
weapon_tex_->Update(weapon_image);
|
||||
beam_tex_->Update(beam_image);
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
#ifndef PLAYER_H
|
||||
#define PLAYER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../base/vecmath.h"
|
||||
#include "../engine/animator.h"
|
||||
#include "../engine/image_quad.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
#include "damage_type.h"
|
||||
|
||||
namespace eng {
|
||||
class InputEvent;
|
||||
} // namespace eng
|
||||
|
||||
class Player {
|
||||
public:
|
||||
Player();
|
||||
~Player();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void Update(float delta_time);
|
||||
|
||||
void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
|
||||
|
||||
void Draw(float frame_frac);
|
||||
|
||||
base::Vector2 GetWeaponPos(DamageType type) const;
|
||||
base::Vector2 GetWeaponScale() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<eng::Texture> weapon_tex_;
|
||||
std::shared_ptr<eng::Texture> beam_tex_;
|
||||
|
||||
eng::ImageQuad drag_sign_[2];
|
||||
eng::ImageQuad weapon_[2];
|
||||
eng::ImageQuad beam_[2];
|
||||
eng::ImageQuad beam_spark_[2];
|
||||
|
||||
eng::Animator warmup_animator_[2];
|
||||
eng::Animator cooldown_animator_[2];
|
||||
eng::Animator beam_animator_[2];
|
||||
eng::Animator spark_animator_[2];
|
||||
|
||||
DamageType active_weapon_ = kDamageType_Invalid;
|
||||
|
||||
base::Vector2 drag_start_ = {0, 0};
|
||||
base::Vector2 drag_end_ = {0, 0};
|
||||
bool drag_valid_ = false;
|
||||
|
||||
DamageType GetWeaponType(const base::Vector2& pos);
|
||||
|
||||
void SetBeamLength(DamageType type, float len);
|
||||
|
||||
void WarmupWeapon(DamageType type);
|
||||
void CooldownWeapon(DamageType type);
|
||||
|
||||
void Fire(DamageType type, base::Vector2 dir);
|
||||
bool IsFiring(DamageType type);
|
||||
|
||||
void SetupWeapons();
|
||||
|
||||
void UpdateTarget();
|
||||
|
||||
void DragStart(const base::Vector2& pos);
|
||||
void Drag(const base::Vector2& pos);
|
||||
void DragEnd();
|
||||
void DragCancel();
|
||||
bool ValidateDrag();
|
||||
|
||||
void NavigateBack();
|
||||
|
||||
bool CreateRenderResources();
|
||||
};
|
||||
|
||||
#endif // PLAYER_H
|
|
@ -0,0 +1,64 @@
|
|||
#include "sky_quad.h"
|
||||
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "../base/random.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/renderer/geometry.h"
|
||||
#include "../engine/renderer/shader.h"
|
||||
#include "../engine/shader_source.h"
|
||||
|
||||
using namespace base;
|
||||
using namespace eng;
|
||||
|
||||
SkyQuad::SkyQuad()
|
||||
: shader_(Engine::Get().CreateRenderResource<Shader>()),
|
||||
sky_offset_{
|
||||
0, Lerp(0.0f, 10.0f, Engine::Get().GetRandomGenerator().GetFloat())} {
|
||||
}
|
||||
|
||||
SkyQuad::~SkyQuad() = default;
|
||||
|
||||
bool SkyQuad::Create() {
|
||||
Engine& engine = Engine::Get();
|
||||
|
||||
auto sky_source = engine.GetAsset<ShaderSource>("sky.glsl");
|
||||
if (!sky_source)
|
||||
return false;
|
||||
shader_->Create(sky_source, engine.GetQuad()->vertex_description());
|
||||
|
||||
scale_ = engine.GetScreenSize();
|
||||
|
||||
color_animator_.Attach(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkyQuad::Update(float delta_time) {
|
||||
sky_offset_ += {0, delta_time * 0.04f};
|
||||
color_animator_.Update(delta_time);
|
||||
}
|
||||
|
||||
void SkyQuad::Draw(float frame_frac) {
|
||||
Vector2 sky_offset = Lerp(last_sky_offset_, sky_offset_, frame_frac);
|
||||
|
||||
shader_->Activate();
|
||||
shader_->SetUniform("scale", scale_);
|
||||
shader_->SetUniform("projection", Engine::Get().GetProjectionMarix());
|
||||
shader_->SetUniform("sky_offset", sky_offset);
|
||||
shader_->SetUniform("nebula_color",
|
||||
{nebula_color_.x, nebula_color_.y, nebula_color_.z});
|
||||
|
||||
Engine::Get().GetQuad()->Draw();
|
||||
last_sky_offset_ = sky_offset_;
|
||||
}
|
||||
|
||||
void SkyQuad::ContextLost() {
|
||||
Create();
|
||||
}
|
||||
|
||||
void SkyQuad::SwitchColor(const Vector4& color) {
|
||||
color_animator_.SetBlending(color, 5,
|
||||
std::bind(SmoothStep, std::placeholders::_1));
|
||||
color_animator_.Play(Animator::kBlending, false);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef SKY_QUAD_H
|
||||
#define SKY_QUAD_H
|
||||
|
||||
#include "../base/vecmath.h"
|
||||
#include "../engine/animatable.h"
|
||||
#include "../engine/animator.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace eng {
|
||||
class Shader;
|
||||
} // namespace eng
|
||||
|
||||
class SkyQuad : public eng::Animatable {
|
||||
public:
|
||||
SkyQuad();
|
||||
~SkyQuad();
|
||||
|
||||
SkyQuad(const SkyQuad&) = delete;
|
||||
SkyQuad& operator=(const SkyQuad&) = delete;
|
||||
|
||||
bool Create();
|
||||
|
||||
void Update(float delta_time);
|
||||
|
||||
// Animatable interface.
|
||||
void SetFrame(size_t frame) override {}
|
||||
size_t GetFrame() override { return 0; }
|
||||
size_t GetNumFrames() override { return 0; }
|
||||
void SetColor(const base::Vector4& color) override { nebula_color_ = color; }
|
||||
base::Vector4 GetColor() const override { return nebula_color_; }
|
||||
|
||||
void Draw(float frame_frac);
|
||||
void ContextLost();
|
||||
|
||||
void SwitchColor(const base::Vector4& color);
|
||||
|
||||
private:
|
||||
std::shared_ptr<eng::Shader> shader_;
|
||||
|
||||
base::Vector2 sky_offset_ = {0, 0};
|
||||
base::Vector2 last_sky_offset_ = {0, 0};
|
||||
base::Vector4 nebula_color_ = {0, 0, 0, 1};
|
||||
base::Vector2 scale_ = {1, 1};
|
||||
|
||||
eng::Animator color_animator_;
|
||||
};
|
||||
|
||||
#endif // SKY_QUAD_H
|
|
@ -0,0 +1,33 @@
|
|||
#include "animatable.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
void Animatable::Translate(const Vector2& offset) {
|
||||
offset_ += offset;
|
||||
}
|
||||
|
||||
void Animatable::Scale(const Vector2& scale) {
|
||||
scale_ *= scale;
|
||||
}
|
||||
|
||||
void Animatable::Scale(float scale) {
|
||||
scale_ *= scale;
|
||||
}
|
||||
|
||||
void Animatable::Rotate(float angle) {
|
||||
theta_ += angle;
|
||||
rotation_.x = sin(theta_);
|
||||
rotation_.y = cos(theta_);
|
||||
}
|
||||
|
||||
void Animatable::SetTheta(float theta) {
|
||||
theta_ = theta;
|
||||
rotation_.x = sin(theta_);
|
||||
rotation_.y = cos(theta_);
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,66 @@
|
|||
#ifndef SHAPE_H
|
||||
#define SHAPE_H
|
||||
|
||||
#include "../base/vecmath.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Animatable {
|
||||
public:
|
||||
Animatable() = default;
|
||||
virtual ~Animatable() = default;
|
||||
|
||||
void Translate(const base::Vector2& offset);
|
||||
void Scale(const base::Vector2& scale);
|
||||
void Scale(float scale);
|
||||
void Rotate(float angle);
|
||||
|
||||
void SetOffset(const base::Vector2& offset) { offset_ = offset; }
|
||||
void SetScale(const base::Vector2& scale) { scale_ = scale; }
|
||||
void SetPivot(const base::Vector2& pivot) { pivot_ = pivot; }
|
||||
void SetTheta(float theta);
|
||||
|
||||
base::Vector2 GetOffset() const { return offset_; }
|
||||
base::Vector2 GetScale() const { return scale_; }
|
||||
base::Vector2 GetPivot() const { return pivot_; }
|
||||
float GetTheta() const { return theta_; }
|
||||
|
||||
// Pure virtuals for frame animation support.
|
||||
virtual void SetFrame(size_t frame) = 0;
|
||||
virtual size_t GetFrame() = 0;
|
||||
virtual size_t GetNumFrames() = 0;
|
||||
|
||||
virtual void SetColor(const base::Vector4& color) = 0;
|
||||
virtual base::Vector4 GetColor() const = 0;
|
||||
|
||||
void SetVisible(bool visible) { visible_ = visible; }
|
||||
bool IsVisible() const { return visible_; }
|
||||
|
||||
void PlaceToLeftOf(const Animatable& s) {
|
||||
Translate({s.GetScale().x / -2.0f + GetScale().x / -2.0f, 0});
|
||||
}
|
||||
|
||||
void PlaceToRightOf(const Animatable& s) {
|
||||
Translate({s.GetScale().x / 2.0f + GetScale().x / 2.0f, 0});
|
||||
}
|
||||
|
||||
void PlaceToTopOf(const Animatable& s) {
|
||||
Translate({0, s.GetScale().y / 2.0f + GetScale().y / 2.0f});
|
||||
}
|
||||
|
||||
void PlaceToBottomOf(const Animatable& s) {
|
||||
Translate({0, s.GetScale().y / -2.0f + GetScale().y / -2.0f});
|
||||
}
|
||||
|
||||
protected:
|
||||
base::Vector2 offset_ = {0, 0};
|
||||
base::Vector2 scale_ = {1, 1};
|
||||
base::Vector2 pivot_ = {0, 0};
|
||||
base::Vector2 rotation_ = {0, 1};
|
||||
float theta_ = 0;
|
||||
bool visible_ = false;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // SHAPE_H
|
|
@ -0,0 +1,269 @@
|
|||
#include "animator.h"
|
||||
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "animatable.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
void Animator::Attach(Animatable* animatable) {
|
||||
elements_.push_back({animatable,
|
||||
{0, 0},
|
||||
0,
|
||||
animatable->GetColor(),
|
||||
(int)animatable->GetFrame()});
|
||||
}
|
||||
|
||||
void Animator::Play(int animation, bool loop) {
|
||||
play_flags_ |= animation;
|
||||
loop_flags_ |= loop ? animation : 0;
|
||||
}
|
||||
|
||||
void Animator::Pause(int animation) {
|
||||
play_flags_ &= ~animation;
|
||||
}
|
||||
|
||||
void Animator::Stop(int animation) {
|
||||
if ((animation & kMovement) != 0)
|
||||
movement_time_ = 0;
|
||||
if ((animation & kRotation) != 0)
|
||||
rotation_time_ = 0;
|
||||
if ((animation & kBlending) != 0)
|
||||
blending_time_ = 0;
|
||||
if ((animation & kFrames) != 0)
|
||||
frame_time_ = 0;
|
||||
if ((animation & kTimer) != 0)
|
||||
timer_time_ = 0;
|
||||
|
||||
play_flags_ |= animation;
|
||||
Update(0);
|
||||
play_flags_ &= ~animation;
|
||||
}
|
||||
|
||||
void Animator::SetEndCallback(int animation, base::Closure cb) {
|
||||
if ((inside_cb_ & animation) != 0) {
|
||||
has_pending_cb_ = true;
|
||||
pending_cb_ = std::move(cb);
|
||||
}
|
||||
if ((animation & kMovement) != 0 && inside_cb_ != kMovement)
|
||||
movement_cb_ = std::move(cb);
|
||||
if ((animation & kRotation) != 0 && inside_cb_ != kRotation)
|
||||
rotation_cb_ = std::move(cb);
|
||||
if ((animation & kBlending) != 0 && inside_cb_ != kBlending)
|
||||
blending_cb_ = std::move(cb);
|
||||
if ((animation & kFrames) != 0 && inside_cb_ != kFrames)
|
||||
frame_cb_ = std::move(cb);
|
||||
if ((animation & kTimer) != 0 && inside_cb_ != kTimer)
|
||||
timer_cb_ = std::move(cb);
|
||||
}
|
||||
|
||||
void Animator::SetMovement(Vector2 direction,
|
||||
float duration,
|
||||
Interpolator interpolator) {
|
||||
movement_direction_ = direction;
|
||||
movement_speed_ = 1.0f / duration;
|
||||
movement_interpolator_ = std::move(interpolator);
|
||||
}
|
||||
|
||||
void Animator::SetRotation(float trget,
|
||||
float duration,
|
||||
Interpolator interpolator) {
|
||||
rotation_target_ = trget;
|
||||
rotation_speed_ = 1.0f / duration;
|
||||
rotation_interpolator_ = std::move(interpolator);
|
||||
}
|
||||
|
||||
void Animator::SetBlending(Vector4 target,
|
||||
float duration,
|
||||
Interpolator interpolator) {
|
||||
blending_target_ = target;
|
||||
blending_speed_ = 1.0f / duration;
|
||||
for (auto& a : elements_)
|
||||
a.blending_start = a.animatable->GetColor();
|
||||
blending_interpolator_ = std::move(interpolator);
|
||||
}
|
||||
|
||||
void Animator::SetFrames(int count,
|
||||
int frames_per_second,
|
||||
Interpolator interpolator) {
|
||||
frame_count_ = count;
|
||||
frame_speed_ = (float)frames_per_second / (float)count;
|
||||
for (auto& a : elements_)
|
||||
a.frame_start_ = a.animatable->GetFrame();
|
||||
frame_interpolator_ = std::move(interpolator);
|
||||
}
|
||||
|
||||
void Animator::SetTimer(float duration) {
|
||||
timer_speed_ = 1.0f / duration;
|
||||
}
|
||||
|
||||
void Animator::SetVisible(bool visible) {
|
||||
for (auto& a : elements_)
|
||||
a.animatable->SetVisible(visible);
|
||||
}
|
||||
|
||||
void Animator::Update(float delta_time) {
|
||||
if (play_flags_ & kMovement)
|
||||
UpdateMovement(delta_time);
|
||||
if (play_flags_ & kRotation)
|
||||
UpdateRotation(delta_time);
|
||||
if (play_flags_ & kBlending)
|
||||
UpdateBlending(delta_time);
|
||||
if (play_flags_ & kFrames)
|
||||
UpdateFrame(delta_time);
|
||||
if (play_flags_ & kTimer)
|
||||
UpdateTimer(delta_time);
|
||||
|
||||
for (auto& a : elements_) {
|
||||
if (play_flags_ & kMovement) {
|
||||
float interpolated_time = movement_interpolator_
|
||||
? movement_interpolator_(movement_time_)
|
||||
: movement_time_;
|
||||
Vector2 offset =
|
||||
base::Lerp({0, 0}, movement_direction_, interpolated_time);
|
||||
a.animatable->Translate(offset - a.movement_last_offset);
|
||||
a.movement_last_offset = offset;
|
||||
}
|
||||
|
||||
if (play_flags_ & kRotation) {
|
||||
float interpolated_time = rotation_interpolator_
|
||||
? rotation_interpolator_(rotation_time_)
|
||||
: rotation_time_;
|
||||
float theta = base::Lerp(0.0f, rotation_target_, interpolated_time);
|
||||
a.animatable->Rotate(theta - a.rotation_last_theta);
|
||||
a.rotation_last_theta = theta;
|
||||
}
|
||||
|
||||
if (play_flags_ & kBlending) {
|
||||
float interpolated_time = blending_interpolator_
|
||||
? blending_interpolator_(blending_time_)
|
||||
: blending_time_;
|
||||
Vector4 r =
|
||||
base::Lerp(a.blending_start, blending_target_, interpolated_time);
|
||||
a.animatable->SetColor(r);
|
||||
}
|
||||
|
||||
if (play_flags_ & kFrames) {
|
||||
float interpolated_time =
|
||||
frame_interpolator_ ? frame_interpolator_(frame_time_) : frame_time_;
|
||||
int target = a.frame_start_ + frame_count_;
|
||||
int r = base::Lerp(a.frame_start_, target, interpolated_time);
|
||||
if (r < target)
|
||||
a.animatable->SetFrame(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Animator::UpdateMovement(float delta_time) {
|
||||
if ((loop_flags_ & kMovement) == 0 && movement_time_ == 1.0f) {
|
||||
movement_time_ = 0;
|
||||
play_flags_ &= ~kMovement;
|
||||
if (movement_cb_) {
|
||||
inside_cb_ = kMovement;
|
||||
movement_cb_();
|
||||
inside_cb_ = kNone;
|
||||
if (has_pending_cb_) {
|
||||
has_pending_cb_ = false;
|
||||
movement_cb_ = std::move(pending_cb_);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
movement_time_ += movement_speed_ * delta_time;
|
||||
if (movement_time_ > 1)
|
||||
movement_time_ =
|
||||
(loop_flags_ & kMovement) == 0 ? 1 : fmod(movement_time_, 1.0f);
|
||||
}
|
||||
|
||||
void Animator::UpdateRotation(float delta_time) {
|
||||
if ((loop_flags_ & kRotation) == 0 && rotation_time_ == 1.0f) {
|
||||
rotation_time_ = 0;
|
||||
play_flags_ &= ~kRotation;
|
||||
if (rotation_cb_) {
|
||||
inside_cb_ = kRotation;
|
||||
rotation_cb_();
|
||||
inside_cb_ = kNone;
|
||||
if (has_pending_cb_) {
|
||||
has_pending_cb_ = false;
|
||||
rotation_cb_ = std::move(pending_cb_);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
rotation_time_ += rotation_speed_ * delta_time;
|
||||
if (rotation_time_ > 1)
|
||||
rotation_time_ =
|
||||
(loop_flags_ & kRotation) == 0 ? 1 : fmod(rotation_time_, 1.0f);
|
||||
}
|
||||
|
||||
void Animator::UpdateBlending(float delta_time) {
|
||||
if ((loop_flags_ & kBlending) == 0 && blending_time_ == 1.0f) {
|
||||
blending_time_ = 0;
|
||||
play_flags_ &= ~kBlending;
|
||||
if (blending_cb_) {
|
||||
inside_cb_ = kBlending;
|
||||
blending_cb_();
|
||||
inside_cb_ = kNone;
|
||||
if (has_pending_cb_) {
|
||||
has_pending_cb_ = false;
|
||||
blending_cb_ = std::move(pending_cb_);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
blending_time_ += blending_speed_ * delta_time;
|
||||
if (blending_time_ > 1)
|
||||
blending_time_ =
|
||||
(loop_flags_ & kBlending) == 0 ? 1 : fmod(blending_time_, 1.0f);
|
||||
}
|
||||
|
||||
void Animator::UpdateFrame(float delta_time) {
|
||||
if ((loop_flags_ & kFrames) == 0 && frame_time_ == 1.0f) {
|
||||
frame_time_ = 0;
|
||||
play_flags_ &= ~kFrames;
|
||||
if (frame_cb_) {
|
||||
inside_cb_ = kFrames;
|
||||
frame_cb_();
|
||||
inside_cb_ = kNone;
|
||||
if (has_pending_cb_) {
|
||||
has_pending_cb_ = false;
|
||||
frame_cb_ = std::move(pending_cb_);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if ((loop_flags_ & kFrames) != 0 && frame_time_ == 1.0f) {
|
||||
frame_time_ = 0;
|
||||
}
|
||||
|
||||
frame_time_ += frame_speed_ * delta_time;
|
||||
if (frame_time_ > 1)
|
||||
frame_time_ = 1;
|
||||
}
|
||||
|
||||
void Animator::UpdateTimer(float delta_time) {
|
||||
if (timer_time_ == 1.0f) {
|
||||
timer_time_ = 0;
|
||||
play_flags_ &= ~kTimer;
|
||||
if (timer_cb_) {
|
||||
inside_cb_ = kTimer;
|
||||
timer_cb_();
|
||||
inside_cb_ = kNone;
|
||||
if (has_pending_cb_) {
|
||||
has_pending_cb_ = false;
|
||||
timer_cb_ = std::move(pending_cb_);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
timer_time_ += timer_speed_ * delta_time;
|
||||
if (timer_time_ > 1)
|
||||
timer_time_ = 1;
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,136 @@
|
|||
#ifndef ANIMATOR_H
|
||||
#define ANIMATOR_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../base/closure.h"
|
||||
#include "../base/vecmath.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Animatable;
|
||||
|
||||
class Animator {
|
||||
public:
|
||||
// Animation type flags.
|
||||
enum Flags {
|
||||
kNone = 0,
|
||||
kMovement = 1,
|
||||
kRotation = 2,
|
||||
kBlending = 4,
|
||||
kFrames = 8,
|
||||
kTimer = 16,
|
||||
kAllAnimations = kMovement | kRotation | kBlending | kFrames
|
||||
};
|
||||
|
||||
using Interpolator = std::function<float(float)>;
|
||||
|
||||
Animator() = default;
|
||||
~Animator() = default;
|
||||
|
||||
// Attached the given animatable to this animator and sets the start values.
|
||||
void Attach(Animatable* animatable);
|
||||
|
||||
void Play(int animation, bool loop);
|
||||
void Pause(int animation);
|
||||
void Stop(int animation);
|
||||
|
||||
// Set callback for the given animations. It's called for each animation once
|
||||
// it ends. Not that it's not called for looping animations.
|
||||
void SetEndCallback(int animation, base::Closure cb);
|
||||
|
||||
// Set movement animation parameters. Movement is relative to the attached
|
||||
// animatable's current position. Distance is calculated from the magnitude of
|
||||
// direction vector. Duration is in seconds.
|
||||
void SetMovement(base::Vector2 direction,
|
||||
float duration,
|
||||
Interpolator interpolator = nullptr);
|
||||
|
||||
// Set rotation animation parameters. Rotation is relative to the attached
|
||||
// animatable's current rotation. Duration is in seconds.
|
||||
void SetRotation(float target,
|
||||
float duration,
|
||||
Interpolator interpolator = nullptr);
|
||||
|
||||
// Set color blending animation parameters. Color blending animation is
|
||||
// absolute. The absolute start colors are obtained from the attached
|
||||
// animatables. Duration is in seconds.
|
||||
void SetBlending(base::Vector4 target,
|
||||
float duration,
|
||||
Interpolator interpolator = nullptr);
|
||||
|
||||
// Set frame playback animation parameters. Frame animation is absolute. The
|
||||
// absolute start frames are obtained from the attached animatables. Plays
|
||||
// count number of frames.
|
||||
void SetFrames(int count,
|
||||
int frames_per_second,
|
||||
Interpolator interpolator = nullptr);
|
||||
|
||||
// Set Timer parameters. Timer doesn't play any animation. Usefull for
|
||||
// triggering a callback after the given seconds passed. Loop parameter is
|
||||
// ignored when played.
|
||||
void SetTimer(float duration);
|
||||
|
||||
// Set visibility of all attached animatables.
|
||||
void SetVisible(bool visible);
|
||||
|
||||
void Update(float delta_time);
|
||||
|
||||
bool IsPlaying(int animation) const { return play_flags_ & animation; }
|
||||
|
||||
private:
|
||||
struct Element {
|
||||
Animatable* animatable;
|
||||
base::Vector2 movement_last_offset = {0, 0};
|
||||
float rotation_last_theta = 0;
|
||||
base::Vector4 blending_start = {0, 0, 0, 0};
|
||||
int frame_start_ = 0;
|
||||
};
|
||||
|
||||
unsigned int play_flags_ = 0;
|
||||
unsigned int loop_flags_ = 0;
|
||||
std::vector<Element> elements_;
|
||||
|
||||
base::Vector2 movement_direction_ = {0, 0};
|
||||
float movement_speed_ = 0;
|
||||
float movement_time_ = 0;
|
||||
Interpolator movement_interpolator_;
|
||||
base::Closure movement_cb_;
|
||||
|
||||
float rotation_target_ = 0;
|
||||
float rotation_speed_ = 0;
|
||||
float rotation_time_ = 0;
|
||||
Interpolator rotation_interpolator_;
|
||||
base::Closure rotation_cb_;
|
||||
|
||||
base::Vector4 blending_target_ = {0, 0, 0, 0};
|
||||
float blending_speed_ = 0;
|
||||
float blending_time_ = 0;
|
||||
Interpolator blending_interpolator_;
|
||||
base::Closure blending_cb_;
|
||||
|
||||
int frame_count_ = 0;
|
||||
float frame_speed_ = 0;
|
||||
float frame_time_ = 0;
|
||||
Interpolator frame_interpolator_;
|
||||
base::Closure frame_cb_;
|
||||
|
||||
float timer_speed_ = 0;
|
||||
float timer_time_ = 0;
|
||||
base::Closure timer_cb_;
|
||||
|
||||
// State used to set new callback during a callback.
|
||||
bool has_pending_cb_ = false;
|
||||
base::Closure pending_cb_;
|
||||
Flags inside_cb_ = kNone;
|
||||
|
||||
void UpdateMovement(float delta_time);
|
||||
void UpdateRotation(float delta_time);
|
||||
void UpdateBlending(float delta_time);
|
||||
void UpdateFrame(float delta_time);
|
||||
void UpdateTimer(float delta_time);
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // ANIMATOR_H
|
|
@ -0,0 +1,53 @@
|
|||
#ifndef ASSET_H
|
||||
#define ASSET_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Asset {
|
||||
public:
|
||||
Asset() = default;
|
||||
virtual ~Asset() = default;
|
||||
|
||||
virtual bool Load(const std::string& file_name) = 0;
|
||||
|
||||
void SetName(const std::string& name) { name_ = name; }
|
||||
const std::string& GetName() const { return name_; }
|
||||
|
||||
void SetImmutable() { immutable_ = true; }
|
||||
bool IsImmutable() const { return immutable_; }
|
||||
|
||||
protected:
|
||||
std::string name_;
|
||||
bool immutable_ = false;
|
||||
};
|
||||
|
||||
class AssetFactoryBase {
|
||||
public:
|
||||
AssetFactoryBase(const std::string& name) : name_(name) {}
|
||||
virtual ~AssetFactoryBase() = default;
|
||||
|
||||
virtual std::shared_ptr<eng::Asset> Create() = 0;
|
||||
|
||||
const std::string& name() { return name_; };
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class AssetFactory : public AssetFactoryBase {
|
||||
public:
|
||||
~AssetFactory() override = default;
|
||||
|
||||
AssetFactory(const std::string& name) : AssetFactoryBase(name) {}
|
||||
|
||||
std::shared_ptr<eng::Asset> Create() override {
|
||||
return std::make_shared<T>();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // ASSET_H
|
|
@ -0,0 +1,313 @@
|
|||
#include "engine.h"
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "../base/worker.h"
|
||||
#include "font.h"
|
||||
#include "game.h"
|
||||
#include "game_factory.h"
|
||||
#include "image.h"
|
||||
#include "input_event.h"
|
||||
#include "mesh.h"
|
||||
#include "platform/platform.h"
|
||||
#include "renderer/geometry.h"
|
||||
#include "renderer/render_command.h"
|
||||
#include "renderer/renderer.h"
|
||||
#include "renderer/shader.h"
|
||||
#include "renderer/texture.h"
|
||||
#include "shader_source.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
Engine* Engine::singleton = nullptr;
|
||||
|
||||
Engine::Engine(Platform* platform, Renderer* renderer)
|
||||
: platform_(platform), renderer_(renderer) {
|
||||
assert(!singleton);
|
||||
singleton = this;
|
||||
|
||||
renderer_->SetContextLostCB(std::bind(&Engine::ContextLost, this));
|
||||
|
||||
quad_ = CreateRenderResource<Geometry>();
|
||||
pass_through_shader_ = CreateRenderResource<Shader>();
|
||||
solid_shader_ = CreateRenderResource<Shader>();
|
||||
}
|
||||
|
||||
Engine::~Engine() {
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
Engine& Engine::Get() {
|
||||
return *singleton;
|
||||
}
|
||||
|
||||
bool Engine::Initialize() {
|
||||
// The orthogonal viewport is (-1.0 .. 1.0) for the short edge of the screen.
|
||||
// For the long endge, it's calculated from aspect ratio.
|
||||
if (GetScreenWidth() > GetScreenHeight()) {
|
||||
float aspect_ratio = (float)GetScreenWidth() / (float)GetScreenHeight();
|
||||
LOG << "aspect ratio: " << aspect_ratio;
|
||||
screen_size_ = {aspect_ratio * 2.0f, 2.0f};
|
||||
projection_ = base::Ortho(-aspect_ratio, aspect_ratio, -1.0f, 1.0f);
|
||||
} else {
|
||||
float aspect_ratio = (float)GetScreenHeight() / (float)GetScreenWidth();
|
||||
LOG << "aspect_ratio: " << aspect_ratio;
|
||||
screen_size_ = {2.0f, aspect_ratio * 2.0f};
|
||||
projection_ = base::Ortho(-1.0, 1.0, -aspect_ratio, aspect_ratio);
|
||||
}
|
||||
|
||||
system_font_ = GetAsset<Font>("engine/RobotoMono-Regular.ttf");
|
||||
if (!system_font_) {
|
||||
// Do not fail. Just create a null-font.
|
||||
auto font = std::make_shared<Font>();
|
||||
font->SetImmutable();
|
||||
system_font_ = font;
|
||||
}
|
||||
|
||||
if (!CreateRenderResources())
|
||||
return false;
|
||||
|
||||
game_ = GameFactoryBase::CreateGame("");
|
||||
if (!game_) {
|
||||
printf("No game found to run.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!game_->Initialize()) {
|
||||
LOG << "Failed to initialize the game.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Engine::Shutdown() {
|
||||
LOG << "Shutting down engine.";
|
||||
}
|
||||
|
||||
void Engine::Update(float delta_time) {
|
||||
seconds_accumulated_ += delta_time;
|
||||
|
||||
task_runner_.Run();
|
||||
|
||||
game_->Update(delta_time);
|
||||
|
||||
fps_seconds_ += delta_time;
|
||||
if (fps_seconds_ >= 1) {
|
||||
fps_ = renderer_->GetAndResetFPS();
|
||||
fps_seconds_ = 0;
|
||||
}
|
||||
|
||||
if (stats_.IsVisible())
|
||||
PrintStats();
|
||||
}
|
||||
|
||||
void Engine::Draw(float frame_frac) {
|
||||
auto cmd = std::make_unique<CmdClear>();
|
||||
cmd->rgba = {0, 0, 0, 1};
|
||||
renderer_->EnqueueCommand(std::move(cmd));
|
||||
renderer_->EnqueueCommand(std::make_unique<CmdEableBlend>());
|
||||
|
||||
game_->Draw(frame_frac);
|
||||
|
||||
if (stats_.IsVisible())
|
||||
stats_.Draw();
|
||||
|
||||
renderer_->EnqueueCommand(std::make_unique<CmdPresent>());
|
||||
}
|
||||
|
||||
void Engine::LostFocus() {
|
||||
if (game_)
|
||||
game_->LostFocus();
|
||||
}
|
||||
|
||||
void Engine::GainedFocus() {
|
||||
if (game_)
|
||||
game_->GainedFocus();
|
||||
}
|
||||
|
||||
void Engine::TrimMemory() {
|
||||
LOG << "Trimming memory.";
|
||||
assets_.clear();
|
||||
}
|
||||
|
||||
void Engine::Exit() {
|
||||
platform_->Exit();
|
||||
}
|
||||
|
||||
Vector2 Engine::ToScale(const Vector2& vec) {
|
||||
return GetScreenSize() * vec /
|
||||
Vector2((float)GetScreenWidth(), (float)GetScreenHeight());
|
||||
}
|
||||
|
||||
Vector2 Engine::ToPosition(const Vector2& vec) {
|
||||
return ToScale(vec) - GetScreenSize() / 2.0f;
|
||||
}
|
||||
|
||||
void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) {
|
||||
switch (event->GetType()) {
|
||||
case InputEvent::kTap:
|
||||
if (((GetScreenSize() / 2) * 0.9f - event->GetVector(0)).Magnitude() <=
|
||||
0.25f) {
|
||||
SetSatsVisible(!stats_.IsVisible());
|
||||
// Consume event.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case InputEvent::kKeyPress:
|
||||
if (event->GetKeyPress() == 's') {
|
||||
SetSatsVisible(!stats_.IsVisible());
|
||||
// Consume event.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case InputEvent::kDrag:
|
||||
if (stats_.IsVisible()) {
|
||||
if ((stats_.GetOffset() - event->GetVector(0)).Magnitude() <=
|
||||
stats_.GetScale().y)
|
||||
stats_.SetOffset(event->GetVector(0));
|
||||
// TODO: Enqueue DragCancel so we can consume this event.
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
input_queue_.push_back(std::move(event));
|
||||
}
|
||||
|
||||
std::unique_ptr<InputEvent> Engine::GetNextInputEvent() {
|
||||
std::unique_ptr<InputEvent> event;
|
||||
if (!input_queue_.empty()) {
|
||||
event.swap(input_queue_.front());
|
||||
input_queue_.pop_front();
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
int Engine::GetScreenWidth() const {
|
||||
return renderer_->screen_width();
|
||||
}
|
||||
|
||||
int Engine::GetScreenHeight() const {
|
||||
return renderer_->screen_height();
|
||||
}
|
||||
|
||||
int Engine::GetDeviceDpi() const {
|
||||
return platform_->GetDeviceDpi();
|
||||
}
|
||||
|
||||
const std::string& Engine::GetRootPath() const {
|
||||
return platform_->GetRootPath();
|
||||
}
|
||||
|
||||
bool Engine::IsMobile() const {
|
||||
return platform_->mobile_device();
|
||||
}
|
||||
|
||||
std::shared_ptr<RenderResource> Engine::CreateRenderResourceInternal(
|
||||
RenderResourceFactoryBase& factory) {
|
||||
return renderer_->CreateResource(factory);
|
||||
}
|
||||
|
||||
std::shared_ptr<Asset> Engine::GetAssetInternal(AssetFactoryBase& factory) {
|
||||
auto it = assets_.find(factory.name());
|
||||
if (it != assets_.end())
|
||||
return it->second;
|
||||
|
||||
auto asset = factory.Create();
|
||||
if (!asset->Load(factory.name().c_str()))
|
||||
return nullptr;
|
||||
asset->SetImmutable();
|
||||
|
||||
assets_[factory.name()] = asset;
|
||||
return asset;
|
||||
}
|
||||
|
||||
void Engine::ContextLost() {
|
||||
if (!task_runner_.IsBoundToCurrentThread()) {
|
||||
task_runner_.Enqueue(std::bind(&Engine::ContextLost, this));
|
||||
return;
|
||||
}
|
||||
|
||||
renderer_->DestroyAllResources();
|
||||
|
||||
CreateRenderResources();
|
||||
|
||||
game_->ContextLost();
|
||||
}
|
||||
|
||||
bool Engine::CreateRenderResources() {
|
||||
// Create the quad geometry we can reuse for all sprites.
|
||||
auto quad_mesh = GetAsset<Mesh>("engine/quad.mesh");
|
||||
if (!quad_mesh) {
|
||||
LOG << "Could not create quad mesh.";
|
||||
return false;
|
||||
}
|
||||
quad_->Create(quad_mesh);
|
||||
|
||||
// Create the shader we can reuse for texture rendering.
|
||||
auto pts_source = GetAsset<ShaderSource>("engine/pass_through.glsl");
|
||||
if (!pts_source) {
|
||||
LOG << "Could not create pass through shader.";
|
||||
return false;
|
||||
}
|
||||
pass_through_shader_->Create(pts_source, quad_->vertex_description());
|
||||
|
||||
// Create the shader we can reuse for solid rendering.
|
||||
auto ss_source = GetAsset<ShaderSource>("engine/solid.glsl");
|
||||
if (!ss_source) {
|
||||
LOG << "Could not create solid shader.";
|
||||
return false;
|
||||
}
|
||||
solid_shader_->Create(ss_source, quad_->vertex_description());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Engine::SetSatsVisible(bool visible) {
|
||||
stats_.SetVisible(visible);
|
||||
if (visible)
|
||||
stats_.Create(CreateRenderResource<Texture>());
|
||||
else
|
||||
stats_.Destory();
|
||||
}
|
||||
|
||||
void Engine::PrintStats() {
|
||||
constexpr int width = 200;
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
line = "fps: ";
|
||||
line += std::to_string(fps_);
|
||||
lines.push_back(line);
|
||||
line = "cmd: ";
|
||||
line += std::to_string(renderer_->global_queue_size() +
|
||||
renderer_->render_queue_size());
|
||||
lines.push_back(line);
|
||||
|
||||
constexpr int margin = 5;
|
||||
int line_height = system_font_->GetLineHeight();
|
||||
int image_width = width + margin * 2;
|
||||
int image_height = (line_height + margin) * lines.size() + margin;
|
||||
|
||||
auto image = std::make_shared<Image>();
|
||||
image->Create(image_width, image_height);
|
||||
image->Clear({1, 1, 1, 0.08f});
|
||||
|
||||
Worker worker(2);
|
||||
int y = margin;
|
||||
for (auto& text : lines) {
|
||||
worker.Enqueue(std::bind(&Font::Print, system_font_, margin, y,
|
||||
text.c_str(), image->GetBuffer(),
|
||||
image->GetWidth()));
|
||||
y += line_height + margin;
|
||||
}
|
||||
worker.Join();
|
||||
|
||||
image->SetImmutable();
|
||||
stats_.GetTexture()->Update(image);
|
||||
stats_.AutoScale();
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,154 @@
|
|||
#ifndef ENGINE_H
|
||||
#define ENGINE_H
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../base/random.h"
|
||||
#include "../base/task_runner.h"
|
||||
#include "../base/vecmath.h"
|
||||
#include "asset.h"
|
||||
#include "image_quad.h"
|
||||
#include "renderer/render_resource.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Font;
|
||||
class Game;
|
||||
class InputEvent;
|
||||
class Renderer;
|
||||
struct RenderCommand;
|
||||
class Platform;
|
||||
class Geometry;
|
||||
class Shader;
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
Engine(Platform* platform, Renderer* renderer);
|
||||
~Engine();
|
||||
|
||||
static Engine& Get();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void Update(float delta_time);
|
||||
void Draw(float frame_frac);
|
||||
|
||||
void LostFocus();
|
||||
void GainedFocus();
|
||||
|
||||
void TrimMemory();
|
||||
|
||||
void Exit();
|
||||
|
||||
// Convert size from pixels to viewport scale.
|
||||
base::Vector2 ToScale(const base::Vector2& vec);
|
||||
|
||||
// Convert position form pixels to viewport coordinates.
|
||||
base::Vector2 ToPosition(const base::Vector2& vec);
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> CreateRenderResource() {
|
||||
RenderResourceFactory<T> factory;
|
||||
return std::dynamic_pointer_cast<T>(CreateRenderResourceInternal(factory));
|
||||
}
|
||||
|
||||
// Returns immutable asset that can be accessed between multiple threads
|
||||
// without locking. Returns nullptr if no asset was found with the given name.
|
||||
template <typename T>
|
||||
std::shared_ptr<const T> GetAsset(const std::string& name) {
|
||||
AssetFactory<T> factory(name);
|
||||
return std::dynamic_pointer_cast<T>(GetAssetInternal(factory));
|
||||
}
|
||||
|
||||
void AddInputEvent(std::unique_ptr<InputEvent> event);
|
||||
std::unique_ptr<InputEvent> GetNextInputEvent();
|
||||
|
||||
// Access to the render resources.
|
||||
std::shared_ptr<Geometry> GetQuad() { return quad_; }
|
||||
std::shared_ptr<Shader> GetPassThroughShader() {
|
||||
return pass_through_shader_;
|
||||
}
|
||||
std::shared_ptr<Shader> GetSolidShader() { return solid_shader_; }
|
||||
|
||||
std::shared_ptr<const eng::Font> GetSystemFont() { return system_font_; }
|
||||
|
||||
base::Random& GetRandomGenerator() { return random_; }
|
||||
|
||||
Game* GetGame() { return game_.get(); }
|
||||
|
||||
// Return screen width/height in pixels.
|
||||
int GetScreenWidth() const;
|
||||
int GetScreenHeight() const;
|
||||
|
||||
// Return screen size in viewport scale.
|
||||
base::Vector2 GetScreenSize() const { return screen_size_; }
|
||||
|
||||
const base::Matrix4x4& GetProjectionMarix() const { return projection_; }
|
||||
|
||||
int GetDeviceDpi() const;
|
||||
|
||||
const std::string& GetRootPath() const;
|
||||
|
||||
bool IsMobile() const;
|
||||
|
||||
float seconds_accumulated() const { return seconds_accumulated_; }
|
||||
|
||||
private:
|
||||
static Engine* singleton;
|
||||
|
||||
Platform* platform_ = nullptr;
|
||||
|
||||
Renderer* renderer_ = nullptr;
|
||||
|
||||
std::unique_ptr<Game> game_;
|
||||
|
||||
// Asset cache.
|
||||
std::unordered_map<std::string, std::shared_ptr<Asset>> assets_;
|
||||
|
||||
std::shared_ptr<Geometry> quad_;
|
||||
std::shared_ptr<Shader> pass_through_shader_;
|
||||
std::shared_ptr<Shader> solid_shader_;
|
||||
|
||||
base::Vector2 screen_size_ = {0, 0};
|
||||
base::Matrix4x4 projection_;
|
||||
|
||||
std::shared_ptr<const eng::Font> system_font_;
|
||||
|
||||
ImageQuad stats_;
|
||||
|
||||
float fps_seconds_ = 0;
|
||||
int fps_ = 0;
|
||||
|
||||
float seconds_accumulated_ = 0.0f;
|
||||
|
||||
std::deque<std::unique_ptr<InputEvent>> input_queue_;
|
||||
|
||||
base::TaskRunner task_runner_;
|
||||
|
||||
base::Random random_;
|
||||
|
||||
std::shared_ptr<RenderResource> CreateRenderResourceInternal(
|
||||
RenderResourceFactoryBase& factory);
|
||||
|
||||
std::shared_ptr<Asset> GetAssetInternal(AssetFactoryBase& factory);
|
||||
|
||||
void ContextLost();
|
||||
|
||||
bool CreateRenderResources();
|
||||
|
||||
void KillUnusedResources(float delta_time);
|
||||
|
||||
void SetSatsVisible(bool visible);
|
||||
void PrintStats();
|
||||
|
||||
Engine(const Engine&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // ENGINE_H
|
|
@ -0,0 +1,199 @@
|
|||
#include "font.h"
|
||||
|
||||
#include "../base/asset_file.h"
|
||||
#include "../base/log.h"
|
||||
#include "engine.h"
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "../third_party/stb/stb_truetype.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
bool Font::Load(const std::string& file_name) {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Font is immutable. Failed to load.";
|
||||
return false;
|
||||
}
|
||||
|
||||
SetName(file_name);
|
||||
|
||||
// Read the font file.
|
||||
size_t buffer_size = 0;
|
||||
std::unique_ptr<char[]> buffer = base::AssetFile::ReadWholeFile(
|
||||
file_name.c_str(), Engine::Get().GetRootPath().c_str(), &buffer_size);
|
||||
if (!buffer) {
|
||||
LOG << "Failed to read font file.";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
do {
|
||||
// Allocate a cache bitmap for the glyphs.
|
||||
// This is one 8 bit channel intensity data.
|
||||
// It's tighly packed.
|
||||
glyph_cache_ = std::make_unique<uint8_t[]>(kGlyphSize * kGlyphSize);
|
||||
if (!glyph_cache_) {
|
||||
LOG << "Failed to allocate glyph cache.";
|
||||
break;
|
||||
}
|
||||
|
||||
// Rasterize glyphs and pack them into the cache.
|
||||
const float kFontHeight = 32.0f;
|
||||
if (stbtt_BakeFontBitmap((unsigned char*)buffer.get(), 0, kFontHeight,
|
||||
glyph_cache_.get(), kGlyphSize, kGlyphSize,
|
||||
kFirstChar, kNumChars, glyph_info_) <= 0) {
|
||||
LOG << "Failed to bake the glyph cache: " << result;
|
||||
glyph_cache_.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
result = true;
|
||||
} while (0);
|
||||
|
||||
int x0, y0, x1, y1;
|
||||
CalculateBoundingBox("`IlfKgjy_{)", x0, y0, x1, y1);
|
||||
line_height_ = y1 - y0;
|
||||
yoff_ = -y0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void StretchBlit_I8_to_RGBA32(int dst_x0,
|
||||
int dst_y0,
|
||||
int dst_x1,
|
||||
int dst_y1,
|
||||
int src_x0,
|
||||
int src_y0,
|
||||
int src_x1,
|
||||
int src_y1,
|
||||
uint8_t* dst_rgba,
|
||||
int dst_pitch,
|
||||
const uint8_t* src_i,
|
||||
int src_pitch) {
|
||||
// LOG << "-- StretchBlit: --";
|
||||
// LOG << "dst: rect(" << dst_x0 << ", " << dst_y0 << ")..("
|
||||
// << dst_x1 << ".." << dst_y1 << "), pitch(" << dst_pitch << ")";
|
||||
// LOG << "src: rect(" << src_x0 << ", " << src_y0 << ")..("
|
||||
// << src_x1 << ".." << src_y1 << "), pitch(" << src_pitch << ")";
|
||||
|
||||
int dst_width = dst_x1 - dst_x0, dst_height = dst_y1 - dst_y0,
|
||||
src_width = src_x1 - src_x0, src_height = src_y1 - src_y0;
|
||||
|
||||
// int dst_dx = dst_width > 0 ? 1 : -1,
|
||||
// dst_dy = dst_height > 0 ? 1 : -1;
|
||||
|
||||
// LOG << "dst_width = " << dst_width << ", dst_height = " << dst_height;
|
||||
// LOG << "src_width = " << src_width << ", src_height = " << src_height;
|
||||
|
||||
uint8_t* dst = dst_rgba + (dst_x0 + dst_y0 * dst_pitch) * 4;
|
||||
const uint8_t* src = src_i + (src_x0 + src_y0 * src_pitch) * 1;
|
||||
|
||||
// First check if we have to stretch at all.
|
||||
if ((dst_width == src_width) && (dst_height == src_height)) {
|
||||
// No, straight blit then.
|
||||
for (int y = 0; y < dst_height; ++y) {
|
||||
for (int x = 0; x < dst_width; ++x) {
|
||||
// Alpha test, no blending for now.
|
||||
if (src[x]) {
|
||||
#if 0
|
||||
dst[x * 4 + 0] = src[x];
|
||||
dst[x * 4 + 1] = src[x];
|
||||
dst[x * 4 + 2] = src[x];
|
||||
dst[x * 4 + 3] = 255;
|
||||
#else
|
||||
dst[x * 4 + 3] = src[x];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
dst += dst_pitch * 4;
|
||||
src += src_pitch * 1;
|
||||
}
|
||||
} else {
|
||||
// ToDo
|
||||
}
|
||||
}
|
||||
|
||||
void Font::CalculateBoundingBox(const std::string& text,
|
||||
int& x0,
|
||||
int& y0,
|
||||
int& x1,
|
||||
int& y1) const {
|
||||
x0 = 0;
|
||||
y0 = 0;
|
||||
x1 = 0;
|
||||
y1 = 0;
|
||||
|
||||
if (!glyph_cache_)
|
||||
return;
|
||||
|
||||
float x = 0, y = 0;
|
||||
|
||||
const char* ptr = text.c_str();
|
||||
while (*ptr) {
|
||||
if (*ptr >= kFirstChar /*&& *ptr < (kFirstChar + kNumChars)*/) {
|
||||
stbtt_aligned_quad q;
|
||||
stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar,
|
||||
&x, &y, &q, 1);
|
||||
|
||||
int ix0 = (int)q.x0, iy0 = (int)q.y0, ix1 = (int)q.x1, iy1 = (int)q.y1;
|
||||
|
||||
if (ix0 < x0)
|
||||
x0 = ix0;
|
||||
if (iy0 < y0)
|
||||
y0 = iy0;
|
||||
if (ix1 > x1)
|
||||
x1 = ix1;
|
||||
if (iy1 > y1)
|
||||
y1 = iy1;
|
||||
|
||||
++ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Font::CalculateBoundingBox(const std::string& text,
|
||||
int& width,
|
||||
int& height) const {
|
||||
int x0, y0, x1, y1;
|
||||
CalculateBoundingBox(text, x0, y0, x1, y1);
|
||||
width = x1 - x0;
|
||||
height = y1 - y0;
|
||||
// LOG << "width = " << width << ", height = " << height;
|
||||
}
|
||||
|
||||
void Font::Print(int x,
|
||||
int y,
|
||||
const std::string& text,
|
||||
uint8_t* buffer,
|
||||
int width) const {
|
||||
// LOG("Font::Print() = %s\n", text);
|
||||
|
||||
if (!glyph_cache_)
|
||||
return;
|
||||
|
||||
float fx = (float)x, fy = (float)y + (float)yoff_;
|
||||
|
||||
const char* ptr = text.c_str();
|
||||
while (*ptr) {
|
||||
if (*ptr >= kFirstChar /*&& *ptr < (kFirstChar + kNumChars)*/) {
|
||||
stbtt_aligned_quad q;
|
||||
stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar,
|
||||
&fx, &fy, &q, 1);
|
||||
|
||||
// LOG("-- glyph --\nxy = (%f %f) .. (%f %f)\nuv = (%f %f) .. (%f %f)\n",
|
||||
// q.x0, q.y0, q.x1, q.y1, q.s0, q.t0, q.s1, q.t1);
|
||||
|
||||
int ix0 = (int)q.x0, iy0 = (int)q.y0, ix1 = (int)q.x1, iy1 = (int)q.y1,
|
||||
iu0 = (int)(q.s0 * kGlyphSize), iv0 = (int)(q.t0 * kGlyphSize),
|
||||
iu1 = (int)(q.s1 * kGlyphSize), iv1 = (int)(q.t1 * kGlyphSize);
|
||||
|
||||
StretchBlit_I8_to_RGBA32(ix0, iy0, ix1, iy1, iu0, iv0, iu1, iv1, buffer,
|
||||
width, glyph_cache_.get(), kGlyphSize);
|
||||
|
||||
++ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef FONT_H
|
||||
#define FONT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../third_party/stb/stb_truetype.h"
|
||||
#include "asset.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Font : public Asset {
|
||||
public:
|
||||
Font() = default;
|
||||
~Font() override = default;
|
||||
|
||||
bool Load(const std::string& file_name) override;
|
||||
|
||||
void CalculateBoundingBox(const std::string& text,
|
||||
int& width,
|
||||
int& height) const;
|
||||
void CalculateBoundingBox(const std::string& text,
|
||||
int& x0,
|
||||
int& y0,
|
||||
int& x1,
|
||||
int& y1) const;
|
||||
|
||||
void Print(int x,
|
||||
int y,
|
||||
const std::string& text,
|
||||
uint8_t* buffer,
|
||||
int width) const;
|
||||
|
||||
int GetLineHeight() const { return line_height_; }
|
||||
|
||||
bool IsValid() const { return !!glyph_cache_; }
|
||||
|
||||
private:
|
||||
enum Constants {
|
||||
kGlyphSize = 512,
|
||||
kFirstChar = 32, // ' ' (space)
|
||||
kNumChars = 96 // Covers almost all ASCII chars.
|
||||
};
|
||||
|
||||
std::unique_ptr<uint8_t[]> glyph_cache_; // Image data.
|
||||
stbtt_bakedchar glyph_info_[kNumChars]; // Coordinates and advance.
|
||||
|
||||
int line_height_ = 0;
|
||||
int yoff_ = 0;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // FONT_H
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef GAME_H
|
||||
#define GAME_H
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Game {
|
||||
public:
|
||||
Game() = default;
|
||||
virtual ~Game() = default;
|
||||
|
||||
virtual bool Initialize() = 0;
|
||||
|
||||
virtual void Update(float delta_time) = 0;
|
||||
|
||||
virtual void Draw(float frame_frac) = 0;
|
||||
|
||||
virtual void ContextLost() = 0;
|
||||
|
||||
virtual void LostFocus() = 0;
|
||||
|
||||
virtual void GainedFocus() = 0;
|
||||
|
||||
private:
|
||||
Game(const Game&) = delete;
|
||||
Game& operator=(const Game&) = delete;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // GAME_H
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef GAME_FACTORY_H
|
||||
#define GAME_FACTORY_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define DECLARE_GAME_BEGIN \
|
||||
std::vector<std::pair<std::string, eng::GameFactoryBase*>> \
|
||||
eng::GameFactoryBase::game_classes = {
|
||||
#define DECLARE_GAME(CLASS) {#CLASS, new eng::GameFactory<CLASS>()},
|
||||
#define DECLARE_GAME_END };
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Game;
|
||||
|
||||
class GameFactoryBase {
|
||||
public:
|
||||
virtual ~GameFactoryBase() = default;
|
||||
|
||||
static std::unique_ptr<Game> CreateGame(const std::string& name) {
|
||||
if (name.empty())
|
||||
return game_classes.size() > 0
|
||||
? game_classes.begin()->second->CreateGame()
|
||||
: nullptr;
|
||||
for (auto& element : game_classes) {
|
||||
if (element.first == name)
|
||||
return element.second->CreateGame();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual std::unique_ptr<Game> CreateGame() { return nullptr; }
|
||||
|
||||
static std::vector<std::pair<std::string, GameFactoryBase*>> game_classes;
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
class GameFactory : public GameFactoryBase {
|
||||
public:
|
||||
~GameFactory() override = default;
|
||||
|
||||
private:
|
||||
using GameType = Type;
|
||||
|
||||
std::unique_ptr<Game> CreateGame() override {
|
||||
return std::make_unique<GameType>();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // GAME_FACTORY_H
|
|
@ -0,0 +1,268 @@
|
|||
#include "image.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "../base/asset_file.h"
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "../base/misc.h"
|
||||
#include "engine.h"
|
||||
|
||||
// This 3rd party library is written in C and uses malloc, which means that we
|
||||
// have to do the same.
|
||||
#define STBI_NO_STDIO
|
||||
#include "../third_party/stb/stb_image.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
Image::Image() = default;
|
||||
|
||||
Image::Image(const Image& other) {
|
||||
Copy(other);
|
||||
}
|
||||
|
||||
Image::~Image() = default;
|
||||
|
||||
Image& Image::operator=(const Image& other) {
|
||||
Copy(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Image::Create(int w, int h) {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Image is immutable. Failed to create.";
|
||||
return false;
|
||||
}
|
||||
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
buffer_.reset((uint8_t*)AlignedAlloc(w * h * 4 * sizeof(uint8_t)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Image::Copy(const Image& other) {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Image is immutable. Failed to copy.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (other.buffer_) {
|
||||
int size = other.GetSize();
|
||||
buffer_.reset((uint8_t*)AlignedAlloc(size));
|
||||
memcpy(buffer_.get(), other.buffer_.get(), size);
|
||||
}
|
||||
width_ = other.width_;
|
||||
height_ = other.height_;
|
||||
format_ = other.format_;
|
||||
}
|
||||
|
||||
bool Image::Load(const std::string& file_name) {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Image is immutable. Failed to load.";
|
||||
return false;
|
||||
}
|
||||
|
||||
SetName(file_name);
|
||||
|
||||
size_t buffer_size = 0;
|
||||
std::unique_ptr<char[]> file_buffer = AssetFile::ReadWholeFile(
|
||||
file_name.c_str(), Engine::Get().GetRootPath().c_str(), &buffer_size);
|
||||
if (!file_buffer) {
|
||||
LOG << "Failed to read file: " << file_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
int w, h, c;
|
||||
buffer_.reset((uint8_t*)stbi_load_from_memory(
|
||||
(const stbi_uc*)file_buffer.get(), buffer_size, &w, &h, &c, 0));
|
||||
if (!buffer_) {
|
||||
LOG << "Failed to load image file: " << file_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* converted_buffer = NULL;
|
||||
switch (c) {
|
||||
case 1:
|
||||
// LOG("Converting image from 1 to 4 channels.\n");
|
||||
// Assume it's an intensity, duplicate it to RGB and fill A with opaque.
|
||||
converted_buffer = (uint8_t*)AlignedAlloc(w * h * 4 * sizeof(uint8_t));
|
||||
for (int i = 0; i < w * h; ++i) {
|
||||
converted_buffer[i * 4 + 0] = buffer_[i];
|
||||
converted_buffer[i * 4 + 1] = buffer_[i];
|
||||
converted_buffer[i * 4 + 2] = buffer_[i];
|
||||
converted_buffer[i * 4 + 3] = 255;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// LOG("Converting image from 3 to 4 channels.\n");
|
||||
// Add an opaque channel.
|
||||
converted_buffer = (uint8_t*)AlignedAlloc(w * h * 4 * sizeof(uint8_t));
|
||||
for (int i = 0; i < w * h; ++i) {
|
||||
converted_buffer[i * 4 + 0] = buffer_[i * 3 + 0];
|
||||
converted_buffer[i * 4 + 1] = buffer_[i * 3 + 1];
|
||||
converted_buffer[i * 4 + 2] = buffer_[i * 3 + 2];
|
||||
converted_buffer[i * 4 + 3] = 255;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
break; // This is the wanted format.
|
||||
|
||||
case 2:
|
||||
default:
|
||||
LOG << "Image had unsuitable number of color components: " << c << " "
|
||||
<< file_name;
|
||||
buffer_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (converted_buffer) {
|
||||
buffer_.reset(converted_buffer);
|
||||
}
|
||||
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
#if 0 // Fill the alpha channel with transparent gradient alpha for testing
|
||||
uint8_t* modifyBuf = buffer;
|
||||
for (int j = 0; j < height; ++j, modifyBuf += width * 4)
|
||||
{
|
||||
for (int i = 0; i < width; ++i)
|
||||
{
|
||||
float dist = sqrt(float(i*i + j*j));
|
||||
float alpha = (((dist > 0.0f ? dist : 0.0f) / sqrt((float)(width * width + height * height))) * 255.0f);
|
||||
modifyBuf[i * 4 + 3] = (unsigned char)alpha;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return !!buffer_;
|
||||
}
|
||||
|
||||
size_t Image::GetSize() const {
|
||||
switch (format_) {
|
||||
case kRGBA32:
|
||||
return width_ * height_ * 4;
|
||||
case kDXT1:
|
||||
return ((width_ + 3) / 4) * ((height_ + 3) / 4) * 8;
|
||||
case kDXT5:
|
||||
return ((width_ + 3) / 4) * ((height_ + 3) / 4) * 16;
|
||||
case kATC:
|
||||
return ((width_ + 3) / 4) * ((height_ + 3) / 4) * 16;
|
||||
case kETC1:
|
||||
return (width_ * height_ * 4) / 8;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Image::ConvertToPow2() {
|
||||
int new_width = RoundUpToPow2(width_);
|
||||
int new_height = RoundUpToPow2(height_);
|
||||
if ((new_width != width_) || (new_height != height_)) {
|
||||
LOG << "Converting image " << GetName() << " from (" << width_ << ", "
|
||||
<< height_ << ") to (" << new_width << ", " << new_height << ")";
|
||||
|
||||
int bigger_size = new_width * new_height * 4 * sizeof(uint8_t);
|
||||
uint8_t* bigger_buffer = (uint8_t*)AlignedAlloc(bigger_size);
|
||||
|
||||
// Fill it with black.
|
||||
memset(bigger_buffer, 0, bigger_size);
|
||||
|
||||
// Copy over the old bitmap.
|
||||
#if 0
|
||||
// Centered in the new bitmap.
|
||||
int offset_x = (new_width - width_) / 2;
|
||||
int offset_y = (new_height - height_) / 2;
|
||||
for (int y = 0; y < height_; ++y)
|
||||
memcpy(bigger_buffer + (offset_x + (y + offset_y) * new_width) * 4,
|
||||
buffer_.get() + y * width_ * 4, width_ * 4);
|
||||
#else
|
||||
for (int y = 0; y < height_; ++y)
|
||||
memcpy(bigger_buffer + (y * new_width) * 4,
|
||||
buffer_.get() + y * width_ * 4, width_ * 4);
|
||||
#endif
|
||||
|
||||
// Swap the buffers and dimensions.
|
||||
buffer_.reset(bigger_buffer);
|
||||
width_ = new_width;
|
||||
height_ = new_height;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* Image::GetBuffer() {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Image is immutable. Failed to return writable buffer.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return buffer_.get();
|
||||
}
|
||||
|
||||
void Image::Clear(Vector4 rgba) {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Image is immutable. Failed to clear.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Quantize the color to target resolution.
|
||||
uint8_t r = (uint8_t)(rgba.x * 255.0f), g = (uint8_t)(rgba.y * 255.0f),
|
||||
b = (uint8_t)(rgba.z * 255.0f), a = (uint8_t)(rgba.w * 255.0f);
|
||||
|
||||
// Fill out the first line manually.
|
||||
for (int w = 0; w < width_; ++w) {
|
||||
buffer_.get()[w * 4 + 0] = r;
|
||||
buffer_.get()[w * 4 + 1] = g;
|
||||
buffer_.get()[w * 4 + 2] = b;
|
||||
buffer_.get()[w * 4 + 3] = a;
|
||||
}
|
||||
|
||||
// Copy the first line to the rest of them.
|
||||
for (int h = 1; h < height_; ++h)
|
||||
memcpy(buffer_.get() + h * width_ * 4, buffer_.get(), width_ * 4);
|
||||
}
|
||||
|
||||
void Image::GradientH() {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Image is immutable. Failed to apply gradient.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill out the first line manually.
|
||||
for (int x = 0; x < width_; ++x) {
|
||||
uint8_t intensity = x > 255 ? 255 : x;
|
||||
buffer_.get()[x * 4 + 0] = intensity;
|
||||
buffer_.get()[x * 4 + 1] = intensity;
|
||||
buffer_.get()[x * 4 + 2] = intensity;
|
||||
buffer_.get()[x * 4 + 3] = 255;
|
||||
}
|
||||
|
||||
// Copy the first line to the rest of them.
|
||||
for (int h = 1; h < height_; ++h)
|
||||
memcpy(buffer_.get() + h * width_ * 4, buffer_.get(), width_ * 4);
|
||||
}
|
||||
|
||||
void Image::GradientV(const Vector4& c1, const Vector4& c2, int height) {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Image is immutable. Failed to apply gradient.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill each section with gradient.
|
||||
for (int h = 0; h < height_; ++h) {
|
||||
Vector4 c = Lerp(c1, c2, fmod(h, height) / (float)height);
|
||||
for (int x = 0; x < width_; ++x) {
|
||||
buffer_.get()[h * width_ * 4 + x * 4 + 0] = c.x * 255;
|
||||
buffer_.get()[h * width_ * 4 + x * 4 + 1] = c.y * 255;
|
||||
buffer_.get()[h * width_ * 4 + x * 4 + 2] = c.z * 255;
|
||||
buffer_.get()[h * width_ * 4 + x * 4 + 3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef IMAGE_H
|
||||
#define IMAGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include "../base/mem.h"
|
||||
#include "../base/vecmath.h"
|
||||
#include "asset.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Image : public Asset {
|
||||
public:
|
||||
enum Format { kRGBA32, kDXT1, kDXT5, kETC1, kATC };
|
||||
|
||||
Image();
|
||||
Image(const Image& other);
|
||||
~Image() override;
|
||||
|
||||
Image& operator=(const Image& other);
|
||||
|
||||
bool Create(int width, int height);
|
||||
void Copy(const Image& other);
|
||||
bool Load(const std::string& file_name) override;
|
||||
|
||||
void ConvertToPow2();
|
||||
|
||||
int GetWidth() const { return width_; }
|
||||
int GetHeight() const { return height_; }
|
||||
|
||||
Format GetFormat() const { return format_; }
|
||||
bool IsCompressed() const { return format_ > kRGBA32; }
|
||||
|
||||
size_t GetSize() const;
|
||||
|
||||
const uint8_t* GetBuffer() const { return buffer_.get(); }
|
||||
uint8_t* GetBuffer();
|
||||
|
||||
bool IsValid() const { return !!buffer_; }
|
||||
|
||||
void Clear(base::Vector4 rgba);
|
||||
void GradientH();
|
||||
void GradientV(const base::Vector4& c1, const base::Vector4& c2, int height);
|
||||
|
||||
private:
|
||||
base::AlignedMem<uint8_t[]>::ScoppedPtr buffer_;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
Format format_ = kRGBA32;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // IMAGE_H
|
|
@ -0,0 +1,87 @@
|
|||
#include "image_quad.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "engine.h"
|
||||
#include "renderer/geometry.h"
|
||||
#include "renderer/shader.h"
|
||||
#include "renderer/texture.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
void ImageQuad::Create(std::shared_ptr<Texture> texture,
|
||||
std::array<int, 2> num_frames,
|
||||
int frame_width,
|
||||
int frame_height) {
|
||||
texture_ = texture;
|
||||
num_frames_ = std::move(num_frames);
|
||||
frame_width_ = frame_width;
|
||||
frame_height_ = frame_height;
|
||||
}
|
||||
|
||||
void ImageQuad::Destory() {
|
||||
texture_.reset();
|
||||
}
|
||||
|
||||
void ImageQuad::AutoScale() {
|
||||
Vector2 dimensions = {GetFrameWidth(), GetFrameHeight()};
|
||||
SetScale(Engine::Get().ToScale(dimensions));
|
||||
Scale((float)Engine::Get().GetDeviceDpi() / 200.0f);
|
||||
}
|
||||
|
||||
void ImageQuad::SetFrame(size_t frame) {
|
||||
assert(frame < GetNumFrames());
|
||||
current_frame_ = frame;
|
||||
}
|
||||
|
||||
size_t ImageQuad::GetNumFrames() {
|
||||
return num_frames_[0] * num_frames_[1];
|
||||
}
|
||||
|
||||
void ImageQuad::Draw() {
|
||||
if (!IsVisible() || !texture_ || !texture_->IsValid())
|
||||
return;
|
||||
|
||||
texture_->Activate();
|
||||
|
||||
Vector2 tex_scale = {GetFrameWidth() / texture_->GetWidth(),
|
||||
GetFrameHeight() / texture_->GetHeight()};
|
||||
|
||||
std::shared_ptr<Geometry> quad = Engine::Get().GetQuad();
|
||||
std::shared_ptr<Shader> shader = Engine::Get().GetPassThroughShader();
|
||||
|
||||
shader->Activate();
|
||||
shader->SetUniform("offset", offset_);
|
||||
shader->SetUniform("scale", scale_);
|
||||
shader->SetUniform("pivot", pivot_);
|
||||
shader->SetUniform("rotation", rotation_);
|
||||
shader->SetUniform("tex_offset", GetUVOffset(current_frame_));
|
||||
shader->SetUniform("tex_scale", tex_scale);
|
||||
shader->SetUniform("projection", Engine::Get().GetProjectionMarix());
|
||||
shader->SetUniform("color", color_);
|
||||
shader->SetUniform("texture", 0);
|
||||
|
||||
quad->Draw();
|
||||
}
|
||||
|
||||
float ImageQuad::GetFrameWidth() const {
|
||||
return frame_width_ > 0 ? (float)frame_width_
|
||||
: texture_->GetWidth() / (float)num_frames_[0];
|
||||
}
|
||||
|
||||
float ImageQuad::GetFrameHeight() const {
|
||||
return frame_height_ > 0 ? (float)frame_height_
|
||||
: texture_->GetHeight() / (float)num_frames_[1];
|
||||
}
|
||||
|
||||
// Return the uv offset for the given frame.
|
||||
Vector2 ImageQuad::GetUVOffset(int frame) const {
|
||||
assert(frame < num_frames_[0] * num_frames_[1]);
|
||||
if (num_frames_[0] == 1 && num_frames_[1] == 1)
|
||||
return {0, 0};
|
||||
return {(float)(frame % num_frames_[0]), (float)(frame / num_frames_[0])};
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,57 @@
|
|||
#ifndef IMAGE_QUAD_H
|
||||
#define IMAGE_QUAD_H
|
||||
|
||||
#include "../base/vecmath.h"
|
||||
#include "animatable.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Texture;
|
||||
|
||||
class ImageQuad : public Animatable {
|
||||
public:
|
||||
ImageQuad() = default;
|
||||
~ImageQuad() override = default;
|
||||
|
||||
void Create(std::shared_ptr<Texture> texture,
|
||||
std::array<int, 2> num_frames = {1, 1},
|
||||
int frame_width = 0,
|
||||
int frame_height = 0);
|
||||
|
||||
void Destory();
|
||||
|
||||
void AutoScale();
|
||||
|
||||
// Animatable interface.
|
||||
void SetFrame(size_t frame) override;
|
||||
size_t GetFrame() override { return current_frame_; }
|
||||
size_t GetNumFrames() override;
|
||||
void SetColor(const base::Vector4& color) override { color_ = color; }
|
||||
base::Vector4 GetColor() const override { return color_; }
|
||||
|
||||
void Draw();
|
||||
|
||||
std::shared_ptr<Texture> GetTexture() { return texture_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Texture> texture_;
|
||||
|
||||
size_t current_frame_ = 0;
|
||||
std::array<int, 2> num_frames_ = {1, 1}; // horizontal, vertical
|
||||
int frame_width_ = 0;
|
||||
int frame_height_ = 0;
|
||||
|
||||
base::Vector4 color_ = {1, 1, 1, 1};
|
||||
|
||||
float GetFrameWidth() const;
|
||||
float GetFrameHeight() const;
|
||||
|
||||
base::Vector2 GetUVOffset(int frame) const;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // IMAGE_QUAD_H
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef INPUT_EVENT_H
|
||||
#define INPUT_EVENT_H
|
||||
|
||||
#include <cassert>
|
||||
#include "../base/vecmath.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class InputEvent {
|
||||
public:
|
||||
enum Type {
|
||||
kInvalid,
|
||||
kTap,
|
||||
kDoubleTap,
|
||||
kDragStart,
|
||||
kDrag,
|
||||
kDragEnd,
|
||||
kDragCancel,
|
||||
kPinchStart,
|
||||
kPinch,
|
||||
kNavigateBack,
|
||||
kKeyPress,
|
||||
kType_Max // Not a type.
|
||||
};
|
||||
|
||||
InputEvent(Type type) : type_(type) {}
|
||||
InputEvent(Type type, const base::Vector2& vec1)
|
||||
: type_(type), vec_{vec1, {0, 0}} {}
|
||||
InputEvent(Type type, const base::Vector2& vec1, const base::Vector2& vec2)
|
||||
: type_(type), vec_{vec1, vec2} {}
|
||||
InputEvent(Type type, char key) : type_(type), key_(key) {}
|
||||
~InputEvent() = default;
|
||||
|
||||
Type GetType() { return type_; }
|
||||
base::Vector2 GetVector(size_t i) {
|
||||
assert(i < 2);
|
||||
return vec_[i];
|
||||
}
|
||||
char GetKeyPress() { return key_; }
|
||||
|
||||
private:
|
||||
Type type_ = kInvalid;
|
||||
base::Vector2 vec_[2] = {{0, 0}, {0, 0}};
|
||||
char key_ = 0;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // INPUT_EVENT_H
|
|
@ -0,0 +1,181 @@
|
|||
#include "mesh.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <cassert>
|
||||
|
||||
#include "../base/asset_file.h"
|
||||
#include "../base/log.h"
|
||||
#include "../third_party/jsoncpp/json.h"
|
||||
#include "engine.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
// Used to parse the vertex layout,
|
||||
// e.g. "p3f;c4b" for "position 3 floats, color 4 bytes".
|
||||
const char Mesh::kLayoutDelimiter[] = ";/ \t";
|
||||
|
||||
bool Mesh::Create(Primitive primitive,
|
||||
const std::string& vertex_description,
|
||||
size_t num_vertices,
|
||||
const void* vertices,
|
||||
DataType index_description,
|
||||
size_t num_indices,
|
||||
const void* indices) {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Mesh is immutable. Failed to create.";
|
||||
return false;
|
||||
}
|
||||
|
||||
primitive_ = primitive;
|
||||
num_vertices_ = num_vertices;
|
||||
index_description_ = index_description;
|
||||
num_indices_ = num_indices;
|
||||
|
||||
if (!ParseVertexDescription(vertex_description, vertex_description_)) {
|
||||
LOG << "Failed to parse vertex description.";
|
||||
return false;
|
||||
}
|
||||
|
||||
int vertex_buffer_size = GetVertexSize() * num_vertices_;
|
||||
if (vertex_buffer_size > 0) {
|
||||
vertices_ = std::make_unique<char[]>(vertex_buffer_size);
|
||||
memcpy(vertices_.get(), vertices, vertex_buffer_size);
|
||||
}
|
||||
|
||||
if (!indices)
|
||||
return true;
|
||||
|
||||
int index_buffer_size = GetIndexSize() * num_indices_;
|
||||
if (index_buffer_size > 0) {
|
||||
indices_ = std::make_unique<char[]>(index_buffer_size);
|
||||
memcpy(indices_.get(), indices, index_buffer_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mesh::Load(const std::string& file_name) {
|
||||
if (IsImmutable()) {
|
||||
LOG << "Error: Mesh is immutable. Failed to load.";
|
||||
return false;
|
||||
}
|
||||
|
||||
SetName(file_name);
|
||||
|
||||
size_t buffer_size = 0;
|
||||
std::unique_ptr<char[]> json_mesh = base::AssetFile::ReadWholeFile(
|
||||
file_name.c_str(), Engine::Get().GetRootPath().c_str(), &buffer_size,
|
||||
true);
|
||||
if (!json_mesh) {
|
||||
LOG << "Failed to read file: " << file_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string err;
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder builder;
|
||||
const std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
|
||||
if (!reader->parse(json_mesh.get(), json_mesh.get() + buffer_size, &root,
|
||||
&err)) {
|
||||
LOG << "Failed to load mesh. Json parser error: " << err;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& primitive_str = root["primitive"].asString();
|
||||
if (primitive_str == "Triangles") {
|
||||
primitive_ = kPrimitive_Triangles;
|
||||
} else if (primitive_str == "TriangleStrip") {
|
||||
primitive_ = kPrimitive_TriangleStrip;
|
||||
} else {
|
||||
LOG << "Failed to load mesh. Invalid primitive: " << primitive_str;
|
||||
return false;
|
||||
}
|
||||
|
||||
num_vertices_ = root["num_vertices"].asUInt();
|
||||
|
||||
if (!ParseVertexDescription(root["vertex_description"].asString(),
|
||||
vertex_description_)) {
|
||||
LOG << "Failed to parse vertex description.";
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t array_size = 0;
|
||||
for (auto& attr : vertex_description_) {
|
||||
array_size += std::get<2>(attr);
|
||||
}
|
||||
array_size *= num_vertices_;
|
||||
|
||||
const Json::Value vertices = root["vertices"];
|
||||
if (vertices.size() != array_size) {
|
||||
LOG << "Failed to load mesh. Vertex array size: " << vertices.size()
|
||||
<< ", expected " << array_size;
|
||||
return false;
|
||||
}
|
||||
|
||||
int vertex_buffer_size = GetVertexSize() * num_vertices_;
|
||||
if (vertex_buffer_size <= 0) {
|
||||
LOG << "Failed to load mesh. Invalid vertex size.";
|
||||
return false;
|
||||
}
|
||||
|
||||
vertices_ = std::make_unique<char[]>(vertex_buffer_size);
|
||||
|
||||
char* dst = vertices_.get();
|
||||
int i = 0;
|
||||
while (i < vertices.size()) {
|
||||
for (auto& attr : vertex_description_) {
|
||||
auto [attrib_type, data_type, num_elements, type_size] = attr;
|
||||
while (num_elements--) {
|
||||
switch (data_type) {
|
||||
case kDataType_Byte:
|
||||
*((unsigned char*)dst) = (unsigned char)vertices[i].asUInt();
|
||||
break;
|
||||
case kDataType_Float:
|
||||
*((float*)dst) = (float)vertices[i].asFloat();
|
||||
break;
|
||||
case kDataType_Int:
|
||||
*((int*)dst) = vertices[i].asInt();
|
||||
break;
|
||||
case kDataType_Short:
|
||||
*((short*)dst) = (short)vertices[i].asInt();
|
||||
break;
|
||||
case kDataType_UInt:
|
||||
*((unsigned int*)dst) = vertices[i].asUInt();
|
||||
break;
|
||||
case kDataType_UShort:
|
||||
*((unsigned short*)dst) = (unsigned short)vertices[i].asUInt();
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
dst += type_size;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t Mesh::GetVertexSize() const {
|
||||
unsigned int size = 0;
|
||||
for (auto& attr : vertex_description_) {
|
||||
size += std::get<2>(attr) * std::get<3>(attr);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t Mesh::GetIndexSize() const {
|
||||
switch (index_description_) {
|
||||
case kDataType_Byte:
|
||||
return sizeof(char);
|
||||
case kDataType_UShort:
|
||||
return sizeof(unsigned short);
|
||||
case kDataType_UInt:
|
||||
return sizeof(unsigned int);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,56 @@
|
|||
#ifndef MESH_H
|
||||
#define MESH_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "asset.h"
|
||||
#include "renderer/renderer_types.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Mesh : public Asset {
|
||||
public:
|
||||
static const char kLayoutDelimiter[];
|
||||
|
||||
Mesh() = default;
|
||||
~Mesh() override = default;
|
||||
|
||||
bool Create(Primitive primitive,
|
||||
const std::string& vertex_description,
|
||||
size_t num_vertices,
|
||||
const void* vertices,
|
||||
DataType index_description = kDataType_Invalid,
|
||||
size_t num_indices = 0,
|
||||
const void* indices = nullptr);
|
||||
|
||||
bool Load(const std::string& file_name) override;
|
||||
|
||||
const void* GetVertices() const { return (void*)vertices_.get(); }
|
||||
const void* GetIndices() const { return (void*)indices_.get(); }
|
||||
|
||||
size_t GetVertexSize() const;
|
||||
size_t GetIndexSize() const;
|
||||
|
||||
Primitive primitive() const { return primitive_; }
|
||||
const VertexDescripton& vertex_description() const {
|
||||
return vertex_description_;
|
||||
}
|
||||
size_t num_vertices() const { return num_vertices_; }
|
||||
DataType index_description() const { return index_description_; }
|
||||
size_t num_indices() const { return num_indices_; }
|
||||
|
||||
bool IsValid() const { return !!vertices_.get(); }
|
||||
|
||||
protected:
|
||||
Primitive primitive_ = kPrimitive_TriangleStrip;
|
||||
VertexDescripton vertex_description_;
|
||||
size_t num_vertices_ = 0;
|
||||
DataType index_description_ = kDataType_Invalid;
|
||||
size_t num_indices_ = 0;
|
||||
std::unique_ptr<char[]> vertices_;
|
||||
std::unique_ptr<char[]> indices_;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // MESH_H
|
|
@ -0,0 +1,73 @@
|
|||
#include "platform.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../engine.h"
|
||||
#include "../renderer/renderer.h"
|
||||
|
||||
// Save battery on mobile devices.
|
||||
#define USE_SLEEP
|
||||
|
||||
namespace eng {
|
||||
|
||||
Platform::InternalError Platform::internal_error;
|
||||
|
||||
void Platform::Shutdown() {
|
||||
LOG << "Shutting down platform.";
|
||||
renderer_->Shutdown();
|
||||
}
|
||||
|
||||
void Platform::RunMainLoop() {
|
||||
engine_ = std::make_unique<Engine>(this, renderer_.get());
|
||||
if (!engine_->Initialize()) {
|
||||
LOG << "Failed to initialize the engine.";
|
||||
throw internal_error;
|
||||
}
|
||||
|
||||
// Use fixed time steps.
|
||||
constexpr float time_step = 1.0f / 60.0f;
|
||||
constexpr float speed = 1.0f;
|
||||
constexpr float epsilon = 0.0001f;
|
||||
|
||||
timer_.Reset();
|
||||
float accumulator = 0.0;
|
||||
float frame_frac = 0.0f;
|
||||
|
||||
for (;;) {
|
||||
engine_->Draw(frame_frac);
|
||||
|
||||
// Accumulate time.
|
||||
#ifdef USE_SLEEP
|
||||
while (accumulator < time_step) {
|
||||
timer_.Update();
|
||||
accumulator += timer_.GetSecondsPassed();
|
||||
if (time_step - accumulator > epsilon) {
|
||||
float sleep_time = time_step - accumulator - epsilon;
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::microseconds((int)(sleep_time * 1000000.0f)));
|
||||
}
|
||||
};
|
||||
#else
|
||||
timer_.Update();
|
||||
accumulator += timer_.GetSecondsPassed();
|
||||
#endif // USE_SLEEP
|
||||
|
||||
// Subdivide the frame time.
|
||||
while (accumulator >= time_step) {
|
||||
Update();
|
||||
if (should_exit_) {
|
||||
engine_->Shutdown();
|
||||
engine_.reset();
|
||||
return;
|
||||
}
|
||||
engine_->Update(time_step * speed);
|
||||
accumulator -= time_step;
|
||||
};
|
||||
|
||||
// Calculate frame fraction from remainder of the frame time.
|
||||
frame_frac = accumulator / time_step;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,86 @@
|
|||
#ifndef PLATFORM_H
|
||||
#define PLATFORM_H
|
||||
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../../base/timer.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
struct android_app;
|
||||
struct AInputEvent;
|
||||
|
||||
namespace ndk_helper {
|
||||
class TapDetector;
|
||||
class PinchDetector;
|
||||
class DragDetector;
|
||||
} // namespace ndk_helper
|
||||
#endif
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Renderer;
|
||||
class Engine;
|
||||
|
||||
class Platform {
|
||||
public:
|
||||
Platform();
|
||||
~Platform();
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
void Initialize(android_app* app);
|
||||
#elif defined(__linux__)
|
||||
void Initialize();
|
||||
#endif
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void Update();
|
||||
|
||||
void RunMainLoop();
|
||||
|
||||
void Exit();
|
||||
|
||||
Renderer* GetRenderer() { return renderer_.get(); }
|
||||
|
||||
int GetDeviceDpi() const { return device_dpi_; }
|
||||
|
||||
const std::string& GetRootPath() const { return root_path_; }
|
||||
|
||||
bool mobile_device() const { return mobile_device_; }
|
||||
|
||||
static class InternalError : public std::exception {
|
||||
} internal_error;
|
||||
|
||||
private:
|
||||
base::Timer timer_;
|
||||
|
||||
bool mobile_device_ = false;
|
||||
int device_dpi_ = 200;
|
||||
std::string root_path_;
|
||||
|
||||
bool has_focus_ = false;
|
||||
bool should_exit_ = false;
|
||||
|
||||
std::unique_ptr<Renderer> renderer_;
|
||||
std::unique_ptr<Engine> engine_;
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
android_app* app_ = nullptr;
|
||||
|
||||
std::unique_ptr<ndk_helper::TapDetector> tap_detector_;
|
||||
std::unique_ptr<ndk_helper::PinchDetector> pinch_detector_;
|
||||
std::unique_ptr<ndk_helper::DragDetector> drag_detector_;
|
||||
|
||||
static int32_t HandleInput(android_app* app, AInputEvent* event);
|
||||
static void HandleCmd(android_app* app, int32_t cmd);
|
||||
#endif
|
||||
|
||||
Platform(const Platform&) = delete;
|
||||
Platform& operator=(const Platform&) = delete;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // PLATFORM_H
|
|
@ -0,0 +1,283 @@
|
|||
#include "platform.h"
|
||||
|
||||
#include <android_native_app_glue.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
|
||||
#include "../../base/file.h"
|
||||
#include "../../base/log.h"
|
||||
#include "../../third_party/android/gestureDetector.h"
|
||||
#include "../engine.h"
|
||||
#include "../input_event.h"
|
||||
#include "../renderer/renderer.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GetApkPath(ANativeActivity* activity) {
|
||||
JNIEnv* env = nullptr;
|
||||
activity->vm->AttachCurrentThread(&env, nullptr);
|
||||
|
||||
jclass activity_clazz = env->GetObjectClass(activity->clazz);
|
||||
jmethodID get_application_info_id =
|
||||
env->GetMethodID(activity_clazz, "getApplicationInfo",
|
||||
"()Landroid/content/pm/ApplicationInfo;");
|
||||
jobject app_info_obj =
|
||||
env->CallObjectMethod(activity->clazz, get_application_info_id);
|
||||
|
||||
jclass app_info_clazz = env->GetObjectClass(app_info_obj);
|
||||
jfieldID source_dir_id =
|
||||
env->GetFieldID(app_info_clazz, "sourceDir", "Ljava/lang/String;");
|
||||
jstring source_dir_obj =
|
||||
(jstring)env->GetObjectField(app_info_obj, source_dir_id);
|
||||
|
||||
const char* source_dir = env->GetStringUTFChars(source_dir_obj, nullptr);
|
||||
std::string apk_path = std::string(source_dir);
|
||||
|
||||
env->ReleaseStringUTFChars(source_dir_obj, source_dir);
|
||||
env->DeleteLocalRef(app_info_clazz);
|
||||
env->DeleteLocalRef(activity_clazz);
|
||||
activity->vm->DetachCurrentThread();
|
||||
|
||||
return apk_path;
|
||||
}
|
||||
|
||||
int32_t getDensityDpi(android_app* app) {
|
||||
AConfiguration* config = AConfiguration_new();
|
||||
AConfiguration_fromAssetManager(config, app->activity->assetManager);
|
||||
int32_t density = AConfiguration_getDensity(config);
|
||||
AConfiguration_delete(config);
|
||||
return density;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace eng {
|
||||
|
||||
Platform::Platform() = default;
|
||||
Platform::~Platform() = default;
|
||||
|
||||
int32_t Platform::HandleInput(android_app* app, AInputEvent* event) {
|
||||
Platform* platform = reinterpret_cast<Platform*>(app->userData);
|
||||
|
||||
if (!platform->engine_)
|
||||
return 0;
|
||||
|
||||
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY &&
|
||||
AKeyEvent_getKeyCode(event) == AKEYCODE_BACK) {
|
||||
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_UP) {
|
||||
auto input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kNavigateBack);
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
|
||||
ndk_helper::GESTURE_STATE tap_state =
|
||||
platform->tap_detector_->Detect(event);
|
||||
ndk_helper::GESTURE_STATE drag_state =
|
||||
platform->drag_detector_->Detect(event);
|
||||
ndk_helper::GESTURE_STATE pinch_state =
|
||||
platform->pinch_detector_->Detect(event);
|
||||
|
||||
// Tap detector has a priority over other detectors
|
||||
if (tap_state == ndk_helper::GESTURE_STATE_ACTION) {
|
||||
platform->engine_->AddInputEvent(
|
||||
std::make_unique<InputEvent>(InputEvent::kDragCancel));
|
||||
// Detect tap
|
||||
Vector2 v;
|
||||
platform->tap_detector_->GetPointer(v);
|
||||
v = platform->engine_->ToPosition(v);
|
||||
// DLOG << "Tap: " << v;
|
||||
auto input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kTap, v * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
} else {
|
||||
// Handle drag state
|
||||
if (drag_state & ndk_helper::GESTURE_STATE_START) {
|
||||
// Otherwise, start dragging
|
||||
Vector2 v;
|
||||
platform->drag_detector_->GetPointer(v);
|
||||
v = platform->engine_->ToPosition(v);
|
||||
// DLOG << "drag-start: " << v;
|
||||
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragStart,
|
||||
v * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
} else if (drag_state & ndk_helper::GESTURE_STATE_MOVE) {
|
||||
Vector2 v;
|
||||
platform->drag_detector_->GetPointer(v);
|
||||
v = platform->engine_->ToPosition(v);
|
||||
// DLOG << "drag: " << v;
|
||||
auto input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kDrag, v * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
} else if (drag_state & ndk_helper::GESTURE_STATE_END) {
|
||||
// DLOG << "drag-end!";
|
||||
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragEnd);
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
|
||||
// Handle pinch state
|
||||
if (pinch_state & ndk_helper::GESTURE_STATE_START) {
|
||||
platform->engine_->AddInputEvent(
|
||||
std::make_unique<InputEvent>(InputEvent::kDragCancel));
|
||||
// Start new pinch
|
||||
Vector2 v1;
|
||||
Vector2 v2;
|
||||
platform->pinch_detector_->GetPointers(v1, v2);
|
||||
v1 = platform->engine_->ToPosition(v1);
|
||||
v2 = platform->engine_->ToPosition(v2);
|
||||
// DLOG << "pinch-start: " << v1 << " " << v2;
|
||||
auto input_event = std::make_unique<InputEvent>(
|
||||
InputEvent::kPinchStart, v1 * Vector2(1, -1), v2 * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
} else if (pinch_state & ndk_helper::GESTURE_STATE_MOVE) {
|
||||
// Multi touch
|
||||
// Start new pinch
|
||||
Vector2 v1;
|
||||
Vector2 v2;
|
||||
platform->pinch_detector_->GetPointers(v1, v2);
|
||||
v1 = platform->engine_->ToPosition(v1);
|
||||
v2 = platform->engine_->ToPosition(v2);
|
||||
// DLOG << "pinch: " << v1 << " " << v2;
|
||||
auto input_event = std::make_unique<InputEvent>(
|
||||
InputEvent::kPinch, v1 * Vector2(1, -1), v2 * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Platform::HandleCmd(android_app* app, int32_t cmd) {
|
||||
Platform* platform = reinterpret_cast<Platform*>(app->userData);
|
||||
|
||||
switch (cmd) {
|
||||
case APP_CMD_SAVE_STATE:
|
||||
break;
|
||||
|
||||
case APP_CMD_INIT_WINDOW:
|
||||
DLOG << "APP_CMD_INIT_WINDOW";
|
||||
if (app->window != NULL) {
|
||||
if (!platform->renderer_->Initialize(app->window)) {
|
||||
LOG << "Failed to initialize the renderer.";
|
||||
throw internal_error;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case APP_CMD_TERM_WINDOW:
|
||||
DLOG << "APP_CMD_TERM_WINDOW";
|
||||
platform->renderer_->Shutdown();
|
||||
break;
|
||||
|
||||
case APP_CMD_CONFIG_CHANGED:
|
||||
DLOG << "APP_CMD_CONFIG_CHANGED";
|
||||
if (platform->app_->window != NULL) {
|
||||
int width = platform->renderer_->screen_width();
|
||||
int height = platform->renderer_->screen_height();
|
||||
if (width != ANativeWindow_getWidth(app->window) ||
|
||||
height != ANativeWindow_getHeight(app->window)) {
|
||||
platform->renderer_->Shutdown();
|
||||
if (!platform->renderer_->Initialize(platform->app_->window)) {
|
||||
LOG << "Failed to initialize the renderer.";
|
||||
throw internal_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case APP_CMD_STOP:
|
||||
DLOG << "APP_CMD_STOP";
|
||||
break;
|
||||
|
||||
case APP_CMD_GAINED_FOCUS:
|
||||
DLOG << "APP_CMD_GAINED_FOCUS";
|
||||
platform->timer_.Reset();
|
||||
platform->has_focus_ = true;
|
||||
if (platform->engine_)
|
||||
platform->engine_->GainedFocus();
|
||||
break;
|
||||
|
||||
case APP_CMD_LOST_FOCUS:
|
||||
DLOG << "APP_CMD_LOST_FOCUS";
|
||||
platform->has_focus_ = false;
|
||||
if (platform->engine_)
|
||||
platform->engine_->LostFocus();
|
||||
break;
|
||||
|
||||
case APP_CMD_LOW_MEMORY:
|
||||
DLOG << "APP_CMD_LOW_MEMORY";
|
||||
if (platform->engine_)
|
||||
platform->engine_->TrimMemory();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Platform::Initialize(android_app* app) {
|
||||
LOG << "Initializing platform.";
|
||||
app_ = app;
|
||||
|
||||
renderer_ = std::make_unique<Renderer>();
|
||||
|
||||
tap_detector_ = std::make_unique<ndk_helper::TapDetector>();
|
||||
drag_detector_ = std::make_unique<ndk_helper::DragDetector>();
|
||||
pinch_detector_ = std::make_unique<ndk_helper::PinchDetector>();
|
||||
|
||||
tap_detector_->SetConfiguration(app_->config);
|
||||
drag_detector_->SetConfiguration(app_->config);
|
||||
pinch_detector_->SetConfiguration(app_->config);
|
||||
|
||||
mobile_device_ = true;
|
||||
|
||||
root_path_ = GetApkPath(app->activity);
|
||||
LOG << "Root path: " << root_path_.c_str();
|
||||
|
||||
device_dpi_ = getDensityDpi(app);
|
||||
LOG << "Device DPI: " << device_dpi_;
|
||||
|
||||
app->userData = reinterpret_cast<void*>(this);
|
||||
app->onAppCmd = Platform::HandleCmd;
|
||||
app->onInputEvent = Platform::HandleInput;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void Platform::Update() {
|
||||
int id;
|
||||
int events;
|
||||
android_poll_source* source;
|
||||
|
||||
while ((id = ALooper_pollAll(has_focus_ ? 0 : -1, NULL, &events,
|
||||
(void**)&source)) >= 0) {
|
||||
if (source != NULL)
|
||||
source->process(app_, source);
|
||||
if (app_->destroyRequested != 0) {
|
||||
LOG << "App destroy requested.";
|
||||
should_exit_ = true;
|
||||
break;
|
||||
}
|
||||
if (has_focus_)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Platform::Exit() {
|
||||
ANativeActivity_finish(app_->activity);
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
||||
void android_main(android_app* app) {
|
||||
eng::Platform platform;
|
||||
try {
|
||||
platform.Initialize(app);
|
||||
platform.RunMainLoop();
|
||||
platform.Shutdown();
|
||||
} catch (eng::Platform::InternalError& e) {
|
||||
}
|
||||
_exit(0);
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
#include "platform.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../../base/vecmath.h"
|
||||
#include "../engine.h"
|
||||
#include "../input_event.h"
|
||||
#include "../renderer/renderer.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
Platform::Platform() = default;
|
||||
Platform::~Platform() = default;
|
||||
|
||||
void Platform::Initialize() {
|
||||
root_path_ = "../../";
|
||||
LOG << "Root path: " << root_path_.c_str();
|
||||
|
||||
renderer_ = std::make_unique<Renderer>();
|
||||
if (!renderer_->Initialize()) {
|
||||
LOG << "Failed to initialize renderer.";
|
||||
throw internal_error;
|
||||
}
|
||||
LOG << "Initialized the renderer.";
|
||||
|
||||
Display* display = renderer_->display();
|
||||
Window window = renderer_->window();
|
||||
XSelectInput(
|
||||
display, window,
|
||||
KeyPressMask | Button1MotionMask | ButtonPressMask | ButtonReleaseMask);
|
||||
Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false);
|
||||
XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1);
|
||||
}
|
||||
|
||||
void Platform::Update() {
|
||||
if (!engine_)
|
||||
return;
|
||||
|
||||
Display* display = renderer_->display();
|
||||
while (XPending(display)) {
|
||||
XEvent e;
|
||||
XNextEvent(display, &e);
|
||||
switch (e.type) {
|
||||
case KeyPress: {
|
||||
KeySym key = XLookupKeysym(&e.xkey, 0);
|
||||
auto input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kKeyPress, key);
|
||||
engine_->AddInputEvent(std::move(input_event));
|
||||
// TODO: e.xkey.state & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask))
|
||||
break;
|
||||
}
|
||||
case MotionNotify: {
|
||||
Vector2 v(e.xmotion.x, e.xmotion.y);
|
||||
v = engine_->ToPosition(v);
|
||||
// DLOG << "drag: " << v;
|
||||
auto input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kDrag, v * Vector2(1, -1));
|
||||
engine_->AddInputEvent(std::move(input_event));
|
||||
break;
|
||||
}
|
||||
case ButtonPress: {
|
||||
if (e.xbutton.button == 1) {
|
||||
Vector2 v(e.xbutton.x, e.xbutton.y);
|
||||
v = engine_->ToPosition(v);
|
||||
// DLOG << "drag-start: " << v;
|
||||
auto input_event = std::make_unique<InputEvent>(
|
||||
InputEvent::kDragStart, v * Vector2(1, -1));
|
||||
engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ButtonRelease: {
|
||||
if (e.xbutton.button == 1) {
|
||||
// DLOG << "drag-end!";
|
||||
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragEnd);
|
||||
engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClientMessage: {
|
||||
// TODO: Should check here for other client message types. However the
|
||||
// only protocol registered above is WM_DELETE_WINDOW for now.
|
||||
should_exit_ = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Platform::Exit() {
|
||||
should_exit_ = true;
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
eng::Platform platform;
|
||||
try {
|
||||
platform.Initialize();
|
||||
platform.RunMainLoop();
|
||||
platform.Shutdown();
|
||||
} catch (eng::Platform::InternalError& e) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
#include "geometry.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "../engine.h"
|
||||
#include "../mesh.h"
|
||||
#include "render_command.h"
|
||||
#include "renderer.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
Geometry::Geometry(unsigned resource_id,
|
||||
std::shared_ptr<void> impl_data,
|
||||
Renderer* renderer)
|
||||
: RenderResource(resource_id, impl_data, renderer) {}
|
||||
|
||||
Geometry::~Geometry() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Geometry::Create(std::shared_ptr<const Mesh> mesh) {
|
||||
assert(mesh->IsImmutable());
|
||||
|
||||
Destroy();
|
||||
valid_ = true;
|
||||
|
||||
vertex_description_ = mesh->vertex_description();
|
||||
|
||||
auto cmd = std::make_unique<CmdCreateGeometry>();
|
||||
cmd->mesh = mesh;
|
||||
cmd->impl_data = std::static_pointer_cast<void>(impl_data_);
|
||||
renderer_->EnqueueCommand(std::move(cmd));
|
||||
}
|
||||
|
||||
void Geometry::Destroy() {
|
||||
if (valid_) {
|
||||
auto cmd = std::make_unique<CmdDestroyGeometry>();
|
||||
cmd->impl_data = std::static_pointer_cast<void>(impl_data_);
|
||||
renderer_->EnqueueCommand(std::move(cmd));
|
||||
valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Geometry::Draw() {
|
||||
if (valid_) {
|
||||
auto cmd = std::make_unique<CmdDrawGeometry>();
|
||||
cmd->impl_data = std::static_pointer_cast<void>(impl_data_);
|
||||
renderer_->EnqueueCommand(std::move(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef GEOMETRY_H
|
||||
#define GEOMETRY_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "render_resource.h"
|
||||
#include "renderer_types.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Renderer;
|
||||
class Mesh;
|
||||
|
||||
class Geometry : public RenderResource {
|
||||
public:
|
||||
Geometry(unsigned resource_id,
|
||||
std::shared_ptr<void> impl_data,
|
||||
Renderer* renderer);
|
||||
~Geometry() override;
|
||||
|
||||
void Create(std::shared_ptr<const Mesh> mesh);
|
||||
|
||||
void Destroy() override;
|
||||
|
||||
void Draw();
|
||||
|
||||
const VertexDescripton& vertex_description() const {
|
||||
return vertex_description_;
|
||||
}
|
||||
|
||||
private:
|
||||
VertexDescripton vertex_description_;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // GEOMETRY_H
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef OPENGL_H
|
||||
#define OPENGL_H
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// Use the modified Khronos header from ndk-helper. This gives access to
|
||||
// additional functionality the drivers may expose but which the system headers
|
||||
// do not.
|
||||
#include "../../third_party/android/gl3stub.h"
|
||||
#include <GLES2/gl2ext.h>
|
||||
|
||||
#elif defined(__linux__)
|
||||
#include "../../third_party/glew/glew.h"
|
||||
|
||||
// Define the missing format for the etc1
|
||||
#ifndef GL_ETC1_RGB8_OES
|
||||
#define GL_ETC1_RGB8_OES 0x8D64
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif // OPENGL_H
|
|
@ -0,0 +1,140 @@
|
|||
#ifndef RENDER_COMMAND_H
|
||||
#define RENDER_COMMAND_H
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "../../base/hash.h"
|
||||
#include "../../base/vecmath.h"
|
||||
#include "renderer_types.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Image;
|
||||
class ShaderSource;
|
||||
class Mesh;
|
||||
|
||||
// Global render commands are guaranteed to be processed. Others commands are
|
||||
// frame specific and can be discared by the renderer.
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define RENDER_COMMAND_BEGIN(NAME, GLOBAL) \
|
||||
struct NAME : RenderCommand { \
|
||||
static constexpr CommandId CMD_ID = HHASH(#NAME); \
|
||||
NAME() : RenderCommand(CMD_ID, GLOBAL, #NAME) {}
|
||||
#define RENDER_COMMAND_END };
|
||||
#else
|
||||
#define RENDER_COMMAND_BEGIN(NAME, GLOBAL) \
|
||||
struct NAME : RenderCommand { \
|
||||
static constexpr CommandId CMD_ID = HHASH(#NAME); \
|
||||
NAME() : RenderCommand(CMD_ID, GLOBAL) {}
|
||||
#define RENDER_COMMAND_END };
|
||||
#endif
|
||||
|
||||
struct RenderCommand {
|
||||
using CommandId = size_t;
|
||||
static constexpr CommandId INVALID_CMD_ID = 0;
|
||||
|
||||
#ifdef _DEBUG
|
||||
RenderCommand(CommandId id, bool g, const char* name)
|
||||
: cmd_id(id), global(g), cmd_name(name) {}
|
||||
#else
|
||||
RenderCommand(CommandId id, bool g) : cmd_id(id), global(g) {}
|
||||
#endif
|
||||
|
||||
const CommandId cmd_id = INVALID_CMD_ID;
|
||||
const bool global = false;
|
||||
#ifdef _DEBUG
|
||||
std::string cmd_name;
|
||||
#endif
|
||||
};
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdEableBlend, false)
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdClear, false)
|
||||
std::array<float, 4> rgba;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdPresent, false)
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdUpdateTexture, true)
|
||||
std::shared_ptr<const Image> image;
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdDestoryTexture, true)
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdActivateTexture, false)
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdCreateGeometry, true)
|
||||
std::shared_ptr<const Mesh> mesh;
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdDestroyGeometry, true)
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdDrawGeometry, false)
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdCreateShader, true)
|
||||
std::shared_ptr<const ShaderSource> source;
|
||||
VertexDescripton vertex_description;
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdDestroyShader, true)
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdActivateShader, false)
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdSetUniformVec2, false)
|
||||
std::string name;
|
||||
base::Vector2 v;
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdSetUniformVec3, false)
|
||||
std::string name;
|
||||
base::Vector3 v;
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdSetUniformVec4, false)
|
||||
std::string name;
|
||||
base::Vector4 v;
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdSetUniformMat4, false)
|
||||
std::string name;
|
||||
base::Matrix4x4 m;
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdSetUniformInt, false)
|
||||
std::string name;
|
||||
int i;
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdSetUniformFloat, false)
|
||||
std::string name;
|
||||
float f;
|
||||
std::shared_ptr<void> impl_data;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // RENDER_COMMAND_H
|