forked from Elkien3/citysim_game
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
1,333 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
models/* | ||
!models/character.b3d | ||
!models/character.b3d.gltf | ||
!models/character.blend | ||
modeldata.lua |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Character Animations (`character_anim`) | ||
|
||
Animates the character. Resembles [`playeranim`](https://github.com/minetest-mods/playeranim) and [`headanim`](https://github.com/LoneWolfHT/headanim). | ||
|
||
## About | ||
|
||
Depends on [`modlib`](https://github.com/appgurueu/modlib) and [`cmdlib`](https://github.com/appgurueu/cmdlib). Code written by Lars Mueller aka LMD or appguru(eu) and licensed under the MIT license. Media (player model) was created by [MTG contributors](https://github.com/minetest/minetest_game/blob/master/mods/player_api/README.txt) (MirceaKitsune, stujones11 and An0n3m0us) and is licensed under the CC BY-SA 3.0 license. | ||
|
||
## Screenshot | ||
|
||
![Image](screenshot.png) | ||
|
||
## Links | ||
|
||
* [GitHub](https://github.com/appgurueu/character_anim) - sources, issue tracking, contributing | ||
* [Discord](https://discordapp.com/invite/ysP74by) - discussion, chatting | ||
* [Minetest Forum](https://forum.minetest.net/viewtopic.php?f=9&t=25385) - (more organized) discussion | ||
* [ContentDB](https://content.minetest.net/packages/LMD/character_anim) - releases (cloning from GitHub is recommended) | ||
|
||
# Features | ||
|
||
* Animates head, right arm & body | ||
* Advantages over `playeranim`: | ||
* Extracts exact animations and bone positions from glTF models | ||
* Also animates attached players (with restrictions on angles) | ||
* Advantages over `headanim`: | ||
* Provides compatibility for Minetest 5.2.0 and lower | ||
* Head angles are clamped, head can tilt sideways | ||
* Animates right arm & body as well | ||
|
||
# Instructions | ||
|
||
0. If you want to use a custom model, install [`binarystream`](https://luarocks.org/modules/Tarik02/binarystream) from LuaRocks: | ||
1. `sudo luarocks install binarystream` on many UNIX-systems | ||
2. Add `player_animations` to `secure.trusted_mods` (or disable mod security) | ||
3. Export the model as `glTF` and save it under `models/modelname.extension.gltf` | ||
4. Do `/ca import modelname.extension` | ||
1. Install and use `character_anim` like any other mod |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
local angle = { type = "number", range = { -180, 180 } } | ||
local range = { | ||
type = "table", | ||
children = { angle, angle }, | ||
func = function(range) | ||
if range[2] < range[1] then return "First range value is not <= second range value" end | ||
end | ||
} | ||
local model = { | ||
type = "table", | ||
children = { | ||
body = { | ||
type = "table", | ||
children = { turn_speed = { type = "number", range = { 0, 1e3 } } } | ||
}, | ||
head = { | ||
type = "table", | ||
children = { | ||
pitch = range, | ||
yaw = range, | ||
yaw_restricted = range, | ||
yaw_restriction = angle | ||
} | ||
}, | ||
arm_right = { | ||
type = "table", | ||
children = { radius = angle, speed = { type = "number", range = { 0, 1e4 } }, yaw = range } | ||
} | ||
} | ||
} | ||
conf = modlib.conf.import(minetest.get_current_modname(), { | ||
type = "table", | ||
children = { | ||
default = model, | ||
models = { type = "table", keys = { type = "string" }, values = model } | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"default": { | ||
"body": { "turn_speed": 0.2 }, | ||
"head": { | ||
"pitch": [ -60, 80 ], | ||
"yaw": [ -90, 90 ], | ||
"yaw_restricted": [ 0, 45 ], | ||
"yaw_restriction": 60 | ||
}, | ||
"arm_right": { "radius": 10, "speed": 1e3, "yaw": [ -30, 160 ] } | ||
}, | ||
"models": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
-- TODO use minetest.request_insecure_environment() | ||
local BinaryStream = require("binarystream") | ||
|
||
local data_uri_start = "data:application/octet-stream;base64," | ||
function read_bonedata(path) | ||
local gltf = minetest.parse_json(modlib.file.read(path)) | ||
local buffers = {} | ||
for index, buffer in ipairs(gltf.buffers) do | ||
buffer = buffer.uri | ||
assert(modlib.text.starts_with(buffer, data_uri_start)) | ||
buffers[index] = minetest.decode_base64(buffer:sub((data_uri_start):len()+1)) | ||
end | ||
local accessors = gltf.accessors | ||
local function read_accessor(accessor) | ||
local buffer_view = gltf.bufferViews[accessor.bufferView + 1] | ||
buffer = buffers[buffer_view.buffer + 1] | ||
local binary_stream = BinaryStream(buffer, buffer:len()) | ||
-- See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations | ||
local component_readers = { | ||
[5120] = function() | ||
return math.max(binary_stream:readS8() / 127, -1) | ||
end, | ||
[5121] = function() | ||
return math.max(binary_stream:readU8() / 255) | ||
end, | ||
[5122] = function() | ||
return math.max(binary_stream:readS16() / 32767, -1) | ||
end, | ||
[5123] = function() | ||
return math.max(binary_stream:readU16() / 255) | ||
end, | ||
[5125] = function() | ||
return math.max(binary_stream:readU8() / 255) | ||
end, | ||
[5126] = function() | ||
return binary_stream:readF32() | ||
end | ||
} | ||
local accessor_type = accessor.type | ||
local component_reader = component_readers[accessor.componentType] | ||
binary_stream:skip(buffer_view.byteOffset) | ||
local values = {} | ||
for index = 1, accessor.count do | ||
if accessor_type == "SCALAR" then | ||
values[index] = component_reader() | ||
elseif accessor_type == "VEC3" then | ||
values[index] = { | ||
x = component_reader(), | ||
y = component_reader(), | ||
z = component_reader() | ||
} | ||
elseif accessor_type == "VEC4" then | ||
values[index] = { | ||
component_reader(), | ||
component_reader(), | ||
component_reader(), | ||
component_reader() | ||
} | ||
end | ||
end | ||
return values | ||
end | ||
local nodes = gltf.nodes | ||
local animation = gltf.animations[1] | ||
local channels, samplers = animation.channels, animation.samplers | ||
local animations_by_nodename = {} | ||
for _, node in pairs(nodes) do | ||
animations_by_nodename[node.name] = { | ||
default_translation = node.translation, | ||
default_rotation = node.rotation | ||
} | ||
end | ||
for _, channel in ipairs(channels) do | ||
local path, node_index, sampler = channel.target.path, channel.target.node, samplers[channel.sampler + 1] | ||
assert(sampler.interpolation == "LINEAR") | ||
if path == "translation" or path == "rotation" then | ||
local time_accessor = accessors[sampler.input + 1] | ||
local time, transform = read_accessor(time_accessor), read_accessor(accessors[sampler.output + 1]) | ||
local min_time, max_time = time_accessor.min and time_accessor.min[1] or modlib.table.min(time), time_accessor.max and time_accessor.max[1] or modlib.table.max(time) | ||
local nodename = nodes[node_index + 1].name | ||
assert(not animations_by_nodename[nodename][path]) | ||
animations_by_nodename[nodename][path] = { | ||
start_time = min_time, | ||
end_time = max_time, | ||
keyframes = time, | ||
values = transform | ||
} | ||
end | ||
end | ||
-- HACK to remove unanimated bones (technically invalid, but only proper way to remove Armature / Player / Camera / Suns) | ||
for bone, animation in pairs(animations_by_nodename) do | ||
if not(animation.translation or animation.rotation) then | ||
animations_by_nodename[bone] = nil | ||
end | ||
end | ||
local is_root, is_child = {}, {} | ||
for index, node in pairs(nodes) do | ||
if animations_by_nodename[node.name] then | ||
local children = node.children | ||
if children and #children > 0 then | ||
is_root[index] = node | ||
for _, child_index in pairs(children) do | ||
child_index = child_index + 1 | ||
assert(not is_child[child_index]) | ||
is_child[child_index] = true | ||
is_root[child_index] = nil | ||
end | ||
end | ||
end | ||
end | ||
local order = {} | ||
local insert = modlib.func.curry(table.insert, order) | ||
for node_index in pairs(is_root) do | ||
local node = nodes[node_index] | ||
insert(node.name) | ||
local function insert_children(parent, children) | ||
for _, child_index in ipairs(children) do | ||
local child = nodes[child_index + 1] | ||
local name = child.name | ||
animations_by_nodename[name].parent = parent | ||
insert(name) | ||
if child.children then | ||
insert_children(name, child.children) | ||
end | ||
end | ||
end | ||
insert_children(node.name, node.children) | ||
end | ||
for index, node in ipairs(nodes) do | ||
if animations_by_nodename[node.name] and not(is_root[index] or is_child[index]) then | ||
insert(node.name) | ||
end | ||
end | ||
return {order = order, animations_by_nodename = animations_by_nodename} | ||
end | ||
|
||
local basepath = modlib.mod.get_resource"" | ||
|
||
function import_model(filename) | ||
local path = basepath .. "models/".. filename .. ".gltf" | ||
if not modlib.file.exists(path) then | ||
return false | ||
end | ||
modeldata[filename] = read_bonedata(path) | ||
modlib.file.write(basepath .. "modeldata.lua", minetest.serialize(modeldata)) | ||
return true | ||
end | ||
|
||
cmdlib.register_chatcommand("ca import", { | ||
params = "<filename>", | ||
description = "Imports a model for use with character_anim", | ||
privs = {server = true}, | ||
func = function(_, params) | ||
local filename = params.filename | ||
local success = import_model(filename) | ||
return success, success and "Model " .. filename .. " imported successfully" or "File "..filename.." does not exist" | ||
end | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
setfenv(1, modlib.mod) | ||
local namespace = create_namespace() | ||
local quaternion = setmetatable({}, {__index = namespace}) | ||
include_env(get_resource"quaternion.lua", quaternion) | ||
namespace.quaternion = quaternion | ||
extend"conf" | ||
extend"importer" | ||
extend"main" |
Oops, something went wrong.