mirror of
https://github.com/sys-32Dev/TACZ.git
synced 2026-08-08 20:06:25 +00:00
完成初次提交
This commit is contained in:
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Disable autocrlf on generated files, they always generate with LF
|
||||
# Add any extra files or paths here to make git stop saying they
|
||||
# are changed when only line endings change.
|
||||
src/generated/**/.cache/cache text eol=lf
|
||||
src/generated/**/*.json text eol=lf
|
||||
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# eclipse
|
||||
bin
|
||||
*.launch
|
||||
.settings
|
||||
.metadata
|
||||
.classpath
|
||||
.project
|
||||
|
||||
# idea
|
||||
out
|
||||
*.ipr
|
||||
*.iws
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# gradle
|
||||
build
|
||||
.gradle
|
||||
|
||||
# other
|
||||
eclipse
|
||||
run
|
||||
test
|
||||
logs
|
||||
|
||||
# Files from Forge MDK
|
||||
forge*changelog.txt
|
||||
223
build.gradle
Normal file
223
build.gradle
Normal file
@@ -0,0 +1,223 @@
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
// 设置日期格式,用于填充快照版版本号
|
||||
SimpleDateFormat FORMAT = new SimpleDateFormat("MMdd-HHmmss")
|
||||
FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"))
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url = 'https://files.minecraftforge.net/maven' }
|
||||
maven { url = 'https://maven.parchmentmc.org' }
|
||||
maven { url = 'https://repo.spongepowered.org/maven' }
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
|
||||
classpath 'org.parchmentmc:librarian:1.+'
|
||||
classpath group: 'org.spongepowered', name: 'mixingradle', version: '0.7-SNAPSHOT'
|
||||
// shadow 插件,用来打包 Apache Commons Math 库
|
||||
classpath 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'net.minecraftforge.gradle'
|
||||
apply plugin: 'org.parchmentmc.librarian.forgegradle'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'org.spongepowered.mixin'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
// version = "1.0.0"
|
||||
// 版本号,正式发布需要修改这一行
|
||||
version = FORMAT.format(new Date())
|
||||
group = "com.tacz"
|
||||
archivesBaseName = "tacz-1.20.1"
|
||||
|
||||
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
|
||||
|
||||
mixin {
|
||||
add sourceSets.main, "tacz.refmap.json"
|
||||
config "tacz.mixins.json"
|
||||
}
|
||||
|
||||
minecraft {
|
||||
// 使用 parchment 来反混淆变量名
|
||||
mappings channel: 'parchment', version: '2023.08.20-1.20.1'
|
||||
// 使用 access transformer 来修改原版的一些方法访问修饰符
|
||||
accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
|
||||
// 运行参数
|
||||
runs {
|
||||
client {
|
||||
// 使用 JetBrainsRuntime 时需要的参数,否则无法热重载
|
||||
// 如果你使用别的 JDK,那么可以删除这一行
|
||||
jvmArgs "-XX:+AllowEnhancedClassRedefinition"
|
||||
// 每个启动单独区分文件夹
|
||||
workingDirectory project.file('run/client_a')
|
||||
property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
property 'mixin.env.remapRefMap', 'true'
|
||||
property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg"
|
||||
mods {
|
||||
tacz {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client2 {
|
||||
parent minecraft.runs.client
|
||||
workingDirectory project.file('run/client_b')
|
||||
// 设定用户名
|
||||
args '--username', 'mayday_memory'
|
||||
mods {
|
||||
tacz {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
workingDirectory project.file('run/server')
|
||||
property 'mixin.env.disableRefMap', 'true'
|
||||
property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
property 'mixin.env.remapRefMap', 'true'
|
||||
property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg"
|
||||
mods {
|
||||
tacz {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data {
|
||||
workingDirectory project.file('run/data')
|
||||
property 'mixin.env.disableRefMap', 'true'
|
||||
property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP'
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
args '--mod', 'tacz', '--all', '--output', file('src/generated/resources/')
|
||||
mods {
|
||||
tacz {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets.main.resources { srcDir 'src/generated/resources' }
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
// 阿里云镜像,方便国内开发
|
||||
url = uri("https://maven.aliyun.com/repository/public/")
|
||||
}
|
||||
maven {
|
||||
// location of the maven that hosts JEI files since January 2023
|
||||
// Patchouli
|
||||
name = "Jared's maven"
|
||||
url = "https://maven.blamejared.com/"
|
||||
}
|
||||
maven {
|
||||
// location of a maven mirror for JEI files, as a fallback
|
||||
name = "Mod Maven"
|
||||
url = "https://modmaven.k-4u.nl"
|
||||
}
|
||||
maven {
|
||||
// cloth config api
|
||||
url "https://maven.shedaniel.me/"
|
||||
}
|
||||
maven {
|
||||
url "https://cursemaven.com"
|
||||
content {
|
||||
includeGroup "curse.maven"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft "net.minecraftforge:forge:${forge_version}"
|
||||
|
||||
// Apache Commons Math 库,用于进行一些插值运算
|
||||
minecraftLibrary 'org.apache.commons:commons-math3:3.6.1'
|
||||
|
||||
// compile against the JEI API but do not include it at runtime
|
||||
compileOnly(fg.deobf("mezz.jei:jei-1.20.1-common-api:${jei_version}"))
|
||||
compileOnly(fg.deobf("mezz.jei:jei-1.20.1-forge-api:${jei_version}"))
|
||||
// at runtime, use the full JEI jar for Forge
|
||||
runtimeOnly(fg.deobf("mezz.jei:jei-1.20.1-forge:${jei_version}"))
|
||||
|
||||
implementation fg.deobf("me.shedaniel.cloth:cloth-config-forge:${cloth_config_forge}")
|
||||
compileOnly fg.deobf("org.embeddedt:embeddium-1.20.1:${embeddedt_id}")
|
||||
compileOnly fg.deobf("curse.maven:oculus-581495:${oculus_id}")
|
||||
|
||||
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
|
||||
}
|
||||
|
||||
jar {
|
||||
// 打包时,给 manifest 文件写入一些有用信息
|
||||
// 这些信息会被游戏调用,从而显示成模组的名称和版本信息
|
||||
manifest {
|
||||
attributes([
|
||||
"Implementation-Title" : project.name,
|
||||
"Implementation-Version": project.version
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
// 编译源码文件,方便发布 maven
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
// 发布到 jitpack maven,方便其他模组开发者
|
||||
publications.create('mavenJava', MavenPublication) {
|
||||
from components.java
|
||||
groupId = project.group
|
||||
version = project.version
|
||||
artifactId = project.archivesBaseName
|
||||
pom {
|
||||
name = project.archivesBaseName
|
||||
// 删除所有依赖,不需要
|
||||
pom.withXml {
|
||||
asNode().dependencies.dependency.each { dep ->
|
||||
assert dep.parent().remove(dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 禁用元数据生成,它生成的 maven 项目有问题
|
||||
tasks.withType(GenerateModuleMetadata).configureEach {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
dependencies {
|
||||
// 打包 Apache Commons Math 库
|
||||
include(dependency("org.apache.commons:commons-math3:3.6.1"))
|
||||
// 排除 Apache Commons Math 库中不需要的说明文件
|
||||
exclude('META-INF/LICENSE.txt')
|
||||
exclude('META-INF/NOTICE.txt')
|
||||
exclude('META-INF/maven/')
|
||||
}
|
||||
// 防止与其他模组导致的同名路径冲突
|
||||
relocate 'org.apache.commons.math3', 'com.tacz.guns.libs.org.apache.commons.math3'
|
||||
// 最小化
|
||||
minimize()
|
||||
}
|
||||
|
||||
reobf {
|
||||
// 与名称的混淆有关的,反正需要 shadowJar 一下
|
||||
shadowJar {}
|
||||
}
|
||||
|
||||
// 保证编译时的 assemble 在 shadowJar 之后执行
|
||||
assemble.dependsOn shadowJar
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
7
gradle.properties
Normal file
7
gradle.properties
Normal file
@@ -0,0 +1,7 @@
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
org.gradle.daemon=false
|
||||
forge_version=1.20.1-47.1.0
|
||||
jei_version=15.0.0.12
|
||||
cloth_config_forge=11.1.106
|
||||
embeddedt_id=0.2.18-git.ce29192+mc1.20.1
|
||||
oculus_id=4767500
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
185
gradlew
vendored
Normal file
185
gradlew
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# 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\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# 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
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
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" -a "$nonstop" = "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 or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# 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=`expr $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
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
36
src/main/java/com/tacz/guns/GunMod.java
Normal file
36
src/main/java/com/tacz/guns/GunMod.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.tacz.guns;
|
||||
|
||||
import com.tacz.guns.config.ClientConfig;
|
||||
import com.tacz.guns.config.CommonConfig;
|
||||
import com.tacz.guns.config.ServerConfig;
|
||||
import com.tacz.guns.init.*;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@Mod(GunMod.MOD_ID)
|
||||
public class GunMod {
|
||||
public static final String MOD_ID = "tacz";
|
||||
public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
|
||||
|
||||
public GunMod() {
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, CommonConfig.init());
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ServerConfig.init());
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ClientConfig.init());
|
||||
|
||||
IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
ModBlocks.BLOCKS.register(bus);
|
||||
ModBlocks.TILE_ENTITIES.register(bus);
|
||||
ModItems.ITEMS.register(bus);
|
||||
ModEntities.ENTITY_TYPES.register(bus);
|
||||
ModEntities.DATA_SERIALIZERS.register(bus);
|
||||
ModRecipe.RECIPE_SERIALIZERS.register(bus);
|
||||
ModContainer.CONTAINER_TYPE.register(bus);
|
||||
ModSounds.SOUNDS.register(bus);
|
||||
ModParticles.PARTICLE_TYPES.register(bus);
|
||||
}
|
||||
}
|
||||
20
src/main/java/com/tacz/guns/api/DefaultAssets.java
Normal file
20
src/main/java/com/tacz/guns/api/DefaultAssets.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.tacz.guns.api;
|
||||
|
||||
import com.tacz.guns.GunMod;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public final class DefaultAssets {
|
||||
public static ResourceLocation EMPTY_GUN_ID = new ResourceLocation(GunMod.MOD_ID, "empty");
|
||||
public static ResourceLocation DEFAULT_GUN_DISPLAY = new ResourceLocation(GunMod.MOD_ID, "ak47_display");
|
||||
public static ResourceLocation DEFAULT_GUN_DATA = new ResourceLocation(GunMod.MOD_ID, "ak47_data");
|
||||
|
||||
public static ResourceLocation DEFAULT_AMMO_ID = new ResourceLocation(GunMod.MOD_ID, "762x39");
|
||||
public static ResourceLocation DEFAULT_AMMO_DISPLAY = new ResourceLocation(GunMod.MOD_ID, "762x39_display");
|
||||
public static ResourceLocation EMPTY_AMMO_ID = new ResourceLocation(GunMod.MOD_ID, "empty");
|
||||
|
||||
public static ResourceLocation DEFAULT_ATTACHMENT_ID = new ResourceLocation(GunMod.MOD_ID, "sight_sro_dot");
|
||||
public static ResourceLocation EMPTY_ATTACHMENT_ID = new ResourceLocation(GunMod.MOD_ID, "empty");
|
||||
|
||||
public static ResourceLocation DEFAULT_ATTACHMENT_SKIN_ID = new ResourceLocation(GunMod.MOD_ID, "sight_sro_dot_blue");
|
||||
public static ResourceLocation EMPTY_ATTACHMENT_SKIN_ID = new ResourceLocation(GunMod.MOD_ID, "empty");
|
||||
}
|
||||
89
src/main/java/com/tacz/guns/api/TimelessAPI.java
Normal file
89
src/main/java/com/tacz/guns/api/TimelessAPI.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package com.tacz.guns.api;
|
||||
|
||||
import com.tacz.guns.api.client.other.IThirdPersonAnimation;
|
||||
import com.tacz.guns.api.client.other.ThirdPersonManager;
|
||||
import com.tacz.guns.client.resource.ClientGunPackLoader;
|
||||
import com.tacz.guns.client.resource.index.ClientAmmoIndex;
|
||||
import com.tacz.guns.client.resource.index.ClientAttachmentIndex;
|
||||
import com.tacz.guns.client.resource.index.ClientGunIndex;
|
||||
import com.tacz.guns.crafting.GunSmithTableRecipe;
|
||||
import com.tacz.guns.resource.CommonAssetManager;
|
||||
import com.tacz.guns.resource.CommonGunPackLoader;
|
||||
import com.tacz.guns.resource.index.CommonAmmoIndex;
|
||||
import com.tacz.guns.resource.index.CommonAttachmentIndex;
|
||||
import com.tacz.guns.resource.index.CommonGunIndex;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public final class TimelessAPI {
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static Optional<ClientGunIndex> getClientGunIndex(ResourceLocation gunId) {
|
||||
return ClientGunPackLoader.getGunIndex(gunId);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static Optional<ClientAttachmentIndex> getClientAttachmentIndex(ResourceLocation attachmentId) {
|
||||
return ClientGunPackLoader.getAttachmentIndex(attachmentId);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static Optional<ClientAmmoIndex> getClientAmmoIndex(ResourceLocation ammoId) {
|
||||
return ClientGunPackLoader.getAmmoIndex(ammoId);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static Set<Map.Entry<ResourceLocation, ClientGunIndex>> getAllClientGunIndex() {
|
||||
return ClientGunPackLoader.getAllGuns();
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static Set<Map.Entry<ResourceLocation, ClientAmmoIndex>> getAllClientAmmoIndex() {
|
||||
return ClientGunPackLoader.getAllAmmo();
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static Set<Map.Entry<ResourceLocation, ClientAttachmentIndex>> getAllClientAttachmentIndex() {
|
||||
return ClientGunPackLoader.getAllAttachments();
|
||||
}
|
||||
|
||||
public static Optional<CommonGunIndex> getCommonGunIndex(ResourceLocation gunId) {
|
||||
return CommonGunPackLoader.getGunIndex(gunId);
|
||||
}
|
||||
|
||||
public static Optional<CommonAttachmentIndex> getCommonAttachmentIndex(ResourceLocation attachmentId) {
|
||||
return CommonGunPackLoader.getAttachmentIndex(attachmentId);
|
||||
}
|
||||
|
||||
public static Optional<CommonAmmoIndex> getCommonAmmoIndex(ResourceLocation ammoId) {
|
||||
return CommonGunPackLoader.getAmmoIndex(ammoId);
|
||||
}
|
||||
|
||||
public static Optional<GunSmithTableRecipe> getRecipe(ResourceLocation recipeId) {
|
||||
return CommonAssetManager.INSTANCE.getRecipe(recipeId);
|
||||
}
|
||||
|
||||
public static Set<Map.Entry<ResourceLocation, CommonGunIndex>> getAllCommonGunIndex() {
|
||||
return CommonGunPackLoader.getAllGuns();
|
||||
}
|
||||
|
||||
public static Set<Map.Entry<ResourceLocation, CommonAmmoIndex>> getAllCommonAmmoIndex() {
|
||||
return CommonGunPackLoader.getAllAmmo();
|
||||
}
|
||||
|
||||
public static Set<Map.Entry<ResourceLocation, CommonAttachmentIndex>> getAllCommonAttachmentIndex() {
|
||||
return CommonGunPackLoader.getAllAttachments();
|
||||
}
|
||||
|
||||
public static Map<ResourceLocation, GunSmithTableRecipe> getAllRecipes() {
|
||||
return CommonAssetManager.INSTANCE.getAllRecipes();
|
||||
}
|
||||
|
||||
public static void registerThirdPersonAnimation(String name, IThirdPersonAnimation animation) {
|
||||
ThirdPersonManager.register(name, animation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.tacz.guns.api.client.event;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
|
||||
/**
|
||||
* 在调用 ItemInHandRenderer#renderHandsWithItems 方法时触发该事件
|
||||
* 用于相机动画相关调用
|
||||
*/
|
||||
public class BeforeRenderHandEvent extends Event {
|
||||
private final PoseStack poseStack;
|
||||
|
||||
public BeforeRenderHandEvent(PoseStack poseStack) {
|
||||
this.poseStack = poseStack;
|
||||
}
|
||||
|
||||
public PoseStack getPoseStack() {
|
||||
return poseStack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.tacz.guns.api.client.event;
|
||||
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraftforge.client.event.EntityViewRenderEvent;
|
||||
|
||||
/**
|
||||
* 在调用 GameRenderer#getFov 方法时触发该事件
|
||||
* 用于瞄准镜瞄准时 FOV 相关变化时调用
|
||||
*/
|
||||
public class FieldOfView extends EntityViewRenderEvent {
|
||||
private final boolean isItemWithHand;
|
||||
private double fov;
|
||||
|
||||
public FieldOfView(GameRenderer renderer, Camera camera, double partialTicks, double fov, boolean isItemWithHand) {
|
||||
super(renderer, camera, partialTicks);
|
||||
this.fov = fov;
|
||||
this.isItemWithHand = isItemWithHand;
|
||||
}
|
||||
|
||||
public double getFOV() {
|
||||
return fov;
|
||||
}
|
||||
|
||||
public void setFOV(double fov) {
|
||||
this.fov = fov;
|
||||
}
|
||||
|
||||
public boolean isItemWithHand() {
|
||||
return isItemWithHand;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.tacz.guns.api.client.event;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Cancelable;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
|
||||
/**
|
||||
* 当第一人称视角触发摇晃时,玩家手部的摇晃
|
||||
*/
|
||||
public class RenderItemInHandBobEvent extends Event {
|
||||
/**
|
||||
* 使用注解也可以,但是热重载会导致游戏崩溃
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Cancelable
|
||||
public static class BobHurt extends RenderItemInHandBobEvent {
|
||||
}
|
||||
|
||||
@Cancelable
|
||||
public static class BobView extends RenderItemInHandBobEvent {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.tacz.guns.api.client.event;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Cancelable;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
|
||||
/**
|
||||
* 当第一人称视角触发摇晃时,世界背景的摇晃
|
||||
*/
|
||||
public class RenderLevelBobEvent extends Event {
|
||||
/**
|
||||
* 使用注解也可以,但是热重载会导致游戏崩溃
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Cancelable
|
||||
public static class BobHurt extends RenderLevelBobEvent {
|
||||
}
|
||||
|
||||
@Cancelable
|
||||
public static class BobView extends RenderLevelBobEvent {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.tacz.guns.api.client.event;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
|
||||
/**
|
||||
* 玩家交换主副手物品时触发该事件
|
||||
*/
|
||||
public class SwapItemWithOffHand extends Event {
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.tacz.guns.api.client.gameplay;
|
||||
|
||||
import com.tacz.guns.api.entity.ShootResult;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
/**
|
||||
* 客户端枪械操纵者
|
||||
* 目前仅用于 LocalPlayer
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public interface IClientPlayerGunOperator {
|
||||
/**
|
||||
* LocalPlayer 通过 Mixin 的方式实现了这个接口
|
||||
*/
|
||||
static IClientPlayerGunOperator fromLocalPlayer(LocalPlayer player) {
|
||||
return (IClientPlayerGunOperator) player;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查玩家能否开火,并执行客户端开火逻辑。
|
||||
*
|
||||
* @return 返回开火的结果
|
||||
*/
|
||||
ShootResult shoot();
|
||||
|
||||
/**
|
||||
* 执行客户端切枪逻辑。
|
||||
*/
|
||||
void draw(ItemStack lastItem);
|
||||
|
||||
/**
|
||||
* 客户端手动换弹
|
||||
*/
|
||||
void bolt();
|
||||
|
||||
/**
|
||||
* 客户端换弹
|
||||
*/
|
||||
void reload();
|
||||
|
||||
/**
|
||||
* 客户端检视
|
||||
*/
|
||||
void inspect();
|
||||
|
||||
/**
|
||||
* 客户端切换开火模式
|
||||
*/
|
||||
void fireSelect();
|
||||
|
||||
/**
|
||||
* 客户端瞄准
|
||||
*/
|
||||
void aim(boolean isAim);
|
||||
|
||||
/**
|
||||
* 客户端是否处于瞄准状态
|
||||
*/
|
||||
boolean isAim();
|
||||
|
||||
/**
|
||||
* 客户端瞄准进度
|
||||
*
|
||||
* @return 0-1,1 代表开镜进度到 100%
|
||||
*/
|
||||
float getClientAimingProgress(float partialTicks);
|
||||
|
||||
/**
|
||||
* 客户端射击冷却时间
|
||||
*/
|
||||
long getClientShootCoolDown();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.tacz.guns.api.client.other;
|
||||
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
||||
public interface IThirdPersonAnimation {
|
||||
/**
|
||||
* 第三人称动画:主手持有枪械时
|
||||
*
|
||||
* @param entity 持有枪械的实体
|
||||
* @param rightArm 右手模型
|
||||
* @param leftArm 左手模型
|
||||
* @param head 头部模型
|
||||
*/
|
||||
void animateGunHold(LivingEntity entity, ModelPart rightArm, ModelPart leftArm, ModelPart body, ModelPart head);
|
||||
|
||||
/**
|
||||
* 第三人称动画:枪械瞄准时
|
||||
*
|
||||
* @param entity 持有枪械的实体
|
||||
* @param rightArm 右手模型
|
||||
* @param leftArm 左手模型
|
||||
* @param head 头部模型
|
||||
* @param aimProgress 瞄准进度 0-1
|
||||
*/
|
||||
void animateGunAim(LivingEntity entity, ModelPart rightArm, ModelPart leftArm, ModelPart body, ModelPart head, float aimProgress);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.tacz.guns.api.client.other;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* 用来在收物品时,让其保持一段时间渲染的接口
|
||||
*/
|
||||
public interface KeepingItemRenderer {
|
||||
/**
|
||||
* 物品保持渲染的时间
|
||||
*
|
||||
* @param itemStack 保持的物品
|
||||
* @param timeMs 时间,单位毫秒
|
||||
*/
|
||||
void keep(ItemStack itemStack, long timeMs);
|
||||
|
||||
/**
|
||||
* 获取当前主手正在渲染的物品
|
||||
*/
|
||||
ItemStack getCurrentItem();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.tacz.guns.api.client.other;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 简单的第三人称持枪动画 Manager
|
||||
*/
|
||||
public final class ThirdPersonManager {
|
||||
private static final Map<String, IThirdPersonAnimation> CACHE = Maps.newHashMap();
|
||||
private static final String RESERVED_DEFAULT_NAME = "default";
|
||||
private static final IThirdPersonAnimation DEFAULT = new IThirdPersonAnimation() {
|
||||
@Override
|
||||
public void animateGunHold(LivingEntity entity, ModelPart rightArm, ModelPart leftArm, ModelPart body, ModelPart head) {
|
||||
rightArm.yRot = -0.3F + head.yRot;
|
||||
leftArm.yRot = 0.8F + head.yRot;
|
||||
rightArm.xRot = -1.4F + head.xRot;
|
||||
leftArm.xRot = -1.4F + head.xRot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animateGunAim(LivingEntity entity, ModelPart rightArm, ModelPart leftArm, ModelPart body, ModelPart head, float aimProgress) {
|
||||
float lerp1 = Mth.lerp(aimProgress, 0.3f, 0.35f);
|
||||
float lerp2 = Mth.lerp(aimProgress, 1.4f, 1.6f);
|
||||
rightArm.yRot = -lerp1 + head.yRot;
|
||||
leftArm.yRot = 0.8F + head.yRot;
|
||||
rightArm.xRot = -lerp2 + head.xRot;
|
||||
leftArm.xRot = -lerp2 + head.xRot;
|
||||
}
|
||||
};
|
||||
|
||||
public static void registerDefault() {
|
||||
CACHE.put(RESERVED_DEFAULT_NAME, DEFAULT);
|
||||
}
|
||||
|
||||
public static void register(String name, IThirdPersonAnimation animation) {
|
||||
if (name.equals(RESERVED_DEFAULT_NAME)) {
|
||||
return;
|
||||
}
|
||||
CACHE.put(name, animation);
|
||||
}
|
||||
|
||||
public static IThirdPersonAnimation getAnimation(String name) {
|
||||
return CACHE.getOrDefault(name, DEFAULT);
|
||||
}
|
||||
}
|
||||
113
src/main/java/com/tacz/guns/api/entity/IGunOperator.java
Normal file
113
src/main/java/com/tacz/guns/api/entity/IGunOperator.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package com.tacz.guns.api.entity;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface IGunOperator {
|
||||
/**
|
||||
* LivingEntity 通过 Mixin 的方式实现了这个接口
|
||||
*/
|
||||
static IGunOperator fromLivingEntity(LivingEntity entity) {
|
||||
return (IGunOperator) entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取从服务端同步的射击的冷却
|
||||
*/
|
||||
long getSynShootCoolDown();
|
||||
|
||||
/**
|
||||
* 获取从服务端同步的切枪的冷却
|
||||
*/
|
||||
long getSynDrawCoolDown();
|
||||
|
||||
/**
|
||||
* 获取从服务端同步的手动换弹的冷却
|
||||
*/
|
||||
long getSynBoltCoolDown();
|
||||
|
||||
/**
|
||||
* 获取从服务端同步的换弹状态
|
||||
*/
|
||||
ReloadState getSynReloadState();
|
||||
|
||||
/**
|
||||
* 获取从服务端同步的瞄准进度
|
||||
*/
|
||||
float getSynAimingProgress();
|
||||
|
||||
/**
|
||||
* 获取该实体是否正在瞄准。
|
||||
* 注意,这个方法并不等价于 getSynAimingProgress() > 0。
|
||||
* 如果玩家正在瞄准,瞄准进度会增加,否则瞄准进度会减少。
|
||||
*/
|
||||
boolean getSynIsAiming();
|
||||
|
||||
/**
|
||||
* 获取玩家持枪奔跑的时长。
|
||||
* 最大不会大于枪械数据中设置的 sprintTime,最小不会小于 0。
|
||||
*/
|
||||
float getSynSprintTime();
|
||||
|
||||
/**
|
||||
* 初始化枪械操作的各个数据,如换弹冷却、开火冷却等。
|
||||
*/
|
||||
void initialData();
|
||||
|
||||
/**
|
||||
* 服务端切枪逻辑
|
||||
*/
|
||||
void draw(Supplier<ItemStack> itemStackSupplier);
|
||||
|
||||
/**
|
||||
* 服务端拉栓逻辑
|
||||
*/
|
||||
void bolt();
|
||||
|
||||
/**
|
||||
* 服务端换弹逻辑
|
||||
*/
|
||||
void reload();
|
||||
|
||||
/**
|
||||
* 服务端切换开火模式的逻辑
|
||||
*/
|
||||
void fireSelect();
|
||||
|
||||
/**
|
||||
* 服务端调整倍镜的逻辑
|
||||
*/
|
||||
void zoom();
|
||||
|
||||
/**
|
||||
* 从实体的位置,向指定的方向开枪
|
||||
*
|
||||
* @param pitch 开火方向的俯仰角(即 xRot )
|
||||
* @param yaw 开火方向的偏航角(即 yRot )
|
||||
* @return 本次射击的结果
|
||||
*/
|
||||
ShootResult shoot(float pitch, float yaw);
|
||||
|
||||
/**
|
||||
* 服务端,该操作者是否受弹药数影响
|
||||
*
|
||||
* @return 如果为 false,那么开火时不会检查弹药,无论是玩家背包内还是枪械内的
|
||||
*/
|
||||
boolean needCheckAmmo();
|
||||
|
||||
/**
|
||||
* 服务端,开火是否消耗弹药
|
||||
*
|
||||
* @return 如果为 false,那么开火不会消耗枪械弹药
|
||||
*/
|
||||
boolean consumesAmmoOrNot();
|
||||
|
||||
/**
|
||||
* 服务端,应用瞄准的逻辑
|
||||
*
|
||||
* @param isAim 是否瞄准
|
||||
*/
|
||||
void aim(boolean isAim);
|
||||
}
|
||||
19
src/main/java/com/tacz/guns/api/entity/ITargetEntity.java
Normal file
19
src/main/java/com/tacz/guns/api/entity/ITargetEntity.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.tacz.guns.api.entity;
|
||||
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
|
||||
/**
|
||||
* 用于进行一些并非 {@link LivingEntity} 但是可被子弹击中的特殊实体的处理
|
||||
*/
|
||||
public interface ITargetEntity {
|
||||
/**
|
||||
* @param projectile 弹射物实体
|
||||
* @param result 击中实体的位置
|
||||
* @param source 伤害源类型
|
||||
* @param damage 伤害值
|
||||
*/
|
||||
void onProjectileHit(Entity projectile, EntityHitResult result, DamageSource source, float damage);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.tacz.guns.api.entity;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
||||
/**
|
||||
* 用于修改实体被子弹击中后的击退效果的设计
|
||||
* 默认给所有 LivingEntity 添加了此接口
|
||||
*/
|
||||
public interface KnockBackModifier {
|
||||
/**
|
||||
* LivingEntity 通过 Mixin 的方式实现了这个接口
|
||||
*/
|
||||
static KnockBackModifier fromLivingEntity(LivingEntity entity) {
|
||||
return (KnockBackModifier) entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置击退效果,实体此时恢复正常原版击退逻辑
|
||||
*/
|
||||
void resetKnockBackStrength();
|
||||
|
||||
/**
|
||||
* 获取击退强度
|
||||
*/
|
||||
double getKnockBackStrength();
|
||||
|
||||
/**
|
||||
* 设置击退强度
|
||||
*/
|
||||
void setKnockBackStrength(double strength);
|
||||
}
|
||||
111
src/main/java/com/tacz/guns/api/entity/ReloadState.java
Normal file
111
src/main/java/com/tacz/guns/api/entity/ReloadState.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package com.tacz.guns.api.entity;
|
||||
|
||||
public class ReloadState {
|
||||
/**
|
||||
* 没有进行换弹操作时,倒计时为 -1
|
||||
*/
|
||||
public static final int NOT_RELOADING_COUNTDOWN = -1;
|
||||
/**
|
||||
* 换弹状态
|
||||
*/
|
||||
protected ReloadState.StateType stateType;
|
||||
/**
|
||||
* 换弹状态的剩余时长,毫秒
|
||||
*/
|
||||
protected long countDown;
|
||||
|
||||
public ReloadState() {
|
||||
stateType = StateType.NOT_RELOADING;
|
||||
countDown = NOT_RELOADING_COUNTDOWN;
|
||||
}
|
||||
|
||||
public ReloadState(ReloadState src) {
|
||||
stateType = src.stateType;
|
||||
countDown = src.countDown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 返回当前的换弹状态的类型。可用于判断是否正在进行换弹、换弹处在的阶段等。
|
||||
*/
|
||||
public StateType getStateType() {
|
||||
return stateType;
|
||||
}
|
||||
|
||||
public void setStateType(StateType stateType) {
|
||||
this.stateType = stateType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 如果 StateType 为 NOT_RELOADING,则返回 NOT_RELOADING_COUNTDOWN(= -1), 否则返回当前状态剩余的时长,单位为 ms 。
|
||||
*/
|
||||
public long getCountDown() {
|
||||
if (stateType == StateType.NOT_RELOADING) {
|
||||
return NOT_RELOADING_COUNTDOWN;
|
||||
}
|
||||
return countDown;
|
||||
}
|
||||
|
||||
public void setCountDown(long countDown) {
|
||||
this.countDown = countDown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof ReloadState reloadState) {
|
||||
return reloadState.stateType.equals(stateType) && reloadState.countDown == countDown;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public enum StateType {
|
||||
/**
|
||||
* 表示当前玩家未进行换弹。
|
||||
*/
|
||||
NOT_RELOADING,
|
||||
/**
|
||||
* 表示当前换弹状态为 正在进行空仓换弹 ,并处在填装弹药阶段。
|
||||
*/
|
||||
EMPTY_RELOAD_FEEDING,
|
||||
/**
|
||||
* 表示当前换弹状态为 正在进行空仓换弹,并处在收尾阶段。
|
||||
*/
|
||||
EMPTY_RELOAD_FINISHING,
|
||||
/**
|
||||
* 表示当前换弹状态为 正在进行战术快速换弹 ,并处在填装弹药阶段。
|
||||
*/
|
||||
TACTICAL_RELOAD_FEEDING,
|
||||
/**
|
||||
* 表示当前换弹状态为 正在进行战术快速换弹,并处在收尾阶段。
|
||||
*/
|
||||
TACTICAL_RELOAD_FINISHING;
|
||||
|
||||
/**
|
||||
* 判断这个状态是否是空仓换弹过程中的其中一个阶段。包括空仓换弹的收尾阶段。
|
||||
*/
|
||||
public boolean isReloadingEmpty() {
|
||||
return this == EMPTY_RELOAD_FEEDING || this == EMPTY_RELOAD_FINISHING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断这个状态是否是战术换弹过程中的其中一个阶段。包括战术换弹的收尾阶段。
|
||||
*/
|
||||
public boolean isReloadingTactical() {
|
||||
return this == TACTICAL_RELOAD_FEEDING || this == TACTICAL_RELOAD_FINISHING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断这个状态是否是任意换弹过程中的其中一个阶段。包括任意换弹的收尾阶段。
|
||||
*/
|
||||
public boolean isReloading() {
|
||||
return isReloadingEmpty() || isReloadingTactical();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断这个状态是否是任意换弹过程中的的收尾阶段。
|
||||
*/
|
||||
public boolean isReloadFinishing() {
|
||||
return this == StateType.EMPTY_RELOAD_FINISHING || this == StateType.TACTICAL_RELOAD_FINISHING;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/main/java/com/tacz/guns/api/entity/ShootResult.java
Normal file
56
src/main/java/com/tacz/guns/api/entity/ShootResult.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package com.tacz.guns.api.entity;
|
||||
|
||||
public enum ShootResult {
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS,
|
||||
/**
|
||||
* 未知原因失败
|
||||
*/
|
||||
UNKNOWN_FAIL,
|
||||
/**
|
||||
* 射击冷却时间还没到
|
||||
*/
|
||||
COOL_DOWN,
|
||||
/**
|
||||
* 无弹药
|
||||
*/
|
||||
NO_AMMO,
|
||||
/**
|
||||
* 没有执行切枪逻辑
|
||||
*/
|
||||
NOT_DRAW,
|
||||
/**
|
||||
* 当前物品不是枪
|
||||
*/
|
||||
NOT_GUN,
|
||||
/**
|
||||
* 枪械 ID 不存在
|
||||
*/
|
||||
ID_NOT_EXIST,
|
||||
/**
|
||||
* 需要手动上膛
|
||||
*/
|
||||
NEED_BOLT,
|
||||
/**
|
||||
* 正处于换弹状态
|
||||
*/
|
||||
IS_RELOADING,
|
||||
/**
|
||||
* 正处于切枪状态
|
||||
*/
|
||||
IS_DRAWING,
|
||||
/**
|
||||
* 正处于拉拴状态
|
||||
*/
|
||||
IS_BOLTING,
|
||||
/**
|
||||
* 正处于疾跑状态
|
||||
*/
|
||||
IS_SPRINTING,
|
||||
/**
|
||||
* Forge 事件原因取消
|
||||
*/
|
||||
FORGE_EVENT_CANCEL
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.tacz.guns.api.event.common;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
/**
|
||||
* 生物的枪击发的事件。与 {@link GunShootEvent}不同的是,扣动一次扳机可能多次触发这个事件(如枪械处于 Burst 模式),但 {@link GunShootEvent} 只会触发一次
|
||||
*/
|
||||
public class GunFireEvent extends Event {
|
||||
private final LivingEntity shooter;
|
||||
private final ItemStack gunItemStack;
|
||||
private final LogicalSide logicalSide;
|
||||
|
||||
public GunFireEvent(LivingEntity shooter, ItemStack gunItemStack, LogicalSide side) {
|
||||
this.shooter = shooter;
|
||||
this.gunItemStack = gunItemStack;
|
||||
this.logicalSide = side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public LivingEntity getShooter() {
|
||||
return shooter;
|
||||
}
|
||||
|
||||
public ItemStack getGunItemStack() {
|
||||
return gunItemStack;
|
||||
}
|
||||
|
||||
public LogicalSide getLogicalSide() {
|
||||
return logicalSide;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.tacz.guns.api.event.common;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
/**
|
||||
* 生物切换枪械开火模式时触发的事件
|
||||
*/
|
||||
public class GunFireSelectEvent extends Event {
|
||||
private final LivingEntity shooter;
|
||||
private final ItemStack gunItemStack;
|
||||
private final LogicalSide logicalSide;
|
||||
|
||||
public GunFireSelectEvent(LivingEntity shooter, ItemStack gunItemStack, LogicalSide side) {
|
||||
this.shooter = shooter;
|
||||
this.gunItemStack = gunItemStack;
|
||||
this.logicalSide = side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public LivingEntity getShooter() {
|
||||
return shooter;
|
||||
}
|
||||
|
||||
public ItemStack getGunItemStack() {
|
||||
return gunItemStack;
|
||||
}
|
||||
|
||||
public LogicalSide getLogicalSide() {
|
||||
return logicalSide;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.tacz.guns.api.event.common;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
/**
|
||||
* 生物开始更换枪械弹药时触发的事件。
|
||||
*/
|
||||
public class GunReloadEvent extends Event {
|
||||
private final LivingEntity entity;
|
||||
private final ItemStack gunItemStack;
|
||||
private final LogicalSide logicalSide;
|
||||
|
||||
public GunReloadEvent(LivingEntity entity, ItemStack gunItemStack, LogicalSide side) {
|
||||
this.entity = entity;
|
||||
this.gunItemStack = gunItemStack;
|
||||
this.logicalSide = side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public LivingEntity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
public ItemStack getGunItemStack() {
|
||||
return gunItemStack;
|
||||
}
|
||||
|
||||
public LogicalSide getLogicalSide() {
|
||||
return logicalSide;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.tacz.guns.api.event.common;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
/**
|
||||
* 生物射击时触发的事件。与 {@link GunFireEvent}不同的是,扣动一次扳机只会触发一次这个事件,但可能多次触发 {@link GunFireEvent}(如枪械处于 Burst 模式)
|
||||
*/
|
||||
public class GunShootEvent extends Event {
|
||||
private final LivingEntity shooter;
|
||||
private final ItemStack gunItemStack;
|
||||
private final LogicalSide logicalSide;
|
||||
|
||||
public GunShootEvent(LivingEntity shooter, ItemStack gunItemStack, LogicalSide side) {
|
||||
this.shooter = shooter;
|
||||
this.gunItemStack = gunItemStack;
|
||||
this.logicalSide = side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public LivingEntity getShooter() {
|
||||
return shooter;
|
||||
}
|
||||
|
||||
public ItemStack getGunItemStack() {
|
||||
return gunItemStack;
|
||||
}
|
||||
|
||||
public LogicalSide getLogicalSide() {
|
||||
return logicalSide;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.tacz.guns.api.event.common;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 生物被枪械子弹伤害时触发的事件
|
||||
*/
|
||||
public class LivingHurtByGunEvent extends Event {
|
||||
private final @Nullable LivingEntity hurtEntity;
|
||||
private final @Nullable LivingEntity attacker;
|
||||
private final ResourceLocation gunId;
|
||||
private final float amount;
|
||||
private final boolean isHeadShot;
|
||||
private final LogicalSide logicalSide;
|
||||
|
||||
public LivingHurtByGunEvent(@Nullable LivingEntity hurtEntity, @Nullable LivingEntity attacker, ResourceLocation gunId, float amount, boolean isHeadShot, LogicalSide logicalSide) {
|
||||
this.hurtEntity = hurtEntity;
|
||||
this.attacker = attacker;
|
||||
this.gunId = gunId;
|
||||
this.amount = amount;
|
||||
this.isHeadShot = isHeadShot;
|
||||
this.logicalSide = logicalSide;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LivingEntity getHurtEntity() {
|
||||
return hurtEntity;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LivingEntity getAttacker() {
|
||||
return attacker;
|
||||
}
|
||||
|
||||
public ResourceLocation getGunId() {
|
||||
return gunId;
|
||||
}
|
||||
|
||||
public float getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public boolean isHeadShot() {
|
||||
return isHeadShot;
|
||||
}
|
||||
|
||||
public LogicalSide getLogicalSide() {
|
||||
return logicalSide;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.tacz.guns.api.event.common;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 生物被枪械子弹击杀时触发的事件
|
||||
*/
|
||||
public class LivingKillByGunEvent extends Event {
|
||||
private final @Nullable LivingEntity killedEntity;
|
||||
private final @Nullable LivingEntity attacker;
|
||||
private final ResourceLocation gunId;
|
||||
private final boolean isHeadShot;
|
||||
private final LogicalSide logicalSide;
|
||||
|
||||
public LivingKillByGunEvent(@Nullable LivingEntity hurtEntity, @Nullable LivingEntity attacker, ResourceLocation gunId, boolean isHeadShot, LogicalSide logicalSide) {
|
||||
this.killedEntity = hurtEntity;
|
||||
this.attacker = attacker;
|
||||
this.gunId = gunId;
|
||||
this.isHeadShot = isHeadShot;
|
||||
this.logicalSide = logicalSide;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LivingEntity getKilledEntity() {
|
||||
return killedEntity;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LivingEntity getAttacker() {
|
||||
return attacker;
|
||||
}
|
||||
|
||||
public ResourceLocation getGunId() {
|
||||
return gunId;
|
||||
}
|
||||
|
||||
public boolean isHeadShot() {
|
||||
return isHeadShot;
|
||||
}
|
||||
|
||||
public LogicalSide getLogicalSide() {
|
||||
return logicalSide;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.tacz.guns.api.event.server;
|
||||
|
||||
import com.tacz.guns.entity.EntityKineticBullet;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
|
||||
/**
|
||||
* 子弹击中方块时触发的事件,目前仅在服务端触发
|
||||
*/
|
||||
public class AmmoHitBlockEvent extends Event {
|
||||
private final Level level;
|
||||
private final BlockHitResult hitResult;
|
||||
private final BlockState state;
|
||||
private final EntityKineticBullet ammo;
|
||||
|
||||
public AmmoHitBlockEvent(Level level, BlockHitResult hitResult, BlockState state, EntityKineticBullet ammo) {
|
||||
this.level = level;
|
||||
this.hitResult = hitResult;
|
||||
this.state = state;
|
||||
this.ammo = ammo;
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public BlockHitResult getHitResult() {
|
||||
return hitResult;
|
||||
}
|
||||
|
||||
public BlockState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public EntityKineticBullet getAmmo() {
|
||||
return ammo;
|
||||
}
|
||||
}
|
||||
44
src/main/java/com/tacz/guns/api/item/IAmmo.java
Normal file
44
src/main/java/com/tacz/guns/api/item/IAmmo.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.tacz.guns.api.item;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface IAmmo {
|
||||
/**
|
||||
* @return 如果物品类型为 IAttachment 则返回显式转换后的实例,否则返回 null。
|
||||
*/
|
||||
@Nullable
|
||||
static IAmmo getIAmmoOrNull(@Nullable ItemStack stack) {
|
||||
if (stack == null) {
|
||||
return null;
|
||||
}
|
||||
if (stack.getItem() instanceof IAmmo iAmmo) {
|
||||
return iAmmo;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取弹药 ID
|
||||
*
|
||||
* @param ammo 输入物品
|
||||
* @return 弹药 ID
|
||||
*/
|
||||
ResourceLocation getAmmoId(ItemStack ammo);
|
||||
|
||||
/**
|
||||
* 设置弹药 ID
|
||||
*/
|
||||
void setAmmoId(ItemStack ammo, @Nullable ResourceLocation ammoId);
|
||||
|
||||
/**
|
||||
* 弹药是否属于这把枪
|
||||
*
|
||||
* @param gun 检查的枪械物品
|
||||
* @param ammo 检查的子弹物品
|
||||
* @return 是否属于这把枪
|
||||
*/
|
||||
boolean isAmmoOfGun(ItemStack gun, ItemStack ammo);
|
||||
}
|
||||
52
src/main/java/com/tacz/guns/api/item/IAmmoBox.java
Normal file
52
src/main/java/com/tacz/guns/api/item/IAmmoBox.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package com.tacz.guns.api.item;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* 子弹盒接口
|
||||
*/
|
||||
public interface IAmmoBox {
|
||||
/**
|
||||
* 获取子弹盒中的子弹 ID
|
||||
*
|
||||
* @param ammoBox 子弹盒
|
||||
* @return 子弹盒中的子弹 ID
|
||||
*/
|
||||
ResourceLocation getAmmoId(ItemStack ammoBox);
|
||||
|
||||
/**
|
||||
* 获取子弹盒中的子弹数量
|
||||
*
|
||||
* @param ammoBox 子弹盒
|
||||
* @return 子弹数量
|
||||
*/
|
||||
int getAmmoCount(ItemStack ammoBox);
|
||||
|
||||
/**
|
||||
* 设置子弹盒中子弹的 ID
|
||||
*/
|
||||
void setAmmoId(ItemStack ammoBox, ResourceLocation ammoId);
|
||||
|
||||
/**
|
||||
* 设置子弹盒中子弹数量
|
||||
*/
|
||||
void setAmmoCount(ItemStack ammoBox, int count);
|
||||
|
||||
/**
|
||||
* 子弹盒中的子弹是否属于这把枪
|
||||
*
|
||||
* @param gun 枪
|
||||
* @param ammoBox 子弹盒
|
||||
* @return 是否属于这把枪
|
||||
*/
|
||||
boolean isAmmoBoxOfGun(ItemStack gun, ItemStack ammoBox);
|
||||
|
||||
/**
|
||||
* 是否是无限子弹盒
|
||||
*
|
||||
* @param ammoBox 子弹盒
|
||||
* @return 是否是无限子弹盒
|
||||
*/
|
||||
boolean isCreative(ItemStack ammoBox);
|
||||
}
|
||||
62
src/main/java/com/tacz/guns/api/item/IAttachment.java
Normal file
62
src/main/java/com/tacz/guns/api/item/IAttachment.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package com.tacz.guns.api.item;
|
||||
|
||||
import com.tacz.guns.api.item.attachment.AttachmentType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface IAttachment {
|
||||
/**
|
||||
* @return 如果物品类型为 IAttachment 则返回显式转换后的实例,否则返回 null。
|
||||
*/
|
||||
@Nullable
|
||||
static IAttachment getIAttachmentOrNull(@Nullable ItemStack stack) {
|
||||
if (stack == null) {
|
||||
return null;
|
||||
}
|
||||
if (stack.getItem() instanceof IAttachment iAttachment) {
|
||||
return iAttachment;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配件 ID
|
||||
*/
|
||||
@Nonnull
|
||||
ResourceLocation getAttachmentId(ItemStack attachmentStack);
|
||||
|
||||
/**
|
||||
* 设置配件 ID
|
||||
*/
|
||||
void setAttachmentId(ItemStack attachmentStack, @Nullable ResourceLocation attachmentId);
|
||||
|
||||
/**
|
||||
* 获取配件的皮肤 ID
|
||||
*/
|
||||
@Nullable
|
||||
ResourceLocation getSkinId(ItemStack attachmentStack);
|
||||
|
||||
/**
|
||||
* 设置配件的皮肤 ID
|
||||
*/
|
||||
void setSkinId(ItemStack attachmentStack, @Nullable ResourceLocation skinId);
|
||||
|
||||
/**
|
||||
* 获取瞄具配件的缩放倍率的数字索引,仅瞄具配件可用
|
||||
*/
|
||||
int getZoomNumber(ItemStack attachmentStack);
|
||||
|
||||
/**
|
||||
* 设置瞄具配件的缩放倍率的数字索引
|
||||
*/
|
||||
void setZoomNumber(ItemStack attachmentStack, int zoomNumber);
|
||||
|
||||
/**
|
||||
* 配件类型
|
||||
*/
|
||||
@Nonnull
|
||||
AttachmentType getType(ItemStack attachmentStack);
|
||||
}
|
||||
183
src/main/java/com/tacz/guns/api/item/IGun.java
Normal file
183
src/main/java/com/tacz/guns/api/item/IGun.java
Normal file
@@ -0,0 +1,183 @@
|
||||
package com.tacz.guns.api.item;
|
||||
|
||||
import com.tacz.guns.api.item.attachment.AttachmentType;
|
||||
import com.tacz.guns.api.item.gun.AbstractGunItem;
|
||||
import com.tacz.guns.api.item.gun.FireMode;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 这里不包含枪械的逻辑,只包含枪械的各种 nbt 访问。<br>
|
||||
* 你可以在 {@link AbstractGunItem} 看到枪械逻辑
|
||||
*/
|
||||
public interface IGun {
|
||||
/**
|
||||
* @return 如果物品类型为 IGun 则返回显式转换后的实例,否则返回 null。
|
||||
*/
|
||||
@Nullable
|
||||
static IGun getIGunOrNull(@Nullable ItemStack stack) {
|
||||
if (stack == null) {
|
||||
return null;
|
||||
}
|
||||
if (stack.getItem() instanceof IGun iGun) {
|
||||
return iGun;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否主手持枪
|
||||
*/
|
||||
static boolean mainhandHoldGun(LivingEntity livingEntity) {
|
||||
return livingEntity.getMainHandItem().getItem() instanceof IGun;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主手枪械的开火模式
|
||||
*/
|
||||
static FireMode getMainhandFireMode(LivingEntity livingEntity) {
|
||||
ItemStack mainhandItem = livingEntity.getMainHandItem();
|
||||
if (mainhandItem.getItem() instanceof IGun iGun) {
|
||||
return iGun.getFireMode(mainhandItem);
|
||||
}
|
||||
return FireMode.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取瞄准进度
|
||||
*
|
||||
* @return 0-1,1 代表 100% 进度
|
||||
*/
|
||||
float getAimingZoom(ItemStack gunItem);
|
||||
|
||||
/**
|
||||
* 获取枪械 ID
|
||||
*/
|
||||
@Nonnull
|
||||
ResourceLocation getGunId(ItemStack gun);
|
||||
|
||||
/**
|
||||
* 设置枪械 ID
|
||||
*/
|
||||
void setGunId(ItemStack gun, @Nullable ResourceLocation gunId);
|
||||
|
||||
/**
|
||||
* 获取输入的经验值对应的等级。
|
||||
*
|
||||
* @param exp 经验值
|
||||
* @return 对应的等级
|
||||
*/
|
||||
int getLevel(int exp);
|
||||
|
||||
/**
|
||||
* 获取输入的等级需要至少多少的经验值。
|
||||
*
|
||||
* @param level 等级
|
||||
* @return 至少需要的经验值
|
||||
*/
|
||||
int getExp(int level);
|
||||
|
||||
/**
|
||||
* 返回允许的最大等级。
|
||||
*
|
||||
* @return 最大等级
|
||||
*/
|
||||
int getMaxLevel();
|
||||
|
||||
/**
|
||||
* 获取枪械当前等级
|
||||
*/
|
||||
int getLevel(ItemStack gun);
|
||||
|
||||
/**
|
||||
* 获取积累的全部经验值。
|
||||
*
|
||||
* @param gun 输入物品
|
||||
* @return 全部经验值
|
||||
*/
|
||||
int getExp(ItemStack gun);
|
||||
|
||||
/**
|
||||
* 获取到下个等级需要的经验值。
|
||||
*
|
||||
* @param gun 输入物品
|
||||
* @return 到下个等级需要的经验值。如果等级已经到达最大,则返回 0
|
||||
*/
|
||||
int getExpToNextLevel(ItemStack gun);
|
||||
|
||||
/**
|
||||
* 获取当前等级已经积累的经验值。
|
||||
*
|
||||
* @param gun 输入物品
|
||||
* @return 当前等级已经积累的经验值
|
||||
*/
|
||||
int getExpCurrentLevel(ItemStack gun);
|
||||
|
||||
/**
|
||||
* 获取开火模式
|
||||
*
|
||||
* @param gun 枪
|
||||
* @return 开火模式
|
||||
*/
|
||||
FireMode getFireMode(ItemStack gun);
|
||||
|
||||
/**
|
||||
* 设置开火模式
|
||||
*/
|
||||
void setFireMode(ItemStack gun, @Nullable FireMode fireMode);
|
||||
|
||||
/**
|
||||
* 获取当前枪械弹药数
|
||||
*/
|
||||
int getCurrentAmmoCount(ItemStack gun);
|
||||
|
||||
/**
|
||||
* 设置当前枪械弹药数
|
||||
*/
|
||||
void setCurrentAmmoCount(ItemStack gun, int ammoCount);
|
||||
|
||||
/**
|
||||
* 减少一个当前枪械弹药数
|
||||
*/
|
||||
void reduceCurrentAmmoCount(ItemStack gun);
|
||||
|
||||
/**
|
||||
* 获取当前枪械指定类型的配件
|
||||
*/
|
||||
@Nonnull
|
||||
ItemStack getAttachment(ItemStack gun, AttachmentType type);
|
||||
|
||||
/**
|
||||
* 安装配件
|
||||
*/
|
||||
void installAttachment(@Nonnull ItemStack gun, @Nonnull ItemStack attachment);
|
||||
|
||||
/**
|
||||
* 卸载配件
|
||||
*/
|
||||
void unloadAttachment(@Nonnull ItemStack gun, AttachmentType type);
|
||||
|
||||
/**
|
||||
* 该枪械是否允许装配该配件
|
||||
*/
|
||||
boolean allowAttachment(ItemStack gun, ItemStack attachmentItem);
|
||||
|
||||
/**
|
||||
* 该枪械是否允许某类型配件
|
||||
*/
|
||||
boolean allowAttachmentType(ItemStack gun, AttachmentType type);
|
||||
|
||||
/**
|
||||
* 枪管中是否有子弹,用于闭膛待击的枪械
|
||||
*/
|
||||
boolean hasBulletInBarrel(ItemStack gun);
|
||||
|
||||
/**
|
||||
* 设置枪管中的子弹有无,用于闭膛待击的枪械
|
||||
*/
|
||||
void setBulletInBarrel(ItemStack gun, boolean bulletInBarrel);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.tacz.guns.api.item.attachment;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public enum AttachmentType {
|
||||
/**
|
||||
* 瞄具
|
||||
*/
|
||||
@SerializedName("scope")
|
||||
SCOPE,
|
||||
/**
|
||||
* 枪口组件
|
||||
*/
|
||||
@SerializedName("muzzle")
|
||||
MUZZLE,
|
||||
/**
|
||||
* 枪托
|
||||
*/
|
||||
@SerializedName("stock")
|
||||
STOCK,
|
||||
/**
|
||||
* 握把
|
||||
*/
|
||||
@SerializedName("grip")
|
||||
GRIP,
|
||||
/**
|
||||
* 激光指示器
|
||||
*/
|
||||
@SerializedName("laser")
|
||||
LASER,
|
||||
/**
|
||||
* 扩容弹夹(匣)
|
||||
*/
|
||||
@SerializedName("extended_mag")
|
||||
EXTENDED_MAG,
|
||||
/**
|
||||
* 用来表示物品不是配件的情况。
|
||||
*/
|
||||
NONE
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.tacz.guns.api.item.builder;
|
||||
|
||||
import com.tacz.guns.api.DefaultAssets;
|
||||
import com.tacz.guns.api.item.IAmmo;
|
||||
import com.tacz.guns.init.ModItems;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
public final class AmmoItemBuilder {
|
||||
private int count = 1;
|
||||
private ResourceLocation ammoId = DefaultAssets.DEFAULT_AMMO_ID;
|
||||
|
||||
private AmmoItemBuilder() {
|
||||
}
|
||||
|
||||
public static AmmoItemBuilder create() {
|
||||
return new AmmoItemBuilder();
|
||||
}
|
||||
|
||||
public AmmoItemBuilder setCount(int count) {
|
||||
this.count = Math.max(count, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AmmoItemBuilder setId(ResourceLocation id) {
|
||||
this.ammoId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ItemStack build() {
|
||||
ItemStack ammo = new ItemStack(ModItems.AMMO.get(), this.count);
|
||||
if (ammo.getItem() instanceof IAmmo iAmmo) {
|
||||
iAmmo.setAmmoId(ammo, this.ammoId);
|
||||
}
|
||||
return ammo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.tacz.guns.api.item.builder;
|
||||
|
||||
import com.tacz.guns.api.DefaultAssets;
|
||||
import com.tacz.guns.api.item.IAttachment;
|
||||
import com.tacz.guns.init.ModItems;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
public class AttachmentItemBuilder {
|
||||
private int count = 1;
|
||||
private ResourceLocation attachmentId = DefaultAssets.DEFAULT_ATTACHMENT_ID;
|
||||
private ResourceLocation skinId = null;
|
||||
|
||||
private AttachmentItemBuilder() {
|
||||
}
|
||||
|
||||
public static AttachmentItemBuilder create() {
|
||||
return new AttachmentItemBuilder();
|
||||
}
|
||||
|
||||
public AttachmentItemBuilder setCount(int count) {
|
||||
this.count = Math.max(count, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AttachmentItemBuilder setId(ResourceLocation id) {
|
||||
this.attachmentId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AttachmentItemBuilder setSkinId(ResourceLocation skinId) {
|
||||
this.skinId = skinId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ItemStack build() {
|
||||
ItemStack attachment = new ItemStack(ModItems.ATTACHMENT.get(), this.count);
|
||||
if (attachment.getItem() instanceof IAttachment iAttachment) {
|
||||
iAttachment.setAttachmentId(attachment, this.attachmentId);
|
||||
iAttachment.setSkinId(attachment, this.skinId);
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.tacz.guns.api.item.builder;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.tacz.guns.api.TimelessAPI;
|
||||
import com.tacz.guns.api.item.IGun;
|
||||
import com.tacz.guns.api.item.gun.AbstractGunItem;
|
||||
import com.tacz.guns.api.item.gun.FireMode;
|
||||
import com.tacz.guns.api.item.gun.GunItemManager;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
public final class GunItemBuilder {
|
||||
private int count = 1;
|
||||
private int ammoCount = 0;
|
||||
private ResourceLocation gunId;
|
||||
private FireMode fireMode = FireMode.UNKNOWN;
|
||||
private boolean bulletInBarrel = false;
|
||||
|
||||
private GunItemBuilder() {
|
||||
}
|
||||
|
||||
public static GunItemBuilder create() {
|
||||
return new GunItemBuilder();
|
||||
}
|
||||
|
||||
public GunItemBuilder setCount(int count) {
|
||||
this.count = Math.max(count, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GunItemBuilder setAmmoCount(int count) {
|
||||
this.ammoCount = Math.max(count, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GunItemBuilder setId(ResourceLocation id) {
|
||||
this.gunId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GunItemBuilder setFireMode(FireMode fireMode) {
|
||||
this.fireMode = fireMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GunItemBuilder setAmmoInBarrel(boolean ammoInBarrel) {
|
||||
this.bulletInBarrel = ammoInBarrel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ItemStack build() {
|
||||
String itemType = TimelessAPI.getCommonGunIndex(gunId).map(index -> index.getPojo().getItemType()).orElse(null);
|
||||
Preconditions.checkArgument(itemType != null, "Could not found gun id: " + gunId);
|
||||
|
||||
RegistryObject<? extends AbstractGunItem> gunItemRegistryObject = GunItemManager.getGunItemRegistryObject(itemType);
|
||||
Preconditions.checkArgument(gunItemRegistryObject != null, "Could not found gun item type: " + itemType);
|
||||
|
||||
ItemStack gun = new ItemStack(gunItemRegistryObject.get(), this.count);
|
||||
if (gun.getItem() instanceof IGun iGun) {
|
||||
iGun.setGunId(gun, this.gunId);
|
||||
iGun.setFireMode(gun, this.fireMode);
|
||||
iGun.setCurrentAmmoCount(gun, this.ammoCount);
|
||||
iGun.setBulletInBarrel(gun, this.bulletInBarrel);
|
||||
}
|
||||
return gun;
|
||||
}
|
||||
}
|
||||
201
src/main/java/com/tacz/guns/api/item/gun/AbstractGunItem.java
Normal file
201
src/main/java/com/tacz/guns/api/item/gun/AbstractGunItem.java
Normal file
@@ -0,0 +1,201 @@
|
||||
package com.tacz.guns.api.item.gun;
|
||||
|
||||
import com.tacz.guns.api.TimelessAPI;
|
||||
import com.tacz.guns.api.item.IAttachment;
|
||||
import com.tacz.guns.api.item.IGun;
|
||||
import com.tacz.guns.api.item.attachment.AttachmentType;
|
||||
import com.tacz.guns.api.item.builder.GunItemBuilder;
|
||||
import com.tacz.guns.client.renderer.item.GunItemRenderer;
|
||||
import com.tacz.guns.client.resource.index.ClientGunIndex;
|
||||
import com.tacz.guns.client.tab.CustomTab;
|
||||
import com.tacz.guns.inventory.tooltip.GunTooltip;
|
||||
import com.tacz.guns.resource.index.CommonGunIndex;
|
||||
import com.tacz.guns.resource.pojo.data.gun.AttachmentPass;
|
||||
import com.tacz.guns.resource.pojo.data.gun.GunData;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.TranslatableComponent;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.inventory.tooltip.TooltipComponent;
|
||||
import net.minecraft.world.item.CreativeModeTab;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.client.IItemRenderProperties;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class AbstractGunItem extends Item implements IGun {
|
||||
protected AbstractGunItem(Properties pProperties) {
|
||||
super(pProperties);
|
||||
}
|
||||
|
||||
private static Comparator<Map.Entry<ResourceLocation, ClientGunIndex>> idNameSort() {
|
||||
return Comparator.comparingInt(m -> m.getValue().getSort());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉栓完成时调用
|
||||
*/
|
||||
public abstract void bolt(ItemStack gunItem);
|
||||
|
||||
/**
|
||||
* 射击时触发
|
||||
*/
|
||||
public abstract void shoot(ItemStack gunItem, float pitch, float yaw, boolean tracer, LivingEntity shooter);
|
||||
|
||||
/**
|
||||
* 切换开火模式时调用
|
||||
*/
|
||||
public abstract void fireSelect(ItemStack gunItem);
|
||||
|
||||
/**
|
||||
* 换弹时触发枪械子弹更新时调用
|
||||
*
|
||||
* @param gunItem 枪械物品
|
||||
* @param ammoCount 填充的子弹数量
|
||||
* @param loadBarrel 是否需要往枪管里填子弹
|
||||
*/
|
||||
public abstract void reloadAmmo(ItemStack gunItem, int ammoCount, boolean loadBarrel);
|
||||
|
||||
/**
|
||||
* 能否添加到此 CustomTab 中
|
||||
*
|
||||
* @param tab CustomTab
|
||||
* @param stack 待添加的物品
|
||||
*/
|
||||
public abstract boolean canAddInTab(CustomTab tab, ItemStack stack);
|
||||
|
||||
/**
|
||||
* 该方法具有通用的实现,放在此处
|
||||
*/
|
||||
@Override
|
||||
public boolean allowAttachment(ItemStack gun, ItemStack attachmentItem) {
|
||||
IAttachment iAttachment = IAttachment.getIAttachmentOrNull(attachmentItem);
|
||||
IGun iGun = IGun.getIGunOrNull(gun);
|
||||
if (iGun != null && iAttachment != null) {
|
||||
AttachmentType type = iAttachment.getType(attachmentItem);
|
||||
ResourceLocation attachmentId = iAttachment.getAttachmentId(attachmentItem);
|
||||
return TimelessAPI.getCommonGunIndex(iGun.getGunId(gun)).map(gunIndex -> {
|
||||
Map<AttachmentType, AttachmentPass> map = gunIndex.getGunData().getAllowAttachments();
|
||||
if (map == null) {
|
||||
return false;
|
||||
}
|
||||
AttachmentPass pass = map.get(type);
|
||||
if (pass == null) {
|
||||
return false;
|
||||
}
|
||||
return pass.isAllow(attachmentId);
|
||||
}).orElse(false);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 该方法具有通用的实现,放在此处
|
||||
*/
|
||||
@Override
|
||||
public boolean allowAttachmentType(ItemStack gun, AttachmentType type) {
|
||||
IGun iGun = IGun.getIGunOrNull(gun);
|
||||
if (iGun != null) {
|
||||
return TimelessAPI.getCommonGunIndex(iGun.getGunId(gun)).map(gunIndex -> {
|
||||
Map<AttachmentType, AttachmentPass> map = gunIndex.getGunData().getAllowAttachments();
|
||||
if (map == null) {
|
||||
return false;
|
||||
}
|
||||
return map.containsKey(type);
|
||||
}).orElse(false);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 该方法具有通用的实现,放在此处
|
||||
*/
|
||||
@Override
|
||||
@Nonnull
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public Component getName(@Nonnull ItemStack stack) {
|
||||
ResourceLocation gunId = this.getGunId(stack);
|
||||
Optional<ClientGunIndex> gunIndex = TimelessAPI.getClientGunIndex(gunId);
|
||||
if (gunIndex.isPresent()) {
|
||||
return new TranslatableComponent(gunIndex.get().getName());
|
||||
}
|
||||
return super.getName(stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该方法具有通用的实现,放在此处
|
||||
*/
|
||||
@Override
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public void fillItemCategory(@Nonnull CreativeModeTab modeTab, @Nonnull NonNullList<ItemStack> stacks) {
|
||||
if (modeTab instanceof CustomTab tab) {
|
||||
String key = tab.getKey();
|
||||
TimelessAPI.getAllClientGunIndex().stream().sorted(idNameSort()).forEach(entry -> {
|
||||
ClientGunIndex index = entry.getValue();
|
||||
if (key.equals(index.getType())) {
|
||||
GunData gunData = index.getGunData();
|
||||
ItemStack itemStack = GunItemBuilder.create()
|
||||
.setId(entry.getKey())
|
||||
.setFireMode(gunData.getFireModeSet().get(0))
|
||||
.setAmmoCount(gunData.getAmmoAmount())
|
||||
.setAmmoInBarrel(true)
|
||||
.build();
|
||||
if (canAddInTab(tab, itemStack)) {
|
||||
stacks.add(itemStack);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 阻止玩家手臂挥动动画的播放
|
||||
*/
|
||||
@Override
|
||||
public boolean onEntitySwing(ItemStack stack, LivingEntity entity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该方法具有通用的实现,放在此处
|
||||
*/
|
||||
@Override
|
||||
public void initializeClient(Consumer<IItemRenderProperties> consumer) {
|
||||
consumer.accept(new IItemRenderProperties() {
|
||||
@Override
|
||||
public BlockEntityWithoutLevelRenderer getItemStackRenderer() {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
return new GunItemRenderer(minecraft.getBlockEntityRenderDispatcher(), minecraft.getEntityModels());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 该方法具有通用的实现,放在此处
|
||||
*/
|
||||
@Override
|
||||
@Nonnull
|
||||
public Optional<TooltipComponent> getTooltipImage(ItemStack stack) {
|
||||
if (stack.getItem() instanceof IGun iGun) {
|
||||
Optional<CommonGunIndex> optional = TimelessAPI.getCommonGunIndex(this.getGunId(stack));
|
||||
if (optional.isPresent()) {
|
||||
CommonGunIndex gunIndex = optional.get();
|
||||
ResourceLocation ammoId = gunIndex.getGunData().getAmmoId();
|
||||
return Optional.of(new GunTooltip(stack, iGun, ammoId, gunIndex));
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
26
src/main/java/com/tacz/guns/api/item/gun/FireMode.java
Normal file
26
src/main/java/com/tacz/guns/api/item/gun/FireMode.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.tacz.guns.api.item.gun;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public enum FireMode {
|
||||
/**
|
||||
* 全自动
|
||||
*/
|
||||
@SerializedName("auto")
|
||||
AUTO,
|
||||
/**
|
||||
* 半自动
|
||||
*/
|
||||
@SerializedName("semi")
|
||||
SEMI,
|
||||
/**
|
||||
* 多连发
|
||||
*/
|
||||
@SerializedName("burst")
|
||||
BURST,
|
||||
/**
|
||||
* 未知的其他情况?
|
||||
*/
|
||||
@SerializedName("unknown")
|
||||
UNKNOWN
|
||||
}
|
||||
26
src/main/java/com/tacz/guns/api/item/gun/GunItemManager.java
Normal file
26
src/main/java/com/tacz/guns/api/item/gun/GunItemManager.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.tacz.guns.api.item.gun;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class GunItemManager {
|
||||
private static final Map<String, RegistryObject<? extends AbstractGunItem>> GUN_ITEM_MAP = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* 建议在 RegistryEvent.Register<Item> 事件时注册此枪械变种
|
||||
*/
|
||||
public static void registerGunItem(String name, RegistryObject<? extends AbstractGunItem> registryObject) {
|
||||
GUN_ITEM_MAP.put(name, registryObject);
|
||||
}
|
||||
|
||||
public static RegistryObject<? extends AbstractGunItem> getGunItemRegistryObject(String key) {
|
||||
return GUN_ITEM_MAP.get(key);
|
||||
}
|
||||
|
||||
public static Collection<RegistryObject<? extends AbstractGunItem>> getAllGunItems() {
|
||||
return GUN_ITEM_MAP.values();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.tacz.guns.api.item.nbt;
|
||||
|
||||
import com.tacz.guns.api.DefaultAssets;
|
||||
import com.tacz.guns.api.TimelessAPI;
|
||||
import com.tacz.guns.api.item.IAmmoBox;
|
||||
import com.tacz.guns.api.item.IGun;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
public interface AmmoBoxItemDataAccessor extends IAmmoBox {
|
||||
String AMMO_ID_TAG = "AmmoId";
|
||||
String AMMO_COUNT_TAG = "AmmoCount";
|
||||
String CREATIVE_TAG = "Creative";
|
||||
|
||||
@Override
|
||||
default ResourceLocation getAmmoId(ItemStack ammoBox) {
|
||||
CompoundTag tag = ammoBox.getOrCreateTag();
|
||||
if (tag.contains(AMMO_ID_TAG, Tag.TAG_STRING)) {
|
||||
return new ResourceLocation(tag.getString(AMMO_ID_TAG));
|
||||
}
|
||||
return DefaultAssets.EMPTY_AMMO_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setAmmoId(ItemStack ammoBox, ResourceLocation ammoId) {
|
||||
CompoundTag tag = ammoBox.getOrCreateTag();
|
||||
tag.putString(AMMO_ID_TAG, ammoId.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getAmmoCount(ItemStack ammoBox) {
|
||||
CompoundTag tag = ammoBox.getOrCreateTag();
|
||||
if (tag.contains(CREATIVE_TAG, Tag.TAG_BYTE)) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
if (tag.contains(AMMO_COUNT_TAG, Tag.TAG_INT)) {
|
||||
return tag.getInt(AMMO_COUNT_TAG);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setAmmoCount(ItemStack ammoBox, int count) {
|
||||
CompoundTag tag = ammoBox.getOrCreateTag();
|
||||
if (tag.contains(CREATIVE_TAG, Tag.TAG_BYTE)) {
|
||||
tag.putInt(AMMO_COUNT_TAG, Integer.MAX_VALUE);
|
||||
return;
|
||||
}
|
||||
tag.putInt(AMMO_COUNT_TAG, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isAmmoBoxOfGun(ItemStack gun, ItemStack ammoBox) {
|
||||
if (gun.getItem() instanceof IGun iGun && ammoBox.getItem() instanceof IAmmoBox iAmmoBox) {
|
||||
ResourceLocation ammoId = iAmmoBox.getAmmoId(ammoBox);
|
||||
if (ammoId.equals(DefaultAssets.EMPTY_AMMO_ID)) {
|
||||
return false;
|
||||
}
|
||||
ResourceLocation gunId = iGun.getGunId(gun);
|
||||
return TimelessAPI.getCommonGunIndex(gunId).map(gunIndex -> gunIndex.getGunData().getAmmoId().equals(ammoId)).orElse(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isCreative(ItemStack ammoBox) {
|
||||
CompoundTag tag = ammoBox.getTag();
|
||||
if (tag != null && tag.contains(CREATIVE_TAG, Tag.TAG_BYTE)) {
|
||||
return tag.getBoolean(CREATIVE_TAG);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.tacz.guns.api.item.nbt;
|
||||
|
||||
import com.tacz.guns.api.DefaultAssets;
|
||||
import com.tacz.guns.api.TimelessAPI;
|
||||
import com.tacz.guns.api.item.IAmmo;
|
||||
import com.tacz.guns.api.item.IGun;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
public interface AmmoItemDataAccessor extends IAmmo {
|
||||
String AMMO_ID_TAG = "AmmoId";
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
default ResourceLocation getAmmoId(ItemStack ammo) {
|
||||
CompoundTag nbt = ammo.getOrCreateTag();
|
||||
if (nbt.contains(AMMO_ID_TAG, Tag.TAG_STRING)) {
|
||||
ResourceLocation gunId = ResourceLocation.tryParse(nbt.getString(AMMO_ID_TAG));
|
||||
return Objects.requireNonNullElse(gunId, DefaultAssets.EMPTY_AMMO_ID);
|
||||
}
|
||||
return DefaultAssets.EMPTY_AMMO_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setAmmoId(ItemStack ammo, @Nullable ResourceLocation ammoId) {
|
||||
CompoundTag nbt = ammo.getOrCreateTag();
|
||||
if (ammoId != null) {
|
||||
nbt.putString(AMMO_ID_TAG, ammoId.toString());
|
||||
return;
|
||||
}
|
||||
nbt.putString(AMMO_ID_TAG, DefaultAssets.DEFAULT_AMMO_ID.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isAmmoOfGun(ItemStack gun, ItemStack ammo) {
|
||||
if (gun.getItem() instanceof IGun iGun && ammo.getItem() instanceof IAmmo iAmmo) {
|
||||
ResourceLocation gunId = iGun.getGunId(gun);
|
||||
ResourceLocation ammoId = iAmmo.getAmmoId(ammo);
|
||||
return TimelessAPI.getCommonGunIndex(gunId).map(gunIndex -> gunIndex.getGunData().getAmmoId().equals(ammoId)).orElse(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.tacz.guns.api.item.nbt;
|
||||
|
||||
import com.tacz.guns.api.DefaultAssets;
|
||||
import com.tacz.guns.api.item.IAttachment;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
public interface AttachmentItemDataAccessor extends IAttachment {
|
||||
String ATTACHMENT_ID_TAG = "AttachmentId";
|
||||
String SKIN_ID_TAG = "Skin";
|
||||
String ZOOM_NUMBER_TAG = "ZoomNumber";
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
default ResourceLocation getAttachmentId(ItemStack attachmentStack) {
|
||||
CompoundTag nbt = attachmentStack.getOrCreateTag();
|
||||
if (nbt.contains(ATTACHMENT_ID_TAG, Tag.TAG_STRING)) {
|
||||
ResourceLocation attachmentId = ResourceLocation.tryParse(nbt.getString(ATTACHMENT_ID_TAG));
|
||||
return Objects.requireNonNullElse(attachmentId, DefaultAssets.EMPTY_ATTACHMENT_ID);
|
||||
}
|
||||
return DefaultAssets.EMPTY_ATTACHMENT_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setAttachmentId(ItemStack attachmentStack, @Nullable ResourceLocation attachmentId) {
|
||||
CompoundTag nbt = attachmentStack.getOrCreateTag();
|
||||
if (attachmentId != null) {
|
||||
nbt.putString(ATTACHMENT_ID_TAG, attachmentId.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
default ResourceLocation getSkinId(ItemStack attachmentStack) {
|
||||
CompoundTag nbt = attachmentStack.getOrCreateTag();
|
||||
if (nbt.contains(SKIN_ID_TAG, Tag.TAG_STRING)) {
|
||||
return ResourceLocation.tryParse(nbt.getString(SKIN_ID_TAG));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setSkinId(ItemStack attachmentStack, @Nullable ResourceLocation skinId) {
|
||||
CompoundTag nbt = attachmentStack.getOrCreateTag();
|
||||
if (skinId != null) {
|
||||
nbt.putString(SKIN_ID_TAG, skinId.toString());
|
||||
} else {
|
||||
nbt.remove(SKIN_ID_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getZoomNumber(ItemStack attachmentStack) {
|
||||
CompoundTag nbt = attachmentStack.getOrCreateTag();
|
||||
if (nbt.contains(ZOOM_NUMBER_TAG, Tag.TAG_INT)) {
|
||||
return nbt.getInt(ZOOM_NUMBER_TAG);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setZoomNumber(ItemStack attachmentStack, int zoomNumber) {
|
||||
CompoundTag nbt = attachmentStack.getOrCreateTag();
|
||||
nbt.putInt(ZOOM_NUMBER_TAG, zoomNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.tacz.guns.api.item.nbt;
|
||||
|
||||
import com.tacz.guns.api.DefaultAssets;
|
||||
import com.tacz.guns.api.TimelessAPI;
|
||||
import com.tacz.guns.api.item.IAttachment;
|
||||
import com.tacz.guns.api.item.IGun;
|
||||
import com.tacz.guns.api.item.attachment.AttachmentType;
|
||||
import com.tacz.guns.api.item.gun.FireMode;
|
||||
import com.tacz.guns.client.resource.index.ClientAttachmentIndex;
|
||||
import com.tacz.guns.client.resource.index.ClientGunIndex;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
public interface GunItemDataAccessor extends IGun {
|
||||
String GUN_ID_TAG = "GunId";
|
||||
String GUN_FIRE_MODE_TAG = "GunFireMode";
|
||||
String GUN_HAS_BULLET_IN_BARREL = "HasBulletInBarrel";
|
||||
String GUN_CURRENT_AMMO_COUNT_TAG = "GunCurrentAmmoCount";
|
||||
String GUN_ATTACHMENT_BASE = "Attachment";
|
||||
String GUN_EXP_TAG = "GunLevelExp";
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
default ResourceLocation getGunId(ItemStack gun) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
if (nbt.contains(GUN_ID_TAG, Tag.TAG_STRING)) {
|
||||
ResourceLocation gunId = ResourceLocation.tryParse(nbt.getString(GUN_ID_TAG));
|
||||
return Objects.requireNonNullElse(gunId, DefaultAssets.EMPTY_GUN_ID);
|
||||
}
|
||||
return DefaultAssets.EMPTY_GUN_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setGunId(ItemStack gun, @Nullable ResourceLocation gunId) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
if (gunId != null) {
|
||||
nbt.putString(GUN_ID_TAG, gunId.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getLevel(ItemStack gun) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
if (nbt.contains(GUN_EXP_TAG, Tag.TAG_INT)) {
|
||||
return getLevel(nbt.getInt(GUN_EXP_TAG));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getExp(ItemStack gun) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
if (nbt.contains(GUN_EXP_TAG, Tag.TAG_INT)) {
|
||||
return nbt.getInt(GUN_EXP_TAG);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getExpToNextLevel(ItemStack gun) {
|
||||
int exp = getExp(gun);
|
||||
int level = getLevel(exp);
|
||||
if (level >= getMaxLevel()) {
|
||||
return 0;
|
||||
}
|
||||
int nextLevelExp = getExp(level + 1);
|
||||
return nextLevelExp - exp;
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getExpCurrentLevel(ItemStack gun) {
|
||||
int exp = getExp(gun);
|
||||
int level = getLevel(exp);
|
||||
if (level <= 0) {
|
||||
return exp;
|
||||
} else {
|
||||
return exp - getExp(level - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default FireMode getFireMode(ItemStack gun) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
if (nbt.contains(GUN_FIRE_MODE_TAG, Tag.TAG_STRING)) {
|
||||
return FireMode.valueOf(nbt.getString(GUN_FIRE_MODE_TAG));
|
||||
}
|
||||
return FireMode.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setFireMode(ItemStack gun, @Nullable FireMode fireMode) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
if (fireMode != null) {
|
||||
nbt.putString(GUN_FIRE_MODE_TAG, fireMode.name());
|
||||
return;
|
||||
}
|
||||
nbt.putString(GUN_FIRE_MODE_TAG, FireMode.UNKNOWN.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getCurrentAmmoCount(ItemStack gun) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
if (nbt.contains(GUN_CURRENT_AMMO_COUNT_TAG, Tag.TAG_INT)) {
|
||||
return nbt.getInt(GUN_CURRENT_AMMO_COUNT_TAG);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setCurrentAmmoCount(ItemStack gun, int ammoCount) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
nbt.putInt(GUN_CURRENT_AMMO_COUNT_TAG, Math.max(ammoCount, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
default void reduceCurrentAmmoCount(ItemStack gun) {
|
||||
setCurrentAmmoCount(gun, getCurrentAmmoCount(gun) - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
default ItemStack getAttachment(ItemStack gun, AttachmentType type) {
|
||||
if (!allowAttachmentType(gun, type)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
String key = GUN_ATTACHMENT_BASE + type.name();
|
||||
if (nbt.contains(key, Tag.TAG_COMPOUND)) {
|
||||
return ItemStack.of(nbt.getCompound(key));
|
||||
}
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void installAttachment(@Nonnull ItemStack gun, @Nonnull ItemStack attachment) {
|
||||
if (!allowAttachment(gun, attachment)) {
|
||||
return;
|
||||
}
|
||||
IAttachment iAttachment = IAttachment.getIAttachmentOrNull(attachment);
|
||||
if (iAttachment == null) {
|
||||
return;
|
||||
}
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
String key = GUN_ATTACHMENT_BASE + iAttachment.getType(attachment).name();
|
||||
CompoundTag attachmentTag = new CompoundTag();
|
||||
attachment.save(attachmentTag);
|
||||
nbt.put(key, attachmentTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void unloadAttachment(@Nonnull ItemStack gun, AttachmentType type) {
|
||||
if (!allowAttachmentType(gun, type)) {
|
||||
return;
|
||||
}
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
String key = GUN_ATTACHMENT_BASE + type.name();
|
||||
CompoundTag attachmentTag = new CompoundTag();
|
||||
ItemStack.EMPTY.save(attachmentTag);
|
||||
nbt.put(key, attachmentTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
default float getAimingZoom(ItemStack gunItem) {
|
||||
float zoom = 1;
|
||||
ItemStack scopeItem = this.getAttachment(gunItem, AttachmentType.SCOPE);
|
||||
IAttachment iAttachment = IAttachment.getIAttachmentOrNull(scopeItem);
|
||||
if (iAttachment != null) {
|
||||
ResourceLocation scopeId = iAttachment.getAttachmentId(scopeItem);
|
||||
int zoomNumber = iAttachment.getZoomNumber(scopeItem);
|
||||
float[] zooms = TimelessAPI.getClientAttachmentIndex(scopeId).map(ClientAttachmentIndex::getZoom).orElse(null);
|
||||
if (zooms != null) {
|
||||
zoom = zooms[zoomNumber % zooms.length];
|
||||
}
|
||||
} else {
|
||||
zoom = TimelessAPI.getClientGunIndex(this.getGunId(gunItem)).map(ClientGunIndex::getIronZoom).orElse(1f);
|
||||
}
|
||||
return zoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean hasBulletInBarrel(ItemStack gun) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
if (nbt.contains(GUN_HAS_BULLET_IN_BARREL, Tag.TAG_BYTE)) {
|
||||
return nbt.getBoolean(GUN_HAS_BULLET_IN_BARREL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setBulletInBarrel(ItemStack gun, boolean bulletInBarrel) {
|
||||
CompoundTag nbt = gun.getOrCreateTag();
|
||||
nbt.putBoolean(GUN_HAS_BULLET_IN_BARREL, bulletInBarrel);
|
||||
}
|
||||
}
|
||||
130
src/main/java/com/tacz/guns/block/GunSmithTableBlock.java
Normal file
130
src/main/java/com/tacz/guns/block/GunSmithTableBlock.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package com.tacz.guns.block;
|
||||
|
||||
import com.tacz.guns.block.entity.GunSmithTableBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BedPart;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||
import net.minecraft.world.level.block.state.properties.EnumProperty;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
import net.minecraft.world.level.material.PushReaction;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class GunSmithTableBlock extends BaseEntityBlock {
|
||||
public static final VoxelShape BLOCK_AABB = Block.box(0, 0, 0, 16, 15, 16);
|
||||
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
|
||||
public static final EnumProperty<BedPart> PART = BlockStateProperties.BED_PART;
|
||||
|
||||
public GunSmithTableBlock() {
|
||||
super(Properties.of(Material.WOOD).sound(SoundType.WOOD).strength(2.0F, 3.0F).noOcclusion());
|
||||
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(PART, BedPart.FOOT));
|
||||
}
|
||||
|
||||
private static Direction getNeighbourDirection(BedPart bedPart, Direction direction) {
|
||||
return bedPart == BedPart.FOOT ? direction : direction.getOpposite();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult use(BlockState pState, Level level, BlockPos pos, Player player, InteractionHand pHand, BlockHitResult pHit) {
|
||||
if (level.isClientSide) {
|
||||
return InteractionResult.SUCCESS;
|
||||
} else {
|
||||
BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||
if (blockEntity instanceof GunSmithTableBlockEntity gunSmithTable) {
|
||||
player.openMenu(gunSmithTable);
|
||||
}
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(FACING, PART);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
return new GunSmithTableBlockEntity(pos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||
Direction direction = context.getHorizontalDirection();
|
||||
BlockPos clickedPos = context.getClickedPos();
|
||||
BlockPos relative = clickedPos.relative(direction);
|
||||
Level level = context.getLevel();
|
||||
if (level.getBlockState(relative).canBeReplaced(context) && level.getWorldBorder().isWithinBounds(relative)) {
|
||||
return this.defaultBlockState().setValue(FACING, direction);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerWillDestroy(Level level, BlockPos pos, BlockState blockState, Player player) {
|
||||
if (!level.isClientSide && player.isCreative()) {
|
||||
BedPart bedPart = blockState.getValue(PART);
|
||||
if (bedPart == BedPart.FOOT) {
|
||||
BlockPos blockpos = pos.relative(getNeighbourDirection(bedPart, blockState.getValue(FACING)));
|
||||
BlockState blockstate = level.getBlockState(blockpos);
|
||||
if (blockstate.is(this) && blockstate.getValue(PART) == BedPart.HEAD) {
|
||||
level.setBlock(blockpos, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL | Block.UPDATE_SUPPRESS_DROPS);
|
||||
level.levelEvent(player, LevelEvent.PARTICLES_DESTROY_BLOCK, blockpos, Block.getId(blockstate));
|
||||
}
|
||||
}
|
||||
}
|
||||
super.playerWillDestroy(level, pos, blockState, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlacedBy(Level worldIn, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
|
||||
super.setPlacedBy(worldIn, pos, state, placer, stack);
|
||||
if (!worldIn.isClientSide) {
|
||||
BlockPos relative = pos.relative(state.getValue(FACING));
|
||||
worldIn.setBlock(relative, state.setValue(PART, BedPart.HEAD), Block.UPDATE_ALL);
|
||||
worldIn.blockUpdated(pos, Blocks.AIR);
|
||||
state.updateNeighbourShapes(worldIn, pos, Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState updateShape(BlockState state, Direction direction, BlockState facingState, LevelAccessor level, BlockPos currentPos, BlockPos facingPos) {
|
||||
if (direction == getNeighbourDirection(state.getValue(PART), state.getValue(FACING))) {
|
||||
return facingState.is(this) && facingState.getValue(PART) != state.getValue(PART) ? state : Blocks.AIR.defaultBlockState();
|
||||
} else {
|
||||
return super.updateShape(state, direction, facingState, level, currentPos, facingPos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderShape getRenderShape(BlockState pState) {
|
||||
return RenderShape.ENTITYBLOCK_ANIMATED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PushReaction getPistonPushReaction(BlockState state) {
|
||||
return PushReaction.DESTROY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) {
|
||||
return BLOCK_AABB;
|
||||
}
|
||||
}
|
||||
235
src/main/java/com/tacz/guns/block/TargetBlock.java
Normal file
235
src/main/java/com/tacz/guns/block/TargetBlock.java
Normal file
@@ -0,0 +1,235 @@
|
||||
package com.tacz.guns.block;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.tacz.guns.block.entity.TargetBlockEntity;
|
||||
import com.tacz.guns.init.ModBlocks;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.entity.projectile.Projectile;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.*;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
import net.minecraft.world.level.material.PushReaction;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class TargetBlock extends BaseEntityBlock {
|
||||
public static final IntegerProperty OUTPUT_POWER = BlockStateProperties.POWER;
|
||||
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
|
||||
public static final EnumProperty<DoubleBlockHalf> HALF = BlockStateProperties.DOUBLE_BLOCK_HALF;
|
||||
public static final BooleanProperty STAND = BooleanProperty.create("stand");
|
||||
public static final VoxelShape BOX_BOTTOM_STAND_X = Shapes.or(Block.box(6, 0, 6, 10, 16, 10), Block.box(6, 13, 2, 10, 16, 14));
|
||||
public static final VoxelShape BOX_BOTTOM_STAND_Z = Shapes.or(Block.box(6, 0, 6, 10, 16, 10), Block.box(2, 13, 6, 14, 16, 10));
|
||||
public static final VoxelShape BOX_BOTTOM_DOWN = Block.box(6, 0, 6, 10, 4, 10);
|
||||
public static final VoxelShape BOX_UPPER_X = Block.box(6, 0, 2, 10, 16, 14);
|
||||
public static final VoxelShape BOX_UPPER_Z = Block.box(2, 0, 6, 14, 16, 10);
|
||||
|
||||
public TargetBlock() {
|
||||
super(Properties.of(Material.WOOD).sound(SoundType.WOOD).strength(2.0F, 3.0F).noOcclusion());
|
||||
this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(HALF, DoubleBlockHalf.LOWER).setValue(STAND, true).setValue(OUTPUT_POWER, 0));
|
||||
}
|
||||
|
||||
public static int getRedstoneStrength(BlockHitResult hit, boolean isUpperBlock) {
|
||||
// 击中下方,恒为 1
|
||||
if (!isUpperBlock) {
|
||||
return 1;
|
||||
}
|
||||
Vec3 hitLocation = hit.getLocation();
|
||||
Direction direction = hit.getDirection();
|
||||
// 标靶中心为 (0.5, 0.32, 0.5)
|
||||
double x = Math.abs(Mth.frac(hitLocation.x) - 0.5);
|
||||
double y = Math.abs(Mth.frac(hitLocation.y) - 0.32);
|
||||
double z = Math.abs(Mth.frac(hitLocation.z) - 0.5);
|
||||
Direction.Axis axis = direction.getAxis();
|
||||
double distance;
|
||||
if (axis == Direction.Axis.Y) {
|
||||
distance = Math.max(x, z);
|
||||
} else if (axis == Direction.Axis.Z) {
|
||||
distance = Math.max(x, y);
|
||||
} else {
|
||||
distance = Math.max(y, z);
|
||||
}
|
||||
// 离开中心 0.25 单位就是最低分?
|
||||
double percent = Mth.clamp((0.25 - distance) / 0.25, 0, 1);
|
||||
return Math.max(1, Mth.ceil(15 * percent));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
|
||||
return state.getValue(HALF).equals(DoubleBlockHalf.LOWER) && level.isClientSide() ? createTickerHelper(blockEntityType, ModBlocks.TARGET_BE.get(), TargetBlockEntity::clientTick) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(FACING, HALF, STAND, OUTPUT_POWER);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
if (blockState.getValue(HALF).equals(DoubleBlockHalf.LOWER)) {
|
||||
return new TargetBlockEntity(pos, blockState);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) {
|
||||
boolean stand = state.getValue(STAND);
|
||||
boolean axis = state.getValue(FACING).getAxis().equals(Direction.Axis.X);
|
||||
if (state.getValue(HALF).equals(DoubleBlockHalf.UPPER)) {
|
||||
return stand ? (axis ? BOX_UPPER_X : BOX_UPPER_Z) : Shapes.empty();
|
||||
}
|
||||
return stand ? (axis ? BOX_BOTTOM_STAND_X : BOX_BOTTOM_STAND_Z) : BOX_BOTTOM_DOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(BlockState state, ServerLevel level, BlockPos pos, Random random) {
|
||||
// 计划刻的内容
|
||||
if (!state.getValue(STAND)) {
|
||||
level.setBlock(pos, state.setValue(STAND, true).setValue(OUTPUT_POWER, 0), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) {
|
||||
if (hit.getDirection().getOpposite().equals(state.getValue(FACING))) {
|
||||
if (state.getValue(HALF).equals(DoubleBlockHalf.LOWER)) {
|
||||
world.getBlockEntity(hit.getBlockPos(), TargetBlockEntity.TYPE).ifPresent(e -> e.hit(world, state, hit, false));
|
||||
} else if (state.getValue(HALF).equals(DoubleBlockHalf.UPPER)) {
|
||||
world.getBlockEntity(hit.getBlockPos().below(), TargetBlockEntity.TYPE).ifPresent(e -> e.hit(world, state, hit, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor level, BlockPos currentPos, BlockPos facingPos) {
|
||||
DoubleBlockHalf half = state.getValue(HALF);
|
||||
boolean stand = state.getValue(STAND);
|
||||
|
||||
if (facing.getAxis() == Direction.Axis.Y) {
|
||||
if (half.equals(DoubleBlockHalf.LOWER) && facing == Direction.UP || half.equals(DoubleBlockHalf.UPPER) && facing == Direction.DOWN) {
|
||||
// 拆一半另外一半跟着没
|
||||
if (!facingState.is(this)) {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
// 同步击倒状态
|
||||
if (facingState.getValue(STAND) != stand) {
|
||||
return state.setValue(STAND, facingState.getValue(STAND)).setValue(OUTPUT_POWER, facingState.getValue(OUTPUT_POWER));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底下方块没了也拆掉
|
||||
if (half == DoubleBlockHalf.LOWER && facing == Direction.DOWN && !state.canSurvive(level, currentPos)) {
|
||||
return Blocks.AIR.defaultBlockState();
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||
Direction direction = context.getHorizontalDirection();
|
||||
BlockPos clickedPos = context.getClickedPos();
|
||||
BlockPos above = clickedPos.above();
|
||||
Level level = context.getLevel();
|
||||
if (level.getBlockState(above).canBeReplaced(context) && level.getWorldBorder().isWithinBounds(above)) {
|
||||
return this.defaultBlockState().setValue(FACING, direction);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
|
||||
super.setPlacedBy(world, pos, state, placer, stack);
|
||||
if (!world.isClientSide) {
|
||||
BlockPos above = pos.above();
|
||||
world.setBlock(above, state.setValue(HALF, DoubleBlockHalf.UPPER), Block.UPDATE_ALL);
|
||||
world.blockUpdated(pos, Blocks.AIR);
|
||||
state.updateNeighbourShapes(world, pos, Block.UPDATE_ALL);
|
||||
if (stack.hasCustomHoverName()) {
|
||||
BlockEntity blockentity = world.getBlockEntity(pos);
|
||||
if (blockentity instanceof TargetBlockEntity e) {
|
||||
GameProfile gameprofile = new GameProfile(null, stack.getHoverName().getString());
|
||||
e.setOwner(gameprofile);
|
||||
e.setCustomName(stack.getHoverName());
|
||||
e.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getCloneItemStack(BlockState state, HitResult target, BlockGetter level, BlockPos pos, Player player) {
|
||||
BlockPos blockPos = state.getValue(HALF) == DoubleBlockHalf.LOWER ? pos : pos.below();
|
||||
BlockEntity blockentity = level.getBlockEntity(blockPos);
|
||||
if (blockentity instanceof TargetBlockEntity e) {
|
||||
return new ItemStack(this).setHoverName(e.getCustomName());
|
||||
}
|
||||
return super.getCloneItemStack(state, target, level, pos, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
|
||||
BlockPos blockpos = pos.below();
|
||||
BlockState blockstate = level.getBlockState(blockpos);
|
||||
if (state.getValue(HALF) == DoubleBlockHalf.LOWER) {
|
||||
return true;
|
||||
}
|
||||
return blockstate.is(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSignalSource(BlockState state) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side) {
|
||||
return blockState.getValue(OUTPUT_POWER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
|
||||
if (!level.isClientSide() && !state.is(oldState.getBlock())) {
|
||||
if (state.getValue(OUTPUT_POWER) > 0 && !level.getBlockTicks().hasScheduledTick(pos, this)) {
|
||||
level.setBlock(pos, state.setValue(OUTPUT_POWER, 0), Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderShape getRenderShape(BlockState state) {
|
||||
return RenderShape.ENTITYBLOCK_ANIMATED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PushReaction getPistonPushReaction(BlockState state) {
|
||||
return PushReaction.DESTROY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.tacz.guns.block.entity;
|
||||
|
||||
import com.tacz.guns.init.ModBlocks;
|
||||
import com.tacz.guns.inventory.GunSmithTableMenu;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.TextComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class GunSmithTableBlockEntity extends BlockEntity implements MenuProvider {
|
||||
public static final BlockEntityType<GunSmithTableBlockEntity> TYPE = BlockEntityType.Builder.of(GunSmithTableBlockEntity::new, ModBlocks.GUN_SMITH_TABLE.get()).build(null);
|
||||
|
||||
public GunSmithTableBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(TYPE, pos, blockState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public AABB getRenderBoundingBox() {
|
||||
return new AABB(worldPosition.offset(-2, 0, -2), worldPosition.offset(2, 1, 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return new TextComponent("Gun Smith Table");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
|
||||
return new GunSmithTableMenu(id, inventory);
|
||||
}
|
||||
}
|
||||
145
src/main/java/com/tacz/guns/block/entity/TargetBlockEntity.java
Normal file
145
src/main/java/com/tacz/guns/block/entity/TargetBlockEntity.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package com.tacz.guns.block.entity;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.tacz.guns.block.TargetBlock;
|
||||
import com.tacz.guns.config.common.OtherConfig;
|
||||
import com.tacz.guns.init.ModBlocks;
|
||||
import com.tacz.guns.init.ModSounds;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.TextComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.Nameable;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.tacz.guns.block.TargetBlock.OUTPUT_POWER;
|
||||
import static com.tacz.guns.block.TargetBlock.STAND;
|
||||
|
||||
public class TargetBlockEntity extends BlockEntity implements Nameable {
|
||||
public static final BlockEntityType<TargetBlockEntity> TYPE = BlockEntityType.Builder.of(TargetBlockEntity::new, ModBlocks.TARGET.get()).build(null);
|
||||
/**
|
||||
* 标靶复位时间,暂定为 5 秒
|
||||
*/
|
||||
private static final int RESET_TIME = 5 * 20;
|
||||
private static final String OWNER_TAG = "Owner";
|
||||
private static final String CUSTOM_NAME_TAG = "CustomName";
|
||||
public float rot = 0;
|
||||
public float oRot = 0;
|
||||
private @Nullable GameProfile owner;
|
||||
private @Nullable Component name;
|
||||
|
||||
public TargetBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(TYPE, pos, blockState);
|
||||
}
|
||||
|
||||
public static void clientTick(Level level, BlockPos pos, BlockState state, TargetBlockEntity pBlockEntity) {
|
||||
pBlockEntity.oRot = pBlockEntity.rot;
|
||||
if (state.getValue(STAND)) {
|
||||
pBlockEntity.rot = Math.max(pBlockEntity.rot - 18, 0);
|
||||
} else {
|
||||
pBlockEntity.rot = Math.min(pBlockEntity.rot + 45, 90);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GameProfile getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(@Nullable GameProfile owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
if (tag.contains(OWNER_TAG, Tag.TAG_COMPOUND)) {
|
||||
this.owner = NbtUtils.readGameProfile(tag.getCompound(OWNER_TAG));
|
||||
}
|
||||
if (tag.contains(CUSTOM_NAME_TAG, Tag.TAG_STRING)) {
|
||||
this.name = Component.Serializer.fromJson(tag.getString(CUSTOM_NAME_TAG));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
if (owner != null) {
|
||||
tag.put(OWNER_TAG, NbtUtils.writeGameProfile(new CompoundTag(), owner));
|
||||
}
|
||||
if (this.name != null) {
|
||||
tag.putString(CUSTOM_NAME_TAG, Component.Serializer.toJson(this.name));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getName() {
|
||||
return this.name != null ? this.name : TextComponent.EMPTY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Component getCustomName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setCustomName(Component name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag() {
|
||||
return saveWithoutMetadata();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
this.setChanged();
|
||||
if (level != null) {
|
||||
BlockState state = level.getBlockState(worldPosition);
|
||||
level.sendBlockUpdated(worldPosition, state, state, Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AABB getRenderBoundingBox() {
|
||||
return new AABB(worldPosition.offset(-2, 0, -2), worldPosition.offset(2, 2, 2));
|
||||
}
|
||||
|
||||
public void hit(Level level, BlockState state, BlockHitResult hit, boolean isUpperBlock) {
|
||||
if (this.level != null && state.getValue(STAND)) {
|
||||
BlockPos blockPos = hit.getBlockPos();
|
||||
// 如果是击中上方,把状态移动到下方处理
|
||||
if (isUpperBlock) {
|
||||
blockPos = blockPos.below();
|
||||
state = level.getBlockState(blockPos);
|
||||
}
|
||||
int redstoneStrength = TargetBlock.getRedstoneStrength(hit, isUpperBlock);
|
||||
level.setBlock(blockPos, state.setValue(STAND, false).setValue(OUTPUT_POWER, redstoneStrength), Block.UPDATE_ALL);
|
||||
level.scheduleTick(blockPos, state.getBlock(), RESET_TIME);
|
||||
// 原版的声音传播距离由 volume 决定
|
||||
// 当声音大于 1 时,距离为 = 16 * volume
|
||||
float volume = OtherConfig.TARGET_SOUND_DISTANCE.get() / 16.0f;
|
||||
volume = Math.max(volume, 0);
|
||||
level.playSound(null, blockPos, ModSounds.TARGET_HIT.get(), SoundSource.BLOCKS, volume, this.level.random.nextFloat() * 0.1F + 0.9F);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
@FieldsAreNonnullByDefault
|
||||
package com.tacz.guns.block.entity;
|
||||
|
||||
import net.minecraft.FieldsAreNonnullByDefault;
|
||||
import net.minecraft.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
9
src/main/java/com/tacz/guns/block/package-info.java
Normal file
9
src/main/java/com/tacz/guns/block/package-info.java
Normal file
@@ -0,0 +1,9 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
@FieldsAreNonnullByDefault
|
||||
package com.tacz.guns.block;
|
||||
|
||||
import net.minecraft.FieldsAreNonnullByDefault;
|
||||
import net.minecraft.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.tacz.guns.client.animation;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class AnimationChannelContent {
|
||||
public float[] keyframeTimeS;
|
||||
/**
|
||||
* 动画数值。该数组的第一维每个元素都与上面的 keyframeTime 顺序对应,
|
||||
* 第二维可以是位移、旋转或缩放的值,其中旋转是四元数,数组长度可以为 8 或 4。(长度为 8 时,前四位存 Pre 数值,后四位存 Post 数值)
|
||||
* 位移和缩放是三轴数值,数组长度可以为 6 或 3。
|
||||
*/
|
||||
public float[][] values;
|
||||
/**
|
||||
* 对于使用一般插值器的 Channel,这个动画值没有意义。它专门用于 CustomInterpolator
|
||||
*/
|
||||
public LerpMode[] lerpModes;
|
||||
|
||||
public AnimationChannelContent() {
|
||||
}
|
||||
|
||||
public AnimationChannelContent(AnimationChannelContent source) {
|
||||
if (source.keyframeTimeS != null) {
|
||||
this.keyframeTimeS = Arrays.copyOf(source.keyframeTimeS, source.keyframeTimeS.length);
|
||||
}
|
||||
if (source.values != null) {
|
||||
// 深拷贝动画数值
|
||||
this.values = Arrays.stream(source.values)
|
||||
.map(values -> Arrays.copyOf(values, values.length))
|
||||
.toArray(float[][]::new);
|
||||
}
|
||||
if (source.lerpModes != null) {
|
||||
this.lerpModes = Arrays.copyOf(source.lerpModes, source.lerpModes.length);
|
||||
}
|
||||
}
|
||||
|
||||
public enum LerpMode {
|
||||
LINEAR, SPHERICAL_LINEAR, CATMULLROM, SPHERICAL_CATMULLROM
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.tacz.guns.client.animation;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class AnimationController {
|
||||
protected final ArrayList<ObjectAnimationRunner> currentRunners = new ArrayList<>();
|
||||
protected final ArrayList<Boolean> blending = new ArrayList<>();
|
||||
private final AnimationListenerSupplier listenerSupplier;
|
||||
private final ArrayList<Queue<AnimationPlan>> animationQueue = new ArrayList<>();
|
||||
protected Map<String, ObjectAnimation> prototypes = Maps.newHashMap();
|
||||
|
||||
public AnimationController(List<ObjectAnimation> animationPrototypes, AnimationListenerSupplier model) {
|
||||
for (ObjectAnimation prototype : animationPrototypes) {
|
||||
if (prototype == null) {
|
||||
continue;
|
||||
}
|
||||
prototypes.put(prototype.name, prototype);
|
||||
}
|
||||
this.listenerSupplier = model;
|
||||
}
|
||||
|
||||
public void providePrototypeIfAbsent(String name, Supplier<ObjectAnimation> supplier) {
|
||||
if (!prototypes.containsKey(name)) {
|
||||
prototypes.put(name, supplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containPrototype(String name) {
|
||||
return prototypes.containsKey(name);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ObjectAnimationRunner getAnimation(int track) {
|
||||
if (track >= currentRunners.size()) {
|
||||
return null;
|
||||
}
|
||||
return currentRunners.get(track);
|
||||
}
|
||||
|
||||
public void removeAnimation(int track) {
|
||||
if (track < currentRunners.size()) {
|
||||
currentRunners.set(track, null);
|
||||
}
|
||||
if (track < animationQueue.size()) {
|
||||
animationQueue.set(track, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void queueAnimation(int track, Queue<AnimationPlan> queue) {
|
||||
// 确保数组长度正确
|
||||
for (int i = animationQueue.size(); i <= track; i++) {
|
||||
animationQueue.add(null);
|
||||
}
|
||||
animationQueue.set(track, queue);
|
||||
if (queue != null) {
|
||||
AnimationPlan plan = null;
|
||||
while (plan == null && !queue.isEmpty()) {
|
||||
plan = queue.poll();
|
||||
}
|
||||
if (plan != null) {
|
||||
run(track, plan.animationName, plan.playType, plan.transitionTimeS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void runAnimation(int track, String animationName, ObjectAnimation.PlayType playType, float transitionTimeS) {
|
||||
// 运行单个动画的时候视为执行一个只有一个动画的动画队列,因此需要清理旧的队列。
|
||||
if (track < animationQueue.size()) {
|
||||
animationQueue.set(track, null);
|
||||
}
|
||||
run(track, animationName, playType, transitionTimeS);
|
||||
}
|
||||
|
||||
synchronized private void run(int track, String animationName, ObjectAnimation.PlayType playType, float transitionTimeS) {
|
||||
ObjectAnimation prototype = prototypes.get(animationName);
|
||||
if (prototype == null) {
|
||||
return;
|
||||
}
|
||||
// 确保数组长度正确
|
||||
for (int i = currentRunners.size(); i <= track; i++) {
|
||||
currentRunners.add(null);
|
||||
}
|
||||
|
||||
ObjectAnimation animation = new ObjectAnimation(prototype);
|
||||
animation.applyAnimationListeners(listenerSupplier);
|
||||
animation.playType = playType;
|
||||
ObjectAnimationRunner runner = new ObjectAnimationRunner(animation);
|
||||
runner.setProgressNs(0);
|
||||
runner.run();
|
||||
|
||||
ObjectAnimationRunner oldRunner = currentRunners.get(track);
|
||||
if (transitionTimeS > 0) {
|
||||
if (oldRunner != null) {
|
||||
oldRunner.transition(runner, (long) (transitionTimeS * 1e9));
|
||||
} else {
|
||||
currentRunners.set(track, runner);
|
||||
}
|
||||
} else {
|
||||
currentRunners.set(track, runner);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlending(int track, boolean blend) {
|
||||
// 确保数组长度正确
|
||||
for (int i = blending.size(); i <= track; i++) {
|
||||
blending.add(false);
|
||||
}
|
||||
blending.set(track, blend);
|
||||
}
|
||||
|
||||
synchronized public void update() {
|
||||
// 动画混合时,track 级别越低,优先级越低(体现在旋转的叠加先后顺序上)
|
||||
for (int i = currentRunners.size() - 1; i >= 0; i--) {
|
||||
boolean blend = i < blending.size() ? blending.get(i) : false;
|
||||
ObjectAnimationRunner runner = currentRunners.get(i);
|
||||
if (runner == null) {
|
||||
continue;
|
||||
}
|
||||
//更新当前动画runner
|
||||
if (runner.isRunning() || runner.isHolding() || runner.isTransitioning()) {
|
||||
runner.update(blend);
|
||||
}
|
||||
//更新过渡目标动画runner,并且如果过渡已经完成,将其塞进currentRunners
|
||||
if (runner.getTransitionTo() != null) {
|
||||
runner.getTransitionTo().update(blend);
|
||||
if (!runner.isTransitioning()) {
|
||||
currentRunners.set(i, runner.getTransitionTo());
|
||||
runner = runner.getTransitionTo();
|
||||
}
|
||||
}
|
||||
// 如果动画结束,检查队列是否有下一个动画,有则播放
|
||||
if ((runner.isHolding() || runner.isStopped()) && !runner.isTransitioning()) {
|
||||
if (i < animationQueue.size()) {
|
||||
Queue<AnimationPlan> queue = animationQueue.get(i);
|
||||
if (queue != null) {
|
||||
AnimationPlan plan = null;
|
||||
while (plan == null && !queue.isEmpty()) {
|
||||
plan = queue.poll();
|
||||
}
|
||||
if (plan != null) {
|
||||
run(i, plan.animationName, plan.playType, plan.transitionTimeS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.tacz.guns.client.animation;
|
||||
|
||||
public interface AnimationListener {
|
||||
/**
|
||||
* @param values When ChannelType is TRANSLATION, the length of values will be 3 and will store xyz offsets(NOT GLOBAL TRANSLATION!!! IT IS LOCAL OFFSET).
|
||||
* When ChannelType is ROTATION, the length of values will be 4 and will store quaternions.(ALSO LOCAL ROTATION)
|
||||
* When ChannelType is SCALE, the length of values will be 3 and will store xyz scale.(ALSO LOCAL SCALE)
|
||||
* @param blend When blending, animation value should be accumulated, instead of being covered.
|
||||
*/
|
||||
void update(float[] values, boolean blend);
|
||||
|
||||
float[] recover();
|
||||
|
||||
ObjectAnimationChannel.ChannelType getType();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.tacz.guns.client.animation;
|
||||
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface AnimationListenerSupplier {
|
||||
@Nullable
|
||||
AnimationListener supplyListeners(String nodeName, ObjectAnimationChannel.ChannelType type);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.tacz.guns.client.animation;
|
||||
|
||||
public class AnimationPlan {
|
||||
public String animationName;
|
||||
public ObjectAnimation.PlayType playType;
|
||||
public float transitionTimeS;
|
||||
|
||||
public AnimationPlan(String animationName, ObjectAnimation.PlayType playType, float transitionTimeS) {
|
||||
this.animationName = animationName;
|
||||
this.playType = playType;
|
||||
this.transitionTimeS = transitionTimeS;
|
||||
}
|
||||
}
|
||||
354
src/main/java/com/tacz/guns/client/animation/Animations.java
Normal file
354
src/main/java/com/tacz/guns/client/animation/Animations.java
Normal file
@@ -0,0 +1,354 @@
|
||||
package com.tacz.guns.client.animation;
|
||||
|
||||
import com.mojang.math.Vector3f;
|
||||
import com.tacz.guns.GunMod;
|
||||
import com.tacz.guns.client.animation.gltf.AccessorModel;
|
||||
import com.tacz.guns.client.animation.gltf.AnimationModel;
|
||||
import com.tacz.guns.client.animation.gltf.AnimationStructure;
|
||||
import com.tacz.guns.client.animation.gltf.NodeModel;
|
||||
import com.tacz.guns.client.animation.gltf.accessor.AccessorData;
|
||||
import com.tacz.guns.client.animation.gltf.accessor.AccessorFloatData;
|
||||
import com.tacz.guns.client.animation.interpolator.CustomInterpolator;
|
||||
import com.tacz.guns.client.animation.interpolator.InterpolatorUtil;
|
||||
import com.tacz.guns.client.model.BedrockAnimatedModel;
|
||||
import com.tacz.guns.client.model.bedrock.BedrockModel;
|
||||
import com.tacz.guns.client.model.bedrock.BedrockPart;
|
||||
import com.tacz.guns.client.resource.pojo.animation.bedrock.AnimationBone;
|
||||
import com.tacz.guns.client.resource.pojo.animation.bedrock.AnimationKeyframes;
|
||||
import com.tacz.guns.client.resource.pojo.animation.bedrock.BedrockAnimation;
|
||||
import com.tacz.guns.client.resource.pojo.animation.bedrock.BedrockAnimationFile;
|
||||
import com.tacz.guns.client.resource.pojo.model.BonesItem;
|
||||
import com.tacz.guns.util.math.MathUtil;
|
||||
import it.unimi.dsi.fastutil.doubles.Double2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Animations {
|
||||
public static AnimationController createControllerFromGltf(@Nonnull AnimationStructure structure, @Nonnull AnimationListenerSupplier supplier) {
|
||||
return new AnimationController(createAnimationFromGltf(structure, (AnimationListenerSupplier[]) null), supplier);
|
||||
}
|
||||
|
||||
public static @Nonnull List<ObjectAnimation> createAnimationFromGltf(@Nonnull AnimationStructure structure, @Nullable AnimationListenerSupplier... suppliers) {
|
||||
List<ObjectAnimation> result = new ArrayList<>();
|
||||
|
||||
List<AnimationModel> animationModels = structure.getAnimationModels();
|
||||
for (AnimationModel animationModel : animationModels) {
|
||||
ObjectAnimation animation = new ObjectAnimation(animationModel.getName());
|
||||
|
||||
// 初始化动画轨道
|
||||
List<AnimationModel.Channel> channelModels = animationModel.getChannels();
|
||||
for (AnimationModel.Channel channelModel : channelModels) {
|
||||
ObjectAnimationChannel channel = new ObjectAnimationChannel(ObjectAnimationChannel.ChannelType.valueOf(channelModel.path().toUpperCase()));
|
||||
AnimationModel.Sampler sampler = channelModel.sampler();
|
||||
|
||||
// 初始化轨道的节点名称和插值器
|
||||
AnimationModel.Interpolation interpolation = sampler.interpolation();
|
||||
NodeModel nodeModel = channelModel.nodeModel();
|
||||
// 四元数需要特殊的插值
|
||||
if (channel.type.equals(ObjectAnimationChannel.ChannelType.ROTATION) && interpolation.equals(AnimationModel.Interpolation.LINEAR)) {
|
||||
channel.interpolator = InterpolatorUtil.fromInterpolation(InterpolatorUtil.InterpolatorType.SLERP);
|
||||
} else {
|
||||
channel.interpolator = InterpolatorUtil.fromInterpolation(InterpolatorUtil.InterpolatorType.valueOf(interpolation.name()));
|
||||
}
|
||||
channel.node = nodeModel.getName();
|
||||
|
||||
// 初始化轨道的关键帧时间和关键帧数值
|
||||
// 关键帧时间的访问器
|
||||
AccessorModel input = sampler.input();
|
||||
AccessorData inputData = input.getAccessorData();
|
||||
if (!(inputData instanceof AccessorFloatData inputFloatData)) {
|
||||
GunMod.LOGGER.warn("Input data is not an AccessorFloatData, but {}", inputData.getClass());
|
||||
return result;
|
||||
}
|
||||
// 关键帧时间的访问器
|
||||
AccessorModel output = sampler.output();
|
||||
AccessorData outputData = output.getAccessorData();
|
||||
if (!(outputData instanceof AccessorFloatData outputFloatData)) {
|
||||
GunMod.LOGGER.warn("Output data is not an AccessorFloatData, but {}", inputData.getClass());
|
||||
return result;
|
||||
}
|
||||
int numKeyElements = inputFloatData.getNumElements();
|
||||
int numValuesElements = outputFloatData.getTotalNumComponents() / numKeyElements;
|
||||
float[] keyframeTimeS = new float[numKeyElements];
|
||||
float[][] values = new float[numKeyElements][numValuesElements];
|
||||
for (int i = 0; i < numKeyElements; i++) {
|
||||
keyframeTimeS[i] = inputFloatData.get(i);
|
||||
for (int j = 0; j < numValuesElements; j++) {
|
||||
values[i][j] = outputFloatData.get(i * numValuesElements + j);
|
||||
}
|
||||
}
|
||||
channel.content.keyframeTimeS = keyframeTimeS;
|
||||
channel.content.values = values;
|
||||
|
||||
// 加载完所有内容后编译插值器
|
||||
channel.interpolator.compile(channel.content);
|
||||
|
||||
// 将轨道添加到动画
|
||||
animation.addChannel(channel);
|
||||
|
||||
// 将动画监听器添加到动画中
|
||||
if (suppliers != null) {
|
||||
for (AnimationListenerSupplier supplier : suppliers) {
|
||||
AnimationListener listener = supplier.supplyListeners(channel.node, channel.type);
|
||||
if (listener != null) {
|
||||
channel.addListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将动画添加到结果列表
|
||||
result.add(animation);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static AnimationController createControllerFromBedrock(BedrockAnimationFile animationFile, BedrockAnimatedModel animatedModel) {
|
||||
return new AnimationController(createAnimationFromBedrock(animationFile, animatedModel), animatedModel);
|
||||
}
|
||||
|
||||
public static @Nonnull List<ObjectAnimation> createAnimationFromBedrock(BedrockAnimationFile animationFile, BedrockModel model) {
|
||||
List<ObjectAnimation> result = new ArrayList<>();
|
||||
for (Map.Entry<String, BedrockAnimation> animationEntry : animationFile.getAnimations().entrySet()) {
|
||||
ObjectAnimation animation = new ObjectAnimation(animationEntry.getKey());
|
||||
BedrockAnimation bedrockAnimation = animationEntry.getValue();
|
||||
for (Map.Entry<String, AnimationBone> boneEntry : bedrockAnimation.getBones().entrySet()) {
|
||||
AnimationBone bone = boneEntry.getValue();
|
||||
AnimationKeyframes translationKeyframes = bone.getPosition();
|
||||
AnimationKeyframes rotationKeyframes = bone.getRotation();
|
||||
AnimationKeyframes scaleKeyframes = bone.getScale();
|
||||
if (translationKeyframes != null) {
|
||||
ObjectAnimationChannel translationChannel = new ObjectAnimationChannel(ObjectAnimationChannel.ChannelType.TRANSLATION);
|
||||
translationChannel.node = boneEntry.getKey();
|
||||
translationChannel.interpolator = new CustomInterpolator();
|
||||
// 将位移数据转移进 AnimationChannel
|
||||
writeBedrockTranslation(translationChannel, bone.getPosition(), model);
|
||||
translationChannel.interpolator.compile(translationChannel.content);
|
||||
animation.addChannel(translationChannel);
|
||||
}
|
||||
if (rotationKeyframes != null) {
|
||||
ObjectAnimationChannel rotationChannel = new ObjectAnimationChannel(ObjectAnimationChannel.ChannelType.ROTATION);
|
||||
rotationChannel.node = boneEntry.getKey();
|
||||
rotationChannel.interpolator = new CustomInterpolator();
|
||||
// 将旋转数据转移进 AnimationChannel
|
||||
writeBedrockRotation(rotationChannel, bone.getRotation(), model);
|
||||
rotationChannel.interpolator.compile(rotationChannel.content);
|
||||
animation.addChannel(rotationChannel);
|
||||
}
|
||||
if (scaleKeyframes != null) {
|
||||
ObjectAnimationChannel scaleChannel = new ObjectAnimationChannel(ObjectAnimationChannel.ChannelType.SCALE);
|
||||
scaleChannel.node = boneEntry.getKey();
|
||||
scaleChannel.interpolator = new CustomInterpolator();
|
||||
// 将缩放数据转移进 AnimationChannel
|
||||
writeBedrockScale(scaleChannel, bone.getScale());
|
||||
scaleChannel.interpolator.compile(scaleChannel.content);
|
||||
animation.addChannel(scaleChannel);
|
||||
}
|
||||
}
|
||||
result.add(animation);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void writeBedrockTranslation(ObjectAnimationChannel animationChannel, AnimationKeyframes keyframes, BedrockModel model) {
|
||||
// 基岩版动画中储存的动画数据为相对值,而 tac 的动画系统使用的是绝对值,所以需要叠加初始值。
|
||||
// 此处就是在获取动画数据的初始值。
|
||||
Vector3f base;
|
||||
BedrockPart node = model.getNode(animationChannel.node);
|
||||
if (node != null) {
|
||||
if (node.getParent() != null) {
|
||||
base = new Vector3f(node.x, -node.y, node.z);
|
||||
} else {
|
||||
BonesItem bone = model.getBone(animationChannel.node);
|
||||
base = new Vector3f(-bone.getPivot().get(0), bone.getPivot().get(1), bone.getPivot().get(2));
|
||||
}
|
||||
} else {
|
||||
base = new Vector3f(0, 0, 0);
|
||||
}
|
||||
Double2ObjectRBTreeMap<AnimationKeyframes.Keyframe> keyframesMap = keyframes.getKeyframes();
|
||||
animationChannel.content.keyframeTimeS = new float[keyframesMap.size()];
|
||||
animationChannel.content.values = new float[keyframesMap.size()][];
|
||||
animationChannel.content.lerpModes = new AnimationChannelContent.LerpMode[keyframesMap.size()];
|
||||
int index = 0;
|
||||
for (Double2ObjectMap.Entry<AnimationKeyframes.Keyframe> entry : keyframesMap.double2ObjectEntrySet()) {
|
||||
// 写入关键帧时间
|
||||
animationChannel.content.keyframeTimeS[index] = (float) entry.getDoubleKey();
|
||||
// 写入关键帧数值。
|
||||
AnimationKeyframes.Keyframe keyframe = entry.getValue();
|
||||
if (keyframe.pre() != null || keyframe.post() != null) {
|
||||
if (keyframe.pre() != null && keyframe.post() != null) {
|
||||
animationChannel.content.values[index] = new float[6];
|
||||
Vector3f pre = keyframe.pre().copy();
|
||||
Vector3f post = keyframe.post().copy();
|
||||
pre.add(base);
|
||||
post.add(base);
|
||||
pre.mul(-1 / 16f, 1 / 16f, 1 / 16f);
|
||||
post.mul(-1 / 16f, 1 / 16f, 1 / 16f);
|
||||
readVector3fToArray(animationChannel.content.values[index], pre, 0);
|
||||
readVector3fToArray(animationChannel.content.values[index], post, 3);
|
||||
} else if (keyframe.pre() != null) {
|
||||
animationChannel.content.values[index] = new float[3];
|
||||
Vector3f pre = keyframe.pre().copy();
|
||||
pre.add(base);
|
||||
pre.mul(-1 / 16f, 1 / 16f, 1 / 16f);
|
||||
readVector3fToArray(animationChannel.content.values[index], pre, 0);
|
||||
} else {
|
||||
animationChannel.content.values[index] = new float[3];
|
||||
Vector3f post = keyframe.post().copy();
|
||||
post.add(base);
|
||||
post.mul(-1 / 16f, 1 / 16f, 1 / 16f);
|
||||
readVector3fToArray(animationChannel.content.values[index], post, 0);
|
||||
}
|
||||
} else if (keyframe.data() != null) {
|
||||
animationChannel.content.values[index] = new float[3];
|
||||
Vector3f data = keyframe.data().copy();
|
||||
data.add(base);
|
||||
data.mul(-1 / 16f, 1 / 16f, 1 / 16f);
|
||||
readVector3fToArray(animationChannel.content.values[index], data, 0);
|
||||
}
|
||||
// 写入关键帧插值类型
|
||||
String lerpModeName = keyframe.lerpMode();
|
||||
if (lerpModeName != null) {
|
||||
try {
|
||||
animationChannel.content.lerpModes[index] = AnimationChannelContent.LerpMode.valueOf(lerpModeName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
animationChannel.content.lerpModes[index] = AnimationChannelContent.LerpMode.LINEAR;
|
||||
}
|
||||
} else {
|
||||
animationChannel.content.lerpModes[index] = AnimationChannelContent.LerpMode.LINEAR;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeBedrockRotation(ObjectAnimationChannel animationChannel, AnimationKeyframes keyframes, BedrockModel model) {
|
||||
Vector3f base;
|
||||
BedrockPart node = model.getNode(animationChannel.node);
|
||||
if (node != null) {
|
||||
// 基岩版模型上下颠倒,x、y轴运动需要反向。
|
||||
base = new Vector3f(-node.xRot, -node.yRot, node.zRot);
|
||||
} else {
|
||||
base = new Vector3f(0, 0, 0);
|
||||
}
|
||||
Double2ObjectRBTreeMap<AnimationKeyframes.Keyframe> keyframesMap = keyframes.getKeyframes();
|
||||
animationChannel.content.keyframeTimeS = new float[keyframesMap.size()];
|
||||
animationChannel.content.values = new float[keyframesMap.size()][];
|
||||
animationChannel.content.lerpModes = new AnimationChannelContent.LerpMode[keyframesMap.size()];
|
||||
int index = 0;
|
||||
for (Double2ObjectMap.Entry<AnimationKeyframes.Keyframe> entry : keyframesMap.double2ObjectEntrySet()) {
|
||||
// 写入关键帧时间
|
||||
animationChannel.content.keyframeTimeS[index] = (float) entry.getDoubleKey();
|
||||
// 写入关键帧数值。
|
||||
AnimationKeyframes.Keyframe keyframe = entry.getValue();
|
||||
if (keyframe.pre() != null || keyframe.post() != null) {
|
||||
if (keyframe.pre() != null && keyframe.post() != null) {
|
||||
animationChannel.content.values[index] = new float[8];
|
||||
Vector3f pre = keyframe.pre().copy();
|
||||
Vector3f post = keyframe.post().copy();
|
||||
toAngle(pre);
|
||||
toAngle(post);
|
||||
pre.mul(-1, -1, 1);
|
||||
post.mul(-1, -1, 1);
|
||||
float[] q1 = MathUtil.toQuaternion(pre.x() + base.x(), pre.y() + base.y(), pre.z() + base.z());
|
||||
float[] q2 = MathUtil.toQuaternion(post.x() + base.x(), post.y() + base.y(), post.z() + base.z());
|
||||
System.arraycopy(q1, 0, animationChannel.content.values[index], 0, 4);
|
||||
System.arraycopy(q2, 0, animationChannel.content.values[index], 4, 4);
|
||||
} else if (keyframe.pre() != null) {
|
||||
animationChannel.content.values[index] = new float[4];
|
||||
Vector3f pre = keyframe.pre().copy();
|
||||
toAngle(pre);
|
||||
pre.mul(-1, -1, 1);
|
||||
float[] q = MathUtil.toQuaternion(pre.x() + base.x(), pre.y() + base.y(), pre.z() + base.z());
|
||||
System.arraycopy(q, 0, animationChannel.content.values[index], 0, 4);
|
||||
} else {
|
||||
animationChannel.content.values[index] = new float[4];
|
||||
Vector3f post = keyframe.post().copy();
|
||||
toAngle(post);
|
||||
post.mul(-1, -1, 1);
|
||||
float[] q = MathUtil.toQuaternion(post.x() + base.x(), post.y() + base.y(), post.z() + base.z());
|
||||
System.arraycopy(q, 0, animationChannel.content.values[index], 0, 4);
|
||||
}
|
||||
} else if (keyframe.data() != null) {
|
||||
animationChannel.content.values[index] = new float[4];
|
||||
Vector3f data = keyframe.data().copy();
|
||||
toAngle(data);
|
||||
data.mul(-1, -1, 1);
|
||||
float[] q = MathUtil.toQuaternion(data.x() + base.x(), data.y() + base.y(), data.z() + base.z());
|
||||
System.arraycopy(q, 0, animationChannel.content.values[index], 0, 4);
|
||||
}
|
||||
String lerpModeName = keyframe.lerpMode();
|
||||
if (lerpModeName != null) {
|
||||
if (lerpModeName.equals(AnimationChannelContent.LerpMode.CATMULLROM.name().toLowerCase())) {
|
||||
animationChannel.content.lerpModes[index] = AnimationChannelContent.LerpMode.SPHERICAL_CATMULLROM;
|
||||
} else {
|
||||
animationChannel.content.lerpModes[index] = AnimationChannelContent.LerpMode.SPHERICAL_LINEAR;
|
||||
}
|
||||
} else {
|
||||
animationChannel.content.lerpModes[index] = AnimationChannelContent.LerpMode.SPHERICAL_LINEAR;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeBedrockScale(ObjectAnimationChannel animationChannel, AnimationKeyframes keyframes) {
|
||||
Double2ObjectRBTreeMap<AnimationKeyframes.Keyframe> keyframesMap = keyframes.getKeyframes();
|
||||
animationChannel.content.keyframeTimeS = new float[keyframesMap.size()];
|
||||
animationChannel.content.values = new float[keyframesMap.size()][];
|
||||
animationChannel.content.lerpModes = new AnimationChannelContent.LerpMode[keyframesMap.size()];
|
||||
int index = 0;
|
||||
for (Double2ObjectMap.Entry<AnimationKeyframes.Keyframe> entry : keyframesMap.double2ObjectEntrySet()) {
|
||||
// 写入关键帧时间
|
||||
animationChannel.content.keyframeTimeS[index] = (float) entry.getDoubleKey();
|
||||
// 写入关键帧数值。
|
||||
AnimationKeyframes.Keyframe keyframe = entry.getValue();
|
||||
if (keyframe.pre() != null || keyframe.post() != null) {
|
||||
if (keyframe.pre() != null && keyframe.post() != null) {
|
||||
animationChannel.content.values[index] = new float[6];
|
||||
Vector3f pre = keyframe.pre();
|
||||
Vector3f post = keyframe.post();
|
||||
readVector3fToArray(animationChannel.content.values[index], pre, 0);
|
||||
readVector3fToArray(animationChannel.content.values[index], post, 3);
|
||||
} else if (keyframe.pre() != null) {
|
||||
animationChannel.content.values[index] = new float[3];
|
||||
Vector3f pre = keyframe.pre();
|
||||
readVector3fToArray(animationChannel.content.values[index], pre, 0);
|
||||
} else {
|
||||
animationChannel.content.values[index] = new float[3];
|
||||
Vector3f post = keyframe.post();
|
||||
readVector3fToArray(animationChannel.content.values[index], post, 0);
|
||||
}
|
||||
} else if (keyframe.data() != null) {
|
||||
animationChannel.content.values[index] = new float[3];
|
||||
Vector3f data = keyframe.data();
|
||||
readVector3fToArray(animationChannel.content.values[index], data, 0);
|
||||
}
|
||||
// 写入关键帧插值类型
|
||||
String lerpModeName = keyframe.lerpMode();
|
||||
if (lerpModeName != null) {
|
||||
try {
|
||||
animationChannel.content.lerpModes[index] = AnimationChannelContent.LerpMode.valueOf(lerpModeName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
animationChannel.content.lerpModes[index] = AnimationChannelContent.LerpMode.LINEAR;
|
||||
}
|
||||
} else {
|
||||
animationChannel.content.lerpModes[index] = AnimationChannelContent.LerpMode.LINEAR;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void toAngle(Vector3f vector3f) {
|
||||
vector3f.set((float) Math.toRadians(vector3f.x()), (float) Math.toRadians(vector3f.y()), (float) Math.toRadians(vector3f.z()));
|
||||
}
|
||||
|
||||
private static void readVector3fToArray(float[] array, Vector3f vector3f, int offset) {
|
||||
array[offset] = vector3f.x();
|
||||
array[offset + 1] = vector3f.y();
|
||||
array[offset + 2] = vector3f.z();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.tacz.guns.client.animation;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 创建一个 {@link ObjectAnimationRunner} 实例以运行 {@link ObjectAnimation}
|
||||
*/
|
||||
public class ObjectAnimation {
|
||||
/**
|
||||
* 动画名称
|
||||
*/
|
||||
public final String name;
|
||||
/**
|
||||
* 此 map 的 key 是节点名称
|
||||
*/
|
||||
private final Map<String, List<ObjectAnimationChannel>> channels = new HashMap<>();
|
||||
/**
|
||||
* 播放类型
|
||||
*/
|
||||
public @Nonnull PlayType playType = PlayType.PLAY_ONCE_HOLD;
|
||||
/**
|
||||
* 当前播放进度时间,以纳秒为单位
|
||||
*/
|
||||
public long timeNs = 0;
|
||||
/**
|
||||
* 所有轨道的最大结束时间 {@link ObjectAnimationChannel#getEndTimeS()}
|
||||
*/
|
||||
private float maxEndTimeS = 0f;
|
||||
|
||||
protected ObjectAnimation(@Nonnull String name) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建源对象动画的拷贝,
|
||||
* 新对象动画的值与源动画的值相同,
|
||||
* 但新对象动画不会包含任何动画监听器。
|
||||
*/
|
||||
public ObjectAnimation(ObjectAnimation source) {
|
||||
this.name = source.name;
|
||||
this.playType = source.playType;
|
||||
this.maxEndTimeS = source.maxEndTimeS;
|
||||
this.timeNs = source.timeNs;
|
||||
for (Map.Entry<String, List<ObjectAnimationChannel>> entry : source.channels.entrySet()) {
|
||||
List<ObjectAnimationChannel> newList = new ArrayList<>();
|
||||
for (ObjectAnimationChannel channel : entry.getValue()) {
|
||||
ObjectAnimationChannel newChannel = new ObjectAnimationChannel(channel.type, channel.content);
|
||||
newChannel.node = channel.node;
|
||||
newChannel.interpolator = channel.interpolator;
|
||||
newList.add(newChannel);
|
||||
}
|
||||
this.channels.put(entry.getKey(), newList);
|
||||
}
|
||||
}
|
||||
|
||||
protected void addChannel(ObjectAnimationChannel channel) {
|
||||
channels.compute(channel.node, (node, list) -> {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
list.add(channel);
|
||||
return list;
|
||||
});
|
||||
if (channel.getEndTimeS() > maxEndTimeS) {
|
||||
maxEndTimeS = channel.getEndTimeS();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, List<ObjectAnimationChannel>> getChannels() {
|
||||
return channels;
|
||||
}
|
||||
|
||||
public void applyAnimationListeners(AnimationListenerSupplier supplier) {
|
||||
for (List<ObjectAnimationChannel> channelList : channels.values()) {
|
||||
for (ObjectAnimationChannel channel : channelList) {
|
||||
AnimationListener listener = supplier.supplyListeners(channel.node, channel.type);
|
||||
if (listener != null) {
|
||||
channel.addListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发所有监听器,通知它们更新相关数值
|
||||
*/
|
||||
public void update(boolean blend) {
|
||||
for (List<ObjectAnimationChannel> channels : channels.values()) {
|
||||
for (ObjectAnimationChannel channel : channels) {
|
||||
channel.update(timeNs / 1e9f, blend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float getMaxEndTimeS() {
|
||||
return maxEndTimeS;
|
||||
}
|
||||
|
||||
public enum PlayType {
|
||||
/**
|
||||
* 播放一次,停留在最后一帧
|
||||
*/
|
||||
PLAY_ONCE_HOLD,
|
||||
/**
|
||||
* 播放一次后停止
|
||||
*/
|
||||
PLAY_ONCE_STOP,
|
||||
/**
|
||||
* 循环播放
|
||||
*/
|
||||
LOOP
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.tacz.guns.client.animation;
|
||||
|
||||
import com.tacz.guns.client.animation.interpolator.Interpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ObjectAnimationChannel {
|
||||
public final ChannelType type;
|
||||
private final List<AnimationListener> listeners = new ArrayList<>();
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
public String node;
|
||||
/**
|
||||
* 这个轨道的内容,包括关键帧
|
||||
*/
|
||||
public AnimationChannelContent content;
|
||||
public Interpolator interpolator;
|
||||
/**
|
||||
* 此变量用于动画过渡,
|
||||
* 如果你不明白在做什么,请不要更改它
|
||||
*/
|
||||
boolean transitioning = false;
|
||||
|
||||
public ObjectAnimationChannel(ChannelType type) {
|
||||
this.type = type;
|
||||
this.content = new AnimationChannelContent();
|
||||
}
|
||||
|
||||
public ObjectAnimationChannel(ChannelType type, AnimationChannelContent content) {
|
||||
this.type = type;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public void addListener(AnimationListener listener) {
|
||||
if (listener.getType().equals(type)) {
|
||||
listeners.add(listener);
|
||||
} else {
|
||||
throw new RuntimeException("trying to add wrong type of listener to channel.");
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(AnimationListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
public void clearListeners() {
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
public List<AnimationListener> getListeners() {
|
||||
return listeners;
|
||||
}
|
||||
|
||||
public float getEndTimeS() {
|
||||
return content.keyframeTimeS[content.keyframeTimeS.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据输入时间执行计算,并将结果通知所有 AnimationListener
|
||||
*
|
||||
* @param timeS 绝对时间(以秒为单位)
|
||||
*/
|
||||
public void update(float timeS, boolean blend) {
|
||||
if (!transitioning) {
|
||||
float[] result = getResult(timeS);
|
||||
for (AnimationListener listener : listeners) {
|
||||
listener.update(result, blend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float[] getResult(float timeS) {
|
||||
int indexFrom = computeIndex(timeS);
|
||||
int indexTo = Math.min(content.keyframeTimeS.length - 1, indexFrom + 1);
|
||||
float alpha = computeAlpha(timeS, indexFrom);
|
||||
int resultLength = type == ChannelType.ROTATION ? 4 : 3;
|
||||
float[] result = new float[resultLength];
|
||||
interpolator.interpolate(indexFrom, indexTo, alpha, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private int computeIndex(float timeS) {
|
||||
int index = Arrays.binarySearch(content.keyframeTimeS, timeS);
|
||||
if (index >= 0) {
|
||||
return index;
|
||||
}
|
||||
return Math.max(0, -index - 2);
|
||||
}
|
||||
|
||||
private float computeAlpha(float timeS, int indexFrom) {
|
||||
if (timeS <= content.keyframeTimeS[0]) {
|
||||
return 0.0f;
|
||||
}
|
||||
if (timeS >= content.keyframeTimeS[content.keyframeTimeS.length - 1]) {
|
||||
return 1.0f;
|
||||
}
|
||||
float local = timeS - content.keyframeTimeS[indexFrom];
|
||||
float delta = content.keyframeTimeS[indexFrom + 1] - content.keyframeTimeS[indexFrom];
|
||||
return local / delta;
|
||||
}
|
||||
|
||||
public enum ChannelType {
|
||||
/**
|
||||
* 位移
|
||||
*/
|
||||
TRANSLATION,
|
||||
/**
|
||||
* 旋转
|
||||
*/
|
||||
ROTATION,
|
||||
/**
|
||||
* 缩放
|
||||
*/
|
||||
SCALE
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
package com.tacz.guns.client.animation;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
public class ObjectAnimationRunner {
|
||||
@Nonnull
|
||||
private final ObjectAnimation animation;
|
||||
protected long transitionTimeNs;
|
||||
/**
|
||||
* 用于动画过渡,储存的是过渡起点动画的值,与下方transitionFromChannels一一对应
|
||||
*/
|
||||
protected ArrayList<float[]> valueFrom;
|
||||
/**
|
||||
* 用于动画过渡,储存的是过渡起点需要恢复原位的channel的值,与下方recoverChannels一一对应
|
||||
*/
|
||||
protected ArrayList<float[]> valueRecover;
|
||||
/**
|
||||
* 用于动画过渡,储存的是过渡起点动画的channel
|
||||
*/
|
||||
protected ArrayList<ObjectAnimationChannel> transitionFromChannels;
|
||||
/**
|
||||
* 用于动画过渡,储存的是过渡终点动画的channel,顺序与上面对应
|
||||
*/
|
||||
protected ArrayList<ObjectAnimationChannel> transitionToChannels;
|
||||
/**
|
||||
* 用于动画过渡,储存的是过渡起点动画需要恢复到原位的channel
|
||||
*/
|
||||
protected ArrayList<ObjectAnimationChannel> recoverChannels;
|
||||
private boolean running = false;
|
||||
private long lastUpdateNs;
|
||||
/**
|
||||
* 当前动画播放进度
|
||||
*/
|
||||
private long progressNs;
|
||||
private boolean isTransitioning = false;
|
||||
@Nullable
|
||||
private ObjectAnimationRunner transitionTo;
|
||||
private long transitionProgressNs;
|
||||
|
||||
public ObjectAnimationRunner(@Nonnull ObjectAnimation animation) {
|
||||
this.animation = Objects.requireNonNull(animation);
|
||||
}
|
||||
|
||||
public @Nonnull ObjectAnimation getAnimation() {
|
||||
return animation;
|
||||
}
|
||||
|
||||
public @Nullable ObjectAnimationRunner getTransitionTo() {
|
||||
return transitionTo;
|
||||
}
|
||||
|
||||
public boolean isTransitioning() {
|
||||
return isTransitioning;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (!running) {
|
||||
running = true;
|
||||
lastUpdateNs = System.nanoTime();
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
public void hold() {
|
||||
progressNs = (long) (animation.getMaxEndTimeS() * 1e9) + 1;
|
||||
pause();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
progressNs = (long) (animation.getMaxEndTimeS() * 1e9) + 2;
|
||||
pause();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
progressNs = 0;
|
||||
}
|
||||
|
||||
public long getProgressNs() {
|
||||
return progressNs;
|
||||
}
|
||||
|
||||
public void setProgressNs(long progressNs) {
|
||||
this.progressNs = progressNs;
|
||||
}
|
||||
|
||||
public void transition(ObjectAnimationRunner transitionTo, long transitionTimeNS) {
|
||||
if (this.transitionTo == null) {
|
||||
this.valueFrom = new ArrayList<>();
|
||||
this.valueRecover = new ArrayList<>();
|
||||
this.transitionFromChannels = new ArrayList<>();
|
||||
this.transitionToChannels = new ArrayList<>();
|
||||
this.recoverChannels = new ArrayList<>();
|
||||
this.transitionTo = transitionTo;
|
||||
this.pause();
|
||||
for (Map.Entry<String, List<ObjectAnimationChannel>> entry : animation.getChannels().entrySet()) {
|
||||
List<ObjectAnimationChannel> toChannels = transitionTo.animation.getChannels().get(entry.getKey());
|
||||
if (toChannels != null) {
|
||||
// 如果过渡终点的动画中同一个node 包含相同类型的动画数据(位移、旋转、缩放),那么加入到 list 中用于更新。
|
||||
for (ObjectAnimationChannel channel : entry.getValue()) {
|
||||
Optional<ObjectAnimationChannel> toChannel =
|
||||
toChannels.stream().filter(c -> c.type.equals(channel.type)).findAny();
|
||||
if (toChannel.isPresent()) {
|
||||
valueFrom.add(channel.getResult(progressNs / 1e9f));
|
||||
transitionFromChannels.add(channel);
|
||||
transitionToChannels.add(toChannel.get());
|
||||
// 取消过渡目标的channel对模型的更新,统一在起点channel进行更新。
|
||||
toChannel.get().transitioning = true;
|
||||
} else {
|
||||
valueRecover.add(channel.getResult(progressNs / 1e9f));
|
||||
recoverChannels.add(channel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果过渡终点的动画中 同一个 node 不包含动画数据,那么将过渡到原位。
|
||||
for (ObjectAnimationChannel channel : entry.getValue()) {
|
||||
valueRecover.add(channel.getResult(progressNs / 1e9f));
|
||||
recoverChannels.add(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isTransitioning) {
|
||||
ArrayList<float[]> newValueFrom = new ArrayList<>();
|
||||
ArrayList<float[]> newValueRecover = new ArrayList<>();
|
||||
ArrayList<ObjectAnimationChannel> newTransitionFromChannels = new ArrayList<>();
|
||||
ArrayList<ObjectAnimationChannel> newTransitionToChannels = new ArrayList<>();
|
||||
ArrayList<ObjectAnimationChannel> newRecoverChannels = new ArrayList<>();
|
||||
// 如果正在过渡,则需要把当前过渡计算出的插值保存,作为下次过渡的起点
|
||||
for (int i = 0; i < transitionFromChannels.size(); i++) {
|
||||
assert this.transitionTo != null;
|
||||
ObjectAnimationChannel fromChannel = transitionFromChannels.get(i);
|
||||
ObjectAnimationChannel toChannel = transitionToChannels.get(i);
|
||||
float[] from = valueFrom.get(i);
|
||||
float[] to = toChannel.getResult(this.transitionTo.progressNs / 1e9f);
|
||||
float[] result = new float[from.length];
|
||||
float progress = easeOutCubic((float) transitionProgressNs / transitionTimeNs);
|
||||
if (fromChannel.type.equals(ObjectAnimationChannel.ChannelType.TRANSLATION)) {
|
||||
lerp(from, to, progress, result);
|
||||
} else if (fromChannel.type.equals(ObjectAnimationChannel.ChannelType.ROTATION)) {
|
||||
slerp(from, to, progress, result);
|
||||
} else if (fromChannel.type.equals(ObjectAnimationChannel.ChannelType.SCALE)) {
|
||||
lerp(from, to, progress, result);
|
||||
}
|
||||
|
||||
List<ObjectAnimationChannel> newToChannels = transitionTo.animation.getChannels().get(fromChannel.node);
|
||||
if (newToChannels != null) {
|
||||
Optional<ObjectAnimationChannel> newToChannel =
|
||||
newToChannels.stream().filter(c -> c.type.equals(fromChannel.type)).findAny();
|
||||
if (newToChannel.isPresent()) {
|
||||
newValueFrom.add(result);
|
||||
newTransitionFromChannels.add(fromChannel);
|
||||
newTransitionToChannels.add(newToChannel.get());
|
||||
// 取消过渡目标的channel对模型的更新,统一在起点channel进行更新。
|
||||
newToChannel.get().transitioning = true;
|
||||
} else {
|
||||
newValueRecover.add(result);
|
||||
newRecoverChannels.add(fromChannel);
|
||||
}
|
||||
} else {
|
||||
newValueRecover.add(result);
|
||||
newRecoverChannels.add(fromChannel);
|
||||
}
|
||||
toChannel.transitioning = false;
|
||||
}
|
||||
this.valueFrom = newValueFrom;
|
||||
this.valueRecover = newValueRecover;
|
||||
this.transitionToChannels = newTransitionToChannels;
|
||||
this.transitionFromChannels = newTransitionFromChannels;
|
||||
this.recoverChannels = newRecoverChannels;
|
||||
this.transitionTo = transitionTo;
|
||||
}
|
||||
this.transitionTimeNs = transitionTimeNS;
|
||||
this.transitionProgressNs = 0;
|
||||
this.isTransitioning = true;
|
||||
}
|
||||
|
||||
public long getTransitionTimeNs() {
|
||||
return transitionTimeNs;
|
||||
}
|
||||
|
||||
public long getTransitionProgressNs() {
|
||||
return transitionProgressNs;
|
||||
}
|
||||
|
||||
public void setTransitionProgressNs(long progressNs) {
|
||||
this.transitionProgressNs = progressNs;
|
||||
}
|
||||
|
||||
public void stopTransition() {
|
||||
this.isTransitioning = false;
|
||||
for (ObjectAnimationChannel channel : transitionToChannels) {
|
||||
channel.transitioning = false;
|
||||
}
|
||||
this.transitionTimeNs = 0;
|
||||
this.transitionProgressNs = 0;
|
||||
this.transitionFromChannels = null;
|
||||
this.transitionToChannels = null;
|
||||
this.recoverChannels = null;
|
||||
this.valueFrom = null;
|
||||
this.valueRecover = null;
|
||||
}
|
||||
|
||||
public void update(boolean blend) {
|
||||
long currentNs = System.nanoTime();
|
||||
|
||||
if (running) {
|
||||
progressNs += currentNs - lastUpdateNs;
|
||||
}
|
||||
switch (animation.playType) {
|
||||
case PLAY_ONCE_HOLD -> {
|
||||
if (progressNs / 1e9 > animation.getMaxEndTimeS()) {
|
||||
hold();
|
||||
}
|
||||
}
|
||||
case PLAY_ONCE_STOP -> {
|
||||
if (progressNs / 1e9 > animation.getMaxEndTimeS()) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
case LOOP -> {
|
||||
if (progressNs / 1e9 > animation.getMaxEndTimeS()) {
|
||||
if (animation.getMaxEndTimeS() == 0) {
|
||||
progressNs = 0;
|
||||
} else {
|
||||
progressNs = progressNs % (long) (animation.getMaxEndTimeS() * 1e9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
animation.timeNs = progressNs;
|
||||
|
||||
if (isTransitioning) {
|
||||
transitionProgressNs += currentNs - lastUpdateNs;
|
||||
if (transitionProgressNs >= transitionTimeNs) {
|
||||
stopTransition();
|
||||
} else {
|
||||
float transitionProgress = (float) transitionProgressNs / transitionTimeNs;
|
||||
updateTransition(easeOutCubic(transitionProgress), blend);
|
||||
}
|
||||
} else {
|
||||
animation.update(blend);
|
||||
}
|
||||
|
||||
lastUpdateNs = currentNs;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
public boolean isHolding() {
|
||||
return progressNs == (long) (getAnimation().getMaxEndTimeS() * 1e9) + 1;
|
||||
}
|
||||
|
||||
public boolean isStopped() {
|
||||
return progressNs == (long) (getAnimation().getMaxEndTimeS() * 1e9) + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画过渡的时候,计算出的插值将通过当前Runner中包含的ObjectAnimation中的channel对模型进行update
|
||||
* 这意味着需要暂时取消transitionTo中对应channel的update功能(将变量available设置为false)
|
||||
*/
|
||||
private void updateTransition(float progress, boolean blend) {
|
||||
assert transitionTo != null;
|
||||
for (int i = 0; i < transitionToChannels.size(); i++) {
|
||||
ObjectAnimationChannel fromChannel = transitionFromChannels.get(i);
|
||||
ObjectAnimationChannel toChannel = transitionToChannels.get(i);
|
||||
|
||||
float[] from = valueFrom.get(i);
|
||||
float[] to = toChannel.getResult(transitionTo.progressNs / 1e9f);
|
||||
float[] result = new float[from.length];
|
||||
|
||||
if (fromChannel.type.equals(ObjectAnimationChannel.ChannelType.TRANSLATION)) {
|
||||
lerp(from, to, progress, result);
|
||||
} else if (fromChannel.type.equals(ObjectAnimationChannel.ChannelType.ROTATION)) {
|
||||
slerp(from, to, progress, result);
|
||||
} else if (fromChannel.type.equals(ObjectAnimationChannel.ChannelType.SCALE)) {
|
||||
lerp(from, to, progress, result);
|
||||
}
|
||||
for (AnimationListener listener : fromChannel.getListeners()) {
|
||||
listener.update(result, blend);
|
||||
}
|
||||
|
||||
}
|
||||
if (animation.playType != ObjectAnimation.PlayType.PLAY_ONCE_STOP) {
|
||||
// 如果是 PLAY_ONCE_STOP,动画结束后不应该 update 其本身的关键帧,因此不进行恢复过渡
|
||||
for (int i = 0; i < recoverChannels.size(); i++) {
|
||||
ObjectAnimationChannel channel = recoverChannels.get(i);
|
||||
float[] from = valueRecover.get(i);
|
||||
float[] result = new float[from.length];
|
||||
if (channel.type.equals(ObjectAnimationChannel.ChannelType.TRANSLATION)) {
|
||||
for (AnimationListener listener : channel.getListeners()) {
|
||||
float[] to = listener.recover();
|
||||
lerp(from, to, progress, result);
|
||||
listener.update(result, blend);
|
||||
}
|
||||
} else if (channel.type.equals(ObjectAnimationChannel.ChannelType.ROTATION)) {
|
||||
for (AnimationListener listener : channel.getListeners()) {
|
||||
float[] to = listener.recover();
|
||||
slerp(from, to, progress, result);
|
||||
listener.update(result, blend);
|
||||
}
|
||||
} else if (channel.type.equals(ObjectAnimationChannel.ChannelType.SCALE)) {
|
||||
for (AnimationListener listener : channel.getListeners()) {
|
||||
float[] to = listener.recover();
|
||||
lerp(from, to, progress, result);
|
||||
listener.update(result, blend);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float easeOutCubic(double x) {
|
||||
return (float) (1 - Math.pow(1 - x, 4));
|
||||
}
|
||||
|
||||
private void lerp(float[] from, float[] to, float alpha, float[] result) {
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = from[i] * (1 - alpha) + to[i] * alpha;
|
||||
}
|
||||
}
|
||||
|
||||
private void slerp(float[] from, float[] to, float alpha, float[] result) {
|
||||
float ax = from[0];
|
||||
float ay = from[1];
|
||||
float az = from[2];
|
||||
float aw = from[3];
|
||||
float bx = to[0];
|
||||
float by = to[1];
|
||||
float bz = to[2];
|
||||
float bw = to[3];
|
||||
|
||||
float dot = ax * bx + ay * by + az * bz + aw * bw;
|
||||
if (dot < 0) {
|
||||
bx = -bx;
|
||||
by = -by;
|
||||
bz = -bz;
|
||||
bw = -bw;
|
||||
dot = -dot;
|
||||
}
|
||||
float epsilon = 1e-6f;
|
||||
float s0, s1;
|
||||
if ((1.0 - dot) > epsilon) {
|
||||
float omega = (float) Math.acos(dot);
|
||||
float invSinOmega = 1.0f / (float) Math.sin(omega);
|
||||
s0 = (float) Math.sin((1.0 - alpha) * omega) * invSinOmega;
|
||||
s1 = (float) Math.sin(alpha * omega) * invSinOmega;
|
||||
} else {
|
||||
s0 = 1.0f - alpha;
|
||||
s1 = alpha;
|
||||
}
|
||||
float rx = s0 * ax + s1 * bx;
|
||||
float ry = s0 * ay + s1 * by;
|
||||
float rz = s0 * az + s1 * bz;
|
||||
float rw = s0 * aw + s1 * bw;
|
||||
result[0] = rx;
|
||||
result[1] = ry;
|
||||
result[2] = rz;
|
||||
result[3] = rw;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.tacz.guns.client.animation.gltf;
|
||||
|
||||
import com.tacz.guns.client.animation.gltf.accessor.AccessorData;
|
||||
import com.tacz.guns.client.animation.gltf.accessor.AccessorDatas;
|
||||
import com.tacz.guns.client.animation.gltf.accessor.Accessors;
|
||||
|
||||
public class AccessorModel {
|
||||
/**
|
||||
* The component type, as a GL constant
|
||||
*/
|
||||
private final int componentType;
|
||||
/**
|
||||
* The {@link ElementType} of this accessor
|
||||
*/
|
||||
private final ElementType elementType;
|
||||
/**
|
||||
* The number of elements
|
||||
*/
|
||||
private final int count;
|
||||
/**
|
||||
* The offset in bytes, referring to the buffer view
|
||||
*/
|
||||
private int byteOffset;
|
||||
/**
|
||||
* The {@link BufferViewModel} for this model
|
||||
*/
|
||||
private BufferViewModel bufferViewModel;
|
||||
/**
|
||||
* The stride between the start of one element and the next
|
||||
*/
|
||||
private int byteStride;
|
||||
|
||||
/**
|
||||
* The {@link AccessorData}
|
||||
*/
|
||||
private AccessorData accessorData;
|
||||
|
||||
/**
|
||||
* The minimum components
|
||||
*/
|
||||
private Number[] max;
|
||||
|
||||
/**
|
||||
* The maximum components
|
||||
*/
|
||||
private Number[] min;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param componentType The component type GL constant
|
||||
* @param count The number of elements
|
||||
* @param elementType The element type
|
||||
*/
|
||||
public AccessorModel(
|
||||
int componentType,
|
||||
int count,
|
||||
ElementType elementType) {
|
||||
this.componentType = componentType;
|
||||
this.count = count;
|
||||
this.elementType = elementType;
|
||||
}
|
||||
|
||||
public BufferViewModel getBufferViewModel() {
|
||||
return bufferViewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link BufferViewModel} for this model
|
||||
*
|
||||
* @param bufferViewModel The {@link BufferViewModel}
|
||||
*/
|
||||
public void setBufferViewModel(BufferViewModel bufferViewModel) {
|
||||
this.bufferViewModel = bufferViewModel;
|
||||
}
|
||||
|
||||
public int getComponentType() {
|
||||
return componentType;
|
||||
}
|
||||
|
||||
public Class<?> getComponentDataType() {
|
||||
return Accessors.getDataTypeForAccessorComponentType(
|
||||
getComponentType());
|
||||
}
|
||||
|
||||
public int getComponentSizeInBytes() {
|
||||
return Accessors.getNumBytesForAccessorComponentType(componentType);
|
||||
}
|
||||
|
||||
public int getElementSizeInBytes() {
|
||||
return elementType.getNumComponents() * getComponentSizeInBytes();
|
||||
}
|
||||
|
||||
public int getByteOffset() {
|
||||
return byteOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the byte offset, referring to the {@link BufferViewModel}
|
||||
*
|
||||
* @param byteOffset The byte offset
|
||||
*/
|
||||
public void setByteOffset(int byteOffset) {
|
||||
this.byteOffset = byteOffset;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public ElementType getElementType() {
|
||||
return elementType;
|
||||
}
|
||||
|
||||
public int getByteStride() {
|
||||
return byteStride;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the byte stride, indicating the number of bytes between the start
|
||||
* of one element and the start of the next element
|
||||
*
|
||||
* @param byteStride The byte stride
|
||||
*/
|
||||
public void setByteStride(int byteStride) {
|
||||
this.byteStride = byteStride;
|
||||
}
|
||||
|
||||
public AccessorData getAccessorData() {
|
||||
if (accessorData == null) {
|
||||
accessorData = AccessorDatas.create(this);
|
||||
}
|
||||
return accessorData;
|
||||
}
|
||||
|
||||
public Number[] getMin() {
|
||||
if (min == null) {
|
||||
min = AccessorDatas.computeMin(getAccessorData());
|
||||
}
|
||||
return min.clone();
|
||||
}
|
||||
|
||||
public Number[] getMax() {
|
||||
if (max == null) {
|
||||
max = AccessorDatas.computeMax(getAccessorData());
|
||||
}
|
||||
return max.clone();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.tacz.guns.client.animation.gltf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AnimationModel {
|
||||
/**
|
||||
* The {@link Channel} instances
|
||||
* of this animation
|
||||
*/
|
||||
private final List<Channel> channels = new ArrayList<>();
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given {@link Channel}
|
||||
*
|
||||
* @param channel The {@link Channel}
|
||||
*/
|
||||
public void addChannel(Channel channel) {
|
||||
Objects.requireNonNull(channel, "The channel may not be null");
|
||||
this.channels.add(channel);
|
||||
}
|
||||
|
||||
public List<Channel> getChannels() {
|
||||
return Collections.unmodifiableList(channels);
|
||||
}
|
||||
|
||||
public enum Interpolation {
|
||||
/**
|
||||
* Stepwise interpolation
|
||||
*/
|
||||
STEP,
|
||||
|
||||
/**
|
||||
* Linear interpolation
|
||||
*/
|
||||
LINEAR,
|
||||
|
||||
/**
|
||||
* Spline interpolation
|
||||
*/
|
||||
SPLINE
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param input The input data
|
||||
* @param interpolation The interpolation method
|
||||
* @param output The output data
|
||||
*/
|
||||
public record Sampler(AccessorModel input, Interpolation interpolation, AccessorModel output) {
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param input The input
|
||||
* @param interpolation The interpolation
|
||||
* @param output The output
|
||||
*/
|
||||
public Sampler(
|
||||
AccessorModel input,
|
||||
Interpolation interpolation,
|
||||
AccessorModel output) {
|
||||
this.input = Objects.requireNonNull(
|
||||
input, "The input may not be null");
|
||||
this.interpolation = Objects.requireNonNull(
|
||||
interpolation, "The interpolation may not be null");
|
||||
this.output = Objects.requireNonNull(
|
||||
output, "The output may not be null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sampler The sampler
|
||||
* @param nodeModel The node model
|
||||
* @param path The path
|
||||
*/
|
||||
public record Channel(Sampler sampler, NodeModel nodeModel, String path) {
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param sampler The sampler
|
||||
* @param nodeModel The node model
|
||||
* @param path The path
|
||||
*/
|
||||
public Channel(
|
||||
Sampler sampler,
|
||||
NodeModel nodeModel,
|
||||
String path) {
|
||||
this.sampler = Objects.requireNonNull(
|
||||
sampler, "The sampler may not be null");
|
||||
this.nodeModel = nodeModel;
|
||||
this.path = Objects.requireNonNull(
|
||||
path, "The path may not be null");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
package com.tacz.guns.client.animation.gltf;
|
||||
|
||||
import com.tacz.guns.client.animation.gltf.accessor.AccessorData;
|
||||
import com.tacz.guns.client.animation.gltf.accessor.AccessorDatas;
|
||||
import com.tacz.guns.client.animation.gltf.accessor.AccessorSparseUtils;
|
||||
import com.tacz.guns.client.resource.pojo.animation.gltf.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class AnimationStructure {
|
||||
private final List<AccessorModel> accessorModels = new ArrayList<>();
|
||||
private final List<AnimationModel> animationModels = new ArrayList<>();
|
||||
private final List<BufferModel> bufferModels = new ArrayList<>();
|
||||
private final List<BufferViewModel> bufferViewModels = new ArrayList<>();
|
||||
private final List<NodeModel> nodeModels = new ArrayList<>();
|
||||
private final RawAnimationStructure gltf;
|
||||
|
||||
public AnimationStructure(RawAnimationStructure asset) {
|
||||
gltf = asset;
|
||||
createAccessorModels();
|
||||
createAnimationModels();
|
||||
createBufferModels();
|
||||
createBufferViewModels();
|
||||
createNodeModels();
|
||||
|
||||
initBufferModels();
|
||||
initBufferViewModels();
|
||||
initAccessorModels();
|
||||
initAnimationModels();
|
||||
initNodeModels();
|
||||
}
|
||||
|
||||
private static float[] clone(float[] array) {
|
||||
if (array == null) {
|
||||
return null;
|
||||
}
|
||||
return array.clone();
|
||||
}
|
||||
|
||||
private static BufferViewModel createBufferViewModel(
|
||||
String uriString, ByteBuffer bufferData) {
|
||||
BufferModel bufferModel = new BufferModel();
|
||||
bufferModel.setUri(uriString);
|
||||
bufferModel.setBufferData(bufferData);
|
||||
|
||||
BufferViewModel bufferViewModel =
|
||||
new BufferViewModel(null);
|
||||
bufferViewModel.setByteOffset(0);
|
||||
bufferViewModel.setByteLength(bufferData.capacity());
|
||||
bufferViewModel.setBufferModel(bufferModel);
|
||||
|
||||
return bufferViewModel;
|
||||
}
|
||||
|
||||
private static BufferViewModel createBufferViewModel(
|
||||
BufferView bufferView) {
|
||||
int byteOffset = bufferView.getByteOffset() == null ? 0 : bufferView.getByteOffset();
|
||||
int byteLength = bufferView.getByteLength();
|
||||
Integer byteStride = bufferView.getByteStride();
|
||||
Integer target = bufferView.getTarget();
|
||||
BufferViewModel bufferViewModel =
|
||||
new BufferViewModel(target);
|
||||
bufferViewModel.setByteOffset(byteOffset);
|
||||
bufferViewModel.setByteLength(byteLength);
|
||||
bufferViewModel.setByteStride(byteStride);
|
||||
return bufferViewModel;
|
||||
}
|
||||
|
||||
private static boolean isDataUriString(String uriString) {
|
||||
if (uriString == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
URI uri = new URI(uriString);
|
||||
return isDataUri(uri);
|
||||
} catch (URISyntaxException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDataUri(URI uri) {
|
||||
return "data".equalsIgnoreCase(uri.getScheme());
|
||||
}
|
||||
|
||||
public static byte[] readDataUri(String uriString) {
|
||||
String encoding = "base64,";
|
||||
int encodingIndex = uriString.indexOf(encoding);
|
||||
if (encodingIndex < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"The given URI string is not a base64 encoded "
|
||||
+ "data URI string: " + uriString);
|
||||
}
|
||||
int contentStartIndex = encodingIndex + encoding.length();
|
||||
return Base64.getDecoder().decode(
|
||||
uriString.substring(contentStartIndex));
|
||||
}
|
||||
|
||||
public List<BufferModel> getBufferModels() {
|
||||
return Collections.unmodifiableList(bufferModels);
|
||||
}
|
||||
|
||||
public List<AccessorModel> getAccessorModels() {
|
||||
return accessorModels;
|
||||
}
|
||||
|
||||
public List<AnimationModel> getAnimationModels() {
|
||||
return animationModels;
|
||||
}
|
||||
|
||||
public List<BufferViewModel> getBufferViewModels() {
|
||||
return bufferViewModels;
|
||||
}
|
||||
|
||||
public List<NodeModel> getNodeModels() {
|
||||
return nodeModels;
|
||||
}
|
||||
|
||||
private void createBufferModels() {
|
||||
List<Buffer> buffers = gltf.getBuffers() == null ? Collections.emptyList() : gltf.getBuffers();
|
||||
for (int i = 0; i < buffers.size(); i++) {
|
||||
Buffer buffer = buffers.get(i);
|
||||
BufferModel bufferModel = new BufferModel();
|
||||
bufferModel.setUri(buffer.getUri());
|
||||
bufferModels.add(bufferModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void initBufferModels() {
|
||||
List<Buffer> buffers = gltf.getBuffers() == null ? Collections.emptyList() : gltf.getBuffers();
|
||||
for (int i = 0; i < buffers.size(); i++) {
|
||||
Buffer buffer = buffers.get(i);
|
||||
BufferModel bufferModel = bufferModels.get(i);
|
||||
{
|
||||
String uri = buffer.getUri();
|
||||
if (isDataUriString(uri)) {
|
||||
byte[] data = readDataUri(uri);
|
||||
ByteBuffer bufferData = Buffers.create(data);
|
||||
bufferModel.setBufferData(bufferData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createAccessorModels() {
|
||||
List<Accessor> accessors = gltf.getAccessors() == null ? Collections.emptyList() : gltf.getAccessors();
|
||||
for (Accessor accessor : accessors) {
|
||||
Integer componentType = accessor.getComponentType();
|
||||
Integer count = accessor.getCount();
|
||||
ElementType elementType = ElementType.forString(accessor.getType());
|
||||
AccessorModel accessorModel = new AccessorModel(
|
||||
componentType, count, elementType);
|
||||
accessorModels.add(accessorModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void initAccessorModels() {
|
||||
List<Accessor> accessors = gltf.getAccessors() == null ? Collections.emptyList() : gltf.getAccessors();
|
||||
for (int i = 0; i < accessors.size(); i++) {
|
||||
Accessor accessor = accessors.get(i);
|
||||
AccessorModel accessorModel = accessorModels.get(i);
|
||||
|
||||
int byteOffset = accessor.getByteOffset() == null ? 0 : accessor.getByteOffset();
|
||||
accessorModel.setByteOffset(byteOffset);
|
||||
|
||||
AccessorSparse accessorSparse = accessor.getSparse();
|
||||
if (accessorSparse == null) {
|
||||
initDenseAccessorModel(i, accessor, accessorModel);
|
||||
} else {
|
||||
initSparseAccessorModel(i, accessor, accessorModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createAnimationModels() {
|
||||
List<Animation> animations = gltf.getAnimations() == null ? Collections.emptyList() : gltf.getAnimations();
|
||||
for (int i = 0; i < animations.size(); i++) {
|
||||
animationModels.add(new AnimationModel());
|
||||
}
|
||||
}
|
||||
|
||||
private void initAnimationModels() {
|
||||
List<Animation> animations = gltf.getAnimations() == null ? Collections.emptyList() : gltf.getAnimations();
|
||||
;
|
||||
for (int i = 0; i < animations.size(); i++) {
|
||||
Animation animation = animations.get(i);
|
||||
AnimationModel animationModel = animationModels.get(i);
|
||||
animationModel.setName(animation.getName());
|
||||
List<AnimationChannel> channels =
|
||||
animation.getChannels();
|
||||
for (AnimationChannel animationChannel : channels) {
|
||||
AnimationModel.Channel channel = createChannel(animation, animationChannel);
|
||||
animationModel.addChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createBufferViewModels() {
|
||||
List<BufferView> bufferViews = gltf.getBufferViews() == null ? Collections.emptyList() : gltf.getBufferViews();
|
||||
;
|
||||
for (BufferView bufferView : bufferViews) {
|
||||
BufferViewModel bufferViewModel =
|
||||
createBufferViewModel(bufferView);
|
||||
bufferViewModels.add(bufferViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void initBufferViewModels() {
|
||||
List<BufferView> bufferViews = gltf.getBufferViews() == null ? Collections.emptyList() : gltf.getBufferViews();
|
||||
;
|
||||
for (int i = 0; i < bufferViews.size(); i++) {
|
||||
BufferView bufferView = bufferViews.get(i);
|
||||
|
||||
BufferViewModel bufferViewModel = bufferViewModels.get(i);
|
||||
|
||||
int bufferIndex = bufferView.getBuffer();
|
||||
BufferModel bufferModel = bufferModels.get(bufferIndex);
|
||||
bufferViewModel.setBufferModel(bufferModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void createNodeModels() {
|
||||
List<Node> nodes = gltf.getNodes() == null ? Collections.emptyList() : gltf.getNodes();
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
nodeModels.add(new NodeModel());
|
||||
}
|
||||
}
|
||||
|
||||
private void initNodeModels() {
|
||||
List<Node> nodes = gltf.getNodes() == null ? Collections.emptyList() : gltf.getNodes();
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
Node node = nodes.get(i);
|
||||
|
||||
NodeModel nodeModel = nodeModels.get(i);
|
||||
nodeModel.setName(node.getName());
|
||||
|
||||
List<Integer> childIndices = node.getChildren() == null ? Collections.emptyList() : node.getChildren();
|
||||
;
|
||||
for (Integer childIndex : childIndices) {
|
||||
NodeModel child = nodeModels.get(childIndex);
|
||||
nodeModel.addChild(child);
|
||||
}
|
||||
|
||||
float[] matrix = node.getMatrix();
|
||||
float[] translation = node.getTranslation();
|
||||
float[] rotation = node.getRotation();
|
||||
float[] scale = node.getScale();
|
||||
nodeModel.setMatrix(clone(matrix));
|
||||
nodeModel.setTranslation(clone(translation));
|
||||
nodeModel.setRotation(clone(rotation));
|
||||
nodeModel.setScale(clone(scale));
|
||||
}
|
||||
}
|
||||
|
||||
private AnimationModel.Channel createChannel(
|
||||
Animation animation, AnimationChannel animationChannel) {
|
||||
List<AnimationSampler> samplers = animation.getSamplers();
|
||||
|
||||
int samplerIndex = animationChannel.getSampler();
|
||||
AnimationSampler animationSampler = samplers.get(samplerIndex);
|
||||
|
||||
int inputAccessorIndex = animationSampler.getInput();
|
||||
AccessorModel inputAccessorModel =
|
||||
accessorModels.get(inputAccessorIndex);
|
||||
|
||||
int outputAccessorIndex = animationSampler.getOutput();
|
||||
AccessorModel outputAccessorModel =
|
||||
accessorModels.get(outputAccessorIndex);
|
||||
|
||||
String interpolationString =
|
||||
animationSampler.getInterpolation();
|
||||
AnimationModel.Interpolation interpolation =
|
||||
interpolationString == null ? AnimationModel.Interpolation.LINEAR :
|
||||
AnimationModel.Interpolation.valueOf(interpolationString);
|
||||
|
||||
AnimationModel.Sampler sampler = new AnimationModel.Sampler(
|
||||
inputAccessorModel, interpolation, outputAccessorModel);
|
||||
|
||||
AnimationChannelTarget animationChannelTarget =
|
||||
animationChannel.getTarget();
|
||||
|
||||
Integer nodeIndex = animationChannelTarget.getNode();
|
||||
NodeModel nodeModel = null;
|
||||
if (nodeIndex != null) {
|
||||
nodeModel = nodeModels.get(nodeIndex);
|
||||
}
|
||||
String path = animationChannelTarget.getPath();
|
||||
|
||||
return new AnimationModel.Channel(sampler, nodeModel, path);
|
||||
}
|
||||
|
||||
private void initDenseAccessorModel(int accessorIndex,
|
||||
Accessor accessor, AccessorModel accessorModel) {
|
||||
Integer bufferViewIndex = accessor.getBufferView();
|
||||
if (bufferViewIndex != null) {
|
||||
// When there is a BufferView referenced from the accessor, then
|
||||
// the corresponding BufferViewModel may be assigned directly
|
||||
BufferViewModel bufferViewModel =
|
||||
bufferViewModels.get(bufferViewIndex);
|
||||
accessorModel.setBufferViewModel(bufferViewModel);
|
||||
} else {
|
||||
// When there is no BufferView referenced from the accessor,
|
||||
// then a NEW BufferViewModel (and Buffer) have to be created
|
||||
int count = accessorModel.getCount();
|
||||
int elementSizeInBytes = accessorModel.getElementSizeInBytes();
|
||||
int byteLength = elementSizeInBytes * count;
|
||||
ByteBuffer bufferData = Buffers.create(byteLength);
|
||||
String uriString = "buffer_for_accessor" + accessorIndex + ".bin";
|
||||
BufferViewModel bufferViewModel =
|
||||
createBufferViewModel(uriString, bufferData);
|
||||
accessorModel.setBufferViewModel(bufferViewModel);
|
||||
}
|
||||
|
||||
BufferViewModel bufferViewModel = accessorModel.getBufferViewModel();
|
||||
Integer byteStride = bufferViewModel.getByteStride();
|
||||
if (byteStride == null) {
|
||||
accessorModel.setByteStride(
|
||||
accessorModel.getElementSizeInBytes());
|
||||
} else {
|
||||
accessorModel.setByteStride(byteStride);
|
||||
}
|
||||
}
|
||||
|
||||
private void initSparseAccessorModel(int accessorIndex,
|
||||
Accessor accessor, AccessorModel accessorModel) {
|
||||
// When the (sparse!) Accessor already refers to a BufferView,
|
||||
// then this BufferView has to be replaced with a new one,
|
||||
// to which the data substitution will be applied
|
||||
int count = accessorModel.getCount();
|
||||
int elementSizeInBytes = accessorModel.getElementSizeInBytes();
|
||||
int byteLength = elementSizeInBytes * count;
|
||||
ByteBuffer bufferData = Buffers.create(byteLength);
|
||||
String uriString = "buffer_for_accessor" + accessorIndex + ".bin";
|
||||
BufferViewModel denseBufferViewModel =
|
||||
createBufferViewModel(uriString, bufferData);
|
||||
accessorModel.setBufferViewModel(denseBufferViewModel);
|
||||
accessorModel.setByteOffset(0);
|
||||
|
||||
Integer bufferViewIndex = accessor.getBufferView();
|
||||
if (bufferViewIndex != null) {
|
||||
// If the accessor refers to a BufferView, then the corresponding
|
||||
// data serves as the basis for the initialization of the values,
|
||||
// before the sparse substitution is applied
|
||||
Consumer<ByteBuffer> sparseSubstitutionCallback = denseByteBuffer ->
|
||||
{
|
||||
BufferViewModel baseBufferViewModel =
|
||||
bufferViewModels.get(bufferViewIndex);
|
||||
ByteBuffer baseBufferViewData =
|
||||
baseBufferViewModel.getBufferViewData();
|
||||
AccessorData baseAccessorData = AccessorDatas.create(
|
||||
accessorModel, baseBufferViewData);
|
||||
AccessorData denseAccessorData =
|
||||
AccessorDatas.create(accessorModel, bufferData);
|
||||
substituteSparseAccessorData(accessor, accessorModel,
|
||||
denseAccessorData, baseAccessorData);
|
||||
};
|
||||
denseBufferViewModel.setSparseSubstitutionCallback(
|
||||
sparseSubstitutionCallback);
|
||||
} else {
|
||||
// When the sparse accessor does not yet refer to a BufferView,
|
||||
// then a new one is created,
|
||||
Consumer<ByteBuffer> sparseSubstitutionCallback = denseByteBuffer ->
|
||||
{
|
||||
AccessorData denseAccessorData =
|
||||
AccessorDatas.create(accessorModel, bufferData);
|
||||
substituteSparseAccessorData(accessor, accessorModel,
|
||||
denseAccessorData, null);
|
||||
};
|
||||
denseBufferViewModel.setSparseSubstitutionCallback(
|
||||
sparseSubstitutionCallback);
|
||||
}
|
||||
}
|
||||
|
||||
private void substituteSparseAccessorData(
|
||||
Accessor accessor, AccessorModel accessorModel,
|
||||
AccessorData denseAccessorData, AccessorData baseAccessorData) {
|
||||
AccessorSparse accessorSparse = accessor.getSparse();
|
||||
int count = accessorSparse.getCount();
|
||||
|
||||
AccessorSparseIndices accessorSparseIndices =
|
||||
accessorSparse.getIndices();
|
||||
AccessorData sparseIndicesAccessorData =
|
||||
createSparseIndicesAccessorData(accessorSparseIndices, count);
|
||||
|
||||
AccessorSparseValues accessorSparseValues = accessorSparse.getValues();
|
||||
ElementType elementType = accessorModel.getElementType();
|
||||
AccessorData sparseValuesAccessorData =
|
||||
createSparseValuesAccessorData(accessorSparseValues,
|
||||
accessorModel.getComponentType(),
|
||||
elementType.getNumComponents(), count);
|
||||
|
||||
AccessorSparseUtils.substituteAccessorData(
|
||||
denseAccessorData,
|
||||
baseAccessorData,
|
||||
sparseIndicesAccessorData,
|
||||
sparseValuesAccessorData);
|
||||
}
|
||||
|
||||
private AccessorData createSparseIndicesAccessorData(
|
||||
AccessorSparseIndices accessorSparseIndices, int count) {
|
||||
Integer componentType = accessorSparseIndices.getComponentType();
|
||||
Integer bufferViewIndex = accessorSparseIndices.getBufferView();
|
||||
BufferViewModel bufferViewModel = bufferViewModels.get(bufferViewIndex);
|
||||
ByteBuffer bufferViewData = bufferViewModel.getBufferViewData();
|
||||
int byteOffset = accessorSparseIndices.getByteOffset() == null ? 0 : accessorSparseIndices.getByteOffset();
|
||||
return AccessorDatas.create(
|
||||
componentType, bufferViewData, byteOffset, count, 1, null);
|
||||
}
|
||||
|
||||
private AccessorData createSparseValuesAccessorData(
|
||||
AccessorSparseValues accessorSparseValues,
|
||||
int componentType, int numComponentsPerElement, int count) {
|
||||
Integer bufferViewIndex = accessorSparseValues.getBufferView();
|
||||
BufferViewModel bufferViewModel = bufferViewModels.get(bufferViewIndex);
|
||||
ByteBuffer bufferViewData = bufferViewModel.getBufferViewData();
|
||||
int byteOffset = accessorSparseValues.getByteOffset() == null ? 0 : accessorSparseValues.getByteOffset();
|
||||
return AccessorDatas.create(
|
||||
componentType, bufferViewData, byteOffset, count,
|
||||
numComponentsPerElement, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.tacz.guns.client.animation.gltf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class BufferModel {
|
||||
/**
|
||||
* The URI of the buffer data
|
||||
*/
|
||||
private String uri;
|
||||
|
||||
/**
|
||||
* The actual data of the buffer
|
||||
*/
|
||||
private ByteBuffer bufferData;
|
||||
|
||||
public String getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URI for the buffer data
|
||||
*
|
||||
* @param uri The URI of the buffer data
|
||||
*/
|
||||
public void setUri(String uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public int getByteLength() {
|
||||
return bufferData.capacity();
|
||||
}
|
||||
|
||||
public ByteBuffer getBufferData() {
|
||||
return Buffers.createSlice(bufferData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data of this buffer
|
||||
*
|
||||
* @param bufferData The buffer data
|
||||
*/
|
||||
public void setBufferData(ByteBuffer bufferData) {
|
||||
this.bufferData = bufferData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.tacz.guns.client.animation.gltf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BufferViewModel {
|
||||
/**
|
||||
* The optional target
|
||||
*/
|
||||
private final Integer target;
|
||||
/**
|
||||
* The {@link BufferModel} for this model
|
||||
*/
|
||||
private BufferModel bufferModel;
|
||||
/**
|
||||
* The byte offset
|
||||
*/
|
||||
private int byteOffset;
|
||||
/**
|
||||
* The byte length
|
||||
*/
|
||||
private int byteLength;
|
||||
/**
|
||||
* The byte stride
|
||||
*/
|
||||
private Integer byteStride;
|
||||
/**
|
||||
* An optional callback that will be used to perform the
|
||||
* substitution of sparse accessor data in the
|
||||
* {@link #getBufferViewData() buffer view data}
|
||||
* when it is obtained for the first time.
|
||||
*/
|
||||
private Consumer<? super ByteBuffer> sparseSubstitutionCallback;
|
||||
|
||||
/**
|
||||
* Whether the sparse substitution was already applied
|
||||
*/
|
||||
private boolean sparseSubstitutionApplied;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param target The optional target
|
||||
*/
|
||||
public BufferViewModel(Integer target) {
|
||||
this.byteOffset = 0;
|
||||
this.byteLength = 0;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the callback that will perform the substitution of sparse accessor
|
||||
* data in the {@link #getBufferViewData() buffer view data} when it is
|
||||
* obtained for the first time.
|
||||
*
|
||||
* @param sparseSubstitutionCallback The callback
|
||||
*/
|
||||
public void setSparseSubstitutionCallback(
|
||||
Consumer<? super ByteBuffer> sparseSubstitutionCallback) {
|
||||
this.sparseSubstitutionCallback = sparseSubstitutionCallback;
|
||||
}
|
||||
|
||||
public ByteBuffer getBufferViewData() {
|
||||
ByteBuffer bufferData = bufferModel.getBufferData();
|
||||
ByteBuffer bufferViewData =
|
||||
Buffers.createSlice(bufferData, getByteOffset(), getByteLength());
|
||||
if (sparseSubstitutionCallback != null && !sparseSubstitutionApplied) {
|
||||
sparseSubstitutionCallback.accept(bufferViewData);
|
||||
sparseSubstitutionApplied = true;
|
||||
}
|
||||
return bufferViewData;
|
||||
}
|
||||
|
||||
public BufferModel getBufferModel() {
|
||||
return bufferModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link BufferModel} for this model
|
||||
*
|
||||
* @param bufferModel The {@link BufferModel}
|
||||
*/
|
||||
public void setBufferModel(BufferModel bufferModel) {
|
||||
this.bufferModel = bufferModel;
|
||||
}
|
||||
|
||||
public int getByteOffset() {
|
||||
return byteOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the byte offset of this view referring to its {@link BufferModel}
|
||||
*
|
||||
* @param byteOffset The byte offset
|
||||
*/
|
||||
public void setByteOffset(int byteOffset) {
|
||||
this.byteOffset = byteOffset;
|
||||
}
|
||||
|
||||
public int getByteLength() {
|
||||
return byteLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the byte length of this buffer view
|
||||
*
|
||||
* @param byteLength The byte length
|
||||
*/
|
||||
public void setByteLength(int byteLength) {
|
||||
this.byteLength = byteLength;
|
||||
}
|
||||
|
||||
public Integer getByteStride() {
|
||||
return byteStride;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the optional byte stride. This byte stride must be
|
||||
* non-<code>null</code> if more than one accessor refers
|
||||
* to this buffer view.
|
||||
*
|
||||
* @param byteStride The byte stride
|
||||
*/
|
||||
public void setByteStride(Integer byteStride) {
|
||||
this.byteStride = byteStride;
|
||||
}
|
||||
|
||||
public Integer getTarget() {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
138
src/main/java/com/tacz/guns/client/animation/gltf/Buffers.java
Normal file
138
src/main/java/com/tacz/guns/client/animation/gltf/Buffers.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* www.javagl.de - JglTF
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Utility methods related to buffers
|
||||
*/
|
||||
public class Buffers {
|
||||
/**
|
||||
* Private constructor to prevent instantiation
|
||||
*/
|
||||
private Buffers() {
|
||||
// Private constructor to prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a slice of the given byte buffer, using its current position
|
||||
* and limit. The returned slice will have the same byte order as the
|
||||
* given buffer. If the given buffer is <code>null</code>, then
|
||||
* <code>null</code> will be returned.
|
||||
*
|
||||
* @param byteBuffer The byte buffer
|
||||
* @return The slice
|
||||
*/
|
||||
public static ByteBuffer createSlice(ByteBuffer byteBuffer) {
|
||||
if (byteBuffer == null) {
|
||||
return null;
|
||||
}
|
||||
return byteBuffer.slice().order(byteBuffer.order());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a slice of the given byte buffer, in the specified range.
|
||||
* The returned buffer will have the same byte order as the given
|
||||
* buffer. If the given buffer is <code>null</code>, then
|
||||
* <code>null</code> will be returned.
|
||||
*
|
||||
* @param byteBuffer The byte buffer
|
||||
* @param position The position where the slice should start
|
||||
* @param length The length of the slice
|
||||
* @return The slice
|
||||
* @throws IllegalArgumentException If the range that is specified
|
||||
* by the position and length are not valid for the given buffer
|
||||
*/
|
||||
public static ByteBuffer createSlice(
|
||||
ByteBuffer byteBuffer, int position, int length) {
|
||||
if (byteBuffer == null) {
|
||||
return null;
|
||||
}
|
||||
int oldPosition = byteBuffer.position();
|
||||
int oldLimit = byteBuffer.limit();
|
||||
try {
|
||||
int newLimit = position + length;
|
||||
if (newLimit > byteBuffer.capacity()) {
|
||||
throw new IllegalArgumentException(
|
||||
"The new limit is " + newLimit + ", but the capacity is "
|
||||
+ byteBuffer.capacity());
|
||||
}
|
||||
byteBuffer.limit(newLimit);
|
||||
byteBuffer.position(position);
|
||||
ByteBuffer slice = byteBuffer.slice();
|
||||
slice.order(byteBuffer.order());
|
||||
return slice;
|
||||
} finally {
|
||||
byteBuffer.limit(oldLimit);
|
||||
byteBuffer.position(oldPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new, direct byte buffer that contains the given data,
|
||||
* with little-endian byte order
|
||||
*
|
||||
* @param data The data
|
||||
* @return The byte buffer
|
||||
*/
|
||||
public static ByteBuffer create(byte data[]) {
|
||||
return create(data, 0, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new, direct byte buffer that contains the specified range
|
||||
* of the given data, with little-endian byte order
|
||||
*
|
||||
* @param data The data
|
||||
* @param offset The offset in the data array
|
||||
* @param length The length of the range
|
||||
* @return The byte buffer
|
||||
*/
|
||||
public static ByteBuffer create(byte data[], int offset, int length) {
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(length);
|
||||
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
byteBuffer.put(data, offset, length);
|
||||
byteBuffer.position(0);
|
||||
return byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new direct byte buffer with the given size, and little-endian
|
||||
* byte order.
|
||||
*
|
||||
* @param size The size of the buffer
|
||||
* @return The byte buffer
|
||||
* @throws IllegalArgumentException If the given size is negative
|
||||
*/
|
||||
public static ByteBuffer create(int size) {
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size);
|
||||
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
return byteBuffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.tacz.guns.client.animation.gltf;
|
||||
|
||||
public enum ElementType {
|
||||
/**
|
||||
* The scalar type
|
||||
*/
|
||||
SCALAR(1),
|
||||
|
||||
/**
|
||||
* The 2D vector type
|
||||
*/
|
||||
VEC2(2),
|
||||
|
||||
/**
|
||||
* The 3D vector type
|
||||
*/
|
||||
VEC3(3),
|
||||
|
||||
/**
|
||||
* The 4D vector type
|
||||
*/
|
||||
VEC4(4),
|
||||
|
||||
/**
|
||||
* The 2x2 matrix type
|
||||
*/
|
||||
MAT2(4),
|
||||
|
||||
/**
|
||||
* The 3x3 matrix type
|
||||
*/
|
||||
MAT3(9),
|
||||
|
||||
/**
|
||||
* The 4x4 matrix type
|
||||
*/
|
||||
MAT4(16);
|
||||
|
||||
/**
|
||||
* The number of components that one element consists of
|
||||
*/
|
||||
private final int numComponents;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given number of components
|
||||
*
|
||||
* @param numComponents The number of components
|
||||
*/
|
||||
ElementType(int numComponents) {
|
||||
this.numComponents = numComponents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given string is a valid element type name, and may be
|
||||
* passed to <code>ElementType.valueOf</code> without causing an exception.
|
||||
*
|
||||
* @param s The string
|
||||
* @return Whether the given string is a valid element type
|
||||
*/
|
||||
public static boolean contains(String s) {
|
||||
for (ElementType elementType : values()) {
|
||||
if (elementType.name().equals(s)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element type for the given string. If the string is
|
||||
* <code>null</code> or does not describe a valid element type,
|
||||
* then <code>null</code> is returned
|
||||
*
|
||||
* @param string The string
|
||||
* @return The element type
|
||||
*/
|
||||
public static ElementType forString(String string) {
|
||||
if (string == null) {
|
||||
return null;
|
||||
}
|
||||
if (!contains(string)) {
|
||||
return null;
|
||||
}
|
||||
return ElementType.valueOf(string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of components that one element consists of
|
||||
*
|
||||
* @return The number of components
|
||||
*/
|
||||
public int getNumComponents() {
|
||||
return numComponents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,687 @@
|
||||
/*
|
||||
* www.javagl.de - JglTF
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf;
|
||||
|
||||
|
||||
public class GltfConstants {
|
||||
/**
|
||||
* The GL_BGR constant (32992)
|
||||
*/
|
||||
public static final int GL_BGR = 32992;
|
||||
|
||||
/**
|
||||
* The GL_RGB constant (6407)
|
||||
*/
|
||||
public static final int GL_RGB = 6407;
|
||||
|
||||
/**
|
||||
* The GL_RGBA constant (6408)
|
||||
*/
|
||||
public static final int GL_RGBA = 6408;
|
||||
|
||||
/**
|
||||
* The GL_BGRA constant (32993)
|
||||
*/
|
||||
public static final int GL_BGRA = 32993;
|
||||
|
||||
/**
|
||||
* The GL_BYTE constant (5120)
|
||||
*/
|
||||
public static final int GL_BYTE = 5120;
|
||||
|
||||
/**
|
||||
* The GL_UNSIGNED_BYTE constant (5121)
|
||||
*/
|
||||
public static final int GL_UNSIGNED_BYTE = 5121;
|
||||
|
||||
/**
|
||||
* The GL_SHORT constant (5122)
|
||||
*/
|
||||
public static final int GL_SHORT = 5122;
|
||||
|
||||
/**
|
||||
* The GL_UNSIGNED_SHORT constant (5123)
|
||||
*/
|
||||
public static final int GL_UNSIGNED_SHORT = 5123;
|
||||
|
||||
/**
|
||||
* The GL_INT constant (5124)
|
||||
*/
|
||||
public static final int GL_INT = 5124;
|
||||
|
||||
/**
|
||||
* The GL_UNSIGNED_INT constant (5125)
|
||||
*/
|
||||
public static final int GL_UNSIGNED_INT = 5125;
|
||||
|
||||
/**
|
||||
* The GL_FLOAT constant (5126)
|
||||
*/
|
||||
public static final int GL_FLOAT = 5126;
|
||||
|
||||
|
||||
/**
|
||||
* The GL_FLOAT_VEC2 constant (35664)
|
||||
*/
|
||||
public static final int GL_FLOAT_VEC2 = 35664;
|
||||
|
||||
/**
|
||||
* The GL_FLOAT_VEC3 constant (35665)
|
||||
*/
|
||||
public static final int GL_FLOAT_VEC3 = 35665;
|
||||
|
||||
/**
|
||||
* The GL_FLOAT_VEC4 constant (35666)
|
||||
*/
|
||||
public static final int GL_FLOAT_VEC4 = 35666;
|
||||
|
||||
/**
|
||||
* The GL_INT_VEC2 constant (35667)
|
||||
*/
|
||||
public static final int GL_INT_VEC2 = 35667;
|
||||
|
||||
/**
|
||||
* The GL_INT_VEC3 constant (35668)
|
||||
*/
|
||||
public static final int GL_INT_VEC3 = 35668;
|
||||
|
||||
/**
|
||||
* The GL_INT_VEC4 constant (35669)
|
||||
*/
|
||||
public static final int GL_INT_VEC4 = 35669;
|
||||
|
||||
/**
|
||||
* The GL_BOOL constant (35670)
|
||||
*/
|
||||
public static final int GL_BOOL = 35670;
|
||||
|
||||
/**
|
||||
* The GL_BOOL_VEC2 constant (35671)
|
||||
*/
|
||||
public static final int GL_BOOL_VEC2 = 35671;
|
||||
|
||||
/**
|
||||
* The GL_BOOL_VEC3 constant (35672)
|
||||
*/
|
||||
public static final int GL_BOOL_VEC3 = 35672;
|
||||
|
||||
/**
|
||||
* The GL_BOOL_VEC4 constant (35673)
|
||||
*/
|
||||
public static final int GL_BOOL_VEC4 = 35673;
|
||||
|
||||
/**
|
||||
* The GL_FLOAT_MAT2 constant (35674)
|
||||
*/
|
||||
public static final int GL_FLOAT_MAT2 = 35674;
|
||||
|
||||
/**
|
||||
* The GL_FLOAT_MAT3 constant (35675)
|
||||
*/
|
||||
public static final int GL_FLOAT_MAT3 = 35675;
|
||||
|
||||
/**
|
||||
* The GL_FLOAT_MAT4 constant (35676)
|
||||
*/
|
||||
public static final int GL_FLOAT_MAT4 = 35676;
|
||||
|
||||
/**
|
||||
* The GL_SAMPLER_2D constant (35678)
|
||||
*/
|
||||
public static final int GL_SAMPLER_2D = 35678;
|
||||
|
||||
/**
|
||||
* The GL_SAMPLER_CUBE constant (35680)
|
||||
*/
|
||||
public static final int GL_SAMPLER_CUBE = 35680;
|
||||
|
||||
|
||||
/**
|
||||
* The GL_POINTS constant (0)
|
||||
*/
|
||||
public static final int GL_POINTS = 0;
|
||||
|
||||
/**
|
||||
* The GL_LINES constant (1)
|
||||
*/
|
||||
public static final int GL_LINES = 1;
|
||||
|
||||
/**
|
||||
* The GL_LINE_LOOP constant (2)
|
||||
*/
|
||||
public static final int GL_LINE_LOOP = 2;
|
||||
|
||||
/**
|
||||
* The GL_LINE_STRIP constant (3)
|
||||
*/
|
||||
public static final int GL_LINE_STRIP = 3;
|
||||
|
||||
/**
|
||||
* The GL_TRIANGLES constant (4)
|
||||
*/
|
||||
public static final int GL_TRIANGLES = 4;
|
||||
|
||||
/**
|
||||
* The GL_TRIANGLE_STRIP constant (5)
|
||||
*/
|
||||
public static final int GL_TRIANGLE_STRIP = 5;
|
||||
|
||||
/**
|
||||
* The GL_TRIANGLE_FAN constant (6)
|
||||
*/
|
||||
public static final int GL_TRIANGLE_FAN = 6;
|
||||
|
||||
|
||||
/**
|
||||
* The GL_VERTEX_SHADER constant (35633)
|
||||
*/
|
||||
public static final int GL_VERTEX_SHADER = 35633;
|
||||
|
||||
/**
|
||||
* The GL_VERTEX_SHADER constant (35632)
|
||||
*/
|
||||
public static final int GL_FRAGMENT_SHADER = 35632;
|
||||
|
||||
|
||||
/**
|
||||
* The GL_TEXTURE_2D constant (3553)
|
||||
*/
|
||||
public static final int GL_TEXTURE_2D = 3553;
|
||||
|
||||
|
||||
/**
|
||||
* The GL_ARRAY_BUFFER constant (34962)
|
||||
*/
|
||||
public static final int GL_ARRAY_BUFFER = 34962;
|
||||
|
||||
/**
|
||||
* The GL_ELEMENT_ARRAY_BUFFER constant (34963)
|
||||
*/
|
||||
public static final int GL_ELEMENT_ARRAY_BUFFER = 34963;
|
||||
|
||||
|
||||
// glEnable for technique.states
|
||||
|
||||
/**
|
||||
* The GL_BLEND constant (3042)
|
||||
*/
|
||||
public static final int GL_BLEND = 3042;
|
||||
|
||||
/**
|
||||
* The GL_CULL_FACE constant (2884)
|
||||
*/
|
||||
public static final int GL_CULL_FACE = 2884;
|
||||
|
||||
/**
|
||||
* The GL_DEPTH_TEST constant (2929)
|
||||
*/
|
||||
public static final int GL_DEPTH_TEST = 2929;
|
||||
|
||||
/**
|
||||
* The GL_POLYGON_OFFSET_FILL constant (32823)
|
||||
*/
|
||||
public static final int GL_POLYGON_OFFSET_FILL = 32823;
|
||||
|
||||
/**
|
||||
* The GL_SAMPLE_ALPHA_TO_COVERAGE constant (32926)
|
||||
*/
|
||||
public static final int GL_SAMPLE_ALPHA_TO_COVERAGE = 32926;
|
||||
|
||||
/**
|
||||
* The GL_SCISSOR_TEST constant (3089)
|
||||
*/
|
||||
public static final int GL_SCISSOR_TEST = 3089;
|
||||
|
||||
// glBlendEquationSeparate
|
||||
|
||||
/**
|
||||
* The GL_FUNC_ADD constant (32774)
|
||||
*/
|
||||
public static final int GL_FUNC_ADD = 32774;
|
||||
|
||||
/**
|
||||
* The GL_FUNC_SUBTRACT constant (32778)
|
||||
*/
|
||||
public static final int GL_FUNC_SUBTRACT = 32778;
|
||||
|
||||
/**
|
||||
* The GL_FUNC_REVERSE_SUBTRACT constant (32779)
|
||||
*/
|
||||
public static final int GL_FUNC_REVERSE_SUBTRACT = 32779;
|
||||
|
||||
// glBlendFuncSeparate
|
||||
|
||||
/**
|
||||
* The GL_ZERO constant (0)
|
||||
*/
|
||||
public static final int GL_ZERO = 0;
|
||||
|
||||
/**
|
||||
* The GL_ONE constant (1)
|
||||
*/
|
||||
public static final int GL_ONE = 1;
|
||||
|
||||
/**
|
||||
* The GL_SRC_COLOR constant (768)
|
||||
*/
|
||||
public static final int GL_SRC_COLOR = 768;
|
||||
|
||||
/**
|
||||
* The GL_ONE_MINUS_SRC_COLOR constant (769)
|
||||
*/
|
||||
public static final int GL_ONE_MINUS_SRC_COLOR = 769;
|
||||
|
||||
/**
|
||||
* The GL_DST_COLOR constant (774)
|
||||
*/
|
||||
public static final int GL_DST_COLOR = 774;
|
||||
|
||||
/**
|
||||
* The GL_ONE_MINUS_DST_COLOR constant (775)
|
||||
*/
|
||||
public static final int GL_ONE_MINUS_DST_COLOR = 775;
|
||||
|
||||
/**
|
||||
* The GL_SRC_ALPHA constant (770)
|
||||
*/
|
||||
public static final int GL_SRC_ALPHA = 770;
|
||||
|
||||
/**
|
||||
* The GL_ONE_MINUS_SRC_ALPHA constant (771)
|
||||
*/
|
||||
public static final int GL_ONE_MINUS_SRC_ALPHA = 771;
|
||||
|
||||
/**
|
||||
* The GL_DST_ALPHA constant (772)
|
||||
*/
|
||||
public static final int GL_DST_ALPHA = 772;
|
||||
|
||||
/**
|
||||
* The GL_ONE_MINUS_DST_ALPHA constant (773)
|
||||
*/
|
||||
public static final int GL_ONE_MINUS_DST_ALPHA = 773;
|
||||
|
||||
/**
|
||||
* The GL_CONSTANT_COLOR constant (32769)
|
||||
*/
|
||||
public static final int GL_CONSTANT_COLOR = 32769;
|
||||
|
||||
/**
|
||||
* The GL_ONE_MINUS_CONSTANT_COLOR constant (32770)
|
||||
*/
|
||||
public static final int GL_ONE_MINUS_CONSTANT_COLOR = 32770;
|
||||
|
||||
/**
|
||||
* The GL_CONSTANT_ALPHA constant (32771)
|
||||
*/
|
||||
public static final int GL_CONSTANT_ALPHA = 32771;
|
||||
|
||||
/**
|
||||
* The GL_ONE_MINUS_CONSTANT_ALPHA constant (32772)
|
||||
*/
|
||||
public static final int GL_ONE_MINUS_CONSTANT_ALPHA = 32772;
|
||||
|
||||
/**
|
||||
* The GL_SRC_ALPHA_SATURATE constant (776)
|
||||
*/
|
||||
public static final int GL_SRC_ALPHA_SATURATE = 776;
|
||||
|
||||
// glCullFace
|
||||
|
||||
/**
|
||||
* The GL_FRONT constant (1028)
|
||||
*/
|
||||
public static final int GL_FRONT = 1028;
|
||||
|
||||
/**
|
||||
* The GL_BACK constant (1029)
|
||||
*/
|
||||
public static final int GL_BACK = 1029;
|
||||
|
||||
/**
|
||||
* The GL_FRONT_AND_BACK constant (1032)
|
||||
*/
|
||||
public static final int GL_FRONT_AND_BACK = 1032;
|
||||
|
||||
// glDepthFunc
|
||||
|
||||
/**
|
||||
* The GL_NEVER constant (512)
|
||||
*/
|
||||
public static final int GL_NEVER = 512;
|
||||
|
||||
/**
|
||||
* The GL_LESS constant (513)
|
||||
*/
|
||||
public static final int GL_LESS = 513;
|
||||
|
||||
/**
|
||||
* The GL_LEQUAL constant (515)
|
||||
*/
|
||||
public static final int GL_LEQUAL = 515;
|
||||
|
||||
/**
|
||||
* The GL_EQUAL constant (514)
|
||||
*/
|
||||
public static final int GL_EQUAL = 514;
|
||||
|
||||
/**
|
||||
* The GL_GREATER constant (516)
|
||||
*/
|
||||
public static final int GL_GREATER = 516;
|
||||
|
||||
/**
|
||||
* The GL_NOTEQUAL constant (517)
|
||||
*/
|
||||
public static final int GL_NOTEQUAL = 517;
|
||||
|
||||
/**
|
||||
* The GL_GEQUAL constant (518)
|
||||
*/
|
||||
public static final int GL_GEQUAL = 518;
|
||||
|
||||
/**
|
||||
* The GL_ALWAYS constant (519)
|
||||
*/
|
||||
public static final int GL_ALWAYS = 519;
|
||||
|
||||
// glFrontFace
|
||||
|
||||
/**
|
||||
* The GL_CW constant (2304)
|
||||
*/
|
||||
public static final int GL_CW = 2304;
|
||||
|
||||
/**
|
||||
* The GL_CCW constant (2305)
|
||||
*/
|
||||
public static final int GL_CCW = 2305;
|
||||
|
||||
|
||||
// glTexParameter
|
||||
|
||||
/**
|
||||
* The GL_NEAREST constant (9728)
|
||||
*/
|
||||
public static final int GL_NEAREST = 9728;
|
||||
|
||||
/**
|
||||
* The GL_LINEAR constant (9729)
|
||||
*/
|
||||
public static final int GL_LINEAR = 9729;
|
||||
|
||||
/**
|
||||
* The GL_NEAREST_MIPMAP_NEAREST constant (9984)
|
||||
*/
|
||||
public static final int GL_NEAREST_MIPMAP_NEAREST = 9984;
|
||||
|
||||
/**
|
||||
* The GL_LINEAR_MIPMAP_NEAREST constant (9985)
|
||||
*/
|
||||
public static final int GL_LINEAR_MIPMAP_NEAREST = 9985;
|
||||
|
||||
/**
|
||||
* The GL_NEAREST_MIPMAP_LINEAR constant (9986)
|
||||
*/
|
||||
public static final int GL_NEAREST_MIPMAP_LINEAR = 9986;
|
||||
|
||||
/**
|
||||
* The GL_LINEAR_MIPMAP_LINEAR constant (9987)
|
||||
*/
|
||||
public static final int GL_LINEAR_MIPMAP_LINEAR = 9987;
|
||||
|
||||
// glSamplerParameter
|
||||
|
||||
/**
|
||||
* The GL_REPEAT constant (10497)
|
||||
*/
|
||||
public static final int GL_REPEAT = 10497;
|
||||
|
||||
/**
|
||||
* The GL_MIRRORED_REPEAT constant (33648)
|
||||
*/
|
||||
public static final int GL_MIRRORED_REPEAT = 33648;
|
||||
|
||||
/**
|
||||
* The GL_CLAMP_TO_EDGE constant (33071)
|
||||
*/
|
||||
public static final int GL_CLAMP_TO_EDGE = 33071;
|
||||
|
||||
/**
|
||||
* The GL_CLAMP_TO_BORDER constant (33069)
|
||||
*/
|
||||
public static final int GL_CLAMP_TO_BORDER = 33069;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation
|
||||
*/
|
||||
private GltfConstants() {
|
||||
// Private constructor to prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the String representation of the given constant
|
||||
*
|
||||
* @param constant The constant
|
||||
* @return The String for the constant
|
||||
*/
|
||||
public static String stringFor(int constant) {
|
||||
switch (constant) {
|
||||
case GL_BGR:
|
||||
return "GL_BGR";
|
||||
case GL_RGB:
|
||||
return "GL_RGB";
|
||||
case GL_RGBA:
|
||||
return "GL_RGBA";
|
||||
case GL_BGRA:
|
||||
return "GL_BGRA";
|
||||
|
||||
case GL_BYTE:
|
||||
return "GL_BYTE";
|
||||
case GL_UNSIGNED_BYTE:
|
||||
return "GL_UNSIGNED_BYTE";
|
||||
case GL_SHORT:
|
||||
return "GL_SHORT";
|
||||
case GL_UNSIGNED_SHORT:
|
||||
return "GL_UNSIGNED_SHORT";
|
||||
case GL_INT:
|
||||
return "GL_INT";
|
||||
case GL_UNSIGNED_INT:
|
||||
return "GL_UNSIGNED_INT";
|
||||
case GL_FLOAT:
|
||||
return "GL_FLOAT";
|
||||
|
||||
case GL_FLOAT_VEC2:
|
||||
return "GL_FLOAT_VEC2";
|
||||
case GL_FLOAT_VEC3:
|
||||
return "GL_FLOAT_VEC3";
|
||||
case GL_FLOAT_VEC4:
|
||||
return "GL_FLOAT_VEC4";
|
||||
case GL_INT_VEC2:
|
||||
return "GL_INT_VEC2";
|
||||
case GL_INT_VEC3:
|
||||
return "GL_INT_VEC3";
|
||||
case GL_INT_VEC4:
|
||||
return "GL_INT_VEC4";
|
||||
case GL_BOOL:
|
||||
return "GL_BOOL";
|
||||
case GL_BOOL_VEC2:
|
||||
return "GL_BOOL_VEC2";
|
||||
case GL_BOOL_VEC3:
|
||||
return "GL_BOOL_VEC3";
|
||||
case GL_BOOL_VEC4:
|
||||
return "GL_BOOL_VEC4";
|
||||
case GL_FLOAT_MAT2:
|
||||
return "GL_FLOAT_MAT2";
|
||||
case GL_FLOAT_MAT3:
|
||||
return "GL_FLOAT_MAT3";
|
||||
case GL_FLOAT_MAT4:
|
||||
return "GL_FLOAT_MAT4";
|
||||
case GL_SAMPLER_2D:
|
||||
return "GL_SAMPLER_2D";
|
||||
|
||||
case GL_POINTS:
|
||||
return "GL_ZERO or GL_POINTS";
|
||||
case GL_LINES:
|
||||
return "GL_ONE or GL_LINES";
|
||||
case GL_LINE_LOOP:
|
||||
return "GL_LINE_LOOP";
|
||||
case GL_LINE_STRIP:
|
||||
return "GL_LINE_STRIP";
|
||||
case GL_TRIANGLES:
|
||||
return "GL_TRIANGLES";
|
||||
case GL_TRIANGLE_STRIP:
|
||||
return "GL_TRIANGLE_STRIP";
|
||||
|
||||
case GL_VERTEX_SHADER:
|
||||
return "GL_VERTEX_SHADER";
|
||||
case GL_FRAGMENT_SHADER:
|
||||
return "GL_FRAGMENT_SHADER";
|
||||
|
||||
case GL_TEXTURE_2D:
|
||||
return "GL_TEXTURE_2D";
|
||||
|
||||
case GL_ARRAY_BUFFER:
|
||||
return "GL_ARRAY_BUFFER";
|
||||
case GL_ELEMENT_ARRAY_BUFFER:
|
||||
return "GL_ELEMENT_ARRAY_BUFFER";
|
||||
|
||||
// glEnable for technique.states
|
||||
case GL_BLEND:
|
||||
return "GL_BLEND";
|
||||
case GL_CULL_FACE:
|
||||
return "GL_CULL_FACE";
|
||||
case GL_DEPTH_TEST:
|
||||
return "GL_DEPTH_TEST";
|
||||
case GL_POLYGON_OFFSET_FILL:
|
||||
return "GL_POLYGON_OFFSET_FILL";
|
||||
case GL_SAMPLE_ALPHA_TO_COVERAGE:
|
||||
return "GL_SAMPLE_ALPHA_TO_COVERAGE";
|
||||
case GL_SCISSOR_TEST:
|
||||
return "GL_SCISSOR_TEST";
|
||||
|
||||
// glBlendEquationSeparate
|
||||
case GL_FUNC_ADD:
|
||||
return "GL_FUNC_ADD";
|
||||
case GL_FUNC_SUBTRACT:
|
||||
return "GL_FUNC_SUBTRACT";
|
||||
case GL_FUNC_REVERSE_SUBTRACT:
|
||||
return "GL_FUNC_REVERSE_SUBTRACT";
|
||||
|
||||
// glBlendFuncSeparate
|
||||
//case GL_ZERO: return "GL_ZERO"; // see GL_POINTS
|
||||
//case GL_ONE: return "GL_ONE"; // see GL_LINES
|
||||
case GL_SRC_COLOR:
|
||||
return "GL_SRC_COLOR";
|
||||
case GL_ONE_MINUS_SRC_COLOR:
|
||||
return "GL_ONE_MINUS_SRC_COLOR";
|
||||
case GL_DST_COLOR:
|
||||
return "GL_DST_COLOR";
|
||||
case GL_ONE_MINUS_DST_COLOR:
|
||||
return "GL_ONE_MINUS_DST_COLOR";
|
||||
case GL_SRC_ALPHA:
|
||||
return "GL_SRC_ALPHA";
|
||||
case GL_ONE_MINUS_SRC_ALPHA:
|
||||
return "GL_ONE_MINUS_SRC_ALPHA";
|
||||
case GL_DST_ALPHA:
|
||||
return "GL_DST_ALPHA";
|
||||
case GL_ONE_MINUS_DST_ALPHA:
|
||||
return "GL_ONE_MINUS_DST_ALPHA";
|
||||
case GL_CONSTANT_COLOR:
|
||||
return "GL_CONSTANT_COLOR";
|
||||
case GL_ONE_MINUS_CONSTANT_COLOR:
|
||||
return "GL_ONE_MINUS_CONSTANT_COLOR";
|
||||
case GL_CONSTANT_ALPHA:
|
||||
return "GL_CONSTANT_ALPHA";
|
||||
case GL_ONE_MINUS_CONSTANT_ALPHA:
|
||||
return "GL_ONE_MINUS_CONSTANT_ALPHA";
|
||||
case GL_SRC_ALPHA_SATURATE:
|
||||
return "GL_SRC_ALPHA_SATURATE";
|
||||
|
||||
// glCullFace
|
||||
case GL_FRONT:
|
||||
return "GL_FRONT";
|
||||
case GL_BACK:
|
||||
return "GL_BACK";
|
||||
case GL_FRONT_AND_BACK:
|
||||
return "GL_FRONT_AND_BACK";
|
||||
|
||||
// glDepthFunc
|
||||
case GL_NEVER:
|
||||
return "GL_NEVER";
|
||||
case GL_LESS:
|
||||
return "GL_LESS";
|
||||
case GL_LEQUAL:
|
||||
return "GL_LEQUAL";
|
||||
case GL_EQUAL:
|
||||
return "GL_EQUAL";
|
||||
case GL_GREATER:
|
||||
return "GL_GREATER";
|
||||
case GL_NOTEQUAL:
|
||||
return "GL_NOTEQUAL";
|
||||
case GL_GEQUAL:
|
||||
return "GL_GEQUAL";
|
||||
case GL_ALWAYS:
|
||||
return "GL_ALWAYS";
|
||||
|
||||
// glFrontFace
|
||||
case GL_CW:
|
||||
return "GL_CW";
|
||||
case GL_CCW:
|
||||
return "GL_CCW";
|
||||
|
||||
// glTexParameter
|
||||
case GL_NEAREST:
|
||||
return "GL_NEAREST";
|
||||
case GL_LINEAR:
|
||||
return "GL_LINEAR";
|
||||
case GL_NEAREST_MIPMAP_NEAREST:
|
||||
return "GL_NEAREST_MIPMAP_NEAREST";
|
||||
case GL_LINEAR_MIPMAP_NEAREST:
|
||||
return "GL_LINEAR_MIPMAP_NEAREST";
|
||||
case GL_NEAREST_MIPMAP_LINEAR:
|
||||
return "GL_NEAREST_MIPMAP_LINEAR";
|
||||
case GL_LINEAR_MIPMAP_LINEAR:
|
||||
return "GL_LINEAR_MIPMAP_LINEAR";
|
||||
|
||||
// glSamplerParameter
|
||||
case GL_REPEAT:
|
||||
return "GL_REPEAT";
|
||||
case GL_MIRRORED_REPEAT:
|
||||
return "GL_MIRRORED_REPEAT";
|
||||
case GL_CLAMP_TO_EDGE:
|
||||
return "GL_CLAMP_TO_EDGE";
|
||||
case GL_CLAMP_TO_BORDER:
|
||||
return "GL_CLAMP_TO_BORDER";
|
||||
|
||||
default:
|
||||
return "UNKNOWN_GL_CONSTANT[" + constant + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.tacz.guns.client.animation.gltf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class NodeModel {
|
||||
private final List<NodeModel> children = new ArrayList<>();
|
||||
private String name;
|
||||
private float[] matrix;
|
||||
private float[] translation;
|
||||
private float[] rotation;
|
||||
private float[] scale;
|
||||
private NodeModel parent;
|
||||
|
||||
private static float[] check(float[] array, int expectedLength) {
|
||||
if (array == null) {
|
||||
return null;
|
||||
}
|
||||
if (array.length != expectedLength) {
|
||||
throw new IllegalArgumentException("Expected " + expectedLength
|
||||
+ " array elements, but found " + array.length);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public NodeModel getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public List<NodeModel> getChildren() {
|
||||
return Collections.unmodifiableList(children);
|
||||
}
|
||||
|
||||
public void addChild(NodeModel child) {
|
||||
Objects.requireNonNull(child, "The child may not be null");
|
||||
children.add(child);
|
||||
child.parent = this;
|
||||
}
|
||||
|
||||
public float[] getMatrix() {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
public void setMatrix(float[] matrix) {
|
||||
this.matrix = check(matrix, 16);
|
||||
}
|
||||
|
||||
public float[] getTranslation() {
|
||||
return translation;
|
||||
}
|
||||
|
||||
public void setTranslation(float[] translation) {
|
||||
this.translation = check(translation, 3);
|
||||
}
|
||||
|
||||
public float[] getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public void setRotation(float[] rotation) {
|
||||
this.rotation = check(rotation, 4);
|
||||
}
|
||||
|
||||
public float[] getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public void setScale(float[] scale) {
|
||||
this.scale = check(scale, 3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Package-private abstract base implementation of an {@link AccessorData}
|
||||
*/
|
||||
abstract class AbstractAccessorData implements AccessorData {
|
||||
/**
|
||||
* The component type
|
||||
*/
|
||||
private final Class<?> componentType;
|
||||
|
||||
/**
|
||||
* The byte buffer of the buffer view that the accessor
|
||||
* refers to
|
||||
*/
|
||||
private final ByteBuffer bufferViewByteBuffer;
|
||||
|
||||
/**
|
||||
* The offset for the accessor inside the byte buffer of
|
||||
* the buffer view
|
||||
*/
|
||||
private final int byteOffset;
|
||||
|
||||
/**
|
||||
* The number of elements
|
||||
*/
|
||||
private final int numElements;
|
||||
|
||||
/**
|
||||
* The number of components per element
|
||||
*/
|
||||
private final int numComponentsPerElement;
|
||||
|
||||
/**
|
||||
* The number of bytes per component
|
||||
*/
|
||||
private final int numBytesPerComponent;
|
||||
|
||||
/**
|
||||
* The stride, in number of bytes, between two consecutive elements
|
||||
*/
|
||||
private final int byteStridePerElement;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param componentType The component type
|
||||
* @param bufferViewByteBuffer The byte buffer of the buffer view
|
||||
* @param byteOffset The byte offset in the buffer view
|
||||
* @param numElements The number of elements
|
||||
* @param numComponentsPerElement The number of components per element
|
||||
* @param numBytesPerComponent The number of bytes per component
|
||||
* @param byteStride The byte stride between two elements. If this
|
||||
* is <code>null</code> or <code>0</code>, then the stride will
|
||||
* be the size of one element.
|
||||
* @throws NullPointerException If the bufferViewByteBuffer is
|
||||
* <code>null</code>
|
||||
*/
|
||||
AbstractAccessorData(Class<?> componentType,
|
||||
ByteBuffer bufferViewByteBuffer, int byteOffset,
|
||||
int numElements, int numComponentsPerElement,
|
||||
int numBytesPerComponent, Integer byteStride) {
|
||||
Objects.requireNonNull(bufferViewByteBuffer,
|
||||
"The bufferViewByteBuffer is null");
|
||||
|
||||
this.componentType = componentType;
|
||||
this.bufferViewByteBuffer = bufferViewByteBuffer;
|
||||
this.byteOffset = byteOffset;
|
||||
this.numElements = numElements;
|
||||
this.numComponentsPerElement = numComponentsPerElement;
|
||||
this.numBytesPerComponent = numBytesPerComponent;
|
||||
if (byteStride == null || byteStride == 0) {
|
||||
this.byteStridePerElement =
|
||||
numComponentsPerElement * numBytesPerComponent;
|
||||
} else {
|
||||
this.byteStridePerElement = byteStride;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Class<?> getComponentType() {
|
||||
return componentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getNumElements() {
|
||||
return numElements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getNumComponentsPerElement() {
|
||||
return numComponentsPerElement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getTotalNumComponents() {
|
||||
return numElements * numComponentsPerElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the byte in the byte buffer where the specified
|
||||
* component starts
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @return The byte index
|
||||
*/
|
||||
protected final int getByteIndex(int elementIndex, int componentIndex) {
|
||||
return byteOffset + elementIndex * byteStridePerElement + componentIndex * numBytesPerComponent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the underlying byte buffer
|
||||
*
|
||||
* @return The byte buffer
|
||||
*/
|
||||
protected final ByteBuffer getBufferViewByteBuffer() {
|
||||
return bufferViewByteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte stride per element
|
||||
*
|
||||
* @return The byte stride
|
||||
*/
|
||||
protected final int getByteStridePerElement() {
|
||||
return byteStridePerElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes per component
|
||||
*
|
||||
* @return The number of bytes per component
|
||||
*/
|
||||
protected final int getNumBytesPerComponent() {
|
||||
return numBytesPerComponent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* www.javagl.de - JglTF
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A class for accessing the data that is described by an accessor.
|
||||
* It allows accessing the byte buffer of the buffer view of the
|
||||
* accessor, depending on the accessor parameters.<br>
|
||||
* <br>
|
||||
* This data consists of several elements (for example, 3D byte vectors),
|
||||
* which consist of several components (for example, the 3 byte values).
|
||||
*/
|
||||
public final class AccessorByteData
|
||||
extends AbstractAccessorData implements AccessorData {
|
||||
/**
|
||||
* Whether the data should be interpreted as unsigned values
|
||||
*/
|
||||
private final boolean unsigned;
|
||||
|
||||
/**
|
||||
* Creates a new instance for accessing the data in the given
|
||||
* byte buffer, according to the rules described by the given
|
||||
* accessor parameters.
|
||||
*
|
||||
* @param componentType The component type
|
||||
* @param bufferViewByteBuffer The byte buffer of the buffer view
|
||||
* @param byteOffset The byte offset in the buffer view
|
||||
* @param numElements The number of elements
|
||||
* @param numComponentsPerElement The number of components per element
|
||||
* @param byteStride The byte stride between two elements. If this
|
||||
* is <code>null</code> or <code>0</code>, then the stride will
|
||||
* be the size of one element.
|
||||
* @throws NullPointerException If the bufferViewByteBuffer is
|
||||
* <code>null</code>
|
||||
* @throws IllegalArgumentException If the component type is not
|
||||
* <code>GL_BYTE</code> or <code>GL_UNSIGEND_BYTE</code>
|
||||
* @throws IllegalArgumentException If the given byte buffer does not
|
||||
* have a sufficient capacity to provide the data for the accessor
|
||||
*/
|
||||
public AccessorByteData(int componentType,
|
||||
ByteBuffer bufferViewByteBuffer, int byteOffset, int numElements,
|
||||
int numComponentsPerElement, Integer byteStride) {
|
||||
super(byte.class, bufferViewByteBuffer, byteOffset, numElements,
|
||||
numComponentsPerElement, Byte.BYTES, byteStride);
|
||||
AccessorDatas.validateByteType(componentType);
|
||||
this.unsigned = AccessorDatas.isUnsignedType(componentType);
|
||||
AccessorDatas.validateCapacity(byteOffset, getNumElements(),
|
||||
getByteStridePerElement(), bufferViewByteBuffer.capacity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the data should be interpreted as unsigned
|
||||
*
|
||||
* @return Whether the data should be interpreted as unsigned
|
||||
*/
|
||||
public boolean isUnsigned() {
|
||||
return unsigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component of the specified element
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public byte get(int elementIndex, int componentIndex) {
|
||||
int byteIndex = getByteIndex(elementIndex, componentIndex);
|
||||
return getBufferViewByteBuffer().get(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public byte get(int globalComponentIndex) {
|
||||
int elementIndex =
|
||||
globalComponentIndex / getNumComponentsPerElement();
|
||||
int componentIndex =
|
||||
globalComponentIndex % getNumComponentsPerElement();
|
||||
return get(elementIndex, componentIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the specified component of the specified element
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @param value The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public void set(int elementIndex, int componentIndex, byte value) {
|
||||
int byteIndex = getByteIndex(elementIndex, componentIndex);
|
||||
getBufferViewByteBuffer().put(byteIndex, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the specified component
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @param value The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public void set(int globalComponentIndex, byte value) {
|
||||
int elementIndex =
|
||||
globalComponentIndex / getNumComponentsPerElement();
|
||||
int componentIndex =
|
||||
globalComponentIndex % getNumComponentsPerElement();
|
||||
set(elementIndex, componentIndex, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component of the specified element,
|
||||
* taking into account whether the data {@link #isUnsigned()}: If the data
|
||||
* is unsigned, the returned byte value will be converted into an
|
||||
* unsigned integer value.
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public int getInt(int elementIndex, int componentIndex) {
|
||||
byte value = get(elementIndex, componentIndex);
|
||||
return unsigned ? Byte.toUnsignedInt(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component, taking into account
|
||||
* whether the data {@link #isUnsigned()}: If the data is unsigned,
|
||||
* the returned byte value will be converted into an unsigned integer
|
||||
* value.
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public int getInt(int globalComponentIndex) {
|
||||
byte value = get(globalComponentIndex);
|
||||
return unsigned ? Byte.toUnsignedInt(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the minimum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public byte[] computeMin() {
|
||||
byte[] result = new byte[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Byte.MAX_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = (byte) Math.min(result[c], get(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the maximum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public byte[] computeMax() {
|
||||
byte[] result = new byte[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Byte.MIN_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = (byte) Math.max(result[c], get(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the minimum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
* These values are computed based on {@link #getInt(int, int)}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public int[] computeMinInt() {
|
||||
int[] result = new int[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Integer.MAX_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.min(result[c], getInt(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the maximum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
* These values are computed based on {@link #getInt(int, int)}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public int[] computeMaxInt() {
|
||||
int result[] = new int[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Integer.MIN_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.max(result[c], getInt(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer createByteBuffer() {
|
||||
int totalNumComponents = getTotalNumComponents();
|
||||
int totalBytes = totalNumComponents * getNumBytesPerComponent();
|
||||
ByteBuffer result = ByteBuffer.allocateDirect(totalBytes)
|
||||
.order(ByteOrder.nativeOrder());
|
||||
for (int i = 0; i < totalNumComponents; i++) {
|
||||
byte component = get(i);
|
||||
result.put(component);
|
||||
}
|
||||
result.position(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (potentially large!) string representation of the data
|
||||
*
|
||||
* @param locale The locale used for number formatting
|
||||
* @param format The number format string
|
||||
* @param elementsPerRow The number of elements per row. If this
|
||||
* is not greater than 0, then all elements will be in a single row.
|
||||
* @return The data string
|
||||
*/
|
||||
public String createString(
|
||||
Locale locale, String format, int elementsPerRow) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int nc = getNumComponentsPerElement();
|
||||
sb.append("[");
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
if (e > 0) {
|
||||
sb.append(", ");
|
||||
if (elementsPerRow > 0 && (e % elementsPerRow) == 0) {
|
||||
sb.append("\n ");
|
||||
}
|
||||
}
|
||||
if (nc > 1) {
|
||||
sb.append("(");
|
||||
}
|
||||
for (int c = 0; c < nc; c++) {
|
||||
if (c > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
int component = getInt(e, c);
|
||||
sb.append(String.format(locale, format, component));
|
||||
}
|
||||
if (nc > 1) {
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Interface for classes that provide typed access to raw accessor data.
|
||||
* The exact type of the data (and thus, the implementing class) is
|
||||
* defined by the {@link #getComponentType() component type}:<br>
|
||||
* <ul>
|
||||
* <li>For <code>byte.class</code>, the implementation is an
|
||||
* {@link AccessorByteData}</li>
|
||||
* <li>For <code>short.class</code>, the implementation is an
|
||||
* {@link AccessorShortData}</li>
|
||||
* <li>For <code>int.class</code>, the implementation is an
|
||||
* {@link AccessorIntData}</li>
|
||||
* <li>For <code>float.class</code>, the implementation is an
|
||||
* {@link AccessorFloatData}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public interface AccessorData {
|
||||
/**
|
||||
* Returns the type of the components that this class provides access to.
|
||||
* This will usually be a primitive type, like <code>float.class</code>
|
||||
* or <code>short.class</code>.
|
||||
*
|
||||
* @return The component type
|
||||
*/
|
||||
Class<?> getComponentType();
|
||||
|
||||
/**
|
||||
* Returns the number of elements in this data (for example, the number
|
||||
* of 3D vectors)
|
||||
*
|
||||
* @return The number of elements
|
||||
*/
|
||||
int getNumElements();
|
||||
|
||||
/**
|
||||
* Returns the number of components per element (for example, 3 if the
|
||||
* elements are 3D vectors)
|
||||
*
|
||||
* @return The number of components per element
|
||||
*/
|
||||
int getNumComponentsPerElement();
|
||||
|
||||
/**
|
||||
* Returns the total number of components (that is, the number of elements
|
||||
* multiplied with the number of components per element)
|
||||
*
|
||||
* @return The total number of components
|
||||
*/
|
||||
int getTotalNumComponents();
|
||||
|
||||
/**
|
||||
* Creates a new, direct byte buffer (with native byte order) that
|
||||
* contains the data for the accessor, in a compact form,
|
||||
* without any offset, and without any additional stride (that is,
|
||||
* all elements will be tightly packed).
|
||||
*
|
||||
* @return The byte buffer
|
||||
*/
|
||||
ByteBuffer createByteBuffer();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
import com.tacz.guns.client.animation.gltf.AccessorModel;
|
||||
import com.tacz.guns.client.animation.gltf.BufferViewModel;
|
||||
import com.tacz.guns.client.animation.gltf.GltfConstants;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Methods to create instances of the accessor data utility classes
|
||||
* that allow a <i>typed</i> access to the data that is contained in the
|
||||
* buffer view that the accessor refers to.<br>
|
||||
* <br>
|
||||
* Unless otherwise noted, none of the arguments to these methods may
|
||||
* be <code>null</code>.
|
||||
*/
|
||||
public class AccessorDatas {
|
||||
/**
|
||||
* Private constructor to prevent instantiation
|
||||
*/
|
||||
private AccessorDatas() {
|
||||
// Private constructor to prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link AccessorData} for the given {@link AccessorModel}
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @return The {@link AccessorData}
|
||||
*/
|
||||
public static AccessorData create(AccessorModel accessorModel) {
|
||||
BufferViewModel bufferViewModel = accessorModel.getBufferViewModel();
|
||||
ByteBuffer bufferViewData = bufferViewModel.getBufferViewData();
|
||||
return create(accessorModel, bufferViewData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link AccessorData} for the given {@link AccessorModel}
|
||||
* that refers to the data from the given buffer.
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @param byteBuffer The byte buffer containing the data
|
||||
* @return The {@link AccessorData}
|
||||
*/
|
||||
public static AccessorData create(
|
||||
AccessorModel accessorModel, ByteBuffer byteBuffer) {
|
||||
if (accessorModel.getComponentDataType() == byte.class) {
|
||||
return createByte(accessorModel, byteBuffer);
|
||||
}
|
||||
if (accessorModel.getComponentDataType() == short.class) {
|
||||
return createShort(accessorModel, byteBuffer);
|
||||
}
|
||||
if (accessorModel.getComponentDataType() == int.class) {
|
||||
return createInt(accessorModel, byteBuffer);
|
||||
}
|
||||
if (accessorModel.getComponentDataType() == float.class) {
|
||||
return createFloat(accessorModel, byteBuffer);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link AccessorData} depending on the given component type.
|
||||
* This will return an {@link AccessorByteData}, {@link AccessorShortData},
|
||||
* {@link AccessorIntData} or {@link AccessorFloatData}
|
||||
*
|
||||
* @param componentType The component type, as a GL constant (for example,
|
||||
* <code>GL_UNSIGNED_SHORT</code> or <code>GL_FLOAT</code>)
|
||||
* @param bufferViewData The buffer view data that the accessor refers to
|
||||
* @param byteOffset The byte offset for the accessor
|
||||
* @param count The count (number of elements) for the accessor
|
||||
* @param numComponentsPerElement The number of components per element.
|
||||
* For example, if the accessor type is <code>"VEC3"</code>, then this
|
||||
* will be 3
|
||||
* @param byteStride The optional byte stride for the accessor data
|
||||
* @return The {@link AccessorData}
|
||||
* @throws IllegalArgumentException If the given component type is
|
||||
* not a valid GL constant
|
||||
*/
|
||||
public static AccessorData create(
|
||||
int componentType, ByteBuffer bufferViewData, int byteOffset,
|
||||
int count, int numComponentsPerElement, Integer byteStride) {
|
||||
if (isByteType(componentType)) {
|
||||
return new AccessorByteData(
|
||||
componentType, bufferViewData, byteOffset, count,
|
||||
numComponentsPerElement, byteStride);
|
||||
}
|
||||
if (isShortType(componentType)) {
|
||||
return new AccessorShortData(
|
||||
componentType, bufferViewData, byteOffset, count,
|
||||
numComponentsPerElement, byteStride);
|
||||
}
|
||||
if (isIntType(componentType)) {
|
||||
return new AccessorIntData(
|
||||
componentType, bufferViewData, byteOffset, count,
|
||||
numComponentsPerElement, byteStride);
|
||||
}
|
||||
if (isFloatType(componentType)) {
|
||||
return new AccessorFloatData(
|
||||
componentType, bufferViewData, byteOffset, count,
|
||||
numComponentsPerElement, byteStride);
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Not a valid component type: " + componentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given constant is <code>GL_BYTE</code> or
|
||||
* <code>GL_UNSIGNED_BYTE</code>.
|
||||
*
|
||||
* @param type The type constant
|
||||
* @return Whether the type is a <code>byte</code> type
|
||||
*/
|
||||
public static boolean isByteType(int type) {
|
||||
return
|
||||
type == GltfConstants.GL_BYTE ||
|
||||
type == GltfConstants.GL_UNSIGNED_BYTE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given constant is <code>GL_SHORT</code> or
|
||||
* <code>GL_UNSIGNED_SHORT</code>.
|
||||
*
|
||||
* @param type The type constant
|
||||
* @return Whether the type is a <code>short</code> type
|
||||
*/
|
||||
public static boolean isShortType(int type) {
|
||||
return
|
||||
type == GltfConstants.GL_SHORT ||
|
||||
type == GltfConstants.GL_UNSIGNED_SHORT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given constant is <code>GL_INT</code> or
|
||||
* <code>GL_UNSIGNED_INT</code>.
|
||||
*
|
||||
* @param type The type constant
|
||||
* @return Whether the type is an <code>int</code> type
|
||||
*/
|
||||
public static boolean isIntType(int type) {
|
||||
return
|
||||
type == GltfConstants.GL_INT ||
|
||||
type == GltfConstants.GL_UNSIGNED_INT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given constant is <code>GL_FLOAT</code>.
|
||||
*
|
||||
* @param type The type constant
|
||||
* @return Whether the type is a <code>float</code> type
|
||||
*/
|
||||
public static boolean isFloatType(int type) {
|
||||
return type == GltfConstants.GL_FLOAT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given constant is <code>GL_UNSIGNED_BYTE</code>,
|
||||
* <code>GL_UNSIGNED_SHORT</code> or <code>GL_UNSIGNED_INT</code>.
|
||||
*
|
||||
* @param type The type constant
|
||||
* @return Whether the type is an unsigned type
|
||||
*/
|
||||
static boolean isUnsignedType(int type) {
|
||||
return
|
||||
type == GltfConstants.GL_UNSIGNED_BYTE ||
|
||||
type == GltfConstants.GL_UNSIGNED_SHORT ||
|
||||
type == GltfConstants.GL_UNSIGNED_INT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the given type is <code>GL_BYTE</code> or
|
||||
* <code>GL_UNSIGNED_BYTE</code>, and throw an
|
||||
* <code>IllegalArgumentException</code> if this is not the case.
|
||||
*
|
||||
* @param type The type constant
|
||||
* @throws IllegalArgumentException If the given type is not
|
||||
* <code>GL_BYTE</code> or <code>GL_UNSIGNED_BYTE</code>
|
||||
*/
|
||||
static void validateByteType(int type) {
|
||||
if (!isByteType(type)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The type is not GL_BYTE or GL_UNSIGNED_BYTE, but " +
|
||||
GltfConstants.stringFor(type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the given type is <code>GL_SHORT</code> or
|
||||
* <code>GL_UNSIGNED_SHORT</code>, and throw an
|
||||
* <code>IllegalArgumentException</code> if this is not the case.
|
||||
*
|
||||
* @param type The type constant
|
||||
* @throws IllegalArgumentException If the given type is not
|
||||
* <code>GL_SHORT</code> or <code>GL_UNSIGNED_BYTE</code>
|
||||
*/
|
||||
static void validateShortType(int type) {
|
||||
if (!isShortType(type)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The type is not GL_SHORT or GL_UNSIGNED_SHORT, but " +
|
||||
GltfConstants.stringFor(type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the given type is <code>GL_INT</code> or
|
||||
* <code>GL_UNSIGNED_INT</code>, and throw an
|
||||
* <code>IllegalArgumentException</code> if this is not the case.
|
||||
*
|
||||
* @param type The type constant
|
||||
* @throws IllegalArgumentException If the given type is not
|
||||
* <code>GL_INT</code> or <code>GL_UNSIGNED_INT</code>
|
||||
*/
|
||||
static void validateIntType(int type) {
|
||||
if (!isIntType(type)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The type is not GL_INT or GL_UNSIGNED_INT, but " +
|
||||
GltfConstants.stringFor(type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the given type is <code>GL_FLOAT</code>, and throw an
|
||||
* <code>IllegalArgumentException</code> if this is not the case.
|
||||
*
|
||||
* @param type The type constant
|
||||
* @throws IllegalArgumentException If the given type is not
|
||||
* <code>GL_FLOAT</code>
|
||||
*/
|
||||
static void validateFloatType(int type) {
|
||||
if (!isFloatType(type)) {
|
||||
throw new IllegalArgumentException(
|
||||
"The type is not GL_FLOAT, but " +
|
||||
GltfConstants.stringFor(type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AccessorByteData} for the given {@link AccessorModel}
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @return The {@link AccessorByteData}
|
||||
* @throws IllegalArgumentException If the
|
||||
* {@link AccessorModel#getComponentType() component type} of the given
|
||||
* accessor is not <code>GL_BYTE</code> or <code>GL_UNSIGNED_BYTE</code>
|
||||
*/
|
||||
static AccessorByteData createByte(AccessorModel accessorModel) {
|
||||
BufferViewModel bufferViewModel = accessorModel.getBufferViewModel();
|
||||
return createByte(accessorModel, bufferViewModel.getBufferViewData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AccessorByteData} for the given {@link AccessorModel}
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @param bufferViewByteBuffer The byte buffer of the
|
||||
* {@link BufferViewModel} referenced by the {@link AccessorModel}
|
||||
* @return The {@link AccessorByteData}
|
||||
* @throws NullPointerException If any argument is <code>null</code>
|
||||
* @throws IllegalArgumentException If the
|
||||
* {@link AccessorModel#getComponentType() component type} of the given
|
||||
* accessorModel is not <code>GL_BYTE</code> or
|
||||
* <code>GL_UNSIGNED_BYTE</code>
|
||||
*/
|
||||
private static AccessorByteData createByte(
|
||||
AccessorModel accessorModel, ByteBuffer bufferViewByteBuffer) {
|
||||
return new AccessorByteData(accessorModel.getComponentType(),
|
||||
bufferViewByteBuffer,
|
||||
accessorModel.getByteOffset(),
|
||||
accessorModel.getCount(),
|
||||
accessorModel.getElementType().getNumComponents(),
|
||||
accessorModel.getByteStride());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AccessorShortData} for the given {@link AccessorModel}
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @return The {@link AccessorShortData}
|
||||
* @throws IllegalArgumentException If the
|
||||
* {@link AccessorModel#getComponentType() component type} of the given
|
||||
* accessorModel is not <code>GL_SHORT</code> or
|
||||
* <code>GL_UNSIGNED_SHORT</code>
|
||||
*/
|
||||
static AccessorShortData createShort(AccessorModel accessorModel) {
|
||||
BufferViewModel bufferViewModel = accessorModel.getBufferViewModel();
|
||||
return createShort(accessorModel, bufferViewModel.getBufferViewData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AccessorShortData} for the given {@link AccessorModel}
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @param bufferViewByteBuffer The byte buffer of the
|
||||
* {@link BufferViewModel} referenced by the {@link AccessorModel}
|
||||
* @return The {@link AccessorShortData}
|
||||
* @throws NullPointerException If any argument is <code>null</code>
|
||||
* @throws IllegalArgumentException If the
|
||||
* {@link AccessorModel#getComponentType() component type} of the given
|
||||
* accessorModel is not <code>GL_SHORT</code> or
|
||||
* <code>GL_UNSIGNED_SHORT</code>
|
||||
*/
|
||||
private static AccessorShortData createShort(
|
||||
AccessorModel accessorModel, ByteBuffer bufferViewByteBuffer) {
|
||||
return new AccessorShortData(accessorModel.getComponentType(),
|
||||
bufferViewByteBuffer,
|
||||
accessorModel.getByteOffset(),
|
||||
accessorModel.getCount(),
|
||||
accessorModel.getElementType().getNumComponents(),
|
||||
accessorModel.getByteStride());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AccessorIntData} for the given {@link AccessorModel}
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @return The {@link AccessorIntData}
|
||||
* @throws IllegalArgumentException If the
|
||||
* {@link AccessorModel#getComponentType() component type} of the given
|
||||
* accessorModel is not <code>GL_INT</code> or <code>GL_UNSIGNED_INT</code>
|
||||
*/
|
||||
static AccessorIntData createInt(AccessorModel accessorModel) {
|
||||
BufferViewModel bufferViewModel = accessorModel.getBufferViewModel();
|
||||
return createInt(accessorModel, bufferViewModel.getBufferViewData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AccessorIntData} for the given {@link AccessorModel}
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @param bufferViewByteBuffer The byte buffer of the
|
||||
* {@link BufferViewModel} referenced by the {@link AccessorModel}
|
||||
* @return The {@link AccessorIntData}
|
||||
* @throws NullPointerException If any argument is <code>null</code>
|
||||
* @throws IllegalArgumentException If the
|
||||
* {@link AccessorModel#getComponentType() component type} of the given
|
||||
* accessorModel is not <code>GL_INT</code> or <code>GL_UNSIGNED_INT</code>
|
||||
*/
|
||||
private static AccessorIntData createInt(
|
||||
AccessorModel accessorModel, ByteBuffer bufferViewByteBuffer) {
|
||||
return new AccessorIntData(accessorModel.getComponentType(),
|
||||
bufferViewByteBuffer,
|
||||
accessorModel.getByteOffset(),
|
||||
accessorModel.getCount(),
|
||||
accessorModel.getElementType().getNumComponents(),
|
||||
accessorModel.getByteStride());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AccessorFloatData} for the given {@link AccessorModel}
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @return The {@link AccessorFloatData}
|
||||
* @throws IllegalArgumentException If the
|
||||
* {@link AccessorModel#getComponentType() component type} of the given
|
||||
* accessorModel is not <code>GL_FLOAT</code>
|
||||
*/
|
||||
public static AccessorFloatData createFloat(AccessorModel accessorModel) {
|
||||
BufferViewModel bufferViewModel = accessorModel.getBufferViewModel();
|
||||
return createFloat(accessorModel, bufferViewModel.getBufferViewData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AccessorFloatData} for the given {@link AccessorModel}
|
||||
*
|
||||
* @param accessorModel The {@link AccessorModel}
|
||||
* @param bufferViewByteBuffer The byte buffer of the
|
||||
* {@link BufferViewModel} referenced by the {@link AccessorModel}
|
||||
* @return The {@link AccessorFloatData}
|
||||
* @throws NullPointerException If any argument is <code>null</code>
|
||||
* @throws IllegalArgumentException If the
|
||||
*/
|
||||
private static AccessorFloatData createFloat(
|
||||
AccessorModel accessorModel, ByteBuffer bufferViewByteBuffer) {
|
||||
return new AccessorFloatData(accessorModel.getComponentType(),
|
||||
bufferViewByteBuffer,
|
||||
accessorModel.getByteOffset(),
|
||||
accessorModel.getCount(),
|
||||
accessorModel.getElementType().getNumComponents(),
|
||||
accessorModel.getByteStride());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the given {@link AccessorModel} parameters are valid for
|
||||
* accessing a buffer with the given capacity
|
||||
*
|
||||
* @param byteOffset The byte offset
|
||||
* @param numElements The number of elements
|
||||
* @param byteStridePerElement The byte stride
|
||||
* @param bufferCapacity The buffer capacity
|
||||
* @throws IllegalArgumentException If the given byte buffer does not
|
||||
* have a sufficient capacity
|
||||
*/
|
||||
static void validateCapacity(int byteOffset, int numElements,
|
||||
int byteStridePerElement, int bufferCapacity) {
|
||||
int expectedCapacity = numElements * byteStridePerElement;
|
||||
if (expectedCapacity > bufferCapacity) {
|
||||
throw new IllegalArgumentException(
|
||||
"The accessorModel has an offset of " + byteOffset + " and " +
|
||||
numElements + " elements with a byte stride of " +
|
||||
byteStridePerElement + ", requiring " + expectedCapacity +
|
||||
" bytes, but the buffer view has only " +
|
||||
bufferCapacity + " bytes");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the the minimum component values of the given
|
||||
* {@link AccessorData}
|
||||
*
|
||||
* @param accessorData The {@link AccessorData}
|
||||
* @return The minimum values
|
||||
* @throws IllegalArgumentException If the given model has an unknown type
|
||||
*/
|
||||
public static Number[] computeMin(AccessorData accessorData) {
|
||||
if (accessorData instanceof AccessorByteData) {
|
||||
AccessorByteData accessorByteData =
|
||||
(AccessorByteData) accessorData;
|
||||
return NumberArrays.asNumbers(
|
||||
accessorByteData.computeMinInt());
|
||||
}
|
||||
if (accessorData instanceof AccessorShortData) {
|
||||
AccessorShortData accessorShortData =
|
||||
(AccessorShortData) accessorData;
|
||||
return NumberArrays.asNumbers(
|
||||
accessorShortData.computeMinInt());
|
||||
}
|
||||
if (accessorData instanceof AccessorIntData) {
|
||||
AccessorIntData accessorIntData =
|
||||
(AccessorIntData) accessorData;
|
||||
return NumberArrays.asNumbers(
|
||||
accessorIntData.computeMinLong());
|
||||
}
|
||||
if (accessorData instanceof AccessorFloatData) {
|
||||
AccessorFloatData accessorFloatData =
|
||||
(AccessorFloatData) accessorData;
|
||||
return NumberArrays.asNumbers(
|
||||
accessorFloatData.computeMin());
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid data type: " + accessorData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the the maximum component values of the given
|
||||
* {@link AccessorData}
|
||||
*
|
||||
* @param accessorData The {@link AccessorData}
|
||||
* @return The maximum values
|
||||
* @throws IllegalArgumentException If the given model has an unknown type
|
||||
*/
|
||||
public static Number[] computeMax(AccessorData accessorData) {
|
||||
if (accessorData instanceof AccessorByteData) {
|
||||
AccessorByteData accessorByteData =
|
||||
(AccessorByteData) accessorData;
|
||||
return NumberArrays.asNumbers(
|
||||
accessorByteData.computeMaxInt());
|
||||
}
|
||||
if (accessorData instanceof AccessorShortData) {
|
||||
AccessorShortData accessorShortData =
|
||||
(AccessorShortData) accessorData;
|
||||
return NumberArrays.asNumbers(
|
||||
accessorShortData.computeMaxInt());
|
||||
}
|
||||
if (accessorData instanceof AccessorIntData) {
|
||||
AccessorIntData accessorIntData =
|
||||
(AccessorIntData) accessorData;
|
||||
return NumberArrays.asNumbers(
|
||||
accessorIntData.computeMaxLong());
|
||||
}
|
||||
if (accessorData instanceof AccessorFloatData) {
|
||||
AccessorFloatData accessorFloatData =
|
||||
(AccessorFloatData) accessorData;
|
||||
return NumberArrays.asNumbers(
|
||||
accessorFloatData.computeMax());
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid data type: " + accessorData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (possibly large!) string representation of the given
|
||||
* {@link AccessorData}, by calling
|
||||
* {@link AccessorByteData#createString(Locale, String, int)},
|
||||
* {@link AccessorShortData#createString(Locale, String, int)},
|
||||
* {@link AccessorIntData#createString(Locale, String, int)} or
|
||||
* {@link AccessorFloatData#createString(Locale, String, int)},
|
||||
* depending on the type of the given data, with an unspecified
|
||||
* format string.
|
||||
*
|
||||
* @param accessorData The {@link AccessorData}
|
||||
* @param elementsPerRow The number of elements per row
|
||||
* @return The string
|
||||
*/
|
||||
public static String createString(
|
||||
AccessorData accessorData, int elementsPerRow) {
|
||||
if (accessorData instanceof AccessorByteData) {
|
||||
AccessorByteData accessorByteData =
|
||||
(AccessorByteData) accessorData;
|
||||
String accessorDataString =
|
||||
accessorByteData.createString(
|
||||
Locale.ENGLISH, "%4d", elementsPerRow);
|
||||
return accessorDataString;
|
||||
}
|
||||
if (accessorData instanceof AccessorShortData) {
|
||||
AccessorShortData accessorShortData =
|
||||
(AccessorShortData) accessorData;
|
||||
String accessorDataString =
|
||||
accessorShortData.createString(
|
||||
Locale.ENGLISH, "%6d", elementsPerRow);
|
||||
return accessorDataString;
|
||||
}
|
||||
if (accessorData instanceof AccessorIntData) {
|
||||
AccessorIntData accessorIntData =
|
||||
(AccessorIntData) accessorData;
|
||||
String accessorDataString =
|
||||
accessorIntData.createString(
|
||||
Locale.ENGLISH, "%11d", elementsPerRow);
|
||||
return accessorDataString;
|
||||
}
|
||||
if (accessorData instanceof AccessorFloatData) {
|
||||
AccessorFloatData accessorFloatData =
|
||||
(AccessorFloatData) accessorData;
|
||||
String accessorDataString =
|
||||
accessorFloatData.createString(
|
||||
Locale.ENGLISH, "%10.5f", elementsPerRow);
|
||||
return accessorDataString;
|
||||
}
|
||||
return "Unknown accessor data type: " + accessorData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* www.javagl.de - JglTF
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A class for accessing the data that is described by an accessor.
|
||||
* It allows accessing the byte buffer of the buffer view of the
|
||||
* accessor, depending on the accessor parameters.<br>
|
||||
* <br>
|
||||
* This data consists of several elements (for example, 3D float vectors),
|
||||
* which consist of several components (for example, the 3 float values).
|
||||
*/
|
||||
public final class AccessorFloatData
|
||||
extends AbstractAccessorData
|
||||
implements AccessorData {
|
||||
/**
|
||||
* Creates a new instance for accessing the data in the given
|
||||
* byte buffer, according to the rules described by the given
|
||||
* accessor parameters.
|
||||
*
|
||||
* @param componentType The component type
|
||||
* @param bufferViewByteBuffer The byte buffer of the buffer view
|
||||
* @param byteOffset The byte offset in the buffer view
|
||||
* @param numElements The number of elements
|
||||
* @param numComponentsPerElement The number of components per element
|
||||
* @param byteStride The byte stride between two elements. If this
|
||||
* is <code>null</code> or <code>0</code>, then the stride will
|
||||
* be the size of one element.
|
||||
* @throws NullPointerException If the bufferViewByteBuffer is
|
||||
* <code>null</code>
|
||||
* @throws IllegalArgumentException If the component type is not
|
||||
* <code>GL_FLOAT</code>
|
||||
* @throws IllegalArgumentException If the given byte buffer does not
|
||||
* have a sufficient capacity to provide the data for the accessor
|
||||
*/
|
||||
public AccessorFloatData(int componentType,
|
||||
ByteBuffer bufferViewByteBuffer, int byteOffset, int numElements,
|
||||
int numComponentsPerElement, Integer byteStride) {
|
||||
super(float.class, bufferViewByteBuffer, byteOffset, numElements,
|
||||
numComponentsPerElement, Float.BYTES, byteStride);
|
||||
AccessorDatas.validateFloatType(componentType);
|
||||
|
||||
AccessorDatas.validateCapacity(byteOffset, getNumElements(),
|
||||
getByteStridePerElement(), bufferViewByteBuffer.capacity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component of the specified element
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public float get(int elementIndex, int componentIndex) {
|
||||
int byteIndex = getByteIndex(elementIndex, componentIndex);
|
||||
return getBufferViewByteBuffer().getFloat(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public float get(int globalComponentIndex) {
|
||||
int elementIndex =
|
||||
globalComponentIndex / getNumComponentsPerElement();
|
||||
int componentIndex =
|
||||
globalComponentIndex % getNumComponentsPerElement();
|
||||
return get(elementIndex, componentIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the specified component of the specified element
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @param value The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public void set(int elementIndex, int componentIndex, float value) {
|
||||
int byteIndex = getByteIndex(elementIndex, componentIndex);
|
||||
getBufferViewByteBuffer().putFloat(byteIndex, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the specified component
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @param value The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public void set(int globalComponentIndex, float value) {
|
||||
int elementIndex =
|
||||
globalComponentIndex / getNumComponentsPerElement();
|
||||
int componentIndex =
|
||||
globalComponentIndex % getNumComponentsPerElement();
|
||||
set(elementIndex, componentIndex, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array containing the minimum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public float[] computeMin() {
|
||||
float result[] = new float[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Float.MAX_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.min(result[c], get(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the maximum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public float[] computeMax() {
|
||||
float result[] = new float[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, -Float.MAX_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.max(result[c], get(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer createByteBuffer() {
|
||||
int totalNumComponents = getTotalNumComponents();
|
||||
int totalBytes = totalNumComponents * getNumBytesPerComponent();
|
||||
ByteBuffer result = ByteBuffer.allocateDirect(totalBytes)
|
||||
.order(ByteOrder.nativeOrder());
|
||||
for (int i = 0; i < totalNumComponents; i++) {
|
||||
float component = get(i);
|
||||
result.putFloat(component);
|
||||
}
|
||||
result.position(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (potentially large!) string representation of the data
|
||||
*
|
||||
* @param locale The locale used for number formatting
|
||||
* @param format The number format string
|
||||
* @param elementsPerRow The number of elements per row. If this
|
||||
* is not greater than 0, then all elements will be in a single row.
|
||||
* @return The data string
|
||||
*/
|
||||
public String createString(
|
||||
Locale locale, String format, int elementsPerRow) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int nc = getNumComponentsPerElement();
|
||||
sb.append("[");
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
if (e > 0) {
|
||||
sb.append(", ");
|
||||
if (elementsPerRow > 0 && (e % elementsPerRow) == 0) {
|
||||
sb.append("\n ");
|
||||
}
|
||||
}
|
||||
if (nc > 1) {
|
||||
sb.append("(");
|
||||
}
|
||||
for (int c = 0; c < nc; c++) {
|
||||
if (c > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
float component = get(e, c);
|
||||
sb.append(String.format(locale, format, component));
|
||||
}
|
||||
if (nc > 1) {
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* www.javagl.de - JglTF
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A class for accessing the data that is described by an accessor.
|
||||
* It allows accessing the byte buffer of the buffer view of the
|
||||
* accessor, depending on the accessor parameters.<br>
|
||||
* <br>
|
||||
* This data consists of several elements (for example, 3D int vectors),
|
||||
* which consist of several components (for example, the 3 int values).
|
||||
*/
|
||||
public final class AccessorIntData
|
||||
extends AbstractAccessorData
|
||||
implements AccessorData {
|
||||
/**
|
||||
* Whether the data should be interpreted as unsigned values
|
||||
*/
|
||||
private final boolean unsigned;
|
||||
|
||||
/**
|
||||
* Creates a new instance for accessing the data in the given
|
||||
* byte buffer, according to the rules described by the given
|
||||
* accessor parameters.
|
||||
*
|
||||
* @param componentType The component type
|
||||
* @param bufferViewByteBuffer The byte buffer of the buffer view
|
||||
* @param byteOffset The byte offset in the buffer view
|
||||
* @param numElements The number of elements
|
||||
* @param numComponentsPerElement The number of components per element
|
||||
* @param byteStride The byte stride between two elements. If this
|
||||
* is <code>null</code> or <code>0</code>, then the stride will
|
||||
* be the size of one element.
|
||||
* @throws NullPointerException If the bufferViewByteBuffer is
|
||||
* <code>null</code>
|
||||
* @throws IllegalArgumentException If the component type is not
|
||||
* <code>GL_INT</code> or <code>GL_UNSIGEND_INT</code>
|
||||
* @throws IllegalArgumentException If the given byte buffer does not
|
||||
* have a sufficient capacity to provide the data for the accessor
|
||||
*/
|
||||
public AccessorIntData(int componentType,
|
||||
ByteBuffer bufferViewByteBuffer, int byteOffset, int numElements,
|
||||
int numComponentsPerElement, Integer byteStride) {
|
||||
super(int.class, bufferViewByteBuffer, byteOffset, numElements,
|
||||
numComponentsPerElement, Integer.BYTES, byteStride);
|
||||
AccessorDatas.validateIntType(componentType);
|
||||
|
||||
this.unsigned = AccessorDatas.isUnsignedType(componentType);
|
||||
AccessorDatas.validateCapacity(byteOffset, getNumElements(),
|
||||
getByteStridePerElement(), bufferViewByteBuffer.capacity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the data should be interpreted as unsigned
|
||||
*
|
||||
* @return Whether the data should be interpreted as unsigned
|
||||
*/
|
||||
public boolean isUnsigned() {
|
||||
return unsigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component of the specified element
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public int get(int elementIndex, int componentIndex) {
|
||||
int byteIndex = getByteIndex(elementIndex, componentIndex);
|
||||
return getBufferViewByteBuffer().getInt(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public int get(int globalComponentIndex) {
|
||||
int elementIndex =
|
||||
globalComponentIndex / getNumComponentsPerElement();
|
||||
int componentIndex =
|
||||
globalComponentIndex % getNumComponentsPerElement();
|
||||
return get(elementIndex, componentIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the specified component of the specified element
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @param value The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public void set(int elementIndex, int componentIndex, int value) {
|
||||
int byteIndex = getByteIndex(elementIndex, componentIndex);
|
||||
getBufferViewByteBuffer().putInt(byteIndex, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the specified component
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @param value The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public void set(int globalComponentIndex, int value) {
|
||||
int elementIndex =
|
||||
globalComponentIndex / getNumComponentsPerElement();
|
||||
int componentIndex =
|
||||
globalComponentIndex % getNumComponentsPerElement();
|
||||
set(elementIndex, componentIndex, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component of the specified element,
|
||||
* taking into account whether the data {@link #isUnsigned()}: If the data
|
||||
* is unsigned, the returned int value will be converted into an
|
||||
* unsigned long value.
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public long getLong(int elementIndex, int componentIndex) {
|
||||
int value = get(elementIndex, componentIndex);
|
||||
return unsigned ? Integer.toUnsignedLong(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component, taking into account
|
||||
* whether the data {@link #isUnsigned()}: If the data is unsigned,
|
||||
* the returned int value will be converted into an unsigned integer
|
||||
* value.
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public long getLong(int globalComponentIndex) {
|
||||
int value = get(globalComponentIndex);
|
||||
return unsigned ? Integer.toUnsignedLong(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the minimum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public int[] computeMin() {
|
||||
int result[] = new int[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Integer.MAX_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.min(result[c], get(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the maximum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public int[] computeMax() {
|
||||
int result[] = new int[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Integer.MIN_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.max(result[c], get(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the minimum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
* These values are computed based on {@link #getLong(int, int)}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public long[] computeMinLong() {
|
||||
long result[] = new long[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Long.MAX_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.min(result[c], getLong(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the maximum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
* These values are computed based on {@link #getLong(int, int)}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public long[] computeMaxLong() {
|
||||
long result[] = new long[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Long.MIN_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.max(result[c], getLong(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer createByteBuffer() {
|
||||
int totalNumComponents = getTotalNumComponents();
|
||||
int totalBytes = totalNumComponents * getNumBytesPerComponent();
|
||||
ByteBuffer result = ByteBuffer.allocateDirect(totalBytes)
|
||||
.order(ByteOrder.nativeOrder());
|
||||
for (int i = 0; i < totalNumComponents; i++) {
|
||||
int component = get(i);
|
||||
result.putInt(component);
|
||||
}
|
||||
result.position(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (potentially large!) string representation of the data
|
||||
*
|
||||
* @param locale The locale used for number formatting
|
||||
* @param format The number format string
|
||||
* @param elementsPerRow The number of elements per row. If this
|
||||
* is not greater than 0, then all elements will be in a single row.
|
||||
* @return The data string
|
||||
*/
|
||||
public String createString(
|
||||
Locale locale, String format, int elementsPerRow) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int nc = getNumComponentsPerElement();
|
||||
sb.append("[");
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
if (e > 0) {
|
||||
sb.append(", ");
|
||||
if (elementsPerRow > 0 && (e % elementsPerRow) == 0) {
|
||||
sb.append("\n ");
|
||||
}
|
||||
}
|
||||
if (nc > 1) {
|
||||
sb.append("(");
|
||||
}
|
||||
for (int c = 0; c < nc; c++) {
|
||||
if (c > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
long component = getLong(e, c);
|
||||
sb.append(String.format(locale, format, component));
|
||||
}
|
||||
if (nc > 1) {
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* www.javagl.de - JglTF
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A class for accessing the data that is described by an accessor.
|
||||
* It allows accessing the byte buffer of the buffer view of the
|
||||
* accessor, depending on the accessor parameters.<br>
|
||||
* <br>
|
||||
* This data consists of several elements (for example, 3D short vectors),
|
||||
* which consist of several components (for example, the 3 short values).
|
||||
*/
|
||||
public final class AccessorShortData
|
||||
extends AbstractAccessorData
|
||||
implements AccessorData {
|
||||
/**
|
||||
* Whether the data should be interpreted as unsigned values
|
||||
*/
|
||||
private final boolean unsigned;
|
||||
|
||||
/**
|
||||
* Creates a new instance for accessing the data in the given
|
||||
* byte buffer, according to the rules described by the given
|
||||
* accessor parameters.
|
||||
*
|
||||
* @param componentType The component type
|
||||
* @param bufferViewByteBuffer The byte buffer of the buffer view
|
||||
* @param byteOffset The byte offset in the buffer view
|
||||
* @param numElements The number of elements
|
||||
* @param numComponentsPerElement The number of components per element
|
||||
* @param byteStride The byte stride between two elements. If this
|
||||
* is <code>null</code> or <code>0</code>, then the stride will
|
||||
* be the size of one element.
|
||||
* @throws NullPointerException If the bufferViewByteBuffer is
|
||||
* <code>null</code>
|
||||
* @throws IllegalArgumentException If the component type is not
|
||||
* <code>GL_SHORT</code> or <code>GL_UNSIGEND_SHORT</code>
|
||||
* @throws IllegalArgumentException If the given byte buffer does not
|
||||
* have a sufficient capacity to provide the data for the accessor
|
||||
*/
|
||||
public AccessorShortData(int componentType,
|
||||
ByteBuffer bufferViewByteBuffer, int byteOffset, int numElements,
|
||||
int numComponentsPerElement, Integer byteStride) {
|
||||
super(short.class, bufferViewByteBuffer, byteOffset, numElements,
|
||||
numComponentsPerElement, Short.BYTES, byteStride);
|
||||
AccessorDatas.validateShortType(componentType);
|
||||
|
||||
this.unsigned = AccessorDatas.isUnsignedType(componentType);
|
||||
AccessorDatas.validateCapacity(byteOffset, getNumElements(),
|
||||
getByteStridePerElement(), bufferViewByteBuffer.capacity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the data should be interpreted as unsigned
|
||||
*
|
||||
* @return Whether the data should be interpreted as unsigned
|
||||
*/
|
||||
public boolean isUnsigned() {
|
||||
return unsigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component of the specified element
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public short get(int elementIndex, int componentIndex) {
|
||||
int byteIndex = getByteIndex(elementIndex, componentIndex);
|
||||
return getBufferViewByteBuffer().getShort(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public short get(int globalComponentIndex) {
|
||||
int elementIndex =
|
||||
globalComponentIndex / getNumComponentsPerElement();
|
||||
int componentIndex =
|
||||
globalComponentIndex % getNumComponentsPerElement();
|
||||
return get(elementIndex, componentIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the specified component of the specified element
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @param value The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public void set(int elementIndex, int componentIndex, short value) {
|
||||
int byteIndex = getByteIndex(elementIndex, componentIndex);
|
||||
getBufferViewByteBuffer().putShort(byteIndex, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the specified component
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @param value The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public void set(int globalComponentIndex, short value) {
|
||||
int elementIndex =
|
||||
globalComponentIndex / getNumComponentsPerElement();
|
||||
int componentIndex =
|
||||
globalComponentIndex % getNumComponentsPerElement();
|
||||
set(elementIndex, componentIndex, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component of the specified element,
|
||||
* taking into account whether the data {@link #isUnsigned()}: If the data
|
||||
* is unsigned, the returned short value will be converted into an
|
||||
* unsigned integer value.
|
||||
*
|
||||
* @param elementIndex The element index
|
||||
* @param componentIndex The component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given indices cause the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public int getInt(int elementIndex, int componentIndex) {
|
||||
short value = get(elementIndex, componentIndex);
|
||||
return unsigned ? Short.toUnsignedInt(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified component, taking into account
|
||||
* whether the data {@link #isUnsigned()}: If the data is unsigned,
|
||||
* the returned short value will be converted into an unsigned integer
|
||||
* value.
|
||||
*
|
||||
* @param globalComponentIndex The global component index
|
||||
* @return The value
|
||||
* @throws IndexOutOfBoundsException If the given index causes the
|
||||
* underlying buffer to be accessed out of bounds
|
||||
*/
|
||||
public int getInt(int globalComponentIndex) {
|
||||
short value = get(globalComponentIndex);
|
||||
return unsigned ? Short.toUnsignedInt(value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the minimum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public short[] computeMin() {
|
||||
short result[] = new short[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Short.MAX_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = (short) Math.min(result[c], get(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the maximum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public short[] computeMax() {
|
||||
short result[] = new short[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Short.MIN_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = (short) Math.max(result[c], get(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the minimum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
* These values are computed based on {@link #getInt(int, int)}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public int[] computeMinInt() {
|
||||
int result[] = new int[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Integer.MAX_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.min(result[c], getInt(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the maximum component values of all elements
|
||||
* of this accessor data. This will be an array whose length is the
|
||||
* {@link #getNumComponentsPerElement() number of components per element}.
|
||||
* These values are computed based on {@link #getInt(int, int)}.
|
||||
*
|
||||
* @return The minimum values
|
||||
*/
|
||||
public int[] computeMaxInt() {
|
||||
int result[] = new int[getNumComponentsPerElement()];
|
||||
Arrays.fill(result, Integer.MIN_VALUE);
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
for (int c = 0; c < getNumComponentsPerElement(); c++) {
|
||||
result[c] = Math.max(result[c], getInt(e, c));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer createByteBuffer() {
|
||||
int totalNumComponents = getTotalNumComponents();
|
||||
int totalBytes = totalNumComponents * getNumBytesPerComponent();
|
||||
ByteBuffer result = ByteBuffer.allocateDirect(totalBytes)
|
||||
.order(ByteOrder.nativeOrder());
|
||||
for (int i = 0; i < totalNumComponents; i++) {
|
||||
short component = get(i);
|
||||
result.putShort(component);
|
||||
}
|
||||
result.position(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (potentially large!) string representation of the data
|
||||
*
|
||||
* @param locale The locale used for number formatting
|
||||
* @param format The number format string
|
||||
* @param elementsPerRow The number of elements per row. If this
|
||||
* is not greater than 0, then all elements will be in a single row.
|
||||
* @return The data string
|
||||
*/
|
||||
public String createString(
|
||||
Locale locale, String format, int elementsPerRow) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int nc = getNumComponentsPerElement();
|
||||
sb.append("[");
|
||||
for (int e = 0; e < getNumElements(); e++) {
|
||||
if (e > 0) {
|
||||
sb.append(", ");
|
||||
if (elementsPerRow > 0 && (e % elementsPerRow) == 0) {
|
||||
sb.append("\n ");
|
||||
}
|
||||
}
|
||||
if (nc > 1) {
|
||||
sb.append("(");
|
||||
}
|
||||
for (int c = 0; c < nc; c++) {
|
||||
if (c > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
int component = getInt(e, c);
|
||||
sb.append(String.format(locale, format, component));
|
||||
}
|
||||
if (nc > 1) {
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class AccessorSparseUtils {
|
||||
/**
|
||||
* The logger used in this class
|
||||
*/
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(AccessorSparseUtils.class.getName());
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation
|
||||
*/
|
||||
private AccessorSparseUtils() {
|
||||
// Private constructor to prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract indices from the given {@link AccessorData}. The given
|
||||
* {@link AccessorData} must contain an integral type. That is,
|
||||
* its {@link AccessorData#getComponentType() component type} must
|
||||
* be <code>byte.class</code>, <code>short.class</code> or
|
||||
* <code>int.class</code>.
|
||||
*
|
||||
* @param accessorData The {@link AccessorData}
|
||||
* @return The indices
|
||||
* @throws IllegalArgumentException If the given data does not contain
|
||||
* an integral type
|
||||
*/
|
||||
private static int[] extractIndices(AccessorData accessorData) {
|
||||
if (accessorData.getComponentType() == byte.class) {
|
||||
AccessorByteData accessorByteData =
|
||||
(AccessorByteData) accessorData;
|
||||
int numElements = accessorByteData.getNumElements();
|
||||
int indices[] = new int[numElements];
|
||||
for (int i = 0; i < numElements; i++) {
|
||||
indices[i] = accessorByteData.getInt(i, 0);
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
if (accessorData.getComponentType() == short.class) {
|
||||
AccessorShortData accessorShortData =
|
||||
(AccessorShortData) accessorData;
|
||||
int numElements = accessorShortData.getNumElements();
|
||||
int indices[] = new int[numElements];
|
||||
for (int i = 0; i < numElements; i++) {
|
||||
indices[i] = accessorShortData.getInt(i, 0);
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
if (accessorData.getComponentType() == int.class) {
|
||||
AccessorIntData accessorIntData =
|
||||
(AccessorIntData) accessorData;
|
||||
int numElements = accessorIntData.getNumElements();
|
||||
int indices[] = new int[numElements];
|
||||
for (int i = 0; i < numElements; i++) {
|
||||
indices[i] = accessorIntData.get(i, 0);
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid type for indices: " + accessorData.getComponentType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitute the data in the given dense {@link AccessorData} with the
|
||||
* (sparse) data that is provided by the given {@link AccessorData}
|
||||
* objects. <br>
|
||||
* <br>
|
||||
* The <code>baseAccessorData</code> is the data that the dense data
|
||||
* will be initialized with, <b>before</b> applying the substitution
|
||||
* that is defined by the given sparse indices and values.<br>
|
||||
* <br>
|
||||
* The <code>sparseIndicesAccessorData</code> is an {@link AccessorData}
|
||||
* that was created from the <code>accessor.sparse.indices</code>
|
||||
* structure.<br>
|
||||
* <br>
|
||||
* The <code>sparseValuesAccessorData</code> is an {@link AccessorData}
|
||||
* that was created from the <code>accessor.sparse.values</code>
|
||||
* structure.<br>
|
||||
* <br>
|
||||
* This method does very few sanity checks. The caller is responsible
|
||||
* for calling it only with arguments that are valid (in terms of
|
||||
* indices and data types).
|
||||
*
|
||||
* @param denseAccessorData The dense {@link AccessorData} to be filled
|
||||
* @param baseAccessorData The optional "base" {@link AccessorData}
|
||||
* @param sparseIndicesAccessorData The sparse indices {@link AccessorData}
|
||||
* @param sparseValuesAccessorData The sparse values {@link AccessorData}
|
||||
* @throws IllegalArgumentException If the sparseIndicesAccessorData does
|
||||
* not contain data with an integral type (byte, short, int).
|
||||
*/
|
||||
public static void substituteAccessorData(
|
||||
AccessorData denseAccessorData,
|
||||
AccessorData baseAccessorData,
|
||||
AccessorData sparseIndicesAccessorData,
|
||||
AccessorData sparseValuesAccessorData) {
|
||||
Class<?> componentType = denseAccessorData.getComponentType();
|
||||
if (componentType == byte.class) {
|
||||
AccessorByteData sparseValuesAccessorByteData =
|
||||
(AccessorByteData) sparseValuesAccessorData;
|
||||
AccessorByteData baseAccessorByteData =
|
||||
(AccessorByteData) baseAccessorData;
|
||||
AccessorByteData denseAccessorByteData =
|
||||
(AccessorByteData) denseAccessorData;
|
||||
substituteByteAccessorData(
|
||||
denseAccessorByteData,
|
||||
baseAccessorByteData,
|
||||
sparseIndicesAccessorData,
|
||||
sparseValuesAccessorByteData);
|
||||
} else if (componentType == short.class) {
|
||||
AccessorShortData sparseValuesAccessorShortData =
|
||||
(AccessorShortData) sparseValuesAccessorData;
|
||||
AccessorShortData baseAccessorShortData =
|
||||
(AccessorShortData) baseAccessorData;
|
||||
AccessorShortData denseAccessorShortData =
|
||||
(AccessorShortData) denseAccessorData;
|
||||
substituteShortAccessorData(
|
||||
denseAccessorShortData,
|
||||
baseAccessorShortData,
|
||||
sparseIndicesAccessorData,
|
||||
sparseValuesAccessorShortData);
|
||||
} else if (componentType == int.class) {
|
||||
AccessorIntData sparseValuesAccessorIntData =
|
||||
(AccessorIntData) sparseValuesAccessorData;
|
||||
AccessorIntData baseAccessorIntData =
|
||||
(AccessorIntData) baseAccessorData;
|
||||
AccessorIntData denseAccessorIntData =
|
||||
(AccessorIntData) denseAccessorData;
|
||||
substituteIntAccessorData(
|
||||
denseAccessorIntData,
|
||||
baseAccessorIntData,
|
||||
sparseIndicesAccessorData,
|
||||
sparseValuesAccessorIntData);
|
||||
} else if (componentType == float.class) {
|
||||
AccessorFloatData sparseValuesAccessorFloatData =
|
||||
(AccessorFloatData) sparseValuesAccessorData;
|
||||
AccessorFloatData baseAccessorFloatData =
|
||||
(AccessorFloatData) baseAccessorData;
|
||||
AccessorFloatData denseAccessorFloatData =
|
||||
(AccessorFloatData) denseAccessorData;
|
||||
|
||||
substituteFloatAccessorData(
|
||||
denseAccessorFloatData,
|
||||
baseAccessorFloatData,
|
||||
sparseIndicesAccessorData,
|
||||
sparseValuesAccessorFloatData);
|
||||
} else {
|
||||
logger.warning("Invalid component type for accessor: "
|
||||
+ componentType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #substituteAccessorData}
|
||||
*
|
||||
* @param denseAccessorData The dense {@link AccessorData} to be filled
|
||||
* @param baseAccessorData The optional "base" {@link AccessorData}
|
||||
* @param sparseIndicesAccessorData The sparse indices {@link AccessorData}
|
||||
* @param sparseValuesAccessorData The sparse values {@link AccessorData}
|
||||
* @throws IllegalArgumentException If the sparseIndicesAccessorData does
|
||||
* not contain data with an integral type (byte, short, int).
|
||||
*/
|
||||
private static void substituteByteAccessorData(
|
||||
AccessorByteData denseAccessorData,
|
||||
AccessorByteData baseAccessorData,
|
||||
AccessorData sparseIndicesAccessorData,
|
||||
AccessorByteData sparseValuesAccessorData) {
|
||||
int numElements = denseAccessorData.getNumElements();
|
||||
int numComponentsPerElement =
|
||||
denseAccessorData.getNumComponentsPerElement();
|
||||
|
||||
if (baseAccessorData != null) {
|
||||
// Fill the dense AccessorData with the base data
|
||||
for (int e = 0; e < numElements; e++) {
|
||||
for (int c = 0; c < numComponentsPerElement; c++) {
|
||||
byte value = baseAccessorData.get(e, c);
|
||||
denseAccessorData.set(e, c, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the substitution based on the sparse indices and values
|
||||
int indices[] = extractIndices(sparseIndicesAccessorData);
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
int targetElementIndex = indices[i];
|
||||
for (int c = 0; c < numComponentsPerElement; c++) {
|
||||
byte substitution = sparseValuesAccessorData.get(i, c);
|
||||
denseAccessorData.set(targetElementIndex, c, substitution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #substituteAccessorData}
|
||||
*
|
||||
* @param denseAccessorData The dense {@link AccessorData} to be filled
|
||||
* @param baseAccessorData The optional "base" {@link AccessorData}
|
||||
* @param sparseIndicesAccessorData The sparse indices {@link AccessorData}
|
||||
* @param sparseValuesAccessorData The sparse values {@link AccessorData}
|
||||
* @throws IllegalArgumentException If the sparseIndicesAccessorData does
|
||||
* not contain data with an integral type (byte, short, int).
|
||||
*/
|
||||
private static void substituteShortAccessorData(
|
||||
AccessorShortData denseAccessorData,
|
||||
AccessorShortData baseAccessorData,
|
||||
AccessorData sparseIndicesAccessorData,
|
||||
AccessorShortData sparseValuesAccessorData) {
|
||||
int numElements = denseAccessorData.getNumElements();
|
||||
int numComponentsPerElement =
|
||||
denseAccessorData.getNumComponentsPerElement();
|
||||
|
||||
if (baseAccessorData != null) {
|
||||
// Fill the dense AccessorData with the base data
|
||||
for (int e = 0; e < numElements; e++) {
|
||||
for (int c = 0; c < numComponentsPerElement; c++) {
|
||||
short value = baseAccessorData.get(e, c);
|
||||
denseAccessorData.set(e, c, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the substitution based on the sparse indices and values
|
||||
int indices[] = extractIndices(sparseIndicesAccessorData);
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
int targetElementIndex = indices[i];
|
||||
for (int c = 0; c < numComponentsPerElement; c++) {
|
||||
short substitution = sparseValuesAccessorData.get(i, c);
|
||||
denseAccessorData.set(targetElementIndex, c, substitution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #substituteAccessorData}
|
||||
*
|
||||
* @param denseAccessorData The dense {@link AccessorData} to be filled
|
||||
* @param baseAccessorData The optional "base" {@link AccessorData}
|
||||
* @param sparseIndicesAccessorData The sparse indices {@link AccessorData}
|
||||
* @param sparseValuesAccessorData The sparse values {@link AccessorData}
|
||||
* @throws IllegalArgumentException If the sparseIndicesAccessorData does
|
||||
* not contain data with an integral type (byte, short, int).
|
||||
*/
|
||||
private static void substituteIntAccessorData(
|
||||
AccessorIntData denseAccessorData,
|
||||
AccessorIntData baseAccessorData,
|
||||
AccessorData sparseIndicesAccessorData,
|
||||
AccessorIntData sparseValuesAccessorData) {
|
||||
int numElements = denseAccessorData.getNumElements();
|
||||
int numComponentsPerElement =
|
||||
denseAccessorData.getNumComponentsPerElement();
|
||||
|
||||
if (baseAccessorData != null) {
|
||||
// Fill the dense AccessorData with the base data
|
||||
for (int e = 0; e < numElements; e++) {
|
||||
for (int c = 0; c < numComponentsPerElement; c++) {
|
||||
int value = baseAccessorData.get(e, c);
|
||||
denseAccessorData.set(e, c, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the substitution based on the sparse indices and values
|
||||
int indices[] = extractIndices(sparseIndicesAccessorData);
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
int targetElementIndex = indices[i];
|
||||
for (int c = 0; c < numComponentsPerElement; c++) {
|
||||
int substitution = sparseValuesAccessorData.get(i, c);
|
||||
denseAccessorData.set(targetElementIndex, c, substitution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link #substituteAccessorData}
|
||||
*
|
||||
* @param denseAccessorData The dense {@link AccessorData} to be filled
|
||||
* @param baseAccessorData The optional "base" {@link AccessorData}
|
||||
* @param sparseIndicesAccessorData The sparse indices {@link AccessorData}
|
||||
* @param sparseValuesAccessorData The sparse values {@link AccessorData}
|
||||
* @throws IllegalArgumentException If the sparseIndicesAccessorData does
|
||||
* not contain data with an integral type (byte, short, int).
|
||||
*/
|
||||
private static void substituteFloatAccessorData(
|
||||
AccessorFloatData denseAccessorData,
|
||||
AccessorFloatData baseAccessorData,
|
||||
AccessorData sparseIndicesAccessorData,
|
||||
AccessorFloatData sparseValuesAccessorData) {
|
||||
int numElements = denseAccessorData.getNumElements();
|
||||
int numComponentsPerElement =
|
||||
denseAccessorData.getNumComponentsPerElement();
|
||||
|
||||
if (baseAccessorData != null) {
|
||||
// Fill the dense AccessorData with the base data
|
||||
for (int e = 0; e < numElements; e++) {
|
||||
for (int c = 0; c < numComponentsPerElement; c++) {
|
||||
float value = baseAccessorData.get(e, c);
|
||||
denseAccessorData.set(e, c, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the substitution based on the sparse indices and values
|
||||
int indices[] = extractIndices(sparseIndicesAccessorData);
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
int targetElementIndex = indices[i];
|
||||
for (int c = 0; c < numComponentsPerElement; c++) {
|
||||
float substitution = sparseValuesAccessorData.get(i, c);
|
||||
denseAccessorData.set(targetElementIndex, c, substitution);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
import com.tacz.guns.client.animation.gltf.GltfConstants;
|
||||
|
||||
public class Accessors {
|
||||
/**
|
||||
* Private constructor to prevent instantiation
|
||||
*/
|
||||
private Accessors() {
|
||||
// Private constructor to prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of components that one element has for the given
|
||||
* accessor type. Valid parameters are
|
||||
* <pre><code>
|
||||
* "SCALAR" : 1
|
||||
* "VEC2" : 2
|
||||
* "VEC3" : 3
|
||||
* "VEC4" : 4
|
||||
* "MAT2" : 4
|
||||
* "MAT3" : 9
|
||||
* "MAT4" : 16
|
||||
* </code></pre>
|
||||
*
|
||||
* @param accessorType The accessor type.
|
||||
* @return The number of components
|
||||
* @throws IllegalArgumentException If the given type is none of the
|
||||
* valid parameters
|
||||
*/
|
||||
public static int getNumComponentsForAccessorType(String accessorType) {
|
||||
switch (accessorType) {
|
||||
case "SCALAR":
|
||||
return 1;
|
||||
case "VEC2":
|
||||
return 2;
|
||||
case "VEC3":
|
||||
return 3;
|
||||
case "VEC4":
|
||||
return 4;
|
||||
case "MAT2":
|
||||
return 4;
|
||||
case "MAT3":
|
||||
return 9;
|
||||
case "MAT4":
|
||||
return 16;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid accessor type: " + accessorType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that one component with the given
|
||||
* accessor component type consists of.
|
||||
* Valid parameters are
|
||||
* <pre><code>
|
||||
* GL_BYTE : 1
|
||||
* GL_UNSIGNED_BYTE : 1
|
||||
* GL_SHORT : 2
|
||||
* GL_UNSIGNED_SHORT : 2
|
||||
* GL_INT : 4
|
||||
* GL_UNSIGNED_INT : 4
|
||||
* GL_FLOAT : 4
|
||||
* </code></pre>
|
||||
*
|
||||
* @param componentType The component type
|
||||
* @return The number of bytes
|
||||
* @throws IllegalArgumentException If the given type is none of the
|
||||
* valid parameters
|
||||
*/
|
||||
public static int getNumBytesForAccessorComponentType(int componentType) {
|
||||
switch (componentType) {
|
||||
case GltfConstants.GL_BYTE:
|
||||
return 1;
|
||||
case GltfConstants.GL_UNSIGNED_BYTE:
|
||||
return 1;
|
||||
case GltfConstants.GL_SHORT:
|
||||
return 2;
|
||||
case GltfConstants.GL_UNSIGNED_SHORT:
|
||||
return 2;
|
||||
case GltfConstants.GL_INT:
|
||||
return 4;
|
||||
case GltfConstants.GL_UNSIGNED_INT:
|
||||
return 4;
|
||||
case GltfConstants.GL_FLOAT:
|
||||
return 4;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid accessor component type: " + componentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data type for the given accessor component type.
|
||||
* Valid parameters and their return values are
|
||||
* <pre><code>
|
||||
* GL_BYTE : byte.class
|
||||
* GL_UNSIGNED_BYTE : byte.class
|
||||
* GL_SHORT : short.class
|
||||
* GL_UNSIGNED_SHORT : short.class
|
||||
* GL_INT : int.class
|
||||
* GL_UNSIGNED_INT : int.class
|
||||
* GL_FLOAT : float.class
|
||||
* </code></pre>
|
||||
*
|
||||
* @param componentType The component type
|
||||
* @return The data type
|
||||
* @throws IllegalArgumentException If the given type is none of the
|
||||
* valid parameters
|
||||
*/
|
||||
public static Class<?> getDataTypeForAccessorComponentType(
|
||||
int componentType) {
|
||||
switch (componentType) {
|
||||
case GltfConstants.GL_BYTE:
|
||||
return byte.class;
|
||||
case GltfConstants.GL_UNSIGNED_BYTE:
|
||||
return byte.class;
|
||||
case GltfConstants.GL_SHORT:
|
||||
return short.class;
|
||||
case GltfConstants.GL_UNSIGNED_SHORT:
|
||||
return short.class;
|
||||
case GltfConstants.GL_INT:
|
||||
return int.class;
|
||||
case GltfConstants.GL_UNSIGNED_INT:
|
||||
return int.class;
|
||||
case GltfConstants.GL_FLOAT:
|
||||
return float.class;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid accessor component type: " + componentType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* www.javagl.de - JglTF
|
||||
*
|
||||
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.tacz.guns.client.animation.gltf.accessor;
|
||||
|
||||
/**
|
||||
* Methods to convert primitive arrays to arrays of Number objects
|
||||
*/
|
||||
class NumberArrays {
|
||||
/**
|
||||
* Private constructor to prevent instantiation
|
||||
*/
|
||||
private NumberArrays() {
|
||||
// Private constructor to prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given array into a Number array
|
||||
*
|
||||
* @param array The array
|
||||
* @return The result
|
||||
*/
|
||||
static Number[] asNumbers(int array[]) {
|
||||
Number result[] = new Number[array.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
result[i] = array[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given array into a Number array
|
||||
*
|
||||
* @param array The array
|
||||
* @return The result
|
||||
*/
|
||||
static Number[] asNumbers(long array[]) {
|
||||
Number result[] = new Number[array.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
result[i] = array[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given array into a Number array
|
||||
*
|
||||
* @param array The array
|
||||
* @return The result
|
||||
*/
|
||||
static Number[] asNumbers(float array[]) {
|
||||
Number result[] = new Number[array.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
result[i] = array[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.tacz.guns.client.animation.internal;
|
||||
|
||||
public final class GunAnimationConstant {
|
||||
/**
|
||||
* 空仓挂机
|
||||
*/
|
||||
public static final String STATIC_BOLT_CAUGHT_ANIMATION = "static_bolt_caught";
|
||||
/**
|
||||
* 默认持枪动作
|
||||
*/
|
||||
public static final String STATIC_IDLE_ANIMATION = "static_idle";
|
||||
/**
|
||||
* 射击
|
||||
*/
|
||||
public static final String SHOOT_ANIMATION = "shoot";
|
||||
/**
|
||||
* 空仓换弹
|
||||
*/
|
||||
public static final String RELOAD_EMPTY_ANIMATION = "reload_empty";
|
||||
/**
|
||||
* 装了扩容弹匣后的空仓换弹
|
||||
*/
|
||||
public static final String RELOAD_EMPTY_EXTENDED_ANIMATION = "reload_empty_extended";
|
||||
/**
|
||||
* 拉栓
|
||||
*/
|
||||
public static final String BOLT_ANIMATION = "bolt";
|
||||
/**
|
||||
* 战术换弹
|
||||
*/
|
||||
public static final String RELOAD_TACTICAL_ANIMATION = "reload_tactical";
|
||||
/**
|
||||
* 装了扩容弹匣后的战术换弹
|
||||
*/
|
||||
public static final String RELOAD_TACTICAL_EXTENDED_ANIMATION = "reload_tactical_extended";
|
||||
/**
|
||||
* 切枪动画,切入
|
||||
*/
|
||||
public static final String DRAW_ANIMATION = "draw";
|
||||
/**
|
||||
* 切枪动画,切出
|
||||
*/
|
||||
public static final String PUT_AWAY_ANIMATION = "put_away";
|
||||
/**
|
||||
* 检视动画,非空仓
|
||||
*/
|
||||
public static final String INSPECT_ANIMATION = "inspect";
|
||||
/**
|
||||
* 检视动画,空仓
|
||||
*/
|
||||
public static final String INSPECT_EMPTY_ANIMATION = "inspect_empty";
|
||||
/**
|
||||
* 静止时的动画
|
||||
*/
|
||||
public static final String IDLE_ANIMATION = "idle";
|
||||
/**
|
||||
* 跑步动画,起始部分
|
||||
*/
|
||||
public static final String RUN_START_ANIMATION = "run_start";
|
||||
/**
|
||||
* 跑步动画
|
||||
*/
|
||||
public static final String RUN_LOOP_ANIMATION = "run";
|
||||
/**
|
||||
* 跑步持枪动画
|
||||
*/
|
||||
public static final String RUN_HOLD_ANIMATION = "run_hold";
|
||||
/**
|
||||
* 跑步动画,结束部分
|
||||
*/
|
||||
public static final String RUN_END_ANIMATION = "run_end";
|
||||
/**
|
||||
* 向前行走动画
|
||||
*/
|
||||
public static final String WALK_FORWARD_ANIMATION = "walk_forward";
|
||||
/**
|
||||
* 侧向行走动画
|
||||
*/
|
||||
public static final String WALK_SIDEWAY_ANIMATION = "walk_sideway";
|
||||
/**
|
||||
* 后退行走动画
|
||||
*/
|
||||
public static final String WALK_BACKWARD_ANIMATION = "walk_backward";
|
||||
/**
|
||||
* 行走时瞄准动画
|
||||
*/
|
||||
public static final String WALK_AIMING_ANIMATION = "walk_aiming";
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
package com.tacz.guns.client.animation.internal;
|
||||
|
||||
import com.tacz.guns.client.animation.AnimationController;
|
||||
import com.tacz.guns.client.animation.AnimationPlan;
|
||||
import com.tacz.guns.client.animation.ObjectAnimation;
|
||||
import com.tacz.guns.client.animation.ObjectAnimationRunner;
|
||||
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import net.minecraft.client.player.Input;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
import static com.tacz.guns.client.animation.internal.GunAnimationConstant.*;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class GunAnimationStateMachine {
|
||||
/**
|
||||
* 下面两个变量需要放置在类的最顶端,
|
||||
* IDEA 如果格式化修改了这两个变量位置,会导致游戏内动画异常
|
||||
*/
|
||||
protected static final IntSet BLENDING_TRACKS = new IntLinkedOpenHashSet();
|
||||
protected static int TRACK_INDEX_TOP = 0;
|
||||
|
||||
/**
|
||||
* 射击轨道 12 个,支持 0.5 秒的射击动画在 rpm 1200 以内播放
|
||||
*/
|
||||
public static final int[] SHOOTING_TRACKS = {
|
||||
blendingTrack(), blendingTrack(), blendingTrack(), blendingTrack(),
|
||||
blendingTrack(), blendingTrack(), blendingTrack(), blendingTrack(),
|
||||
blendingTrack(), blendingTrack(), blendingTrack(), blendingTrack()
|
||||
};
|
||||
public static final int MOVEMENT_TRACK = blendingTrack();
|
||||
public static final int MAIN_TRACK = staticTrack();
|
||||
public static final int BOLT_CATCH_STATIC_TRACK = staticTrack();
|
||||
public static final int HOLDING_POSE_STATIC_TRACK = staticTrack();
|
||||
|
||||
protected AnimationController controller;
|
||||
protected boolean noAmmo = false;
|
||||
protected boolean magExtended = false;
|
||||
protected boolean onGround = true;
|
||||
protected boolean pauseWalkAndRun = false;
|
||||
protected boolean isAiming = false;
|
||||
/**
|
||||
* 记录开始冲刺时玩家的 walk distance,以便让冲刺动画有统一的开头
|
||||
*/
|
||||
protected float baseDistanceWalked = 0.0f;
|
||||
protected float keepDistanceWalked = 0.0f;
|
||||
protected WalkDirection lastWalkDirection = WalkDirection.NONE;
|
||||
protected boolean isWalkAiming = false;
|
||||
|
||||
public GunAnimationStateMachine(AnimationController controller) {
|
||||
this.controller = controller;
|
||||
for (int i = 0; i < TRACK_INDEX_TOP; i++) {
|
||||
controller.setBlending(i, BLENDING_TRACKS.contains(i));
|
||||
}
|
||||
}
|
||||
|
||||
protected static int staticTrack() {
|
||||
return TRACK_INDEX_TOP++;
|
||||
}
|
||||
|
||||
protected static int blendingTrack() {
|
||||
int track = TRACK_INDEX_TOP++;
|
||||
BLENDING_TRACKS.add(track);
|
||||
return track;
|
||||
}
|
||||
|
||||
public void onGunShoot() {
|
||||
// 开火动画应当打断检视动画
|
||||
if (isPlayingInspectAnimation()) {
|
||||
controller.removeAnimation(MAIN_TRACK);
|
||||
}
|
||||
for (int track : SHOOTING_TRACKS) {
|
||||
if (tryRunShootAnimation(track)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
controller.runAnimation(SHOOTING_TRACKS[0], SHOOT_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_STOP, 0f);
|
||||
}
|
||||
|
||||
public void onGunReload() {
|
||||
if (noAmmo) {
|
||||
if (magExtended && controller.containPrototype(RELOAD_EMPTY_EXTENDED_ANIMATION)) {
|
||||
controller.runAnimation(MAIN_TRACK, RELOAD_EMPTY_EXTENDED_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_STOP, 0.2f);
|
||||
} else {
|
||||
controller.runAnimation(MAIN_TRACK, RELOAD_EMPTY_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_STOP, 0.2f);
|
||||
}
|
||||
} else {
|
||||
if (magExtended && controller.containPrototype(RELOAD_TACTICAL_EXTENDED_ANIMATION)) {
|
||||
controller.runAnimation(MAIN_TRACK, RELOAD_TACTICAL_EXTENDED_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_STOP, 0.2f);
|
||||
} else {
|
||||
controller.runAnimation(MAIN_TRACK, RELOAD_TACTICAL_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_STOP, 0.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onGunBolt() {
|
||||
controller.runAnimation(MAIN_TRACK, BOLT_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_STOP, 0.2f);
|
||||
}
|
||||
|
||||
public void onGunDraw() {
|
||||
controller.runAnimation(MOVEMENT_TRACK, IDLE_ANIMATION, ObjectAnimation.PlayType.LOOP, 0);
|
||||
lastWalkDirection = WalkDirection.NONE;
|
||||
controller.runAnimation(MAIN_TRACK, DRAW_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_STOP, 0);
|
||||
}
|
||||
|
||||
public void onShooterRun(float walkDist) {
|
||||
if (isPlayingRunIntroOrLoop()) {
|
||||
if (!onGround && !isPlayingRunHold()) {
|
||||
controller.runAnimation(MOVEMENT_TRACK, RUN_HOLD_ANIMATION, ObjectAnimation.PlayType.LOOP, 0.6f);
|
||||
isWalkAiming = false;
|
||||
lastWalkDirection = WalkDirection.NONE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (onGround) {
|
||||
ArrayDeque<AnimationPlan> deque = new ArrayDeque<>();
|
||||
if (!isPlayingRunHold()) {
|
||||
deque.add(new AnimationPlan(RUN_START_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_HOLD, 0.2f));
|
||||
}
|
||||
deque.add(new AnimationPlan(RUN_LOOP_ANIMATION, ObjectAnimation.PlayType.LOOP, 0.2f));
|
||||
controller.queueAnimation(MOVEMENT_TRACK, deque);
|
||||
isWalkAiming = false;
|
||||
lastWalkDirection = WalkDirection.NONE;
|
||||
baseDistanceWalked = walkDist;
|
||||
}
|
||||
}
|
||||
|
||||
public void onGunPutAway(float putAwayTimeS) {
|
||||
controller.runAnimation(MAIN_TRACK, PUT_AWAY_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_HOLD, putAwayTimeS * 0.75f);
|
||||
// 改变 put away 动画的进度,如果刚刚切枪不久,则收枪应当更快。
|
||||
ObjectAnimationRunner runner = controller.getAnimation(MAIN_TRACK);
|
||||
if (runner != null) {
|
||||
if (runner.isRunning() && PUT_AWAY_ANIMATION.equals(runner.getAnimation().name)) {
|
||||
long progress = (long) (Math.max(runner.getAnimation().getMaxEndTimeS() - putAwayTimeS, 0) * 1e9);
|
||||
runner.setProgressNs(progress);
|
||||
return;
|
||||
}
|
||||
if (runner.getTransitionTo() != null && PUT_AWAY_ANIMATION.equals(runner.getTransitionTo().getAnimation().name)) {
|
||||
long progress = (long) (Math.max(runner.getTransitionTo().getAnimation().getMaxEndTimeS() - putAwayTimeS, 0) * 1e9);
|
||||
runner.getTransitionTo().setProgressNs(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onShooterIdle() {
|
||||
if (isPlayingIdleAnimation()) {
|
||||
return;
|
||||
}
|
||||
ObjectAnimationRunner runner = controller.getAnimation(MOVEMENT_TRACK);
|
||||
if (runner != null && (runner.isRunning() || runner.isTransitioning())) {
|
||||
if (!isPlayingWalkAnimation() && !isPlayingRunIntroOrLoop() && !isPlayingRunHold()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
isWalkAiming = false;
|
||||
lastWalkDirection = WalkDirection.NONE;
|
||||
ArrayDeque<AnimationPlan> deque = new ArrayDeque<>();
|
||||
if (isPlayingRunIntroOrLoop()) {
|
||||
deque.add(new AnimationPlan(RUN_END_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_HOLD, 0.3f));
|
||||
}
|
||||
deque.add(new AnimationPlan(IDLE_ANIMATION, ObjectAnimation.PlayType.LOOP, 0.4f));
|
||||
controller.queueAnimation(MOVEMENT_TRACK, deque);
|
||||
}
|
||||
|
||||
public void onGunInspect() {
|
||||
if (noAmmo) {
|
||||
controller.runAnimation(MAIN_TRACK, INSPECT_EMPTY_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_STOP, 0.2f);
|
||||
} else {
|
||||
controller.runAnimation(MAIN_TRACK, INSPECT_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_STOP, 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
public void onShooterWalk(Input input, float walkDist) {
|
||||
if (!onGround && !isPlayingIdleAnimation()) {
|
||||
controller.runAnimation(MOVEMENT_TRACK, IDLE_ANIMATION, ObjectAnimation.PlayType.LOOP, 0.6f);
|
||||
isWalkAiming = false;
|
||||
lastWalkDirection = WalkDirection.NONE;
|
||||
return;
|
||||
}
|
||||
if (onGround) {
|
||||
// 如果一边走路一边瞄准,则需要播放特定的动画 WALK_AIMING_ANIMATION。
|
||||
if (isAiming) {
|
||||
if (isWalkAiming) {
|
||||
return;
|
||||
}
|
||||
isWalkAiming = true;
|
||||
lastWalkDirection = WalkDirection.NONE;
|
||||
ArrayDeque<AnimationPlan> deque = new ArrayDeque<>();
|
||||
if (isPlayingRunIntroOrLoop() || isPlayingRunHold()) {
|
||||
deque.add(new AnimationPlan(RUN_END_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_HOLD, 0.3f));
|
||||
}
|
||||
deque.add(new AnimationPlan(WALK_AIMING_ANIMATION, ObjectAnimation.PlayType.LOOP, 0.3f));
|
||||
controller.queueAnimation(MOVEMENT_TRACK, deque);
|
||||
baseDistanceWalked = walkDist;
|
||||
return;
|
||||
}
|
||||
WalkDirection direction = WalkDirection.fromInput(input);
|
||||
// 同一个方向的动画播放只需要触发一次。
|
||||
if (direction == lastWalkDirection) {
|
||||
return;
|
||||
}
|
||||
isWalkAiming = false;
|
||||
lastWalkDirection = direction;
|
||||
ArrayDeque<AnimationPlan> deque = new ArrayDeque<>();
|
||||
if (isPlayingRunIntroOrLoop() || isPlayingRunHold()) {
|
||||
deque.add(new AnimationPlan(RUN_END_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_HOLD, 0.3f));
|
||||
}
|
||||
switch (direction) {
|
||||
case FORWARD ->
|
||||
deque.add(new AnimationPlan(WALK_FORWARD_ANIMATION, ObjectAnimation.PlayType.LOOP, 0.4f));
|
||||
case BACKWARD ->
|
||||
deque.add(new AnimationPlan(WALK_BACKWARD_ANIMATION, ObjectAnimation.PlayType.LOOP, 0.4f));
|
||||
case SIDE_WAY ->
|
||||
deque.add(new AnimationPlan(WALK_SIDEWAY_ANIMATION, ObjectAnimation.PlayType.LOOP, 0.4f));
|
||||
}
|
||||
controller.queueAnimation(MOVEMENT_TRACK, deque);
|
||||
baseDistanceWalked = walkDist;
|
||||
}
|
||||
}
|
||||
|
||||
public void onGunFireSelect() {
|
||||
// TODO:切换开火方式的动画
|
||||
}
|
||||
|
||||
public void onGunCatchBolt() {
|
||||
if (!isPlayingAnimation(BOLT_CATCH_STATIC_TRACK, STATIC_BOLT_CAUGHT_ANIMATION)) {
|
||||
controller.runAnimation(BOLT_CATCH_STATIC_TRACK, STATIC_BOLT_CAUGHT_ANIMATION, ObjectAnimation.PlayType.LOOP, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public GunAnimationStateMachine setNoAmmo(boolean noAmmo) {
|
||||
this.noAmmo = noAmmo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GunAnimationStateMachine setMagExtended(boolean magExtended) {
|
||||
this.magExtended = magExtended;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GunAnimationStateMachine setOnGround(boolean onGround) {
|
||||
this.onGround = onGround;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GunAnimationStateMachine setPauseWalkAndRun(boolean pause) {
|
||||
this.pauseWalkAndRun = pause;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GunAnimationStateMachine setAiming(boolean isAiming) {
|
||||
this.isAiming = isAiming;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AnimationController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 返回当前正在播放的动画是否需要隐藏准心。
|
||||
*/
|
||||
public boolean shouldHideCrossHair() {
|
||||
if (isPlayingInspectAnimation()) {
|
||||
return true;
|
||||
}
|
||||
return isPlayingRunHold() || isPlayingRunLoop();
|
||||
}
|
||||
|
||||
public void update(float partialTicks, Entity entity) {
|
||||
ObjectAnimationRunner runner = controller.getAnimation(MOVEMENT_TRACK);
|
||||
if (runner != null) {
|
||||
// 为了让冲刺和行走动画和原版的 viewBobbing 相适应,需要手动更新冲刺动画的进度
|
||||
// 当前动画是run或者正在过渡向 run 动画的时候,就手动设置 run 动画的进度。
|
||||
float deltaDistanceWalked = entity.walkDist - entity.walkDistO;
|
||||
float distanceWalked;
|
||||
if (pauseWalkAndRun) {
|
||||
// 保持 distanceWalked 与 keepDistanceWalked 相同,即不随时间增长
|
||||
distanceWalked = keepDistanceWalked;
|
||||
baseDistanceWalked = entity.walkDist + deltaDistanceWalked * partialTicks - keepDistanceWalked;
|
||||
} else {
|
||||
// distanceWalked 与 keepDistanceWalked 一同随时间增长
|
||||
distanceWalked = entity.walkDist + deltaDistanceWalked * partialTicks - baseDistanceWalked;
|
||||
keepDistanceWalked = distanceWalked;
|
||||
}
|
||||
String animationName = runner.getAnimation().name;
|
||||
if ((isNamedWalkAnimation(animationName) || RUN_LOOP_ANIMATION.equals(animationName)) && runner.isRunning()) {
|
||||
runner.setProgressNs((long) (runner.getAnimation().getMaxEndTimeS() * (distanceWalked % 2f) / 2f * 1e9f));
|
||||
}
|
||||
if (runner.isTransitioning() && runner.getTransitionTo() != null) {
|
||||
animationName = runner.getTransitionTo().getAnimation().name;
|
||||
if (isNamedWalkAnimation(animationName) || RUN_LOOP_ANIMATION.equals(animationName)) {
|
||||
runner.getTransitionTo().setProgressNs((long) (runner.getTransitionTo().getAnimation().getMaxEndTimeS() * (distanceWalked % 2f) / 2f * 1e9f));
|
||||
}
|
||||
}
|
||||
}
|
||||
controller.update();
|
||||
}
|
||||
|
||||
public boolean isPlayingAnimation(int track, @Nonnull String... names) {
|
||||
ObjectAnimationRunner runner = controller.getAnimation(track);
|
||||
if (runner != null) {
|
||||
String animationName = runner.getAnimation().name;
|
||||
if (runner.isRunning()) {
|
||||
for (String name : names) {
|
||||
if (name.equals(animationName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (runner.isTransitioning() && runner.getTransitionTo() != null) {
|
||||
animationName = runner.getTransitionTo().getAnimation().name;
|
||||
for (String name : names) {
|
||||
if (name.equals(animationName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isPlayingRunAnimation() {
|
||||
return isPlayingAnimation(MOVEMENT_TRACK, RUN_START_ANIMATION, RUN_LOOP_ANIMATION, RUN_HOLD_ANIMATION, RUN_END_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingRunIntroOrLoop() {
|
||||
return isPlayingAnimation(MOVEMENT_TRACK, RUN_LOOP_ANIMATION, RUN_START_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingRunStart() {
|
||||
return isPlayingAnimation(MOVEMENT_TRACK, RUN_START_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingRunLoop() {
|
||||
return isPlayingAnimation(MOVEMENT_TRACK, RUN_LOOP_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingRunHold() {
|
||||
return isPlayingAnimation(MOVEMENT_TRACK, RUN_HOLD_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingRunEnd() {
|
||||
return isPlayingAnimation(MOVEMENT_TRACK, RUN_END_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingWalkAnimation() {
|
||||
return isPlayingAnimation(MOVEMENT_TRACK, WALK_FORWARD_ANIMATION, WALK_BACKWARD_ANIMATION, WALK_SIDEWAY_ANIMATION, WALK_AIMING_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingIdleAnimation() {
|
||||
return isPlayingAnimation(MOVEMENT_TRACK, IDLE_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingShootAnimation() {
|
||||
for (int track : SHOOTING_TRACKS) {
|
||||
ObjectAnimationRunner runner = controller.getAnimation(track);
|
||||
if (runner != null) {
|
||||
if (runner.isRunning()) {
|
||||
return true;
|
||||
}
|
||||
if (runner.isTransitioning() && runner.getTransitionTo() != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isPlayingInspectAnimation() {
|
||||
return isPlayingAnimation(MAIN_TRACK, INSPECT_ANIMATION, INSPECT_EMPTY_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingReloadAnimation() {
|
||||
return isPlayingAnimation(MAIN_TRACK, RELOAD_EMPTY_ANIMATION, RELOAD_TACTICAL_ANIMATION);
|
||||
}
|
||||
|
||||
public boolean isPlayingDrawAnimation() {
|
||||
return isPlayingAnimation(MAIN_TRACK, DRAW_ANIMATION);
|
||||
}
|
||||
|
||||
public void onGunReleaseBolt() {
|
||||
controller.removeAnimation(BOLT_CATCH_STATIC_TRACK);
|
||||
}
|
||||
|
||||
public void onIdleHoldingPose() {
|
||||
if (!isPlayingAnimation(HOLDING_POSE_STATIC_TRACK, STATIC_IDLE_ANIMATION)) {
|
||||
controller.runAnimation(HOLDING_POSE_STATIC_TRACK, STATIC_IDLE_ANIMATION, ObjectAnimation.PlayType.LOOP, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNamedWalkAnimation(String animationName) {
|
||||
return WALK_SIDEWAY_ANIMATION.equals(animationName) || WALK_FORWARD_ANIMATION.equals(animationName) || WALK_BACKWARD_ANIMATION.equals(animationName)
|
||||
|| WALK_AIMING_ANIMATION.equals(animationName);
|
||||
}
|
||||
|
||||
private boolean tryRunShootAnimation(int track) {
|
||||
ObjectAnimationRunner runner = controller.getAnimation(track);
|
||||
if (runner != null && runner.isRunning() && SHOOT_ANIMATION.equals(runner.getAnimation().name)) {
|
||||
return false;
|
||||
}
|
||||
if (runner != null && runner.getTransitionTo() != null && SHOOT_ANIMATION.equals(runner.getTransitionTo().getAnimation().name)) {
|
||||
return false;
|
||||
}
|
||||
controller.runAnimation(track, SHOOT_ANIMATION, ObjectAnimation.PlayType.PLAY_ONCE_HOLD, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.tacz.guns.client.animation.internal;
|
||||
|
||||
import net.minecraft.client.player.Input;
|
||||
|
||||
public enum WalkDirection {
|
||||
FORWARD,
|
||||
SIDE_WAY,
|
||||
BACKWARD,
|
||||
NONE;
|
||||
|
||||
public static WalkDirection fromInput(Input input) {
|
||||
if (input.up) {
|
||||
return FORWARD;
|
||||
}
|
||||
if (input.down) {
|
||||
return BACKWARD;
|
||||
}
|
||||
if (input.left || input.right) {
|
||||
return SIDE_WAY;
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.tacz.guns.client.animation.interpolator;
|
||||
|
||||
import com.tacz.guns.client.animation.AnimationChannelContent;
|
||||
import com.tacz.guns.client.animation.AnimationChannelContent.LerpMode;
|
||||
import com.tacz.guns.util.math.MathUtil;
|
||||
|
||||
public class CustomInterpolator implements Interpolator {
|
||||
private AnimationChannelContent content;
|
||||
|
||||
@Override
|
||||
public void compile(AnimationChannelContent content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate(int indexFrom, int indexTo, float alpha, float[] result) {
|
||||
LerpMode fromLerpMode = content.lerpModes[indexFrom];
|
||||
LerpMode toLerpMode = content.lerpModes[indexTo];
|
||||
if (fromLerpMode == LerpMode.SPHERICAL_LINEAR && toLerpMode == LerpMode.SPHERICAL_LINEAR) {
|
||||
// 球面线性插值
|
||||
this.doSphericalLinearLerp(indexFrom, indexTo, alpha, result);
|
||||
}
|
||||
if (fromLerpMode == LerpMode.SPHERICAL_CATMULLROM || toLerpMode == LerpMode.SPHERICAL_CATMULLROM) {
|
||||
// 球面 Catmull-Rom 插值
|
||||
this.doSphericalCatmullRomLerp(indexFrom, indexTo, alpha, result);
|
||||
} else if (fromLerpMode == LerpMode.CATMULLROM || toLerpMode == LerpMode.CATMULLROM) {
|
||||
// Catmull-Rom 插值
|
||||
this.doCatmullromLerp(indexFrom, indexTo, alpha, result);
|
||||
} else {
|
||||
// 其他情况的插值计算
|
||||
this.doOtherLerp(indexFrom, indexTo, alpha, result);
|
||||
}
|
||||
}
|
||||
|
||||
private void doOtherLerp(int indexFrom, int indexTo, float alpha, float[] result) {
|
||||
// 如果动画值有 6 个,后三个为 Post 数值,用于插值起点
|
||||
int offset = content.values[indexFrom].length == 6 ? 3 : 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (indexFrom == indexTo) {
|
||||
result[i] = content.values[indexFrom][i + offset];
|
||||
} else {
|
||||
result[i] = content.values[indexFrom][i + offset] * (1 - alpha) + content.values[indexTo][i] * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doCatmullromLerp(int indexFrom, int indexTo, float alpha, float[] result) {
|
||||
if (content.values.length == 1) {
|
||||
result[0] = content.values[0][0];
|
||||
result[1] = content.values[0][1];
|
||||
result[2] = content.values[0][2];
|
||||
return;
|
||||
}
|
||||
float[] vx = new float[4];
|
||||
float[] vy = new float[4];
|
||||
float[] vz = new float[4];
|
||||
int prev = indexFrom == 0 ? 0 : indexFrom - 1;
|
||||
int next = indexTo == (content.values.length - 1) ? (content.values.length - 1) : indexTo + 1;
|
||||
int prevOffset = content.values[prev].length == 6 ? 3 : 0;
|
||||
vx[0] = content.values[prev][prevOffset];
|
||||
vy[0] = content.values[prev][1 + prevOffset];
|
||||
vz[0] = content.values[prev][2 + prevOffset];
|
||||
vx[1] = content.values[indexFrom][0];
|
||||
vy[1] = content.values[indexFrom][1];
|
||||
vz[1] = content.values[indexFrom][2];
|
||||
vx[2] = content.values[indexTo][0];
|
||||
vy[2] = content.values[indexTo][1];
|
||||
vz[2] = content.values[indexTo][2];
|
||||
vx[3] = content.values[next][0];
|
||||
vy[3] = content.values[next][1];
|
||||
vz[3] = content.values[next][2];
|
||||
// 这里用的是三次样条插值,主要是为了和 BlockBench 中的表现贴合。
|
||||
// BlockBench 中调用的是 THREE.SplineCurve,其实现是三次样条插值。如果用 Catmull-Rom 插值,区别会比较大。
|
||||
result[0] = MathUtil.splineCurve(vx, 0.5f, alpha);
|
||||
result[1] = MathUtil.splineCurve(vy, 0.5f, alpha);
|
||||
result[2] = MathUtil.splineCurve(vz, 0.5f, alpha);
|
||||
}
|
||||
|
||||
private void doSphericalLinearLerp(int indexFrom, int indexTo, float alpha, float[] result) {
|
||||
if (content.values.length == 1) {
|
||||
result[0] = content.values[0][0];
|
||||
result[1] = content.values[0][1];
|
||||
result[2] = content.values[0][2];
|
||||
result[3] = content.values[0][3];
|
||||
return;
|
||||
}
|
||||
// 如果旋转值有 8 个,后四个为 Post 数值,用于插值起点
|
||||
int offset = content.values[indexFrom].length == 8 ? 4 : 0;
|
||||
float ax = content.values[indexFrom][offset];
|
||||
float ay = content.values[indexFrom][1 + offset];
|
||||
float az = content.values[indexFrom][2 + offset];
|
||||
float aw = content.values[indexFrom][3 + offset];
|
||||
float bx = content.values[indexTo][0];
|
||||
float by = content.values[indexTo][1];
|
||||
float bz = content.values[indexTo][2];
|
||||
float bw = content.values[indexTo][3];
|
||||
|
||||
float dot = ax * bx + ay * by + az * bz + aw * bw;
|
||||
if (dot < 0) {
|
||||
bx = -bx;
|
||||
by = -by;
|
||||
bz = -bz;
|
||||
bw = -bw;
|
||||
dot = -dot;
|
||||
}
|
||||
float epsilon = 1e-6f;
|
||||
float s0, s1;
|
||||
if ((1.0 - dot) > epsilon) {
|
||||
float omega = (float) Math.acos(dot);
|
||||
float invSinOmega = 1.0f / (float) Math.sin(omega);
|
||||
s0 = (float) Math.sin((1.0 - alpha) * omega) * invSinOmega;
|
||||
s1 = (float) Math.sin(alpha * omega) * invSinOmega;
|
||||
} else {
|
||||
s0 = 1.0f - alpha;
|
||||
s1 = alpha;
|
||||
}
|
||||
float rx = s0 * ax + s1 * bx;
|
||||
float ry = s0 * ay + s1 * by;
|
||||
float rz = s0 * az + s1 * bz;
|
||||
float rw = s0 * aw + s1 * bw;
|
||||
result[0] = rx;
|
||||
result[1] = ry;
|
||||
result[2] = rz;
|
||||
result[3] = rw;
|
||||
}
|
||||
|
||||
private void doSphericalCatmullRomLerp(int indexFrom, int indexTo, float alpha, float[] result) {
|
||||
if (content.values.length == 1) {
|
||||
result[0] = content.values[0][0];
|
||||
result[1] = content.values[0][1];
|
||||
result[2] = content.values[0][2];
|
||||
result[3] = content.values[0][3];
|
||||
return;
|
||||
}
|
||||
int prev = indexFrom == 0 ? 0 : indexFrom - 1;
|
||||
int next = indexTo == (content.values.length - 1) ? (content.values.length - 1) : indexTo + 1;
|
||||
int prevOffset = content.values[prev].length == 8 ? 4 : 0;
|
||||
float[] prevValue = content.values[prev];
|
||||
float[] q0 = new float[]{prevValue[prevOffset], prevValue[1 + prevOffset], prevValue[2 + prevOffset], prevValue[3 + prevOffset]};
|
||||
// 这里用的是三次样条插值,主要是为了和 BlockBench 中的表现贴合。
|
||||
// BlockBench 中调用的是 THREE.SplineCurve,其实现是三次样条插值。如果用 Catmull-Rom 插值,区别会比较大。
|
||||
float[] r = MathUtil.quaternionSplineCurve(new float[][]{q0, content.values[indexFrom], content.values[indexTo], content.values[next]}, 0.5f, alpha);
|
||||
result[0] = r[0];
|
||||
result[1] = r[1];
|
||||
result[2] = r[2];
|
||||
result[3] = r[3];
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomInterpolator clone() {
|
||||
try {
|
||||
CustomInterpolator interpolator = (CustomInterpolator) super.clone();
|
||||
interpolator.content = this.content;
|
||||
return interpolator;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.tacz.guns.client.animation.interpolator;
|
||||
|
||||
import com.tacz.guns.client.animation.AnimationChannelContent;
|
||||
|
||||
public interface Interpolator extends Cloneable {
|
||||
void compile(AnimationChannelContent content);
|
||||
|
||||
void interpolate(int indexFrom, int indexTo, float alpha, float[] result);
|
||||
|
||||
Interpolator clone();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.tacz.guns.client.animation.interpolator;
|
||||
|
||||
public class InterpolatorUtil {
|
||||
public static Interpolator fromInterpolation(InterpolatorType interpolation) {
|
||||
switch (interpolation) {
|
||||
case SPLINE -> {
|
||||
return new Spline();
|
||||
}
|
||||
case STEP -> {
|
||||
return new Step();
|
||||
}
|
||||
case SLERP -> {
|
||||
return new SLerp();
|
||||
}
|
||||
default -> {
|
||||
return new Linear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum InterpolatorType {
|
||||
LINEAR,
|
||||
SLERP,
|
||||
SPLINE,
|
||||
STEP
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.tacz.guns.client.animation.interpolator;
|
||||
|
||||
import com.tacz.guns.client.animation.AnimationChannelContent;
|
||||
|
||||
public class Linear implements Interpolator {
|
||||
private AnimationChannelContent content;
|
||||
|
||||
@Override
|
||||
public void compile(AnimationChannelContent content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate(int indexFrom, int indexTo, float alpha, float[] result) {
|
||||
// 如果动画值有 6 个,后三个为 Post 数值,用于插值起点
|
||||
int offset = content.values[indexFrom].length == 6 ? 3 : 0;
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
if (indexFrom == indexTo) {
|
||||
result[i] = content.values[indexFrom][i + offset];
|
||||
} else {
|
||||
result[i] = content.values[indexFrom][i + offset] * (1 - alpha) + content.values[indexTo][i] * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Linear clone() {
|
||||
try {
|
||||
Linear linear = (Linear) super.clone();
|
||||
linear.content = this.content;
|
||||
return linear;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.tacz.guns.client.animation.interpolator;
|
||||
|
||||
import com.tacz.guns.client.animation.AnimationChannelContent;
|
||||
|
||||
/**
|
||||
* 用于四元数的线性插值。
|
||||
*/
|
||||
public class SLerp implements Interpolator {
|
||||
private AnimationChannelContent content;
|
||||
|
||||
@Override
|
||||
public void compile(AnimationChannelContent content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate(int indexFrom, int indexTo, float alpha, float[] result) {
|
||||
// 如果旋转值有 8 个,后四个为 Post 数值,用于插值起点
|
||||
int offset = content.values[indexFrom].length == 8 ? 4 : 0;
|
||||
float ax = content.values[indexFrom][offset];
|
||||
float ay = content.values[indexFrom][1 + offset];
|
||||
float az = content.values[indexFrom][2 + offset];
|
||||
float aw = content.values[indexFrom][3 + offset];
|
||||
float bx = indexFrom == indexTo ? content.values[indexFrom][offset] : content.values[indexTo][0];
|
||||
float by = indexFrom == indexTo ? content.values[indexFrom][1 + offset] : content.values[indexTo][1];
|
||||
float bz = indexFrom == indexTo ? content.values[indexFrom][2 + offset] : content.values[indexTo][2];
|
||||
float bw = indexFrom == indexTo ? content.values[indexFrom][3 + offset] : content.values[indexTo][3];
|
||||
|
||||
float dot = ax * bx + ay * by + az * bz + aw * bw;
|
||||
if (dot < 0) {
|
||||
bx = -bx;
|
||||
by = -by;
|
||||
bz = -bz;
|
||||
bw = -bw;
|
||||
dot = -dot;
|
||||
}
|
||||
float epsilon = 1e-6f;
|
||||
float s0, s1;
|
||||
if ((1.0 - dot) > epsilon) {
|
||||
float omega = (float) Math.acos(dot);
|
||||
float invSinOmega = 1.0f / (float) Math.sin(omega);
|
||||
s0 = (float) Math.sin((1.0 - alpha) * omega) * invSinOmega;
|
||||
s1 = (float) Math.sin(alpha * omega) * invSinOmega;
|
||||
} else {
|
||||
s0 = 1.0f - alpha;
|
||||
s1 = alpha;
|
||||
}
|
||||
float rx = s0 * ax + s1 * bx;
|
||||
float ry = s0 * ay + s1 * by;
|
||||
float rz = s0 * az + s1 * bz;
|
||||
float rw = s0 * aw + s1 * bw;
|
||||
result[0] = rx;
|
||||
result[1] = ry;
|
||||
result[2] = rz;
|
||||
result[3] = rw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SLerp clone() {
|
||||
try {
|
||||
SLerp sLerp = (SLerp) super.clone();
|
||||
sLerp.content = this.content;
|
||||
return sLerp;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.tacz.guns.client.animation.interpolator;
|
||||
|
||||
import com.tacz.guns.client.animation.AnimationChannelContent;
|
||||
|
||||
public class Spline implements Interpolator {
|
||||
@Override
|
||||
public void compile(AnimationChannelContent content) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate(int indexFrom, int indexTo, float alpha, float[] result) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interpolator clone() {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.tacz.guns.client.animation.interpolator;
|
||||
|
||||
import com.tacz.guns.client.animation.AnimationChannelContent;
|
||||
|
||||
public class Step implements Interpolator {
|
||||
private AnimationChannelContent content;
|
||||
|
||||
@Override
|
||||
public void compile(AnimationChannelContent content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interpolate(int indexFrom, int indexTo, float alpha, float[] result) {
|
||||
// 如果动画值有 6 个,后三个为 Post 数值,用于插值起点
|
||||
int offset = content.values[indexFrom].length == 6 ? 3 : 0;
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
if (alpha < 1 || indexFrom == indexTo) {
|
||||
result[i] = content.values[indexFrom][i + offset];
|
||||
} else {
|
||||
result[i] = content.values[indexTo][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Step clone() {
|
||||
try {
|
||||
Step step = (Step) super.clone();
|
||||
step.content = this.content;
|
||||
return step;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.tacz.guns.client.download;
|
||||
|
||||
import com.tacz.guns.GunMod;
|
||||
import com.tacz.guns.config.sync.SyncConfig;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class ClientGunPackDownloadManager {
|
||||
private static final Path DOWNLOAD_DIR_PATH = Paths.get("config", GunMod.MOD_ID, "server", "download");
|
||||
private static final Pattern SHA1 = Pattern.compile("^[a-fA-F0-9]{40}$");
|
||||
private static final ClientGunPackDownloader DOWNLOADER = new ClientGunPackDownloader(DOWNLOAD_DIR_PATH);
|
||||
|
||||
public static void init() {
|
||||
createFolder();
|
||||
}
|
||||
|
||||
public static void downloadClientGunPack() {
|
||||
List<List<String>> download = SyncConfig.CLIENT_GUN_PACK_DOWNLOAD_URLS.get();
|
||||
download.forEach(data -> {
|
||||
String url = data.get(0);
|
||||
String sha1 = data.get(1);
|
||||
if (StringUtils.isBlank(url)) {
|
||||
return;
|
||||
}
|
||||
if (!SHA1.matcher(sha1).matches()) {
|
||||
return;
|
||||
}
|
||||
download(url, sha1);
|
||||
});
|
||||
}
|
||||
|
||||
public static void download(String url, String hash) {
|
||||
// 统一使用小写
|
||||
String lowerCaseHash = hash.toLowerCase(Locale.US);
|
||||
DOWNLOADER.downloadAndLoadGunPack(url, lowerCaseHash).thenRun(() -> {
|
||||
// 我成功下载资源并加载了
|
||||
}).exceptionally(throwable -> {
|
||||
// 失败了
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static void createFolder() {
|
||||
File folder = DOWNLOAD_DIR_PATH.toFile();
|
||||
if (!folder.isDirectory()) {
|
||||
try {
|
||||
Files.createDirectories(folder.toPath());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.tacz.guns.client.download;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.tacz.guns.GunMod;
|
||||
import com.tacz.guns.client.gui.ClientGunPackDownloadProgressScreen;
|
||||
import com.tacz.guns.client.resource.ClientReloadManager;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.WorldVersion;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.User;
|
||||
import net.minecraft.client.gui.screens.ConfirmScreen;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.network.chat.CommonComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.TranslatableComponent;
|
||||
import net.minecraft.util.HttpUtil;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class ClientGunPackDownloader {
|
||||
/**
|
||||
* 最大允许文件大小 250 M
|
||||
*/
|
||||
private static final int MAX_FILE_SIZE = 250 * 1024 * 1024;
|
||||
private final ReentrantLock downloadLock = new ReentrantLock();
|
||||
private final Path serverGunPackPath;
|
||||
private @Nullable CompletableFuture<?> currentDownload;
|
||||
|
||||
public ClientGunPackDownloader(Path serverGunPackPath) {
|
||||
this.serverGunPackPath = serverGunPackPath;
|
||||
}
|
||||
|
||||
private static Map<String, String> getDownloadHeaders() {
|
||||
Map<String, String> map = Maps.newHashMap();
|
||||
User user = Minecraft.getInstance().getUser();
|
||||
WorldVersion currentVersion = SharedConstants.getCurrentVersion();
|
||||
|
||||
map.put("X-Minecraft-Username", user.getName());
|
||||
map.put("X-Minecraft-UUID", user.getUuid());
|
||||
map.put("X-Minecraft-Version", currentVersion.getName());
|
||||
map.put("X-Minecraft-Version-ID", currentVersion.getId());
|
||||
map.put("X-TACZ-Version", ModList.get().getModFileById(GunMod.MOD_ID).versionString());
|
||||
map.put("User-Agent", "Minecraft Java/" + currentVersion.getName());
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public CompletableFuture<?> downloadAndLoadGunPack(String url, String hash) {
|
||||
// 加锁
|
||||
this.downloadLock.lock();
|
||||
// 最终返回的结果
|
||||
CompletableFuture<?> resultFuture;
|
||||
try {
|
||||
// 检查并清除之前未完成,或者可能失败的下载线程
|
||||
this.clearDownloadingGunPack();
|
||||
// 将 hash 作为下载资源包的名称
|
||||
File gunPack = serverGunPackPath.resolve(hash).toFile();
|
||||
// 检查缓存的文件对不对,不对进行删除
|
||||
this.removeMismatchFile(hash, gunPack);
|
||||
// 下载线程
|
||||
CompletableFuture<?> downloadFuture;
|
||||
// 如果此资源包存在,那么直接加载即可
|
||||
if (gunPack.exists()) {
|
||||
downloadFuture = CompletableFuture.completedFuture("");
|
||||
}
|
||||
// 否则下载,并打开下载界面
|
||||
else {
|
||||
// 下载进度界面
|
||||
ClientGunPackDownloadProgressScreen progressScreen = new ClientGunPackDownloadProgressScreen();
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
minecraft.executeBlocking(() -> minecraft.setScreen(progressScreen));
|
||||
downloadFuture = HttpUtil.downloadTo(gunPack, url, getDownloadHeaders(), MAX_FILE_SIZE, progressScreen, minecraft.getProxy());
|
||||
}
|
||||
|
||||
// 下载完成后的处理
|
||||
this.currentDownload = downloadFuture.thenCompose(target -> {
|
||||
// 文件 hash 不匹配,抛出错误
|
||||
if (this.notMatchHash(hash, gunPack)) {
|
||||
return Util.failedFuture(new RuntimeException("Hash check failure for file " + gunPack + ", see log"));
|
||||
} else {
|
||||
// 否则,加载枪械包客户端部分
|
||||
return this.loadClientGunPack(gunPack);
|
||||
}
|
||||
}).whenComplete((target, throwable) -> this.afterFail(throwable, gunPack));
|
||||
resultFuture = this.currentDownload;
|
||||
} finally {
|
||||
this.downloadLock.unlock();
|
||||
}
|
||||
return resultFuture;
|
||||
}
|
||||
|
||||
private void afterFail(Throwable throwable, File gunPack) {
|
||||
if (throwable == null) {
|
||||
return;
|
||||
}
|
||||
GunMod.LOGGER.warn("Pack application failed: {}, deleting file {}", throwable.getMessage(), gunPack);
|
||||
try {
|
||||
Files.delete(gunPack.toPath());
|
||||
} catch (IOException exception) {
|
||||
GunMod.LOGGER.warn("Failed to delete file {}: {}", gunPack, exception.getMessage());
|
||||
}
|
||||
Minecraft.getInstance().execute(() -> this.displayFailScreen(Minecraft.getInstance()));
|
||||
}
|
||||
|
||||
private void displayFailScreen(Minecraft mc) {
|
||||
TranslatableComponent title = new TranslatableComponent("gui.tacz.client_gun_pack_downloader.fail.title");
|
||||
TranslatableComponent subTitle = new TranslatableComponent("gui.tacz.client_gun_pack_downloader.fail.subtitle");
|
||||
Component yesButton = CommonComponents.GUI_PROCEED;
|
||||
TranslatableComponent noButton = new TranslatableComponent("menu.disconnect");
|
||||
mc.setScreen(new ConfirmScreen(button -> {
|
||||
if (button) {
|
||||
mc.setScreen(null);
|
||||
} else {
|
||||
ClientPacketListener clientpacketlistener = mc.getConnection();
|
||||
if (clientpacketlistener != null) {
|
||||
clientpacketlistener.getConnection().disconnect(new TranslatableComponent("connect.aborted"));
|
||||
}
|
||||
}
|
||||
}, title, subTitle, yesButton, noButton));
|
||||
}
|
||||
|
||||
public void clearDownloadingGunPack() {
|
||||
this.downloadLock.lock();
|
||||
try {
|
||||
if (this.currentDownload != null) {
|
||||
this.currentDownload.cancel(true);
|
||||
}
|
||||
this.currentDownload = null;
|
||||
} finally {
|
||||
this.downloadLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeMismatchFile(String expectedHash, File file) {
|
||||
if (file.exists() && notMatchHash(expectedHash, file)) {
|
||||
try {
|
||||
FileUtils.delete(file);
|
||||
} catch (IOException exception) {
|
||||
GunMod.LOGGER.warn("Failed to delete file {}: {}", file, exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean notMatchHash(String expectedHash, File file) {
|
||||
try (FileInputStream stream = new FileInputStream(file)) {
|
||||
String fileHash = DigestUtils.sha1Hex(stream);
|
||||
if (fileHash.toLowerCase(Locale.US).equals(expectedHash.toLowerCase(Locale.US))) {
|
||||
GunMod.LOGGER.info("Found file {} matching requested fileHash {}", file, expectedHash);
|
||||
return false;
|
||||
}
|
||||
GunMod.LOGGER.warn("File {} had wrong fileHash (expected {}, found {}).", file, expectedHash, fileHash);
|
||||
} catch (IOException ioexception) {
|
||||
GunMod.LOGGER.warn("File {} couldn't be hashed.", file, ioexception);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public CompletableFuture<?> loadClientGunPack(File file) {
|
||||
ClientReloadManager.loadClientDownloadGunPack(file);
|
||||
return CompletableFuture.completedFuture("");
|
||||
}
|
||||
}
|
||||
205
src/main/java/com/tacz/guns/client/event/CameraSetupEvent.java
Normal file
205
src/main/java/com/tacz/guns/client/event/CameraSetupEvent.java
Normal file
@@ -0,0 +1,205 @@
|
||||
package com.tacz.guns.client.event;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Quaternion;
|
||||
import com.tacz.guns.GunMod;
|
||||
import com.tacz.guns.api.TimelessAPI;
|
||||
import com.tacz.guns.api.client.event.BeforeRenderHandEvent;
|
||||
import com.tacz.guns.api.client.event.FieldOfView;
|
||||
import com.tacz.guns.api.client.gameplay.IClientPlayerGunOperator;
|
||||
import com.tacz.guns.api.client.other.KeepingItemRenderer;
|
||||
import com.tacz.guns.api.entity.IGunOperator;
|
||||
import com.tacz.guns.api.event.common.GunFireEvent;
|
||||
import com.tacz.guns.api.item.IGun;
|
||||
import com.tacz.guns.client.model.BedrockGunModel;
|
||||
import com.tacz.guns.client.resource.index.ClientGunIndex;
|
||||
import com.tacz.guns.resource.pojo.data.attachment.RecoilModifier;
|
||||
import com.tacz.guns.resource.pojo.data.gun.GunData;
|
||||
import com.tacz.guns.util.AttachmentDataUtils;
|
||||
import com.tacz.guns.util.math.MathUtil;
|
||||
import com.tacz.guns.util.math.SecondOrderDynamics;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.EntityViewRenderEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = GunMod.MOD_ID)
|
||||
public class CameraSetupEvent {
|
||||
/**
|
||||
* 用于平滑 FOV 变化
|
||||
*/
|
||||
private static final SecondOrderDynamics FOV_DYNAMICS = new SecondOrderDynamics(0.5f, 1.2f, 0.5f, 0);
|
||||
private static PolynomialSplineFunction pitchSplineFunction;
|
||||
private static PolynomialSplineFunction yawSplineFunction;
|
||||
private static long shootTimeStamp = -1L;
|
||||
private static double xRotO = 0;
|
||||
private static double yRot0 = 0;
|
||||
private static BedrockGunModel lastModel = null;
|
||||
|
||||
@SubscribeEvent
|
||||
public static void applyLevelCameraAnimation(EntityViewRenderEvent.CameraSetup event) {
|
||||
if (!Minecraft.getInstance().options.bobView) {
|
||||
return;
|
||||
}
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
ItemStack stack = ((KeepingItemRenderer) Minecraft.getInstance().getItemInHandRenderer()).getCurrentItem();
|
||||
if (!(stack.getItem() instanceof IGun iGun)) {
|
||||
return;
|
||||
}
|
||||
TimelessAPI.getClientGunIndex(iGun.getGunId(stack)).ifPresent(gunIndex -> {
|
||||
BedrockGunModel gunModel = gunIndex.getGunModel();
|
||||
if (lastModel != gunModel) {
|
||||
// 切换枪械模型的时候清理一下摄像机动画数据,以避免上一次播放到一半的摄像机动画影响观感。
|
||||
gunModel.cleanCameraAnimationTransform();
|
||||
lastModel = gunModel;
|
||||
}
|
||||
IClientPlayerGunOperator clientPlayerGunOperator = IClientPlayerGunOperator.fromLocalPlayer(player);
|
||||
float partialTicks = Minecraft.getInstance().getFrameTime();
|
||||
float aimingProgress = clientPlayerGunOperator.getClientAimingProgress(partialTicks);
|
||||
float zoom = iGun.getAimingZoom(stack);
|
||||
float multiplier = 1 - aimingProgress + aimingProgress / (float) Math.sqrt(zoom);
|
||||
Quaternion q = MathUtil.multiplyQuaternion(gunModel.getCameraAnimationObject().rotationQuaternion, multiplier);
|
||||
double yaw = Math.asin(2 * (q.r() * q.j() - q.i() * q.k()));
|
||||
double pitch = Math.atan2(2 * (q.r() * q.i() + q.j() * q.k()), 1 - 2 * (q.i() * q.i() + q.j() * q.j()));
|
||||
double roll = Math.atan2(2 * (q.r() * q.k() + q.i() * q.j()), 1 - 2 * (q.j() * q.j() + q.k() * q.k()));
|
||||
yaw = Math.toDegrees(yaw);
|
||||
pitch = Math.toDegrees(pitch);
|
||||
roll = Math.toDegrees(roll);
|
||||
event.setYaw((float) yaw + event.getYaw());
|
||||
event.setPitch((float) pitch + event.getPitch());
|
||||
event.setRoll((float) roll + event.getRoll());
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void applyItemInHandCameraAnimation(BeforeRenderHandEvent event) {
|
||||
if (!Minecraft.getInstance().options.bobView) {
|
||||
return;
|
||||
}
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
ItemStack stack = ((KeepingItemRenderer) Minecraft.getInstance().getItemInHandRenderer()).getCurrentItem();
|
||||
if (!(stack.getItem() instanceof IGun iGun)) {
|
||||
return;
|
||||
}
|
||||
TimelessAPI.getClientGunIndex(iGun.getGunId(stack)).ifPresent(gunIndex -> {
|
||||
BedrockGunModel gunModel = gunIndex.getGunModel();
|
||||
PoseStack poseStack = event.getPoseStack();
|
||||
IClientPlayerGunOperator clientPlayerGunOperator = IClientPlayerGunOperator.fromLocalPlayer(player);
|
||||
float partialTicks = Minecraft.getInstance().getFrameTime();
|
||||
float aimingProgress = clientPlayerGunOperator.getClientAimingProgress(partialTicks);
|
||||
float zoom = iGun.getAimingZoom(stack);
|
||||
float multiplier = 1 - aimingProgress + aimingProgress / (float) Math.sqrt(zoom);
|
||||
Quaternion quaternion = MathUtil.multiplyQuaternion(gunModel.getCameraAnimationObject().rotationQuaternion, multiplier);
|
||||
poseStack.mulPose(quaternion);
|
||||
// 截至目前,摄像机动画数据已消费完毕。是否有更好的清理动画数据的方法?
|
||||
gunModel.cleanCameraAnimationTransform();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void applyScopeMagnification(FieldOfView event) {
|
||||
if (event.isItemWithHand()) {
|
||||
return;
|
||||
}
|
||||
Entity entity = event.getCamera().getEntity();
|
||||
if (entity instanceof LivingEntity livingEntity) {
|
||||
ItemStack stack = ((KeepingItemRenderer) Minecraft.getInstance().getItemInHandRenderer()).getCurrentItem();
|
||||
if (!(stack.getItem() instanceof IGun iGun)) {
|
||||
float fov = FOV_DYNAMICS.update((float) event.getFOV());
|
||||
event.setFOV(fov);
|
||||
return;
|
||||
}
|
||||
float zoom = iGun.getAimingZoom(stack);
|
||||
if (livingEntity instanceof LocalPlayer localPlayer) {
|
||||
IClientPlayerGunOperator gunOperator = IClientPlayerGunOperator.fromLocalPlayer(localPlayer);
|
||||
float aimingProgress = gunOperator.getClientAimingProgress((float) event.getPartialTicks());
|
||||
float fov = FOV_DYNAMICS.update((float) MathUtil.magnificationToFov(1 + (zoom - 1) * aimingProgress, event.getFOV()));
|
||||
event.setFOV(fov);
|
||||
} else {
|
||||
IGunOperator gunOperator = IGunOperator.fromLivingEntity(livingEntity);
|
||||
float aimingProgress = gunOperator.getSynAimingProgress();
|
||||
float fov = FOV_DYNAMICS.update((float) MathUtil.magnificationToFov(1 + (zoom - 1) * aimingProgress, event.getFOV()));
|
||||
event.setFOV(fov);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void initialCameraRecoil(GunFireEvent event) {
|
||||
if (event.getLogicalSide().isClient()) {
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
ItemStack mainhandItem = player.getMainHandItem();
|
||||
if (!(mainhandItem.getItem() instanceof IGun iGun)) {
|
||||
return;
|
||||
}
|
||||
ResourceLocation gunId = iGun.getGunId(mainhandItem);
|
||||
Optional<ClientGunIndex> gunIndexOptional = TimelessAPI.getClientGunIndex(gunId);
|
||||
if (gunIndexOptional.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ClientGunIndex gunIndex = gunIndexOptional.get();
|
||||
GunData gunData = gunIndex.getGunData();
|
||||
// 获取所有配件对摄像机后坐力的修改
|
||||
final float[] attachmentRecoilModifier = new float[]{0f, 0f};
|
||||
AttachmentDataUtils.getAllAttachmentData(mainhandItem, gunData, attachmentData -> {
|
||||
RecoilModifier recoilModifier = attachmentData.getRecoilModifier();
|
||||
if (recoilModifier == null) {
|
||||
return;
|
||||
}
|
||||
attachmentRecoilModifier[0] += recoilModifier.getPitch();
|
||||
attachmentRecoilModifier[1] += recoilModifier.getYaw();
|
||||
});
|
||||
IClientPlayerGunOperator clientPlayerGunOperator = IClientPlayerGunOperator.fromLocalPlayer(player);
|
||||
float partialTicks = Minecraft.getInstance().getFrameTime();
|
||||
float aimingProgress = clientPlayerGunOperator.getClientAimingProgress(partialTicks);
|
||||
float zoom = iGun.getAimingZoom(mainhandItem);
|
||||
float aimingRecoilModifier = 1 - aimingProgress + aimingProgress / (float) Math.sqrt(zoom);
|
||||
pitchSplineFunction = gunData.getRecoil().genPitchSplineFunction(modifierNumber(attachmentRecoilModifier[0]) * aimingRecoilModifier);
|
||||
yawSplineFunction = gunData.getRecoil().genYawSplineFunction(modifierNumber(attachmentRecoilModifier[1]) * aimingRecoilModifier);
|
||||
shootTimeStamp = System.currentTimeMillis();
|
||||
xRotO = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void applyCameraRecoil(EntityViewRenderEvent.CameraSetup event) {
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
long timeTotal = System.currentTimeMillis() - shootTimeStamp;
|
||||
if (pitchSplineFunction != null && pitchSplineFunction.isValidPoint(timeTotal)) {
|
||||
double value = pitchSplineFunction.value(timeTotal);
|
||||
player.setXRot(player.getXRot() - (float) (value - xRotO));
|
||||
xRotO = value;
|
||||
}
|
||||
if (yawSplineFunction != null && yawSplineFunction.isValidPoint(timeTotal)) {
|
||||
double value = yawSplineFunction.value(timeTotal);
|
||||
player.setYRot(player.getYRot() - (float) (value - yRot0));
|
||||
yRot0 = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static float modifierNumber(float modifier) {
|
||||
return Math.max(0, 1 + modifier);
|
||||
}
|
||||
}
|
||||
56
src/main/java/com/tacz/guns/client/event/ClientHitMark.java
Normal file
56
src/main/java/com/tacz/guns/client/event/ClientHitMark.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package com.tacz.guns.client.event;
|
||||
|
||||
import com.tacz.guns.api.TimelessAPI;
|
||||
import com.tacz.guns.api.event.common.LivingHurtByGunEvent;
|
||||
import com.tacz.guns.api.event.common.LivingKillByGunEvent;
|
||||
import com.tacz.guns.client.gui.overlay.KillAmountOverlay;
|
||||
import com.tacz.guns.client.sound.SoundPlayManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
@Mod.EventBusSubscriber(value = Dist.CLIENT)
|
||||
public class ClientHitMark {
|
||||
@SubscribeEvent
|
||||
public static void onEntityHurt(LivingHurtByGunEvent event) {
|
||||
LogicalSide logicalSide = event.getLogicalSide();
|
||||
if (logicalSide != LogicalSide.CLIENT) {
|
||||
return;
|
||||
}
|
||||
LivingEntity attacker = event.getAttacker();
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player != null && player.equals(attacker)) {
|
||||
ResourceLocation gunId = event.getGunId();
|
||||
RenderCrosshairEvent.markHitTimestamp();
|
||||
if (event.isHeadShot()) {
|
||||
RenderCrosshairEvent.markHeadShotTimestamp();
|
||||
TimelessAPI.getClientGunIndex(gunId).ifPresent(index -> SoundPlayManager.playHeadHitSound(player, index));
|
||||
} else {
|
||||
TimelessAPI.getClientGunIndex(gunId).ifPresent(index -> SoundPlayManager.playFleshHitSound(player, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onEntityKill(LivingKillByGunEvent event) {
|
||||
LogicalSide logicalSide = event.getLogicalSide();
|
||||
if (logicalSide != LogicalSide.CLIENT) {
|
||||
return;
|
||||
}
|
||||
LivingEntity attacker = event.getAttacker();
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player != null && player.equals(attacker)) {
|
||||
RenderCrosshairEvent.markKillTimestamp();
|
||||
KillAmountOverlay.markTimestamp();
|
||||
TimelessAPI.getClientGunIndex(event.getGunId()).ifPresent(index -> SoundPlayManager.playKillSound(player, index));
|
||||
if (event.isHeadShot()) {
|
||||
RenderCrosshairEvent.markHeadShotTimestamp();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.tacz.guns.client.event;
|
||||
|
||||
import com.tacz.guns.GunMod;
|
||||
import com.tacz.guns.api.item.IGun;
|
||||
import com.tacz.guns.client.input.InteractKey;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.InputEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = GunMod.MOD_ID)
|
||||
public class ClientPreventGunClick {
|
||||
@SubscribeEvent
|
||||
public static void onClickInput(InputEvent.ClickInputEvent event) {
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
// 当交互键按下时,允许交互
|
||||
if (InteractKey.INTERACT_KEY.isDown()) {
|
||||
return;
|
||||
}
|
||||
// 只要主手有枪,那么禁止交互
|
||||
ItemStack itemInHand = player.getItemInHand(InteractionHand.MAIN_HAND);
|
||||
if (itemInHand.getItem() instanceof IGun) {
|
||||
// 展示框可以交互
|
||||
HitResult hitResult = Minecraft.getInstance().hitResult;
|
||||
if (hitResult instanceof EntityHitResult entityHitResult && entityHitResult.getEntity() instanceof ItemFrame) {
|
||||
return;
|
||||
}
|
||||
// 这个设置为 false 就能阻止客户端粒子的生成
|
||||
event.setSwingHand(false);
|
||||
event.setCanceled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,546 @@
|
||||
package com.tacz.guns.client.event;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Matrix4f;
|
||||
import com.mojang.math.Vector3f;
|
||||
import com.tacz.guns.GunMod;
|
||||
import com.tacz.guns.api.TimelessAPI;
|
||||
import com.tacz.guns.api.client.event.RenderItemInHandBobEvent;
|
||||
import com.tacz.guns.api.client.gameplay.IClientPlayerGunOperator;
|
||||
import com.tacz.guns.api.client.other.KeepingItemRenderer;
|
||||
import com.tacz.guns.api.event.common.GunFireEvent;
|
||||
import com.tacz.guns.api.item.IAttachment;
|
||||
import com.tacz.guns.api.item.IGun;
|
||||
import com.tacz.guns.api.item.attachment.AttachmentType;
|
||||
import com.tacz.guns.client.animation.internal.GunAnimationStateMachine;
|
||||
import com.tacz.guns.client.gui.GunRefitScreen;
|
||||
import com.tacz.guns.client.model.BedrockAttachmentModel;
|
||||
import com.tacz.guns.client.model.BedrockGunModel;
|
||||
import com.tacz.guns.client.model.bedrock.BedrockModel;
|
||||
import com.tacz.guns.client.model.bedrock.BedrockPart;
|
||||
import com.tacz.guns.client.model.functional.MuzzleFlashRender;
|
||||
import com.tacz.guns.client.model.functional.ShellRender;
|
||||
import com.tacz.guns.client.renderer.item.GunItemRenderer;
|
||||
import com.tacz.guns.client.resource.InternalAssetLoader;
|
||||
import com.tacz.guns.client.resource.index.ClientAttachmentIndex;
|
||||
import com.tacz.guns.client.resource.index.ClientGunIndex;
|
||||
import com.tacz.guns.config.client.RenderConfig;
|
||||
import com.tacz.guns.entity.EntityKineticBullet;
|
||||
import com.tacz.guns.util.math.Easing;
|
||||
import com.tacz.guns.util.math.MathUtil;
|
||||
import com.tacz.guns.util.math.PerlinNoise;
|
||||
import com.tacz.guns.util.math.SecondOrderDynamics;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms.TransformType;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.RenderHandEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static net.minecraft.client.renderer.block.model.ItemTransforms.TransformType.FIRST_PERSON_RIGHT_HAND;
|
||||
|
||||
/**
|
||||
* 负责第一人称的枪械模型渲染。其他人称参见 {@link GunItemRenderer}
|
||||
*/
|
||||
@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = GunMod.MOD_ID)
|
||||
public class FirstPersonRenderGunEvent {
|
||||
// 用于生成瞄准动作的运动曲线,使动作看起来更平滑
|
||||
private static final SecondOrderDynamics AIMING_DYNAMICS = new SecondOrderDynamics(1.2f, 1.2f, 0.5f, 0);
|
||||
// 用于打开改装界面时枪械运动的平滑
|
||||
private static final SecondOrderDynamics REFIT_OPENING_DYNAMICS = new SecondOrderDynamics(1f, 1.2f, 0.5f, 0);
|
||||
// 用于跳跃延滞动画的平滑
|
||||
private static final SecondOrderDynamics JUMPING_DYNAMICS = new SecondOrderDynamics(0.28f, 1f, 0.65f, 0);
|
||||
private static final float JUMPING_Y_SWAY = -2f;
|
||||
private static final float JUMPING_SWAY_TIME = 0.3f;
|
||||
private static final float LANDING_SWAY_TIME = 0.15f;
|
||||
// 用于枪械后座的程序动画
|
||||
private static final PerlinNoise SHOOT_X_SWAY_NOISE = new PerlinNoise(-0.2f, 0.2f, 400);
|
||||
private static final PerlinNoise SHOOT_Y_ROTATION_NOISE = new PerlinNoise(-0.0136f, 0.0136f, 100);
|
||||
private static final float SHOOT_Y_SWAY = -0.1f;
|
||||
private static final float SHOOT_ANIMATION_TIME = 0.3f;
|
||||
|
||||
private static float jumpingSwayProgress = 0;
|
||||
private static boolean lastOnGround = false;
|
||||
private static long jumpingTimeStamp = -1;
|
||||
private static long shootTimeStamp = -1;
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderHand(RenderHandEvent event) {
|
||||
if (event.getHand() == InteractionHand.OFF_HAND) {
|
||||
return;
|
||||
}
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
ItemStack stack = event.getItemStack();
|
||||
if (!(stack.getItem() instanceof IGun iGun)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 TransformType
|
||||
TransformType transformType;
|
||||
if (event.getHand() == InteractionHand.MAIN_HAND) {
|
||||
transformType = FIRST_PERSON_RIGHT_HAND;
|
||||
} else {
|
||||
transformType = TransformType.FIRST_PERSON_LEFT_HAND;
|
||||
}
|
||||
|
||||
ResourceLocation gunId = iGun.getGunId(stack);
|
||||
TimelessAPI.getClientGunIndex(gunId).ifPresentOrElse(gunIndex -> {
|
||||
BedrockGunModel gunModel = gunIndex.getGunModel();
|
||||
GunAnimationStateMachine animationStateMachine = gunIndex.getAnimationStateMachine();
|
||||
if (gunModel == null) {
|
||||
return;
|
||||
}
|
||||
// 在渲染之前,先更新动画,让动画数据写入模型
|
||||
if (animationStateMachine != null) {
|
||||
animationStateMachine.update(event.getPartialTicks(), player);
|
||||
}
|
||||
|
||||
PoseStack poseStack = event.getPoseStack();
|
||||
poseStack.pushPose();
|
||||
// 逆转原版施加在手上的延滞效果,改为写入模型动画数据中
|
||||
float xRotOffset = Mth.lerp(event.getPartialTicks(), player.xBobO, player.xBob);
|
||||
float yRotOffset = Mth.lerp(event.getPartialTicks(), player.yBobO, player.yBob);
|
||||
float xRot = player.getViewXRot(event.getPartialTicks()) - xRotOffset;
|
||||
float yRot = player.getViewYRot(event.getPartialTicks()) - yRotOffset;
|
||||
poseStack.mulPose(Vector3f.XP.rotationDegrees(xRot * -0.1F));
|
||||
poseStack.mulPose(Vector3f.YP.rotationDegrees(yRot * -0.1F));
|
||||
BedrockPart rootNode = gunModel.getRootNode();
|
||||
if (rootNode != null) {
|
||||
xRot = (float) Math.tanh(xRot / 25) * 25;
|
||||
yRot = (float) Math.tanh(yRot / 25) * 25;
|
||||
rootNode.offsetX += yRot * 0.1F / 16F / 3F;
|
||||
rootNode.offsetY += -xRot * 0.1F / 16F / 3F;
|
||||
rootNode.additionalQuaternion.mul(Vector3f.XP.rotationDegrees(xRot * 0.05F));
|
||||
rootNode.additionalQuaternion.mul(Vector3f.YP.rotationDegrees(yRot * 0.05F));
|
||||
}
|
||||
// 从渲染原点 (0, 24, 0) 移动到模型原点 (0, 0, 0)
|
||||
poseStack.translate(0, 1.5f, 0);
|
||||
// 基岩版模型是上下颠倒的,需要翻转过来。
|
||||
poseStack.mulPose(Vector3f.ZP.rotationDegrees(180f));
|
||||
// 应用持枪姿态变换,如第一人称摄像机定位
|
||||
applyFirstPersonGunTransform(player, stack, gunIndex, poseStack, gunModel, event.getPartialTicks());
|
||||
|
||||
// 开启第一人称弹壳和火焰渲染
|
||||
MuzzleFlashRender.isSelf = true;
|
||||
ShellRender.isSelf = true;
|
||||
{
|
||||
// 如果正在打开改装界面,则取消手臂渲染
|
||||
boolean renderHand = gunModel.getRenderHand();
|
||||
if (GunRefitScreen.getOpeningProgress() != 0) {
|
||||
gunModel.setRenderHand(false);
|
||||
}
|
||||
// 调用枪械模型渲染
|
||||
RenderType renderType = RenderType.itemEntityTranslucentCull(gunIndex.getModelTexture());
|
||||
gunModel.render(poseStack, stack, transformType, renderType, event.getPackedLight(), OverlayTexture.NO_OVERLAY);
|
||||
// 调用曳光弹渲染
|
||||
renderBulletTracer(player, poseStack, gunModel, event.getPartialTicks());
|
||||
// 恢复手臂渲染
|
||||
gunModel.setRenderHand(renderHand);
|
||||
// 渲染完成后,将动画数据从模型中清除,不对其他视角下的模型渲染产生影响
|
||||
poseStack.popPose();
|
||||
gunModel.cleanAnimationTransform();
|
||||
}
|
||||
// 关闭第一人称弹壳和火焰渲染
|
||||
MuzzleFlashRender.isSelf = false;
|
||||
ShellRender.isSelf = false;
|
||||
|
||||
// 放这里,只有渲染了枪械,才取消后续(虽然一般来说也没有什么后续了)
|
||||
event.setCanceled(true);
|
||||
}, () -> renderBulletTracer(player, event.getPoseStack(), null, event.getPartialTicks()));
|
||||
}
|
||||
|
||||
private static void renderBulletTracer(LocalPlayer player, PoseStack poseStack, BedrockGunModel gunModel, float partialTicks) {
|
||||
if (!RenderConfig.FIRST_PERSON_BULLET_TRACER_ENABLE.get()) {
|
||||
return;
|
||||
}
|
||||
Optional<BedrockModel> modelOptional = InternalAssetLoader.getBedrockModel(InternalAssetLoader.DEFAULT_BULLET_MODEL);
|
||||
if (modelOptional.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
BedrockModel model = modelOptional.get();
|
||||
Level level = player.getLevel();
|
||||
AABB renderArea = player.getBoundingBox().inflate(256, 256, 256);
|
||||
for (Entity entity : level.getEntities(player, renderArea, FirstPersonRenderGunEvent::bulletFromPlayer)) {
|
||||
EntityKineticBullet entityBullet = (EntityKineticBullet) entity;
|
||||
if (!entityBullet.isTracerAmmo()) {
|
||||
continue;
|
||||
}
|
||||
Vec3 deltaMovement = entityBullet.getDeltaMovement().multiply(partialTicks, partialTicks, partialTicks);
|
||||
Vec3 entityPosition = entityBullet.getPosition(0).add(deltaMovement);
|
||||
Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera();
|
||||
Vec3 cameraPosition = camera.getPosition();
|
||||
Vec3 originCameraPosition = entityBullet.getOriginCameraPosition();
|
||||
if (originCameraPosition == null) {
|
||||
if (gunModel == null) {
|
||||
continue;
|
||||
}
|
||||
if (gunModel.getMuzzleFlashPosPath() != null) {
|
||||
poseStack.pushPose();
|
||||
for (BedrockPart bedrockPart : gunModel.getMuzzleFlashPosPath()) {
|
||||
bedrockPart.translateAndRotateAndScale(poseStack);
|
||||
}
|
||||
Matrix4f pose = poseStack.last().pose();
|
||||
originCameraPosition = new Vec3(cameraPosition.x, cameraPosition.y, cameraPosition.z);
|
||||
entityBullet.setOriginCameraPosition(originCameraPosition);
|
||||
entityBullet.setOriginRenderOffset(new Vec3(pose.m03, pose.m13, pose.m23));
|
||||
poseStack.popPose();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Vec3 originRenderOffset = entityBullet.getOriginRenderOffset();
|
||||
Vec3 alphaCameraTranslation = originCameraPosition.subtract(cameraPosition);
|
||||
double distance = entityPosition.distanceTo(originCameraPosition);
|
||||
Vec3 bulletDirection = entityPosition.subtract(originCameraPosition);
|
||||
double yRot = MathUtil.getTwoVecAngle(new Vec3(0, 0, -1), new Vec3(bulletDirection.x, 0, bulletDirection.z));
|
||||
double xRot = MathUtil.getTwoVecAngle(new Vec3(bulletDirection.x, 0, bulletDirection.z), bulletDirection);
|
||||
if (yRot == -1) {
|
||||
yRot = Math.toRadians(camera.getYRot() + 180f);
|
||||
}
|
||||
if (xRot == -1) {
|
||||
xRot = Math.toRadians(camera.getXRot());
|
||||
}
|
||||
xRot *= bulletDirection.y > 0 ? -1 : 1;
|
||||
yRot *= bulletDirection.x > 0 ? 1 : -1;
|
||||
PoseStack poseStack1 = new PoseStack();
|
||||
// 逆转摄像机的旋转,回到起始坐标
|
||||
poseStack1.mulPose(Vector3f.XP.rotationDegrees(camera.getXRot()));
|
||||
poseStack1.mulPose(Vector3f.YP.rotationDegrees(camera.getYRot() + 180f));
|
||||
poseStack1.translate(alphaCameraTranslation.x, alphaCameraTranslation.y, alphaCameraTranslation.z);
|
||||
// 恢复旋转角度,应用枪口定位偏移
|
||||
poseStack1.mulPose(Vector3f.YN.rotation((float) yRot));
|
||||
poseStack1.mulPose(Vector3f.XN.rotation((float) xRot));
|
||||
poseStack1.translate(originRenderOffset.x, originRenderOffset.y, originRenderOffset.z - distance);
|
||||
float trailLength = 0.5f * (float) entityBullet.getDeltaMovement().length();
|
||||
poseStack1.translate(0, 0, -trailLength / 2);
|
||||
poseStack1.scale(0.03f, 0.03f, trailLength);
|
||||
ResourceLocation ammoId = entityBullet.getAmmoId();
|
||||
TimelessAPI.getClientAmmoIndex(ammoId).ifPresent(index -> {
|
||||
float[] tracerColor = index.getTracerColor();
|
||||
RenderType type = RenderType.energySwirl(InternalAssetLoader.DEFAULT_BULLET_TEXTURE, 15, 15);
|
||||
model.render(poseStack1, ItemTransforms.TransformType.NONE, type, LightTexture.pack(15, 15),
|
||||
OverlayTexture.NO_OVERLAY, tracerColor[0], tracerColor[1], tracerColor[2], 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当主手拿着枪械物品的时候,取消应用在它上面的 viewBobbing,以便应用自定义的跑步/走路动画。
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void cancelItemInHandViewBobbing(RenderItemInHandBobEvent.BobView event) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.player == null) {
|
||||
return;
|
||||
}
|
||||
ItemStack itemStack = ((KeepingItemRenderer) Minecraft.getInstance().getItemInHandRenderer()).getCurrentItem();
|
||||
if (IGun.getIGunOrNull(itemStack) != null) {
|
||||
event.setCanceled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onGunFire(GunFireEvent event) {
|
||||
if (event.getLogicalSide().isClient()) {
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
ItemStack mainhandItem = player.getMainHandItem();
|
||||
IGun iGun = IGun.getIGunOrNull(mainhandItem);
|
||||
if (iGun == null) {
|
||||
return;
|
||||
}
|
||||
TimelessAPI.getClientGunIndex(iGun.getGunId(mainhandItem)).ifPresent(gunIndex -> {
|
||||
// 记录开火时间戳,用于后坐力程序动画
|
||||
shootTimeStamp = System.currentTimeMillis();
|
||||
// 记录枪口火焰数据
|
||||
MuzzleFlashRender.onShoot();
|
||||
// 抛壳
|
||||
if (gunIndex.getShellEjection() != null) {
|
||||
ShellRender.addShell(gunIndex.getShellEjection().getRandomVelocity());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean bulletFromPlayer(Entity entity) {
|
||||
if (entity instanceof EntityKineticBullet entityBullet) {
|
||||
return entityBullet.getOwner() instanceof LocalPlayer;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void applyFirstPersonGunTransform(LocalPlayer player, ItemStack gunItemStack, ClientGunIndex gunIndex, PoseStack poseStack, BedrockGunModel model, float partialTicks) {
|
||||
// 配合运动曲线,计算改装枪口的打开进度
|
||||
float refitScreenOpeningProgress = REFIT_OPENING_DYNAMICS.update(GunRefitScreen.getOpeningProgress());
|
||||
// 配合运动曲线,计算瞄准进度
|
||||
float aimingProgress = AIMING_DYNAMICS.update(IClientPlayerGunOperator.fromLocalPlayer(player).getClientAimingProgress(partialTicks));
|
||||
// 应用枪械动态,如后坐力、持枪跳跃等
|
||||
applyGunMovements(model, aimingProgress, partialTicks);
|
||||
// 应用各种摄像机定位组的变换(默认持枪、瞄准、改装界面等)
|
||||
applyFirstPersonPositioningTransform(poseStack, model, gunItemStack, aimingProgress, refitScreenOpeningProgress);
|
||||
// 应用动画约束变换
|
||||
applyAnimationConstraintTransform(poseStack, model, aimingProgress * (1 - refitScreenOpeningProgress));
|
||||
}
|
||||
|
||||
private static void applyGunMovements(BedrockGunModel model, float aimingProgress, float partialTicks) {
|
||||
applyShootSwayAndRotation(model, aimingProgress);
|
||||
applyJumpingSway(model, partialTicks);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用瞄具摄像机定位组、机瞄摄像机定位组和 Idle 摄像机定位组的变换。会在几个摄像机定位之间插值。
|
||||
*/
|
||||
private static void applyFirstPersonPositioningTransform(PoseStack poseStack, BedrockGunModel model, ItemStack stack, float aimingProgress, float refitScreenOpeningProgress) {
|
||||
IGun iGun = IGun.getIGunOrNull(stack);
|
||||
if (iGun == null) {
|
||||
return;
|
||||
}
|
||||
Matrix4f transformMatrix = new Matrix4f();
|
||||
transformMatrix.setIdentity();
|
||||
// 应用瞄准定位
|
||||
List<BedrockPart> idleNodePath = model.getIdleSightPath();
|
||||
List<BedrockPart> aimingNodePath = null;
|
||||
ItemStack scopeItem = iGun.getAttachment(stack, AttachmentType.SCOPE);
|
||||
if (scopeItem.isEmpty()) {
|
||||
// 未安装瞄具,使用机瞄定位组
|
||||
aimingNodePath = model.getIronSightPath();
|
||||
} else {
|
||||
// 安装瞄具,组合瞄具定位组和瞄具视野定位组
|
||||
List<BedrockPart> scopeNodePath = model.getScopePosPath();
|
||||
if (scopeNodePath != null) {
|
||||
aimingNodePath = new ArrayList<>(scopeNodePath);
|
||||
IAttachment iAttachment = IAttachment.getIAttachmentOrNull(scopeItem);
|
||||
if (iAttachment != null) {
|
||||
ResourceLocation scopeId = iAttachment.getAttachmentId(scopeItem);
|
||||
Optional<ClientAttachmentIndex> indexOptional = TimelessAPI.getClientAttachmentIndex(scopeId);
|
||||
if (indexOptional.isPresent()) {
|
||||
BedrockAttachmentModel attachmentModel = indexOptional.get().getAttachmentModel();
|
||||
if (attachmentModel.getScopeViewPath() != null) {
|
||||
aimingNodePath.addAll(attachmentModel.getScopeViewPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MathUtil.applyMatrixLerp(transformMatrix, getPositioningNodeInverse(idleNodePath), transformMatrix, (1 - refitScreenOpeningProgress));
|
||||
MathUtil.applyMatrixLerp(transformMatrix, getPositioningNodeInverse(aimingNodePath), transformMatrix, (1 - refitScreenOpeningProgress) * aimingProgress);
|
||||
// 应用改装界面开启时的定位
|
||||
float refitTransformProgress = (float) Easing.easeOutCubic(GunRefitScreen.getTransformProgress());
|
||||
AttachmentType oldType = GunRefitScreen.getOldTransformType();
|
||||
AttachmentType currentType = GunRefitScreen.getCurrentTransformType();
|
||||
List<BedrockPart> fromNode = model.getRefitAttachmentViewPath(oldType);
|
||||
List<BedrockPart> toNode = model.getRefitAttachmentViewPath(currentType);
|
||||
MathUtil.applyMatrixLerp(transformMatrix, getPositioningNodeInverse(fromNode), transformMatrix, refitScreenOpeningProgress);
|
||||
MathUtil.applyMatrixLerp(transformMatrix, getPositioningNodeInverse(toNode), transformMatrix, refitScreenOpeningProgress * refitTransformProgress);
|
||||
// 应用变换到 PoseStack
|
||||
poseStack.translate(0, 1.5f, 0);
|
||||
poseStack.mulPoseMatrix(transformMatrix);
|
||||
poseStack.translate(0, -1.5f, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取摄像机定位组的反相矩阵
|
||||
*/
|
||||
@Nonnull
|
||||
private static Matrix4f getPositioningNodeInverse(List<BedrockPart> nodePath) {
|
||||
Matrix4f matrix4f = new Matrix4f();
|
||||
matrix4f.setIdentity();
|
||||
if (nodePath != null) {
|
||||
for (int i = nodePath.size() - 1; i >= 0; i--) {
|
||||
BedrockPart part = nodePath.get(i);
|
||||
// 计算反向的旋转
|
||||
matrix4f.multiply(Vector3f.XN.rotation(part.xRot));
|
||||
matrix4f.multiply(Vector3f.YN.rotation(part.yRot));
|
||||
matrix4f.multiply(Vector3f.ZN.rotation(part.zRot));
|
||||
// 计算反向的位移
|
||||
if (part.getParent() != null) {
|
||||
matrix4f.multiplyWithTranslation(-part.x / 16.0F, -part.y / 16.0F, -part.z / 16.0F);
|
||||
} else {
|
||||
matrix4f.multiplyWithTranslation(-part.x / 16.0F, (1.5F - part.y / 16.0F), -part.z / 16.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matrix4f;
|
||||
}
|
||||
|
||||
private static void applyShootSwayAndRotation(BedrockGunModel model, float aimingProgress) {
|
||||
BedrockPart rootNode = model.getRootNode();
|
||||
if (rootNode != null) {
|
||||
float progress = 1 - (System.currentTimeMillis() - shootTimeStamp) / (SHOOT_ANIMATION_TIME * 1000);
|
||||
if (progress < 0) {
|
||||
progress = 0;
|
||||
}
|
||||
progress = (float) Easing.easeOutCubic(progress);
|
||||
rootNode.offsetX += SHOOT_X_SWAY_NOISE.getValue() / 16 * progress * (1 - aimingProgress);
|
||||
// 基岩版模型 y 轴上下颠倒,sway 值取相反数
|
||||
rootNode.offsetY += -SHOOT_Y_SWAY / 16 * progress * (1 - aimingProgress);
|
||||
rootNode.additionalQuaternion.mul(Vector3f.YP.rotation(SHOOT_Y_ROTATION_NOISE.getValue() * progress));
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyJumpingSway(BedrockGunModel model, float partialTicks) {
|
||||
if (jumpingTimeStamp == -1) {
|
||||
jumpingTimeStamp = System.currentTimeMillis();
|
||||
}
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player != null) {
|
||||
double posY = Mth.lerp(partialTicks, Minecraft.getInstance().player.yOld, Minecraft.getInstance().player.getY());
|
||||
float velocityY = (float) (posY - Minecraft.getInstance().player.yOld) / partialTicks;
|
||||
if (player.isOnGround()) {
|
||||
if (!lastOnGround) {
|
||||
jumpingSwayProgress = velocityY / -0.1f;
|
||||
if (jumpingSwayProgress > 1) {
|
||||
jumpingSwayProgress = 1;
|
||||
}
|
||||
lastOnGround = true;
|
||||
} else {
|
||||
jumpingSwayProgress -= (System.currentTimeMillis() - jumpingTimeStamp) / (LANDING_SWAY_TIME * 1000);
|
||||
if (jumpingSwayProgress < 0) {
|
||||
jumpingSwayProgress = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (lastOnGround) {
|
||||
// 0.42 是玩家自然起跳的速度
|
||||
jumpingSwayProgress = velocityY / 0.42f;
|
||||
if (jumpingSwayProgress > 1) {
|
||||
jumpingSwayProgress = 1;
|
||||
}
|
||||
lastOnGround = false;
|
||||
} else {
|
||||
jumpingSwayProgress -= (System.currentTimeMillis() - jumpingTimeStamp) / (JUMPING_SWAY_TIME * 1000);
|
||||
if (jumpingSwayProgress < 0) {
|
||||
jumpingSwayProgress = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jumpingTimeStamp = System.currentTimeMillis();
|
||||
float ySway = JUMPING_DYNAMICS.update(JUMPING_Y_SWAY * jumpingSwayProgress);
|
||||
BedrockPart rootNode = model.getRootNode();
|
||||
if (rootNode != null) {
|
||||
// 基岩版模型 y 轴上下颠倒,sway 值取相反数
|
||||
rootNode.offsetY += -ySway / 16;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动画约束点的变换数据。
|
||||
*
|
||||
* @param originTranslation 用于输出约束点的原坐标
|
||||
* @param animatedTranslation 用于输出约束点经过动画变换之后的坐标
|
||||
* @param rotation 用于输出约束点的旋转
|
||||
*/
|
||||
private static void getAnimationConstraintTransform(List<BedrockPart> nodePath, @Nonnull Vector3f originTranslation, @Nonnull Vector3f animatedTranslation, @Nonnull Vector3f rotation) {
|
||||
if (nodePath == null) {
|
||||
return;
|
||||
}
|
||||
// 约束点动画变换矩阵
|
||||
Matrix4f animeMatrix = new Matrix4f();
|
||||
// 约束点初始变换矩阵
|
||||
Matrix4f originMatrix = new Matrix4f();
|
||||
animeMatrix.setIdentity();
|
||||
originMatrix.setIdentity();
|
||||
BedrockPart constrainNode = nodePath.get(nodePath.size() - 1);
|
||||
for (BedrockPart part : nodePath) {
|
||||
// 乘动画位移
|
||||
if (part != constrainNode) {
|
||||
animeMatrix.multiplyWithTranslation(part.offsetX, part.offsetY, part.offsetZ);
|
||||
}
|
||||
// 乘组位移
|
||||
if (part.getParent() != null) {
|
||||
animeMatrix.multiplyWithTranslation(part.x / 16.0F, part.y / 16.0F, part.z / 16.0F);
|
||||
} else {
|
||||
animeMatrix.multiplyWithTranslation(part.x / 16.0F, (part.y / 16.0F - 1.5F), part.z / 16.0F);
|
||||
}
|
||||
// 乘动画旋转
|
||||
if (part != constrainNode) {
|
||||
animeMatrix.multiply(part.additionalQuaternion);
|
||||
}
|
||||
// 乘组旋转
|
||||
animeMatrix.multiply(Vector3f.ZP.rotation(part.zRot));
|
||||
animeMatrix.multiply(Vector3f.YP.rotation(part.yRot));
|
||||
animeMatrix.multiply(Vector3f.XP.rotation(part.xRot));
|
||||
|
||||
// 乘组位移
|
||||
if (part.getParent() != null) {
|
||||
originMatrix.multiplyWithTranslation(part.x / 16.0F, part.y / 16.0F, part.z / 16.0F);
|
||||
} else {
|
||||
originMatrix.multiplyWithTranslation(part.x / 16.0F, (part.y / 16.0F - 1.5F), part.z / 16.0F);
|
||||
}
|
||||
// 乘组旋转
|
||||
originMatrix.multiply(Vector3f.ZP.rotation(part.zRot));
|
||||
originMatrix.multiply(Vector3f.YP.rotation(part.yRot));
|
||||
originMatrix.multiply(Vector3f.XP.rotation(part.xRot));
|
||||
|
||||
}
|
||||
// 把变换数据写入输出
|
||||
animatedTranslation.set(animeMatrix.m03, animeMatrix.m13, animeMatrix.m23);
|
||||
originTranslation.set(originMatrix.m03, originMatrix.m13, originMatrix.m23);
|
||||
Vector3f animatedRotation = MathUtil.getEulerAngles(animeMatrix);
|
||||
Vector3f originRotation = MathUtil.getEulerAngles(originMatrix);
|
||||
animatedRotation.sub(originRotation);
|
||||
rotation.set(animatedRotation.x(), animatedRotation.y(), animatedRotation.z());
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用动画约束变换。
|
||||
*
|
||||
* @param weight 控制约束变换的权重,用于插值。
|
||||
*/
|
||||
public static void applyAnimationConstraintTransform(PoseStack poseStack, BedrockGunModel gunModel, float weight) {
|
||||
List<BedrockPart> nodePath = gunModel.getConstraintPath();
|
||||
if (nodePath == null) {
|
||||
return;
|
||||
}
|
||||
if (gunModel.getConstraintObject() == null) {
|
||||
return;
|
||||
}
|
||||
// 获取动画约束点的变换信息
|
||||
Vector3f originTranslation = new Vector3f();
|
||||
Vector3f animatedTranslation = new Vector3f();
|
||||
Vector3f rotation = new Vector3f();
|
||||
Vector3f translationICA = gunModel.getConstraintObject().translationConstraint;
|
||||
Vector3f rotationICA = gunModel.getConstraintObject().rotationConstraint;
|
||||
getAnimationConstraintTransform(nodePath, originTranslation, animatedTranslation, rotation);
|
||||
// 配合约束系数,计算约束位移需要的反向位移
|
||||
Vector3f inverseTranslation = originTranslation.copy();
|
||||
inverseTranslation.sub(animatedTranslation);
|
||||
inverseTranslation.mul(1 - translationICA.x(), 1 - translationICA.y(), 1 - translationICA.z());
|
||||
// 计算约束旋转需要的反向旋转。因需要插值,获取的是欧拉角
|
||||
Vector3f inverseRotation = rotation.copy();
|
||||
inverseRotation.mul(rotationICA.x() - 1, rotationICA.y() - 1, rotationICA.z() - 1);
|
||||
// 约束旋转
|
||||
poseStack.translate(animatedTranslation.x(), animatedTranslation.y() + 1.5f, animatedTranslation.z());
|
||||
poseStack.mulPose(Vector3f.XP.rotation(inverseRotation.x() * weight));
|
||||
poseStack.mulPose(Vector3f.YP.rotation(inverseRotation.y() * weight));
|
||||
poseStack.mulPose(Vector3f.ZP.rotation(inverseRotation.z() * weight));
|
||||
poseStack.translate(-animatedTranslation.x(), -animatedTranslation.y() - 1.5f, -animatedTranslation.z());
|
||||
// 约束位移
|
||||
poseStack.last().pose().translate(new Vector3f(-inverseTranslation.x() * weight, -inverseTranslation.y() * weight, inverseTranslation.z() * weight));
|
||||
}
|
||||
}
|
||||
78
src/main/java/com/tacz/guns/client/event/InventoryEvent.java
Normal file
78
src/main/java/com/tacz/guns/client/event/InventoryEvent.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package com.tacz.guns.client.event;
|
||||
|
||||
import com.tacz.guns.GunMod;
|
||||
import com.tacz.guns.api.client.event.SwapItemWithOffHand;
|
||||
import com.tacz.guns.api.client.gameplay.IClientPlayerGunOperator;
|
||||
import com.tacz.guns.api.item.IGun;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = GunMod.MOD_ID)
|
||||
public class InventoryEvent {
|
||||
// 用于切枪逻辑
|
||||
private static int oldHotbarSelected = -1;
|
||||
private static ItemStack oldHotbarSelectItem = ItemStack.EMPTY;
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerChangeSelect(TickEvent.ClientTickEvent event) {
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
Inventory inventory = player.getInventory();
|
||||
// 玩家切换选中框的情况
|
||||
if (oldHotbarSelected != inventory.selected) {
|
||||
if (oldHotbarSelected == -1) {
|
||||
IClientPlayerGunOperator.fromLocalPlayer(player).draw(ItemStack.EMPTY);
|
||||
} else {
|
||||
IClientPlayerGunOperator.fromLocalPlayer(player).draw(inventory.getItem(oldHotbarSelected));
|
||||
}
|
||||
oldHotbarSelected = inventory.selected;
|
||||
oldHotbarSelectItem = inventory.getItem(inventory.selected).copy();
|
||||
return;
|
||||
}
|
||||
// 玩家选中的物品改变的情况
|
||||
ItemStack currentItem = inventory.getItem(inventory.selected);
|
||||
if (!ItemStack.matches(oldHotbarSelectItem, currentItem)) {
|
||||
if (!isSame(oldHotbarSelectItem, currentItem)) {
|
||||
IClientPlayerGunOperator.fromLocalPlayer(player).draw(oldHotbarSelectItem);
|
||||
}
|
||||
oldHotbarSelectItem = currentItem.copy();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerSwapMainHand(SwapItemWithOffHand event) {
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
IClientPlayerGunOperator.fromLocalPlayer(player).draw(player.getOffhandItem());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLoggedOut(ClientPlayerNetworkEvent.LoggedOutEvent event) {
|
||||
// 离开游戏时重置客户端 draw 状态
|
||||
oldHotbarSelected = -1;
|
||||
oldHotbarSelectItem = ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
private static boolean isSame(ItemStack i, ItemStack j) {
|
||||
IGun iGun1 = IGun.getIGunOrNull(i);
|
||||
IGun iGun2 = IGun.getIGunOrNull(j);
|
||||
if (iGun1 != null && iGun2 != null) {
|
||||
return iGun1.getGunId(i).equals(iGun2.getGunId(j));
|
||||
}
|
||||
if (i.isEmpty() || j.isEmpty()) {
|
||||
return i.isEmpty() && j.isEmpty();
|
||||
}
|
||||
return i.sameItem(j);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.tacz.guns.client.event;
|
||||
|
||||
import com.mojang.blaze3d.audio.SoundBuffer;
|
||||
import com.tacz.guns.client.sound.GunSoundInstance;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.sound.PlaySoundSourceEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
@Mod.EventBusSubscriber(value = Dist.CLIENT)
|
||||
public class PlayGunSoundEvent {
|
||||
@SubscribeEvent
|
||||
public static void onPlaySoundSource(PlaySoundSourceEvent event) {
|
||||
if (event.getSound() instanceof GunSoundInstance instance) {
|
||||
SoundBuffer soundBuffer = instance.getSoundBuffer();
|
||||
if (soundBuffer != null) {
|
||||
event.getChannel().attachStaticBuffer(soundBuffer);
|
||||
event.getChannel().play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.tacz.guns.client.event;
|
||||
|
||||
import com.tacz.guns.api.TimelessAPI;
|
||||
import com.tacz.guns.api.event.common.LivingHurtByGunEvent;
|
||||
import com.tacz.guns.client.renderer.other.GunHurtBobTweak;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
@Mod.EventBusSubscriber(value = Dist.CLIENT)
|
||||
public class PlayerHurtByGunEvent {
|
||||
@SubscribeEvent
|
||||
public static void onPlayerHurtByGun(LivingHurtByGunEvent event) {
|
||||
LogicalSide logicalSide = event.getLogicalSide();
|
||||
if (logicalSide != LogicalSide.CLIENT) {
|
||||
return;
|
||||
}
|
||||
LivingEntity hurtEntity = event.getHurtEntity();
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
// 当受伤的是自己的时候,触发受伤晃动的调整参数
|
||||
if (player != null && player.equals(hurtEntity)) {
|
||||
ResourceLocation gunId = event.getGunId();
|
||||
TimelessAPI.getCommonGunIndex(gunId).ifPresent(index -> {
|
||||
float tweakMultiplier = index.getGunData().getHurtBobTweakMultiplier();
|
||||
GunHurtBobTweak.markTimestamp(tweakMultiplier);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user