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
|