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,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,17 @@
|
|||
A simple, cross-platform, multi-threaded 2D game engine with OpenGL renderer written in modern
|
||||
C++. Supports Linux and Android platforms.
|
||||
|
||||
Build for Linux (gcc or clang):
|
||||
|
||||
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,12 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
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,9 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
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,61 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#else
|
||||
#define lowp
|
||||
#define mediump
|
||||
#define highp
|
||||
#endif
|
||||
|
||||
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 * 2671.6182);
|
||||
}
|
||||
|
||||
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 - e * 35.0));
|
||||
}
|
||||
|
||||
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,130 @@
|
|||
#
|
||||
# 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)
|
||||
|
||||
# OBOE Library
|
||||
set (OBOE_DIR ../../../src/third_party/oboe)
|
||||
add_subdirectory(${OBOE_DIR} ./oboe-bin)
|
||||
|
||||
|
||||
include(AndroidNdkModules)
|
||||
android_ndk_import_module_cpufeatures()
|
||||
|
||||
# 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/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/audio/audio_base.cc
|
||||
../../../src/engine/audio/audio_oboe.cc
|
||||
../../../src/engine/audio/audio_resource.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/asset_file_android.cc
|
||||
../../../src/engine/platform/asset_file.cc
|
||||
../../../src/engine/platform/platform_android.cc
|
||||
../../../src/engine/platform/platform.cc
|
||||
../../../src/engine/renderer/geometry.cc
|
||||
../../../src/engine/renderer/render_command.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/engine/sound_player.cc
|
||||
../../../src/engine/sound.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
|
||||
../../../src/third_party/r8b/pffft.cpp
|
||||
../../../src/third_party/r8b/r8bbase.cpp
|
||||
../../../src/third_party/texture_compressor/dxt_encoder_internals.cc
|
||||
../../../src/third_party/texture_compressor/dxt_encoder.cc
|
||||
../../../src/third_party/texture_compressor/texture_compressor_etc1.cc
|
||||
../../../src/third_party/texture_compressor/texture_compressor.cc
|
||||
)
|
||||
|
||||
if (ANDROID_ABI STREQUAL armeabi-v7a)
|
||||
target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc)
|
||||
target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc)
|
||||
set_source_files_properties(../../../src/third_party/r8b/pffft.cpp PROPERTIES COMPILE_FLAGS -mfpu=neon)
|
||||
set_source_files_properties(../../../src/third_party/texture_compressor/dxt_encoder_neon.cc PROPERTIES COMPILE_FLAGS -mfpu=neon)
|
||||
set_source_files_properties(../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc PROPERTIES COMPILE_FLAGS -mfpu=neon)
|
||||
endif()
|
||||
|
||||
if (ANDROID_ABI STREQUAL arm64-v8a)
|
||||
target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc)
|
||||
target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc)
|
||||
endif()
|
||||
|
||||
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
|
||||
oboe
|
||||
cpufeatures
|
||||
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,174 @@
|
|||
.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 -lasound
|
||||
|
||||
# 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 "*.cpp" -o -name "*.c"))
|
||||
|
||||
# --- gltest application ---
|
||||
ifneq ($(filter gltest,$(APPS)),)
|
||||
|
||||
GLTEST_SRC := \
|
||||
$(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/audio/audio_alsa.cc \
|
||||
$(SRC_ROOT)/engine/audio/audio_base.cc \
|
||||
$(SRC_ROOT)/engine/audio/audio_resource.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/asset_file_linux.cc \
|
||||
$(SRC_ROOT)/engine/platform/asset_file.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_command.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)/engine/sound_player.cc \
|
||||
$(SRC_ROOT)/engine/sound.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 \
|
||||
$(SRC_ROOT)/third_party/r8b/pffft.cpp \
|
||||
$(SRC_ROOT)/third_party/r8b/r8bbase.cpp \
|
||||
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder_internals.cc \
|
||||
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder.cc \
|
||||
$(SRC_ROOT)/third_party/texture_compressor/texture_compressor_etc1.cc \
|
||||
$(SRC_ROOT)/third_party/texture_compressor/texture_compressor.cc
|
||||
|
||||
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 $@ $<
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_ROOT)/%.cpp
|
||||
@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,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,25 @@
|
|||
#ifndef FILE_H
|
||||
#define FILE_H
|
||||
|
||||
#include <cstdio>
|
||||
#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 <cstdio>
|
||||
#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,52 @@
|
|||
#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>;
|
||||
};
|
||||
|
||||
template <int kAlignment>
|
||||
inline void* AlignedAlloc(size_t size) {
|
||||
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,34 @@
|
|||
#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;
|
||||
}
|
||||
|
||||
inline int RoundUpToPow2(int val) {
|
||||
int i = GetHighestBit(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 sides) {
|
||||
return Lerp(1, sides, 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_); }
|
||||
|
||||
// Roll dice with the given number of sides.
|
||||
int Roll(int sides);
|
||||
|
||||
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,138 @@
|
|||
#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() {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
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::unique_ptr<Image> Credits::CreateImage() {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
int line_height = font.GetLineHeight() + 1;
|
||||
auto image = std::make_unique<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();
|
||||
|
||||
return image;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#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 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_;
|
||||
|
||||
int max_text_width_ = 0;
|
||||
|
||||
std::unique_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,251 @@
|
|||
#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 (!font_.Load("PixelCaps!.ttf"))
|
||||
return false;
|
||||
|
||||
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(3) - 1;
|
||||
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,82 @@
|
|||
#ifndef DEMO_H
|
||||
#define DEMO_H
|
||||
|
||||
#include "../base/closure.h"
|
||||
#include "../engine/font.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();
|
||||
|
||||
const eng::Font& GetFont() { return font_; }
|
||||
|
||||
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;
|
||||
|
||||
eng::Font font_;
|
||||
|
||||
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,476 @@
|
|||
#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 "../engine/sound.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() {
|
||||
explosion_sound_ = std::make_shared<Sound>();
|
||||
if (!explosion_sound_->Load("explosion.mp3"))
|
||||
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);
|
||||
|
||||
target->explosion_.Play(false);
|
||||
|
||||
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(2) - 1);
|
||||
|
||||
Vector2 s = engine.GetScreenSize();
|
||||
int col;
|
||||
col = rnd.Roll(4) - 1;
|
||||
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(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);
|
||||
|
||||
e.explosion_.SetSound(explosion_sound_);
|
||||
e.explosion_.SetVariate(true);
|
||||
e.explosion_.SetSimulateStereo(true);
|
||||
}
|
||||
|
||||
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::unique_ptr<Image> Enemy::GetScoreImage(int score) {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
std::string text = std::to_string(score);
|
||||
int width, height;
|
||||
font.CalculateBoundingBox(text.c_str(), width, height);
|
||||
|
||||
auto image = std::make_unique<Image>();
|
||||
image->Create(width, height);
|
||||
image->Clear({1, 1, 1, 0});
|
||||
|
||||
font.Print(0, 0, text.c_str(), image->GetBuffer(), image->GetWidth());
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
bool Enemy::CreateRenderResources() {
|
||||
auto skull_image = std::make_unique<Image>();
|
||||
if (!skull_image->Load("enemy_anims_01_frames_ok.png"))
|
||||
return false;
|
||||
auto bug_image = std::make_unique<Image>();
|
||||
if (!bug_image->Load("enemy_anims_02_frames_ok.png"))
|
||||
return false;
|
||||
auto target_image = std::make_unique<Image>();
|
||||
if (!target_image->Load("enemy_target_single_ok.png"))
|
||||
return false;
|
||||
auto blast_image = std::make_unique<Image>();
|
||||
if (!blast_image->Load("enemy_anims_blast_ok.png"))
|
||||
return false;
|
||||
|
||||
skull_image->Compress();
|
||||
bug_image->Compress();
|
||||
target_image->Compress();
|
||||
blast_image->Compress();
|
||||
|
||||
skull_tex_->Update(std::move(skull_image));
|
||||
bug_tex_->Update(std::move(bug_image));
|
||||
target_tex_->Update(std::move(target_image));
|
||||
blast_tex_->Update(std::move(blast_image));
|
||||
|
||||
for (int i = 0; i < kEnemyType_Max; ++i)
|
||||
score_tex_[i]->Update(GetScoreImage(GetScore((EnemyType)i)));
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
#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 "../engine/sound_player.h"
|
||||
#include "damage_type.h"
|
||||
|
||||
namespace eng {
|
||||
class Image;
|
||||
class Sound;
|
||||
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;
|
||||
|
||||
eng::SoundPlayer explosion_;
|
||||
};
|
||||
|
||||
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<eng::Sound> explosion_sound_;
|
||||
|
||||
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::unique_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"
|
||||
#include "demo.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();
|
||||
const Font& font = static_cast<Demo*>(engine.GetGame())->GetFont();
|
||||
|
||||
int tmp;
|
||||
font.CalculateBoundingBox("big_enough_text", max_text_width_, tmp);
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto image = CreateImage();
|
||||
|
||||
text_[i].GetTexture()->Update(std::move(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) {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
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());
|
||||
|
||||
text_[i].GetTexture()->Update(std::move(image));
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> Hud::CreateImage() {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
auto image = std::make_unique<Image>();
|
||||
image->Create(max_text_width_, font.GetLineHeight());
|
||||
image->Clear({1, 1, 1, 0});
|
||||
return image;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
#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;
|
||||
} // 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];
|
||||
|
||||
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::unique_ptr<eng::Image> CreateImage();
|
||||
};
|
||||
|
||||
#endif // HUD_H
|
|
@ -0,0 +1,214 @@
|
|||
#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"
|
||||
#include "demo.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() {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
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::unique_ptr<Image> Menu::CreateImage() {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
int line_height = font.GetLineHeight() + 1;
|
||||
auto image = std::make_unique<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();
|
||||
|
||||
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,72 @@
|
|||
#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 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];
|
||||
|
||||
int max_text_width_ = 0;
|
||||
|
||||
Option selected_option_ = kOption_Invalid;
|
||||
|
||||
base::Vector2 tap_pos_[2] = {{0, 0}, {0, 0}};
|
||||
|
||||
std::unique_ptr<eng::Image> CreateImage();
|
||||
|
||||
bool IsAnimating();
|
||||
};
|
||||
|
||||
#endif // MENU_H
|
|
@ -0,0 +1,354 @@
|
|||
#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);
|
||||
|
||||
spark_animator_[type].Stop(Animator::kMovement);
|
||||
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() {
|
||||
auto weapon_image = std::make_unique<Image>();
|
||||
if (!weapon_image->Load("enemy_anims_flare_ok.png"))
|
||||
return false;
|
||||
auto beam_image = std::make_unique<Image>();
|
||||
if (!beam_image->Load("enemy_ray_ok.png"))
|
||||
return false;
|
||||
|
||||
weapon_image->Compress();
|
||||
beam_image->Compress();
|
||||
|
||||
weapon_tex_->Update(std::move(weapon_image));
|
||||
beam_tex_->Update(std::move(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 source = std::make_unique<ShaderSource>();
|
||||
if (!source->Load("sky.glsl"))
|
||||
return false;
|
||||
shader_->Create(std::move(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() const override { return 0; }
|
||||
size_t GetNumFrames() const 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() const = 0;
|
||||
virtual size_t GetNumFrames() const = 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,276 @@
|
|||
#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;
|
||||
loop_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);
|
||||
|
||||
for (auto& a : elements_)
|
||||
a.movement_last_offset = {0, 0};
|
||||
}
|
||||
|
||||
void Animator::SetRotation(float trget,
|
||||
float duration,
|
||||
Interpolator interpolator) {
|
||||
rotation_target_ = trget;
|
||||
rotation_speed_ = 1.0f / duration;
|
||||
rotation_interpolator_ = std::move(interpolator);
|
||||
|
||||
for (auto& a : elements_)
|
||||
a.rotation_last_theta = 0;
|
||||
}
|
||||
|
||||
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,20 @@
|
|||
#ifndef AUDIO_H
|
||||
#define AUDIO_H
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include "audio_oboe.h"
|
||||
#elif defined(__linux__)
|
||||
#include "audio_alsa.h"
|
||||
#endif
|
||||
|
||||
namespace eng {
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
using Audio = AudioOboe;
|
||||
#elif defined(__linux__)
|
||||
using Audio = AudioAlsa;
|
||||
#endif
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // AUDIO_H
|
|
@ -0,0 +1,187 @@
|
|||
#include "audio_alsa.h"
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "audio_resource.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
AudioAlsa::AudioAlsa() = default;
|
||||
|
||||
AudioAlsa::~AudioAlsa() = default;
|
||||
|
||||
bool AudioAlsa::Initialize() {
|
||||
LOG << "Initializing audio system.";
|
||||
|
||||
int err;
|
||||
|
||||
// Contains information about the hardware.
|
||||
snd_pcm_hw_params_t* hw_params;
|
||||
|
||||
// "default" is usualy PulseAudio. Use "plughw:CARD=PCH" instead for direct
|
||||
// hardware device with software format conversion.
|
||||
if ((err = snd_pcm_open(&pcm_handle_, "plughw:CARD=PCH",
|
||||
SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
|
||||
LOG << "Cannot open audio device. Error: " << snd_strerror(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
// Allocate the snd_pcm_hw_params_t structure on the stack.
|
||||
snd_pcm_hw_params_alloca(&hw_params);
|
||||
|
||||
// Init hw_params with full configuration space.
|
||||
if ((err = snd_pcm_hw_params_any(pcm_handle_, hw_params)) < 0) {
|
||||
LOG << "Cannot initialize hardware parameter structure. Error: "
|
||||
<< snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_access(
|
||||
pcm_handle_, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
||||
LOG << "Cannot set access type. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_format(pcm_handle_, hw_params,
|
||||
SND_PCM_FORMAT_FLOAT)) < 0) {
|
||||
LOG << "Cannot set sample format. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
// Disable software resampler.
|
||||
if ((err = snd_pcm_hw_params_set_rate_resample(pcm_handle_, hw_params, 0)) <
|
||||
0) {
|
||||
LOG << "Cannot disbale software resampler. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned sample_rate = 48000;
|
||||
if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle_, hw_params,
|
||||
&sample_rate, 0)) < 0) {
|
||||
LOG << "Cannot set sample rate. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_channels(pcm_handle_, hw_params, 2)) < 0) {
|
||||
LOG << "Cannot set channel count. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set period time to 4 ms. The latency will be 12 ms for 3 perods.
|
||||
unsigned period_time = 4000;
|
||||
if ((err = snd_pcm_hw_params_set_period_time_near(pcm_handle_, hw_params,
|
||||
&period_time, 0)) < 0) {
|
||||
LOG << "Cannot set periods. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned periods = 3;
|
||||
if ((err = snd_pcm_hw_params_set_periods_near(pcm_handle_, hw_params,
|
||||
&periods, 0)) < 0) {
|
||||
LOG << "Cannot set periods. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply HW parameter settings to PCM device and prepare device.
|
||||
if ((err = snd_pcm_hw_params(pcm_handle_, hw_params)) < 0) {
|
||||
LOG << "Cannot set parameters. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_prepare(pcm_handle_)) < 0) {
|
||||
LOG << "Cannot prepare audio interface for use. Error: "
|
||||
<< snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
snd_pcm_access_t access;
|
||||
unsigned num_channels;
|
||||
snd_pcm_format_t format;
|
||||
snd_pcm_uframes_t period_size;
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
|
||||
snd_pcm_hw_params_get_access(hw_params, &access);
|
||||
snd_pcm_hw_params_get_channels(hw_params, &num_channels);
|
||||
snd_pcm_hw_params_get_format(hw_params, &format);
|
||||
snd_pcm_hw_params_get_period_size(hw_params, &period_size, nullptr);
|
||||
snd_pcm_hw_params_get_period_time(hw_params, &period_time, nullptr);
|
||||
snd_pcm_hw_params_get_periods(hw_params, &periods, nullptr);
|
||||
snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size);
|
||||
|
||||
LOG << "Alsa Audio:";
|
||||
LOG << " access: " << snd_pcm_access_name(access);
|
||||
LOG << " format: " << snd_pcm_format_name(format);
|
||||
LOG << " channel count: " << num_channels;
|
||||
LOG << " sample rate: " << sample_rate;
|
||||
LOG << " period size: " << period_size;
|
||||
LOG << " period time: " << period_time;
|
||||
LOG << " periods: " << periods;
|
||||
LOG << " buffer_size: " << buffer_size;
|
||||
|
||||
num_channels_ = num_channels;
|
||||
sample_rate_ = sample_rate;
|
||||
period_size_ = period_size;
|
||||
|
||||
StartWorker();
|
||||
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
snd_pcm_close(pcm_handle_);
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioAlsa::Shutdown() {
|
||||
LOG << "Shutting down audio system.";
|
||||
TerminateWorker();
|
||||
snd_pcm_drop(pcm_handle_);
|
||||
snd_pcm_close(pcm_handle_);
|
||||
}
|
||||
|
||||
size_t AudioAlsa::GetSampleRate() {
|
||||
return sample_rate_;
|
||||
}
|
||||
|
||||
bool AudioAlsa::StartWorker() {
|
||||
LOG << "Starting audio thread.";
|
||||
|
||||
std::promise<bool> promise;
|
||||
std::future<bool> future = promise.get_future();
|
||||
worker_thread_ =
|
||||
std::thread(&AudioAlsa::WorkerMain, this, std::move(promise));
|
||||
return future.get();
|
||||
}
|
||||
|
||||
void AudioAlsa::TerminateWorker() {
|
||||
// Notify worker thread and wait for it to terminate.
|
||||
if (terminate_worker_)
|
||||
return;
|
||||
terminate_worker_ = true;
|
||||
LOG << "Terminating audio thread";
|
||||
worker_thread_.join();
|
||||
}
|
||||
|
||||
void AudioAlsa::WorkerMain(std::promise<bool> promise) {
|
||||
promise.set_value(true);
|
||||
|
||||
size_t num_frames = period_size_ / (num_channels_ * sizeof(float));
|
||||
auto buffer = std::make_unique<float[]>(num_frames * 2);
|
||||
|
||||
for (;;) {
|
||||
if (terminate_worker_)
|
||||
return;
|
||||
|
||||
RenderAudio(buffer.get(), num_frames);
|
||||
|
||||
while (snd_pcm_writei(pcm_handle_, buffer.get(), num_frames) < 0) {
|
||||
snd_pcm_prepare(pcm_handle_);
|
||||
LOG << "Audio buffer underrun!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,46 @@
|
|||
#ifndef AUDIO_ALSA_H
|
||||
#define AUDIO_ALSA_H
|
||||
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "audio_base.h"
|
||||
|
||||
typedef struct _snd_pcm snd_pcm_t;
|
||||
|
||||
namespace eng {
|
||||
|
||||
class AudioResource;
|
||||
|
||||
class AudioAlsa : public AudioBase {
|
||||
public:
|
||||
AudioAlsa();
|
||||
~AudioAlsa();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
size_t GetSampleRate();
|
||||
|
||||
private:
|
||||
// Handle for the PCM device.
|
||||
snd_pcm_t* pcm_handle_;
|
||||
|
||||
std::thread worker_thread_;
|
||||
bool terminate_worker_ = false;
|
||||
|
||||
size_t num_channels_ = 0;
|
||||
size_t sample_rate_ = 0;
|
||||
size_t period_size_ = 0;
|
||||
|
||||
bool StartWorker();
|
||||
void TerminateWorker();
|
||||
|
||||
void WorkerMain(std::promise<bool> promise);
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // AUDIO_ALSA_H
|
|
@ -0,0 +1,138 @@
|
|||
#include "audio_base.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../sound.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
AudioBase::AudioBase() = default;
|
||||
|
||||
AudioBase::~AudioBase() {
|
||||
worker_.Join();
|
||||
}
|
||||
|
||||
void AudioBase::Play(std::shared_ptr<AudioSample> sample) {
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
samples_[0].push_back(sample);
|
||||
}
|
||||
|
||||
void AudioBase::Update() {
|
||||
task_runner_.Run();
|
||||
}
|
||||
|
||||
void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
samples_[1].splice(samples_[1].end(), samples_[0]);
|
||||
}
|
||||
|
||||
memset(output_buffer, 0, sizeof(float) * num_frames * kChannelCount);
|
||||
|
||||
for (auto it = samples_[1].begin(); it != samples_[1].end();) {
|
||||
AudioSample* sample = it->get();
|
||||
|
||||
unsigned flags = sample->flags;
|
||||
bool remove = false;
|
||||
|
||||
if (flags & AudioSample::kStopped) {
|
||||
remove = true;
|
||||
} else {
|
||||
auto sound = sample->sound.get();
|
||||
|
||||
const float* src[2] = {const_cast<const Sound*>(sound)->GetBuffer(0),
|
||||
const_cast<const Sound*>(sound)->GetBuffer(1)};
|
||||
if (!src[1])
|
||||
src[1] = src[0]; // mono.
|
||||
|
||||
size_t num_samples = sound->GetNumSamples();
|
||||
size_t num_channels = sound->num_channels();
|
||||
size_t src_index = sample->src_index;
|
||||
size_t step = sample->step;
|
||||
size_t accumulator = sample->accumulator;
|
||||
float amplitude = sample->amplitude;
|
||||
float amplitude_inc = sample->amplitude_inc;
|
||||
float max_amplitude = sample->max_amplitude;
|
||||
|
||||
size_t channel_offset =
|
||||
(flags & AudioSample::kSimulateStereo) && num_channels == 1
|
||||
? sound->hz() / 10
|
||||
: 0;
|
||||
|
||||
for (size_t i = 0; i < num_frames * kChannelCount;) {
|
||||
// Mix the 1st channel.
|
||||
output_buffer[i++] += src[0][src_index] * amplitude;
|
||||
|
||||
// Mix the 2nd channel. Offset the source index for stereo simulation.
|
||||
size_t ind = channel_offset + src_index;
|
||||
if (ind < num_samples)
|
||||
output_buffer[i++] += src[1][ind] * amplitude;
|
||||
else if (flags & AudioSample::kLoop)
|
||||
output_buffer[i++] += src[1][ind % num_samples] * amplitude;
|
||||
else
|
||||
i++;
|
||||
|
||||
// Apply amplitude modification.
|
||||
amplitude += amplitude_inc;
|
||||
if (amplitude <= 0) {
|
||||
remove = true;
|
||||
break;
|
||||
} else if (amplitude > max_amplitude) {
|
||||
amplitude = max_amplitude;
|
||||
}
|
||||
|
||||
// Basic resampling for variations.
|
||||
accumulator += step;
|
||||
src_index += accumulator / 10;
|
||||
accumulator %= 10;
|
||||
|
||||
// Advance source index.
|
||||
if (src_index >= num_samples) {
|
||||
if (!sound->is_streaming_sound()) {
|
||||
if (flags & AudioSample::kLoop) {
|
||||
src_index %= num_samples;
|
||||
} else {
|
||||
remove = true;
|
||||
break;
|
||||
}
|
||||
} else if (!sound->IsStreamingInProgress()) {
|
||||
if (sound->eof()) {
|
||||
remove = true;
|
||||
break;
|
||||
}
|
||||
|
||||
src_index = 0;
|
||||
|
||||
// Swap buffers and start streaming in background.
|
||||
sound->SwapBuffers();
|
||||
src[0] = const_cast<const Sound*>(sound)->GetBuffer(0);
|
||||
src[1] = const_cast<const Sound*>(sound)->GetBuffer(1);
|
||||
|
||||
worker_.Enqueue(std::bind(&Sound::Stream, sample->sound,
|
||||
flags & AudioSample::kLoop));
|
||||
} else {
|
||||
LOG << "Buffer underrun!";
|
||||
src_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sample->src_index = src_index;
|
||||
sample->accumulator = accumulator;
|
||||
sample->amplitude = amplitude;
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
task_runner_.Enqueue(sample->end_cb);
|
||||
sample->active = false;
|
||||
it = samples_[1].erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef AUDIO_BASE_H
|
||||
#define AUDIO_BASE_H
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "../../base/closure.h"
|
||||
#include "../../base/task_runner.h"
|
||||
#include "../../base/worker.h"
|
||||
#include "audio_sample.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Sound;
|
||||
|
||||
class AudioBase {
|
||||
public:
|
||||
void Play(std::shared_ptr<AudioSample> impl_data);
|
||||
|
||||
void Update();
|
||||
|
||||
protected:
|
||||
static constexpr int kChannelCount = 2;
|
||||
|
||||
std::list<std::shared_ptr<AudioSample>> samples_[2];
|
||||
std::mutex mutex_;
|
||||
|
||||
base::Worker worker_{1};
|
||||
|
||||
base::TaskRunner task_runner_;
|
||||
|
||||
AudioBase();
|
||||
~AudioBase();
|
||||
|
||||
void RenderAudio(float* output_buffer, size_t num_frames);
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // AUDIO_BASE_H
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef AUDIO_FORWARD_H
|
||||
#define AUDIO_FORWARD_H
|
||||
|
||||
namespace eng {
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
class AudioOboe;
|
||||
using Audio = AudioOboe;
|
||||
#elif defined(__linux__)
|
||||
class AudioAlsa;
|
||||
using Audio = AudioAlsa;
|
||||
#endif
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // AUDIO_FORWARD_H
|
|
@ -0,0 +1,78 @@
|
|||
#include "audio_oboe.h"
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../../third_party/oboe/include/oboe/Oboe.h"
|
||||
#include "audio_resource.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
AudioOboe::AudioOboe() : callback_(std::make_unique<StreamCallback>(this)) {}
|
||||
|
||||
AudioOboe::~AudioOboe() = default;
|
||||
|
||||
bool AudioOboe::Initialize() {
|
||||
LOG << "Initializing audio system.";
|
||||
|
||||
return RestartStream();
|
||||
}
|
||||
|
||||
void AudioOboe::Shutdown() {
|
||||
LOG << "Shutting down audio system.";
|
||||
}
|
||||
|
||||
size_t AudioOboe::GetSampleRate() {
|
||||
return stream_->getSampleRate();
|
||||
}
|
||||
|
||||
AudioOboe::StreamCallback::StreamCallback(AudioOboe* audio) : audio_(audio) {}
|
||||
|
||||
AudioOboe::StreamCallback::~StreamCallback() = default;
|
||||
|
||||
oboe::DataCallbackResult AudioOboe::StreamCallback::onAudioReady(
|
||||
oboe::AudioStream* oboe_stream,
|
||||
void* audio_data,
|
||||
int32_t num_frames) {
|
||||
float* output_buffer = static_cast<float*>(audio_data);
|
||||
audio_->RenderAudio(output_buffer, num_frames);
|
||||
return oboe::DataCallbackResult::Continue;
|
||||
}
|
||||
|
||||
void AudioOboe::StreamCallback::onErrorAfterClose(
|
||||
oboe::AudioStream* oboe_stream,
|
||||
oboe::Result error) {
|
||||
LOG << "Error after close. Error: " << oboe::convertToText(error);
|
||||
|
||||
audio_->RestartStream();
|
||||
}
|
||||
|
||||
bool AudioOboe::RestartStream() {
|
||||
oboe::AudioStreamBuilder builder;
|
||||
oboe::Result result =
|
||||
builder.setSharingMode(oboe::SharingMode::Exclusive)
|
||||
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||
->setFormat(oboe::AudioFormat::Float)
|
||||
->setChannelCount(kChannelCount)
|
||||
->setDirection(oboe::Direction::Output)
|
||||
->setUsage(oboe::Usage::Game)
|
||||
->setCallback(callback_.get())
|
||||
->openManagedStream(stream_);
|
||||
|
||||
LOG << "Oboe Audio Stream:";
|
||||
LOG << " performance mode: " << (int)stream_->getPerformanceMode();
|
||||
LOG << " format: " << (int)stream_->getFormat();
|
||||
LOG << " channel count: " << stream_->getChannelCount();
|
||||
LOG << " sample rate: " << stream_->getSampleRate();
|
||||
|
||||
if (result != oboe::Result::OK) {
|
||||
LOG << "Failed to create the playback stream. Error: "
|
||||
<< oboe::convertToText(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
stream_->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef AUDIO_OBOE_H
|
||||
#define AUDIO_OBOE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../third_party/oboe/include/oboe/AudioStream.h"
|
||||
#include "../../third_party/oboe/include/oboe/AudioStreamCallback.h"
|
||||
#include "audio_base.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class AudioResource;
|
||||
|
||||
class AudioOboe : public AudioBase {
|
||||
public:
|
||||
AudioOboe();
|
||||
~AudioOboe();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
size_t GetSampleRate();
|
||||
|
||||
private:
|
||||
class StreamCallback : public oboe::AudioStreamCallback {
|
||||
public:
|
||||
StreamCallback(AudioOboe* audio);
|
||||
~StreamCallback() override;
|
||||
|
||||
oboe::DataCallbackResult onAudioReady(oboe::AudioStream* oboe_stream,
|
||||
void* audio_data,
|
||||
int32_t num_frames) override;
|
||||
|
||||
void onErrorAfterClose(oboe::AudioStream* oboe_stream,
|
||||
oboe::Result error) override;
|
||||
|
||||
private:
|
||||
AudioOboe* audio_;
|
||||
};
|
||||
|
||||
oboe::ManagedStream stream_;
|
||||
std::unique_ptr<StreamCallback> callback_;
|
||||
|
||||
bool RestartStream();
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // AUDIO_OBOE_H
|
|
@ -0,0 +1,72 @@
|
|||
#include "audio_resource.h"
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../sound.h"
|
||||
#include "audio.h"
|
||||
#include "audio_sample.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
AudioResource::AudioResource(Audio* audio)
|
||||
: sample_(std::make_shared<AudioSample>()), audio_(audio) {}
|
||||
|
||||
AudioResource::~AudioResource() {
|
||||
sample_->flags |= AudioSample::kStopped;
|
||||
}
|
||||
|
||||
void AudioResource::Play(std::shared_ptr<Sound> sound,
|
||||
float amplitude,
|
||||
bool reset_pos) {
|
||||
if (sample_->active)
|
||||
return;
|
||||
|
||||
if (reset_pos) {
|
||||
sample_->src_index = 0;
|
||||
sample_->accumulator = 0;
|
||||
}
|
||||
sample_->flags &= ~AudioSample::kStopped;
|
||||
sample_->sound = sound;
|
||||
sample_->amplitude = amplitude;
|
||||
sample_->active = true;
|
||||
|
||||
audio_->Play(sample_);
|
||||
}
|
||||
|
||||
void AudioResource::Stop() {
|
||||
if (!sample_->active)
|
||||
return;
|
||||
|
||||
sample_->flags |= AudioSample::kStopped;
|
||||
}
|
||||
|
||||
void AudioResource::SetLoop(bool loop) {
|
||||
if (loop)
|
||||
sample_->flags |= AudioSample::kLoop;
|
||||
else
|
||||
sample_->flags &= ~AudioSample::kLoop;
|
||||
}
|
||||
|
||||
void AudioResource::SetSimulateStereo(bool simulate) {
|
||||
if (simulate)
|
||||
sample_->flags |= AudioSample::kSimulateStereo;
|
||||
else
|
||||
sample_->flags &= ~AudioSample::kSimulateStereo;
|
||||
}
|
||||
|
||||
void AudioResource::SetResampleStep(size_t step) {
|
||||
sample_->step = step + 10;
|
||||
}
|
||||
|
||||
void AudioResource::SetMaxAmplitude(float max_amplitude) {
|
||||
sample_->max_amplitude = max_amplitude;
|
||||
}
|
||||
|
||||
void AudioResource::SetAmplitudeInc(float amplitude_inc) {
|
||||
sample_->amplitude_inc = amplitude_inc;
|
||||
}
|
||||
|
||||
void AudioResource::SetEndCallback(base::Closure cb) {
|
||||
sample_->end_cb = cb;
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef AUDIO_RESOURCE_H
|
||||
#define AUDIO_RESOURCE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../base/closure.h"
|
||||
#include "audio_forward.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
struct AudioSample;
|
||||
class Sound;
|
||||
|
||||
class AudioResource {
|
||||
public:
|
||||
AudioResource(Audio* audio);
|
||||
~AudioResource();
|
||||
|
||||
void Play(std::shared_ptr<Sound> sound, float amplitude, bool reset_pos);
|
||||
|
||||
void Stop();
|
||||
|
||||
void SetLoop(bool loop);
|
||||
void SetSimulateStereo(bool simulate);
|
||||
void SetResampleStep(size_t step);
|
||||
void SetMaxAmplitude(float max_amplitude);
|
||||
void SetAmplitudeInc(float amplitude_inc);
|
||||
void SetEndCallback(base::Closure cb);
|
||||
|
||||
private:
|
||||
std::shared_ptr<AudioSample> sample_;
|
||||
|
||||
Audio* audio_ = nullptr;
|
||||
|
||||
AudioResource(const AudioResource&) = delete;
|
||||
AudioResource& operator=(const AudioResource&) = delete;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // AUDIO_RESOURCE_H
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef AUDIO_SAMPLE_H
|
||||
#define AUDIO_SAMPLE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../base/closure.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Sound;
|
||||
|
||||
struct AudioSample {
|
||||
enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 };
|
||||
|
||||
// Read-only accessed by the audio thread.
|
||||
std::shared_ptr<Sound> sound;
|
||||
unsigned flags = 0;
|
||||
size_t step = 10;
|
||||
float amplitude_inc = 0;
|
||||
float max_amplitude = 1.0f;
|
||||
base::Closure end_cb;
|
||||
|
||||
// Write accessed by the audio thread.
|
||||
size_t src_index = 0;
|
||||
size_t accumulator = 0;
|
||||
float amplitude = 1.0f;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // AUDIO_SAMPLE_H
|
|
@ -0,0 +1,314 @@
|
|||
#include "engine.h"
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "../base/worker.h"
|
||||
#include "../third_party/texture_compressor/texture_compressor.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_resource.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, Audio* audio)
|
||||
: platform_(platform), renderer_(renderer), audio_(audio) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (renderer_->SupportsDXT5()) {
|
||||
tex_comp_alpha_ = TextureCompressor::Create(TextureCompressor::kFormatDXT5);
|
||||
} else if (renderer_->SupportsATC()) {
|
||||
tex_comp_alpha_ =
|
||||
TextureCompressor::Create(TextureCompressor::kFormatATCIA);
|
||||
}
|
||||
|
||||
if (renderer_->SupportsDXT1()) {
|
||||
tex_comp_opaque_ =
|
||||
TextureCompressor::Create(TextureCompressor::kFormatDXT1);
|
||||
} else if (renderer_->SupportsATC()) {
|
||||
tex_comp_opaque_ = TextureCompressor::Create(TextureCompressor::kFormatATC);
|
||||
} else if (renderer_->SupportsETC1()) {
|
||||
tex_comp_opaque_ =
|
||||
TextureCompressor::Create(TextureCompressor::kFormatETC1);
|
||||
}
|
||||
|
||||
system_font_ = std::make_unique<Font>();
|
||||
system_font_->Load("engine/RobotoMono-Regular.ttf");
|
||||
|
||||
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;
|
||||
|
||||
audio_->Update();
|
||||
renderer_->Update();
|
||||
|
||||
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::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;
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioResource> Engine::CreateAudioResource() {
|
||||
return std::make_shared<AudioResource>(audio_);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
TextureCompressor* Engine::GetTextureCompressor(bool opacity) {
|
||||
return opacity ? tex_comp_alpha_.get() : tex_comp_opaque_.get();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
size_t Engine::GetAudioSampleRate() {
|
||||
return audio_->GetSampleRate();
|
||||
}
|
||||
|
||||
bool Engine::IsMobile() const {
|
||||
return platform_->mobile_device();
|
||||
}
|
||||
|
||||
std::shared_ptr<RenderResource> Engine::CreateRenderResourceInternal(
|
||||
RenderResourceFactoryBase& factory) {
|
||||
return renderer_->CreateResource(factory);
|
||||
}
|
||||
|
||||
void Engine::ContextLost() {
|
||||
CreateRenderResources();
|
||||
|
||||
game_->ContextLost();
|
||||
}
|
||||
|
||||
bool Engine::CreateRenderResources() {
|
||||
// Create the quad geometry we can reuse for all sprites.
|
||||
auto quad_mesh = std::make_unique<Mesh>();
|
||||
if (!quad_mesh->Load("engine/quad.mesh")) {
|
||||
LOG << "Could not create quad mesh.";
|
||||
return false;
|
||||
}
|
||||
quad_->Create(std::move(quad_mesh));
|
||||
|
||||
// Create the shader we can reuse for texture rendering.
|
||||
auto source = std::make_unique<ShaderSource>();
|
||||
if (!source->Load("engine/pass_through.glsl")) {
|
||||
LOG << "Could not create pass through shader.";
|
||||
return false;
|
||||
}
|
||||
pass_through_shader_->Create(std::move(source), quad_->vertex_description());
|
||||
|
||||
// Create the shader we can reuse for solid rendering.
|
||||
source = std::make_unique<ShaderSource>();
|
||||
if (!source->Load("engine/solid.glsl")) {
|
||||
LOG << "Could not create solid shader.";
|
||||
return false;
|
||||
}
|
||||
solid_shader_->Create(std::move(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_unique<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_.get(), margin, y,
|
||||
text.c_str(), image->GetBuffer(),
|
||||
image->GetWidth()));
|
||||
y += line_height + margin;
|
||||
}
|
||||
worker.Join();
|
||||
|
||||
stats_.GetTexture()->Update(std::move(image));
|
||||
stats_.AutoScale();
|
||||
}
|
||||
|
||||
} // namespace eng
|
|
@ -0,0 +1,148 @@
|
|||
#ifndef ENGINE_H
|
||||
#define ENGINE_H
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../base/random.h"
|
||||
#include "../base/vecmath.h"
|
||||
#include "audio/audio_forward.h"
|
||||
#include "image_quad.h"
|
||||
#include "renderer/render_resource.h"
|
||||
|
||||
class TextureCompressor;
|
||||
|
||||
namespace eng {
|
||||
|
||||
class AudioResource;
|
||||
class Font;
|
||||
class Game;
|
||||
class InputEvent;
|
||||
class Renderer;
|
||||
struct RenderCommand;
|
||||
class Platform;
|
||||
class Geometry;
|
||||
class Shader;
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
Engine(Platform* platform, Renderer* renderer, Audio* audio);
|
||||
~Engine();
|
||||
|
||||
static Engine& Get();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void Update(float delta_time);
|
||||
void Draw(float frame_frac);
|
||||
|
||||
void LostFocus();
|
||||
void GainedFocus();
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioResource> CreateAudioResource();
|
||||
|
||||
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_; }
|
||||
|
||||
const Font* GetSystemFont() { return system_font_.get(); }
|
||||
|
||||
base::Random& GetRandomGenerator() { return random_; }
|
||||
|
||||
TextureCompressor* GetTextureCompressor(bool opacity);
|
||||
|
||||
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;
|
||||
|
||||
size_t GetAudioSampleRate();
|
||||
|
||||
bool IsMobile() const;
|
||||
|
||||
float seconds_accumulated() const { return seconds_accumulated_; }
|
||||
|
||||
private:
|
||||
static Engine* singleton;
|
||||
|
||||
Platform* platform_ = nullptr;
|
||||
|
||||
Renderer* renderer_ = nullptr;
|
||||
|
||||
Audio* audio_;
|
||||
|
||||
std::unique_ptr<Game> game_;
|
||||
|
||||
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::unique_ptr<Font> system_font_;
|
||||
|
||||
std::unique_ptr<TextureCompressor> tex_comp_opaque_;
|
||||
std::unique_ptr<TextureCompressor> tex_comp_alpha_;
|
||||
|
||||
ImageQuad stats_;
|
||||
|
||||
float fps_seconds_ = 0;
|
||||
int fps_ = 0;
|
||||
|
||||
float seconds_accumulated_ = 0.0f;
|
||||
|
||||
std::deque<std::unique_ptr<InputEvent>> input_queue_;
|
||||
|
||||
base::Random random_;
|
||||
|
||||
std::shared_ptr<RenderResource> CreateRenderResourceInternal(
|
||||
RenderResourceFactoryBase& factory);
|
||||
|
||||
void ContextLost();
|
||||
|
||||
bool CreateRenderResources();
|
||||
|
||||
void SetSatsVisible(bool visible);
|
||||
void PrintStats();
|
||||
|
||||
Engine(const Engine&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // ENGINE_H
|
|
@ -0,0 +1,193 @@
|
|||
#include "font.h"
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "engine.h"
|
||||
#include "platform/asset_file.h"
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "../third_party/stb/stb_truetype.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
bool Font::Load(const std::string& file_name) {
|
||||
// Read the font file.
|
||||
size_t buffer_size = 0;
|
||||
auto buffer = AssetFile::ReadWholeFile(
|
||||
file_name.c_str(), Engine::Get().GetRootPath().c_str(), &buffer_size);
|
||||
if (!buffer) {
|
||||
LOG << "Failed to read font file.";
|
||||
return 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: ";
|
||||
glyph_cache_.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
int x0, y0, x1, y1;
|
||||
CalculateBoundingBox("`IlfKgjy_{)", x0, y0, x1, y1);
|
||||
line_height_ = y1 - y0;
|
||||
yoff_ = -y0;
|
||||
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
glyph_cache_.reset();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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,54 @@
|
|||
#ifndef FONT_H
|
||||
#define FONT_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../third_party/stb/stb_truetype.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Font {
|
||||
public:
|
||||
Font() = default;
|
||||
~Font() = default;
|
||||
|
||||
bool Load(const std::string& file_name);
|
||||
|
||||
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,359 @@
|
|||
#include "image.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "../base/misc.h"
|
||||
#include "../third_party/texture_compressor/texture_compressor.h"
|
||||
#include "engine.h"
|
||||
#include "platform/asset_file.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 {
|
||||
|
||||
// Blend between two colors with equal weights.
|
||||
uint32_t Mix2(uint32_t p0, uint32_t p1) {
|
||||
uint32_t r = (((p0 >> 0) & 0xff) + ((p1 >> 0) & 0xff)) / 2;
|
||||
uint32_t g = (((p0 >> 8) & 0xff) + ((p1 >> 8) & 0xff)) / 2;
|
||||
uint32_t b = (((p0 >> 16) & 0xff) + ((p1 >> 16) & 0xff)) / 2;
|
||||
uint32_t a = (((p0 >> 24) & 0xff) + ((p1 >> 24) & 0xff)) / 2;
|
||||
|
||||
return (r << 0) | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
|
||||
// Blend between four colors with equal weights.
|
||||
uint32_t Mix4(uint32_t p0, uint32_t p1, uint32_t p2, uint32_t p3) {
|
||||
uint32_t r = (((p0 >> 0) & 0xff) + ((p1 >> 0) & 0xff) + ((p2 >> 0) & 0xff) +
|
||||
((p3 >> 0) & 0xff)) /
|
||||
4;
|
||||
uint32_t g = (((p0 >> 8) & 0xff) + ((p1 >> 8) & 0xff) + ((p2 >> 8) & 0xff) +
|
||||
((p3 >> 8) & 0xff)) /
|
||||
4;
|
||||
uint32_t b = (((p0 >> 16) & 0xff) + ((p1 >> 16) & 0xff) +
|
||||
((p2 >> 16) & 0xff) + ((p3 >> 16) & 0xff)) /
|
||||
4;
|
||||
uint32_t a = (((p0 >> 24) & 0xff) + ((p1 >> 24) & 0xff) +
|
||||
((p2 >> 24) & 0xff) + ((p3 >> 24) & 0xff)) /
|
||||
4;
|
||||
|
||||
return (r << 0) | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
|
||||
// Anisotropic blending of colors.
|
||||
void MipNonUniform(void* dst, const void* src, size_t length) {
|
||||
const uint32_t* s = reinterpret_cast<const uint32_t*>(src);
|
||||
uint32_t* d = reinterpret_cast<uint32_t*>(dst);
|
||||
for (size_t y = 0; y < length; ++y) {
|
||||
*d++ = Mix2(s[0], s[1]);
|
||||
s += 2;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
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) {
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
buffer_.reset((uint8_t*)AlignedAlloc<16>(w * h * 4 * sizeof(uint8_t)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Image::Copy(const Image& other) {
|
||||
if (other.buffer_) {
|
||||
int size = other.GetSize();
|
||||
buffer_.reset((uint8_t*)AlignedAlloc<16>(size));
|
||||
memcpy(buffer_.get(), other.buffer_.get(), size);
|
||||
}
|
||||
width_ = other.width_;
|
||||
height_ = other.height_;
|
||||
format_ = other.format_;
|
||||
}
|
||||
|
||||
bool Image::CreateMip(const Image& other) {
|
||||
if (other.width_ <= 1 || other.height_ <= 1 || other.GetFormat() != kRGBA32)
|
||||
return false;
|
||||
|
||||
// Reduce the dimensions.
|
||||
width_ = std::max(other.width_ >> 1, 1);
|
||||
height_ = std::max(other.height_ >> 1, 1);
|
||||
format_ = kRGBA32;
|
||||
buffer_.reset((uint8_t*)AlignedAlloc<16>(GetSize()));
|
||||
|
||||
// If the width isn't perfectly divisable with two, then we end up skewing
|
||||
// the image because the source offset isn't updated properly.
|
||||
bool unaligned_width = other.width_ & 1;
|
||||
|
||||
// Special case the non-uniform/anisotropic cases, eg 4:1 or 1:4 textures.
|
||||
// This is only an issue once we reach the highest mip levels where one
|
||||
// dimension is one pixel.
|
||||
if (other.width_ == 1) {
|
||||
// Interestingly the horizontal and vertical case becomes the same code,
|
||||
// it's only about which value to use as the run length that differs.
|
||||
MipNonUniform(buffer_.get(), other.buffer_.get(), height_);
|
||||
} else if (other.height_ == 1) {
|
||||
MipNonUniform(buffer_.get(), other.buffer_.get(), width_);
|
||||
} else {
|
||||
const uint32_t* s = reinterpret_cast<const uint32_t*>(other.buffer_.get());
|
||||
uint32_t* d = reinterpret_cast<uint32_t*>(buffer_.get());
|
||||
for (size_t y = 0; y < height_; ++y) {
|
||||
for (size_t x = 0; x < width_; ++x) {
|
||||
*d++ = Mix4(s[0], s[1], s[other.width_], s[other.width_ + 1]);
|
||||
s += 2;
|
||||
}
|
||||
if (unaligned_width)
|
||||
++s;
|
||||
s += other.width_;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Image::Load(const std::string& file_name) {
|
||||
size_t buffer_size = 0;
|
||||
auto 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<16>(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<16>(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:
|
||||
case kATC:
|
||||
return ((width_ + 3) / 4) * ((height_ + 3) / 4) * 8;
|
||||
case kDXT5:
|
||||
case kATCIA:
|
||||
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 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<16>(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;
|
||||
}
|
||||
}
|
||||
|
||||
bool Image::Compress() {
|
||||
if (IsCompressed())
|
||||
return true;
|
||||
|
||||
TextureCompressor* tc = Engine::Get().GetTextureCompressor(true);
|
||||
if (!tc)
|
||||
return false;
|
||||
|
||||
switch (tc->format()) {
|
||||
case TextureCompressor::kFormatATC:
|
||||
format_ = kATC;
|
||||
break;
|
||||
case TextureCompressor::kFormatATCIA:
|
||||
format_ = kATCIA;
|
||||
break;
|
||||
case TextureCompressor::kFormatDXT1:
|
||||
format_ = kDXT1;
|
||||
break;
|
||||
case TextureCompressor::kFormatDXT5:
|
||||
format_ = kDXT5;
|
||||
break;
|
||||
case TextureCompressor::kFormatETC1:
|
||||
format_ = kETC1;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG << "Compressing image. Format: " << format_;
|
||||
|
||||
unsigned compressedSize = GetSize();
|
||||
uint8_t* compressedBuffer =
|
||||
(uint8_t*)AlignedAlloc<16>(compressedSize * sizeof(uint8_t));
|
||||
|
||||
const uint8_t* src = buffer_.get();
|
||||
uint8_t* dst = compressedBuffer;
|
||||
|
||||
tc->Compress(src, dst, width_, height_, TextureCompressor::kQualityHigh);
|
||||
|
||||
buffer_.reset(compressedBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t* Image::GetBuffer() {
|
||||
return buffer_.get();
|
||||
}
|
||||
|
||||
void Image::Clear(Vector4 rgba) {
|
||||
// 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() {
|
||||
// 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) {
|
||||
// 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,59 @@
|
|||
#ifndef IMAGE_H
|
||||
#define IMAGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include "../base/mem.h"
|
||||
#include "../base/vecmath.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Image {
|
||||
public:
|
||||
enum Format { kRGBA32, kDXT1, kDXT5, kETC1, kATC, kATCIA };
|
||||
|
||||
Image();
|
||||
Image(const Image& other);
|
||||
~Image();
|
||||
|
||||
Image& operator=(const Image& other);
|
||||
|
||||
bool Create(int width, int height);
|
||||
void Copy(const Image& other);
|
||||
bool CreateMip(const Image& other);
|
||||
bool Load(const std::string& file_name);
|
||||
|
||||
bool Compress();
|
||||
|
||||
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;
|
||||
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
} // 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() const {
|
||||
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() const override { return current_frame_; }
|
||||
size_t GetNumFrames() const 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,169 @@
|
|||
#include "mesh.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <cassert>
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "../third_party/jsoncpp/json.h"
|
||||
#include "engine.h"
|
||||
#include "platform/asset_file.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) {
|
||||
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) {
|
||||
size_t buffer_size = 0;
|
||||
auto json_mesh = 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,55 @@
|
|||
#ifndef MESH_H
|
||||
#define MESH_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "renderer/renderer_types.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Mesh {
|
||||
public:
|
||||
static const char kLayoutDelimiter[];
|
||||
|
||||
Mesh() = default;
|
||||
~Mesh() = 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);
|
||||
|
||||
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
|