diff options
author | Juan Linietsky <reduzio@gmail.com> | 2014-11-12 11:23:23 -0300 |
---|---|---|
committer | Juan Linietsky <reduzio@gmail.com> | 2014-11-12 11:23:23 -0300 |
commit | 6dd8768811cfca5bb831619d93cf870e5d20667f (patch) | |
tree | 9e8837b7c8334855a1bce1bd79ab441edde28129 | |
parent | c8cd5222a7fa931f072e02b23c5b9d826d0ef548 (diff) |
3D Import Import & UDP
-=-=-=-=-=-=-=-=-=-=-
-Animation Import filter support
-Animation Clip import support
-Animation Optimizer Fixes, Improvements and Visibile Options
-Extremely Experimental UDP support.
41 files changed, 1050 insertions, 185 deletions
diff --git a/core/io/packet_peer.cpp b/core/io/packet_peer.cpp index 37fc9c4a0a..b566ce4b7b 100644 --- a/core/io/packet_peer.cpp +++ b/core/io/packet_peer.cpp @@ -111,7 +111,7 @@ Variant PacketPeer::_bnd_get_var() const { void PacketPeer::_bind_methods() { ObjectTypeDB::bind_method(_MD("get_var"),&PacketPeer::_bnd_get_var); - ObjectTypeDB::bind_method(_MD("put_var", "var:Variant"),&PacketPeer::put_var); + ObjectTypeDB::bind_method(_MD("put_var", "var:var"),&PacketPeer::put_var); ObjectTypeDB::bind_method(_MD("get_available_packet_count"),&PacketPeer::get_available_packet_count); }; diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp new file mode 100644 index 0000000000..60f56ed28a --- /dev/null +++ b/core/io/packet_peer_udp.cpp @@ -0,0 +1,48 @@ +#include "packet_peer_udp.h" + + + +PacketPeerUDP* (*PacketPeerUDP::_create)()=NULL; + +int PacketPeerUDP::_get_packet_address() const { + + IP_Address ip = get_packet_address(); + return ip.host; +} + +String PacketPeerUDP::_get_packet_ip() const { + + return get_packet_address(); +} + + +void PacketPeerUDP::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("listen:Error","port","recv_buf_size"),&PacketPeerUDP::listen,DEFVAL(65536)); + ObjectTypeDB::bind_method(_MD("close"),&PacketPeerUDP::close); + ObjectTypeDB::bind_method(_MD("poll:Error"),&PacketPeerUDP::poll); + ObjectTypeDB::bind_method(_MD("is_listening"),&PacketPeerUDP::is_listening); + ObjectTypeDB::bind_method(_MD("get_packet_ip"),&PacketPeerUDP::_get_packet_ip); + ObjectTypeDB::bind_method(_MD("get_packet_address"),&PacketPeerUDP::_get_packet_address); + ObjectTypeDB::bind_method(_MD("set_send_address","address","port"),&PacketPeerUDP::set_send_address); + + +} + +Ref<PacketPeerUDP> PacketPeerUDP::create_ref() { + + if (!_create) + return Ref<PacketPeerUDP>(); + return Ref<PacketPeerUDP>(_create()); +} + +PacketPeerUDP* PacketPeerUDP::create() { + + if (!_create) + return NULL; + return _create(); +} + +PacketPeerUDP::PacketPeerUDP() +{ +} diff --git a/core/io/packet_peer_udp.h b/core/io/packet_peer_udp.h new file mode 100644 index 0000000000..049ac0132f --- /dev/null +++ b/core/io/packet_peer_udp.h @@ -0,0 +1,35 @@ +#ifndef PACKET_PEER_UDP_H +#define PACKET_PEER_UDP_H + + +#include "io/packet_peer.h" + +class PacketPeerUDP : public PacketPeer { + OBJ_TYPE(PacketPeerUDP,PacketPeer); + +protected: + + static PacketPeerUDP* (*_create)(); + static void _bind_methods(); + + int _get_packet_address() const; + String _get_packet_ip() const; + +public: + + virtual Error listen(int p_port,int p_recv_buffer_size=65536)=0; + virtual void close()=0; + virtual Error poll()=0; + virtual bool is_listening() const=0; + virtual IP_Address get_packet_address() const=0; + virtual int get_packet_port() const=0; + virtual void set_send_address(const IP_Address& p_address,int p_port)=0; + + + static Ref<PacketPeerUDP> create_ref(); + static PacketPeerUDP* create(); + + PacketPeerUDP(); +}; + +#endif // PACKET_PEER_UDP_H diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 492068f604..2f16e31de6 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -29,6 +29,7 @@ #include "register_core_types.h" #include "io/tcp_server.h" +#include "io/packet_peer_udp.h" #include "io/config_file.h" #include "os/main_loop.h" #include "io/packet_peer.h" @@ -115,6 +116,7 @@ void register_core_types() { ObjectTypeDB::register_virtual_type<StreamPeer>(); ObjectTypeDB::register_create_type<StreamPeerTCP>(); ObjectTypeDB::register_create_type<TCP_Server>(); + ObjectTypeDB::register_create_type<PacketPeerUDP>(); ObjectTypeDB::register_create_type<StreamPeerSSL>(); ObjectTypeDB::register_virtual_type<IP>(); ObjectTypeDB::register_virtual_type<PacketPeer>(); diff --git a/demos/2d/shower_of_bullets/bullets.gd b/demos/2d/shower_of_bullets/bullets.gd index f76fcc38ba..79f4faaae6 100644 --- a/demos/2d/shower_of_bullets/bullets.gd +++ b/demos/2d/shower_of_bullets/bullets.gd @@ -65,9 +65,9 @@ func _ready(): func _exit_tree(): for b in bullets: - Physics2DServer.free(b.body) + Physics2DServer.free_rid(b.body) - Physics2DServer.free(shape) + Physics2DServer.free_rid(shape) # Initalization here bullets.clear() diff --git a/drivers/gles2/rasterizer_gles2.cpp b/drivers/gles2/rasterizer_gles2.cpp index 4044496953..bc1b17eade 100644 --- a/drivers/gles2/rasterizer_gles2.cpp +++ b/drivers/gles2/rasterizer_gles2.cpp @@ -4173,6 +4173,9 @@ void RasterizerGLES2::capture_viewport(Image* r_capture) { pixels.resize(viewport.width*viewport.height*4); DVector<uint8_t>::Write w = pixels.write(); glPixelStorei(GL_PACK_ALIGNMENT, 4); + + uint64_t time = OS::get_singleton()->get_ticks_usec(); + if (current_rt) { #ifdef GLEW_ENABLED glReadBuffer(GL_COLOR_ATTACHMENT0); @@ -4182,11 +4185,13 @@ void RasterizerGLES2::capture_viewport(Image* r_capture) { // back? glReadPixels( viewport.x, window_size.height-(viewport.height+viewport.y), viewport.width,viewport.height,GL_RGBA,GL_UNSIGNED_BYTE,w.ptr()); } + printf("readpixels time %i\n", (int)(OS::get_singleton()->get_ticks_usec() - time)); w=DVector<uint8_t>::Write(); r_capture->create(viewport.width,viewport.height,0,Image::FORMAT_RGBA,pixels); r_capture->flip_y(); + printf("total time %i\n", (int)(OS::get_singleton()->get_ticks_usec() - time)); #endif diff --git a/drivers/theoraplayer/include/theoraplayer/TheoraVideoClip.h b/drivers/theoraplayer/include/theoraplayer/TheoraVideoClip.h index b2987c01c4..fe71cf8566 100644 --- a/drivers/theoraplayer/include/theoraplayer/TheoraVideoClip.h +++ b/drivers/theoraplayer/include/theoraplayer/TheoraVideoClip.h @@ -87,6 +87,7 @@ protected: std::string mName; int mWidth, mHeight, mStride; int mNumFrames; + int audio_track; int mSubFrameWidth, mSubFrameHeight, mSubFrameOffsetX, mSubFrameOffsetY; float mAudioGain; //! multiplier for audio samples. between 0 and 1 @@ -233,6 +234,7 @@ public: bool getAutoRestart() { return mAutoRestart; } + void set_audio_track(int p_track) { audio_track=p_track; } /** TODO: user priority. Useful only when more than one video is being decoded diff --git a/drivers/theoraplayer/include/theoraplayer/TheoraVideoManager.h b/drivers/theoraplayer/include/theoraplayer/TheoraVideoManager.h index 3ff9b217cd..d94c51b4d4 100644 --- a/drivers/theoraplayer/include/theoraplayer/TheoraVideoManager.h +++ b/drivers/theoraplayer/include/theoraplayer/TheoraVideoManager.h @@ -67,8 +67,8 @@ public: //! search registered clips by name TheoraVideoClip* getVideoClipByName(std::string name); - TheoraVideoClip* createVideoClip(std::string filename,TheoraOutputMode output_mode=TH_RGB,int numPrecachedOverride=0,bool usePower2Stride=0); - TheoraVideoClip* createVideoClip(TheoraDataSource* data_source,TheoraOutputMode output_mode=TH_RGB,int numPrecachedOverride=0,bool usePower2Stride=0); + TheoraVideoClip* createVideoClip(std::string filename,TheoraOutputMode output_mode=TH_RGB,int numPrecachedOverride=0,bool usePower2Stride=0, int p_track=0); + TheoraVideoClip* createVideoClip(TheoraDataSource* data_source,TheoraOutputMode output_mode=TH_RGB,int numPrecachedOverride=0,bool usePower2Stride=0, int p_audio_track=0); void update(float timeDelta); diff --git a/drivers/theoraplayer/src/AVFoundation/TheoraVideoClip_AVFoundation.mm b/drivers/theoraplayer/src/AVFoundation/TheoraVideoClip_AVFoundation.mm index 8c3d2cc3b9..72e3dfc9fa 100644 --- a/drivers/theoraplayer/src/AVFoundation/TheoraVideoClip_AVFoundation.mm +++ b/drivers/theoraplayer/src/AVFoundation/TheoraVideoClip_AVFoundation.mm @@ -271,7 +271,8 @@ void TheoraVideoClip_AVFoundation::load(TheoraDataSource* source) AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; NSArray* audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio]; - AVAssetTrack *audioTrack = audioTracks.count > 0 ? [audioTracks objectAtIndex:0] : NULL; + AVAssetTrack *audioTrack = audioTracks.count > 0 ? [audioTracks objectAtIndex:audio_track] : NULL; + printf("*********** using audio track %i\n", audio_track); #ifdef _AVFOUNDATION_BGRX bool yuv_output = (mOutputMode != TH_BGRX && mOutputMode != TH_RGBA); diff --git a/drivers/theoraplayer/src/TheoraVideoClip.cpp b/drivers/theoraplayer/src/TheoraVideoClip.cpp index b71319e6a1..ed9f2c22da 100644 --- a/drivers/theoraplayer/src/TheoraVideoClip.cpp +++ b/drivers/theoraplayer/src/TheoraVideoClip.cpp @@ -51,6 +51,8 @@ TheoraVideoClip::TheoraVideoClip(TheoraDataSource* data_source, mWaitingForCache(false), mOutputMode(TH_UNDEFINED) { + + audio_track=0; mAudioMutex = NULL; mThreadAccessMutex = new TheoraMutex(); mTimer = mDefaultTimer = new TheoraTimer(); diff --git a/drivers/theoraplayer/src/TheoraVideoManager.cpp b/drivers/theoraplayer/src/TheoraVideoManager.cpp index 87696d12a9..53b211374a 100644 --- a/drivers/theoraplayer/src/TheoraVideoManager.cpp +++ b/drivers/theoraplayer/src/TheoraVideoManager.cpp @@ -35,6 +35,8 @@ extern "C" void initYUVConversionModule(); } +#include "core/os/memory.h" + //#define _DECODING_BENCHMARK //uncomment to test average decoding time on a given device @@ -184,16 +186,18 @@ TheoraAudioInterfaceFactory* TheoraVideoManager::getAudioInterfaceFactory() TheoraVideoClip* TheoraVideoManager::createVideoClip(std::string filename, TheoraOutputMode output_mode, int numPrecachedOverride, - bool usePower2Stride) + bool usePower2Stride, + int p_track) { - TheoraDataSource* src=new TheoraFileDataSource(filename); - return createVideoClip(src,output_mode,numPrecachedOverride,usePower2Stride); + TheoraDataSource* src=memnew(TheoraFileDataSource(filename)); + return createVideoClip(src,output_mode,numPrecachedOverride,usePower2Stride, p_track); } TheoraVideoClip* TheoraVideoManager::createVideoClip(TheoraDataSource* data_source, TheoraOutputMode output_mode, int numPrecachedOverride, - bool usePower2Stride) + bool usePower2Stride, + int p_audio_track) { mWorkMutex->lock(); @@ -226,6 +230,8 @@ TheoraVideoClip* TheoraVideoManager::createVideoClip(TheoraDataSource* data_sour #ifdef __FFMPEG clip = new TheoraVideoClip_FFmpeg(data_source, output_mode, nPrecached, usePower2Stride); #endif + + clip->set_audio_track(p_audio_track); clip->load(data_source); clip->decodeNextFrame(); // ensure the first frame is always preloaded and have the main thread do it to prevent potential thread starvatio diff --git a/drivers/theoraplayer/video_stream_theoraplayer.cpp b/drivers/theoraplayer/video_stream_theoraplayer.cpp index fdf612ff0f..643899aaed 100644 --- a/drivers/theoraplayer/video_stream_theoraplayer.cpp +++ b/drivers/theoraplayer/video_stream_theoraplayer.cpp @@ -39,6 +39,8 @@ #include "core/ring_buffer.h" #include "core/os/thread_safe.h" +#include "core/globals.h" + static TheoraVideoManager* mgr = NULL; class TPDataFA : public TheoraDataSource { @@ -141,6 +143,7 @@ public: playing=false; _clear(); }; + virtual bool is_playing() const { return true; }; virtual void set_paused(bool p_paused) {}; @@ -164,12 +167,16 @@ public: void input(float* p_data, int p_samples) { + _THREAD_SAFE_METHOD_; + //printf("input %i samples from %p\n", p_samples, p_data); if (rb.space_left() < p_samples) { rb_power += 1; rb.resize(rb_power); } rb.write(p_data, p_samples); + + update(); //update too here for less latency }; void update() { @@ -177,15 +184,16 @@ public: _THREAD_SAFE_METHOD_; int todo = get_todo(); int16_t* buffer = get_write_buffer(); - int samples = rb.data_left(); - const int to_write = MIN(todo, samples); + int frames = rb.data_left()/channels; + const int to_write = MIN(todo, frames); - for (int i=0; i<to_write; i++) { + for (int i=0; i<to_write*channels; i++) { - uint16_t sample = uint16_t(rb.read() * 32767); + int v = rb.read() * 32767; + int16_t sample = CLAMP(v,-32768,32767); buffer[i] = sample; }; - write(to_write/channels); + write(to_write); total_wrote += to_write; }; @@ -231,7 +239,7 @@ public: TPAudioGodot(TheoraVideoClip* owner, int nChannels, int p_freq) : TheoraAudioInterface(owner, nChannels, p_freq), TheoraTimer() { - printf("***************** audio interface constructor\n"); + printf("***************** audio interface constructor freq %i\n", p_freq); channels = nChannels; freq = p_freq; stream = Ref<AudioStreamInput>(memnew(AudioStreamInput(nChannels, p_freq))); @@ -247,12 +255,13 @@ public: void update(float time_increase) { - mTime = (float)(stream->get_total_wrote() / channels) / freq; + //mTime = (float)(stream->get_total_wrote()) / freq; + //mTime = MAX(0,mTime-AudioServer::get_singleton()->get_output_delay()); //mTime = (float)sample_count / channels / freq; - //mTime += time_increase; + mTime += time_increase; //float duration=mClip->getDuration(); //if (mTime > duration) mTime=duration; - //printf("time at timer is %f, samples %i\n", mTime, sample_count); + //printf("time at timer is %f, %f, samples %i\n", mTime, time_increase, sample_count); } }; @@ -358,13 +367,15 @@ void VideoStreamTheoraplayer::pop_frame(Ref<ImageTexture> p_tex) { #endif float w=clip->getWidth(),h=clip->getHeight(); - int imgsize = w * h * f->mBpp; + int imgsize = w * h * f->mBpp; int size = f->getStride() * f->getHeight() * f->mBpp; data.resize(imgsize); - DVector<uint8_t>::Write wr = data.write(); - uint8_t* ptr = wr.ptr(); - copymem(ptr, f->getBuffer(), imgsize); + { + DVector<uint8_t>::Write wr = data.write(); + uint8_t* ptr = wr.ptr(); + memcpy(ptr, f->getBuffer(), imgsize); + } /* for (int i=0; i<h; i++) { int dstofs = i * w * f->mBpp; @@ -421,6 +432,13 @@ void VideoStreamTheoraplayer::update(float p_time) { mgr->update(p_time); }; + +void VideoStreamTheoraplayer::set_audio_track(int p_idx) { + audio_track=p_idx; + if (clip) + clip->set_audio_track(audio_track); +} + void VideoStreamTheoraplayer::set_file(const String& p_file) { FileAccess* f = FileAccess::open(p_file, FileAccess::READ); @@ -436,10 +454,13 @@ void VideoStreamTheoraplayer::set_file(const String& p_file) { mgr->setAudioInterfaceFactory(audio_factory); }; + int track = GLOBAL_DEF("theora/audio_track", 0); // hack + if (p_file.find(".mp4") != -1) { std::string file = p_file.replace("res://", "").utf8().get_data(); - clip = mgr->createVideoClip(file, TH_BGRX, 16); + clip = mgr->createVideoClip(file, TH_RGBX, 2, false, track); + //clip->set_audio_track(audio_track); memdelete(f); } else { @@ -448,6 +469,7 @@ void VideoStreamTheoraplayer::set_file(const String& p_file) { try { clip = mgr->createVideoClip(ds); + clip->set_audio_track(audio_track); } catch (_TheoraGenericException e) { printf("exception ocurred! %s\n", e.repr().c_str()); clip = NULL; @@ -478,6 +500,7 @@ VideoStreamTheoraplayer::VideoStreamTheoraplayer() { started = false; playing = false; loop = false; + audio_track=0; }; diff --git a/drivers/theoraplayer/video_stream_theoraplayer.h b/drivers/theoraplayer/video_stream_theoraplayer.h index d88f495032..f926dfdaf5 100644 --- a/drivers/theoraplayer/video_stream_theoraplayer.h +++ b/drivers/theoraplayer/video_stream_theoraplayer.h @@ -18,6 +18,8 @@ class VideoStreamTheoraplayer : public VideoStream { bool playing; bool loop; + int audio_track; + public: virtual void stop(); @@ -43,6 +45,7 @@ public: void update(float p_time); void set_file(const String& p_file); + void set_audio_track(int p_idx); ~VideoStreamTheoraplayer(); VideoStreamTheoraplayer(); diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index ef4cf644fd..e6458068ea 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -42,6 +42,7 @@ #include "dir_access_unix.h" #include "tcp_server_posix.h" #include "stream_peer_tcp_posix.h" +#include "packet_peer_udp_posix.h" #include <stdarg.h> @@ -115,6 +116,7 @@ void OS_Unix::initialize_core() { #ifndef NO_NETWORK TCPServerPosix::make_default(); StreamPeerTCPPosix::make_default(); + PacketPeerUDPPosix::make_default(); IP_Unix::make_default(); #endif mempool_static = new MemoryPoolStaticMalloc; diff --git a/drivers/unix/packet_peer_udp_posix.cpp b/drivers/unix/packet_peer_udp_posix.cpp new file mode 100644 index 0000000000..d951524d3a --- /dev/null +++ b/drivers/unix/packet_peer_udp_posix.cpp @@ -0,0 +1,188 @@ +#include "packet_peer_udp_posix.h" + +#ifdef UNIX_ENABLED + + +#include <errno.h> +#include <unistd.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <stdio.h> + +#ifndef NO_FCNTL +#include <sys/fcntl.h> +#else +#include <sys/ioctl.h> +#endif + +#ifdef JAVASCRIPT_ENABLED +#include <arpa/inet.h> +#endif + + +int PacketPeerUDPPosix::get_available_packet_count() const { + + Error err = const_cast<PacketPeerUDPPosix*>(this)->poll(); + if (err!=OK) + return 0; + + return queue_count; +} + +Error PacketPeerUDPPosix::get_packet(const uint8_t **r_buffer,int &r_buffer_size) const{ + + Error err = const_cast<PacketPeerUDPPosix*>(this)->poll(); + if (err!=OK) + return err; + if (queue_count==0) + return ERR_UNAVAILABLE; + + uint32_t size; + rb.read((uint8_t*)&size,4,true); + rb.read((uint8_t*)&packet_ip.host,4,true); + rb.read((uint8_t*)&packet_port,4,true); + rb.read(packet_buffer,size,true); + --queue_count; + *r_buffer=packet_buffer; + r_buffer_size=size; + return OK; + +} +Error PacketPeerUDPPosix::put_packet(const uint8_t *p_buffer,int p_buffer_size){ + + int sock = _get_socket(); + ERR_FAIL_COND_V( sock == -1, FAILED ); + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(peer_port); + addr.sin_addr = *((struct in_addr*)&peer_addr.host); + + errno = 0; + int err; + while ( (err = sendto(sock, p_buffer, p_buffer_size, 0, (struct sockaddr*)&addr, sizeof(addr))) != p_buffer_size) { + + if (errno != EAGAIN) { + return FAILED; + } + } + + return OK; +} + +int PacketPeerUDPPosix::get_max_packet_size() const{ + + return 512; // uhm maybe not +} + +Error PacketPeerUDPPosix::listen(int p_port, int p_recv_buffer_size){ + + close(); + int sock = _get_socket(); + if (sock == -1 ) + return ERR_CANT_CREATE; + sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_port = htons(p_port); + addr.sin_addr.s_addr = INADDR_ANY; + if (bind(sock, (struct sockaddr*)&addr, sizeof(sockaddr_in)) == -1 ) { + close(); + return ERR_UNAVAILABLE; + } + printf("UDP Connection listening on port %i\n", p_port); + rb.resize(nearest_power_of_2(p_recv_buffer_size)); + return OK; +} + +void PacketPeerUDPPosix::close(){ + + if (sockfd != -1) + ::close(sockfd); + sockfd=-1; + rb.resize(8); + queue_count=0; +} + +Error PacketPeerUDPPosix::poll() { + + struct sockaddr_in from = {0}; + socklen_t len = sizeof(struct sockaddr_in); + int ret; + while ( (ret = recvfrom(sockfd, recv_buffer, MIN(sizeof(recv_buffer),rb.data_left()-12), MSG_DONTWAIT, (struct sockaddr*)&from, &len)) > 0) { + rb.write((uint8_t*)&from.sin_addr, 4); + uint32_t port = ntohs(from.sin_port); + rb.write((uint8_t*)&port, 4); + rb.write((uint8_t*)&ret, 4); + rb.write(recv_buffer, ret); + + len = sizeof(struct sockaddr_in); + ++queue_count; + }; + + if (ret == 0 || (ret == -1 && errno != EAGAIN) ) { + close(); + return FAILED; + }; + + return OK; +} +bool PacketPeerUDPPosix::is_listening() const{ + + return sockfd!=-1; +} + +IP_Address PacketPeerUDPPosix::get_packet_address() const { + + return packet_ip; +} + +int PacketPeerUDPPosix::get_packet_port() const{ + + return packet_port; +} + +int PacketPeerUDPPosix::_get_socket() { + + if (sockfd != -1) + return sockfd; + + sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + ERR_FAIL_COND_V( sockfd == -1, -1 ); + //fcntl(sockfd, F_SETFL, O_NONBLOCK); + + return sockfd; +} + + +void PacketPeerUDPPosix::set_send_address(const IP_Address& p_address,int p_port) { + + peer_addr=p_address; + peer_port=p_port; +} + +PacketPeerUDP* PacketPeerUDPPosix::_create() { + + return memnew(PacketPeerUDPPosix); +}; + +void PacketPeerUDPPosix::make_default() { + + PacketPeerUDP::_create = PacketPeerUDPPosix::_create; +}; + + +PacketPeerUDPPosix::PacketPeerUDPPosix() { + + sockfd=-1; + packet_port=0; + queue_count=0; + peer_port=0; +} + +PacketPeerUDPPosix::~PacketPeerUDPPosix() { + + close(); +} +#endif diff --git a/drivers/unix/packet_peer_udp_posix.h b/drivers/unix/packet_peer_udp_posix.h new file mode 100644 index 0000000000..428c3ac37e --- /dev/null +++ b/drivers/unix/packet_peer_udp_posix.h @@ -0,0 +1,56 @@ +#ifndef PACKET_PEER_UDP_POSIX_H +#define PACKET_PEER_UDP_POSIX_H + +#ifdef UNIX_ENABLED + +#include "io/packet_peer_udp.h" +#include "ring_buffer.h" + +class PacketPeerUDPPosix : public PacketPeerUDP { + + + enum { + PACKET_BUFFER_SIZE=65536 + }; + + mutable RingBuffer<uint8_t> rb; + uint8_t recv_buffer[PACKET_BUFFER_SIZE]; + mutable uint8_t packet_buffer[PACKET_BUFFER_SIZE]; + IP_Address packet_ip; + int packet_port; + mutable int queue_count; + int sockfd; + + IP_Address peer_addr; + int peer_port; + + _FORCE_INLINE_ int _get_socket(); + + static PacketPeerUDP* _create(); + +public: + + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer,int &r_buffer_size) const; + virtual Error put_packet(const uint8_t *p_buffer,int p_buffer_size); + + virtual int get_max_packet_size() const; + + virtual Error listen(int p_port,int p_recv_buffer_size=65536); + virtual void close(); + virtual Error poll(); + virtual bool is_listening() const; + + virtual IP_Address get_packet_address() const; + virtual int get_packet_port() const; + + virtual void set_send_address(const IP_Address& p_address,int p_port); + + static void make_default(); + + PacketPeerUDPPosix(); + ~PacketPeerUDPPosix(); +}; + +#endif // PACKET_PEER_UDP_POSIX_H +#endif diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index bcc818dac8..89f121c3f6 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -1533,7 +1533,7 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() { device_lock = Mutex::create(); quit_request=false; orientation=0; - remove_prev=false; + remove_prev=true; device_thread=Thread::create(_device_poll_thread,this); devices_changed=true; diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 08a775e689..a77428e954 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -7,6 +7,7 @@ common_win=[ "ctxgl_procaddr.cpp", "key_mapping_win.cpp", "tcp_server_winsock.cpp", + "packet_peer_udp_winsock.cpp", "stream_peer_winsock.cpp", ] diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index be53d2d46a..19bf324916 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -45,6 +45,7 @@ #include "servers/visual/visual_server_wrap_mt.h" #include "tcp_server_winsock.h" +#include "packet_peer_udp_winsock.h" #include "stream_peer_winsock.h" #include "os/pc_joystick_map.h" #include "lang_table.h" @@ -173,6 +174,7 @@ void OS_Windows::initialize_core() { TCPServerWinsock::make_default(); StreamPeerWinsock::make_default(); + PacketPeerUDPWinsock::make_default(); mempool_static = new MemoryPoolStaticMalloc; #if 1 diff --git a/platform/windows/packet_peer_udp_winsock.cpp b/platform/windows/packet_peer_udp_winsock.cpp new file mode 100644 index 0000000000..25851c0d73 --- /dev/null +++ b/platform/windows/packet_peer_udp_winsock.cpp @@ -0,0 +1,167 @@ +#include "packet_peer_udp_winsock.h" + +#include <winsock2.h> + +int PacketPeerUDPWinsock::get_available_packet_count() const { + + Error err = const_cast<PacketPeerUDPWinsock*>(this)->poll(); + if (err!=OK) + return 0; + + return queue_count; +} + +Error PacketPeerUDPWinsock::get_packet(const uint8_t **r_buffer,int &r_buffer_size) const{ + + Error err = const_cast<PacketPeerUDPWinsock*>(this)->poll(); + if (err!=OK) + return err; + if (queue_count==0) + return ERR_UNAVAILABLE; + + uint32_t size; + rb.read((uint8_t*)&size,4,true); + rb.read((uint8_t*)&packet_ip.host,4,true); + rb.read((uint8_t*)&packet_port,4,true); + rb.read(packet_buffer,size,true); + --queue_count; + *r_buffer=packet_buffer; + r_buffer_size=size; + return OK; + +} +Error PacketPeerUDPWinsock::put_packet(const uint8_t *p_buffer,int p_buffer_size){ + + int sock = _get_socket(); + ERR_FAIL_COND_V( sock == -1, FAILED ); + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(peer_port); + addr.sin_addr = *((struct in_addr*)&peer_addr.host); + + errno = 0; + int err; + while ( (err = sendto(sock, (const char*)p_buffer, p_buffer_size, 0, (struct sockaddr*)&addr, sizeof(addr))) != p_buffer_size) { + + if (WSAGetLastError() != WSAEWOULDBLOCK) { + return FAILED; + }; + } + + return OK; +} + +int PacketPeerUDPWinsock::get_max_packet_size() const{ + + return 512; // uhm maybe not +} + +Error PacketPeerUDPWinsock::listen(int p_port, int p_recv_buffer_size){ + + close(); + int sock = _get_socket(); + if (sock == -1 ) + return ERR_CANT_CREATE; + sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_port = htons(p_port); + addr.sin_addr.s_addr = INADDR_ANY; + if (bind(sock, (struct sockaddr*)&addr, sizeof(sockaddr_in)) == -1 ) { + close(); + return ERR_UNAVAILABLE; + } + printf("UDP Connection listening on port %i\n", p_port); + rb.resize(nearest_power_of_2(p_recv_buffer_size)); + return OK; +} + +void PacketPeerUDPWinsock::close(){ + + if (sockfd != -1) + ::closesocket(sockfd); + sockfd=-1; + rb.resize(8); + queue_count=0; +} + +Error PacketPeerUDPWinsock::poll() { + + struct sockaddr_in from = {0}; + int len = sizeof(struct sockaddr_in); + int ret; + while ( (ret = recvfrom(sockfd, (char*)recv_buffer, MIN(sizeof(recv_buffer),rb.data_left()-12), 0, (struct sockaddr*)&from, &len)) > 0) { + rb.write((uint8_t*)&from.sin_addr, 4); + uint32_t port = ntohs(from.sin_port); + rb.write((uint8_t*)&port, 4); + rb.write((uint8_t*)&ret, 4); + rb.write(recv_buffer, ret); + + len = sizeof(struct sockaddr_in); + ++queue_count; + }; + + if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) ) { + close(); + return FAILED; + }; + + return OK; +} +bool PacketPeerUDPWinsock::is_listening() const{ + + return sockfd!=-1; +} + +IP_Address PacketPeerUDPWinsock::get_packet_address() const { + + return packet_ip; +} + +int PacketPeerUDPWinsock::get_packet_port() const{ + + return packet_port; +} + +int PacketPeerUDPWinsock::_get_socket() { + + if (sockfd != -1) + return sockfd; + + sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + ERR_FAIL_COND_V( sockfd == -1, -1 ); + //fcntl(sockfd, F_SETFL, O_NONBLOCK); + + return sockfd; +} + + +void PacketPeerUDPWinsock::set_send_address(const IP_Address& p_address,int p_port) { + + peer_addr=p_address; + peer_port=p_port; +} + +void PacketPeerUDPWinsock::make_default() { + + PacketPeerUDP::_create = PacketPeerUDPWinsock::_create; +}; + + +PacketPeerUDP* PacketPeerUDPWinsock::_create() { + + return memnew(PacketPeerUDPWinsock); +}; + + +PacketPeerUDPWinsock::PacketPeerUDPWinsock() { + + sockfd=-1; + packet_port=0; + queue_count=0; + peer_port=0; +} + +PacketPeerUDPWinsock::~PacketPeerUDPWinsock() { + + close(); +} diff --git a/platform/windows/packet_peer_udp_winsock.h b/platform/windows/packet_peer_udp_winsock.h new file mode 100644 index 0000000000..7806666b63 --- /dev/null +++ b/platform/windows/packet_peer_udp_winsock.h @@ -0,0 +1,51 @@ +#ifndef PACKET_PEER_UDP_WINSOCK_H +#define PACKET_PEER_UDP_WINSOCK_H + +#include "io/packet_peer_udp.h" +#include "ring_buffer.h" + +class PacketPeerUDPWinsock : public PacketPeerUDP { + + + enum { + PACKET_BUFFER_SIZE=65536 + }; + + mutable RingBuffer<uint8_t> rb; + uint8_t recv_buffer[PACKET_BUFFER_SIZE]; + mutable uint8_t packet_buffer[PACKET_BUFFER_SIZE]; + IP_Address packet_ip; + int packet_port; + mutable int queue_count; + int sockfd; + + IP_Address peer_addr; + int peer_port; + + _FORCE_INLINE_ int _get_socket(); + + static PacketPeerUDP* _create(); + +public: + + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer,int &r_buffer_size) const; + virtual Error put_packet(const uint8_t *p_buffer,int p_buffer_size); + + virtual int get_max_packet_size() const; + + virtual Error listen(int p_port,int p_recv_buffer_size=65536); + virtual void close(); + virtual Error poll(); + virtual bool is_listening() const; + + virtual IP_Address get_packet_address() const; + virtual int get_packet_port() const; + + virtual void set_send_address(const IP_Address& p_address,int p_port); + + static void make_default(); + PacketPeerUDPWinsock(); + ~PacketPeerUDPWinsock(); +}; +#endif // PACKET_PEER_UDP_WINSOCK_H diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 442fd286dd..cf3bef73ea 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -55,9 +55,9 @@ void BaseButton::_input_event(InputEvent p_event) { if (b.pressed) { if (!toggle_mode) { //mouse press attempt - - status.press_attempt=true; - status.pressing_inside=true; + + status.press_attempt=true; + status.pressing_inside=true; pressed(); emit_signal("pressed"); @@ -74,13 +74,13 @@ void BaseButton::_input_event(InputEvent p_event) { } - } else { - - if (status.press_attempt &&status.pressing_inside) { - pressed(); - emit_signal("pressed"); - } - status.press_attempt=false; + } else { + + if (status.press_attempt && status.pressing_inside) { +// released(); + emit_signal("released"); + } + status.press_attempt=false; } update(); break; @@ -95,14 +95,14 @@ void BaseButton::_input_event(InputEvent p_event) { if (status.press_attempt &&status.pressing_inside) { - + if (!toggle_mode) { //mouse press attempt - + pressed(); - emit_signal("pressed"); + emit_signal("pressed"); } else { - + status.pressed=!status.pressed; pressed(); @@ -110,11 +110,11 @@ void BaseButton::_input_event(InputEvent p_event) { toggled(status.pressed); emit_signal("toggled",status.pressed); - + } - + } - + status.press_attempt=false; } @@ -363,6 +363,7 @@ void BaseButton::_bind_methods() { ObjectTypeDB::bind_method(_MD("get_draw_mode"),&BaseButton::get_draw_mode); ADD_SIGNAL( MethodInfo("pressed" ) ); + ADD_SIGNAL( MethodInfo("released" ) ); ADD_SIGNAL( MethodInfo("toggled", PropertyInfo( Variant::BOOL,"pressed") ) ); ADD_PROPERTY( PropertyInfo( Variant::BOOL, "disabled"), _SCS("set_disabled"), _SCS("is_disabled")); ADD_PROPERTY( PropertyInfo( Variant::BOOL, "toggle_mode"), _SCS("set_toggle_mode"), _SCS("is_toggle_mode")); diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp index 857ea25d0f..050fd890f4 100644 --- a/scene/gui/video_player.cpp +++ b/scene/gui/video_player.cpp @@ -192,7 +192,7 @@ String VideoPlayer::get_stream_name() const { return stream->get_name(); }; -float VideoPlayer::get_pos() const { +float VideoPlayer::get_stream_pos() const { if (stream.is_null()) return 0; @@ -231,7 +231,7 @@ void VideoPlayer::_bind_methods() { ObjectTypeDB::bind_method(_MD("get_stream_name"),&VideoPlayer::get_stream_name); - ObjectTypeDB::bind_method(_MD("get_pos"),&VideoPlayer::get_pos); + ObjectTypeDB::bind_method(_MD("get_stream_pos"),&VideoPlayer::get_stream_pos); ObjectTypeDB::bind_method(_MD("set_autoplay","enabled"),&VideoPlayer::set_autoplay); ObjectTypeDB::bind_method(_MD("has_autoplay"),&VideoPlayer::has_autoplay); diff --git a/scene/gui/video_player.h b/scene/gui/video_player.h index db5f9a58a6..3eb629ced5 100644 --- a/scene/gui/video_player.h +++ b/scene/gui/video_player.h @@ -77,7 +77,7 @@ public: float get_volume_db() const; String get_stream_name() const; - float get_pos() const; + float get_stream_pos() const; void set_autoplay(bool p_vol); bool has_autoplay() const; diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 5561b5ef90..80993c7eaf 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -1716,189 +1716,222 @@ void Animation::clear() { } -void Animation::_transform_track_optimize(int p_idx,float p_alowed_linear_err,float p_alowed_angular_err,float p_max_optimizable_angle) { - ERR_FAIL_INDEX(p_idx,tracks.size()); - ERR_FAIL_COND(tracks[p_idx]->type!=TYPE_TRANSFORM); - TransformTrack *tt= static_cast<TransformTrack*>(tracks[p_idx]); - for(int i=1;i<tt->transforms.size()-1;i++) { - TKey<TransformKey> &t0 = tt->transforms[i-1]; - TKey<TransformKey> &t1 = tt->transforms[i]; - TKey<TransformKey> &t2 = tt->transforms[i+1]; +bool Animation::_transform_track_optimize_key(const TKey<TransformKey> &t0,const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, float p_alowed_linear_err,float p_alowed_angular_err,float p_max_optimizable_angle) { - real_t c = (t1.time-t0.time)/(t2.time-t0.time); - real_t t[3]={-1,-1,-1}; - { //translation + real_t c = (t1.time-t0.time)/(t2.time-t0.time); + real_t t[3]={-1,-1,-1}; - const Vector3 &v0=t0.value.loc; - const Vector3 &v1=t1.value.loc; - const Vector3 &v2=t2.value.loc; + { //translation - if (v0.distance_to(v2)<CMP_EPSILON) { - //0 and 2 are close, let's see if 1 is close - if (v0.distance_to(v1)>CMP_EPSILON) { - //not close, not optimizable - continue; - } + const Vector3 &v0=t0.value.loc; + const Vector3 &v1=t1.value.loc; + const Vector3 &v2=t2.value.loc; - } else { + if (v0.distance_to(v2)<CMP_EPSILON) { + //0 and 2 are close, let's see if 1 is close + if (v0.distance_to(v1)>CMP_EPSILON) { + //not close, not optimizable + return false; + } - Vector3 pd = (v2-v0); - float d0 = pd.dot(v0); - float d1 = pd.dot(v1); - float d2 = pd.dot(v2); - if (d1<d0 || d1>d2) { - continue; //beyond segment range - } + } else { - Vector3 s[2]={ v0, v2 }; - real_t d =Geometry::get_closest_point_to_segment(v1,s).distance_to(v1); + Vector3 pd = (v2-v0); + float d0 = pd.dot(v0); + float d1 = pd.dot(v1); + float d2 = pd.dot(v2); + if (d1<d0 || d1>d2) { + return false; + } - if (d>pd.length()*p_alowed_linear_err) { - continue; //beyond allowed error for colinearity - } + Vector3 s[2]={ v0, v2 }; + real_t d =Geometry::get_closest_point_to_segment(v1,s).distance_to(v1); - t[0] = (d1-d0)/(d2-d0); + if (d>pd.length()*p_alowed_linear_err) { + return false; //beyond allowed error for colinearity } - } - { //rotation + t[0] = (d1-d0)/(d2-d0); + } + } - const Quat &q0=t0.value.rot; - const Quat &q1=t1.value.rot; - const Quat &q2=t2.value.rot; + { //rotation - //localize both to rotation from q0 + const Quat &q0=t0.value.rot; + const Quat &q1=t1.value.rot; + const Quat &q2=t2.value.rot; - if ((q0-q2).length() < CMP_EPSILON) { + //localize both to rotation from q0 - if ((q0-q1).length() > CMP_EPSILON) - continue; + if ((q0-q2).length() < CMP_EPSILON) { - } else { + if ((q0-q1).length() > CMP_EPSILON) + return false; + } else { - Quat r02 = (q0.inverse() * q2).normalized(); - Quat r01 = (q0.inverse() * q1).normalized(); - Vector3 v02,v01; - real_t a02,a01; + Quat r02 = (q0.inverse() * q2).normalized(); + Quat r01 = (q0.inverse() * q1).normalized(); - r02.get_axis_and_angle(v02,a02); - r01.get_axis_and_angle(v01,a01); + Vector3 v02,v01; + real_t a02,a01; - if (Math::abs(a02)>p_max_optimizable_angle) - continue; + r02.get_axis_and_angle(v02,a02); + r01.get_axis_and_angle(v01,a01); - if (v01.dot(v02)<0) { - //make sure both rotations go the same way to compare - v02=-v02; - a02=-a02; - } + if (Math::abs(a02)>p_max_optimizable_angle) + return false; - real_t err_01 = Math::acos(v01.normalized().dot(v02.normalized()))/Math_PI; - if (err_01>p_alowed_angular_err) { - //not rotating in the same axis - continue; - } + if (v01.dot(v02)<0) { + //make sure both rotations go the same way to compare + v02=-v02; + a02=-a02; + } - if (a01*a02 < 0 ) { - //not rotating in the same direction - continue; - } + real_t err_01 = Math::acos(v01.normalized().dot(v02.normalized()))/Math_PI; + if (err_01>p_alowed_angular_err) { + //not rotating in the same axis + return false; + } - real_t tr = a01/a02; - if (tr<0 || tr>1) - continue; //rotating too much or too less + if (a01*a02 < 0 ) { + //not rotating in the same direction + return false; + } - t[1]=tr; + real_t tr = a01/a02; + if (tr<0 || tr>1) + return false; //rotating too much or too less - } + t[1]=tr; } - { //scale + } - const Vector3 &v0=t0.value.scale; - const Vector3 &v1=t1.value.scale; - const Vector3 &v2=t2.value.scale; + { //scale - if (v0.distance_to(v2)<CMP_EPSILON) { - //0 and 2 are close, let's see if 1 is close - if (v0.distance_to(v1)>CMP_EPSILON) { - //not close, not optimizable - continue; - } + const Vector3 &v0=t0.value.scale; + const Vector3 &v1=t1.value.scale; + const Vector3 &v2=t2.value.scale; - } else { + if (v0.distance_to(v2)<CMP_EPSILON) { + //0 and 2 are close, let's see if 1 is close + if (v0.distance_to(v1)>CMP_EPSILON) { + //not close, not optimizable + return false; + } - Vector3 pd = (v2-v0); - float d0 = pd.dot(v0); - float d1 = pd.dot(v1); - float d2 = pd.dot(v2); - if (d1<d0 || d1>d2) { - continue; //beyond segment range - } + } else { - Vector3 s[2]={ v0, v2 }; - real_t d =Geometry::get_closest_point_to_segment(v1,s).distance_to(v1); + Vector3 pd = (v2-v0); + float d0 = pd.dot(v0); + float d1 = pd.dot(v1); + float d2 = pd.dot(v2); + if (d1<d0 || d1>d2) { + return false; //beyond segment range + } - if (d>pd.length()*p_alowed_linear_err) { - continue; //beyond allowed error for colinearity - } + Vector3 s[2]={ v0, v2 }; + real_t d =Geometry::get_closest_point_to_segment(v1,s).distance_to(v1); - t[2] = (d1-d0)/(d2-d0); + if (d>pd.length()*p_alowed_linear_err) { + return false; //beyond allowed error for colinearity } + + t[2] = (d1-d0)/(d2-d0); } + } - bool erase=false; - if (t[0]==-1 && t[1]==-1 && t[2]==-1) { + bool erase=false; + if (t[0]==-1 && t[1]==-1 && t[2]==-1) { - erase=true; - } else { + erase=true; + } else { - erase=true; - real_t lt=-1; - for(int j=0;j<3;j++) { - //search for t on first, one must be it - if (t[j]!=-1) { - lt=t[j]; //official t - //validate rest - for(int k=j+1;k<3;k++) { - if (t[k]==-1) - continue; - - if (Math::abs(lt-t[k])>p_alowed_linear_err) { - erase=false; - break; - } + erase=true; + real_t lt=-1; + for(int j=0;j<3;j++) { + //search for t on first, one must be it + if (t[j]!=-1) { + lt=t[j]; //official t + //validate rest + for(int k=j+1;k<3;k++) { + if (t[k]==-1) + continue; + + if (Math::abs(lt-t[k])>p_alowed_linear_err) { + erase=false; + break; } - break; } + break; } + } - ERR_CONTINUE( lt==-1 ); + ERR_FAIL_COND_V( lt==-1,false ); - if (erase) { + if (erase) { - if (Math::abs(lt-c)>p_alowed_linear_err) { - //todo, evaluate changing the transition if this fails? - //this could be done as a second pass and would be - //able to optimize more - erase=false; - } else { + if (Math::abs(lt-c)>p_alowed_linear_err) { + //todo, evaluate changing the transition if this fails? + //this could be done as a second pass and would be + //able to optimize more + erase=false; + } else { - //print_line(itos(i)+"because of interp"); - } + //print_line(itos(i)+"because of interp"); } + } + + } + + + return erase; + + +} + +void Animation::_transform_track_optimize(int p_idx,float p_alowed_linear_err,float p_alowed_angular_err,float p_max_optimizable_angle) { + + ERR_FAIL_INDEX(p_idx,tracks.size()); + ERR_FAIL_COND(tracks[p_idx]->type!=TYPE_TRANSFORM); + TransformTrack *tt= static_cast<TransformTrack*>(tracks[p_idx]); + bool prev_erased=false; + TKey<TransformKey> first_erased; + + for(int i=1;i<tt->transforms.size()-1;i++) { + + TKey<TransformKey> &t0 = tt->transforms[i-1]; + TKey<TransformKey> &t1 = tt->transforms[i]; + TKey<TransformKey> &t2 = tt->transforms[i+1]; + + bool erase = _transform_track_optimize_key(t0,t1,t2,p_alowed_linear_err,p_alowed_angular_err,p_max_optimizable_angle); + + + if (prev_erased && !_transform_track_optimize_key(t0,first_erased,t2,p_alowed_linear_err,p_alowed_angular_err,p_max_optimizable_angle)) { + //avoid error to go beyond first erased key + erase=false; } + if (erase) { + + if (!prev_erased) { + first_erased=t1; + prev_erased=true; + } + tt->transforms.remove(i); i--; + + } else { + prev_erased=false; } diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 0c0290295a..bf87789e39 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -204,6 +204,7 @@ private: return idxr; } + bool _transform_track_optimize_key(const TKey<TransformKey> &t0,const TKey<TransformKey> &t1, const TKey<TransformKey> &t2, float p_alowed_linear_err,float p_alowed_angular_err,float p_max_optimizable_angle); void _transform_track_optimize(int p_idx, float p_allowed_err=0.05, float p_alowed_angular_err=0.01,float p_max_optimizable_angle=Math_PI*0.125); protected: diff --git a/scene/resources/video_stream.cpp b/scene/resources/video_stream.cpp index fffe1ad7fa..2bbae37510 100644 --- a/scene/resources/video_stream.cpp +++ b/scene/resources/video_stream.cpp @@ -33,6 +33,7 @@ void VideoStream::_bind_methods() { ObjectTypeDB::bind_method(_MD("get_pending_frame_count"),&VideoStream::get_pending_frame_count); ObjectTypeDB::bind_method(_MD("pop_frame"),&VideoStream::pop_frame); ObjectTypeDB::bind_method(_MD("peek_frame"),&VideoStream::peek_frame); + ObjectTypeDB::bind_method(_MD("set_audio_track","idx"),&VideoStream::set_audio_track); }; diff --git a/scene/resources/video_stream.h b/scene/resources/video_stream.h index 1bc8a5e5bc..18f0cc3d05 100644 --- a/scene/resources/video_stream.h +++ b/scene/resources/video_stream.h @@ -62,6 +62,8 @@ public: virtual void pop_frame(Ref<ImageTexture> p_tex)=0; virtual Image peek_frame() const=0; + virtual void set_audio_track(int p_idx) =0; + virtual void update(float p_time)=0; VideoStream(); diff --git a/servers/audio/audio_server_sw.cpp b/servers/audio/audio_server_sw.cpp index f50813731e..55dde1b35b 100644 --- a/servers/audio/audio_server_sw.cpp +++ b/servers/audio/audio_server_sw.cpp @@ -332,6 +332,7 @@ void AudioServerSW::driver_process_chunk(int p_frames,int32_t *p_buffer) { void AudioServerSW::driver_process(int p_frames,int32_t *p_buffer) { + _output_delay=p_frames/double(AudioDriverSW::get_singleton()->get_mix_rate()); //process in chunks to make sure to never process more than INTERNAL_BUFFER_SIZE int todo=p_frames; while(todo) { @@ -795,6 +796,8 @@ void AudioServerSW::init() { mixer = memnew( AudioMixerSW( sample_manager, latency, AudioDriverSW::get_singleton()->get_mix_rate(),mix_chans,mixer_use_fx,mixer_interp,_mixer_callback,this ) ); mixer_step_usecs=mixer->get_step_usecs(); + _output_delay=0; + stream_volume=0.3; // start the audio driver if (AudioDriverSW::get_singleton()) @@ -911,6 +914,11 @@ float AudioServerSW::get_event_voice_global_volume_scale() const { return event_voice_volume_scale; } +double AudioServerSW::get_output_delay() const { + + return _output_delay; +} + double AudioServerSW::get_mix_time() const { return AudioDriverSW::get_singleton()->get_mix_time(); diff --git a/servers/audio/audio_server_sw.h b/servers/audio/audio_server_sw.h index d137c15633..d47c1b4b3f 100644 --- a/servers/audio/audio_server_sw.h +++ b/servers/audio/audio_server_sw.h @@ -92,6 +92,8 @@ class AudioServerSW : public AudioServer { float peak_left,peak_right; uint32_t max_peak; + double _output_delay; + VoiceRBSW voice_rb; bool exit_update_thread; @@ -206,6 +208,9 @@ public: virtual double get_mix_time() const; //useful for video -> audio sync + virtual double get_output_delay() const; + + AudioServerSW(SampleManagerSW *p_sample_manager); ~AudioServerSW(); diff --git a/servers/audio_server.h b/servers/audio_server.h index 85289de58a..511340678a 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -274,6 +274,7 @@ public: static AudioServer *get_singleton(); virtual double get_mix_time() const=0; //useful for video -> audio sync + virtual double get_output_delay() const=0; AudioServer(); virtual ~AudioServer(); diff --git a/servers/physics_2d_server.cpp b/servers/physics_2d_server.cpp index 2760f9bea6..22fb4fc0a8 100644 --- a/servers/physics_2d_server.cpp +++ b/servers/physics_2d_server.cpp @@ -514,7 +514,7 @@ void Physics2DServer::_bind_methods() { ObjectTypeDB::bind_method(_MD("joint_get_type","joint"),&Physics2DServer::joint_get_type); - ObjectTypeDB::bind_method(_MD("free","rid"),&Physics2DServer::free); + ObjectTypeDB::bind_method(_MD("free_rid","rid"),&Physics2DServer::free); ObjectTypeDB::bind_method(_MD("set_active","active"),&Physics2DServer::set_active); diff --git a/servers/physics_server.cpp b/servers/physics_server.cpp index e6b2927fb4..79de253d3b 100644 --- a/servers/physics_server.cpp +++ b/servers/physics_server.cpp @@ -655,7 +655,7 @@ void PhysicsServer::_bind_methods() { ObjectTypeDB::bind_method(_MD("joint_get_type","joint"),&PhysicsServer::joint_get_type); */ - ObjectTypeDB::bind_method(_MD("free","rid"),&PhysicsServer::free); + ObjectTypeDB::bind_method(_MD("free_rid","rid"),&PhysicsServer::free); ObjectTypeDB::bind_method(_MD("set_active","active"),&PhysicsServer::set_active); diff --git a/tools/editor/animation_editor.cpp b/tools/editor/animation_editor.cpp index 68bd051ee9..0d4f5a7b74 100644 --- a/tools/editor/animation_editor.cpp +++ b/tools/editor/animation_editor.cpp @@ -687,9 +687,7 @@ void AnimationKeyEditor::_menu_track(int p_type) { case TRACK_MENU_OPTIMIZE: { - animation->optimize(); - - track_editor->update(); + optimize_dialog->popup_centered(Size2(250,180)); } break; @@ -698,6 +696,18 @@ void AnimationKeyEditor::_menu_track(int p_type) { } + +void AnimationKeyEditor::_animation_optimize() { + + + print_line("OPTIMIZE!"); + animation->optimize(optimize_linear_error->get_val(),optimize_angular_error->get_val(),optimize_max_angle->get_val()); + track_editor->update(); + undo_redo->clear_history(); + +} + + float AnimationKeyEditor::_get_zoom_scale() const { float zv = zoom->get_val(); @@ -2335,11 +2345,12 @@ void AnimationKeyEditor::_notification(int p_what) { tpp->add_item("Out-In",TRACK_MENU_SET_ALL_TRANS_OUTIN); tpp->set_name("Transitions"); tpp->connect("item_pressed",this,"_menu_track"); + optimize_dialog->connect("confirmed",this,"_animation_optimize"); menu_track->get_popup()->add_child(tpp); menu_track->get_popup()->add_submenu_item("Set Transitions..","Transitions"); - //menu_track->get_popup()->add_separator(); - //menu_track->get_popup()->add_item("Optimize Animation",TRACK_MENU_OPTIMIZE); + menu_track->get_popup()->add_separator(); + menu_track->get_popup()->add_item("Optimize Animation",TRACK_MENU_OPTIMIZE); @@ -3099,6 +3110,7 @@ void AnimationKeyEditor::_bind_methods() { ObjectTypeDB::bind_method(_MD("_animation_len_update"),&AnimationKeyEditor::_animation_len_update); ObjectTypeDB::bind_method(_MD("set_animation"),&AnimationKeyEditor::set_animation); + ObjectTypeDB::bind_method(_MD("_animation_optimize"),&AnimationKeyEditor::_animation_optimize); ADD_SIGNAL( MethodInfo("resource_selected", PropertyInfo( Variant::OBJECT, "res"),PropertyInfo( Variant::STRING, "prop") ) ); @@ -3224,6 +3236,37 @@ AnimationKeyEditor::AnimationKeyEditor(UndoRedo *p_undo_redo, EditorHistory *p_h remove_button->set_disabled(true); remove_button->set_tooltip("Remove selected track."); + + optimize_dialog = memnew( ConfirmationDialog ); + add_child(optimize_dialog); + optimize_dialog->set_title("Anim. Optimizer"); + VBoxContainer *optimize_vb = memnew( VBoxContainer ); + optimize_dialog->add_child(optimize_vb); + optimize_dialog->set_child_rect(optimize_vb); + optimize_linear_error = memnew( SpinBox ); + optimize_linear_error->set_max(1.0); + optimize_linear_error->set_min(0.001); + optimize_linear_error->set_step(0.001); + optimize_linear_error->set_val(0.05); + optimize_vb->add_margin_child("Max. Linear Error:",optimize_linear_error); + optimize_angular_error = memnew( SpinBox ); + optimize_angular_error->set_max(1.0); + optimize_angular_error->set_min(0.001); + optimize_angular_error->set_step(0.001); + optimize_angular_error->set_val(0.01); + + optimize_vb->add_margin_child("Max. Angular Error:",optimize_angular_error); + optimize_max_angle = memnew( SpinBox ); + optimize_vb->add_margin_child("Max Optimizable Angle:",optimize_max_angle); + optimize_max_angle->set_max(360.0); + optimize_max_angle->set_min(0.0); + optimize_max_angle->set_step(0.1); + optimize_max_angle->set_val(22); + + optimize_dialog->get_ok()->set_text("Optimize"); + + + /*keying = memnew( Button ); keying->set_toggle_mode(true); //keying->set_text("Keys"); diff --git a/tools/editor/animation_editor.h b/tools/editor/animation_editor.h index 01f6e294cc..9d3e692fad 100644 --- a/tools/editor/animation_editor.h +++ b/tools/editor/animation_editor.h @@ -169,6 +169,11 @@ class AnimationKeyEditor : public VBoxContainer { ToolButton *move_down_button; ToolButton *remove_button; + ConfirmationDialog *optimize_dialog; + SpinBox *optimize_linear_error; + SpinBox *optimize_angular_error; + SpinBox *optimize_max_angle; + SpinBox *step; MenuButton *menu_track; @@ -257,6 +262,7 @@ class AnimationKeyEditor : public VBoxContainer { StringName alc; void _animation_changed(); + void _animation_optimize(); void _scroll_changed(double); diff --git a/tools/editor/editor_file_system.cpp b/tools/editor/editor_file_system.cpp index e0b743a929..5d72928e9c 100644 --- a/tools/editor/editor_file_system.cpp +++ b/tools/editor/editor_file_system.cpp @@ -992,6 +992,35 @@ void EditorFileSystem::_resource_saved(const String& p_path){ EditorFileSystem::get_singleton()->update_file(p_path); } +String EditorFileSystem::_find_first_from_source(EditorFileSystemDirectory* p_dir,const String &p_src) const { + + for(int i=0;i<p_dir->files.size();i++) { + for(int j=0;j<p_dir->files[i].meta.sources.size();j++) { + + if (p_dir->files[i].meta.sources[j].path==p_src) + return p_dir->get_file_path(i); + } + } + + for(int i=0;i<p_dir->subdirs.size();i++) { + + String ret = _find_first_from_source(p_dir->subdirs[i],p_src); + if (ret.length()>0) + return ret; + } + + return String(); +} + + +String EditorFileSystem::find_resource_from_source(const String& p_path) const { + + + if (filesystem) + return _find_first_from_source(filesystem,p_path); + return String(); +} + void EditorFileSystem::update_file(const String& p_file) { EditorFileSystemDirectory *fs=NULL; diff --git a/tools/editor/editor_file_system.h b/tools/editor/editor_file_system.h index c26fc02548..2d14f9012f 100644 --- a/tools/editor/editor_file_system.h +++ b/tools/editor/editor_file_system.h @@ -75,6 +75,7 @@ class EditorFileSystemDirectory : public Object { static void _bind_methods(); + friend class EditorFileSystem; public: @@ -180,6 +181,7 @@ class EditorFileSystem : public Node { List<String> sources_changed; static void _resource_saved(const String& p_path); + String _find_first_from_source(EditorFileSystemDirectory* p_dir,const String &p_src) const; protected: @@ -197,6 +199,7 @@ public: void scan_sources(); void get_changed_sources(List<String> *r_changed); void update_file(const String& p_file); + String find_resource_from_source(const String& p_path) const; EditorFileSystemDirectory *get_path(const String& p_path); String get_file_type(const String& p_file) const; EditorFileSystem(); diff --git a/tools/editor/io_plugins/editor_import_collada.cpp b/tools/editor/io_plugins/editor_import_collada.cpp index 3fb45d1870..8fe7010760 100644 --- a/tools/editor/io_plugins/editor_import_collada.cpp +++ b/tools/editor/io_plugins/editor_import_collada.cpp @@ -1844,12 +1844,15 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones anim_length=collada.state.animation_clips[p_clip].end; while(f<anim_length) { - if (f>=anim_length) - f=anim_length; base_snapshots.push_back(f); f+=snapshot_interval; + + if (f>=anim_length) { + base_snapshots.push_back(anim_length); + } } + //print_line("anim len: "+rtos(anim_length)); animation->set_length(anim_length); @@ -1894,6 +1897,8 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones for(int i=0;i<at.keys.size();i++) snapshots.push_back(at.keys[i].time); + print_line("using anim snapshots"); + } @@ -2185,8 +2190,6 @@ Node* EditorSceneImporterCollada::import_scene(const String& p_path, uint32_t p_ else name=state.animations[i]->get_name(); - if (p_flags&IMPORT_ANIMATION_OPTIMIZE) - state.animations[i]->optimize(); if (p_flags&IMPORT_ANIMATION_DETECT_LOOP) { if (name.begins_with("loop") || name.ends_with("loop") || name.begins_with("cycle") || name.ends_with("cycle")) { @@ -2232,8 +2235,6 @@ Ref<Animation> EditorSceneImporterCollada::import_animation(const String& p_path } } - if (p_flags&IMPORT_ANIMATION_OPTIMIZE) - anim->optimize(); return anim; } diff --git a/tools/editor/io_plugins/editor_scene_import_plugin.cpp b/tools/editor/io_plugins/editor_scene_import_plugin.cpp index f305564622..06780e4d8a 100644 --- a/tools/editor/io_plugins/editor_scene_import_plugin.cpp +++ b/tools/editor/io_plugins/editor_scene_import_plugin.cpp @@ -80,17 +80,25 @@ class EditorImportAnimationOptions : public VBoxContainer { TreeItem *fps; + TreeItem* optimize_linear_error; + TreeItem* optimize_angular_error; + TreeItem* optimize_max_angle; + TreeItem *clips_base; + TextEdit *filters; Vector<TreeItem*> clips; Tree *flags; Tree *clips_tree; + Tree *optimization_tree; Vector<TreeItem*> items; bool updating; bool validating; + + void _changed(); void _item_edited(); void _button_action(Object *p_obj,int p_col,int p_id); @@ -107,6 +115,15 @@ public: void set_fps(int p_fps); int get_fps() const; + void set_optimize_linear_error(float p_error); + float get_optimize_linear_error() const; + + void set_optimize_angular_error(float p_error); + float get_optimize_angular_error() const; + + void set_optimize_max_angle(float p_error); + float get_optimize_max_angle() const; + void setup_clips(const Array& p_clips); Array get_clips() const; @@ -453,9 +470,41 @@ EditorImportAnimationOptions::EditorImportAnimationOptions() { fps = flags->create_item(fps_base); fps->set_cell_mode(0,TreeItem::CELL_MODE_RANGE); fps->set_editable(0,true); - fps->set_range(0,15); fps->set_range_config(0,1,120,1); + fps->set_range(0,15); + optimization_tree = memnew( Tree ); + optimization_tree->set_columns(2); + tab->add_child(optimization_tree); + optimization_tree->set_name("Optimizer"); + optimization_tree->set_column_expand(0,true); + optimization_tree->set_column_expand(1,false); + optimization_tree->set_column_min_width(1,80); + optimization_tree->set_hide_root(true); + + + TreeItem *optimize_root = optimization_tree->create_item(); + + optimize_linear_error = optimization_tree->create_item(optimize_root); + optimize_linear_error->set_text(0,"Max Linear Error"); + optimize_linear_error->set_cell_mode(1,TreeItem::CELL_MODE_RANGE); + optimize_linear_error->set_editable(1,true); + optimize_linear_error->set_range_config(1,0,1,0.001); + optimize_linear_error->set_range(1,0.05); + + optimize_angular_error = optimization_tree->create_item(optimize_root); + optimize_angular_error->set_text(0,"Max Angular Error"); + optimize_angular_error->set_cell_mode(1,TreeItem::CELL_MODE_RANGE); + optimize_angular_error->set_editable(1,true); + optimize_angular_error->set_range_config(1,0,1,0.001); + optimize_angular_error->set_range(1,0.01); + + optimize_max_angle = optimization_tree->create_item(optimize_root); + optimize_max_angle->set_text(0,"Max Angle"); + optimize_max_angle->set_cell_mode(1,TreeItem::CELL_MODE_RANGE); + optimize_max_angle->set_editable(1,true); + optimize_max_angle->set_range_config(1,0,360,0.001); + optimize_max_angle->set_range(1,int(180*0.125)); clips_tree = memnew( Tree ); clips_tree->set_hide_root(true); @@ -498,6 +547,38 @@ int EditorImportAnimationOptions::get_fps() const { return fps->get_range(0); } + +void EditorImportAnimationOptions::set_optimize_linear_error(float p_optimize_linear_error) { + + optimize_linear_error->set_range(1,p_optimize_linear_error); +} + +float EditorImportAnimationOptions::get_optimize_linear_error() const { + + return optimize_linear_error->get_range(1); +} + +void EditorImportAnimationOptions::set_optimize_angular_error(float p_optimize_angular_error) { + + optimize_angular_error->set_range(1,p_optimize_angular_error); +} + +float EditorImportAnimationOptions::get_optimize_angular_error() const { + + return optimize_angular_error->get_range(1); +} + +void EditorImportAnimationOptions::set_optimize_max_angle(float p_optimize_max_angle) { + + optimize_max_angle->set_range(1,p_optimize_max_angle); +} + +float EditorImportAnimationOptions::get_optimize_max_angle() const { + + return optimize_max_angle->get_range(1); +} + + void EditorImportAnimationOptions::set_filter(const String& p_filter) { filters->set_text(p_filter); @@ -544,6 +625,17 @@ void EditorSceneImportDialog::_choose_file(const String& p_path) { } #endif + if (p_path!=String()) { + + String from_path = EditorFileSystem::get_singleton()->find_resource_from_source(EditorImportPlugin::validate_source_path(p_path)); + print_line("from path.."+from_path); + if (from_path!=String()) { + popup_import(from_path); + + } + } + + import_path->set_text(p_path); } @@ -650,6 +742,9 @@ void EditorSceneImportDialog::_import(bool p_and_open) { rim->set_option("texture_quality",texture_options->get_quality()); rim->set_option("animation_flags",animation_options->get_flags()); rim->set_option("animation_bake_fps",animation_options->get_fps()); + rim->set_option("animation_optimizer_linear_error",animation_options->get_optimize_linear_error()); + rim->set_option("animation_optimizer_angular_error",animation_options->get_optimize_angular_error()); + rim->set_option("animation_optimizer_max_angle",animation_options->get_optimize_max_angle()); rim->set_option("animation_filters",animation_options->get_filter()); rim->set_option("animation_clips",animation_options->get_clips()); rim->set_option("post_import_script",script_path->get_text()!=String()?EditorImportPlugin::validate_source_path(script_path->get_text()):String()); @@ -791,6 +886,13 @@ void EditorSceneImportDialog::popup_import(const String &p_from) { animation_options->set_filter(rimd->get_option("animation_filters")); if (rimd->has_option("animation_bake_fps")) animation_options->set_fps(rimd->get_option("animation_bake_fps")); + if (rimd->has_option("animation_optimizer_linear_error")) + animation_options->set_optimize_linear_error(rimd->get_option("animation_optimizer_linear_error")); + if (rimd->has_option("animation_optimizer_angular_error")) + animation_options->set_optimize_angular_error(rimd->get_option("animation_optimizer_angular_error")); + if (rimd->has_option("animation_optimizer_max_angle")) + animation_options->set_optimize_max_angle(rimd->get_option("animation_optimizer_max_angle")); + script_path->set_text(rimd->get_option("post_import_script")); if (rimd->has_option("import_this_time")) this_import->select(rimd->get_option("import_this_time")); @@ -2223,6 +2325,8 @@ Error EditorSceneImportPlugin::import1(const Ref<ResourceImportMetadata>& p_from int fps = 24; if (from->has_option("animation_bake_fps")) fps=from->get_option("animation_bake_fps"); + + Array clips; if (from->has_option("animation_clips")) clips=from->get_option("animation_clips"); @@ -2503,6 +2607,26 @@ void EditorSceneImportPlugin::_filter_tracks(Node *scene, const String& p_text) } +void EditorSceneImportPlugin::_optimize_animations(Node *scene, float p_max_lin_error,float p_max_ang_error,float p_max_angle) { + + if (!scene->has_node(String("AnimationPlayer"))) + return; + Node* n = scene->get_node(String("AnimationPlayer")); + ERR_FAIL_COND(!n); + AnimationPlayer *anim = n->cast_to<AnimationPlayer>(); + ERR_FAIL_COND(!anim); + + + List<StringName> anim_names; + anim->get_animation_list(&anim_names); + for(List<StringName>::Element *E=anim_names.front();E;E=E->next()) { + + Ref<Animation> a = anim->get_animation(E->get()); + a->optimize(p_max_lin_error,p_max_ang_error,Math::deg2rad(p_max_angle)); + } +} + + Error EditorSceneImportPlugin::import2(Node *scene, const String& p_dest_path, const Ref<ResourceImportMetadata>& p_from) { Error err=OK; @@ -2512,6 +2636,16 @@ Error EditorSceneImportPlugin::import2(Node *scene, const String& p_dest_path, c Array animation_clips = p_from->get_option("animation_clips"); String animation_filter = p_from->get_option("animation_filters"); int scene_flags = from->get_option("flags"); + float anim_optimizer_linerr=0.05; + float anim_optimizer_angerr=0.01; + float anim_optimizer_maxang=22; + + if (from->has_option("animation_optimizer_linear_error")) + anim_optimizer_linerr=from->get_option("animation_optimizer_linear_error"); + if (from->has_option("animation_optimizer_angular_error")) + anim_optimizer_angerr=from->get_option("animation_optimizer_angular_error"); + if (from->has_option("animation_optimizer_max_angle")) + anim_optimizer_maxang=from->get_option("animation_optimizer_max_angle"); EditorProgress progress("import","Import Scene",104); progress.step("Importing Scene..",2); @@ -2536,6 +2670,8 @@ Error EditorSceneImportPlugin::import2(Node *scene, const String& p_dest_path, c Map< Ref<ImageTexture>,TextureRole > imagemap; scene=_fix_node(scene,scene,collision_map,scene_flags,imagemap); + if (animation_flags&EditorSceneAnimationImportPlugin::ANIMATION_OPTIMIZE) + _optimize_animations(scene,anim_optimizer_linerr,anim_optimizer_angerr,anim_optimizer_maxang); if (animation_clips.size()) _create_clips(scene,animation_clips,animation_flags&EditorSceneAnimationImportPlugin::ANIMATION_FORCE_ALL_TRACKS_IN_ALL_CLIPS); diff --git a/tools/editor/io_plugins/editor_scene_import_plugin.h b/tools/editor/io_plugins/editor_scene_import_plugin.h index c56be57aed..fa4730f7ee 100644 --- a/tools/editor/io_plugins/editor_scene_import_plugin.h +++ b/tools/editor/io_plugins/editor_scene_import_plugin.h @@ -114,6 +114,7 @@ class EditorSceneImportPlugin : public EditorImportPlugin { void _merge_existing_node(Node *p_node,Node *p_imported_scene,Set<Ref<Resource> >& checked_resources,Set<Node*> &checked_nodes); void _add_new_nodes(Node *p_node,Node *p_imported,Node *p_imported_scene,Set<Node*> &checked_nodes); + void _optimize_animations(Node *scene, float p_max_lin_error,float p_max_ang_error,float p_max_angle); void _merge_scenes(Node *p_node, Node *p_imported); void _scan_materials(Node*p_base,Node *p_node,Map<String,Ref<Material> > &mesh_materials,Map<String,Ref<Material> >& override_materials); diff --git a/tools/export/blender25/io_scene_dae/export_dae.py b/tools/export/blender25/io_scene_dae/export_dae.py index d3337033c8..2612e7e248 100644 --- a/tools/export/blender25/io_scene_dae/export_dae.py +++ b/tools/export/blender25/io_scene_dae/export_dae.py @@ -1457,7 +1457,7 @@ class DaeExporter: print(str(x)) - tcn = self.export_animation(int(x.frame_range[0]),int(x.frame_range[1]),allowed_skeletons) + tcn = self.export_animation(int(x.frame_range[0]),int(x.frame_range[1]+0.5),allowed_skeletons) framelen=(1.0/self.scene.render.fps) start = x.frame_range[0]*framelen end = x.frame_range[1]*framelen |