Merge branch 'master' into deploy-ng

This commit is contained in:
Mitchell Stokes 2017-11-25 14:55:47 -08:00
commit aba26bd862
66 changed files with 2332 additions and 302 deletions

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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 Pythons ability to call C libraries, and ultimately make use of
Panda3Ds 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.

View File

@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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);

View File

@ -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; }

View File

@ -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')

View File

@ -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;
}

View File

@ -42,6 +42,8 @@ PUBLISHED:
INLINE bool is_ready() const;
INLINE AudioSound *get_sound() const;
INLINE AudioSound *result() const;
protected:
virtual DoneStatus do_task();

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -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)");
}
}

View File

@ -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 &params) {
*/
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();

View File

@ -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();

View File

@ -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));
}
}
/**

View File

@ -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();

View File

@ -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;

5
panda/src/chan/README.md Normal file
View File

@ -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.

3
panda/src/cull/README.md Normal file
View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,2 @@
This package contains the abstract display classes, including pipes,
windows, channels, and display regions.

View File

@ -0,0 +1,2 @@
This package handles all communication with the DirectX backend, and
manages state to minimize redundant state changes.

View File

@ -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:

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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

35
panda/src/event/asyncTask_ext.h Executable file
View File

@ -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

View File

@ -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);
}

View File

@ -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.

View File

@ -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() {

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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

View File

@ -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();

View File

@ -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;
}

View File

@ -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();

View File

@ -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

View File

@ -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";

View File

@ -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.

View File

@ -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;
}

View File

@ -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>

View File

@ -0,0 +1 @@
from www.dafont.com/perfect-dos-vga-437.font (info at http://zehfernando.com/2015/revisiting-vga-fonts/)

View File

@ -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>

View File

@ -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.

View File

@ -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.

View File

@ -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;
}

View File

@ -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 }}
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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('<', '&lt;'). \
replace('>', '&gt;'). \
replace('"', '&quot;')
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)

View File

@ -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()