Merge branch 'master' into deploy-ng

This commit is contained in:
rdb 2018-03-11 23:01:10 +01:00
commit aebdefcf60
47 changed files with 784 additions and 363 deletions

View File

@ -7,7 +7,7 @@ from direct.distributed.DoCollectionManager import DoCollectionManager
from direct.showbase import GarbageReport
from .PyDatagramIterator import PyDatagramIterator
import types
import inspect
import gc
__all__ = ["ConnectionRepository", "GCTrigger"]
@ -327,13 +327,13 @@ class ConnectionRepository(
if classDef is None:
self.notify.debug("No class definition for %s." % (className))
else:
if type(classDef) == types.ModuleType:
if inspect.ismodule(classDef):
if not hasattr(classDef, className):
self.notify.warning("Module %s does not define class %s." % (className, className))
continue
classDef = getattr(classDef, className)
if type(classDef) != types.ClassType and type(classDef) != types.TypeType:
if inspect.isclass(classDef):
self.notify.error("Symbol %s is not a class name." % (className))
else:
dclass.setClassDef(classDef)
@ -388,7 +388,7 @@ class ConnectionRepository(
if classDef is None:
self.notify.error("No class definition for %s." % className)
else:
if type(classDef) == types.ModuleType:
if inspect.ismodule(classDef):
if not hasattr(classDef, className):
self.notify.error("Module %s does not define class %s." % (className, className))
classDef = getattr(classDef, className)

View File

@ -75,7 +75,7 @@ class PyDatagramIterator(DatagramIterator):
b = self.getUint8()
retVal.append((a, b))
else:
raise Exception("Error: No such type as: " + str(subAtomicType))
raise Exception("Error: No such type as: " + str(subatomicType))
else:
# See if it is in the handy dict
getFunc = self.FuncDict.get(subatomicType)
@ -121,8 +121,4 @@ class PyDatagramIterator(DatagramIterator):
else:
raise Exception("Error: No such type as: " + str(subatomicType))
return retVal

View File

@ -7,6 +7,8 @@ from direct.task import Task
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.PyDatagram import PyDatagram
import inspect
class ServerRepository:
@ -273,12 +275,12 @@ class ServerRepository:
if classDef == None:
self.notify.debug("No class definition for %s." % (className))
else:
if type(classDef) == types.ModuleType:
if inspect.ismodule(classDef):
if not hasattr(classDef, className):
self.notify.error("Module %s does not define class %s." % (className, className))
classDef = getattr(classDef, className)
if type(classDef) != types.ClassType and type(classDef) != types.TypeType:
if inspect.isclass(classDef):
self.notify.error("Symbol %s is not a class name." % (className))
else:
dclass.setClassDef(classDef)

View File

@ -342,7 +342,6 @@ class MetaInterval(CMetaInterval):
# with all of their associated Python callbacks:
def setManager(self, manager):
rogerroger
self.__manager = manager
CMetaInterval.setManager(self, manager)

View File

@ -110,15 +110,6 @@ class ObjectPool:
print('TYPE: %s, %s objects' % (repr(typ), len(self._type2objs[typ])))
print(getNumberedTypedSortedString(self._type2objs[typ]))
def containerLenStr(self):
s = 'Object Pool: Container Lengths'
s += '\n=============================='
lengths = list(self._len2obj.keys())
lengths.sort()
lengths.reverse()
for count in counts:
pass
def printReferrers(self, numEach=3):
"""referrers of the first few of each type of object"""
counts = list(set(self._count2types.keys()))

View File

@ -12,5 +12,28 @@ else:
if '_Pmw' in sys.modules:
sys.modules['_Pmw'].__name__ = '_Pmw'
# Hack to workaround broken Pmw.NoteBook in Python 3
def bordercolors(root, colorName):
lightRGB = []
darkRGB = []
for value in Pmw.Color.name2rgb(root, colorName, 1):
value40pc = (14 * value) // 10
if value40pc > int(Pmw.Color._MAX_RGB):
value40pc = int(Pmw.Color._MAX_RGB)
valueHalfWhite = (int(Pmw.Color._MAX_RGB) + value) // 2;
lightRGB.append(max(value40pc, valueHalfWhite))
darkValue = (60 * value) // 100
darkRGB.append(darkValue)
return (
'#%04x%04x%04x' % (lightRGB[0], lightRGB[1], lightRGB[2]),
'#%04x%04x%04x' % (darkRGB[0], darkRGB[1], darkRGB[2])
)
Pmw.Color.bordercolors = bordercolors
del bordercolors
def spawnTkLoop():
base.spawnTkLoop()

View File

@ -302,7 +302,7 @@ class BoundedSemaphore(Semaphore):
Semaphore.__init__(value)
def release(self):
if self.getCount() > value:
if self.getCount() > self.__max:
raise ValueError
Semaphore.release(self)

View File

@ -67,8 +67,6 @@
can define all of these stupid symbols to the empty string.
*/
#define EXPCL_EMPTY
#ifdef BUILDING_DTOOL
#define EXPCL_DTOOL EXPORT_CLASS
#define EXPTP_DTOOL EXPORT_TEMPL

View File

@ -194,9 +194,9 @@ output(ostream &out) const {
/**
* Returns a special zero-valued TypeHandle that is used to indicate no type.
*/
INLINE TypeHandle TypeHandle::
CONSTEXPR TypeHandle TypeHandle::
none() {
return _none;
return TypeHandle(0);
}
/**
@ -213,9 +213,15 @@ operator bool () const {
*
* See TypeRegistry::find_type_by_id().
*/
INLINE TypeHandle TypeHandle::
CONSTEXPR TypeHandle TypeHandle::
from_index(int index) {
TypeHandle handle;
handle._index = index;
return handle;
return TypeHandle(index);
}
/**
* Private constructor for initializing a TypeHandle from an index, used by
* none() and by from_index().
*/
CONSTEXPR TypeHandle::
TypeHandle(int index) : _index(index) {
}

View File

@ -80,6 +80,8 @@ class TypedObject;
*/
class EXPCL_DTOOL TypeHandle FINAL {
PUBLISHED:
TypeHandle() NOEXCEPT DEFAULT_CTOR;
enum MemoryClass {
MC_singleton,
MC_array,
@ -127,7 +129,7 @@ PUBLISHED:
INLINE int get_index() const;
INLINE void output(ostream &out) const;
INLINE static TypeHandle none();
CONSTEXPR static TypeHandle none();
INLINE operator bool () const;
MAKE_PROPERTY(index, get_index);
@ -140,12 +142,15 @@ public:
void *reallocate_array(void *ptr, size_t size) RETURNS_ALIGNED(MEMORY_HOOK_ALIGNMENT);
void deallocate_array(void *ptr);
INLINE static TypeHandle from_index(int index);
CONSTEXPR static TypeHandle from_index(int index);
private:
int _index;
CONSTEXPR TypeHandle(int index);
// Only kept temporarily for ABI compatibility.
static TypeHandle _none;
int _index;
friend class TypeRegistry;
};

View File

@ -1,108 +0,0 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file newheader.cxx
* @author drose
* @date 2004-07-05
*/
#include "dtoolbase.h"
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
const char *cxx_style =
"// Filename: %s\n"
"// Created by: %s (%s)\n"
"//\n"
"////////////////////////////////////////////////////////////////////\n"
"//\n"
"// PANDA 3D SOFTWARE\n"
"// Copyright (c) Carnegie Mellon University. All rights reserved.\n"
"//\n"
"// All use of this software is subject to the terms of the revised BSD\n"
"// license. You should have received a copy of this license along\n"
"// with this source code in a file named \"LICENSE.\"\n"
"//\n"
"////////////////////////////////////////////////////////////////////\n"
"\n";
const char *c_style =
"/* Filename: %s\n"
" * Created by: %s (%s)\n"
" *\n"
" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n"
" *\n"
" * PANDA 3D SOFTWARE\n"
" * Copyright (c) Carnegie Mellon University. All rights reserved.\n"
" *\n"
" * All use of this software is subject to the terms of the revised BSD\n"
" * license. You should have received a copy of this license along\n"
" * with this source code in a file named \"LICENSE.\"\n"
" *\n"
" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */\n"
"\n";
struct FileDef {
const char *extension;
const char *header;
};
FileDef file_def[] = {
{ "h", cxx_style },
{ "cxx", cxx_style },
{ "I", cxx_style },
{ "T", cxx_style },
{ "c", c_style },
{ NULL, NULL },
};
void
generate_header(const char *header, const string &filename) {
const char *username = getenv("USER");
if (username == NULL) {
username = "";
}
static const size_t max_date_buffer = 128;
char date_buffer[max_date_buffer];
time_t now = time(NULL);
strftime(date_buffer, max_date_buffer, "%d%b%y", localtime(&now));
printf(header, filename.c_str(), username, date_buffer);
}
int
main(int argc, char *argv[]) {
if (argc < 2) {
cerr << "Must specify the filename to generate a header for.\n";
exit(1);
}
string filename = argv[1];
size_t dot = filename.rfind('.');
if (dot == string::npos) {
// No extension, no header.
return 0;
}
string extension = filename.substr(dot + 1);
size_t i = 0;
while (file_def[i].extension != NULL) {
if (extension == file_def[i].extension) {
generate_header(file_def[i].header, filename);
return 0;
}
i++;
}
// No matching extension, no problem.
return 0;
}

View File

@ -5179,7 +5179,7 @@ if (PkgSkip("SPEEDTREE")==0):
# DIRECTORY: panda/src/testbed/
#
if (not RTDIST and not RUNTIME and PkgSkip("PVIEW")==0 and GetTarget() != 'android'):
if (not RTDIST and not RUNTIME and PkgSkip("PVIEW")==0):
OPTS=['DIR:panda/src/testbed']
TargetAdd('pview_pview.obj', opts=OPTS, input='pview.cxx')
TargetAdd('pview.exe', input='pview_pview.obj')
@ -5198,6 +5198,7 @@ if (not RUNTIME and GetTarget() == 'android'):
TargetAdd('org/panda3d/android/NativeIStream.class', opts=OPTS, input='NativeIStream.java')
TargetAdd('org/panda3d/android/NativeOStream.class', opts=OPTS, input='NativeOStream.java')
TargetAdd('org/panda3d/android/PandaActivity.class', opts=OPTS, input='PandaActivity.java')
TargetAdd('org/panda3d/android/PythonActivity.class', opts=OPTS, input='PythonActivity.java')
TargetAdd('p3android_composite1.obj', opts=OPTS, input='p3android_composite1.cxx')
TargetAdd('libp3android.dll', input='p3android_composite1.obj')
@ -5208,10 +5209,10 @@ if (not RUNTIME and GetTarget() == 'android'):
TargetAdd('android_main.obj', opts=OPTS, input='android_main.cxx')
if (not RTDIST and PkgSkip("PVIEW")==0):
TargetAdd('pview_pview.obj', opts=OPTS, input='pview.cxx')
TargetAdd('libpview_pview.obj', opts=OPTS, input='pview.cxx')
TargetAdd('libpview.dll', input='android_native_app_glue.obj')
TargetAdd('libpview.dll', input='android_main.obj')
TargetAdd('libpview.dll', input='pview_pview.obj')
TargetAdd('libpview.dll', input='libpview_pview.obj')
TargetAdd('libpview.dll', input='libp3framework.dll')
if not PkgSkip("EGG"):
TargetAdd('libpview.dll', input='libpandaegg.dll')
@ -5219,6 +5220,17 @@ if (not RUNTIME and GetTarget() == 'android'):
TargetAdd('libpview.dll', input=COMMON_PANDA_LIBS)
TargetAdd('libpview.dll', opts=['MODULE', 'ANDROID'])
if (not RTDIST and PkgSkip("PYTHON")==0):
OPTS += ['PYTHON']
TargetAdd('ppython_ppython.obj', opts=OPTS, input='python_main.cxx')
TargetAdd('libppython.dll', input='android_native_app_glue.obj')
TargetAdd('libppython.dll', input='android_main.obj')
TargetAdd('libppython.dll', input='ppython_ppython.obj')
TargetAdd('libppython.dll', input='libp3framework.dll')
TargetAdd('libppython.dll', input='libp3android.dll')
TargetAdd('libppython.dll', input=COMMON_PANDA_LIBS)
TargetAdd('libppython.dll', opts=['MODULE', 'ANDROID', 'PYTHON'])
#
# DIRECTORY: panda/src/androiddisplay/
#
@ -7632,7 +7644,7 @@ def MakeInstallerAndroid():
continue
if '.so.' in line:
dep = line.rpartition('.so.')[0] + '.so'
oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target))
oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target), True)
else:
dep = line
@ -7643,6 +7655,7 @@ def MakeInstallerAndroid():
copy_library(os.path.realpath(fulldep), dep)
break
# Now copy every lib in the lib dir, and its dependencies.
for base in os.listdir(source_dir):
if not base.startswith('lib'):
continue
@ -7654,6 +7667,59 @@ def MakeInstallerAndroid():
continue
copy_library(source, base)
# Same for Python extension modules. However, Android is strict about
# library naming, so we have a special naming scheme for these, in
# conjunction with a custom import hook to find these modules.
if not PkgSkip("PYTHON"):
suffix = GetExtensionSuffix()
source_dir = os.path.join(GetOutputDir(), "panda3d")
for base in os.listdir(source_dir):
if not base.endswith(suffix):
continue
modname = base[:-len(suffix)]
source = os.path.join(source_dir, base)
copy_library(source, "libpy.panda3d.{}.so".format(modname))
# Same for standard Python modules.
import _ctypes
source_dir = os.path.dirname(_ctypes.__file__)
for base in os.listdir(source_dir):
if not base.endswith('.so'):
continue
modname = base.partition('.')[0]
source = os.path.join(source_dir, base)
copy_library(source, "libpy.{}.so".format(modname))
def copy_python_tree(source_root, target_root):
for source_dir, dirs, files in os.walk(source_root):
if 'site-packages' in dirs:
dirs.remove('site-packages')
if not any(base.endswith('.py') for base in files):
continue
target_dir = os.path.join(target_root, os.path.relpath(source_dir, source_root))
target_dir = os.path.normpath(target_dir)
os.makedirs(target_dir, 0o755)
for base in files:
if base.endswith('.py'):
target = os.path.join(target_dir, base)
shutil.copy(os.path.join(source_dir, base), target)
# Copy the Python standard library to the .apk as well.
from distutils.sysconfig import get_python_lib
stdlib_source = get_python_lib(False, True)
stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info))
copy_python_tree(stdlib_source, stdlib_target)
# But also copy over our custom site.py.
shutil.copy("panda/src/android/site.py", os.path.join(stdlib_target, "site.py"))
# And now make a site-packages directory containing our direct/panda3d/pandac modules.
for tree in "panda3d", "direct", "pandac":
copy_python_tree(os.path.join(GetOutputDir(), tree), os.path.join(stdlib_target, "site-packages", tree))
# Copy the models and config files to the virtual assets filesystem.
oscmd("mkdir apkroot/assets")
oscmd("cp -R %s apkroot/assets/models" % (os.path.join(GetOutputDir(), "models")))
@ -7672,7 +7738,11 @@ def MakeInstallerAndroid():
oscmd(aapt_cmd)
# And add all the libraries to it.
oscmd("cd apkroot && aapt add ../%s classes.dex lib/%s/lib*.so" % (apk_unaligned, SDK["ANDROID_ABI"]))
oscmd("cd apkroot && aapt add ../%s classes.dex" % (apk_unaligned))
for path, dirs, files in os.walk('apkroot/lib'):
if files:
rel = os.path.relpath(path, 'apkroot')
oscmd("cd apkroot && aapt add ../%s %s/*" % (apk_unaligned, rel))
# Now align the .apk, which is necessary for Android to load it.
oscmd("zipalign -v -p 4 %s %s" % (apk_unaligned, apk_unsigned))

View File

@ -87,9 +87,9 @@ public class PandaActivity extends NativeActivity {
return path;
}
public String getIntentOutputPath() {
public String getIntentOutputUri() {
Intent intent = getIntent();
return intent.getStringExtra("org.panda3d.OUTPUT_PATH");
return intent.getStringExtra("org.panda3d.OUTPUT_URI");
}
public String getCacheDirString() {

View File

@ -0,0 +1,23 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file PythonActivity.java
* @author rdb
* @date 2018-02-04
*/
package org.panda3d.android;
import org.panda3d.android.PandaActivity;
/**
* This is only declared as a separate class from PandaActivity so that we
* can have two separate activity definitions in ApplicationManifest.xml.
*/
public class PythonActivity extends PandaActivity {
}

View File

@ -17,11 +17,14 @@
#include "virtualFileSystem.h"
#include "filename.h"
#include "thread.h"
#include "urlSpec.h"
#include "config_display.h"
// #define OPENGLES_1 #include "config_androiddisplay.h"
#include <android_native_app_glue.h>
#include <sys/socket.h>
#include <arpa/inet.h>
// struct android_app* panda_android_app = NULL;
@ -67,6 +70,55 @@ void android_main(struct android_app* app) {
android_cat.info()
<< "New native activity started on " << *current_thread << "\n";
// Were we given an optional location to write the stdout/stderr streams?
methodID = env->GetMethodID(activity_class, "getIntentOutputUri", "()Ljava/lang/String;");
jstring joutput_uri = (jstring) env->CallObjectMethod(activity->clazz, methodID);
if (joutput_uri != nullptr) {
const char *output_uri = env->GetStringUTFChars(joutput_uri, nullptr);
if (output_uri != nullptr && output_uri[0] != 0) {
URLSpec spec(output_uri);
if (spec.get_scheme() == "file") {
string path = spec.get_path();
int fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY);
if (fd != -1) {
android_cat.info()
<< "Writing standard output to file " << path << "\n";
dup2(fd, 1);
dup2(fd, 2);
} else {
android_cat.error()
<< "Failed to open output path " << path << "\n";
}
} else if (spec.get_scheme() == "tcp") {
string host = spec.get_server();
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(spec.get_port());
serv_addr.sin_addr.s_addr = inet_addr(host.c_str());
if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) {
android_cat.info()
<< "Writing standard output to socket "
<< spec.get_server_and_port() << "\n";
dup2(fd, 1);
dup2(fd, 2);
} else {
android_cat.error()
<< "Failed to open output socket "
<< spec.get_server_and_port() << "\n";
}
close(fd);
} else {
android_cat.error()
<< "Unsupported scheme in output URI: " << output_uri << "\n";
}
env->ReleaseStringUTFChars(joutput_uri, output_uri);
}
}
// Fetch the data directory.
jmethodID get_appinfo = env->GetMethodID(activity_class, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
@ -186,28 +238,6 @@ void android_main(struct android_app* app) {
}
}
// Were we given an optional location to write the stdout/stderr streams?
methodID = env->GetMethodID(activity_class, "getIntentOutputPath", "()Ljava/lang/String;");
jstring joutput_path = (jstring) env->CallObjectMethod(activity->clazz, methodID);
if (joutput_path != nullptr) {
const char *output_path = env->GetStringUTFChars(joutput_path, nullptr);
if (output_path != nullptr && output_path[0] != 0) {
int fd = open(output_path, O_CREAT | O_TRUNC | O_WRONLY);
if (fd != -1) {
android_cat.info()
<< "Writing standard output to file " << output_path << "\n";
dup2(fd, 1);
dup2(fd, 2);
} else {
android_cat.error()
<< "Failed to open output path " << output_path << "\n";
}
env->ReleaseStringUTFChars(joutput_path, output_path);
}
}
// Create bogus argc and argv for calling the main function.
const char *argv[] = {"pview", nullptr, nullptr};
int argc = 1;
@ -266,6 +296,9 @@ void android_main(struct android_app* app) {
env->ReleaseStringUTFChars(filename, filename_str);
}
close(1);
close(2);
// Detach the thread before exiting.
activity->vm->DetachCurrentThread();
}

View File

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-sdk android:minSdkVersion="21" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
@ -44,13 +45,38 @@
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" scheme="content" host="com.termux.files" />
<data android:pathPattern=".*\\.egg" />
<data android:pathPattern=".*\\.egg.pz" />
<data android:pathPattern=".*\\.egg.gz" />
<data android:pathPattern=".*\\.bam" />
<data android:pathPattern=".*\\.bam.pz" />
<data android:pathPattern=".*\\.bam.gz" />
<data android:mimeType="*/*" android:scheme="content" android:host="com.termux.files" android:pathPattern=".*\\.egg" />
</intent-filter>
</activity>
<activity android:name="org.panda3d.android.PythonActivity"
android:label="Panda Python" android:theme="@android:style/Theme.NoTitleBar"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleInstance">
<meta-data android:name="android.app.lib_name"
android:value="ppython" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/x-python" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" android:scheme="file" />
<data android:pathPattern=".*\\.py" />
<data android:pathPattern=".*\\.pyw" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" android:scheme="content" android:host="com.termux.files" />
<data android:pathPattern=".*\\.py" />
<data android:pathPattern=".*\\.pyw" />
</intent-filter>
</activity>
</application>

View File

@ -0,0 +1,80 @@
/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file python_main.cxx
* @author rdb
* @date 2018-02-12
*/
#include "dtoolbase.h"
#include "config_android.h"
#include "executionEnvironment.h"
#undef _POSIX_C_SOURCE
#undef _XOPEN_SOURCE
#include <Python.h>
#if PY_MAJOR_VERSION >= 3
#include <wchar.h>
#endif
#include <dlfcn.h>
/**
* The main entry point for the Python activity. Called by android_main.
*/
int main(int argc, char *argv[]) {
if (argc <= 1) {
return 1;
}
// Help out Python by telling it which encoding to use
Py_FileSystemDefaultEncoding = "utf-8";
Py_SetProgramName(Py_DecodeLocale("ppython", nullptr));
// Set PYTHONHOME to the location of the .apk file.
string apk_path = ExecutionEnvironment::get_binary_name();
Py_SetPythonHome(Py_DecodeLocale(apk_path.c_str(), nullptr));
// We need to make zlib available to zipimport, but I don't know how
// we could inject our import hook before Py_Initialize, so instead
// load it as though it were a built-in module.
void *zlib = dlopen("libpy.zlib.so", RTLD_NOW);
if (zlib != nullptr) {
void *init = dlsym(zlib, "PyInit_zlib");
if (init != nullptr) {
PyImport_AppendInittab("zlib", (PyObject *(*)())init);
}
}
Py_Initialize();
// This is used by the import hook to locate the module libraries.
Filename dtool_name = ExecutionEnvironment::get_dtool_name();
string native_dir = dtool_name.get_dirname();
PyObject *py_native_dir = PyUnicode_FromStringAndSize(native_dir.c_str(), native_dir.size());
PySys_SetObject("_native_library_dir", py_native_dir);
Py_DECREF(py_native_dir);
int sts = 1;
FILE *fp = fopen(argv[1], "r");
if (fp != nullptr) {
int res = PyRun_AnyFile(fp, argv[1]);
if (res > 0) {
sts = 0;
} else {
android_cat.error() << "Error running " << argv[1] << "\n";
PyErr_Print();
}
} else {
android_cat.error() << "Unable to open " << argv[1] << "\n";
}
Py_Finalize();
return sts;
}

14
panda/src/android/run_pview.sh Executable file
View File

@ -0,0 +1,14 @@
# This script can be used for launching the Panda viewer from the Android
# terminal environment, for example from within termux. It uses a socket
# to pipe the command-line output back to the terminal.
port=12345
if [[ $# -eq 0 ]] ; then
echo "Pass full path of model"
exit 1
fi
am start --activity-clear-task -n org.panda3d.sdk/org.panda3d.android.PandaActivity --user 0 --es org.panda3d.OUTPUT_URI tcp://127.0.0.1:$port --grant-read-uri-permission --grant-write-uri-permission file://$(realpath $1)
nc -l -p $port

14
panda/src/android/run_python.sh Executable file
View File

@ -0,0 +1,14 @@
# This script can be used for launching a Python script from the Android
# terminal environment, for example from within termux. It uses a socket
# to pipe the command-line output back to the terminal.
port=12345
if [[ $# -eq 0 ]] ; then
echo "Pass full path of script"
exit 1
fi
am start --activity-clear-task -n org.panda3d.sdk/org.panda3d.android.PythonActivity --user 0 --es org.panda3d.OUTPUT_URI tcp://127.0.0.1:$port --grant-read-uri-permission --grant-write-uri-permission file://$(realpath $1)
nc -l -p $port

34
panda/src/android/site.py Normal file
View File

@ -0,0 +1,34 @@
import sys
import os
from importlib.abc import Loader, MetaPathFinder
from importlib.machinery import ModuleSpec
if sys.version_info >= (3, 5):
from importlib import _bootstrap_external
else:
from importlib import _bootstrap as _bootstrap_external
sys.platform = "android"
class AndroidExtensionFinder(MetaPathFinder):
@classmethod
def find_spec(cls, fullname, path=None, target=None):
soname = 'libpy.' + fullname + '.so'
path = os.path.join(sys._native_library_dir, soname)
if os.path.exists(path):
loader = _bootstrap_external.ExtensionFileLoader(fullname, path)
return ModuleSpec(fullname, loader, origin=path)
def main():
"""Adds the site-packages directory to the sys.path.
Also, registers the import hook for extension modules."""
sys.path.append('{0}/lib/python{1}.{2}/site-packages'.format(sys.prefix, *sys.version_info))
sys.meta_path.append(AndroidExtensionFinder)
if not sys.flags.no_site:
main()

View File

@ -309,8 +309,6 @@ open_window() {
_fb_properties = androidgsg->get_fb_properties();
androiddisplay_cat.error() << "open_window done\n";
return true;
}
@ -366,7 +364,6 @@ create_surface() {
// Create a context.
if (androidgsg->_context == EGL_NO_CONTEXT) {
androiddisplay_cat.error() << "creating context\n";
if (!androidgsg->create_context()) {
return false;
}

View File

@ -30,6 +30,23 @@ ConfigVariableString openal_device
PRC_DESC("Specify the OpenAL device string for audio playback (no quotes). If this "
"is not specified, the OpenAL default device is used."));
ConfigVariableInt openal_buffer_delete_retries
("openal-buffer-delete-retries", 5,
PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL "
"sound plugin will wait a moment and retry deletion, with an "
"exponentially-increasing delay for each try. This number "
"specifies how many repeat tries (not counting the initial try) "
"should be made before giving up and raising an error."));
ConfigVariableDouble openal_buffer_delete_delay
("openal-buffer-delete-delay", 0.001,
PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL "
"sound plugin will wait a moment and retry deletion, with an "
"exponentially-increasing delay for each try. This number "
"specifies how long, in seconds, the OpenAL plugin will wait after "
"its first failed try. The second try will be double this "
"delay, the third quadruple, and so on."));
/**
* Initializes the library. This must be called at least once before any of

View File

@ -26,5 +26,7 @@ extern "C" EXPCL_OPENAL_AUDIO void init_libOpenALAudio();
extern "C" EXPCL_OPENAL_AUDIO Create_AudioManager_proc *get_audio_manager_func_openal_audio();
extern ConfigVariableString openal_device;
extern ConfigVariableInt openal_buffer_delete_retries;
extern ConfigVariableDouble openal_buffer_delete_delay;
#endif // CONFIG_OPENALAUDIO_H

View File

@ -524,7 +524,7 @@ get_sound(const string &file_name, bool positional, int mode) {
void OpenALAudioManager::
uncache_sound(const string& file_name) {
ReMutexHolder holder(_lock);
assert(is_valid());
nassertv(is_valid());
Filename path = file_name;
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
@ -910,7 +910,7 @@ reduce_sounds_playing_to(unsigned int count) {
int limit = _sounds_playing.size() - count;
while (limit-- > 0) {
SoundsPlaying::iterator sound = _sounds_playing.begin();
assert(sound != _sounds_playing.end());
nassertv(sound != _sounds_playing.end());
// When the user stops a sound, there is still a PT in the user's hand.
// When we stop a sound here, however, this can remove the last PT. This
// can cause an ugly recursion where stop calls the destructor, and the
@ -1052,7 +1052,7 @@ OpenALAudioManager::SoundData::
if (_sample != 0) {
if (_manager->_is_valid) {
_manager->make_current();
alDeleteBuffers(1,&_sample);
_manager->delete_buffer(_sample);
}
_sample = 0;
}
@ -1111,8 +1111,8 @@ discard_excess_cache(int sample_limit) {
while (((int)_expiring_samples.size()) > sample_limit) {
SoundData *sd = (SoundData*)(_expiring_samples.front());
assert(sd->_client_count == 0);
assert(sd->_expire == _expiring_samples.begin());
nassertv(sd->_client_count == 0);
nassertv(sd->_expire == _expiring_samples.begin());
_expiring_samples.pop_front();
_sample_cache.erase(_sample_cache.find(sd->_movie->get_filename()));
audio_debug("Expiring: " << sd->_movie->get_filename().get_basename());
@ -1121,10 +1121,49 @@ discard_excess_cache(int sample_limit) {
while (((int)_expiring_streams.size()) > stream_limit) {
SoundData *sd = (SoundData*)(_expiring_streams.front());
assert(sd->_client_count == 0);
assert(sd->_expire == _expiring_streams.begin());
nassertv(sd->_client_count == 0);
nassertv(sd->_expire == _expiring_streams.begin());
_expiring_streams.pop_front();
audio_debug("Expiring: " << sd->_movie->get_filename().get_basename());
delete sd;
}
}
/**
* Deletes an OpenAL buffer. This is a special function because some
* implementations of OpenAL (e.g. Apple's) don't unlock the buffers
* immediately, due to needing to coordinate with another thread. If this is
* the case, the alDeleteBuffers call will error back with AL_INVALID_OPERATION
* as if trying to delete an actively-used buffer, which will tell us to wait a
* bit and try again.
*/
void OpenALAudioManager::
delete_buffer(ALuint buffer) {
ReMutexHolder holder(_lock);
int tries = 0;
ALuint error;
// Keep trying until we succeed (or give up).
while (true) {
alDeleteBuffers(1, &buffer);
error = alGetError();
if (error == AL_NO_ERROR) {
// Success! This will happen right away 99% of the time.
return;
} else if (error != AL_INVALID_OPERATION) {
// We weren't expecting that. This should be reported.
break;
} else if (tries >= openal_buffer_delete_retries.get_value()) {
// We ran out of retries. Give up.
break;
} else {
// Make another try after (delay * 2^n) seconds.
Thread::sleep(openal_buffer_delete_delay.get_value() * (1 << tries));
tries++;
}
}
// If we got here, one of the breaks above happened, indicating an error.
audio_error("failed to delete a buffer: " << alGetString(error) );
}

View File

@ -129,6 +129,8 @@ private:
void decrement_client_count(SoundData *sd);
void discard_excess_cache(int limit);
void delete_buffer(ALuint buffer);
void starting_sound(OpenALAudioSound* audio);
void stopping_sound(OpenALAudioSound* audio);

View File

@ -40,7 +40,7 @@ get_calibrated_clock(double rtc) const {
*
* Returns true on success, false on failure.
*/
bool OpenALAudioSound::
INLINE bool OpenALAudioSound::
require_sound_data() {
if (_sd==0) {
_sd = _manager->get_sound_data(_movie, _desired_mode);
@ -55,11 +55,48 @@ require_sound_data() {
/**
* Checks if the sound data record is present and releasable, and if so,
* releases it.
*
* The sound data is "releasable" if it's from an ordinary, local file. Remote
* streams cannot necessarily be reopened if lost, so we'll hold onto them if
* so. The `force` argument overrides this, indicating we don't intend to
* reacquire the sound data.
*/
void OpenALAudioSound::
release_sound_data() {
if ((_sd!=0) && (!_movie->get_filename().empty())) {
INLINE void OpenALAudioSound::
release_sound_data(bool force) {
if (!has_sound_data()) return;
if (force || !_movie->get_filename().empty()) {
_manager->decrement_client_count(_sd);
_sd = 0;
}
}
/**
* Checks if the sound has NOT been cleaned up yet.
*/
INLINE bool OpenALAudioSound::
is_valid() const {
return _manager != NULL;
}
/**
* Checks if the sound is playing. This is per the OpenALAudioManager's
* definition of "playing" -- as in, "will be called upon every update"
*
* This is mainly intended for use in asserts.
*/
INLINE bool OpenALAudioSound::
is_playing() const {
// Manager only gives us a _source if we need it (to talk to OpenAL), so:
return _source != 0;
}
/**
* Checks if the sound has its SoundData structure open at the moment.
*
* This is mainly intended for use in asserts.
*/
INLINE bool OpenALAudioSound::
has_sound_data() const {
return _sd != NULL;
}

View File

@ -80,7 +80,7 @@ OpenALAudioSound(OpenALAudioManager* manager,
audio_warning("stereo sound " << movie->get_filename() << " will not be spatialized");
}
}
release_sound_data();
release_sound_data(false);
}
@ -99,15 +99,14 @@ OpenALAudioSound::
void OpenALAudioSound::
cleanup() {
ReMutexHolder holder(OpenALAudioManager::_lock);
if (_manager == 0) {
if (!is_valid()) {
return;
}
if (_source) {
if (is_playing()) {
stop();
}
if (_sd) {
_manager->decrement_client_count(_sd);
_sd = 0;
if (has_sound_data()) {
release_sound_data(true);
}
_manager->release_sound(this);
_manager = 0;
@ -119,7 +118,8 @@ cleanup() {
void OpenALAudioSound::
play() {
ReMutexHolder holder(OpenALAudioManager::_lock);
if (_manager == 0) return;
if (!is_valid()) return;
PN_stdfloat px,py,pz,vx,vy,vz;
@ -136,7 +136,7 @@ play() {
}
_manager->starting_sound(this);
if (!_source) {
if (!is_playing()) {
return;
}
@ -195,11 +195,14 @@ play() {
void OpenALAudioSound::
stop() {
ReMutexHolder holder(OpenALAudioManager::_lock);
if (_manager==0) return;
if (_source) {
if (!is_valid()) return;
if (is_playing()) {
_manager->make_current();
nassertv(has_sound_data());
alGetError(); // clear errors
alSourceStop(_source);
al_audio_errcheck("stopping a source");
@ -208,15 +211,14 @@ stop() {
for (int i=0; i<((int)(_stream_queued.size())); i++) {
ALuint buffer = _stream_queued[i]._buffer;
if (buffer != _sd->_sample) {
alDeleteBuffers(1, &buffer);
al_audio_errcheck("deleting a buffer");
_manager->delete_buffer(buffer);
}
}
_stream_queued.resize(0);
}
_manager->stopping_sound(this);
release_sound_data();
release_sound_data(false);
}
/**
@ -225,6 +227,9 @@ stop() {
void OpenALAudioSound::
finished() {
ReMutexHolder holder(OpenALAudioManager::_lock);
if (!is_valid()) return;
stop();
_current_time = _length;
if (!_finished_event.empty()) {
@ -255,7 +260,8 @@ get_loop() const {
void OpenALAudioSound::
set_loop_count(unsigned long loop_count) {
ReMutexHolder holder(OpenALAudioManager::_lock);
if (_manager==0) return;
if (!is_valid()) return;
if (loop_count >= 1000000000) {
loop_count = 0;
@ -282,9 +288,14 @@ void OpenALAudioSound::
restart_stalled_audio() {
ReMutexHolder holder(OpenALAudioManager::_lock);
ALenum status;
if (!is_valid()) return;
nassertv(is_playing());
if (_stream_queued.size() == 0) {
return;
}
alGetError();
alGetSourcei(_source, AL_SOURCE_STATE, &status);
if (status != AL_PLAYING) {
@ -298,6 +309,9 @@ restart_stalled_audio() {
void OpenALAudioSound::
queue_buffer(ALuint buffer, int samples, int loop_index, double time_offset) {
ReMutexHolder holder(OpenALAudioManager::_lock);
nassertv(is_playing());
// Now push the buffer into the stream queue.
alGetError();
alSourceQueueBuffers(_source,1,&buffer);
@ -322,6 +336,8 @@ ALuint OpenALAudioSound::
make_buffer(int samples, int channels, int rate, unsigned char *data) {
ReMutexHolder holder(OpenALAudioManager::_lock);
nassertr(is_playing(), 0);
// Allocate a buffer to hold the data.
alGetError();
ALuint buffer;
@ -354,6 +370,8 @@ int OpenALAudioSound::
read_stream_data(int bytelen, unsigned char *buffer) {
ReMutexHolder holder(OpenALAudioManager::_lock);
nassertr(has_sound_data(), 0);
MovieAudioCursor *cursor = _sd->_stream;
double length = cursor->length();
int channels = cursor->audio_channels();
@ -404,6 +422,9 @@ read_stream_data(int bytelen, unsigned char *buffer) {
void OpenALAudioSound::
correct_calibrated_clock(double rtc, double t) {
ReMutexHolder holder(OpenALAudioManager::_lock);
nassertv(is_playing());
double cc = (rtc - _calibrated_clock_base) * _calibrated_clock_scale;
double diff = cc-t;
_calibrated_clock_decavg = (_calibrated_clock_decavg * 0.95) + (diff * 0.05);
@ -435,6 +456,11 @@ correct_calibrated_clock(double rtc, double t) {
void OpenALAudioSound::
pull_used_buffers() {
ReMutexHolder holder(OpenALAudioManager::_lock);
if (!is_valid()) return;
nassertv(is_playing());
nassertv(has_sound_data());
while (_stream_queued.size()) {
ALuint buffer = 0;
ALint num_buffers = 0;
@ -446,18 +472,34 @@ pull_used_buffers() {
int err = alGetError();
if (err == AL_NO_ERROR) {
if (_stream_queued[0]._buffer != buffer) {
audio_error("corruption in stream queue");
cleanup();
return;
}
_stream_queued.pop_front();
if (_stream_queued.size()) {
double al = _stream_queued[0]._time_offset + _stream_queued[0]._loop_index * _length;
double rtc = TrueClock::get_global_ptr()->get_short_time();
correct_calibrated_clock(rtc, al);
}
if (buffer != _sd->_sample) {
alDeleteBuffers(1,&buffer);
// This is certainly atypical: most implementations of OpenAL unqueue
// buffers in FIFO order. However, some (e.g. Apple's) can unqueue
// buffers out-of-order if playback is interrupted. So, we don't freak
// out unless `buffer` isn't in _stream_queued at all.
bool found_culprit = false;
for (auto it = _stream_queued.begin(); it != _stream_queued.end(); ++it) {
if (it->_buffer == buffer) {
// Phew. Found it. Just remove that.
_stream_queued.erase(it);
found_culprit = true;
break;
}
}
if (!found_culprit) {
audio_error("corruption in stream queue");
cleanup();
return;
}
} else {
_stream_queued.pop_front();
if (_stream_queued.size()) {
double al = _stream_queued[0]._time_offset + _stream_queued[0]._loop_index * _length;
double rtc = TrueClock::get_global_ptr()->get_short_time();
correct_calibrated_clock(rtc, al);
}
if (buffer != _sd->_sample) {
_manager->delete_buffer(buffer);
}
}
} else {
break;
@ -474,6 +516,10 @@ push_fresh_buffers() {
ReMutexHolder holder(OpenALAudioManager::_lock);
static unsigned char data[65536];
if (!is_valid()) return;
nassertv(is_playing());
nassertv(has_sound_data());
if (_sd->_sample) {
while ((_loops_completed < _playing_loops) &&
(_stream_queued.size() < 100)) {
@ -499,9 +545,9 @@ push_fresh_buffers() {
break;
}
ALuint buffer = make_buffer(samples, channels, rate, data);
if (_manager == 0) return;
if (!is_valid() || !buffer) return;
queue_buffer(buffer, samples, loop_index, time_offset);
if (_manager == 0) return;
if (!is_valid()) return;
fill += samples;
}
}
@ -523,7 +569,7 @@ set_time(PN_stdfloat time) {
PN_stdfloat OpenALAudioSound::
get_time() const {
ReMutexHolder holder(OpenALAudioManager::_lock);
if (_manager == 0) {
if (!is_valid()) {
return 0.0;
}
return _current_time;
@ -535,7 +581,9 @@ get_time() const {
void OpenALAudioSound::
cache_time(double rtc) {
ReMutexHolder holder(OpenALAudioManager::_lock);
assert(_source != 0);
nassertv(is_playing());
double t=get_calibrated_clock(rtc);
double max = _length * _playing_loops;
if (t >= max) {
@ -553,7 +601,7 @@ set_volume(PN_stdfloat volume) {
ReMutexHolder holder(OpenALAudioManager::_lock);
_volume=volume;
if (_source) {
if (is_playing()) {
volume*=_manager->get_volume();
_manager->make_current();
alGetError(); // clear errors
@ -597,7 +645,7 @@ void OpenALAudioSound::
set_play_rate(PN_stdfloat play_rate) {
ReMutexHolder holder(OpenALAudioManager::_lock);
_play_rate = play_rate;
if (_source) {
if (is_playing()) {
alSourcef(_source, AL_PITCH, play_rate);
}
}
@ -639,7 +687,7 @@ set_3d_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz, PN_stdfloat vx
_velocity[1] = vz;
_velocity[2] = -vy;
if (_source) {
if (is_playing()) {
_manager->make_current();
alGetError(); // clear errors
@ -675,7 +723,7 @@ set_3d_min_distance(PN_stdfloat dist) {
ReMutexHolder holder(OpenALAudioManager::_lock);
_min_dist = dist;
if (_source) {
if (is_playing()) {
_manager->make_current();
alGetError(); // clear errors
@ -700,7 +748,7 @@ set_3d_max_distance(PN_stdfloat dist) {
ReMutexHolder holder(OpenALAudioManager::_lock);
_max_dist = dist;
if (_source) {
if (is_playing()) {
_manager->make_current();
alGetError(); // clear errors
@ -725,7 +773,7 @@ set_3d_drop_off_factor(PN_stdfloat factor) {
ReMutexHolder holder(OpenALAudioManager::_lock);
_drop_off_factor = factor;
if (_source) {
if (is_playing()) {
_manager->make_current();
alGetError(); // clear errors
@ -743,13 +791,16 @@ get_3d_drop_off_factor() const {
}
/**
* Sets whether the sound is marked "active". By default, the active flag
* Sets whether the sound is marked "active". By default, the active flag is
* true for all sounds. If the active flag is set to false for any particular
* sound, the sound will not be heard.
*/
void OpenALAudioSound::
set_active(bool active) {
ReMutexHolder holder(OpenALAudioManager::_lock);
if (!is_valid()) return;
if (_active!=active) {
_active=active;
if (_active) {
@ -813,7 +864,7 @@ get_name() const {
AudioSound::SoundStatus OpenALAudioSound::
status() const {
ReMutexHolder holder(OpenALAudioManager::_lock);
if (_source==0) {
if (!is_playing()) {
return AudioSound::READY;
}
if ((_loops_completed >= _playing_loops)&&(_stream_queued.size()==0)) {

View File

@ -117,12 +117,14 @@ private:
void pull_used_buffers();
void push_fresh_buffers();
INLINE bool require_sound_data();
INLINE void release_sound_data();
INLINE void release_sound_data(bool force);
INLINE bool is_valid() const;
INLINE bool is_playing() const;
INLINE bool has_sound_data() const;
private:
void do_stop();
PT(MovieAudio) _movie;
OpenALAudioManager::SoundData *_sd;

View File

@ -1104,6 +1104,24 @@ get_group_collision_flag(unsigned int group1, unsigned int group2) const {
return _filter_cb2._collide[group1].get_bit(group2);
}
/**
*
*/
void BulletWorld::
set_force_update_all_aabbs(bool force) {
LightMutexHolder holder(get_global_lock());
_world->setForceUpdateAllAabbs(force);
}
/**
*
*/
bool BulletWorld::
get_force_update_all_aabbs() const {
LightMutexHolder holder(get_global_lock());
return _world->getForceUpdateAllAabbs();
}
/**
*
*/

View File

@ -134,6 +134,9 @@ PUBLISHED:
void set_group_collision_flag(unsigned int group1, unsigned int group2, bool enable);
bool get_group_collision_flag(unsigned int group1, unsigned int group2) const;
void set_force_update_all_aabbs(bool force);
bool get_force_update_all_aabbs() const;
// Callbacks
void set_contact_added_callback(CallbackObject *obj);
void clear_contact_added_callback();
@ -166,6 +169,8 @@ PUBLISHED:
MAKE_SEQ_PROPERTY(vehicles, get_num_vehicles, get_vehicle);
MAKE_SEQ_PROPERTY(constraints, get_num_constraints, get_constraint);
MAKE_SEQ_PROPERTY(manifolds, get_num_manifolds, get_manifold);
MAKE_PROPERTY(force_update_all_aabbs, get_force_update_all_aabbs,
set_force_update_all_aabbs);
PUBLISHED: // Deprecated methods, will be removed soon
void attach_ghost(BulletGhostNode *node);

View File

@ -30,7 +30,6 @@
#include "sliderTable.h"
class CharacterJointBundle;
class ComputedVertices;
/**
* An animated character, with skeleton-morph animation and either soft-

View File

@ -39,7 +39,8 @@ CharacterJoint(const CharacterJoint &copy) :
MovingPartMatrix(copy),
_character(NULL),
_net_transform(copy._net_transform),
_initial_net_transform_inverse(copy._initial_net_transform_inverse)
_initial_net_transform_inverse(copy._initial_net_transform_inverse),
_skinning_matrix(copy._skinning_matrix)
{
// We don't copy the sets of transform nodes.
}
@ -60,9 +61,12 @@ CharacterJoint(Character *character,
// update_internals() to get our _net_transform set properly.
update_internals(root, parent, true, false, current_thread);
// And then compute its inverse. This is needed for ComputedVertices,
// during animation.
// And then compute its inverse. This is needed to track changes in
// _net_transform as the joint moves, so we can recompute _skinning_matrix,
// which maps vertices from their initial positions to their animated
// positions.
_initial_net_transform_inverse = invert(_net_transform);
_skinning_matrix = LMatrix4::ident_mat();
}
/**
@ -141,11 +145,13 @@ update_internals(PartBundle *root, PartGroup *parent, bool self_changed,
}
}
// Also tell our related JointVertexTransforms that they now need to
// recompute themselves.
// Recompute the transform used by any vertices animated by this joint.
_skinning_matrix = _initial_net_transform_inverse * _net_transform;
// Also tell our related JointVertexTransforms that we've changed their
// underlying matrix.
VertexTransforms::iterator vti;
for (vti = _vertex_transforms.begin(); vti != _vertex_transforms.end(); ++vti) {
(*vti)->_matrix_stale = true;
(*vti)->mark_modified(current_thread);
}
}

View File

@ -108,6 +108,12 @@ public:
LMatrix4 _net_transform;
LMatrix4 _initial_net_transform_inverse;
// This is the product of the above; the matrix that gets applied to a
// vertex (whose coordinates are in the coordinate space of the character
// in its neutral pose) to transform it from its neutral position to its
// animated position.
LMatrix4 _skinning_matrix;
public:
virtual TypeHandle get_type() const {
return get_class_type();

View File

@ -18,13 +18,3 @@ INLINE const CharacterJoint *JointVertexTransform::
get_joint() const {
return _joint;
}
/**
* Recomputes _matrix if it needs it.
*/
INLINE void JointVertexTransform::
check_matrix() const {
if (_matrix_stale) {
((JointVertexTransform *)this)->compute_matrix();
}
}

View File

@ -24,8 +24,7 @@ TypeHandle JointVertexTransform::_type_handle;
* Constructs an invalid object; used only by the bam loader.
*/
JointVertexTransform::
JointVertexTransform() :
_matrix_stale(true)
JointVertexTransform()
{
}
@ -35,8 +34,7 @@ JointVertexTransform() :
*/
JointVertexTransform::
JointVertexTransform(CharacterJoint *joint) :
_joint(joint),
_matrix_stale(true)
_joint(joint)
{
// Tell the joint that we need to be informed when it moves.
_joint->_vertex_transforms.insert(this);
@ -57,8 +55,7 @@ JointVertexTransform::
*/
void JointVertexTransform::
get_matrix(LMatrix4 &matrix) const {
check_matrix();
matrix = _matrix;
matrix = _joint->_skinning_matrix;
}
/**
@ -69,8 +66,7 @@ get_matrix(LMatrix4 &matrix) const {
*/
void JointVertexTransform::
mult_matrix(LMatrix4 &result, const LMatrix4 &previous) const {
check_matrix();
result.multiply(_matrix, previous);
result.multiply(_joint->_skinning_matrix, previous);
}
/**
@ -80,9 +76,7 @@ mult_matrix(LMatrix4 &result, const LMatrix4 &previous) const {
*/
void JointVertexTransform::
accumulate_matrix(LMatrix4 &accum, PN_stdfloat weight) const {
check_matrix();
accum.accumulate(_matrix, weight);
accum.accumulate(_joint->_skinning_matrix, weight);
}
/**
@ -93,19 +87,6 @@ output(ostream &out) const {
out << _joint->get_name();
}
/**
* Recomputes _matrix if it needs it. Uses locking.
*/
void JointVertexTransform::
compute_matrix() {
LightMutexHolder holder(_lock);
if (_matrix_stale) {
_matrix = _joint->_initial_net_transform_inverse * _joint->_net_transform;
_matrix_stale = false;
}
}
/**
* Tells the BamReader how to create objects of type JointVertexTransform.
*/
@ -165,6 +146,5 @@ fillin(DatagramIterator &scan, BamReader *manager) {
VertexTransform::fillin(scan, manager);
manager->read_pointer(scan);
_matrix_stale = true;
mark_modified(Thread::get_current_thread());
}

View File

@ -47,15 +47,8 @@ PUBLISHED:
virtual void output(ostream &out) const;
private:
INLINE void check_matrix() const;
void compute_matrix();
PT(CharacterJoint) _joint;
LMatrix4 _matrix;
bool _matrix_stale;
LightMutex _lock;
public:
static void register_with_read_factory();
virtual void write_datagram(BamWriter *manager, Datagram &dg);

View File

@ -244,7 +244,6 @@ add_waiting_task(AsyncTask *task) {
*/
void AsyncFuture::
wake_task(AsyncTask *task) {
cerr << "waking task\n";
AsyncTaskManager *manager = task->_manager;
if (manager == nullptr) {
// If it's an unscheduled task, schedule it on the same manager as the

View File

@ -900,12 +900,52 @@ write_datagram(BamWriter *manager, Datagram &dg) {
int ClipPlaneAttrib::
complete_pointers(TypedWritable **p_list, BamReader *manager) {
int pi = RenderAttrib::complete_pointers(p_list, manager);
AttribNodeRegistry *areg = AttribNodeRegistry::get_global_ptr();
if (manager->get_file_minor_ver() >= 40) {
for (size_t i = 0; i < _off_planes.size(); ++i) {
pi += _off_planes[i].complete_pointers(p_list + pi, manager);
}
for (size_t i = 0; i < _on_planes.size(); ++i) {
pi += _on_planes[i].complete_pointers(p_list + pi, manager);
}
} else {
BamAuxData *aux = (BamAuxData *)manager->get_aux_data(this, "planes");
nassertr(aux != NULL, pi);
int i;
aux->_off_list.reserve(aux->_num_off_planes);
for (i = 0; i < aux->_num_off_planes; ++i) {
PandaNode *node;
DCAST_INTO_R(node, p_list[pi++], pi);
aux->_off_list.push_back(node);
}
aux->_on_list.reserve(aux->_num_on_planes);
for (i = 0; i < aux->_num_on_planes; ++i) {
PandaNode *node;
DCAST_INTO_R(node, p_list[pi++], pi);
aux->_on_list.push_back(node);
}
}
return pi;
}
/**
* Called by the BamReader to perform any final actions needed for setting up
* the object after all objects have been read and all pointers have been
* completed.
*/
void ClipPlaneAttrib::
finalize(BamReader *manager) {
if (manager->get_file_minor_ver() >= 40) {
AttribNodeRegistry *areg = AttribNodeRegistry::get_global_ptr();
// Check if any of the nodes we loaded are mentioned in the
// AttribNodeRegistry. If so, replace them.
for (size_t i = 0; i < _off_planes.size(); ++i) {
int n = areg->find_node(_off_planes[i]);
if (n != -1) {
// If it's in the registry, replace it.
@ -914,8 +954,6 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
}
for (size_t i = 0; i < _on_planes.size(); ++i) {
pi += _on_planes[i].complete_pointers(p_list + pi, manager);
int n = areg->find_node(_on_planes[i]);
if (n != -1) {
// If it's in the registry, replace it.
@ -924,53 +962,46 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
}
} else {
Planes::iterator ci = _off_planes.begin();
while (ci != _off_planes.end()) {
PandaNode *node;
DCAST_INTO_R(node, p_list[pi++], pi);
// Now it's safe to convert our saved PandaNodes into NodePaths.
BamAuxData *aux = (BamAuxData *)manager->get_aux_data(this, "planes");
nassertv(aux != NULL);
nassertv(aux->_num_off_planes == (int)aux->_off_list.size());
nassertv(aux->_num_on_planes == (int)aux->_on_list.size());
// We go through some effort to look up the node in the registry without
// creating a NodePath around it first (which would up, and then down,
// the reference count, possibly deleting the node).
int ni = areg->find_node(node->get_type(), node->get_name());
if (ni != -1) {
(*ci) = areg->get_node(ni);
AttribNodeRegistry *areg = AttribNodeRegistry::get_global_ptr();
_off_planes.reserve(aux->_off_list.size());
NodeList::iterator ni;
for (ni = aux->_off_list.begin(); ni != aux->_off_list.end(); ++ni) {
PandaNode *node = (*ni);
int n = areg->find_node(node->get_type(), node->get_name());
if (n != -1) {
// If it's in the registry, add that NodePath.
_off_planes.push_back(areg->get_node(n));
} else {
(*ci) = NodePath(node);
// Otherwise, add any arbitrary NodePath. Complain if it's ambiguous.
_off_planes.push_back(NodePath(node));
}
++ci;
}
ci = _on_planes.begin();
while (ci != _on_planes.end()) {
PandaNode *node;
DCAST_INTO_R(node, p_list[pi++], pi);
int ni = areg->find_node(node->get_type(), node->get_name());
if (ni != -1) {
(*ci) = areg->get_node(ni);
_on_planes.reserve(aux->_on_list.size());
for (ni = aux->_on_list.begin(); ni != aux->_on_list.end(); ++ni) {
PandaNode *node = (*ni);
int n = areg->find_node(node->get_type(), node->get_name());
if (n != -1) {
// If it's in the registry, add that NodePath.
_on_planes.push_back(areg->get_node(n));
node = _on_planes.back().node();
} else {
(*ci) = NodePath(node);
// Otherwise, add any arbitrary NodePath. Complain if it's ambiguous.
_on_planes.push_back(NodePath(node));
}
++ci;
}
}
// Now that the NodePaths have been filled in, we can sort the list.
_off_planes.sort();
_on_planes.sort();
return pi;
}
/**
* Some objects require all of their nested pointers to have been completed
* before the objects themselves can be completed. If this is the case,
* override this method to return true, and be careful with circular
* references (which would make the object unreadable from a bam file).
*/
bool ClipPlaneAttrib::
require_fully_complete() const {
return true;
}
/**
@ -987,6 +1018,8 @@ make_from_bam(const FactoryParams &params) {
parse_params(params, scan, manager);
attrib->fillin(scan, manager);
manager->register_finalize(attrib);
return attrib;
}
@ -1000,26 +1033,24 @@ fillin(DatagramIterator &scan, BamReader *manager) {
_off_all_planes = scan.get_bool();
int num_off_planes = scan.get_uint16();
// Push back an empty NodePath for each off Plane for now, until we get the
// actual list of pointers later in complete_pointers().
_off_planes.resize(num_off_planes);
if (manager->get_file_minor_ver() >= 40) {
for (int i = 0; i < num_off_planes; i++) {
_off_planes.resize(scan.get_uint16());
for (size_t i = 0; i < _off_planes.size(); ++i) {
_off_planes[i].fillin(scan, manager);
}
} else {
manager->read_pointers(scan, num_off_planes);
}
int num_on_planes = scan.get_uint16();
_on_planes.resize(num_on_planes);
if (manager->get_file_minor_ver() >= 40) {
for (int i = 0; i < num_on_planes; i++) {
manager->read_pointer(scan);
_on_planes.resize(scan.get_uint16());
for (size_t i = 0; i < _on_planes.size(); ++i) {
_on_planes[i].fillin(scan, manager);
}
} else {
manager->read_pointers(scan, num_on_planes);
BamAuxData *aux = new BamAuxData;
manager->set_aux_data(this, "planes", aux);
aux->_num_off_planes = scan.get_uint16();
manager->read_pointers(scan, aux->_num_off_planes);
aux->_num_on_planes = scan.get_uint16();
manager->read_pointers(scan, aux->_num_on_planes);
}
}

View File

@ -125,11 +125,26 @@ PUBLISHED:
}
MAKE_PROPERTY(class_slot, get_class_slot);
public:
// This data is only needed when reading from a bam file.
typedef pvector<PT(PandaNode) > NodeList;
class BamAuxData : public BamReader::AuxData {
public:
// We hold a pointer to each of the PandaNodes on the on_list and
// off_list. We will later convert these to NodePaths in
// finalize().
int _num_off_planes;
int _num_on_planes;
NodeList _off_list;
NodeList _on_list;
};
public:
static void register_with_read_factory();
virtual void write_datagram(BamWriter *manager, Datagram &dg);
virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
virtual bool require_fully_complete() const;
virtual void finalize(BamReader *manager);
protected:
static TypedWritable *make_from_bam(const FactoryParams &params);

View File

@ -128,6 +128,29 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
object->_state = object->_state->compose(get_rescale_normal_state(mode));
}
// Check for a special wireframe setting.
const RenderModeAttrib *rmode;
if (object->_state->get_attrib(rmode)) {
if (rmode->get_mode() == RenderModeAttrib::M_filled_wireframe) {
CullableObject *wireframe_part = new CullableObject(*object);
wireframe_part->_state = get_wireframe_overlay_state(rmode);
if (wireframe_part->munge_geom
(_gsg, _gsg->get_geom_munger(wireframe_part->_state, current_thread),
traverser, force)) {
int wireframe_bin_index = bin_manager->find_bin("fixed");
CullBin *bin = get_bin(wireframe_bin_index);
nassertv(bin != (CullBin *)NULL);
check_flash_bin(wireframe_part->_state, bin_manager, wireframe_bin_index);
bin->add_object(wireframe_part, current_thread);
} else {
delete wireframe_part;
}
object->_state = object->_state->compose(get_wireframe_filled_state());
}
}
// Check to see if there's a special transparency setting.
const TransparencyAttrib *trans;
if (object->_state->get_attrib(trans)) {
@ -216,29 +239,6 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
}
}
// Check for a special wireframe setting.
const RenderModeAttrib *rmode;
if (object->_state->get_attrib(rmode)) {
if (rmode->get_mode() == RenderModeAttrib::M_filled_wireframe) {
CullableObject *wireframe_part = new CullableObject(*object);
wireframe_part->_state = get_wireframe_overlay_state(rmode);
if (wireframe_part->munge_geom
(_gsg, _gsg->get_geom_munger(wireframe_part->_state, current_thread),
traverser, force)) {
int wireframe_bin_index = bin_manager->find_bin("fixed");
CullBin *bin = get_bin(wireframe_bin_index);
nassertv(bin != (CullBin *)NULL);
check_flash_bin(wireframe_part->_state, bin_manager, wireframe_bin_index);
bin->add_object(wireframe_part, current_thread);
} else {
delete wireframe_part;
}
object->_state = object->_state->compose(get_wireframe_filled_state());
}
}
int bin_index = object->_state->get_bin_index();
CullBin *bin = get_bin(bin_index);
nassertv(bin != (CullBin *)NULL);

View File

@ -344,7 +344,7 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) {
case TextureStage::M_modulate_gloss:
if (shader_attrib->auto_gloss_on()) {
info._flags = ShaderKey::TF_map_glow;
info._flags = ShaderKey::TF_map_gloss;
} else {
info._mode = TextureStage::M_modulate;
info._flags = ShaderKey::TF_has_rgb;

View File

@ -1278,7 +1278,8 @@ p_read_object() {
} else {
if (bam_cat.is_spam()) {
bam_cat.spam()
<< "Read a " << object->get_type() << ": " << (void *)object << "\n";
<< "Read a " << object->get_type() << ": " << (void *)object
<< " (id=" << object_id << ")\n";
}
}
}

View File

@ -26,6 +26,10 @@
Configure(config_vision);
NotifyCategoryDef(vision, "");
ConfigVariableBool v4l_blocking
("v4l-blocking", false,
PRC_DESC("Set this to true if you want to block waiting for webcam frames."));
ConfigureFn(config_vision) {
init_libvision();
}

View File

@ -16,9 +16,12 @@
#include "pandabase.h"
#include "notifyCategoryProxy.h"
#include "configVariableBool.h"
NotifyCategoryDecl(vision, EXPCL_VISION, EXPTP_VISION);
extern ConfigVariableBool v4l_blocking;
extern EXPCL_VISION void init_libvision();
#endif

View File

@ -209,7 +209,13 @@ WebcamVideoCursorV4L(WebcamVideoV4L *src) : MovieVideoCursor(src) {
_buffers = NULL;
_buflens = NULL;
_fd = open(src->_device.c_str(), O_RDWR);
int mode = O_RDWR;
if (!v4l_blocking) {
mode = O_NONBLOCK;
}
_fd = open(src->_device.c_str(), mode);
if (-1 == _fd) {
vision_cat.error() << "Failed to open " << src->_device.c_str() << "\n";
return;
@ -247,6 +253,10 @@ WebcamVideoCursorV4L(WebcamVideoV4L *src) : MovieVideoCursor(src) {
_num_components = 4;
break;
case V4L2_PIX_FMT_GREY:
_num_components = 1;
break;
default:
vision_cat.error() << "Unsupported pixel format " << src->get_pixel_format() << "!\n";
_ready = false;
@ -393,6 +403,10 @@ fetch_buffer() {
vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vbuf.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl(_fd, VIDIOC_DQBUF, &vbuf) && errno != EIO) {
if (errno == EAGAIN) {
// Simply nothing is available yet.
return NULL;
}
vision_cat.error() << "Failed to dequeue buffer!\n";
return NULL;
}
@ -484,6 +498,7 @@ fetch_buffer() {
case V4L2_PIX_FMT_BGR24:
case V4L2_PIX_FMT_BGR32:
case V4L2_PIX_FMT_GREY:
// Simplest case: copying every row verbatim.
nassertr(old_bpl == new_bpl, NULL);

View File

@ -174,6 +174,7 @@ void find_all_webcams_v4l() {
case V4L2_PIX_FMT_BGR32:
case V4L2_PIX_FMT_RGB24:
case V4L2_PIX_FMT_RGB32:
case V4L2_PIX_FMT_GREY:
break;
default:

View File

@ -41,7 +41,9 @@ def window(graphics_pipe, graphics_engine):
)
graphics_engine.open_windows()
assert win is not None
if win is None:
pytest.skip("GraphicsPipe cannot make windows")
yield win
if win is not None: