Merge branch 'master' into deploy-ng
This commit is contained in:
commit
aba26bd862
|
|
@ -162,7 +162,7 @@ Reporting Issues
|
|||
If you encounter any bugs when using Panda3D, please report them in the bug
|
||||
tracker. This is hosted at:
|
||||
|
||||
https://bugs.launchpad.net/panda3d
|
||||
https://github.com/panda3d/panda3d/issues
|
||||
|
||||
Make sure to first use the search function to see if the bug has already been
|
||||
reported. When filling out a bug report, make sure that you include as much
|
||||
|
|
|
|||
|
|
@ -21,31 +21,103 @@ class Loader(DirectObject):
|
|||
loaderIndex = 0
|
||||
|
||||
class Callback:
|
||||
def __init__(self, numObjects, gotList, callback, extraArgs):
|
||||
"""Returned by loadModel when used asynchronously. This class is
|
||||
modelled after Future, and can be awaited."""
|
||||
|
||||
# This indicates that this class behaves like a Future.
|
||||
_asyncio_future_blocking = False
|
||||
|
||||
def __init__(self, loader, numObjects, gotList, callback, extraArgs):
|
||||
self._loader = loader
|
||||
self.objects = [None] * numObjects
|
||||
self.gotList = gotList
|
||||
self.callback = callback
|
||||
self.extraArgs = extraArgs
|
||||
self.numRemaining = numObjects
|
||||
self.cancelled = False
|
||||
self.requests = set()
|
||||
self.requestList = []
|
||||
|
||||
def gotObject(self, index, object):
|
||||
self.objects[index] = object
|
||||
self.numRemaining -= 1
|
||||
|
||||
if self.numRemaining == 0:
|
||||
if self.gotList:
|
||||
self.callback(self.objects, *self.extraArgs)
|
||||
else:
|
||||
self.callback(*(self.objects + self.extraArgs))
|
||||
if not self.requests:
|
||||
self._loader = None
|
||||
if self.callback:
|
||||
if self.gotList:
|
||||
self.callback(self.objects, *self.extraArgs)
|
||||
else:
|
||||
self.callback(*(self.objects + self.extraArgs))
|
||||
|
||||
def cancel(self):
|
||||
"Cancels the request. Callback won't be called."
|
||||
if self._loader:
|
||||
self._loader = None
|
||||
for request in self.requests:
|
||||
self._loader.loader.remove(request)
|
||||
del self._loader._requests[request]
|
||||
self.requests = None
|
||||
self.requestList = None
|
||||
|
||||
def cancelled(self):
|
||||
"Returns true if the request was cancelled."
|
||||
return self.requestList is None
|
||||
|
||||
def done(self):
|
||||
"Returns true if all the requests were finished or cancelled."
|
||||
return not self.requests
|
||||
|
||||
def result(self):
|
||||
assert not self.requests, "Result is not ready."
|
||||
if self.gotList:
|
||||
return self.objects
|
||||
else:
|
||||
return self.objects[0]
|
||||
|
||||
def exception(self):
|
||||
assert self.done() and not self.cancelled()
|
||||
return None
|
||||
|
||||
def __await__(self):
|
||||
""" Returns a generator that raises StopIteration when the loading
|
||||
is complete. This allows this class to be used with 'await'."""
|
||||
if self.requests:
|
||||
self._asyncio_future_blocking = True
|
||||
yield self
|
||||
|
||||
# This should be a simple return, but older versions of Python
|
||||
# don't allow return statements with arguments.
|
||||
result = self.result()
|
||||
exc = StopIteration(result)
|
||||
exc.value = result
|
||||
raise exc
|
||||
|
||||
def __aiter__(self):
|
||||
""" This allows using `async for` to iterate asynchronously over
|
||||
the results of this class. It does guarantee to return the
|
||||
results in order, though, even though they may not be loaded in
|
||||
that order. """
|
||||
requestList = self.requestList
|
||||
assert requestList is not None, "Request was cancelled."
|
||||
|
||||
class AsyncIter:
|
||||
index = 0
|
||||
def __anext__(self):
|
||||
if self.index < len(requestList):
|
||||
i = self.index
|
||||
self.index = i + 1
|
||||
return requestList[i]
|
||||
else:
|
||||
raise StopAsyncIteration
|
||||
|
||||
iter = AsyncIter()
|
||||
iter.objects = self.objects
|
||||
return iter
|
||||
|
||||
# special methods
|
||||
def __init__(self, base):
|
||||
self.base = base
|
||||
self.loader = PandaLoader.getGlobalPtr()
|
||||
|
||||
self.__requests = {}
|
||||
self._requests = {}
|
||||
|
||||
self.hook = "async_loader_%s" % (Loader.loaderIndex)
|
||||
Loader.loaderIndex += 1
|
||||
|
|
@ -180,7 +252,7 @@ class Loader(DirectObject):
|
|||
# requested models have been loaded, we'll invoke the
|
||||
# callback (passing it the models on the parameter list).
|
||||
|
||||
cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
|
||||
cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs)
|
||||
i = 0
|
||||
for modelPath in modelList:
|
||||
request = self.loader.makeAsyncRequest(Filename(modelPath), loaderOptions)
|
||||
|
|
@ -189,26 +261,26 @@ class Loader(DirectObject):
|
|||
request.setDoneEvent(self.hook)
|
||||
self.loader.loadAsync(request)
|
||||
cb.requests.add(request)
|
||||
self.__requests[request] = (cb, i)
|
||||
cb.requestList.append(request)
|
||||
self._requests[request] = (cb, i)
|
||||
i += 1
|
||||
return cb
|
||||
|
||||
def cancelRequest(self, cb):
|
||||
"""Cancels an aysynchronous loading or flatten request issued
|
||||
earlier. The callback associated with the request will not be
|
||||
called after cancelRequest() has been performed. """
|
||||
called after cancelRequest() has been performed.
|
||||
|
||||
if not cb.cancelled:
|
||||
cb.cancelled = True
|
||||
for request in cb.requests:
|
||||
self.loader.remove(request)
|
||||
del self.__requests[request]
|
||||
cb.requests = None
|
||||
This is now deprecated: call cb.cancel() instead. """
|
||||
|
||||
cb.cancel()
|
||||
|
||||
def isRequestPending(self, cb):
|
||||
""" Returns true if an asynchronous loading or flatten request
|
||||
issued earlier is still pending, or false if it has completed or
|
||||
been cancelled. """
|
||||
been cancelled.
|
||||
|
||||
This is now deprecated: call cb.done() instead. """
|
||||
|
||||
return bool(cb.requests)
|
||||
|
||||
|
|
@ -344,7 +416,7 @@ class Loader(DirectObject):
|
|||
# requested models have been saved, we'll invoke the
|
||||
# callback (passing it the models on the parameter list).
|
||||
|
||||
cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
|
||||
cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs)
|
||||
i = 0
|
||||
for modelPath, node in modelList:
|
||||
request = self.loader.makeAsyncSaveRequest(Filename(modelPath), loaderOptions, node)
|
||||
|
|
@ -353,7 +425,8 @@ class Loader(DirectObject):
|
|||
request.setDoneEvent(self.hook)
|
||||
self.loader.saveAsync(request)
|
||||
cb.requests.add(request)
|
||||
self.__requests[request] = (cb, i)
|
||||
cb.requestList.append(request)
|
||||
self._requests[request] = (cb, i)
|
||||
i += 1
|
||||
return cb
|
||||
|
||||
|
|
@ -880,13 +953,14 @@ class Loader(DirectObject):
|
|||
# requested sounds have been loaded, we'll invoke the
|
||||
# callback (passing it the sounds on the parameter list).
|
||||
|
||||
cb = Loader.Callback(len(soundList), gotList, callback, extraArgs)
|
||||
cb = Loader.Callback(self, len(soundList), gotList, callback, extraArgs)
|
||||
for i, soundPath in enumerate(soundList):
|
||||
request = AudioLoadRequest(manager, soundPath, positional)
|
||||
request.setDoneEvent(self.hook)
|
||||
self.loader.loadAsync(request)
|
||||
cb.requests.add(request)
|
||||
self.__requests[request] = (cb, i)
|
||||
cb.requestList.append(request)
|
||||
self._requests[request] = (cb, i)
|
||||
return cb
|
||||
|
||||
def unloadSfx(self, sfx):
|
||||
|
|
@ -944,14 +1018,15 @@ class Loader(DirectObject):
|
|||
callback = self.__asyncFlattenDone
|
||||
gotList = True
|
||||
|
||||
cb = Loader.Callback(len(modelList), gotList, callback, extraArgs)
|
||||
cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs)
|
||||
i = 0
|
||||
for model in modelList:
|
||||
request = ModelFlattenRequest(model.node())
|
||||
request.setDoneEvent(self.hook)
|
||||
self.loader.loadAsync(request)
|
||||
cb.requests.add(request)
|
||||
self.__requests[request] = (cb, i)
|
||||
cb.requestList.append(request)
|
||||
self._requests[request] = (cb, i)
|
||||
i += 1
|
||||
return cb
|
||||
|
||||
|
|
@ -980,36 +1055,22 @@ class Loader(DirectObject):
|
|||
of loaded objects, and call the appropriate callback when it's
|
||||
time."""
|
||||
|
||||
if request not in self.__requests:
|
||||
if request not in self._requests:
|
||||
return
|
||||
|
||||
cb, i = self.__requests[request]
|
||||
if cb.cancelled:
|
||||
cb, i = self._requests[request]
|
||||
if cb.cancelled():
|
||||
# Shouldn't be here.
|
||||
del self.__requests[request]
|
||||
del self._requests[request]
|
||||
return
|
||||
|
||||
cb.requests.discard(request)
|
||||
if not cb.requests:
|
||||
del self.__requests[request]
|
||||
del self._requests[request]
|
||||
|
||||
object = None
|
||||
if hasattr(request, "getModel"):
|
||||
node = request.getModel()
|
||||
if node is not None:
|
||||
object = NodePath(node)
|
||||
|
||||
elif hasattr(request, "getSound"):
|
||||
object = request.getSound()
|
||||
|
||||
elif hasattr(request, "getSuccess"):
|
||||
object = request.getSuccess()
|
||||
|
||||
cb.gotObject(i, object)
|
||||
cb.gotObject(i, request.result() or None)
|
||||
|
||||
load_model = loadModel
|
||||
cancel_request = cancelRequest
|
||||
is_request_pending = isRequestPending
|
||||
unload_model = unloadModel
|
||||
save_model = saveModel
|
||||
load_font = loadFont
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ class TaskManager:
|
|||
funcOrTask - either an existing Task object (not already added
|
||||
to the task manager), or a callable function object. If this
|
||||
is a function, a new Task object will be created and returned.
|
||||
You may also pass in a coroutine object.
|
||||
|
||||
name - the name to assign to the Task. Required, unless you
|
||||
are passing in a Task object that already has a name.
|
||||
|
|
@ -385,6 +386,15 @@ class TaskManager:
|
|||
task = funcOrTask
|
||||
elif hasattr(funcOrTask, '__call__'):
|
||||
task = PythonTask(funcOrTask)
|
||||
if name is None:
|
||||
name = getattr(funcOrTask, '__qualname__', None) or \
|
||||
getattr(funcOrTask, '__name__', None)
|
||||
elif hasattr(funcOrTask, 'cr_await') or type(funcOrTask) == types.GeneratorType:
|
||||
# It's a coroutine, or something emulating one.
|
||||
task = PythonTask(funcOrTask)
|
||||
if name is None:
|
||||
name = getattr(funcOrTask, '__qualname__', None) or \
|
||||
getattr(funcOrTask, '__name__', None)
|
||||
else:
|
||||
self.notify.error(
|
||||
'add: Tried to add a task that was not a Task or a func')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
A key advantage of Panda3D is that it provides developers with the
|
||||
ability to use both C++ and Python simultaneously. Essentially, Panda3D
|
||||
gives programmers the best of both worlds, as they are able to take
|
||||
advantage of the high performance and low-level programming found in
|
||||
C++ in addition to the flexibility, interactive scripting, and
|
||||
rapid-prototyping capabilities of Python. This feature is made possible
|
||||
due to Python’s ability to call C libraries, and ultimately make use of
|
||||
Panda3D’s Interrogate System: an automated C++ Extension Module
|
||||
generation utility similar to SWIG. Although Python is the favored
|
||||
scripting language of Panda3D, the engine is highly extensible in this
|
||||
aspect, as any language that has a foreign function interface can make
|
||||
use of the Interrogate System.
|
||||
|
||||
The Interrogate System works like a compiler by scanning and parsing
|
||||
C++ code for the Panda3D-specific, “PUBLISHED” keyword. This keyword
|
||||
marks the particular methods of a class that are to be exposed within a
|
||||
C++ Extension Module for that class which is eventually generated. One
|
||||
benefit of using the “PUBLISHED” keyword is that it alleviates the need
|
||||
for an interface file that provides function prototypes for the class
|
||||
methods that will be exposed within the extension module, as is the
|
||||
case with SWIG. Interrogate turns a class into a loose collection of
|
||||
Python interface wrapper functions that make up the C++ Extension
|
||||
Module.
|
||||
|
||||
This package depends on the 'cppparser' package, which contains the
|
||||
code that parses the C++ headers, and the 'interrogatedb' package,
|
||||
which contains the intermediate representation of the interfaces that
|
||||
can be saved to a database file for FFI generation tools to consume.
|
||||
|
|
@ -846,7 +846,7 @@ setup_properties(const InterrogateFunction &ifunc, InterfaceMaker *interface_mak
|
|||
}
|
||||
|
||||
} else if (fname == "__iter__") {
|
||||
if (_has_this && _parameters.size() == 1 &&
|
||||
if ((int)_parameters.size() == first_param &&
|
||||
TypeManager::is_pointer(_return_type->get_new_type())) {
|
||||
// It receives no parameters, and returns a pointer.
|
||||
_flags |= F_iter;
|
||||
|
|
|
|||
|
|
@ -499,6 +499,24 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
|
|||
}
|
||||
}
|
||||
|
||||
if (method_name == "__await__") {
|
||||
def._answer_location = "am_await";
|
||||
def._wrapper_type = WT_no_params;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (method_name == "__aiter__") {
|
||||
def._answer_location = "am_aiter";
|
||||
def._wrapper_type = WT_no_params;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (method_name == "__anext__") {
|
||||
def._answer_location = "am_anext";
|
||||
def._wrapper_type = WT_no_params;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (method_name == "operator ()") {
|
||||
def._answer_location = "tp_call";
|
||||
def._wrapper_type = WT_none;
|
||||
|
|
@ -2798,6 +2816,20 @@ write_module_class(ostream &out, Object *obj) {
|
|||
out << "};\n\n";
|
||||
}
|
||||
|
||||
bool have_async = false;
|
||||
if (has_parent_class || slots.count("am_await") != 0 ||
|
||||
slots.count("am_aiter") != 0 ||
|
||||
slots.count("am_anext") != 0) {
|
||||
out << "#if PY_VERSION_HEX >= 0x03050000\n";
|
||||
out << "static PyAsyncMethods Dtool_AsyncMethods_" << ClassName << " = {\n";
|
||||
write_function_slot(out, 2, slots, "am_await");
|
||||
write_function_slot(out, 2, slots, "am_aiter");
|
||||
write_function_slot(out, 2, slots, "am_anext");
|
||||
out << "};\n";
|
||||
out << "#endif\n\n";
|
||||
have_async = true;
|
||||
}
|
||||
|
||||
// Output the actual PyTypeObject definition.
|
||||
out << "struct Dtool_PyTypedObject Dtool_" << ClassName << " = {\n";
|
||||
out << " {\n";
|
||||
|
|
@ -2819,7 +2851,13 @@ write_module_class(ostream &out, Object *obj) {
|
|||
write_function_slot(out, 4, slots, "tp_setattr");
|
||||
|
||||
// cmpfunc tp_compare; (reserved in Python 3)
|
||||
out << "#if PY_MAJOR_VERSION >= 3\n";
|
||||
out << "#if PY_VERSION_HEX >= 0x03050000\n";
|
||||
if (have_async) {
|
||||
out << " &Dtool_AsyncMethods_" << ClassName << ",\n";
|
||||
} else {
|
||||
out << " 0, // tp_as_async\n";
|
||||
}
|
||||
out << "#elif PY_MAJOR_VERSION >= 3\n";
|
||||
out << " 0, // tp_reserved\n";
|
||||
out << "#else\n";
|
||||
if (has_hash_compare) {
|
||||
|
|
|
|||
|
|
@ -622,6 +622,10 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
|
|||
return Dtool_Raise_TypeError("PyType_Ready(Dtool_SeqMapWrapper)");
|
||||
}
|
||||
|
||||
if (PyType_Ready(&Dtool_GeneratorWrapper_Type) < 0) {
|
||||
return Dtool_Raise_TypeError("PyType_Ready(Dtool_GeneratorWrapper)");
|
||||
}
|
||||
|
||||
if (PyType_Ready(&Dtool_StaticProperty_Type) < 0) {
|
||||
return Dtool_Raise_TypeError("PyType_Ready(Dtool_StaticProperty_Type)");
|
||||
}
|
||||
|
|
@ -1111,6 +1115,13 @@ static int Dtool_SeqMapWrapper_setitem(PyObject *self, PyObject *key, PyObject *
|
|||
}
|
||||
}
|
||||
|
||||
static PyObject *Dtool_GeneratorWrapper_iternext(PyObject *self) {
|
||||
Dtool_GeneratorWrapper *wrap = (Dtool_GeneratorWrapper *)self;
|
||||
nassertr(wrap, nullptr);
|
||||
nassertr(wrap->_iternext_func, nullptr);
|
||||
return wrap->_iternext_func(wrap->_base._self);
|
||||
}
|
||||
|
||||
static PySequenceMethods Dtool_SequenceWrapper_SequenceMethods = {
|
||||
Dtool_SequenceWrapper_length,
|
||||
0, // sq_concat
|
||||
|
|
@ -1454,4 +1465,66 @@ PyTypeObject Dtool_SeqMapWrapper_Type = {
|
|||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* This variant defines only a generator interface.
|
||||
*/
|
||||
PyTypeObject Dtool_GeneratorWrapper_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"generator wrapper",
|
||||
sizeof(Dtool_GeneratorWrapper),
|
||||
0, // tp_itemsize
|
||||
Dtool_WrapperBase_dealloc,
|
||||
0, // tp_print
|
||||
0, // tp_getattr
|
||||
0, // tp_setattr
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
0, // tp_reserved
|
||||
#else
|
||||
0, // tp_compare
|
||||
#endif
|
||||
0, // tp_repr
|
||||
0, // tp_as_number
|
||||
0, // tp_as_sequence
|
||||
0, // tp_as_mapping
|
||||
0, // tp_hash
|
||||
0, // tp_call
|
||||
0, // tp_str
|
||||
PyObject_GenericGetAttr,
|
||||
PyObject_GenericSetAttr,
|
||||
0, // tp_as_buffer
|
||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
|
||||
0, // tp_doc
|
||||
0, // tp_traverse
|
||||
0, // tp_clear
|
||||
0, // tp_richcompare
|
||||
0, // tp_weaklistoffset
|
||||
PyObject_SelfIter,
|
||||
Dtool_GeneratorWrapper_iternext,
|
||||
0, // tp_methods
|
||||
0, // tp_members
|
||||
0, // tp_getset
|
||||
0, // tp_base
|
||||
0, // tp_dict
|
||||
0, // tp_descr_get
|
||||
0, // tp_descr_set
|
||||
0, // tp_dictoffset
|
||||
0, // tp_init
|
||||
PyType_GenericAlloc,
|
||||
0, // tp_new
|
||||
PyObject_Del,
|
||||
0, // tp_is_gc
|
||||
0, // tp_bases
|
||||
0, // tp_mro
|
||||
0, // tp_cache
|
||||
0, // tp_subclasses
|
||||
0, // tp_weaklist
|
||||
0, // tp_del
|
||||
#if PY_VERSION_HEX >= 0x02060000
|
||||
0, // tp_version_tag
|
||||
#endif
|
||||
#if PY_VERSION_HEX >= 0x03040000
|
||||
0, // tp_finalize
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
|
|
|||
|
|
@ -495,9 +495,15 @@ struct Dtool_SeqMapWrapper {
|
|||
objobjargproc _map_setitem_func;
|
||||
};
|
||||
|
||||
struct Dtool_GeneratorWrapper {
|
||||
Dtool_WrapperBase _base;
|
||||
iternextfunc _iternext_func;
|
||||
};
|
||||
|
||||
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SequenceWrapper_Type;
|
||||
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_MappingWrapper_Type;
|
||||
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_SeqMapWrapper_Type;
|
||||
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_GeneratorWrapper_Type;
|
||||
EXPCL_INTERROGATEDB extern PyTypeObject Dtool_StaticProperty_Type;
|
||||
|
||||
EXPCL_INTERROGATEDB PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset);
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ extern "C" {
|
|||
EXPCL_PYSTUB int PyObject_IsTrue(...);
|
||||
EXPCL_PYSTUB int PyObject_Repr(...);
|
||||
EXPCL_PYSTUB int PyObject_RichCompareBool(...);
|
||||
EXPCL_PYSTUB int PyObject_SelfIter(...);
|
||||
EXPCL_PYSTUB int PyObject_SetAttrString(...);
|
||||
EXPCL_PYSTUB int PyObject_Str(...);
|
||||
EXPCL_PYSTUB int PyObject_Type(...);
|
||||
|
|
@ -336,6 +337,7 @@ int PyObject_IsInstance(...) { return 0; }
|
|||
int PyObject_IsTrue(...) { return 0; }
|
||||
int PyObject_Repr(...) { return 0; }
|
||||
int PyObject_RichCompareBool(...) { return 0; }
|
||||
int PyObject_SelfIter(...) { return 0; }
|
||||
int PyObject_SetAttrString(...) { return 0; }
|
||||
int PyObject_Str(...) { return 0; }
|
||||
int PyObject_Type(...) { return 0; }
|
||||
|
|
|
|||
|
|
@ -3713,6 +3713,7 @@ if (not RUNTIME):
|
|||
TargetAdd('p3event_composite2.obj', opts=OPTS, input='p3event_composite2.cxx')
|
||||
|
||||
OPTS=['DIR:panda/src/event', 'PYTHON']
|
||||
TargetAdd('p3event_asyncTask_ext.obj', opts=OPTS, input='asyncTask_ext.cxx')
|
||||
TargetAdd('p3event_pythonTask.obj', opts=OPTS, input='pythonTask.cxx')
|
||||
IGATEFILES=GetDirectoryContents('panda/src/event', ["*.h", "*_composite*.cxx"])
|
||||
TargetAdd('libp3event.in', opts=OPTS, input=IGATEFILES)
|
||||
|
|
@ -4306,6 +4307,7 @@ if (not RUNTIME):
|
|||
TargetAdd('core.pyd', input='p3pipeline_pythonThread.obj')
|
||||
TargetAdd('core.pyd', input='p3putil_ext_composite.obj')
|
||||
TargetAdd('core.pyd', input='p3pnmimage_pfmFile_ext.obj')
|
||||
TargetAdd('core.pyd', input='p3event_asyncTask_ext.obj')
|
||||
TargetAdd('core.pyd', input='p3event_pythonTask.obj')
|
||||
TargetAdd('core.pyd', input='p3gobj_ext_composite.obj')
|
||||
TargetAdd('core.pyd', input='p3pgraph_ext_composite.obj')
|
||||
|
|
|
|||
|
|
@ -62,11 +62,24 @@ is_ready() const {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the sound that was loaded asynchronously, if any, or NULL if there
|
||||
* was an error. It is an error to call this unless is_ready() returns true.
|
||||
* Returns the sound that was loaded asynchronously, if any, or nullptr if
|
||||
* there was an error. It is an error to call this unless is_ready() returns
|
||||
* true.
|
||||
* @deprecated Use result() instead.
|
||||
*/
|
||||
INLINE AudioSound *AudioLoadRequest::
|
||||
get_sound() const {
|
||||
nassertr(_is_ready, NULL);
|
||||
return _sound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sound that was loaded asynchronously, if any, or nullptr if
|
||||
* there was an error. It is an error to call this unless is_ready() returns
|
||||
* true.
|
||||
*/
|
||||
INLINE AudioSound *AudioLoadRequest::
|
||||
result() const {
|
||||
nassertr(_is_ready, nullptr);
|
||||
return _sound;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ PUBLISHED:
|
|||
INLINE bool is_ready() const;
|
||||
INLINE AudioSound *get_sound() const;
|
||||
|
||||
INLINE AudioSound *result() const;
|
||||
|
||||
protected:
|
||||
virtual DoneStatus do_task();
|
||||
|
||||
|
|
|
|||
|
|
@ -154,9 +154,10 @@ PUBLISHED:
|
|||
PN_stdfloat *ux, PN_stdfloat *uy, PN_stdfloat *uz);
|
||||
|
||||
// Control the "relative scale that sets the distance factor" units for 3D
|
||||
// spacialized audio. Default is 1.0 Fmod uses meters internally, so give a
|
||||
// float in Units-per meter Don't know what Miles uses. Default is 1.0
|
||||
// which is adjust in panda to be feet.
|
||||
// spacialized audio. This is a float in units-per-meter. Default value is
|
||||
// 1.0, which means that Panda units are understood as meters; for e.g.
|
||||
// feet, set 3.28. This factor is applied only to Fmod and OpenAL at the
|
||||
// moment.
|
||||
virtual void audio_3d_set_distance_factor(PN_stdfloat factor);
|
||||
virtual PN_stdfloat audio_3d_get_distance_factor() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -42,14 +42,8 @@ pset<FmodAudioManager *> FmodAudioManager::_all_managers;
|
|||
|
||||
bool FmodAudioManager::_system_is_valid = false;
|
||||
|
||||
|
||||
// This sets the distance factor for 3D audio to use feet. FMOD uses meters
|
||||
// by default. Since Panda use feet we need to compensate for that with a
|
||||
// factor of 3.28 This can be overwritten. You just need to call
|
||||
// audio_3d_set_distance_factor(PN_stdfloat factor) and set your new factor.
|
||||
|
||||
PN_stdfloat FmodAudioManager::_doppler_factor = 1;
|
||||
PN_stdfloat FmodAudioManager::_distance_factor = 3.28;
|
||||
PN_stdfloat FmodAudioManager::_distance_factor = 1;
|
||||
PN_stdfloat FmodAudioManager::_drop_off_factor = 1;
|
||||
|
||||
|
||||
|
|
@ -100,6 +94,8 @@ FmodAudioManager() {
|
|||
_up.y = 0;
|
||||
_up.z = 0;
|
||||
|
||||
_active = true;
|
||||
|
||||
_saved_outputtype = FMOD_OUTPUTTYPE_AUTODETECT;
|
||||
|
||||
if (_system == (FMOD::System *)NULL) {
|
||||
|
|
|
|||
|
|
@ -123,9 +123,11 @@ public:
|
|||
PN_stdfloat *fx, PN_stdfloat *fy, PN_stdfloat *fz,
|
||||
PN_stdfloat *ux, PN_stdfloat *uy, PN_stdfloat *uz);
|
||||
|
||||
// Control the "relative distance factor" for 3D spacialized audio. Default
|
||||
// is 1.0 Fmod uses meters internally, so give a float in Units-per meter
|
||||
// Don't know what Miles uses.
|
||||
// Control the "relative scale that sets the distance factor" units for 3D
|
||||
// spacialized audio. This is a float in units-per-meter. Default value is
|
||||
// 1.0, which means that Panda units are understood as meters; for e.g.
|
||||
// feet, set 3.28. This factor is applied only to Fmod and OpenAL at the
|
||||
// moment.
|
||||
virtual void audio_3d_set_distance_factor(PN_stdfloat factor);
|
||||
virtual PN_stdfloat audio_3d_get_distance_factor() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ FmodAudioSound(AudioManager *manager, Filename file_name, bool positional) {
|
|||
_velocity.y = 0;
|
||||
_velocity.z = 0;
|
||||
|
||||
_min_dist = 1.0;
|
||||
_max_dist = 1000000000.0;
|
||||
|
||||
// Play Rate Variable
|
||||
_playrate = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ OpenALAudioManager() {
|
|||
_is_valid = true;
|
||||
|
||||
// Init 3D attributes
|
||||
_distance_factor = 3.28;
|
||||
_distance_factor = 1;
|
||||
_drop_off_factor = 1;
|
||||
|
||||
_position[0] = 0;
|
||||
|
|
@ -715,12 +715,11 @@ audio_3d_get_listener_attributes(PN_stdfloat *px, PN_stdfloat *py, PN_stdfloat *
|
|||
*uz = _forward_up[4];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set units per foot WARNING: OpenAL has no distance factor but we use this
|
||||
* as a scale on the min/max distances of sounds to preserve FMOD
|
||||
* compatibility. Also, adjusts the speed of sound to compensate for unit
|
||||
* difference. OpenAL's default speed of sound is 343.3 m/s == 1126.3 ft/s
|
||||
* Set value in units per meter
|
||||
* WARNING: OpenAL has no distance factor but we use this as a scale
|
||||
* on the min/max distances of sounds to preserve FMOD compatibility.
|
||||
* Also adjusts the speed of sound to compensate for unit difference.
|
||||
*/
|
||||
void OpenALAudioManager::
|
||||
audio_3d_set_distance_factor(PN_stdfloat factor) {
|
||||
|
|
@ -732,7 +731,7 @@ audio_3d_set_distance_factor(PN_stdfloat factor) {
|
|||
alGetError(); // clear errors
|
||||
|
||||
if (_distance_factor>0) {
|
||||
alSpeedOfSound(1126.3*_distance_factor);
|
||||
alSpeedOfSound(343.3*_distance_factor);
|
||||
al_audio_errcheck("alSpeedOfSound()");
|
||||
// resets the doppler factor to the correct setting in case it was set to
|
||||
// 0.0 by a distance_factor<=0.0
|
||||
|
|
@ -752,7 +751,7 @@ audio_3d_set_distance_factor(PN_stdfloat factor) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets units per foot
|
||||
* Get value in units per meter
|
||||
*/
|
||||
PN_stdfloat OpenALAudioManager::
|
||||
audio_3d_get_distance_factor() const {
|
||||
|
|
|
|||
|
|
@ -84,11 +84,14 @@ class EXPCL_OPENAL_AUDIO OpenALAudioManager : public AudioManager {
|
|||
PN_stdfloat *fx, PN_stdfloat *fy, PN_stdfloat *fz,
|
||||
PN_stdfloat *ux, PN_stdfloat *uy, PN_stdfloat *uz);
|
||||
|
||||
// Control the "relative distance factor" for 3D spacialized audio in units-
|
||||
// per-foot. Default is 1.0 OpenAL has no distance factor but we use this
|
||||
// as a scale on the minmax distances of sounds to preserve FMOD
|
||||
// compatibility. Also, adjusts the speed of sound to compensate for unit
|
||||
// difference.
|
||||
|
||||
// Control the "relative scale that sets the distance factor" units for 3D
|
||||
// spacialized audio. This is a float in units-per-meter. Default value is
|
||||
// 1.0, which means that Panda units are understood as meters; for e.g.
|
||||
// feet, set 3.28. This factor is applied only to Fmod and OpenAL at the
|
||||
// moment.
|
||||
// OpenAL in fact has no distance factor like Fmod, but works with the speed
|
||||
// of sound instead, so we use this factor to scale the speed of sound.
|
||||
virtual void audio_3d_set_distance_factor(PN_stdfloat factor);
|
||||
virtual PN_stdfloat audio_3d_get_distance_factor() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ OpenALAudioSound(OpenALAudioManager* manager,
|
|||
_balance(0),
|
||||
_play_rate(1.0),
|
||||
_positional(positional),
|
||||
_min_dist(3.28f),
|
||||
_min_dist(1.0f),
|
||||
_max_dist(1000000000.0f),
|
||||
_drop_off_factor(1.0f),
|
||||
_length(0.0),
|
||||
|
|
@ -673,7 +673,7 @@ set_3d_min_distance(PN_stdfloat dist) {
|
|||
_manager->make_current();
|
||||
|
||||
alGetError(); // clear errors
|
||||
alSourcef(_source,AL_REFERENCE_DISTANCE,_min_dist*_manager->audio_3d_get_distance_factor());
|
||||
alSourcef(_source,AL_REFERENCE_DISTANCE,_min_dist);
|
||||
al_audio_errcheck("alSourcefv(_source,AL_REFERENCE_DISTANCE)");
|
||||
}
|
||||
}
|
||||
|
|
@ -698,7 +698,7 @@ set_3d_max_distance(PN_stdfloat dist) {
|
|||
_manager->make_current();
|
||||
|
||||
alGetError(); // clear errors
|
||||
alSourcef(_source,AL_MAX_DISTANCE,_max_dist*_manager->audio_3d_get_distance_factor());
|
||||
alSourcef(_source,AL_MAX_DISTANCE,_max_dist);
|
||||
al_audio_errcheck("alSourcefv(_source,AL_MAX_DISTANCE)");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,20 +13,25 @@
|
|||
|
||||
#include "bulletDebugNode.h"
|
||||
|
||||
#include "cullHandler.h"
|
||||
#include "cullTraverser.h"
|
||||
#include "cullableObject.h"
|
||||
#include "geomLines.h"
|
||||
#include "geomVertexData.h"
|
||||
#include "geomTriangles.h"
|
||||
#include "geomVertexFormat.h"
|
||||
#include "geomVertexWriter.h"
|
||||
#include "omniBoundingVolume.h"
|
||||
#include "pStatTimer.h"
|
||||
|
||||
TypeHandle BulletDebugNode::_type_handle;
|
||||
PStatCollector BulletDebugNode::_pstat_debug("App:Bullet:DoPhysics:Debug");
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
BulletDebugNode::
|
||||
BulletDebugNode(const char *name) : GeomNode(name) {
|
||||
BulletDebugNode(const char *name) : PandaNode(name), _debug_stale(true) {
|
||||
|
||||
_wireframe = true;
|
||||
_constraints = true;
|
||||
|
|
@ -37,40 +42,6 @@ BulletDebugNode(const char *name) : GeomNode(name) {
|
|||
set_bounds(bounds);
|
||||
set_final(true);
|
||||
set_overall_hidden(true);
|
||||
|
||||
// Lines
|
||||
{
|
||||
PT(GeomVertexData) vdata;
|
||||
PT(Geom) geom;
|
||||
PT(GeomLines) prim;
|
||||
|
||||
vdata = new GeomVertexData("", GeomVertexFormat::get_v3c4(), Geom::UH_stream);
|
||||
|
||||
prim = new GeomLines(Geom::UH_stream);
|
||||
prim->set_shade_model(Geom::SM_uniform);
|
||||
|
||||
geom = new Geom(vdata);
|
||||
geom->add_primitive(prim);
|
||||
|
||||
add_geom(geom);
|
||||
}
|
||||
|
||||
// Triangles
|
||||
{
|
||||
PT(GeomVertexData) vdata;
|
||||
PT(Geom) geom;
|
||||
PT(GeomTriangles) prim;
|
||||
|
||||
vdata = new GeomVertexData("", GeomVertexFormat::get_v3c4(), Geom::UH_stream);
|
||||
|
||||
prim = new GeomTriangles(Geom::UH_stream);
|
||||
prim->set_shade_model(Geom::SM_uniform);
|
||||
|
||||
geom = new Geom(vdata);
|
||||
geom->add_primitive(prim);
|
||||
|
||||
add_geom(geom);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -174,101 +145,134 @@ draw_mask_changed() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is some value to visiting this particular node during
|
||||
* the cull traversal for any camera, false otherwise. This will be used to
|
||||
* optimize the result of get_net_draw_show_mask(), so that any subtrees that
|
||||
* contain only nodes for which is_renderable() is false need not be visited.
|
||||
*/
|
||||
bool BulletDebugNode::
|
||||
is_renderable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the node's contents to the CullResult we are building up during the
|
||||
* cull traversal, so that it will be drawn at render time. For most nodes
|
||||
* other than GeomNodes, this is a do-nothing operation.
|
||||
*/
|
||||
void BulletDebugNode::
|
||||
add_for_draw(CullTraverser *trav, CullTraverserData &data) {
|
||||
PT(Geom) debug_lines;
|
||||
PT(Geom) debug_triangles;
|
||||
|
||||
{
|
||||
LightMutexHolder holder(_lock);
|
||||
if (_debug_world == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (_debug_stale) {
|
||||
nassertv(_debug_world != nullptr);
|
||||
PStatTimer timer(_pstat_debug);
|
||||
|
||||
// Collect debug geometry data
|
||||
_drawer._lines.clear();
|
||||
_drawer._triangles.clear();
|
||||
|
||||
_debug_world->debugDrawWorld();
|
||||
|
||||
// Render lines
|
||||
{
|
||||
PT(GeomVertexData) vdata =
|
||||
new GeomVertexData("", GeomVertexFormat::get_v3c4(), Geom::UH_stream);
|
||||
vdata->unclean_set_num_rows(_drawer._lines.size() * 2);
|
||||
|
||||
GeomVertexWriter vwriter(vdata, InternalName::get_vertex());
|
||||
GeomVertexWriter cwriter(vdata, InternalName::get_color());
|
||||
|
||||
pvector<Line>::const_iterator lit;
|
||||
for (lit = _drawer._lines.begin(); lit != _drawer._lines.end(); lit++) {
|
||||
const Line &line = *lit;
|
||||
|
||||
vwriter.set_data3(line._p0);
|
||||
vwriter.set_data3(line._p1);
|
||||
cwriter.set_data4(LVecBase4(line._color));
|
||||
cwriter.set_data4(LVecBase4(line._color));
|
||||
}
|
||||
|
||||
PT(GeomPrimitive) prim = new GeomLines(Geom::UH_stream);
|
||||
prim->set_shade_model(Geom::SM_uniform);
|
||||
prim->add_next_vertices(_drawer._lines.size() * 2);
|
||||
|
||||
debug_lines = new Geom(vdata);
|
||||
debug_lines->add_primitive(prim);
|
||||
_debug_lines = debug_lines;
|
||||
}
|
||||
|
||||
// Render triangles
|
||||
{
|
||||
PT(GeomVertexData) vdata =
|
||||
new GeomVertexData("", GeomVertexFormat::get_v3c4(), Geom::UH_stream);
|
||||
vdata->unclean_set_num_rows(_drawer._triangles.size() * 3);
|
||||
|
||||
GeomVertexWriter vwriter(vdata, InternalName::get_vertex());
|
||||
GeomVertexWriter cwriter(vdata, InternalName::get_color());
|
||||
|
||||
pvector<Triangle>::const_iterator tit;
|
||||
for (tit = _drawer._triangles.begin(); tit != _drawer._triangles.end(); tit++) {
|
||||
const Triangle &tri = *tit;
|
||||
|
||||
vwriter.set_data3(tri._p0);
|
||||
vwriter.set_data3(tri._p1);
|
||||
vwriter.set_data3(tri._p2);
|
||||
cwriter.set_data4(LVecBase4(tri._color));
|
||||
cwriter.set_data4(LVecBase4(tri._color));
|
||||
cwriter.set_data4(LVecBase4(tri._color));
|
||||
}
|
||||
|
||||
PT(GeomPrimitive) prim = new GeomTriangles(Geom::UH_stream);
|
||||
prim->set_shade_model(Geom::SM_uniform);
|
||||
prim->add_next_vertices(_drawer._triangles.size() * 3);
|
||||
|
||||
debug_triangles = new Geom(vdata);
|
||||
debug_triangles->add_primitive(prim);
|
||||
_debug_triangles = debug_triangles;
|
||||
}
|
||||
|
||||
// Clear collected data.
|
||||
_drawer._lines.clear();
|
||||
_drawer._triangles.clear();
|
||||
|
||||
_debug_stale = false;
|
||||
} else {
|
||||
debug_lines = _debug_lines;
|
||||
debug_triangles = _debug_triangles;
|
||||
}
|
||||
}
|
||||
|
||||
// Record them without any state or transform.
|
||||
trav->_geoms_pcollector.add_level(2);
|
||||
{
|
||||
CullableObject *object =
|
||||
new CullableObject(move(debug_lines), RenderState::make_empty(), trav->get_scene()->get_cs_world_transform());
|
||||
trav->get_cull_handler()->record_object(object, trav);
|
||||
}
|
||||
{
|
||||
CullableObject *object =
|
||||
new CullableObject(move(debug_triangles), RenderState::make_empty(), trav->get_scene()->get_cs_world_transform());
|
||||
trav->get_cull_handler()->record_object(object, trav);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void BulletDebugNode::
|
||||
sync_b2p(btDynamicsWorld *world) {
|
||||
LightMutexHolder holder(_lock);
|
||||
|
||||
if (is_overall_hidden()) return;
|
||||
|
||||
nassertv(get_num_geoms() == 2);
|
||||
|
||||
// Collect debug geometry data
|
||||
_drawer._lines.clear();
|
||||
_drawer._triangles.clear();
|
||||
|
||||
world->debugDrawWorld();
|
||||
|
||||
// Get inverse of this node's net transform
|
||||
NodePath np = NodePath::any_path((PandaNode *)this);
|
||||
LMatrix4 m = np.get_net_transform()->get_mat();
|
||||
m.invert_in_place();
|
||||
|
||||
// Render lines
|
||||
{
|
||||
PT(GeomVertexData) vdata;
|
||||
PT(Geom) geom;
|
||||
PT(GeomLines) prim;
|
||||
|
||||
vdata = new GeomVertexData("", GeomVertexFormat::get_v3c4(), Geom::UH_stream);
|
||||
|
||||
prim = new GeomLines(Geom::UH_stream);
|
||||
prim->set_shade_model(Geom::SM_uniform);
|
||||
|
||||
GeomVertexWriter vwriter = GeomVertexWriter(vdata, InternalName::get_vertex());
|
||||
GeomVertexWriter cwriter = GeomVertexWriter(vdata, InternalName::get_color());
|
||||
|
||||
int v = 0;
|
||||
|
||||
pvector<Line>::const_iterator lit;
|
||||
for (lit = _drawer._lines.begin(); lit != _drawer._lines.end(); lit++) {
|
||||
Line line = *lit;
|
||||
|
||||
vwriter.add_data3(m.xform_point(line._p0));
|
||||
vwriter.add_data3(m.xform_point(line._p1));
|
||||
cwriter.add_data4(LVecBase4(line._color));
|
||||
cwriter.add_data4(LVecBase4(line._color));
|
||||
|
||||
prim->add_vertex(v++);
|
||||
prim->add_vertex(v++);
|
||||
prim->close_primitive();
|
||||
}
|
||||
|
||||
geom = new Geom(vdata);
|
||||
geom->add_primitive(prim);
|
||||
|
||||
set_geom(0, geom);
|
||||
}
|
||||
|
||||
// Render triangles
|
||||
{
|
||||
PT(GeomVertexData) vdata;
|
||||
PT(Geom) geom;
|
||||
PT(GeomTriangles) prim;
|
||||
|
||||
vdata = new GeomVertexData("", GeomVertexFormat::get_v3c4(), Geom::UH_stream);
|
||||
|
||||
prim = new GeomTriangles(Geom::UH_stream);
|
||||
prim->set_shade_model(Geom::SM_uniform);
|
||||
|
||||
GeomVertexWriter vwriter = GeomVertexWriter(vdata, InternalName::get_vertex());
|
||||
GeomVertexWriter cwriter = GeomVertexWriter(vdata, InternalName::get_color());
|
||||
|
||||
int v = 0;
|
||||
|
||||
pvector<Triangle>::const_iterator tit;
|
||||
for (tit = _drawer._triangles.begin(); tit != _drawer._triangles.end(); tit++) {
|
||||
Triangle tri = *tit;
|
||||
|
||||
vwriter.add_data3(m.xform_point(tri._p0));
|
||||
vwriter.add_data3(m.xform_point(tri._p1));
|
||||
vwriter.add_data3(m.xform_point(tri._p2));
|
||||
cwriter.add_data4(LVecBase4(tri._color));
|
||||
cwriter.add_data4(LVecBase4(tri._color));
|
||||
cwriter.add_data4(LVecBase4(tri._color));
|
||||
|
||||
prim->add_vertex(v++);
|
||||
prim->add_vertex(v++);
|
||||
prim->add_vertex(v++);
|
||||
prim->close_primitive();
|
||||
}
|
||||
|
||||
geom = new Geom(vdata);
|
||||
geom->add_primitive(prim);
|
||||
|
||||
set_geom(1, geom);
|
||||
}
|
||||
_debug_world = world;
|
||||
_debug_stale = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -431,8 +435,6 @@ register_with_read_factory() {
|
|||
*/
|
||||
void BulletDebugNode::
|
||||
write_datagram(BamWriter *manager, Datagram &dg) {
|
||||
// Don't upcall to GeomNode since we're not interested in storing the actual
|
||||
// debug Geoms in the .bam file.
|
||||
PandaNode::write_datagram(manager, dg);
|
||||
|
||||
dg.add_bool(_wireframe);
|
||||
|
|
@ -464,8 +466,6 @@ make_from_bam(const FactoryParams ¶ms) {
|
|||
*/
|
||||
void BulletDebugNode::
|
||||
fillin(DatagramIterator &scan, BamReader *manager) {
|
||||
// Don't upcall to GeomNode since we're not interested in storing the actual
|
||||
// debug Geoms in the .bam file.
|
||||
PandaNode::fillin(scan, manager);
|
||||
|
||||
_wireframe = scan.get_bool();
|
||||
|
|
|
|||
|
|
@ -17,13 +17,12 @@
|
|||
#include "pandabase.h"
|
||||
|
||||
#include "bullet_includes.h"
|
||||
|
||||
#include "geomNode.h"
|
||||
#include "lightMutex.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class EXPCL_PANDABULLET BulletDebugNode : public GeomNode {
|
||||
class EXPCL_PANDABULLET BulletDebugNode : public PandaNode {
|
||||
|
||||
PUBLISHED:
|
||||
BulletDebugNode(const char *name="debug");
|
||||
|
|
@ -53,6 +52,9 @@ public:
|
|||
virtual bool safe_to_combine_children() const;
|
||||
virtual bool safe_to_flatten_below() const;
|
||||
|
||||
virtual bool is_renderable() const;
|
||||
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data);
|
||||
|
||||
private:
|
||||
void sync_b2p(btDynamicsWorld *world);
|
||||
|
||||
|
|
@ -100,14 +102,22 @@ private:
|
|||
int _mode;
|
||||
};
|
||||
|
||||
LightMutex _lock;
|
||||
DebugDraw _drawer;
|
||||
|
||||
bool _debug_stale;
|
||||
btDynamicsWorld *_debug_world;
|
||||
PT(Geom) _debug_lines;
|
||||
PT(Geom) _debug_triangles;
|
||||
|
||||
bool _wireframe;
|
||||
bool _constraints;
|
||||
bool _bounds;
|
||||
|
||||
friend class BulletWorld;
|
||||
|
||||
static PStatCollector _pstat_debug;
|
||||
|
||||
public:
|
||||
static void register_with_read_factory();
|
||||
virtual void write_datagram(BamWriter *manager, Datagram &dg);
|
||||
|
|
@ -121,9 +131,9 @@ public:
|
|||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
GeomNode::init_type();
|
||||
PandaNode::init_type();
|
||||
register_type(_type_handle, "BulletDebugNode",
|
||||
GeomNode::get_class_type());
|
||||
PandaNode::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
|
|
|
|||
|
|
@ -55,21 +55,12 @@ INLINE BulletWorld::
|
|||
*/
|
||||
INLINE void BulletWorld::
|
||||
set_debug_node(BulletDebugNode *node) {
|
||||
|
||||
nassertv(node);
|
||||
|
||||
_debug = node;
|
||||
_world->setDebugDrawer(&(_debug->_drawer));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void BulletWorld::
|
||||
clear_debug_node() {
|
||||
|
||||
_debug = NULL;
|
||||
_world->setDebugDrawer(NULL);
|
||||
if (node != _debug) {
|
||||
clear_debug_node();
|
||||
_debug = node;
|
||||
_world->setDebugDrawer(&(_debug->_drawer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include "bulletSoftBodyWorldInfo.h"
|
||||
|
||||
#include "collideMask.h"
|
||||
#include "lightMutexHolder.h"
|
||||
|
||||
#define clamp(x, x_min, x_max) max(min(x, x_max), x_min)
|
||||
|
||||
|
|
@ -24,7 +25,6 @@ TypeHandle BulletWorld::_type_handle;
|
|||
|
||||
PStatCollector BulletWorld::_pstat_physics("App:Bullet:DoPhysics");
|
||||
PStatCollector BulletWorld::_pstat_simulation("App:Bullet:DoPhysics:Simulation");
|
||||
PStatCollector BulletWorld::_pstat_debug("App:Bullet:DoPhysics:Debug");
|
||||
PStatCollector BulletWorld::_pstat_p2b("App:Bullet:DoPhysics:SyncP2B");
|
||||
PStatCollector BulletWorld::_pstat_b2p("App:Bullet:DoPhysics:SyncB2P");
|
||||
|
||||
|
|
@ -127,6 +127,19 @@ get_world_info() {
|
|||
return BulletSoftBodyWorldInfo(_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a debug node that has been assigned to this BulletWorld.
|
||||
*/
|
||||
void BulletWorld::
|
||||
clear_debug_node() {
|
||||
if (_debug != nullptr) {
|
||||
LightMutexHolder holder(_debug->_lock);
|
||||
_debug->_debug_world = nullptr;
|
||||
_world->setDebugDrawer(nullptr);
|
||||
_debug = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
@ -184,9 +197,7 @@ do_physics(PN_stdfloat dt, int max_substeps, PN_stdfloat stepsize) {
|
|||
|
||||
// Render debug
|
||||
if (_debug) {
|
||||
_pstat_debug.start();
|
||||
_debug->sync_b2p(_world);
|
||||
_pstat_debug.stop();
|
||||
}
|
||||
|
||||
_pstat_physics.stop();
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ PUBLISHED:
|
|||
|
||||
// Debug
|
||||
INLINE void set_debug_node(BulletDebugNode *node);
|
||||
INLINE void clear_debug_node();
|
||||
void clear_debug_node();
|
||||
INLINE BulletDebugNode *get_debug_node() const;
|
||||
INLINE bool has_debug_node() const;
|
||||
|
||||
|
|
@ -208,7 +208,6 @@ private:
|
|||
|
||||
static PStatCollector _pstat_physics;
|
||||
static PStatCollector _pstat_simulation;
|
||||
static PStatCollector _pstat_debug;
|
||||
static PStatCollector _pstat_p2b;
|
||||
static PStatCollector _pstat_b2p;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
This package contains the animation channels. This defines the various
|
||||
kinds of AnimChannels that may be defined, as well as the MovingPart
|
||||
class which binds to the channels and plays the animation. This is a
|
||||
support library for char, as well as any other libraries that want to
|
||||
define objects whose values change over time.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
This package contains the Cull Traverser. The cull traversal collects
|
||||
all state changes specified, and removes unnecessary state change
|
||||
requests. Also does all the depth sorting for proper alphaing.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
This package defines and manages the data graph, which is the hierarchy
|
||||
of devices, tforms, and any other things which might have an input or
|
||||
an output and need to execute every frame.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
This package contains the abstract display classes, including pipes,
|
||||
windows, channels, and display regions.
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
This package handles all communication with the DirectX backend, and
|
||||
manages state to minimize redundant state changes.
|
||||
|
|
@ -33,6 +33,7 @@ is_alive() const {
|
|||
case S_servicing:
|
||||
case S_sleeping:
|
||||
case S_active_nested:
|
||||
case S_awaiting:
|
||||
return true;
|
||||
|
||||
case S_inactive:
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ PUBLISHED:
|
|||
DS_exit, // stop the enclosing sequence
|
||||
DS_pause, // pause, then exit (useful within a sequence)
|
||||
DS_interrupt, // interrupt the task manager, but run task again
|
||||
DS_await, // await a different task's completion
|
||||
};
|
||||
|
||||
enum State {
|
||||
|
|
@ -54,6 +55,7 @@ PUBLISHED:
|
|||
S_servicing_removed, // Still servicing, but wants removal from manager.
|
||||
S_sleeping,
|
||||
S_active_nested, // active within a sequence.
|
||||
S_awaiting, // Waiting for a dependent task to complete
|
||||
};
|
||||
|
||||
INLINE State get_state() const;
|
||||
|
|
@ -98,6 +100,9 @@ PUBLISHED:
|
|||
|
||||
virtual void output(ostream &out) const;
|
||||
|
||||
EXTENSION(static PyObject *__await__(PyObject *self));
|
||||
EXTENSION(static PyObject *__iter__(PyObject *self));
|
||||
|
||||
protected:
|
||||
void jump_to_task_chain(AsyncTaskManager *manager);
|
||||
DoneStatus unlock_and_do_task();
|
||||
|
|
@ -130,11 +135,16 @@ protected:
|
|||
double _total_dt;
|
||||
int _num_frames;
|
||||
|
||||
// Tasks waiting for this one to complete.
|
||||
pvector<PT(AsyncTask)> _waiting_tasks;
|
||||
|
||||
static AtomicAdjust::Integer _next_task_id;
|
||||
|
||||
static PStatCollector _show_code_pcollector;
|
||||
PStatCollector _task_pcollector;
|
||||
|
||||
friend class PythonTask;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ AsyncTaskChain(AsyncTaskManager *manager, const string &name) :
|
|||
_frame_sync(false),
|
||||
_num_busy_threads(0),
|
||||
_num_tasks(0),
|
||||
_num_awaiting_tasks(0),
|
||||
_state(S_initial),
|
||||
_current_sort(-INT_MAX),
|
||||
_pickup_mode(false),
|
||||
|
|
@ -726,6 +727,13 @@ service_one_task(AsyncTaskChain::AsyncTaskChainThread *thread) {
|
|||
}
|
||||
break;
|
||||
|
||||
case AsyncTask::DS_await:
|
||||
// The task wants to wait for another one to finish.
|
||||
task->_state = AsyncTask::S_awaiting;
|
||||
_cvar.notify_all();
|
||||
++_num_awaiting_tasks;
|
||||
break;
|
||||
|
||||
default:
|
||||
// The task has finished.
|
||||
cleanup_task(task, true, true);
|
||||
|
|
@ -775,6 +783,20 @@ cleanup_task(AsyncTask *task, bool upon_death, bool clean_exit) {
|
|||
|
||||
_manager->remove_task_by_name(task);
|
||||
|
||||
// Activate the tasks that were waiting for this one to finish.
|
||||
if (upon_death) {
|
||||
pvector<PT(AsyncTask)>::iterator it;
|
||||
for (it = task->_waiting_tasks.begin(); it != task->_waiting_tasks.end(); ++it) {
|
||||
AsyncTask *task = *it;
|
||||
// Note that this task may not be on the same task chain.
|
||||
nassertd(task->_manager == _manager) continue;
|
||||
task->_state = AsyncTask::S_active;
|
||||
task->_chain->_active.push_back(task);
|
||||
--task->_chain->_num_awaiting_tasks;
|
||||
}
|
||||
task->_waiting_tasks.clear();
|
||||
}
|
||||
|
||||
if (upon_death) {
|
||||
_manager->_lock.release();
|
||||
task->upon_death(_manager, clean_exit);
|
||||
|
|
@ -899,7 +921,7 @@ finish_sort_group() {
|
|||
filter_timeslice_priority();
|
||||
}
|
||||
|
||||
nassertr((size_t)_num_tasks == _active.size() + _this_active.size() + _next_active.size() + _sleeping.size(), true);
|
||||
nassertr((size_t)_num_tasks == _active.size() + _this_active.size() + _next_active.size() + _sleeping.size() + (size_t)_num_awaiting_tasks, true);
|
||||
make_heap(_active.begin(), _active.end(), AsyncTaskSortPriority());
|
||||
|
||||
_current_sort = -INT_MAX;
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ protected:
|
|||
bool _frame_sync;
|
||||
int _num_busy_threads;
|
||||
int _num_tasks;
|
||||
int _num_awaiting_tasks;
|
||||
TaskHeap _active;
|
||||
TaskHeap _this_active;
|
||||
TaskHeap _next_active;
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ private:
|
|||
friend class AsyncTaskChain::AsyncTaskChainThread;
|
||||
friend class AsyncTask;
|
||||
friend class AsyncTaskSequence;
|
||||
friend class PythonTask;
|
||||
};
|
||||
|
||||
INLINE ostream &operator << (ostream &out, const AsyncTaskManager &manager) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* 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 asyncTask_ext.h
|
||||
* @author rdb
|
||||
* @date 2017-10-29
|
||||
*/
|
||||
|
||||
#include "asyncTask_ext.h"
|
||||
#include "nodePath.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
#ifndef CPPPARSER
|
||||
extern struct Dtool_PyTypedObject Dtool_AsyncTask;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Yields continuously until the task has finished.
|
||||
*/
|
||||
static PyObject *gen_next(PyObject *self) {
|
||||
const AsyncTask *request = nullptr;
|
||||
if (!Dtool_Call_ExtractThisPointer(self, Dtool_AsyncTask, (void **)&request)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (request->is_alive()) {
|
||||
// Continue awaiting the result.
|
||||
Py_INCREF(self);
|
||||
return self;
|
||||
} else {
|
||||
// It's done. Do we have a method like result(), eg. in the case of a
|
||||
// ModelLoadRequest? In that case we pass that value into the exception.
|
||||
PyObject *method = PyObject_GetAttrString(self, "result");
|
||||
PyObject *result = nullptr;
|
||||
if (method != nullptr) {
|
||||
if (PyCallable_Check(method)) {
|
||||
result = _PyObject_CallNoArg(method);
|
||||
Py_DECREF(method);
|
||||
if (result == nullptr) {
|
||||
// An exception happened. Pass it on.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
Py_DECREF(method);
|
||||
}
|
||||
Py_INCREF(PyExc_StopIteration);
|
||||
PyErr_Restore(PyExc_StopIteration, result, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a generator that continuously yields an awaitable until the task
|
||||
* has finished. This allows syntax like `model = await loader.load...` to be
|
||||
* used in a Python coroutine.
|
||||
*/
|
||||
PyObject *Extension<AsyncTask>::
|
||||
__await__(PyObject *self) {
|
||||
Dtool_GeneratorWrapper *gen;
|
||||
gen = (Dtool_GeneratorWrapper *)PyType_GenericAlloc(&Dtool_GeneratorWrapper_Type, 0);
|
||||
if (gen != nullptr) {
|
||||
Py_INCREF(self);
|
||||
gen->_base._self = self;
|
||||
gen->_iternext_func = &gen_next;
|
||||
}
|
||||
return (PyObject *)gen;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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 asyncTask_ext.h
|
||||
* @author rdb
|
||||
* @date 2017-10-29
|
||||
*/
|
||||
|
||||
#ifndef ASYNCTASK_EXT_H
|
||||
#define ASYNCTASK_EXT_H
|
||||
|
||||
#include "extension.h"
|
||||
#include "py_panda.h"
|
||||
#include "modelLoadRequest.h"
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
|
||||
/**
|
||||
* Extension class for AsyncTask
|
||||
*/
|
||||
template<>
|
||||
class Extension<AsyncTask> : public ExtensionBase<AsyncTask> {
|
||||
public:
|
||||
static PyObject *__await__(PyObject *self);
|
||||
static PyObject *__iter__(PyObject *self) { return __await__(self); }
|
||||
};
|
||||
|
||||
#endif // HAVE_PYTHON
|
||||
|
||||
#endif // ASYNCTASK_EXT_H
|
||||
|
|
@ -10,3 +10,52 @@
|
|||
* @author drose
|
||||
* @date 2008-09-16
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the function that is called when the task runs.
|
||||
*/
|
||||
INLINE PyObject *PythonTask::
|
||||
get_function() {
|
||||
Py_INCREF(_function);
|
||||
return _function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the function that is called when the task finishes.
|
||||
*/
|
||||
INLINE PyObject *PythonTask::
|
||||
get_upon_death() {
|
||||
Py_INCREF(_upon_death);
|
||||
return _upon_death;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "owner" object. See set_owner().
|
||||
*/
|
||||
INLINE PyObject *PythonTask::
|
||||
get_owner() const {
|
||||
Py_INCREF(_owner);
|
||||
return _owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the "result" of this task. This is the value returned from an "await"
|
||||
* expression on this task.
|
||||
* This can only be called while the task is still alive.
|
||||
*/
|
||||
INLINE void PythonTask::
|
||||
set_result(PyObject *result) {
|
||||
nassertv(is_alive());
|
||||
nassertv(_exception == nullptr);
|
||||
Py_INCREF(result);
|
||||
Py_XDECREF(_exc_value);
|
||||
_exc_value = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as __await__, for backward compatibility with the old coroutine way.
|
||||
*/
|
||||
INLINE PyObject *PythonTask::
|
||||
__iter__(PyObject *self) {
|
||||
return __await__(self);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,28 +19,52 @@
|
|||
#include "py_panda.h"
|
||||
|
||||
#include "pythonThread.h"
|
||||
#include "asyncTaskManager.h"
|
||||
|
||||
TypeHandle PythonTask::_type_handle;
|
||||
|
||||
#ifndef CPPPARSER
|
||||
extern struct Dtool_PyTypedObject Dtool_TypedReferenceCount;
|
||||
extern struct Dtool_PyTypedObject Dtool_AsyncTask;
|
||||
extern struct Dtool_PyTypedObject Dtool_PythonTask;
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
PythonTask::
|
||||
PythonTask(PyObject *function, const string &name) :
|
||||
AsyncTask(name)
|
||||
{
|
||||
_function = NULL;
|
||||
_args = NULL;
|
||||
_upon_death = NULL;
|
||||
_owner = NULL;
|
||||
_registered_to_owner = false;
|
||||
_generator = NULL;
|
||||
PythonTask(PyObject *func_or_coro, const string &name) :
|
||||
AsyncTask(name),
|
||||
_function(nullptr),
|
||||
_args(nullptr),
|
||||
_upon_death(nullptr),
|
||||
_owner(nullptr),
|
||||
_registered_to_owner(false),
|
||||
_exception(nullptr),
|
||||
_exc_value(nullptr),
|
||||
_exc_traceback(nullptr),
|
||||
_generator(nullptr),
|
||||
_future_done(nullptr),
|
||||
_retrieved_exception(false) {
|
||||
|
||||
nassertv(func_or_coro != nullptr);
|
||||
if (func_or_coro == Py_None || PyCallable_Check(func_or_coro)) {
|
||||
_function = func_or_coro;
|
||||
Py_INCREF(_function);
|
||||
#if PY_VERSION_HEX >= 0x03050000
|
||||
} else if (PyCoro_CheckExact(func_or_coro)) {
|
||||
// We also allow passing in a coroutine, because why not.
|
||||
_generator = func_or_coro;
|
||||
Py_INCREF(_generator);
|
||||
#endif
|
||||
} else if (PyGen_CheckExact(func_or_coro)) {
|
||||
// Something emulating a coroutine.
|
||||
_generator = func_or_coro;
|
||||
Py_INCREF(_generator);
|
||||
} else {
|
||||
nassert_raise("Invalid function passed to PythonTask");
|
||||
}
|
||||
|
||||
set_function(function);
|
||||
set_args(Py_None, true);
|
||||
set_upon_death(Py_None);
|
||||
set_owner(Py_None);
|
||||
|
|
@ -60,9 +84,24 @@ PythonTask(PyObject *function, const string &name) :
|
|||
*/
|
||||
PythonTask::
|
||||
~PythonTask() {
|
||||
Py_DECREF(_function);
|
||||
#ifndef NDEBUG
|
||||
// If the coroutine threw an exception, and there was no opportunity to
|
||||
// handle it, let the user know.
|
||||
if (_exception != nullptr && !_retrieved_exception) {
|
||||
task_cat.error()
|
||||
<< *this << " exception was never retrieved:\n";
|
||||
PyErr_Restore(_exception, _exc_value, _exc_traceback);
|
||||
PyErr_Print();
|
||||
PyErr_Restore(nullptr, nullptr, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
Py_XDECREF(_function);
|
||||
Py_DECREF(_args);
|
||||
Py_DECREF(__dict__);
|
||||
Py_XDECREF(_exception);
|
||||
Py_XDECREF(_exc_value);
|
||||
Py_XDECREF(_exc_traceback);
|
||||
Py_XDECREF(_generator);
|
||||
Py_XDECREF(_owner);
|
||||
Py_XDECREF(_upon_death);
|
||||
|
|
@ -83,15 +122,6 @@ set_function(PyObject *function) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the function that is called when the task runs.
|
||||
*/
|
||||
PyObject *PythonTask::
|
||||
get_function() {
|
||||
Py_INCREF(_function);
|
||||
return _function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the argument list that is passed to the task function. The
|
||||
* parameter should be a tuple or list of arguments, or None to indicate the
|
||||
|
|
@ -166,15 +196,6 @@ set_upon_death(PyObject *upon_death) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the function that is called when the task finishes.
|
||||
*/
|
||||
PyObject *PythonTask::
|
||||
get_upon_death() {
|
||||
Py_INCREF(_upon_death);
|
||||
return _upon_death;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a Python object that serves as the "owner" for the task. This
|
||||
* owner object must have two methods: _addTask() and _clearTask(), which will
|
||||
|
|
@ -212,12 +233,87 @@ set_owner(PyObject *owner) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the "owner" object. See set_owner().
|
||||
* Returns the result of this task's execution, as set by set_result() within
|
||||
* the task or returned from a coroutine added to the task manager. If an
|
||||
* exception occurred within this task, it is raised instead.
|
||||
*/
|
||||
PyObject *PythonTask::
|
||||
get_owner() {
|
||||
Py_INCREF(_owner);
|
||||
return _owner;
|
||||
result() const {
|
||||
nassertr(!is_alive(), nullptr);
|
||||
|
||||
if (_exception == nullptr) {
|
||||
// The result of the call is stored in _exc_value.
|
||||
Py_XINCREF(_exc_value);
|
||||
return _exc_value;
|
||||
} else {
|
||||
_retrieved_exception = true;
|
||||
Py_INCREF(_exception);
|
||||
Py_XINCREF(_exc_value);
|
||||
Py_XINCREF(_exc_traceback);
|
||||
PyErr_Restore(_exception, _exc_value, _exc_traceback);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If an exception occurred during execution of this task, returns it. This
|
||||
* is only set if this task returned a coroutine or generator.
|
||||
*/
|
||||
/*PyObject *PythonTask::
|
||||
exception() const {
|
||||
if (_exception == nullptr) {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
} else if (_exc_value == nullptr || _exc_value == Py_None) {
|
||||
return _PyObject_CallNoArg(_exception);
|
||||
} else if (PyTuple_Check(_exc_value)) {
|
||||
return PyObject_Call(_exception, _exc_value, nullptr);
|
||||
} else {
|
||||
return PyObject_CallFunctionObjArgs(_exception, _exc_value, nullptr);
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Returns an iterator that continuously yields an awaitable until the task
|
||||
* has finished.
|
||||
*/
|
||||
PyObject *PythonTask::
|
||||
__await__(PyObject *self) {
|
||||
Dtool_GeneratorWrapper *gen;
|
||||
gen = (Dtool_GeneratorWrapper *)PyType_GenericAlloc(&Dtool_GeneratorWrapper_Type, 0);
|
||||
if (gen != nullptr) {
|
||||
Py_INCREF(self);
|
||||
gen->_base._self = self;
|
||||
gen->_iternext_func = &gen_next;
|
||||
}
|
||||
return (PyObject *)gen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Yields continuously until a task has finished.
|
||||
*/
|
||||
PyObject *PythonTask::
|
||||
gen_next(PyObject *self) {
|
||||
const PythonTask *task = nullptr;
|
||||
if (!Dtool_Call_ExtractThisPointer(self, Dtool_PythonTask, (void **)&task)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (task->is_alive()) {
|
||||
Py_INCREF(self);
|
||||
return self;
|
||||
} else if (task->_exception != nullptr) {
|
||||
task->_retrieved_exception = true;
|
||||
Py_INCREF(task->_exception);
|
||||
Py_INCREF(task->_exc_value);
|
||||
Py_INCREF(task->_exc_traceback);
|
||||
PyErr_Restore(task->_exception, task->_exc_value, task->_exc_traceback);
|
||||
return nullptr;
|
||||
} else {
|
||||
// The result of the call is stored in _exc_value.
|
||||
PyErr_SetObject(PyExc_StopIteration, task->_exc_value);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -396,16 +492,30 @@ do_task() {
|
|||
*/
|
||||
AsyncTask::DoneStatus PythonTask::
|
||||
do_python_task() {
|
||||
PyObject *result = NULL;
|
||||
PyObject *result = nullptr;
|
||||
|
||||
if (_generator == (PyObject *)NULL) {
|
||||
// Are we waiting for a future to finish?
|
||||
if (_future_done != nullptr) {
|
||||
PyObject *is_done = PyObject_CallObject(_future_done, nullptr);
|
||||
if (!PyObject_IsTrue(is_done)) {
|
||||
// Nope, ask again next frame.
|
||||
Py_DECREF(is_done);
|
||||
return DS_cont;
|
||||
}
|
||||
Py_DECREF(is_done);
|
||||
Py_DECREF(_future_done);
|
||||
_future_done = nullptr;
|
||||
}
|
||||
|
||||
if (_generator == nullptr) {
|
||||
// We are calling the function directly.
|
||||
nassertr(_function != nullptr, DS_interrupt);
|
||||
|
||||
PyObject *args = get_args();
|
||||
result = PythonThread::call_python_func(_function, args);
|
||||
Py_DECREF(args);
|
||||
|
||||
#ifdef PyGen_Check
|
||||
if (result != (PyObject *)NULL && PyGen_Check(result)) {
|
||||
if (result != nullptr && PyGen_Check(result)) {
|
||||
// The function has yielded a generator. We will call into that
|
||||
// henceforth, instead of calling the function from the top again.
|
||||
if (task_cat.is_debug()) {
|
||||
|
|
@ -423,30 +533,166 @@ do_python_task() {
|
|||
Py_DECREF(str);
|
||||
}
|
||||
_generator = result;
|
||||
result = NULL;
|
||||
}
|
||||
result = nullptr;
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03050000
|
||||
} else if (result != nullptr && Py_TYPE(result)->tp_as_async != nullptr) {
|
||||
// The function yielded a coroutine, or something of the sort.
|
||||
if (task_cat.is_debug()) {
|
||||
PyObject *str = PyObject_ASCII(_function);
|
||||
PyObject *str2 = PyObject_ASCII(result);
|
||||
task_cat.debug()
|
||||
<< PyUnicode_AsUTF8(str) << " in " << *this
|
||||
<< " yielded an awaitable: " << PyUnicode_AsUTF8(str2) << "\n";
|
||||
Py_DECREF(str);
|
||||
Py_DECREF(str2);
|
||||
}
|
||||
if (PyCoro_CheckExact(result)) {
|
||||
// If a coroutine, am_await is possible but senseless, since we can
|
||||
// just call send(None) on the coroutine itself.
|
||||
_generator = result;
|
||||
} else {
|
||||
unaryfunc await = Py_TYPE(result)->tp_as_async->am_await;
|
||||
_generator = await(result);
|
||||
Py_DECREF(result);
|
||||
}
|
||||
result = nullptr;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (_generator != (PyObject *)NULL) {
|
||||
// We are calling a generator.
|
||||
PyObject *func = PyObject_GetAttrString(_generator, "next");
|
||||
nassertr(func != (PyObject *)NULL, DS_interrupt);
|
||||
|
||||
result = PyObject_CallObject(func, NULL);
|
||||
if (_generator != nullptr) {
|
||||
// We are calling a generator. Use "send" rather than PyIter_Next since
|
||||
// we need to be able to read the value from a StopIteration exception.
|
||||
PyObject *func = PyObject_GetAttrString(_generator, "send");
|
||||
nassertr(func != nullptr, DS_interrupt);
|
||||
result = PyObject_CallFunctionObjArgs(func, Py_None, nullptr);
|
||||
Py_DECREF(func);
|
||||
|
||||
if (result == (PyObject *)NULL && PyErr_Occurred() &&
|
||||
PyErr_ExceptionMatches(PyExc_StopIteration)) {
|
||||
// "Catch" StopIteration and treat it like DS_done.
|
||||
PyErr_Clear();
|
||||
if (result == nullptr) {
|
||||
// An error happened. If StopIteration, that indicates the task has
|
||||
// returned. Otherwise, we need to save it so that it can be re-raised
|
||||
// in the function that awaited this task.
|
||||
Py_DECREF(_generator);
|
||||
_generator = NULL;
|
||||
return DS_done;
|
||||
_generator = nullptr;
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03030000
|
||||
if (_PyGen_FetchStopIterationValue(&result) == 0) {
|
||||
#else
|
||||
if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
|
||||
result = Py_None;
|
||||
Py_INCREF(result);
|
||||
#endif
|
||||
PyErr_Restore(nullptr, nullptr, nullptr);
|
||||
|
||||
// If we passed a coroutine into the task, eg. something like:
|
||||
// taskMgr.add(my_async_function())
|
||||
// then we cannot rerun the task, so the return value is always
|
||||
// assumed to be DS_done. Instead, we pass the return value to the
|
||||
// result of the `await` expression.
|
||||
if (_function == nullptr) {
|
||||
if (task_cat.is_debug()) {
|
||||
task_cat.debug()
|
||||
<< *this << " received StopIteration from coroutine.\n";
|
||||
}
|
||||
// Store the result in _exc_value because that's not used anyway.
|
||||
Py_XDECREF(_exc_value);
|
||||
_exc_value = result;
|
||||
return DS_done;
|
||||
}
|
||||
} else if (_function == nullptr) {
|
||||
// We got an exception. If this is a scheduled coroutine, we will
|
||||
// keep it and instead throw it into whatever 'awaits' this task.
|
||||
// Otherwise, fall through and handle it the regular way.
|
||||
Py_XDECREF(_exception);
|
||||
Py_XDECREF(_exc_value);
|
||||
Py_XDECREF(_exc_traceback);
|
||||
PyErr_Fetch(&_exception, &_exc_value, &_exc_traceback);
|
||||
_retrieved_exception = false;
|
||||
|
||||
if (task_cat.is_debug()) {
|
||||
if (_exception != nullptr && Py_TYPE(_exception) == &PyType_Type) {
|
||||
task_cat.debug()
|
||||
<< *this << " received " << ((PyTypeObject *)_exception)->tp_name << " from coroutine.\n";
|
||||
} else {
|
||||
task_cat.debug()
|
||||
<< *this << " received exception from coroutine.\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the task chain we want to kill ourselves. It doesn't really
|
||||
// matter what we return if we set S_servicing_removed. If we don't
|
||||
// set it, however, it will think this was a clean exit.
|
||||
_manager->_lock.acquire();
|
||||
_state = S_servicing_removed;
|
||||
_manager->_lock.release();
|
||||
return DS_interrupt;
|
||||
}
|
||||
|
||||
} else if (DtoolCanThisBeAPandaInstance(result)) {
|
||||
// We are waiting for a task to finish.
|
||||
void *ptr = ((Dtool_PyInstDef *)result)->_My_Type->_Dtool_UpcastInterface(result, &Dtool_AsyncTask);
|
||||
if (ptr != nullptr) {
|
||||
// Suspend execution of this task until this other task has completed.
|
||||
AsyncTask *task = (AsyncTask *)ptr;
|
||||
AsyncTaskManager *manager = task->_manager;
|
||||
nassertr(manager != nullptr, DS_interrupt);
|
||||
nassertr(manager == _manager, DS_interrupt);
|
||||
manager->_lock.acquire();
|
||||
if (task != (AsyncTask *)this) {
|
||||
if (task->is_alive()) {
|
||||
if (task_cat.is_debug()) {
|
||||
task_cat.debug()
|
||||
<< *this << " is now awaiting <" << *task << ">.\n";
|
||||
}
|
||||
task->_waiting_tasks.push_back(this);
|
||||
} else {
|
||||
// The task is already done. Continue at next opportunity.
|
||||
Py_DECREF(result);
|
||||
manager->_lock.release();
|
||||
return DS_cont;
|
||||
}
|
||||
} else {
|
||||
// This is an error. If we wanted to be fancier we could also
|
||||
// detect deeper circular dependencies.
|
||||
task_cat.error()
|
||||
<< *this << " cannot await itself\n";
|
||||
}
|
||||
task->_manager->_lock.release();
|
||||
Py_DECREF(result);
|
||||
return DS_await;
|
||||
}
|
||||
|
||||
} else {
|
||||
// We are waiting for a future to finish. We currently implement this
|
||||
// by simply checking every frame whether the future is done.
|
||||
PyObject *check = PyObject_GetAttrString(result, "_asyncio_future_blocking");
|
||||
if (check != nullptr && check != Py_None) {
|
||||
Py_DECREF(check);
|
||||
// Next frame, check whether this future is done.
|
||||
_future_done = PyObject_GetAttrString(result, "done");
|
||||
if (_future_done == nullptr || !PyCallable_Check(_future_done)) {
|
||||
task_cat.error()
|
||||
<< "future.done is not callable\n";
|
||||
return DS_interrupt;
|
||||
}
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
if (task_cat.is_debug()) {
|
||||
PyObject *str = PyObject_ASCII(result);
|
||||
task_cat.debug()
|
||||
<< *this << " is now awaiting " << PyUnicode_AsUTF8(str) << ".\n";
|
||||
Py_DECREF(str);
|
||||
}
|
||||
#endif
|
||||
Py_DECREF(result);
|
||||
return DS_cont;
|
||||
}
|
||||
PyErr_Clear();
|
||||
Py_XDECREF(check);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == (PyObject *)NULL) {
|
||||
if (result == nullptr) {
|
||||
if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit)) {
|
||||
// Don't print an error message for SystemExit. Or rather, make it a
|
||||
// debug message.
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
#include "py_panda.h"
|
||||
|
||||
/**
|
||||
* This class exists to allow association of a Python function with the
|
||||
* AsyncTaskManager.
|
||||
* This class exists to allow association of a Python function or coroutine
|
||||
* with the AsyncTaskManager.
|
||||
*/
|
||||
class PythonTask : public AsyncTask {
|
||||
PUBLISHED:
|
||||
|
|
@ -32,16 +32,23 @@ PUBLISHED:
|
|||
ALLOC_DELETED_CHAIN(PythonTask);
|
||||
|
||||
void set_function(PyObject *function);
|
||||
PyObject *get_function();
|
||||
INLINE PyObject *get_function();
|
||||
|
||||
void set_args(PyObject *args, bool append_task);
|
||||
PyObject *get_args();
|
||||
|
||||
void set_upon_death(PyObject *upon_death);
|
||||
PyObject *get_upon_death();
|
||||
INLINE PyObject *get_upon_death();
|
||||
|
||||
void set_owner(PyObject *owner);
|
||||
PyObject *get_owner();
|
||||
INLINE PyObject *get_owner() const;
|
||||
|
||||
INLINE void set_result(PyObject *result);
|
||||
PyObject *result() const;
|
||||
//PyObject *exception() const;
|
||||
|
||||
static PyObject *__await__(PyObject *self);
|
||||
INLINE static PyObject *__iter__(PyObject *self);
|
||||
|
||||
int __setattr__(PyObject *self, PyObject *attr, PyObject *v);
|
||||
int __delattr__(PyObject *self, PyObject *attr);
|
||||
|
|
@ -94,6 +101,8 @@ protected:
|
|||
virtual void upon_death(AsyncTaskManager *manager, bool clean_exit);
|
||||
|
||||
private:
|
||||
static PyObject *gen_next(PyObject *self);
|
||||
|
||||
void register_to_owner();
|
||||
void unregister_from_owner();
|
||||
void call_owner_method(const char *method_name);
|
||||
|
|
@ -102,12 +111,19 @@ private:
|
|||
private:
|
||||
PyObject *_function;
|
||||
PyObject *_args;
|
||||
bool _append_task;
|
||||
PyObject *_upon_death;
|
||||
PyObject *_owner;
|
||||
bool _registered_to_owner;
|
||||
|
||||
PyObject *_exception;
|
||||
PyObject *_exc_value;
|
||||
PyObject *_exc_traceback;
|
||||
|
||||
PyObject *_generator;
|
||||
PyObject *_future_done;
|
||||
|
||||
bool _append_task;
|
||||
bool _registered_to_owner;
|
||||
mutable bool _retrieved_exception;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ get_orig() const {
|
|||
/**
|
||||
* Returns true if this request has completed, false if it is still pending.
|
||||
* When this returns true, you may retrieve the model loaded by calling
|
||||
* get_result().
|
||||
* result().
|
||||
*/
|
||||
INLINE bool ModelFlattenRequest::
|
||||
is_ready() const {
|
||||
|
|
@ -47,6 +47,20 @@ is_ready() const {
|
|||
*/
|
||||
INLINE PandaNode *ModelFlattenRequest::
|
||||
get_model() const {
|
||||
nassertr(_is_ready, NULL);
|
||||
nassertr(_is_ready, nullptr);
|
||||
return _model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the flattened copy of the model wrapped in a NodePath. It is an
|
||||
* error to call this unless is_ready() returns true.
|
||||
*/
|
||||
INLINE NodePath ModelFlattenRequest::
|
||||
result() const {
|
||||
nassertr(_is_ready, NodePath::fail());
|
||||
if (_model != nullptr) {
|
||||
return NodePath(_model);
|
||||
} else {
|
||||
return NodePath::fail();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include "asyncTask.h"
|
||||
#include "pandaNode.h"
|
||||
#include "pointerTo.h"
|
||||
#include "nodePath.h"
|
||||
|
||||
/**
|
||||
* This class object manages a single asynchronous request to flatten a model.
|
||||
|
|
@ -38,9 +39,10 @@ PUBLISHED:
|
|||
INLINE bool is_ready() const;
|
||||
INLINE PandaNode *get_model() const;
|
||||
|
||||
INLINE NodePath result() const;
|
||||
|
||||
MAKE_PROPERTY(orig, get_orig);
|
||||
MAKE_PROPERTY(ready, is_ready);
|
||||
MAKE_PROPERTY(model, get_model);
|
||||
|
||||
protected:
|
||||
virtual DoneStatus do_task();
|
||||
|
|
|
|||
|
|
@ -37,6 +37,16 @@ get_loader() const {
|
|||
return _loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model that was loaded asynchronously as a NodePath, if any, or
|
||||
* the empty NodePath if there was an error.
|
||||
*/
|
||||
INLINE NodePath ModelLoadRequest::
|
||||
result() const {
|
||||
nassertr_always(_is_ready, NodePath::fail());
|
||||
return NodePath(_model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this request has completed, false if it is still pending.
|
||||
* When this returns true, you may retrieve the model loaded by calling
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
* @date 2006-08-29
|
||||
*/
|
||||
|
||||
#ifndef MODELLOADREQUEST
|
||||
#define MODELLOADREQUEST
|
||||
#ifndef MODELLOADREQUEST_H
|
||||
#define MODELLOADREQUEST_H
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
#include "pandaNode.h"
|
||||
#include "pointerTo.h"
|
||||
#include "loader.h"
|
||||
#include "nodePath.h"
|
||||
|
||||
/**
|
||||
* A class object that manages a single asynchronous model load request.
|
||||
|
|
@ -42,6 +43,8 @@ PUBLISHED:
|
|||
INLINE const LoaderOptions &get_options() const;
|
||||
INLINE Loader *get_loader() const;
|
||||
|
||||
INLINE NodePath result() const;
|
||||
|
||||
INLINE bool is_ready() const;
|
||||
INLINE PandaNode *get_model() const;
|
||||
|
||||
|
|
@ -49,7 +52,6 @@ PUBLISHED:
|
|||
MAKE_PROPERTY(options, get_options);
|
||||
MAKE_PROPERTY(loader, get_loader);
|
||||
MAKE_PROPERTY(ready, is_ready);
|
||||
MAKE_PROPERTY(model, get_model);
|
||||
|
||||
protected:
|
||||
virtual DoneStatus do_task();
|
||||
|
|
|
|||
|
|
@ -64,3 +64,13 @@ get_success() const {
|
|||
nassertr(_is_ready, false);
|
||||
return _success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether the model saved correctly. It is an
|
||||
* error to call this unless is_ready() returns true.
|
||||
*/
|
||||
INLINE bool ModelSaveRequest::
|
||||
result() const {
|
||||
nassertr(_is_ready, false);
|
||||
return _success;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
* @date 2012-12-19
|
||||
*/
|
||||
|
||||
#ifndef MODELSAVEREQUEST
|
||||
#define MODELSAVEREQUEST
|
||||
#ifndef MODELSAVEREQUEST_H
|
||||
#define MODELSAVEREQUEST_H
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
|
|
@ -46,12 +46,13 @@ PUBLISHED:
|
|||
INLINE bool is_ready() const;
|
||||
INLINE bool get_success() const;
|
||||
|
||||
INLINE bool result() const;
|
||||
|
||||
MAKE_PROPERTY(filename, get_filename);
|
||||
MAKE_PROPERTY(options, get_options);
|
||||
MAKE_PROPERTY(node, get_node);
|
||||
MAKE_PROPERTY(loader, get_loader);
|
||||
MAKE_PROPERTY(ready, is_ready);
|
||||
MAKE_PROPERTY(success, get_success);
|
||||
|
||||
protected:
|
||||
virtual DoneStatus do_task();
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include "shader.h"
|
||||
#include "texture.h"
|
||||
#include "shaderBuffer.h"
|
||||
#include "extension.h"
|
||||
|
||||
/**
|
||||
* This is a small container class that can hold any one of the value types
|
||||
|
|
|
|||
|
|
@ -1417,7 +1417,10 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) {
|
|||
text << "\t result.rgb = lerp(result, tex" << i << ", tex" << i << ".a).rgb;\n";
|
||||
break;
|
||||
case TextureStage::M_blend:
|
||||
text << "\t result.rgb = lerp(result, tex" << i << " * texcolor_" << i << ", tex" << i << ".r).rgb;\n";
|
||||
text << "\t result.rgb = lerp(result.rgb, texcolor_" << i << ".rgb, tex" << i << ".rgb);\n";
|
||||
if (key._calc_primary_alpha) {
|
||||
text << "\t result.a *= tex" << i << ".a;\n";
|
||||
}
|
||||
break;
|
||||
case TextureStage::M_replace:
|
||||
text << "\t result = tex" << i << ";\n";
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@
|
|||
#include "panda_getopt.h"
|
||||
#include "preprocess_argv.h"
|
||||
#include "graphicsPipeSelection.h"
|
||||
#include "asyncTaskManager.h"
|
||||
#include "asyncTask.h"
|
||||
#include "boundingSphere.h"
|
||||
|
||||
// By including checkPandaVersion.h, we guarantee that runtime attempts to run
|
||||
// pview will fail if it inadvertently links with the wrong version of
|
||||
|
|
@ -231,6 +234,111 @@ report_version() {
|
|||
nout << "\n";
|
||||
}
|
||||
|
||||
// Task that dynamically adjusts the camera len's near/far clipping
|
||||
// planes to ensure the user can zoom in as close as needed to a model.
|
||||
//
|
||||
// Code adapted from WindowFramework::center_trackball(), but
|
||||
// without moving the camera. When the camera is inside the model,
|
||||
// the near clip is set to near-zero.
|
||||
//
|
||||
class AdjustCameraClipPlanesTask : public AsyncTask {
|
||||
public:
|
||||
AdjustCameraClipPlanesTask(const string &name, Camera *camera) :
|
||||
AsyncTask(name), _camera(camera), _lens(camera->get_lens(0)), _sphere(NULL)
|
||||
{
|
||||
NodePath np = framework.get_models();
|
||||
PT(BoundingVolume) volume = np.get_bounds();
|
||||
|
||||
// We expect at least a geometric bounding volume around the world.
|
||||
nassertv(volume != (BoundingVolume *)NULL);
|
||||
nassertv(volume->is_of_type(GeometricBoundingVolume::get_class_type()));
|
||||
CPT(GeometricBoundingVolume) gbv = DCAST(GeometricBoundingVolume, volume);
|
||||
|
||||
if (np.has_parent()) {
|
||||
CPT(TransformState) net_transform = np.get_parent().get_net_transform();
|
||||
PT(GeometricBoundingVolume) new_gbv = DCAST(GeometricBoundingVolume, gbv->make_copy());
|
||||
new_gbv->xform(net_transform->get_mat());
|
||||
gbv = new_gbv;
|
||||
}
|
||||
|
||||
// Determine the bounding sphere around the object.
|
||||
if (gbv->is_infinite()) {
|
||||
framework_cat.warning()
|
||||
<< "Infinite bounding volume for " << np << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (gbv->is_empty()) {
|
||||
framework_cat.warning()
|
||||
<< "Empty bounding volume for " << np << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// The BoundingVolume might be a sphere (it's likely), but since it
|
||||
// might not, we'll take no chances and make our own sphere.
|
||||
_sphere = new BoundingSphere(gbv->get_approx_center(), 0.0f);
|
||||
if (!_sphere->extend_by(gbv)) {
|
||||
framework_cat.warning()
|
||||
<< "Cannot determine bounding volume of " << np << "\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
ALLOC_DELETED_CHAIN(AdjustCameraClipPlanesTask);
|
||||
|
||||
virtual DoneStatus do_task() {
|
||||
if (!_sphere) {
|
||||
return DS_done;
|
||||
}
|
||||
|
||||
if (framework.get_num_windows() == 0) {
|
||||
return DS_cont;
|
||||
}
|
||||
|
||||
WindowFramework *wf = framework.get_window(0);
|
||||
if (!wf) {
|
||||
return DS_cont;
|
||||
}
|
||||
|
||||
// Get current camera position.
|
||||
NodePath cameraNP = wf->get_camera_group();
|
||||
LPoint3 pos = cameraNP.get_pos();
|
||||
|
||||
// See how far or close the camera is
|
||||
LPoint3 center = _sphere->get_center();
|
||||
PN_stdfloat radius = _sphere->get_radius();
|
||||
|
||||
PN_stdfloat min_distance = 0.001 * radius;
|
||||
|
||||
// Choose a suitable distance to view the whole volume in our frame.
|
||||
// This is based on the camera lens in use.
|
||||
PN_stdfloat distance;
|
||||
CPT(GeometricBoundingVolume) gbv = DCAST(GeometricBoundingVolume, _sphere);
|
||||
if (gbv->contains(pos)) {
|
||||
// See as up-close to the model as possible
|
||||
distance = min_distance;
|
||||
} else {
|
||||
// View from a distance
|
||||
distance = (center - pos).length();
|
||||
}
|
||||
|
||||
// Ensure the far plane is far enough back to see the entire object.
|
||||
PN_stdfloat ideal_far_plane = distance + radius * 1.5;
|
||||
_lens->set_far(max(_lens->get_default_far(), ideal_far_plane));
|
||||
|
||||
// And that the near plane is far enough forward, but if inside
|
||||
// the sphere, keep above 0.
|
||||
PN_stdfloat ideal_near_plane = max(min_distance * 10, distance - radius);
|
||||
_lens->set_near(min(_lens->get_default_near(), ideal_near_plane));
|
||||
|
||||
return DS_cont;
|
||||
}
|
||||
|
||||
Camera *_camera;
|
||||
Lens *_lens;
|
||||
PT(BoundingSphere) _sphere;
|
||||
};
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char **argv) {
|
||||
preprocess_argv(argc, argv);
|
||||
|
|
@ -383,6 +491,9 @@ main(int argc, char **argv) {
|
|||
window->set_anim_controls(true);
|
||||
}
|
||||
|
||||
PT(AdjustCameraClipPlanesTask) task = new AdjustCameraClipPlanesTask("Adjust Camera Bounds", window->get_camera(0));
|
||||
framework.get_task_mgr().add(task);
|
||||
|
||||
framework.enable_default_keys();
|
||||
framework.define_key("shift-w", "open a new window", event_W, NULL);
|
||||
framework.define_key("shift-f", "flatten hierarchy", event_F, NULL);
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,38 @@
|
|||
body
|
||||
{
|
||||
font-family: "Perfect DOS VGA 437";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
// use all the allocated texture space
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
|
||||
text#content
|
||||
{
|
||||
z-index: 2;
|
||||
font-size: 30px;
|
||||
|
||||
white-space: pre-wrap;
|
||||
|
||||
margin: auto;
|
||||
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
|
||||
// account for non-proportionality of our 1024x512
|
||||
// buffer compared with VGA font proportions and
|
||||
// wanting to center the screen with 40 columns
|
||||
top: 16px;
|
||||
left: 32px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
color: #888;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<rml>
|
||||
|
||||
<head>
|
||||
<title>Administrative Console</title>
|
||||
<link type="text/rcss" href="console.rcss"/>
|
||||
</head>
|
||||
<!-- events are bound strictly from code -->
|
||||
<body >
|
||||
<text id="content" />
|
||||
</body>
|
||||
</rml>
|
||||
|
|
@ -0,0 +1 @@
|
|||
from www.dafont.com/perfect-dos-vga-437.font (info at http://zehfernando.com/2015/revisiting-vga-fonts/)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<rml>
|
||||
<head >
|
||||
<title>Main Menu</title>
|
||||
<link type="text/template" href="window.rml" />
|
||||
<style>
|
||||
body
|
||||
{
|
||||
width: 400px;
|
||||
height: 225px;
|
||||
|
||||
margin: auto;
|
||||
|
||||
background-color: #44f;
|
||||
}
|
||||
|
||||
|
||||
div#title_bar_content
|
||||
{
|
||||
font-size: 48;
|
||||
|
||||
//text-align: left;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
//vertical-align: center;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import _rocketcore as rocket
|
||||
|
||||
# This handler overrides the 'onkeydown' handler from the template
|
||||
def OnKeyDown(event, document):
|
||||
keyId = event.parameters['key_identifier']
|
||||
if keyId in [ rocket.key_identifier.RETURN,
|
||||
rocket.key_identifier.ESCAPE,
|
||||
rocket.key_identifier.SPACE ]:
|
||||
FireClosing(document)
|
||||
|
||||
# custom event
|
||||
def FireClosing(document):
|
||||
document.DispatchEvent("aboutToClose", { }, False)
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body id='window' template="window" onclick='FireClosing(document)'>
|
||||
<div id="title_bar_content" >
|
||||
<label id="loadingLabel">Loading...</label>
|
||||
</div>
|
||||
</body>
|
||||
</rml>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
ModeNine
|
||||
|
||||
Based on Andrew Bulhak's ModeSeven, in turn inspired by the screen
|
||||
output of the BBC Micro.
|
||||
|
||||
copyright:
|
||||
(C) 1998 Andrew C. Bulhak
|
||||
(C) 2001 Graham H Freeman
|
||||
|
||||
Freely Distributable.
|
||||
|
||||
All we ask is that this readme file must be included with the font package.
|
||||
Impresarios of free font sites and shovelware cd-roms, this means you!
|
||||
|
||||
If you think this font is doovy, let us know at fonts@grudnuk.com, and we
|
||||
might actually make more fonts.
|
||||
|
||||
Another fine Grudnuk Creations produkt | http://grudnuk.com/
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
This is an edited version of this file from blendswap.com (http://www.blendswap.com/blends/view/74468).
|
||||
|
||||
VERY IMPORTANT LICENSE INFORMATION:
|
||||
|
||||
This file has been released by buzo under the following license:
|
||||
|
||||
Creative Commons Zero (Public Domain)
|
||||
|
||||
You can use this model for any purposes according to the following conditions:
|
||||
|
||||
There are no requirements for CC-Zero licensed blends.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Default styles for all the basic elements.
|
||||
*/
|
||||
|
||||
div
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
p
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
em
|
||||
{
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
strong
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
datagrid
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
select, dataselect, datacombo
|
||||
{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tabset tabs
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
<CoordinateSystem> { Z-up }
|
||||
<Material> takeyga_kb {
|
||||
<Scalar> diffr { 0.800000 }
|
||||
<Scalar> diffg { 0.800000 }
|
||||
<Scalar> diffb { 0.800000 }
|
||||
<Scalar> specr { 0.500000 }
|
||||
<Scalar> specg { 0.500000 }
|
||||
<Scalar> specb { 0.500000 }
|
||||
<Scalar> shininess { 12.5 }
|
||||
<Scalar> ambr { 1.000000 }
|
||||
<Scalar> ambg { 1.000000 }
|
||||
<Scalar> ambb { 1.000000 }
|
||||
<Scalar> emitr { 0.000000 }
|
||||
<Scalar> emitg { 0.000000 }
|
||||
<Scalar> emitb { 0.000000 }
|
||||
}
|
||||
|
||||
<Texture> Texture.001 {
|
||||
"./tex/takeyga_kb_specular.dds"
|
||||
<Scalar> envtype { MODULATE }
|
||||
<Scalar> minfilter { LINEAR_MIPMAP_LINEAR }
|
||||
<Scalar> magfilter { LINEAR_MIPMAP_LINEAR }
|
||||
<Scalar> wrap { REPEAT }
|
||||
}
|
||||
|
||||
<Texture> Tex {
|
||||
"./tex/takeyga_kb_diffuse.dds"
|
||||
<Scalar> envtype { MODULATE }
|
||||
<Scalar> minfilter { LINEAR_MIPMAP_LINEAR }
|
||||
<Scalar> magfilter { LINEAR_MIPMAP_LINEAR }
|
||||
<Scalar> wrap { REPEAT }
|
||||
}
|
||||
|
||||
<Texture> Texture {
|
||||
"./tex/takeyga_kb_normal.dds"
|
||||
<Scalar> envtype { NORMAL }
|
||||
<Scalar> minfilter { LINEAR_MIPMAP_LINEAR }
|
||||
<Scalar> magfilter { LINEAR_MIPMAP_LINEAR }
|
||||
<Scalar> wrap { REPEAT }
|
||||
}
|
||||
|
||||
<Group> Cube.001 {
|
||||
<Transform> {
|
||||
<Matrix4> {
|
||||
0.08499996364116669 0.0 0.0 0.0
|
||||
0.0 0.23999987542629242 0.0 0.0
|
||||
0.0 0.0 0.02500000037252903 0.0
|
||||
0.0 0.0 0.0 1.0
|
||||
}
|
||||
}
|
||||
|
||||
<VertexPool> Cube.001 {
|
||||
|
||||
<Vertex> 0 {0.082953 0.240000 -0.025000
|
||||
<UV> {
|
||||
0.158203125 0.595703125
|
||||
}
|
||||
}
|
||||
<Vertex> 1 {0.082953 -0.240000 -0.025000
|
||||
<UV> {
|
||||
0.98828125 0.6015625
|
||||
}
|
||||
}
|
||||
<Vertex> 2 {-0.085000 -0.240000 -0.025000
|
||||
<UV> {
|
||||
0.98828125 0.986328125
|
||||
}
|
||||
}
|
||||
<Vertex> 3 {-0.085000 0.240000 -0.025000
|
||||
<UV> {
|
||||
0.162109375 0.98828125
|
||||
}
|
||||
}
|
||||
<Vertex> 4 {0.073544 0.227744 -0.005546
|
||||
<UV> {
|
||||
0.990234375 0.017578125
|
||||
}
|
||||
}
|
||||
<Vertex> 5 {-0.067932 0.223167 0.018996
|
||||
<UV> {
|
||||
0.98046875 0.46875
|
||||
}
|
||||
}
|
||||
<Vertex> 6 {-0.067932 -0.223167 0.018996
|
||||
<UV> {
|
||||
0.017578125 0.470703125
|
||||
}
|
||||
}
|
||||
<Vertex> 7 {0.073544 -0.227744 -0.005546
|
||||
<UV> {
|
||||
0.01953125 0.013671875
|
||||
}
|
||||
}
|
||||
<Vertex> 8 {0.082372 0.237328 -0.015273
|
||||
<UV> {
|
||||
0.9794921875 0.552734375
|
||||
}
|
||||
}
|
||||
<Vertex> 9 {0.073544 0.227744 -0.005546
|
||||
<UV> {
|
||||
0.9765625 0.57421875
|
||||
}
|
||||
}
|
||||
<Vertex> 10 {0.073544 -0.227744 -0.005546
|
||||
<UV> {
|
||||
0.025390625 0.57421875
|
||||
}
|
||||
}
|
||||
<Vertex> 11 {0.082372 -0.237328 -0.015273
|
||||
<UV> {
|
||||
0.0234375 0.5537109375
|
||||
}
|
||||
}
|
||||
<Vertex> 12 {0.082372 -0.237328 -0.015273
|
||||
<UV> {
|
||||
0.0703125 0.6123046875
|
||||
}
|
||||
}
|
||||
<Vertex> 13 {0.073544 -0.227744 -0.005546
|
||||
<UV> {
|
||||
0.095703125 0.615234375
|
||||
}
|
||||
}
|
||||
<Vertex> 14 {-0.067932 -0.223167 0.018996
|
||||
<UV> {
|
||||
0.13671875 0.97265625
|
||||
}
|
||||
}
|
||||
<Vertex> 15 {-0.080884 -0.235006 0.001122
|
||||
<UV> {
|
||||
0.07421875 0.982421875
|
||||
}
|
||||
}
|
||||
<Vertex> 16 {-0.080884 -0.235006 0.001122
|
||||
<UV> {
|
||||
0.0166015625 0.5361328125
|
||||
}
|
||||
}
|
||||
<Vertex> 17 {-0.067932 -0.223167 0.018996
|
||||
<UV> {
|
||||
0.017578125 0.58203125
|
||||
}
|
||||
}
|
||||
<Vertex> 18 {-0.067932 0.223167 0.018996
|
||||
<UV> {
|
||||
0.978515625 0.580078125
|
||||
}
|
||||
}
|
||||
<Vertex> 19 {-0.080884 0.235006 0.001122
|
||||
<UV> {
|
||||
0.984375 0.5341796875
|
||||
}
|
||||
}
|
||||
<Vertex> 20 {0.082372 0.237328 -0.015273
|
||||
<UV> {
|
||||
0.0703125 0.6123046875
|
||||
}
|
||||
}
|
||||
<Vertex> 21 {0.082953 0.240000 -0.025000
|
||||
<UV> {
|
||||
0.044921875 0.609375
|
||||
}
|
||||
}
|
||||
<Vertex> 22 {-0.085000 0.240000 -0.025000
|
||||
<UV> {
|
||||
0.01171875 0.9921875
|
||||
}
|
||||
}
|
||||
<Vertex> 23 {-0.080884 0.235006 0.001122
|
||||
<UV> {
|
||||
0.07421875 0.982421875
|
||||
}
|
||||
}
|
||||
<Vertex> 24 {0.082953 0.240000 -0.025000
|
||||
<UV> {
|
||||
0.982421875 0.53125
|
||||
}
|
||||
}
|
||||
<Vertex> 25 {0.082372 0.237328 -0.015273
|
||||
<UV> {
|
||||
0.9794921875 0.552734375
|
||||
}
|
||||
}
|
||||
<Vertex> 26 {0.082372 -0.237328 -0.015273
|
||||
<UV> {
|
||||
0.0234375 0.5537109375
|
||||
}
|
||||
}
|
||||
<Vertex> 27 {0.082953 -0.240000 -0.025000
|
||||
<UV> {
|
||||
0.021484375 0.533203125
|
||||
}
|
||||
}
|
||||
<Vertex> 28 {0.082953 -0.240000 -0.025000
|
||||
<UV> {
|
||||
0.044921875 0.609375
|
||||
}
|
||||
}
|
||||
<Vertex> 29 {0.082372 -0.237328 -0.015273
|
||||
<UV> {
|
||||
0.0703125 0.6123046875
|
||||
}
|
||||
}
|
||||
<Vertex> 30 {-0.080884 -0.235006 0.001122
|
||||
<UV> {
|
||||
0.07421875 0.982421875
|
||||
}
|
||||
}
|
||||
<Vertex> 31 {-0.085000 -0.240000 -0.025000
|
||||
<UV> {
|
||||
0.01171875 0.9921875
|
||||
}
|
||||
}
|
||||
<Vertex> 32 {-0.085000 -0.240000 -0.025000
|
||||
<UV> {
|
||||
0.015625 0.490234375
|
||||
}
|
||||
}
|
||||
<Vertex> 33 {-0.080884 -0.235006 0.001122
|
||||
<UV> {
|
||||
0.0166015625 0.5361328125
|
||||
}
|
||||
}
|
||||
<Vertex> 34 {-0.080884 0.235006 0.001122
|
||||
<UV> {
|
||||
0.984375 0.5341796875
|
||||
}
|
||||
}
|
||||
<Vertex> 35 {-0.085000 0.240000 -0.025000
|
||||
<UV> {
|
||||
0.990234375 0.48828125
|
||||
}
|
||||
}
|
||||
<Vertex> 36 {0.073544 0.227744 -0.005546
|
||||
<UV> {
|
||||
0.095703125 0.615234375
|
||||
}
|
||||
}
|
||||
<Vertex> 37 {0.082372 0.237328 -0.015273
|
||||
<UV> {
|
||||
0.0703125 0.6123046875
|
||||
}
|
||||
}
|
||||
<Vertex> 38 {-0.080884 0.235006 0.001122
|
||||
<UV> {
|
||||
0.07421875 0.982421875
|
||||
}
|
||||
}
|
||||
<Vertex> 39 {-0.067932 0.223167 0.018996
|
||||
<UV> {
|
||||
0.13671875 0.97265625
|
||||
}
|
||||
}}
|
||||
|
||||
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {0.000000 0.000000 -1.000000}
|
||||
<VertexRef> { 0 1 2 3 <Ref> { Cube.001 }}
|
||||
}
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {0.508027 -0.000000 0.861341}
|
||||
<VertexRef> { 4 5 6 7 <Ref> { Cube.001 }}
|
||||
}
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {0.966167 -0.000000 0.257917}
|
||||
<VertexRef> { 8 9 10 11 <Ref> { Cube.001 }}
|
||||
}
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {0.028240 -0.996449 0.079322}
|
||||
<VertexRef> { 12 13 14 15 <Ref> { Cube.001 }}
|
||||
}
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {-0.978036 0.000000 0.208436}
|
||||
<VertexRef> { 16 17 18 19 <Ref> { Cube.001 }}
|
||||
}
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {0.001260 0.999752 0.022233}
|
||||
<VertexRef> { 20 21 22 23 <Ref> { Cube.001 }}
|
||||
}
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {0.999846 -0.000000 0.017550}
|
||||
<VertexRef> { 24 25 26 27 <Ref> { Cube.001 }}
|
||||
}
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {0.001259 -0.999752 0.022233}
|
||||
<VertexRef> { 28 29 30 31 <Ref> { Cube.001 }}
|
||||
}
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {-0.998928 0.000000 0.046291}
|
||||
<VertexRef> { 32 33 34 35 <Ref> { Cube.001 }}
|
||||
}
|
||||
<Polygon> {
|
||||
<TRef> { Tex }
|
||||
<MRef> { takeyga_kb }
|
||||
<Normal> {0.028241 0.996449 0.079322}
|
||||
<VertexRef> { 36 37 38 39 <Ref> { Cube.001 }}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,56 @@
|
|||
body
|
||||
{
|
||||
font-family: "MODENINE";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 15;
|
||||
|
||||
}
|
||||
|
||||
body.window
|
||||
{
|
||||
padding-top: 43px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
min-width: 250px;
|
||||
|
||||
min-height: 135px;
|
||||
max-height: 700px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
div#title_bar
|
||||
{
|
||||
z-index: 1;
|
||||
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
|
||||
text-align: center;
|
||||
|
||||
color: #fff;
|
||||
background-color: #22f;
|
||||
}
|
||||
|
||||
|
||||
div#title_bar span
|
||||
{
|
||||
padding-top: 17px;
|
||||
padding-bottom: 48px;
|
||||
|
||||
font-size: 32;
|
||||
font-weight: bold;
|
||||
|
||||
outline-font-effect: outline;
|
||||
outline-width: 1px;
|
||||
outline-color: black;
|
||||
}
|
||||
|
||||
div#title_bar_content
|
||||
{
|
||||
text-align: center;
|
||||
color: #cff;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<template name="window" content="content">
|
||||
<head>
|
||||
<link type="text/rcss" href="rkt.rcss"/>
|
||||
<link type="text/rcss" href="window.rcss"/>
|
||||
|
||||
|
||||
<script>
|
||||
import _rocketcore as rocket
|
||||
|
||||
def OnLoad(document):
|
||||
print "Rocket document loaded"
|
||||
|
||||
# event handlers from templates can be overridden in windows using them
|
||||
def OnKeyDown(event, document):
|
||||
keyId = event.parameters['key_identifier']
|
||||
print "Base keydown: unhandled key ",keyId
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
<body class="window" onload='OnLoad(document)' onkeydown='OnKeyDown(event, document)'>
|
||||
<div id="title_bar">
|
||||
<handle move_target="#document">
|
||||
<span id="title">Rocket Sample</span>
|
||||
|
||||
<div id="title_bar_content">
|
||||
</div>
|
||||
</handle>
|
||||
</div>
|
||||
<div id="window">
|
||||
<div id="content">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- drag and drop of window -->
|
||||
<handle size_target="#document"
|
||||
style="position: absolute; width: 16px; height: 16px; bottom: 0px; right: 0px;">
|
||||
</handle>
|
||||
</body>
|
||||
|
||||
</template>
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
"""
|
||||
Simple console widget for rocket
|
||||
"""
|
||||
import sys, os.path
|
||||
|
||||
# workaround: https://www.panda3d.org/forums/viewtopic.php?t=10062&p=99697#p99054
|
||||
#from panda3d import rocket
|
||||
import _rocketcore as rocket
|
||||
|
||||
from panda3d.rocket import RocketRegion, RocketInputHandler
|
||||
|
||||
class Console(object):
|
||||
def __init__(self, base, context, cols, rows, commandHandler):
|
||||
self.base = base
|
||||
|
||||
self.context = context
|
||||
self.loadFonts()
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
self.commandHandler = commandHandler
|
||||
|
||||
self.setupConsole()
|
||||
self.allowEditing(True)
|
||||
|
||||
def getTextContainer(self):
|
||||
return self.textEl
|
||||
|
||||
def setPrompt(self, prompt):
|
||||
self.consolePrompt = prompt
|
||||
|
||||
def allowEditing(self, editMode):
|
||||
self.editMode = editMode
|
||||
if editMode:
|
||||
self.input = ""
|
||||
if not self.lastLine:
|
||||
self.addLine("")
|
||||
self.newEditLine()
|
||||
|
||||
def loadFonts(self):
|
||||
rocket.LoadFontFace("Perfect DOS VGA 437.ttf")
|
||||
|
||||
def setupConsole(self):
|
||||
self.document = self.context.LoadDocument("console.rml")
|
||||
if not self.document:
|
||||
raise AssertionError("did not find console.rml")
|
||||
|
||||
el = self.document.GetElementById('content')
|
||||
|
||||
self.textEl = el
|
||||
|
||||
# roundabout way of accessing the current object through rocket event...
|
||||
|
||||
# add attribute to let Rocket know about the receiver
|
||||
self.context.console = self
|
||||
|
||||
# then reference through the string format (dunno how else to get the event...)
|
||||
self.document.AddEventListener(
|
||||
'keydown', 'document.context.console.handleKeyDown(event)', True)
|
||||
self.document.AddEventListener(
|
||||
'textinput', 'document.context.console.handleTextInput(event)', True)
|
||||
|
||||
self.consolePrompt = "C:\\>"
|
||||
|
||||
self.input = ""
|
||||
self.lastLine = None
|
||||
|
||||
self.blinkState = False
|
||||
self.queueBlinkCursor()
|
||||
|
||||
self.document.Show()
|
||||
|
||||
def queueBlinkCursor(self):
|
||||
self.base.taskMgr.doMethodLater(0.2, self.blinkCursor, 'blinkCursor')
|
||||
|
||||
def blinkCursor(self, task):
|
||||
self.blinkState = not self.blinkState
|
||||
if self.editMode:
|
||||
self.updateEditLine(self.input)
|
||||
self.queueBlinkCursor()
|
||||
|
||||
def escape(self, text):
|
||||
return text. \
|
||||
replace('<', '<'). \
|
||||
replace('>', '>'). \
|
||||
replace('"', '"')
|
||||
|
||||
def addLine(self, text):
|
||||
curKids = list(self.textEl.child_nodes)
|
||||
while len(curKids) >= self.rows:
|
||||
self.textEl.RemoveChild(curKids[0])
|
||||
curKids = curKids[1:]
|
||||
|
||||
line = self.document.CreateTextNode(self.escape(text) + '\n')
|
||||
self.textEl.AppendChild(line)
|
||||
self.lastLine = line
|
||||
|
||||
def addLines(self, lines):
|
||||
for line in lines:
|
||||
self.addLine(line)
|
||||
|
||||
def updateEditLine(self, newInput=''):
|
||||
newText = self.consolePrompt + newInput
|
||||
self.lastLine.text = self.escape(newText) + (self.blinkState and '_' or '')
|
||||
self.input = newInput
|
||||
|
||||
def scroll(self):
|
||||
self.blinkState = False
|
||||
self.updateEditLine(self.input + '\n')
|
||||
|
||||
def handleKeyDown(self, event):
|
||||
"""
|
||||
Handle control keys
|
||||
"""
|
||||
keyId = event.parameters['key_identifier']
|
||||
if not self.editMode:
|
||||
if keyId == rocket.key_identifier.PAUSE:
|
||||
if event.parameters['ctrl_key']:
|
||||
self.commandHandler(None)
|
||||
|
||||
return
|
||||
|
||||
if keyId == rocket.key_identifier.RETURN:
|
||||
# emit line without cursor
|
||||
self.scroll()
|
||||
|
||||
# handle command
|
||||
self.commandHandler(self.input)
|
||||
|
||||
if self.editMode:
|
||||
# start with new "command"
|
||||
self.addLine(self.consolePrompt)
|
||||
self.updateEditLine("")
|
||||
|
||||
elif keyId == rocket.key_identifier.BACK:
|
||||
self.updateEditLine(self.input[0:-1])
|
||||
|
||||
def handleTextInput(self, event):
|
||||
if not self.editMode:
|
||||
return
|
||||
|
||||
# handle normal text character
|
||||
data = event.parameters['data']
|
||||
if 32 <= data < 128:
|
||||
self.updateEditLine(self.input + chr(data))
|
||||
|
||||
def newEditLine(self):
|
||||
self.addLine("")
|
||||
self.updateEditLine()
|
||||
|
||||
def cls(self):
|
||||
curKids = list(self.textEl.child_nodes)
|
||||
for kid in curKids:
|
||||
self.textEl.RemoveChild(kid)
|
||||
|
|
@ -0,0 +1,410 @@
|
|||
"""
|
||||
Show how to use libRocket in Panda3D.
|
||||
"""
|
||||
import sys
|
||||
from panda3d.core import loadPrcFile, loadPrcFileData, Point3,Vec4, Mat4, LoaderOptions # @UnusedImport
|
||||
from panda3d.core import DirectionalLight, AmbientLight, PointLight
|
||||
from panda3d.core import Texture, PNMImage
|
||||
from panda3d.core import PandaSystem
|
||||
import random
|
||||
from direct.interval.LerpInterval import LerpHprInterval, LerpPosInterval, LerpFunc
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
|
||||
# workaround: https://www.panda3d.org/forums/viewtopic.php?t=10062&p=99697#p99054
|
||||
#from panda3d import rocket
|
||||
import _rocketcore as rocket
|
||||
|
||||
from panda3d.rocket import RocketRegion, RocketInputHandler
|
||||
|
||||
loadPrcFileData("", "model-path $MAIN_DIR/assets")
|
||||
|
||||
import console
|
||||
|
||||
global globalClock
|
||||
|
||||
class MyApp(ShowBase):
|
||||
|
||||
def __init__(self):
|
||||
ShowBase.__init__(self)
|
||||
|
||||
self.win.setClearColor(Vec4(0.2, 0.2, 0.2, 1))
|
||||
|
||||
self.disableMouse()
|
||||
|
||||
self.render.setShaderAuto()
|
||||
|
||||
dlight = DirectionalLight('dlight')
|
||||
alight = AmbientLight('alight')
|
||||
dlnp = self.render.attachNewNode(dlight)
|
||||
alnp = self.render.attachNewNode(alight)
|
||||
dlight.setColor((0.8, 0.8, 0.5, 1))
|
||||
alight.setColor((0.2, 0.2, 0.2, 1))
|
||||
dlnp.setHpr(0, -60, 0)
|
||||
self.render.setLight(dlnp)
|
||||
self.render.setLight(alnp)
|
||||
|
||||
# Put lighting on the main scene
|
||||
plight = PointLight('plight')
|
||||
plnp = self.render.attachNewNode(plight)
|
||||
plnp.setPos(0, 0, 10)
|
||||
self.render.setLight(plnp)
|
||||
self.render.setLight(alnp)
|
||||
|
||||
self.loadRocketFonts()
|
||||
|
||||
self.loadingTask = None
|
||||
|
||||
#self.startModelLoadingAsync()
|
||||
self.startModelLoading()
|
||||
|
||||
self.inputHandler = RocketInputHandler()
|
||||
self.mouseWatcher.attachNewNode(self.inputHandler)
|
||||
|
||||
self.openLoadingDialog()
|
||||
|
||||
def loadRocketFonts(self):
|
||||
""" Load fonts referenced from e.g. 'font-family' RCSS directives.
|
||||
|
||||
Note: the name of the font as used in 'font-family'
|
||||
is not always the same as the filename;
|
||||
open the font in your OS to see its display name.
|
||||
"""
|
||||
rocket.LoadFontFace("modenine.ttf")
|
||||
|
||||
|
||||
def startModelLoading(self):
|
||||
self.monitorNP = None
|
||||
self.keyboardNP = None
|
||||
self.loadingError = False
|
||||
|
||||
self.taskMgr.doMethodLater(1, self.loadModels, 'loadModels')
|
||||
|
||||
def loadModels(self, task):
|
||||
self.monitorNP = self.loader.loadModel("monitor")
|
||||
self.keyboardNP = self.loader.loadModel("takeyga_kb")
|
||||
|
||||
def startModelLoadingAsync(self):
|
||||
"""
|
||||
NOTE: this seems to invoke a few bugs (crashes, sporadic model
|
||||
reading errors, etc) so is disabled for now...
|
||||
"""
|
||||
self.monitorNP = None
|
||||
self.keyboardNP = None
|
||||
self.loadingError = False
|
||||
|
||||
# force the "loading" to take some time after the first run...
|
||||
options = LoaderOptions()
|
||||
options.setFlags(options.getFlags() | LoaderOptions.LFNoCache)
|
||||
|
||||
def gotMonitorModel(model):
|
||||
if not model:
|
||||
self.loadingError = True
|
||||
self.monitorNP = model
|
||||
|
||||
self.loader.loadModel("monitor", loaderOptions=options, callback=gotMonitorModel)
|
||||
|
||||
def gotKeyboardModel(model):
|
||||
if not model:
|
||||
self.loadingError = True
|
||||
self.keyboardNP = model
|
||||
|
||||
self.loader.loadModel("takeyga_kb", loaderOptions=options, callback=gotKeyboardModel)
|
||||
|
||||
def openLoadingDialog(self):
|
||||
self.userConfirmed = False
|
||||
|
||||
self.windowRocketRegion = RocketRegion.make('pandaRocket', self.win)
|
||||
self.windowRocketRegion.setActive(1)
|
||||
|
||||
self.windowRocketRegion.setInputHandler(self.inputHandler)
|
||||
|
||||
self.windowContext = self.windowRocketRegion.getContext()
|
||||
|
||||
self.loadingDocument = self.windowContext.LoadDocument("loading.rml")
|
||||
if not self.loadingDocument:
|
||||
raise AssertionError("did not find loading.rml")
|
||||
|
||||
self.loadingDots = 0
|
||||
el = self.loadingDocument.GetElementById('loadingLabel')
|
||||
self.loadingText = el.first_child
|
||||
self.stopLoadingTime = globalClock.getFrameTime() + 3
|
||||
self.loadingTask = self.taskMgr.add(self.cycleLoading, 'doc changer')
|
||||
|
||||
|
||||
# note: you may encounter errors like 'KeyError: 'document'"
|
||||
# when invoking events using methods from your own scripts with this
|
||||
# obvious code:
|
||||
#
|
||||
# self.loadingDocument.AddEventListener('aboutToClose',
|
||||
# self.onLoadingDialogDismissed, True)
|
||||
#
|
||||
# A workaround is to define callback methods in standalone Python
|
||||
# files with event, self, and document defined to None.
|
||||
#
|
||||
# see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
|
||||
#
|
||||
|
||||
# Or, use this indirection technique to work around the problem,
|
||||
# by publishing the app into the context, then accessing it through
|
||||
# the document's context...
|
||||
|
||||
self.windowContext.app = self
|
||||
self.loadingDocument.AddEventListener('aboutToClose',
|
||||
'document.context.app.handleAboutToClose()', True)
|
||||
|
||||
self.loadingDocument.Show()
|
||||
|
||||
def handleAboutToClose(self):
|
||||
self.userConfirmed = True
|
||||
if self.monitorNP and self.keyboardNP:
|
||||
self.onLoadingDialogDismissed()
|
||||
|
||||
def attachCustomRocketEvent(self, document, rocketEventName, pandaHandler, once=False):
|
||||
# handle custom event
|
||||
|
||||
# note: you may encounter errors like 'KeyError: 'document'"
|
||||
# when invoking events using methods from your own scripts with this
|
||||
# obvious code:
|
||||
#
|
||||
# self.loadingDocument.AddEventListener('aboutToClose',
|
||||
# self.onLoadingDialogDismissed, True)
|
||||
#
|
||||
# see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
|
||||
|
||||
|
||||
# this technique converts Rocket events to Panda3D events
|
||||
|
||||
pandaEvent = 'panda.' + rocketEventName
|
||||
|
||||
document.AddEventListener(
|
||||
rocketEventName,
|
||||
"messenger.send('" + pandaEvent + "', [event])")
|
||||
|
||||
if once:
|
||||
self.acceptOnce(pandaEvent, pandaHandler)
|
||||
else:
|
||||
self.accept(pandaEvent, pandaHandler)
|
||||
|
||||
|
||||
def cycleLoading(self, task):
|
||||
"""
|
||||
Update the "loading" text in the initial window until
|
||||
the user presses Space, Enter, or Escape or clicks (see loading.rxml)
|
||||
or sufficient time has elapsed (self.stopLoadingTime).
|
||||
"""
|
||||
text = self.loadingText
|
||||
|
||||
now = globalClock.getFrameTime()
|
||||
if self.monitorNP and self.keyboardNP:
|
||||
text.text = "Ready"
|
||||
if now > self.stopLoadingTime or self.userConfirmed:
|
||||
self.onLoadingDialogDismissed()
|
||||
return task.done
|
||||
elif self.loadingError:
|
||||
text.text = "Assets not found"
|
||||
else:
|
||||
count = 5
|
||||
intv = int(now * 4) % count # @UndefinedVariable
|
||||
text.text = "Loading" + ("." * (1+intv)) + (" " * (2 - intv))
|
||||
|
||||
return task.cont
|
||||
|
||||
def onLoadingDialogDismissed(self):
|
||||
""" Once a models are loaded, stop 'loading' and proceed to 'start' """
|
||||
if self.loadingDocument:
|
||||
if self.loadingTask:
|
||||
self.taskMgr.remove(self.loadingTask)
|
||||
self.loadingTask = None
|
||||
|
||||
self.showStarting()
|
||||
|
||||
def fadeOut(self, element, time):
|
||||
""" Example updating RCSS attributes from code
|
||||
by modifying the 'color' RCSS attribute to slowly
|
||||
change from solid to transparent.
|
||||
|
||||
element: the Rocket element whose style to modify
|
||||
time: time in seconds for fadeout
|
||||
"""
|
||||
|
||||
# get the current color from RCSS effective style
|
||||
color = element.style.color
|
||||
# convert to RGBA form
|
||||
prefix = color[:color.rindex(',')+1].replace('rgb(', 'rgba(')
|
||||
|
||||
def updateAlpha(t):
|
||||
# another way of setting style on a specific element
|
||||
attr = 'color: ' + prefix + str(int(t)) +');'
|
||||
element.SetAttribute('style', attr)
|
||||
|
||||
alphaInterval = LerpFunc(updateAlpha,
|
||||
duration=time,
|
||||
fromData=255,
|
||||
toData=0,
|
||||
blendType='easeIn')
|
||||
|
||||
return alphaInterval
|
||||
|
||||
def showStarting(self):
|
||||
""" Models are loaded, so update the dialog,
|
||||
fade out, then transition to the console. """
|
||||
self.loadingText.text = 'Starting...'
|
||||
|
||||
alphaInterval = self.fadeOut(self.loadingText, 0.5)
|
||||
alphaInterval.setDoneEvent('fadeOutFinished')
|
||||
|
||||
def fadeOutFinished():
|
||||
if self.loadingDocument:
|
||||
self.loadingDocument.Close()
|
||||
self.loadingDocument = None
|
||||
self.createConsole()
|
||||
|
||||
self.accept('fadeOutFinished', fadeOutFinished)
|
||||
|
||||
alphaInterval.start()
|
||||
|
||||
def createConsole(self):
|
||||
""" Create the in-world console, which displays
|
||||
a RocketRegion in a GraphicsBuffer, which appears
|
||||
in a Texture on the monitor model. """
|
||||
|
||||
self.monitorNP.reparentTo(self.render)
|
||||
self.monitorNP.setScale(1.5)
|
||||
|
||||
self.keyboardNP.reparentTo(self.render)
|
||||
self.keyboardNP.setHpr(-90, 0, 15)
|
||||
self.keyboardNP.setScale(20)
|
||||
|
||||
self.placeItems()
|
||||
|
||||
self.setupRocketConsole()
|
||||
|
||||
# re-enable mouse
|
||||
mat=Mat4(self.camera.getMat())
|
||||
mat.invertInPlace()
|
||||
self.mouseInterfaceNode.setMat(mat)
|
||||
self.enableMouse()
|
||||
|
||||
def placeItems(self):
|
||||
self.camera.setPos(0, -20, 0)
|
||||
self.camera.setHpr(0, 0, 0)
|
||||
self.monitorNP.setPos(0, 0, 1)
|
||||
self.keyboardNP.setPos(0, -5, -2.5)
|
||||
|
||||
|
||||
def setupRocketConsole(self):
|
||||
"""
|
||||
Place a new rocket window onto a texture
|
||||
bound to the front of the monitor.
|
||||
"""
|
||||
self.win.setClearColor(Vec4(0.5, 0.5, 0.8, 1))
|
||||
|
||||
faceplate = self.monitorNP.find("**/Faceplate")
|
||||
assert faceplate
|
||||
|
||||
mybuffer = self.win.makeTextureBuffer("Console Buffer", 1024, 512)
|
||||
tex = mybuffer.getTexture()
|
||||
tex.setMagfilter(Texture.FTLinear)
|
||||
tex.setMinfilter(Texture.FTLinear)
|
||||
|
||||
faceplate.setTexture(tex, 1)
|
||||
|
||||
self.rocketConsole = RocketRegion.make('console', mybuffer)
|
||||
self.rocketConsole.setInputHandler(self.inputHandler)
|
||||
|
||||
self.consoleContext = self.rocketConsole.getContext()
|
||||
self.console = console.Console(self, self.consoleContext, 40, 13, self.handleCommand)
|
||||
|
||||
self.console.addLine("Panda DOS")
|
||||
self.console.addLine("type 'help'")
|
||||
self.console.addLine("")
|
||||
|
||||
self.console.allowEditing(True)
|
||||
|
||||
def handleCommand(self, command):
|
||||
if command is None:
|
||||
# hack for Ctrl-Break
|
||||
self.spewInProgress = False
|
||||
self.console.addLine("*** break ***")
|
||||
self.console.allowEditing(True)
|
||||
return
|
||||
|
||||
command = command.strip()
|
||||
if not command:
|
||||
return
|
||||
|
||||
tokens = [x.strip() for x in command.split(' ')]
|
||||
command = tokens[0].lower()
|
||||
|
||||
if command == 'help':
|
||||
self.console.addLines([
|
||||
"Sorry, this is utter fakery.",
|
||||
"You won't get much more",
|
||||
"out of this simulation unless",
|
||||
"you program it yourself. :)"
|
||||
])
|
||||
elif command == 'dir':
|
||||
self.console.addLines([
|
||||
"Directory of C:\\:",
|
||||
"HELP COM 72 05-06-2015 14:07",
|
||||
"DIR COM 121 05-06-2015 14:11",
|
||||
"SPEW COM 666 05-06-2015 15:02",
|
||||
" 2 Files(s) 859 Bytes.",
|
||||
" 0 Dirs(s) 7333 Bytes free.",
|
||||
""])
|
||||
elif command == 'cls':
|
||||
self.console.cls()
|
||||
elif command == 'echo':
|
||||
self.console.addLine(' '.join(tokens[1:]))
|
||||
elif command == 'ver':
|
||||
self.console.addLine('Panda DOS v0.01 in Panda3D ' + PandaSystem.getVersionString())
|
||||
elif command == 'spew':
|
||||
self.startSpew()
|
||||
elif command == 'exit':
|
||||
self.console.setPrompt("System is shutting down NOW!")
|
||||
self.terminateMonitor()
|
||||
else:
|
||||
self.console.addLine("command not found")
|
||||
|
||||
def startSpew(self):
|
||||
self.console.allowEditing(False)
|
||||
self.console.addLine("LINE NOISE 1.0")
|
||||
self.console.addLine("")
|
||||
|
||||
self.spewInProgress = True
|
||||
|
||||
# note: spewage always occurs in 'doMethodLater';
|
||||
# time.sleep() would be pointless since the whole
|
||||
# UI would be frozen during the wait.
|
||||
self.queueSpew(2)
|
||||
|
||||
def queueSpew(self, delay=0.1):
|
||||
self.taskMgr.doMethodLater(delay, self.spew, 'spew')
|
||||
|
||||
def spew(self, task):
|
||||
# generate random spewage, just like on TV!
|
||||
if not self.spewInProgress:
|
||||
return
|
||||
|
||||
def randchr():
|
||||
return chr(int(random.random() < 0.25 and 32 or random.randint(32, 127)))
|
||||
|
||||
line = ''.join([randchr() for _ in range(40) ])
|
||||
|
||||
self.console.addLine(line)
|
||||
self.queueSpew()
|
||||
|
||||
def terminateMonitor(self):
|
||||
alphaInterval = self.fadeOut(self.console.getTextContainer(), 2)
|
||||
|
||||
alphaInterval.setDoneEvent('fadeOutFinished')
|
||||
|
||||
def fadeOutFinished():
|
||||
sys.exit(0)
|
||||
|
||||
self.accept('fadeOutFinished', fadeOutFinished)
|
||||
|
||||
alphaInterval.start()
|
||||
|
||||
app = MyApp()
|
||||
app.run()
|
||||
Loading…
Reference in New Issue