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
|