From 31286fb558d853aa1378bff793a662bb6e54d5ed Mon Sep 17 00:00:00 2001 From: Timothee 'TTimo' Besset Date: Sun, 27 May 2012 18:50:03 -0500 Subject: [PATCH] import urban terror bumpy q3map2 from http://code.google.com/p/urt-bumpy-q3map2/ - removed some trash files, no modifications yet --- tools/urt/libs/.cvsignore | 1 + tools/urt/libs/archivelib.cpp | 2 + tools/urt/libs/archivelib.h | 237 + tools/urt/libs/bytebool.cpp | 2 + tools/urt/libs/bytebool.h | 11 + tools/urt/libs/bytestreamutils.cpp | 2 + tools/urt/libs/bytestreamutils.h | 100 + tools/urt/libs/character.cpp | 3 + tools/urt/libs/character.h | 27 + tools/urt/libs/cmdlib.h | 106 + tools/urt/libs/cmdlib/.cvsignore | 1 + tools/urt/libs/cmdlib/.cvswrappers | 3 + tools/urt/libs/cmdlib/cmdlib.cpp | 133 + tools/urt/libs/cmdlib/cmdlib.dsp | 101 + tools/urt/libs/cmdlib/cmdlib.vcproj | 122 + tools/urt/libs/container/array.cpp | 19 + tools/urt/libs/container/array.h | 170 + tools/urt/libs/container/cache.cpp | 2 + tools/urt/libs/container/cache.h | 177 + tools/urt/libs/container/container.cpp | 3 + tools/urt/libs/container/container.h | 326 ++ tools/urt/libs/container/hashfunc.cpp | 2 + tools/urt/libs/container/hashfunc.h | 413 ++ tools/urt/libs/container/hashtable.cpp | 45 + tools/urt/libs/container/hashtable.h | 455 ++ tools/urt/libs/container/stack.cpp | 3 + tools/urt/libs/container/stack.h | 219 + tools/urt/libs/convert.cpp | 3 + tools/urt/libs/convert.h | 285 + tools/urt/libs/ddslib.h | 250 + tools/urt/libs/ddslib/ddslib.c | 781 +++ tools/urt/libs/ddslib/ddslib.dsp | 106 + tools/urt/libs/ddslib/ddslib.plg | 27 + tools/urt/libs/ddslib/ddslib.vcproj | 211 + tools/urt/libs/debugging/debugging.cpp | 8 + tools/urt/libs/debugging/debugging.h | 115 + tools/urt/libs/dragplanes.cpp | 3 + tools/urt/libs/dragplanes.h | 233 + tools/urt/libs/eclasslib.cpp | 2 + tools/urt/libs/eclasslib.h | 321 ++ tools/urt/libs/entitylib.cpp | 2 + tools/urt/libs/entitylib.h | 726 +++ tools/urt/libs/entityxml.cpp | 2 + tools/urt/libs/entityxml.h | 88 + tools/urt/libs/fs_filesystem.cpp | 2 + tools/urt/libs/fs_filesystem.h | 160 + tools/urt/libs/fs_path.cpp | 2 + tools/urt/libs/fs_path.h | 72 + tools/urt/libs/generic/arrayrange.cpp | 3 + tools/urt/libs/generic/arrayrange.h | 52 + tools/urt/libs/generic/bitfield.cpp | 3 + tools/urt/libs/generic/bitfield.h | 113 + tools/urt/libs/generic/callback.cpp | 89 + tools/urt/libs/generic/callback.h | 517 ++ tools/urt/libs/generic/enumeration.cpp | 3 + tools/urt/libs/generic/enumeration.h | 40 + tools/urt/libs/generic/object.cpp | 21 + tools/urt/libs/generic/object.h | 78 + tools/urt/libs/generic/reference.cpp | 2 + tools/urt/libs/generic/reference.h | 111 + tools/urt/libs/generic/referencecounted.cpp | 2 + tools/urt/libs/generic/referencecounted.h | 187 + tools/urt/libs/generic/static.cpp | 113 + tools/urt/libs/generic/static.h | 123 + tools/urt/libs/gtkutil/accelerator.cpp | 527 ++ tools/urt/libs/gtkutil/accelerator.h | 97 + tools/urt/libs/gtkutil/button.cpp | 117 + tools/urt/libs/gtkutil/button.h | 23 + tools/urt/libs/gtkutil/clipboard.cpp | 142 + tools/urt/libs/gtkutil/clipboard.h | 13 + tools/urt/libs/gtkutil/closure.cpp | 3 + tools/urt/libs/gtkutil/closure.h | 57 + tools/urt/libs/gtkutil/container.cpp | 3 + tools/urt/libs/gtkutil/container.h | 23 + tools/urt/libs/gtkutil/cursor.cpp | 71 + tools/urt/libs/gtkutil/cursor.h | 173 + tools/urt/libs/gtkutil/dialog.cpp | 283 + tools/urt/libs/gtkutil/dialog.h | 125 + tools/urt/libs/gtkutil/entry.cpp | 3 + tools/urt/libs/gtkutil/entry.h | 43 + tools/urt/libs/gtkutil/filechooser.cpp | 463 ++ tools/urt/libs/gtkutil/filechooser.h | 22 + tools/urt/libs/gtkutil/frame.cpp | 15 + tools/urt/libs/gtkutil/frame.h | 9 + tools/urt/libs/gtkutil/glfont.cpp | 36 + tools/urt/libs/gtkutil/glfont.h | 28 + tools/urt/libs/gtkutil/glwidget.cpp | 254 + tools/urt/libs/gtkutil/glwidget.h | 19 + tools/urt/libs/gtkutil/gtkutil.vcproj | 255 + tools/urt/libs/gtkutil/idledraw.cpp | 3 + tools/urt/libs/gtkutil/idledraw.h | 49 + tools/urt/libs/gtkutil/image.cpp | 76 + tools/urt/libs/gtkutil/image.h | 16 + tools/urt/libs/gtkutil/menu.cpp | 293 ++ tools/urt/libs/gtkutil/menu.h | 37 + tools/urt/libs/gtkutil/messagebox.cpp | 193 + tools/urt/libs/gtkutil/messagebox.h | 11 + tools/urt/libs/gtkutil/nonmodal.cpp | 3 + tools/urt/libs/gtkutil/nonmodal.h | 166 + tools/urt/libs/gtkutil/paned.cpp | 80 + tools/urt/libs/gtkutil/paned.h | 9 + tools/urt/libs/gtkutil/pointer.cpp | 3 + tools/urt/libs/gtkutil/pointer.h | 20 + tools/urt/libs/gtkutil/toolbar.cpp | 58 + tools/urt/libs/gtkutil/toolbar.h | 17 + tools/urt/libs/gtkutil/widget.cpp | 3 + tools/urt/libs/gtkutil/widget.h | 145 + tools/urt/libs/gtkutil/window.cpp | 150 + tools/urt/libs/gtkutil/window.h | 154 + tools/urt/libs/gtkutil/xorrectangle.cpp | 3 + tools/urt/libs/gtkutil/xorrectangle.h | 105 + tools/urt/libs/imagelib.cpp | 3 + tools/urt/libs/imagelib.h | 132 + tools/urt/libs/instancelib.cpp | 2 + tools/urt/libs/instancelib.h | 143 + tools/urt/libs/jpeg6/.cvsignore | 8 + tools/urt/libs/jpeg6/.cvswrappers | 3 + tools/urt/libs/jpeg6/jchuff.h | 68 + tools/urt/libs/jpeg6/jcomapi.cpp | 188 + tools/urt/libs/jpeg6/jconfig.h | 82 + tools/urt/libs/jpeg6/jdapimin.cpp | 800 +++ tools/urt/libs/jpeg6/jdapistd.cpp | 550 ++ tools/urt/libs/jpeg6/jdatasrc.cpp | 215 + tools/urt/libs/jpeg6/jdcoefct.cpp | 1450 ++++++ tools/urt/libs/jpeg6/jdcolor.cpp | 734 +++ tools/urt/libs/jpeg6/jdct.h | 352 ++ tools/urt/libs/jpeg6/jddctmgr.cpp | 540 ++ tools/urt/libs/jpeg6/jdhuff.cpp | 574 ++ tools/urt/libs/jpeg6/jdhuff.h | 202 + tools/urt/libs/jpeg6/jdinput.cpp | 762 +++ tools/urt/libs/jpeg6/jdmainct.cpp | 1024 ++++ tools/urt/libs/jpeg6/jdmarker.cpp | 1052 ++++ tools/urt/libs/jpeg6/jdmaster.cpp | 558 ++ tools/urt/libs/jpeg6/jdpostct.cpp | 580 +++ tools/urt/libs/jpeg6/jdsample.cpp | 956 ++++ tools/urt/libs/jpeg6/jdtrans.cpp | 244 + tools/urt/libs/jpeg6/jerror.cpp | 233 + tools/urt/libs/jpeg6/jerror.h | 278 + tools/urt/libs/jpeg6/jfdctflt.cpp | 336 ++ tools/urt/libs/jpeg6/jidctflt.cpp | 482 ++ tools/urt/libs/jpeg6/jinclude.h | 91 + tools/urt/libs/jpeg6/jmemmgr.cpp | 2230 ++++++++ tools/urt/libs/jpeg6/jmemnobs.cpp | 206 + tools/urt/libs/jpeg6/jmemsys.h | 364 ++ tools/urt/libs/jpeg6/jmorecfg.h | 693 +++ tools/urt/libs/jpeg6/jpeg6.dsp | 218 + tools/urt/libs/jpeg6/jpeg6.vcproj | 692 +++ tools/urt/libs/jpeg6/jpegint.h | 776 +++ tools/urt/libs/jpeg6/jpgload.cpp | 166 + tools/urt/libs/jpeg6/jutils.cpp | 350 ++ tools/urt/libs/jpeg6/jversion.h | 28 + tools/urt/libs/jpeglib.h | 1126 ++++ tools/urt/libs/l_net/.cvsignore | 5 + tools/urt/libs/l_net/l_net.c | 630 +++ tools/urt/libs/l_net/l_net.dsp | 118 + tools/urt/libs/l_net/l_net.h | 128 + tools/urt/libs/l_net/l_net.vcproj | 230 + tools/urt/libs/l_net/l_net_berkley.c | 769 +++ tools/urt/libs/l_net/l_net_wins.c | 792 +++ tools/urt/libs/l_net/l_net_wins.h | 55 + tools/urt/libs/libs.vcproj | 592 +++ tools/urt/libs/maplib.cpp | 2 + tools/urt/libs/maplib.h | 262 + tools/urt/libs/math/aabb.cpp | 3 + tools/urt/libs/math/aabb.h | 281 + tools/urt/libs/math/curve.cpp | 3 + tools/urt/libs/math/curve.h | 253 + tools/urt/libs/math/expression.cpp | 223 + tools/urt/libs/math/expression.h | 597 +++ tools/urt/libs/math/frustum.cpp | 3 + tools/urt/libs/math/frustum.h | 609 +++ tools/urt/libs/math/line.cpp | 3 + tools/urt/libs/math/line.h | 131 + tools/urt/libs/math/matrix.cpp | 3 + tools/urt/libs/math/matrix.h | 1290 +++++ tools/urt/libs/math/pi.cpp | 3 + tools/urt/libs/math/pi.h | 25 + tools/urt/libs/math/plane.cpp | 3 + tools/urt/libs/math/plane.h | 133 + tools/urt/libs/math/quaternion.cpp | 3 + tools/urt/libs/math/quaternion.h | 289 ++ tools/urt/libs/math/vector.cpp | 3 + tools/urt/libs/math/vector.h | 933 ++++ tools/urt/libs/mathlib.h | 425 ++ tools/urt/libs/mathlib/bbox.c | 465 ++ tools/urt/libs/mathlib/line.c | 44 + tools/urt/libs/mathlib/linear.c | 207 + tools/urt/libs/mathlib/m4x4.c | 1880 +++++++ tools/urt/libs/mathlib/mathlib.c | 581 +++ tools/urt/libs/mathlib/mathlib.dsp | 126 + tools/urt/libs/mathlib/mathlib.vcproj | 320 ++ tools/urt/libs/mathlib/ray.c | 143 + tools/urt/libs/md5lib.h | 91 + tools/urt/libs/md5lib/md5lib.c | 395 ++ tools/urt/libs/md5lib/md5lib.dsp | 106 + tools/urt/libs/md5lib/md5lib.vcproj | 211 + tools/urt/libs/memory/allocator.cpp | 60 + tools/urt/libs/memory/allocator.h | 316 ++ tools/urt/libs/moduleobservers.cpp | 3 + tools/urt/libs/moduleobservers.h | 44 + .../urt/libs/modulesystem/moduleregistry.cpp | 3 + tools/urt/libs/modulesystem/moduleregistry.h | 45 + tools/urt/libs/modulesystem/modulesmap.cpp | 2 + tools/urt/libs/modulesystem/modulesmap.h | 132 + .../urt/libs/modulesystem/singletonmodule.cpp | 33 + tools/urt/libs/modulesystem/singletonmodule.h | 130 + tools/urt/libs/os/dir.cpp | 3 + tools/urt/libs/os/dir.h | 55 + tools/urt/libs/os/file.cpp | 3 + tools/urt/libs/os/file.h | 123 + tools/urt/libs/os/path.cpp | 2 + tools/urt/libs/os/path.h | 263 + tools/urt/libs/picomodel.h | 353 ++ tools/urt/libs/picomodel/lwo/clip.c | 278 + tools/urt/libs/picomodel/lwo/envelope.c | 600 +++ tools/urt/libs/picomodel/lwo/libs_rar.rar | Bin 0 -> 537714 bytes tools/urt/libs/picomodel/lwo/list.c | 101 + tools/urt/libs/picomodel/lwo/lwio.c | 442 ++ tools/urt/libs/picomodel/lwo/lwo2.c | 308 ++ tools/urt/libs/picomodel/lwo/lwo2.h | 651 +++ tools/urt/libs/picomodel/lwo/lwob.c | 723 +++ tools/urt/libs/picomodel/lwo/pntspols.c | 537 ++ tools/urt/libs/picomodel/lwo/surface.c | 1005 ++++ tools/urt/libs/picomodel/lwo/vecmath.c | 37 + tools/urt/libs/picomodel/lwo/vmap.c | 243 + tools/urt/libs/picomodel/picointernal.c | 1356 +++++ tools/urt/libs/picomodel/picointernal.h | 206 + tools/urt/libs/picomodel/picomodel.c | 2279 ++++++++ tools/urt/libs/picomodel/picomodel.dsp | 210 + tools/urt/libs/picomodel/picomodel.plg | 16 + tools/urt/libs/picomodel/picomodel.vcproj | 713 +++ tools/urt/libs/picomodel/picomodules.c | 94 + tools/urt/libs/picomodel/pm_3ds.c | 777 +++ tools/urt/libs/picomodel/pm_ase.c | 1427 +++++ tools/urt/libs/picomodel/pm_fm.c | 667 +++ tools/urt/libs/picomodel/pm_fm.h | 367 ++ tools/urt/libs/picomodel/pm_lwo.c | 445 ++ tools/urt/libs/picomodel/pm_md2.c | 667 +++ tools/urt/libs/picomodel/pm_md3.c | 425 ++ tools/urt/libs/picomodel/pm_mdc.c | 750 +++ tools/urt/libs/picomodel/pm_ms3d.c | 494 ++ tools/urt/libs/picomodel/pm_obj.c | 858 +++ tools/urt/libs/picomodel/pm_terrain.c | 610 +++ tools/urt/libs/pivot.cpp | 3 + tools/urt/libs/pivot.h | 280 + tools/urt/libs/profile/file.cpp | 383 ++ tools/urt/libs/profile/file.h | 158 + tools/urt/libs/profile/profile.cpp | 297 ++ tools/urt/libs/profile/profile.h | 19 + tools/urt/libs/profile/profile.vcproj | 129 + tools/urt/libs/radiant_jpeglib.h | 1126 ++++ tools/urt/libs/render.cpp | 2 + tools/urt/libs/render.h | 1338 +++++ tools/urt/libs/scenelib.cpp | 2 + tools/urt/libs/scenelib.h | 913 ++++ tools/urt/libs/script/scripttokeniser.cpp | 3 + tools/urt/libs/script/scripttokeniser.h | 334 ++ tools/urt/libs/script/scripttokenwriter.cpp | 3 + tools/urt/libs/script/scripttokenwriter.h | 68 + tools/urt/libs/selectionlib.cpp | 2 + tools/urt/libs/selectionlib.h | 187 + tools/urt/libs/shaderlib.cpp | 2 + tools/urt/libs/shaderlib.h | 75 + tools/urt/libs/splines/.cvsignore | 1 + tools/urt/libs/splines/Splines.dsp | 160 + tools/urt/libs/splines/Splines.vcproj | 163 + tools/urt/libs/splines/math_angles.cpp | 153 + tools/urt/libs/splines/math_angles.h | 198 + tools/urt/libs/splines/math_matrix.cpp | 137 + tools/urt/libs/splines/math_matrix.h | 226 + tools/urt/libs/splines/math_quaternion.cpp | 81 + tools/urt/libs/splines/math_quaternion.h | 193 + tools/urt/libs/splines/math_vector.cpp | 146 + tools/urt/libs/splines/math_vector.h | 577 +++ tools/urt/libs/splines/q_parse.cpp | 538 ++ tools/urt/libs/splines/q_shared.cpp | 979 ++++ tools/urt/libs/splines/q_shared.h | 799 +++ tools/urt/libs/splines/splines.cpp | 1424 +++++ tools/urt/libs/splines/splines.h | 1099 ++++ tools/urt/libs/splines/util_list.h | 349 ++ tools/urt/libs/splines/util_str.cpp | 631 +++ tools/urt/libs/splines/util_str.h | 820 +++ tools/urt/libs/str.cpp | 2 + tools/urt/libs/str.h | 513 ++ tools/urt/libs/stream/filestream.cpp | 2 + tools/urt/libs/stream/filestream.h | 174 + tools/urt/libs/stream/memstream.cpp | 2 + tools/urt/libs/stream/memstream.h | 61 + tools/urt/libs/stream/stringstream.cpp | 2 + tools/urt/libs/stream/stringstream.h | 157 + tools/urt/libs/stream/textfilestream.cpp | 2 + tools/urt/libs/stream/textfilestream.h | 67 + tools/urt/libs/stream/textstream.cpp | 2 + tools/urt/libs/stream/textstream.h | 372 ++ tools/urt/libs/string/string.cpp | 8 + tools/urt/libs/string/string.h | 583 +++ tools/urt/libs/string/stringfwd.cpp | 3 + tools/urt/libs/string/stringfwd.h | 15 + tools/urt/libs/stringio.cpp | 2 + tools/urt/libs/stringio.h | 465 ++ tools/urt/libs/texturelib.cpp | 2 + tools/urt/libs/texturelib.h | 21 + tools/urt/libs/transformlib.cpp | 2 + tools/urt/libs/transformlib.h | 249 + tools/urt/libs/traverselib.cpp | 2 + tools/urt/libs/traverselib.h | 357 ++ tools/urt/libs/typesystem.cpp | 3 + tools/urt/libs/typesystem.h | 129 + tools/urt/libs/undolib.cpp | 2 + tools/urt/libs/undolib.h | 141 + tools/urt/libs/uniquenames.cpp | 3 + tools/urt/libs/uniquenames.h | 315 ++ tools/urt/libs/versionlib.cpp | 3 + tools/urt/libs/versionlib.h | 69 + tools/urt/libs/xml/ixml.cpp | 2 + tools/urt/libs/xml/ixml.h | 45 + tools/urt/libs/xml/xmlelement.cpp | 2 + tools/urt/libs/xml/xmlelement.h | 89 + tools/urt/libs/xml/xmlparser.cpp | 2 + tools/urt/libs/xml/xmlparser.h | 222 + tools/urt/libs/xml/xmlwriter.cpp | 2 + tools/urt/libs/xml/xmlwriter.h | 220 + tools/urt/tools/quake3/common/aselib.c | 968 ++++ tools/urt/tools/quake3/common/aselib.h | 34 + tools/urt/tools/quake3/common/bspfile.c | 709 +++ tools/urt/tools/quake3/common/bspfile.h | 124 + tools/urt/tools/quake3/common/cmdlib.c | 1156 +++++ tools/urt/tools/quake3/common/cmdlib.h | 163 + tools/urt/tools/quake3/common/glib-object.h | 41 + tools/urt/tools/quake3/common/glib.h | 77 + tools/urt/tools/quake3/common/glib/galloca.h | 60 + tools/urt/tools/quake3/common/glib/garray.h | 167 + .../tools/quake3/common/glib/gasyncqueue.h | 92 + tools/urt/tools/quake3/common/glib/gatomic.h | 62 + .../urt/tools/quake3/common/glib/gbacktrace.h | 61 + tools/urt/tools/quake3/common/glib/gcache.h | 66 + .../tools/quake3/common/glib/gcompletion.h | 74 + tools/urt/tools/quake3/common/glib/gconvert.h | 123 + tools/urt/tools/quake3/common/glib/gdataset.h | 106 + tools/urt/tools/quake3/common/glib/gdate.h | 252 + tools/urt/tools/quake3/common/glib/gdir.h | 41 + tools/urt/tools/quake3/common/glib/gerror.h | 74 + .../urt/tools/quake3/common/glib/gfileutils.h | 102 + tools/urt/tools/quake3/common/glib/ghash.h | 111 + tools/urt/tools/quake3/common/glib/ghook.h | 178 + .../urt/tools/quake3/common/glib/gi18n-lib.h | 55 + tools/urt/tools/quake3/common/glib/gi18n.h | 50 + .../urt/tools/quake3/common/glib/giochannel.h | 350 ++ .../urt/tools/quake3/common/glib/glib-2.0.lib | Bin 0 -> 184366 bytes tools/urt/tools/quake3/common/glib/glist.h | 108 + tools/urt/tools/quake3/common/glib/gmacros.h | 242 + tools/urt/tools/quake3/common/glib/gmain.h | 318 ++ tools/urt/tools/quake3/common/glib/gmarkup.h | 131 + tools/urt/tools/quake3/common/glib/gmem.h | 174 + .../urt/tools/quake3/common/glib/gmessages.h | 348 ++ tools/urt/tools/quake3/common/glib/gnode.h | 168 + tools/urt/tools/quake3/common/glib/gpattern.h | 44 + tools/urt/tools/quake3/common/glib/gprimes.h | 47 + tools/urt/tools/quake3/common/glib/gprintf.h | 62 + tools/urt/tools/quake3/common/glib/gqsort.h | 44 + tools/urt/tools/quake3/common/glib/gquark.h | 46 + tools/urt/tools/quake3/common/glib/gqueue.h | 119 + tools/urt/tools/quake3/common/glib/grand.h | 87 + tools/urt/tools/quake3/common/glib/grel.h | 94 + tools/urt/tools/quake3/common/glib/gscanner.h | 274 + tools/urt/tools/quake3/common/glib/gshell.h | 53 + tools/urt/tools/quake3/common/glib/gslist.h | 102 + tools/urt/tools/quake3/common/glib/gspawn.h | 130 + .../urt/tools/quake3/common/glib/gstrfuncs.h | 242 + tools/urt/tools/quake3/common/glib/gstring.h | 157 + tools/urt/tools/quake3/common/glib/gthread.h | 373 ++ .../tools/quake3/common/glib/gthreadpool.h | 102 + tools/urt/tools/quake3/common/glib/gtimer.h | 58 + tools/urt/tools/quake3/common/glib/gtree.h | 88 + tools/urt/tools/quake3/common/glib/gtypes.h | 419 ++ tools/urt/tools/quake3/common/glib/gunicode.h | 287 + tools/urt/tools/quake3/common/glib/gutils.h | 371 ++ tools/urt/tools/quake3/common/glib/gwin32.h | 100 + tools/urt/tools/quake3/common/glibconfig.h | 188 + tools/urt/tools/quake3/common/gmodule.h | 96 + .../urt/tools/quake3/common/gobject/gboxed.h | 83 + .../tools/quake3/common/gobject/gclosure.h | 162 + .../urt/tools/quake3/common/gobject/genums.h | 125 + .../tools/quake3/common/gobject/gmarshal.h | 169 + .../urt/tools/quake3/common/gobject/gobject.h | 252 + .../common/gobject/gobjectnotifyqueue.c | 169 + .../urt/tools/quake3/common/gobject/gparam.h | 228 + .../tools/quake3/common/gobject/gparamspecs.h | 426 ++ .../urt/tools/quake3/common/gobject/gsignal.h | 270 + .../quake3/common/gobject/gsourceclosure.h | 41 + tools/urt/tools/quake3/common/gobject/gtype.h | 479 ++ .../tools/quake3/common/gobject/gtypemodule.h | 85 + .../tools/quake3/common/gobject/gtypeplugin.h | 79 + .../urt/tools/quake3/common/gobject/gvalue.h | 94 + .../tools/quake3/common/gobject/gvaluearray.h | 75 + .../quake3/common/gobject/gvaluecollector.h | 160 + .../tools/quake3/common/gobject/gvaluetypes.h | 114 + tools/urt/tools/quake3/common/imagelib.c | 1223 +++++ tools/urt/tools/quake3/common/imagelib.h | 47 + tools/urt/tools/quake3/common/inout.c | 378 ++ tools/urt/tools/quake3/common/inout.h | 64 + tools/urt/tools/quake3/common/l3dslib.c | 304 ++ tools/urt/tools/quake3/common/l3dslib.h | 29 + tools/urt/tools/quake3/common/md4.c | 301 ++ tools/urt/tools/quake3/common/mutex.c | 200 + tools/urt/tools/quake3/common/mutex.h | 31 + tools/urt/tools/quake3/common/polylib.c | 748 +++ tools/urt/tools/quake3/common/polylib.h | 60 + tools/urt/tools/quake3/common/polyset.h | 54 + tools/urt/tools/quake3/common/qfiles.h | 492 ++ tools/urt/tools/quake3/common/qthreads.h | 34 + tools/urt/tools/quake3/common/scriplib.c | 412 ++ tools/urt/tools/quake3/common/scriplib.h | 58 + tools/urt/tools/quake3/common/surfaceflags.h | 117 + tools/urt/tools/quake3/common/threads.c | 623 +++ tools/urt/tools/quake3/common/trilib.c | 238 + tools/urt/tools/quake3/common/trilib.h | 29 + tools/urt/tools/quake3/common/unzip.c | 4599 +++++++++++++++++ tools/urt/tools/quake3/common/unzip.h | 324 ++ tools/urt/tools/quake3/common/vfs.c | 365 ++ tools/urt/tools/quake3/common/vfs.h | 41 + tools/urt/tools/quake3/q3data/.cvsignore | 12 + tools/urt/tools/quake3/q3data/.cvswrappers | 2 + tools/urt/tools/quake3/q3data/3dslib.c | 630 +++ tools/urt/tools/quake3/q3data/3dslib.h | 118 + tools/urt/tools/quake3/q3data/compress.c | 750 +++ tools/urt/tools/quake3/q3data/images.c | 465 ++ tools/urt/tools/quake3/q3data/md3lib.c | 193 + tools/urt/tools/quake3/q3data/md3lib.h | 7 + tools/urt/tools/quake3/q3data/models.c | 2134 ++++++++ tools/urt/tools/quake3/q3data/oldstuff.c | 130 + tools/urt/tools/quake3/q3data/p3dlib.c | 324 ++ tools/urt/tools/quake3/q3data/p3dlib.h | 8 + tools/urt/tools/quake3/q3data/polyset.c | 252 + tools/urt/tools/quake3/q3data/q3data.c | 645 +++ tools/urt/tools/quake3/q3data/q3data.dsp | 200 + tools/urt/tools/quake3/q3data/q3data.h | 78 + tools/urt/tools/quake3/q3data/q3data.vcproj | 207 + tools/urt/tools/quake3/q3data/stripper.c | 282 + tools/urt/tools/quake3/q3data/video.c | 1132 ++++ tools/urt/tools/quake3/q3map2/.cvsignore | 12 + tools/urt/tools/quake3/q3map2/brush.c | 986 ++++ tools/urt/tools/quake3/q3map2/brush_primit.c | 84 + tools/urt/tools/quake3/q3map2/bsp.c | 877 ++++ .../tools/quake3/q3map2/bspfile_abstract.c | 838 +++ tools/urt/tools/quake3/q3map2/bspfile_ibsp.c | 587 +++ tools/urt/tools/quake3/q3map2/bspfile_rbsp.c | 343 ++ .../urt/tools/quake3/q3map2/changelog.q3map1 | 371 ++ .../tools/quake3/q3map2/changelog.q3map2.txt | 707 +++ tools/urt/tools/quake3/q3map2/convert_ase.c | 378 ++ tools/urt/tools/quake3/q3map2/convert_map.c | 447 ++ tools/urt/tools/quake3/q3map2/decals.c | 908 ++++ tools/urt/tools/quake3/q3map2/facebsp.c | 601 +++ tools/urt/tools/quake3/q3map2/fog.c | 806 +++ tools/urt/tools/quake3/q3map2/game_ef.h | 198 + tools/urt/tools/quake3/q3map2/game_etut.h | 268 + tools/urt/tools/quake3/q3map2/game_ja.h | 192 + tools/urt/tools/quake3/q3map2/game_jk2.h | 186 + tools/urt/tools/quake3/q3map2/game_quake3.h | 196 + tools/urt/tools/quake3/q3map2/game_sof2.h | 261 + tools/urt/tools/quake3/q3map2/game_t.h | 37 + tools/urt/tools/quake3/q3map2/game_tenebrae.h | 196 + tools/urt/tools/quake3/q3map2/game_wolf.h | 242 + tools/urt/tools/quake3/q3map2/game_wolfet.h | 181 + tools/urt/tools/quake3/q3map2/image.c | 471 ++ tools/urt/tools/quake3/q3map2/leakfile.c | 130 + tools/urt/tools/quake3/q3map2/light.c | 2544 +++++++++ tools/urt/tools/quake3/q3map2/light_bounce.c | 958 ++++ tools/urt/tools/quake3/q3map2/light_shadows.c | 127 + tools/urt/tools/quake3/q3map2/light_trace.c | 1763 +++++++ tools/urt/tools/quake3/q3map2/light_ydnar.c | 4119 +++++++++++++++ tools/urt/tools/quake3/q3map2/lightmaps.c | 499 ++ .../urt/tools/quake3/q3map2/lightmaps_ydnar.c | 3102 +++++++++++ tools/urt/tools/quake3/q3map2/listen.pl | 46 + tools/urt/tools/quake3/q3map2/main.c | 650 +++ tools/urt/tools/quake3/q3map2/map.c | 1670 ++++++ tools/urt/tools/quake3/q3map2/mesh.c | 829 +++ tools/urt/tools/quake3/q3map2/model.c | 792 +++ tools/urt/tools/quake3/q3map2/patch.c | 528 ++ tools/urt/tools/quake3/q3map2/patch.patch | 1891 +++++++ tools/urt/tools/quake3/q3map2/path_init.c | 465 ++ tools/urt/tools/quake3/q3map2/portals.c | 974 ++++ tools/urt/tools/quake3/q3map2/prtfile.c | 295 ++ tools/urt/tools/quake3/q3map2/q3map2.dsp | 379 ++ tools/urt/tools/quake3/q3map2/q3map2.dsw | 137 + tools/urt/tools/quake3/q3map2/q3map2.h | 2354 +++++++++ tools/urt/tools/quake3/q3map2/q3map2.h.rej | 19 + tools/urt/tools/quake3/q3map2/q3map2.ico | Bin 0 -> 1078 bytes tools/urt/tools/quake3/q3map2/q3map2.opt | Bin 0 -> 98304 bytes tools/urt/tools/quake3/q3map2/q3map2.plg | 86 + tools/urt/tools/quake3/q3map2/q3map2.rc | 1 + tools/urt/tools/quake3/q3map2/q3map2.sln | 190 + tools/urt/tools/quake3/q3map2/q3map2.suo | Bin 0 -> 100864 bytes tools/urt/tools/quake3/q3map2/q3map2.vcproj | 1339 +++++ .../urt/tools/quake3/q3map2/q3map2.vcproj.rej | 17 + tools/urt/tools/quake3/q3map2/shaders.c | 2068 ++++++++ tools/urt/tools/quake3/q3map2/surface.c | 3605 +++++++++++++ tools/urt/tools/quake3/q3map2/surface_extra.c | 448 ++ .../urt/tools/quake3/q3map2/surface_foliage.c | 331 ++ tools/urt/tools/quake3/q3map2/surface_fur.c | 132 + tools/urt/tools/quake3/q3map2/surface_meta.c | 1684 ++++++ tools/urt/tools/quake3/q3map2/tjunction.c | 731 +++ tools/urt/tools/quake3/q3map2/tree.c | 162 + tools/urt/tools/quake3/q3map2/version.h | 4 + tools/urt/tools/quake3/q3map2/vis.c | 1137 ++++ tools/urt/tools/quake3/q3map2/visflow.c | 1713 ++++++ tools/urt/tools/quake3/q3map2/writebsp.c | 933 ++++ 507 files changed, 162910 insertions(+) create mode 100644 tools/urt/libs/.cvsignore create mode 100644 tools/urt/libs/archivelib.cpp create mode 100644 tools/urt/libs/archivelib.h create mode 100644 tools/urt/libs/bytebool.cpp create mode 100644 tools/urt/libs/bytebool.h create mode 100644 tools/urt/libs/bytestreamutils.cpp create mode 100644 tools/urt/libs/bytestreamutils.h create mode 100644 tools/urt/libs/character.cpp create mode 100644 tools/urt/libs/character.h create mode 100644 tools/urt/libs/cmdlib.h create mode 100644 tools/urt/libs/cmdlib/.cvsignore create mode 100644 tools/urt/libs/cmdlib/.cvswrappers create mode 100644 tools/urt/libs/cmdlib/cmdlib.cpp create mode 100644 tools/urt/libs/cmdlib/cmdlib.dsp create mode 100644 tools/urt/libs/cmdlib/cmdlib.vcproj create mode 100644 tools/urt/libs/container/array.cpp create mode 100644 tools/urt/libs/container/array.h create mode 100644 tools/urt/libs/container/cache.cpp create mode 100644 tools/urt/libs/container/cache.h create mode 100644 tools/urt/libs/container/container.cpp create mode 100644 tools/urt/libs/container/container.h create mode 100644 tools/urt/libs/container/hashfunc.cpp create mode 100644 tools/urt/libs/container/hashfunc.h create mode 100644 tools/urt/libs/container/hashtable.cpp create mode 100644 tools/urt/libs/container/hashtable.h create mode 100644 tools/urt/libs/container/stack.cpp create mode 100644 tools/urt/libs/container/stack.h create mode 100644 tools/urt/libs/convert.cpp create mode 100644 tools/urt/libs/convert.h create mode 100644 tools/urt/libs/ddslib.h create mode 100644 tools/urt/libs/ddslib/ddslib.c create mode 100644 tools/urt/libs/ddslib/ddslib.dsp create mode 100644 tools/urt/libs/ddslib/ddslib.plg create mode 100644 tools/urt/libs/ddslib/ddslib.vcproj create mode 100644 tools/urt/libs/debugging/debugging.cpp create mode 100644 tools/urt/libs/debugging/debugging.h create mode 100644 tools/urt/libs/dragplanes.cpp create mode 100644 tools/urt/libs/dragplanes.h create mode 100644 tools/urt/libs/eclasslib.cpp create mode 100644 tools/urt/libs/eclasslib.h create mode 100644 tools/urt/libs/entitylib.cpp create mode 100644 tools/urt/libs/entitylib.h create mode 100644 tools/urt/libs/entityxml.cpp create mode 100644 tools/urt/libs/entityxml.h create mode 100644 tools/urt/libs/fs_filesystem.cpp create mode 100644 tools/urt/libs/fs_filesystem.h create mode 100644 tools/urt/libs/fs_path.cpp create mode 100644 tools/urt/libs/fs_path.h create mode 100644 tools/urt/libs/generic/arrayrange.cpp create mode 100644 tools/urt/libs/generic/arrayrange.h create mode 100644 tools/urt/libs/generic/bitfield.cpp create mode 100644 tools/urt/libs/generic/bitfield.h create mode 100644 tools/urt/libs/generic/callback.cpp create mode 100644 tools/urt/libs/generic/callback.h create mode 100644 tools/urt/libs/generic/enumeration.cpp create mode 100644 tools/urt/libs/generic/enumeration.h create mode 100644 tools/urt/libs/generic/object.cpp create mode 100644 tools/urt/libs/generic/object.h create mode 100644 tools/urt/libs/generic/reference.cpp create mode 100644 tools/urt/libs/generic/reference.h create mode 100644 tools/urt/libs/generic/referencecounted.cpp create mode 100644 tools/urt/libs/generic/referencecounted.h create mode 100644 tools/urt/libs/generic/static.cpp create mode 100644 tools/urt/libs/generic/static.h create mode 100644 tools/urt/libs/gtkutil/accelerator.cpp create mode 100644 tools/urt/libs/gtkutil/accelerator.h create mode 100644 tools/urt/libs/gtkutil/button.cpp create mode 100644 tools/urt/libs/gtkutil/button.h create mode 100644 tools/urt/libs/gtkutil/clipboard.cpp create mode 100644 tools/urt/libs/gtkutil/clipboard.h create mode 100644 tools/urt/libs/gtkutil/closure.cpp create mode 100644 tools/urt/libs/gtkutil/closure.h create mode 100644 tools/urt/libs/gtkutil/container.cpp create mode 100644 tools/urt/libs/gtkutil/container.h create mode 100644 tools/urt/libs/gtkutil/cursor.cpp create mode 100644 tools/urt/libs/gtkutil/cursor.h create mode 100644 tools/urt/libs/gtkutil/dialog.cpp create mode 100644 tools/urt/libs/gtkutil/dialog.h create mode 100644 tools/urt/libs/gtkutil/entry.cpp create mode 100644 tools/urt/libs/gtkutil/entry.h create mode 100644 tools/urt/libs/gtkutil/filechooser.cpp create mode 100644 tools/urt/libs/gtkutil/filechooser.h create mode 100644 tools/urt/libs/gtkutil/frame.cpp create mode 100644 tools/urt/libs/gtkutil/frame.h create mode 100644 tools/urt/libs/gtkutil/glfont.cpp create mode 100644 tools/urt/libs/gtkutil/glfont.h create mode 100644 tools/urt/libs/gtkutil/glwidget.cpp create mode 100644 tools/urt/libs/gtkutil/glwidget.h create mode 100644 tools/urt/libs/gtkutil/gtkutil.vcproj create mode 100644 tools/urt/libs/gtkutil/idledraw.cpp create mode 100644 tools/urt/libs/gtkutil/idledraw.h create mode 100644 tools/urt/libs/gtkutil/image.cpp create mode 100644 tools/urt/libs/gtkutil/image.h create mode 100644 tools/urt/libs/gtkutil/menu.cpp create mode 100644 tools/urt/libs/gtkutil/menu.h create mode 100644 tools/urt/libs/gtkutil/messagebox.cpp create mode 100644 tools/urt/libs/gtkutil/messagebox.h create mode 100644 tools/urt/libs/gtkutil/nonmodal.cpp create mode 100644 tools/urt/libs/gtkutil/nonmodal.h create mode 100644 tools/urt/libs/gtkutil/paned.cpp create mode 100644 tools/urt/libs/gtkutil/paned.h create mode 100644 tools/urt/libs/gtkutil/pointer.cpp create mode 100644 tools/urt/libs/gtkutil/pointer.h create mode 100644 tools/urt/libs/gtkutil/toolbar.cpp create mode 100644 tools/urt/libs/gtkutil/toolbar.h create mode 100644 tools/urt/libs/gtkutil/widget.cpp create mode 100644 tools/urt/libs/gtkutil/widget.h create mode 100644 tools/urt/libs/gtkutil/window.cpp create mode 100644 tools/urt/libs/gtkutil/window.h create mode 100644 tools/urt/libs/gtkutil/xorrectangle.cpp create mode 100644 tools/urt/libs/gtkutil/xorrectangle.h create mode 100644 tools/urt/libs/imagelib.cpp create mode 100644 tools/urt/libs/imagelib.h create mode 100644 tools/urt/libs/instancelib.cpp create mode 100644 tools/urt/libs/instancelib.h create mode 100644 tools/urt/libs/jpeg6/.cvsignore create mode 100644 tools/urt/libs/jpeg6/.cvswrappers create mode 100644 tools/urt/libs/jpeg6/jchuff.h create mode 100644 tools/urt/libs/jpeg6/jcomapi.cpp create mode 100644 tools/urt/libs/jpeg6/jconfig.h create mode 100644 tools/urt/libs/jpeg6/jdapimin.cpp create mode 100644 tools/urt/libs/jpeg6/jdapistd.cpp create mode 100644 tools/urt/libs/jpeg6/jdatasrc.cpp create mode 100644 tools/urt/libs/jpeg6/jdcoefct.cpp create mode 100644 tools/urt/libs/jpeg6/jdcolor.cpp create mode 100644 tools/urt/libs/jpeg6/jdct.h create mode 100644 tools/urt/libs/jpeg6/jddctmgr.cpp create mode 100644 tools/urt/libs/jpeg6/jdhuff.cpp create mode 100644 tools/urt/libs/jpeg6/jdhuff.h create mode 100644 tools/urt/libs/jpeg6/jdinput.cpp create mode 100644 tools/urt/libs/jpeg6/jdmainct.cpp create mode 100644 tools/urt/libs/jpeg6/jdmarker.cpp create mode 100644 tools/urt/libs/jpeg6/jdmaster.cpp create mode 100644 tools/urt/libs/jpeg6/jdpostct.cpp create mode 100644 tools/urt/libs/jpeg6/jdsample.cpp create mode 100644 tools/urt/libs/jpeg6/jdtrans.cpp create mode 100644 tools/urt/libs/jpeg6/jerror.cpp create mode 100644 tools/urt/libs/jpeg6/jerror.h create mode 100644 tools/urt/libs/jpeg6/jfdctflt.cpp create mode 100644 tools/urt/libs/jpeg6/jidctflt.cpp create mode 100644 tools/urt/libs/jpeg6/jinclude.h create mode 100644 tools/urt/libs/jpeg6/jmemmgr.cpp create mode 100644 tools/urt/libs/jpeg6/jmemnobs.cpp create mode 100644 tools/urt/libs/jpeg6/jmemsys.h create mode 100644 tools/urt/libs/jpeg6/jmorecfg.h create mode 100644 tools/urt/libs/jpeg6/jpeg6.dsp create mode 100644 tools/urt/libs/jpeg6/jpeg6.vcproj create mode 100644 tools/urt/libs/jpeg6/jpegint.h create mode 100644 tools/urt/libs/jpeg6/jpgload.cpp create mode 100644 tools/urt/libs/jpeg6/jutils.cpp create mode 100644 tools/urt/libs/jpeg6/jversion.h create mode 100644 tools/urt/libs/jpeglib.h create mode 100644 tools/urt/libs/l_net/.cvsignore create mode 100644 tools/urt/libs/l_net/l_net.c create mode 100644 tools/urt/libs/l_net/l_net.dsp create mode 100644 tools/urt/libs/l_net/l_net.h create mode 100644 tools/urt/libs/l_net/l_net.vcproj create mode 100644 tools/urt/libs/l_net/l_net_berkley.c create mode 100644 tools/urt/libs/l_net/l_net_wins.c create mode 100644 tools/urt/libs/l_net/l_net_wins.h create mode 100644 tools/urt/libs/libs.vcproj create mode 100644 tools/urt/libs/maplib.cpp create mode 100644 tools/urt/libs/maplib.h create mode 100644 tools/urt/libs/math/aabb.cpp create mode 100644 tools/urt/libs/math/aabb.h create mode 100644 tools/urt/libs/math/curve.cpp create mode 100644 tools/urt/libs/math/curve.h create mode 100644 tools/urt/libs/math/expression.cpp create mode 100644 tools/urt/libs/math/expression.h create mode 100644 tools/urt/libs/math/frustum.cpp create mode 100644 tools/urt/libs/math/frustum.h create mode 100644 tools/urt/libs/math/line.cpp create mode 100644 tools/urt/libs/math/line.h create mode 100644 tools/urt/libs/math/matrix.cpp create mode 100644 tools/urt/libs/math/matrix.h create mode 100644 tools/urt/libs/math/pi.cpp create mode 100644 tools/urt/libs/math/pi.h create mode 100644 tools/urt/libs/math/plane.cpp create mode 100644 tools/urt/libs/math/plane.h create mode 100644 tools/urt/libs/math/quaternion.cpp create mode 100644 tools/urt/libs/math/quaternion.h create mode 100644 tools/urt/libs/math/vector.cpp create mode 100644 tools/urt/libs/math/vector.h create mode 100644 tools/urt/libs/mathlib.h create mode 100644 tools/urt/libs/mathlib/bbox.c create mode 100644 tools/urt/libs/mathlib/line.c create mode 100644 tools/urt/libs/mathlib/linear.c create mode 100644 tools/urt/libs/mathlib/m4x4.c create mode 100644 tools/urt/libs/mathlib/mathlib.c create mode 100644 tools/urt/libs/mathlib/mathlib.dsp create mode 100644 tools/urt/libs/mathlib/mathlib.vcproj create mode 100644 tools/urt/libs/mathlib/ray.c create mode 100644 tools/urt/libs/md5lib.h create mode 100644 tools/urt/libs/md5lib/md5lib.c create mode 100644 tools/urt/libs/md5lib/md5lib.dsp create mode 100644 tools/urt/libs/md5lib/md5lib.vcproj create mode 100644 tools/urt/libs/memory/allocator.cpp create mode 100644 tools/urt/libs/memory/allocator.h create mode 100644 tools/urt/libs/moduleobservers.cpp create mode 100644 tools/urt/libs/moduleobservers.h create mode 100644 tools/urt/libs/modulesystem/moduleregistry.cpp create mode 100644 tools/urt/libs/modulesystem/moduleregistry.h create mode 100644 tools/urt/libs/modulesystem/modulesmap.cpp create mode 100644 tools/urt/libs/modulesystem/modulesmap.h create mode 100644 tools/urt/libs/modulesystem/singletonmodule.cpp create mode 100644 tools/urt/libs/modulesystem/singletonmodule.h create mode 100644 tools/urt/libs/os/dir.cpp create mode 100644 tools/urt/libs/os/dir.h create mode 100644 tools/urt/libs/os/file.cpp create mode 100644 tools/urt/libs/os/file.h create mode 100644 tools/urt/libs/os/path.cpp create mode 100644 tools/urt/libs/os/path.h create mode 100644 tools/urt/libs/picomodel.h create mode 100644 tools/urt/libs/picomodel/lwo/clip.c create mode 100644 tools/urt/libs/picomodel/lwo/envelope.c create mode 100644 tools/urt/libs/picomodel/lwo/libs_rar.rar create mode 100644 tools/urt/libs/picomodel/lwo/list.c create mode 100644 tools/urt/libs/picomodel/lwo/lwio.c create mode 100644 tools/urt/libs/picomodel/lwo/lwo2.c create mode 100644 tools/urt/libs/picomodel/lwo/lwo2.h create mode 100644 tools/urt/libs/picomodel/lwo/lwob.c create mode 100644 tools/urt/libs/picomodel/lwo/pntspols.c create mode 100644 tools/urt/libs/picomodel/lwo/surface.c create mode 100644 tools/urt/libs/picomodel/lwo/vecmath.c create mode 100644 tools/urt/libs/picomodel/lwo/vmap.c create mode 100644 tools/urt/libs/picomodel/picointernal.c create mode 100644 tools/urt/libs/picomodel/picointernal.h create mode 100644 tools/urt/libs/picomodel/picomodel.c create mode 100644 tools/urt/libs/picomodel/picomodel.dsp create mode 100644 tools/urt/libs/picomodel/picomodel.plg create mode 100644 tools/urt/libs/picomodel/picomodel.vcproj create mode 100644 tools/urt/libs/picomodel/picomodules.c create mode 100644 tools/urt/libs/picomodel/pm_3ds.c create mode 100644 tools/urt/libs/picomodel/pm_ase.c create mode 100644 tools/urt/libs/picomodel/pm_fm.c create mode 100644 tools/urt/libs/picomodel/pm_fm.h create mode 100644 tools/urt/libs/picomodel/pm_lwo.c create mode 100644 tools/urt/libs/picomodel/pm_md2.c create mode 100644 tools/urt/libs/picomodel/pm_md3.c create mode 100644 tools/urt/libs/picomodel/pm_mdc.c create mode 100644 tools/urt/libs/picomodel/pm_ms3d.c create mode 100644 tools/urt/libs/picomodel/pm_obj.c create mode 100644 tools/urt/libs/picomodel/pm_terrain.c create mode 100644 tools/urt/libs/pivot.cpp create mode 100644 tools/urt/libs/pivot.h create mode 100644 tools/urt/libs/profile/file.cpp create mode 100644 tools/urt/libs/profile/file.h create mode 100644 tools/urt/libs/profile/profile.cpp create mode 100644 tools/urt/libs/profile/profile.h create mode 100644 tools/urt/libs/profile/profile.vcproj create mode 100644 tools/urt/libs/radiant_jpeglib.h create mode 100644 tools/urt/libs/render.cpp create mode 100644 tools/urt/libs/render.h create mode 100644 tools/urt/libs/scenelib.cpp create mode 100644 tools/urt/libs/scenelib.h create mode 100644 tools/urt/libs/script/scripttokeniser.cpp create mode 100644 tools/urt/libs/script/scripttokeniser.h create mode 100644 tools/urt/libs/script/scripttokenwriter.cpp create mode 100644 tools/urt/libs/script/scripttokenwriter.h create mode 100644 tools/urt/libs/selectionlib.cpp create mode 100644 tools/urt/libs/selectionlib.h create mode 100644 tools/urt/libs/shaderlib.cpp create mode 100644 tools/urt/libs/shaderlib.h create mode 100644 tools/urt/libs/splines/.cvsignore create mode 100644 tools/urt/libs/splines/Splines.dsp create mode 100644 tools/urt/libs/splines/Splines.vcproj create mode 100644 tools/urt/libs/splines/math_angles.cpp create mode 100644 tools/urt/libs/splines/math_angles.h create mode 100644 tools/urt/libs/splines/math_matrix.cpp create mode 100644 tools/urt/libs/splines/math_matrix.h create mode 100644 tools/urt/libs/splines/math_quaternion.cpp create mode 100644 tools/urt/libs/splines/math_quaternion.h create mode 100644 tools/urt/libs/splines/math_vector.cpp create mode 100644 tools/urt/libs/splines/math_vector.h create mode 100644 tools/urt/libs/splines/q_parse.cpp create mode 100644 tools/urt/libs/splines/q_shared.cpp create mode 100644 tools/urt/libs/splines/q_shared.h create mode 100644 tools/urt/libs/splines/splines.cpp create mode 100644 tools/urt/libs/splines/splines.h create mode 100644 tools/urt/libs/splines/util_list.h create mode 100644 tools/urt/libs/splines/util_str.cpp create mode 100644 tools/urt/libs/splines/util_str.h create mode 100644 tools/urt/libs/str.cpp create mode 100644 tools/urt/libs/str.h create mode 100644 tools/urt/libs/stream/filestream.cpp create mode 100644 tools/urt/libs/stream/filestream.h create mode 100644 tools/urt/libs/stream/memstream.cpp create mode 100644 tools/urt/libs/stream/memstream.h create mode 100644 tools/urt/libs/stream/stringstream.cpp create mode 100644 tools/urt/libs/stream/stringstream.h create mode 100644 tools/urt/libs/stream/textfilestream.cpp create mode 100644 tools/urt/libs/stream/textfilestream.h create mode 100644 tools/urt/libs/stream/textstream.cpp create mode 100644 tools/urt/libs/stream/textstream.h create mode 100644 tools/urt/libs/string/string.cpp create mode 100644 tools/urt/libs/string/string.h create mode 100644 tools/urt/libs/string/stringfwd.cpp create mode 100644 tools/urt/libs/string/stringfwd.h create mode 100644 tools/urt/libs/stringio.cpp create mode 100644 tools/urt/libs/stringio.h create mode 100644 tools/urt/libs/texturelib.cpp create mode 100644 tools/urt/libs/texturelib.h create mode 100644 tools/urt/libs/transformlib.cpp create mode 100644 tools/urt/libs/transformlib.h create mode 100644 tools/urt/libs/traverselib.cpp create mode 100644 tools/urt/libs/traverselib.h create mode 100644 tools/urt/libs/typesystem.cpp create mode 100644 tools/urt/libs/typesystem.h create mode 100644 tools/urt/libs/undolib.cpp create mode 100644 tools/urt/libs/undolib.h create mode 100644 tools/urt/libs/uniquenames.cpp create mode 100644 tools/urt/libs/uniquenames.h create mode 100644 tools/urt/libs/versionlib.cpp create mode 100644 tools/urt/libs/versionlib.h create mode 100644 tools/urt/libs/xml/ixml.cpp create mode 100644 tools/urt/libs/xml/ixml.h create mode 100644 tools/urt/libs/xml/xmlelement.cpp create mode 100644 tools/urt/libs/xml/xmlelement.h create mode 100644 tools/urt/libs/xml/xmlparser.cpp create mode 100644 tools/urt/libs/xml/xmlparser.h create mode 100644 tools/urt/libs/xml/xmlwriter.cpp create mode 100644 tools/urt/libs/xml/xmlwriter.h create mode 100644 tools/urt/tools/quake3/common/aselib.c create mode 100644 tools/urt/tools/quake3/common/aselib.h create mode 100644 tools/urt/tools/quake3/common/bspfile.c create mode 100644 tools/urt/tools/quake3/common/bspfile.h create mode 100644 tools/urt/tools/quake3/common/cmdlib.c create mode 100644 tools/urt/tools/quake3/common/cmdlib.h create mode 100644 tools/urt/tools/quake3/common/glib-object.h create mode 100644 tools/urt/tools/quake3/common/glib.h create mode 100644 tools/urt/tools/quake3/common/glib/galloca.h create mode 100644 tools/urt/tools/quake3/common/glib/garray.h create mode 100644 tools/urt/tools/quake3/common/glib/gasyncqueue.h create mode 100644 tools/urt/tools/quake3/common/glib/gatomic.h create mode 100644 tools/urt/tools/quake3/common/glib/gbacktrace.h create mode 100644 tools/urt/tools/quake3/common/glib/gcache.h create mode 100644 tools/urt/tools/quake3/common/glib/gcompletion.h create mode 100644 tools/urt/tools/quake3/common/glib/gconvert.h create mode 100644 tools/urt/tools/quake3/common/glib/gdataset.h create mode 100644 tools/urt/tools/quake3/common/glib/gdate.h create mode 100644 tools/urt/tools/quake3/common/glib/gdir.h create mode 100644 tools/urt/tools/quake3/common/glib/gerror.h create mode 100644 tools/urt/tools/quake3/common/glib/gfileutils.h create mode 100644 tools/urt/tools/quake3/common/glib/ghash.h create mode 100644 tools/urt/tools/quake3/common/glib/ghook.h create mode 100644 tools/urt/tools/quake3/common/glib/gi18n-lib.h create mode 100644 tools/urt/tools/quake3/common/glib/gi18n.h create mode 100644 tools/urt/tools/quake3/common/glib/giochannel.h create mode 100644 tools/urt/tools/quake3/common/glib/glib-2.0.lib create mode 100644 tools/urt/tools/quake3/common/glib/glist.h create mode 100644 tools/urt/tools/quake3/common/glib/gmacros.h create mode 100644 tools/urt/tools/quake3/common/glib/gmain.h create mode 100644 tools/urt/tools/quake3/common/glib/gmarkup.h create mode 100644 tools/urt/tools/quake3/common/glib/gmem.h create mode 100644 tools/urt/tools/quake3/common/glib/gmessages.h create mode 100644 tools/urt/tools/quake3/common/glib/gnode.h create mode 100644 tools/urt/tools/quake3/common/glib/gpattern.h create mode 100644 tools/urt/tools/quake3/common/glib/gprimes.h create mode 100644 tools/urt/tools/quake3/common/glib/gprintf.h create mode 100644 tools/urt/tools/quake3/common/glib/gqsort.h create mode 100644 tools/urt/tools/quake3/common/glib/gquark.h create mode 100644 tools/urt/tools/quake3/common/glib/gqueue.h create mode 100644 tools/urt/tools/quake3/common/glib/grand.h create mode 100644 tools/urt/tools/quake3/common/glib/grel.h create mode 100644 tools/urt/tools/quake3/common/glib/gscanner.h create mode 100644 tools/urt/tools/quake3/common/glib/gshell.h create mode 100644 tools/urt/tools/quake3/common/glib/gslist.h create mode 100644 tools/urt/tools/quake3/common/glib/gspawn.h create mode 100644 tools/urt/tools/quake3/common/glib/gstrfuncs.h create mode 100644 tools/urt/tools/quake3/common/glib/gstring.h create mode 100644 tools/urt/tools/quake3/common/glib/gthread.h create mode 100644 tools/urt/tools/quake3/common/glib/gthreadpool.h create mode 100644 tools/urt/tools/quake3/common/glib/gtimer.h create mode 100644 tools/urt/tools/quake3/common/glib/gtree.h create mode 100644 tools/urt/tools/quake3/common/glib/gtypes.h create mode 100644 tools/urt/tools/quake3/common/glib/gunicode.h create mode 100644 tools/urt/tools/quake3/common/glib/gutils.h create mode 100644 tools/urt/tools/quake3/common/glib/gwin32.h create mode 100644 tools/urt/tools/quake3/common/glibconfig.h create mode 100644 tools/urt/tools/quake3/common/gmodule.h create mode 100644 tools/urt/tools/quake3/common/gobject/gboxed.h create mode 100644 tools/urt/tools/quake3/common/gobject/gclosure.h create mode 100644 tools/urt/tools/quake3/common/gobject/genums.h create mode 100644 tools/urt/tools/quake3/common/gobject/gmarshal.h create mode 100644 tools/urt/tools/quake3/common/gobject/gobject.h create mode 100644 tools/urt/tools/quake3/common/gobject/gobjectnotifyqueue.c create mode 100644 tools/urt/tools/quake3/common/gobject/gparam.h create mode 100644 tools/urt/tools/quake3/common/gobject/gparamspecs.h create mode 100644 tools/urt/tools/quake3/common/gobject/gsignal.h create mode 100644 tools/urt/tools/quake3/common/gobject/gsourceclosure.h create mode 100644 tools/urt/tools/quake3/common/gobject/gtype.h create mode 100644 tools/urt/tools/quake3/common/gobject/gtypemodule.h create mode 100644 tools/urt/tools/quake3/common/gobject/gtypeplugin.h create mode 100644 tools/urt/tools/quake3/common/gobject/gvalue.h create mode 100644 tools/urt/tools/quake3/common/gobject/gvaluearray.h create mode 100644 tools/urt/tools/quake3/common/gobject/gvaluecollector.h create mode 100644 tools/urt/tools/quake3/common/gobject/gvaluetypes.h create mode 100644 tools/urt/tools/quake3/common/imagelib.c create mode 100644 tools/urt/tools/quake3/common/imagelib.h create mode 100644 tools/urt/tools/quake3/common/inout.c create mode 100644 tools/urt/tools/quake3/common/inout.h create mode 100644 tools/urt/tools/quake3/common/l3dslib.c create mode 100644 tools/urt/tools/quake3/common/l3dslib.h create mode 100644 tools/urt/tools/quake3/common/md4.c create mode 100644 tools/urt/tools/quake3/common/mutex.c create mode 100644 tools/urt/tools/quake3/common/mutex.h create mode 100644 tools/urt/tools/quake3/common/polylib.c create mode 100644 tools/urt/tools/quake3/common/polylib.h create mode 100644 tools/urt/tools/quake3/common/polyset.h create mode 100644 tools/urt/tools/quake3/common/qfiles.h create mode 100644 tools/urt/tools/quake3/common/qthreads.h create mode 100644 tools/urt/tools/quake3/common/scriplib.c create mode 100644 tools/urt/tools/quake3/common/scriplib.h create mode 100644 tools/urt/tools/quake3/common/surfaceflags.h create mode 100644 tools/urt/tools/quake3/common/threads.c create mode 100644 tools/urt/tools/quake3/common/trilib.c create mode 100644 tools/urt/tools/quake3/common/trilib.h create mode 100644 tools/urt/tools/quake3/common/unzip.c create mode 100644 tools/urt/tools/quake3/common/unzip.h create mode 100644 tools/urt/tools/quake3/common/vfs.c create mode 100644 tools/urt/tools/quake3/common/vfs.h create mode 100644 tools/urt/tools/quake3/q3data/.cvsignore create mode 100644 tools/urt/tools/quake3/q3data/.cvswrappers create mode 100644 tools/urt/tools/quake3/q3data/3dslib.c create mode 100644 tools/urt/tools/quake3/q3data/3dslib.h create mode 100644 tools/urt/tools/quake3/q3data/compress.c create mode 100644 tools/urt/tools/quake3/q3data/images.c create mode 100644 tools/urt/tools/quake3/q3data/md3lib.c create mode 100644 tools/urt/tools/quake3/q3data/md3lib.h create mode 100644 tools/urt/tools/quake3/q3data/models.c create mode 100644 tools/urt/tools/quake3/q3data/oldstuff.c create mode 100644 tools/urt/tools/quake3/q3data/p3dlib.c create mode 100644 tools/urt/tools/quake3/q3data/p3dlib.h create mode 100644 tools/urt/tools/quake3/q3data/polyset.c create mode 100644 tools/urt/tools/quake3/q3data/q3data.c create mode 100644 tools/urt/tools/quake3/q3data/q3data.dsp create mode 100644 tools/urt/tools/quake3/q3data/q3data.h create mode 100644 tools/urt/tools/quake3/q3data/q3data.vcproj create mode 100644 tools/urt/tools/quake3/q3data/stripper.c create mode 100644 tools/urt/tools/quake3/q3data/video.c create mode 100644 tools/urt/tools/quake3/q3map2/.cvsignore create mode 100644 tools/urt/tools/quake3/q3map2/brush.c create mode 100644 tools/urt/tools/quake3/q3map2/brush_primit.c create mode 100644 tools/urt/tools/quake3/q3map2/bsp.c create mode 100644 tools/urt/tools/quake3/q3map2/bspfile_abstract.c create mode 100644 tools/urt/tools/quake3/q3map2/bspfile_ibsp.c create mode 100644 tools/urt/tools/quake3/q3map2/bspfile_rbsp.c create mode 100644 tools/urt/tools/quake3/q3map2/changelog.q3map1 create mode 100644 tools/urt/tools/quake3/q3map2/changelog.q3map2.txt create mode 100644 tools/urt/tools/quake3/q3map2/convert_ase.c create mode 100644 tools/urt/tools/quake3/q3map2/convert_map.c create mode 100644 tools/urt/tools/quake3/q3map2/decals.c create mode 100644 tools/urt/tools/quake3/q3map2/facebsp.c create mode 100644 tools/urt/tools/quake3/q3map2/fog.c create mode 100644 tools/urt/tools/quake3/q3map2/game_ef.h create mode 100644 tools/urt/tools/quake3/q3map2/game_etut.h create mode 100644 tools/urt/tools/quake3/q3map2/game_ja.h create mode 100644 tools/urt/tools/quake3/q3map2/game_jk2.h create mode 100644 tools/urt/tools/quake3/q3map2/game_quake3.h create mode 100644 tools/urt/tools/quake3/q3map2/game_sof2.h create mode 100644 tools/urt/tools/quake3/q3map2/game_t.h create mode 100644 tools/urt/tools/quake3/q3map2/game_tenebrae.h create mode 100644 tools/urt/tools/quake3/q3map2/game_wolf.h create mode 100644 tools/urt/tools/quake3/q3map2/game_wolfet.h create mode 100644 tools/urt/tools/quake3/q3map2/image.c create mode 100644 tools/urt/tools/quake3/q3map2/leakfile.c create mode 100644 tools/urt/tools/quake3/q3map2/light.c create mode 100644 tools/urt/tools/quake3/q3map2/light_bounce.c create mode 100644 tools/urt/tools/quake3/q3map2/light_shadows.c create mode 100644 tools/urt/tools/quake3/q3map2/light_trace.c create mode 100644 tools/urt/tools/quake3/q3map2/light_ydnar.c create mode 100644 tools/urt/tools/quake3/q3map2/lightmaps.c create mode 100644 tools/urt/tools/quake3/q3map2/lightmaps_ydnar.c create mode 100644 tools/urt/tools/quake3/q3map2/listen.pl create mode 100644 tools/urt/tools/quake3/q3map2/main.c create mode 100644 tools/urt/tools/quake3/q3map2/map.c create mode 100644 tools/urt/tools/quake3/q3map2/mesh.c create mode 100644 tools/urt/tools/quake3/q3map2/model.c create mode 100644 tools/urt/tools/quake3/q3map2/patch.c create mode 100644 tools/urt/tools/quake3/q3map2/patch.patch create mode 100644 tools/urt/tools/quake3/q3map2/path_init.c create mode 100644 tools/urt/tools/quake3/q3map2/portals.c create mode 100644 tools/urt/tools/quake3/q3map2/prtfile.c create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.dsp create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.dsw create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.h create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.h.rej create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.ico create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.opt create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.plg create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.rc create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.sln create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.suo create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.vcproj create mode 100644 tools/urt/tools/quake3/q3map2/q3map2.vcproj.rej create mode 100644 tools/urt/tools/quake3/q3map2/shaders.c create mode 100644 tools/urt/tools/quake3/q3map2/surface.c create mode 100644 tools/urt/tools/quake3/q3map2/surface_extra.c create mode 100644 tools/urt/tools/quake3/q3map2/surface_foliage.c create mode 100644 tools/urt/tools/quake3/q3map2/surface_fur.c create mode 100644 tools/urt/tools/quake3/q3map2/surface_meta.c create mode 100644 tools/urt/tools/quake3/q3map2/tjunction.c create mode 100644 tools/urt/tools/quake3/q3map2/tree.c create mode 100644 tools/urt/tools/quake3/q3map2/version.h create mode 100644 tools/urt/tools/quake3/q3map2/vis.c create mode 100644 tools/urt/tools/quake3/q3map2/visflow.c create mode 100644 tools/urt/tools/quake3/q3map2/writebsp.c diff --git a/tools/urt/libs/.cvsignore b/tools/urt/libs/.cvsignore new file mode 100644 index 00000000..62555de1 --- /dev/null +++ b/tools/urt/libs/.cvsignore @@ -0,0 +1 @@ +.consign diff --git a/tools/urt/libs/archivelib.cpp b/tools/urt/libs/archivelib.cpp new file mode 100644 index 00000000..d2ab0dfa --- /dev/null +++ b/tools/urt/libs/archivelib.cpp @@ -0,0 +1,2 @@ + +#include "archivelib.h" diff --git a/tools/urt/libs/archivelib.h b/tools/urt/libs/archivelib.h new file mode 100644 index 00000000..8224bd47 --- /dev/null +++ b/tools/urt/libs/archivelib.h @@ -0,0 +1,237 @@ + +#if !defined (INCLUDED_ARCHIVELIB_H) +#define INCLUDED_ARCHIVELIB_H + +#include "debugging/debugging.h" +#include "iarchive.h" +#include "stream/filestream.h" +#include "stream/textfilestream.h" +#include "memory/allocator.h" +#include "string/string.h" + +/// \brief A single-byte-reader wrapper around an InputStream. +/// Optimised for reading one byte at a time. +/// Uses a buffer to reduce the number of times the wrapped stream must be read. +template +class SingleByteInputStream +{ + typedef typename InputStreamType::byte_type byte_type; + enum { c_bufferSize = SIZE }; + + InputStreamType& m_inputStream; + byte_type m_buffer[c_bufferSize]; + byte_type* m_cur; + byte_type* m_end; + +public: + + SingleByteInputStream(InputStreamType& inputStream) : m_inputStream(inputStream), m_cur(m_buffer + c_bufferSize), m_end(m_cur) + { + } + bool readByte(byte_type& b) + { + if(m_cur == m_end) + { + if(m_end != m_buffer + c_bufferSize) + { + return false; + } + + m_end = m_buffer + m_inputStream.read(m_buffer, c_bufferSize); + m_cur = m_buffer; + + if(m_end == m_buffer) + { + return false; + } + } + + b = *m_cur++; + + return true; + } +}; + +/// \brief A binary-to-text wrapper around an InputStream. +/// Converts CRLF or LFCR line-endings to LF line-endings. +template +class BinaryToTextInputStream : public TextInputStream +{ + SingleByteInputStream m_inputStream; +public: + BinaryToTextInputStream(BinaryInputStreamType& inputStream) : m_inputStream(inputStream) + { + } + std::size_t read(char* buffer, std::size_t length) + { + char* p = buffer; + for(;;) + { + if(length != 0 && m_inputStream.readByte(*reinterpret_cast(p))) + { + if(*p != '\r') + { + ++p; + --length; + } + } + else + { + return p - buffer; + } + } + } +}; + +/// \brief An ArchiveFile which is stored uncompressed as part of a larger archive file. +class StoredArchiveFile : public ArchiveFile +{ + CopiedString m_name; + FileInputStream m_filestream; + SubFileInputStream m_substream; + FileInputStream::size_type m_size; +public: + typedef FileInputStream::size_type size_type; + typedef FileInputStream::position_type position_type; + + StoredArchiveFile(const char* name, const char* archiveName, position_type position, size_type stream_size, size_type file_size) + : m_name(name), m_filestream(archiveName), m_substream(m_filestream, position, stream_size), m_size(file_size) + { + } + + static StoredArchiveFile* create(const char* name, const char* archiveName, position_type position, size_type stream_size, size_type file_size) + { + return New().scalar(name, archiveName, position, stream_size, file_size); + } + + void release() + { + Delete().scalar(this); + } + size_type size() const + { + return m_size; + } + const char* getName() const + { + return m_name.c_str(); + } + InputStream& getInputStream() + { + return m_substream; + } +}; + +/// \brief An ArchiveTextFile which is stored uncompressed as part of a larger archive file. +class StoredArchiveTextFile : public ArchiveTextFile +{ + CopiedString m_name; + FileInputStream m_filestream; + SubFileInputStream m_substream; + BinaryToTextInputStream m_textStream; +public: + typedef FileInputStream::size_type size_type; + typedef FileInputStream::position_type position_type; + + StoredArchiveTextFile(const char* name, const char* archiveName, position_type position, size_type stream_size) + : m_name(name), m_filestream(archiveName), m_substream(m_filestream, position, stream_size), m_textStream(m_substream) + { + } + + static StoredArchiveTextFile* create(const char* name, const char* archiveName, position_type position, size_type stream_size) + { + return New().scalar(name, archiveName, position, stream_size); + } + + void release() + { + Delete().scalar(this); + } + const char* getName() const + { + return m_name.c_str(); + } + TextInputStream& getInputStream() + { + return m_textStream; + } +}; + +/// \brief An ArchiveFile which is stored as a single file on disk. +class DirectoryArchiveFile : public ArchiveFile +{ + CopiedString m_name; + FileInputStream m_istream; + FileInputStream::size_type m_size; +public: + typedef FileInputStream::size_type size_type; + + DirectoryArchiveFile(const char* name, const char* filename) + : m_name(name), m_istream(filename) + { + if(!failed()) + { + m_istream.seek(0, FileInputStream::end); + m_size = m_istream.tell(); + m_istream.seek(0); + } + else + { + m_size = 0; + } + } + bool failed() const + { + return m_istream.failed(); + } + + void release() + { + delete this; + } + size_type size() const + { + return m_size; + } + const char* getName() const + { + return m_name.c_str(); + } + InputStream& getInputStream() + { + return m_istream; + } +}; + +/// \brief An ArchiveTextFile which is stored as a single file on disk. +class DirectoryArchiveTextFile : public ArchiveTextFile +{ + CopiedString m_name; + TextFileInputStream m_inputStream; +public: + + DirectoryArchiveTextFile(const char* name, const char* filename) + : m_name(name), m_inputStream(filename) + { + } + bool failed() const + { + return m_inputStream.failed(); + } + + void release() + { + delete this; + } + const char* getName() const + { + return m_name.c_str(); + } + TextInputStream& getInputStream() + { + return m_inputStream; + } +}; + + +#endif diff --git a/tools/urt/libs/bytebool.cpp b/tools/urt/libs/bytebool.cpp new file mode 100644 index 00000000..d66010de --- /dev/null +++ b/tools/urt/libs/bytebool.cpp @@ -0,0 +1,2 @@ + +#include "bytebool.h" diff --git a/tools/urt/libs/bytebool.h b/tools/urt/libs/bytebool.h new file mode 100644 index 00000000..63874d05 --- /dev/null +++ b/tools/urt/libs/bytebool.h @@ -0,0 +1,11 @@ +#ifndef __BYTEBOOL__ +#define __BYTEBOOL__ + +// defines boolean and byte types usable in both c and c++ code +// this header is not really meant for direct inclusion, +// it is used by mathlib and cmdlib + +typedef enum { qfalse, qtrue } qboolean; +typedef unsigned char byte; + +#endif diff --git a/tools/urt/libs/bytestreamutils.cpp b/tools/urt/libs/bytestreamutils.cpp new file mode 100644 index 00000000..a4606ef1 --- /dev/null +++ b/tools/urt/libs/bytestreamutils.cpp @@ -0,0 +1,2 @@ + +#include "bytestreamutils.h" diff --git a/tools/urt/libs/bytestreamutils.h b/tools/urt/libs/bytestreamutils.h new file mode 100644 index 00000000..d01fd22e --- /dev/null +++ b/tools/urt/libs/bytestreamutils.h @@ -0,0 +1,100 @@ + +#if !defined(INCLUDED_BYTESTREAMUTILS_H) +#define INCLUDED_BYTESTREAMUTILS_H + +#include + +#if defined(_MSC_VER) + +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; + +#else + +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 + +#define __USE_ISOC9X 1 +#define __USE_ISOC99 1 + +#include + +#endif + + +template +inline void istream_read_little_endian(InputStreamType& istream, Type& value) +{ + istream.read(reinterpret_cast(&value), sizeof(Type)); +#if defined(__BIG_ENDIAN__) + std::reverse(reinterpret_cast(&value), reinterpret_cast(&value) + sizeof(Type)); +#endif +} + +template +inline void istream_read_big_endian(InputStreamType& istream, Type& value) +{ + istream.read(reinterpret_cast(&value), sizeof(Type)); +#if !defined(__BIG_ENDIAN__) + std::reverse(reinterpret_cast(&value), reinterpret_cast(&value) + sizeof(Type)); +#endif +} + +template +inline void istream_read_byte(InputStreamType& istream, typename InputStreamType::byte_type& b) +{ + istream.read(&b, 1); +} + + +template +inline int16_t istream_read_int16_le(InputStreamType& istream) +{ + int16_t value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline uint16_t istream_read_uint16_le(InputStreamType& istream) +{ + uint16_t value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline int32_t istream_read_int32_le(InputStreamType& istream) +{ + int32_t value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline uint32_t istream_read_uint32_le(InputStreamType& istream) +{ + uint32_t value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline float istream_read_float32_le(InputStreamType& istream) +{ + float value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline typename InputStreamType::byte_type istream_read_byte(InputStreamType& istream) +{ + typename InputStreamType::byte_type b; + istream.read(&b, sizeof(typename InputStreamType::byte_type)); + return b; +} + +#endif diff --git a/tools/urt/libs/character.cpp b/tools/urt/libs/character.cpp new file mode 100644 index 00000000..a83d71f3 --- /dev/null +++ b/tools/urt/libs/character.cpp @@ -0,0 +1,3 @@ + +#include "character.h" + diff --git a/tools/urt/libs/character.h b/tools/urt/libs/character.h new file mode 100644 index 00000000..457928bd --- /dev/null +++ b/tools/urt/libs/character.h @@ -0,0 +1,27 @@ + +#if !defined(INCLUDED_CHARACTER_H) +#define INCLUDED_CHARACTER_H + +/// \file +/// \brief Character encoding. + +/// \brief Returns true if \p c is an ASCII character that can be represented with 7 bits. +inline bool char_is_ascii(char c) +{ + return (c & 0x80) == 0; +} + +/// \brief Returns true if \p string consists entirely of ASCII characters. +inline bool string_is_ascii(const char* string) +{ + while(*string != '\0') + { + if(!char_is_ascii(*string++)) + { + return false; + } + } + return true; +} + +#endif diff --git a/tools/urt/libs/cmdlib.h b/tools/urt/libs/cmdlib.h new file mode 100644 index 00000000..be41c13e --- /dev/null +++ b/tools/urt/libs/cmdlib.h @@ -0,0 +1,106 @@ +/* +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// start of shared cmdlib stuff +// + +#ifndef __CMDLIB__ +#define __CMDLIB__ + +#include + + +// TTimo started adding portability code: +// return true if spawning was successful, false otherwise +// on win32 we have a bCreateConsole flag to create a new console or run inside the current one +//boolean Q_Exec(const char* pCmd, boolean bCreateConsole); +// execute a system command: +// cmd: the command to run +// cmdline: the command line +// NOTE TTimo following are win32 specific: +// execdir: the directory to execute in +// bCreateConsole: spawn a new console or not +// return values; +// if the spawn was fine +// TODO TTimo add functionality to track the process until it dies + +bool Q_Exec(const char *cmd, char *cmdline, const char *execdir, bool bCreateConsole); + +// some easy portability crap + + +#define access_owner_read 0400 +#define access_owner_write 0200 +#define access_owner_execute 0100 +#define access_owner_rw_ 0600 +#define access_owner_r_x 0500 +#define access_owner__wx 0300 +#define access_owner_rwx 0700 + +#define access_group_read 0040 +#define access_group_write 0020 +#define access_group_execute 0010 +#define access_group_rw_ 0060 +#define access_group_r_x 0050 +#define access_group__wx 0030 +#define access_group_rwx 0070 + +#define access_others_read 0004 +#define access_others_write 0002 +#define access_others_execute 0001 +#define access_others_rw_ 0006 +#define access_others_r_x 0005 +#define access_others__wx 0003 +#define access_others_rwx 0007 + + +#define access_rwxrwxr_x (access_owner_rwx | access_group_rwx | access_others_r_x) +#define access_rwxrwxrwx (access_owner_rwx | access_group_rwx | access_others_rwx) + +// Q_mkdir +// returns true if succeeded in creating directory +#ifdef WIN32 +#include +inline bool Q_mkdir(const char* name) +{ + return _mkdir(name) != -1; +} +#else +#include +inline bool Q_mkdir(const char* name) +{ + return mkdir(name, access_rwxrwxr_x) != -1; +} +#endif + + +inline double Sys_DoubleTime(void) +{ + return clock()/ 1000.0; +} + + + +#endif diff --git a/tools/urt/libs/cmdlib/.cvsignore b/tools/urt/libs/cmdlib/.cvsignore new file mode 100644 index 00000000..b5ee5ae1 --- /dev/null +++ b/tools/urt/libs/cmdlib/.cvsignore @@ -0,0 +1 @@ +Debug Release *.ncb *.opt *.plg *.001 *.BAK \ No newline at end of file diff --git a/tools/urt/libs/cmdlib/.cvswrappers b/tools/urt/libs/cmdlib/.cvswrappers new file mode 100644 index 00000000..cdfd6d4a --- /dev/null +++ b/tools/urt/libs/cmdlib/.cvswrappers @@ -0,0 +1,3 @@ +*.dsp -m 'COPY' -k 'b' +*.dsw -m 'COPY' -k 'b' +*.scc -m 'COPY' -k 'b' diff --git a/tools/urt/libs/cmdlib/cmdlib.cpp b/tools/urt/libs/cmdlib/cmdlib.cpp new file mode 100644 index 00000000..1bcaafa2 --- /dev/null +++ b/tools/urt/libs/cmdlib/cmdlib.cpp @@ -0,0 +1,133 @@ +/* +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// start of shared cmdlib stuff +// + +#include "cmdlib.h" + +#include +#include + +#include "string/string.h" +#include "os/path.h" +#include "container/array.h" + +#ifdef WIN32 + #include +#endif +#if defined (__linux__) || defined (__APPLE__) + #include +#endif + + +#if defined (__linux__) || defined (__APPLE__) +bool Q_Exec(const char *cmd, char *cmdline, const char *, bool) +{ + char fullcmd[2048]; + char *pCmd; +#ifdef _DEBUG + printf("Q_Exec damnit\n"); +#endif + switch (fork()) + { + case -1: + return true; + break; + case 0: + // always concat the command on linux + if (cmd) + { + strcpy(fullcmd, cmd); + } + else + fullcmd[0] = '\0'; + if (cmdline) + { + strcat(fullcmd, " "); + strcat(fullcmd, cmdline); + } + pCmd = fullcmd; + while (*pCmd == ' ') + pCmd++; +#ifdef _DEBUG + printf("Running system...\n"); + printf("Command: %s\n", pCmd); +#endif + system( pCmd ); +#ifdef _DEBUG + printf ("system() returned\n"); +#endif + _exit (0); + break; + } + return true; +} +#endif + +#ifdef WIN32 +// NOTE TTimo windows is VERY nitpicky about the syntax in CreateProcess +bool Q_Exec(const char *cmd, char *cmdline, const char *execdir, bool bCreateConsole) +{ + PROCESS_INFORMATION ProcessInformation; + STARTUPINFO startupinfo = {0}; + DWORD dwCreationFlags; + GetStartupInfo (&startupinfo); + if (bCreateConsole) + dwCreationFlags = CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS; + else + dwCreationFlags = DETACHED_PROCESS | NORMAL_PRIORITY_CLASS; + const char *pCmd; + char *pCmdline; + pCmd = cmd; + if (pCmd) + { + while (*pCmd == ' ') + pCmd++; + } + pCmdline = cmdline; + if (pCmdline) + { + while (*pCmdline == ' ') + pCmdline++; + } + + if (CreateProcess( + pCmd, + pCmdline, + NULL, + NULL, + FALSE, + dwCreationFlags, + NULL, + execdir, + &startupinfo, + &ProcessInformation + )) + return true; + return false; +} +#endif + diff --git a/tools/urt/libs/cmdlib/cmdlib.dsp b/tools/urt/libs/cmdlib/cmdlib.dsp new file mode 100644 index 00000000..ee229aa5 --- /dev/null +++ b/tools/urt/libs/cmdlib/cmdlib.dsp @@ -0,0 +1,101 @@ +# Microsoft Developer Studio Project File - Name="cmdlib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=cmdlib - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "cmdlib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "cmdlib.mak" CFG="cmdlib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cmdlib - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "cmdlib - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "cmdlib" +# PROP Scc_LocalPath ".." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cmdlib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE F90 /include:"Release/" +# ADD F90 /include:"Release/" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /O2 /I "..\\" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "cmdlib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE F90 /include:"Debug/" +# ADD F90 /include:"Debug/" +# ADD BASE CPP /nologo /W3 /GX /Z7 /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MDd /W3 /Z7 /Od /I "..\\" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "cmdlib - Win32 Release" +# Name "cmdlib - Win32 Debug" +# Begin Source File + +SOURCE=.\CMDLIB.cpp +# End Source File +# Begin Source File + +SOURCE=..\cmdlib.h +# End Source File +# End Target +# End Project diff --git a/tools/urt/libs/cmdlib/cmdlib.vcproj b/tools/urt/libs/cmdlib/cmdlib.vcproj new file mode 100644 index 00000000..2bd36f28 --- /dev/null +++ b/tools/urt/libs/cmdlib/cmdlib.vcproj @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/urt/libs/container/array.cpp b/tools/urt/libs/container/array.cpp new file mode 100644 index 00000000..a34630ff --- /dev/null +++ b/tools/urt/libs/container/array.cpp @@ -0,0 +1,19 @@ + +#include "array.h" + +namespace +{ + class Bleh + { + Array m_array; + public: + Bleh() : m_array(16) + { + } + }; + + void testAutoArray() + { + Array array(32); + } +} \ No newline at end of file diff --git a/tools/urt/libs/container/array.h b/tools/urt/libs/container/array.h new file mode 100644 index 00000000..08236fac --- /dev/null +++ b/tools/urt/libs/container/array.h @@ -0,0 +1,170 @@ + +#if !defined(INCLUDED_CONTAINER_ARRAY_H) +#define INCLUDED_CONTAINER_ARRAY_H + +#include +#include + +#include "memory/allocator.h" + +/// \brief An array whose size is variable at run-time. +/// +/// - Resizing the array destroys all the existing elements and invalidates all iterators. +/// - Default-Constructible, Copyable, Assignable. +/// - Compatible with the containers and algorithms in the Standard Template Library (STL) - http://www.sgi.com/tech/stl/ +/// +/// \param Element The type to be stored in the array. Must provide a default-constructor and a copy-constructor. +/// \param Allocator A custom memory-allocator, conforming to the std::allocator interface. +template > +class Array : public Allocator +{ + std::size_t m_size; + Element* m_data; + + Element* construct(std::size_t size) + { +#if 1 + return New(*this).vector(size); +#else + return new Element[size]; +#endif + } + template + Element* construct(std::size_t size, const T1& value) + { + return New(*this).vector(size, value); + } + void destroy(Element* data, std::size_t size) + { +#if 1 + Delete(*this).vector(data, size); +#else + delete[] data; +#endif + } + +public: + typedef Element value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + Array() + : m_size(0), m_data(0) + { + } + Array(std::size_t size) + : m_size(size), m_data(construct(size)) + { + } + template + Array(std::size_t size, const T1& value) + : m_size(size), m_data(construct(size, value)) + { + } + Array(const Array& other) + : Allocator(other), m_size(other.size()), m_data(construct(m_size)) + { + std::copy(other.begin(), other.end(), begin()); + } + template + Array(Iterator start, Iterator finish) + : m_size(std::distance(start, finish)), m_data(construct(m_size)) + { + std::copy(start, finish, begin()); + } + ~Array() + { + destroy(m_data, m_size); + } + + Array& operator=(const Array& other) + { + Array temp(other); + temp.swap(*this); + return *this; + } + + void swap(Array& other) + { + std::swap(m_size, other.m_size); + std::swap(m_data, other.m_data); + } + + iterator begin() + { + return m_data; + } + const_iterator begin() const + { + return m_data; + } + iterator end() + { + return m_data + m_size; + } + const_iterator end() const + { + return m_data + m_size; + } + + value_type& operator[](std::size_t index) + { +#if defined(_DEBUG) + ASSERT_MESSAGE(index < size(), "array index out of bounds"); +#endif + return m_data[index]; + } + const value_type& operator[](std::size_t index) const + { +#if defined(_DEBUG) + ASSERT_MESSAGE(index < size(), "array index out of bounds"); +#endif + return m_data[index]; + } + value_type* data() + { + return m_data; + } + const value_type* data() const + { + return m_data; + } + std::size_t size() const + { + return m_size; + } + bool empty() const + { + return m_size == 0; + } + + void resize(std::size_t count) + { + if(count != size()) + { + Array temp(count); + temp.swap(*this); + } + } + void resize(std::size_t count, const value_type& value) + { + if(count != size()) + { + Array temp(count, value); + temp.swap(*this); + } + } +}; + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap. + template + inline void swap(Array& self, Array& other) + { + self.swap(other); + } +} + +#endif diff --git a/tools/urt/libs/container/cache.cpp b/tools/urt/libs/container/cache.cpp new file mode 100644 index 00000000..e7fb2cc5 --- /dev/null +++ b/tools/urt/libs/container/cache.cpp @@ -0,0 +1,2 @@ + +#include "cache.h" diff --git a/tools/urt/libs/container/cache.h b/tools/urt/libs/container/cache.h new file mode 100644 index 00000000..f2d35245 --- /dev/null +++ b/tools/urt/libs/container/cache.h @@ -0,0 +1,177 @@ + +#if !defined(INCLUDED_CONTAINER_CACHE_H) +#define INCLUDED_CONTAINER_CACHE_H + +#include +#include "container/hashtable.h" +#include "memory/allocator.h" + +template +class DefaultCreationPolicy +{ +public: + Type* construct(const Parameter& parameter) + { + return New().scalar(parameter); + } + void destroy(Type* p) + { + Delete().scalar(p); + } +}; + +template +class SharedValue +{ + typedef Type value_type; + typedef value_type* pointer; + typedef value_type& reference; + + std::size_t m_count; + pointer m_value; + +public: + SharedValue() + : m_count(0), m_value(0) + { + } + ~SharedValue() + { + ASSERT_MESSAGE(m_count == 0 , "destroying a referenced object\n"); + } + void set(pointer value) + { + m_value = value; + } + pointer get() + { + return m_value; + } + std::size_t increment() + { + return ++m_count; + } + std::size_t decrement() + { + ASSERT_MESSAGE(!empty(), "destroying a non-existent object\n"); + return --m_count; + } + std::size_t count() + { + return m_count; + } + bool empty() + { + return m_count == 0; + } + reference operator*() const + { + ASSERT_NOTNULL(m_value); + return *m_value; + } + pointer operator->() const + { + return &(operator*()); + } +}; + + + +/// \brief Caches values that are uniquely identified by a key. +/// +/// - Automatically removes objects that are no longer referenced. +/// +/// \param Key Uniquely identifies each element. +/// \param Cached The type to be cached. Must define a constructor that accepts \c Key. +template, typename CreationPolicy = DefaultCreationPolicy > +class HashedCache : public CreationPolicy +{ + typedef SharedValue Element; + typedef HashTable map_type; + + map_type m_map; + +public: + explicit HashedCache(const CreationPolicy& creation = CreationPolicy()) + : CreationPolicy(creation), m_map(256) + { + } + ~HashedCache() + { + ASSERT_MESSAGE(empty(), "HashedCache::~HashedCache: not empty"); + } + + typedef typename map_type::iterator iterator; + typedef typename map_type::value_type value_type; + + iterator begin() + { + return m_map.begin(); + } + iterator end() + { + return m_map.end(); + } + + bool empty() const + { + return m_map.empty(); + } + + iterator find(const Key& key) + { + return m_map.find(key); + } + + void capture(iterator i) + { + (*i).value.increment(); + } + void release(iterator i) + { + if((*i).value.decrement() == 0) + { + CreationPolicy::destroy((*i).value.get()); + m_map.erase(i); + } + } + +#if 1 + Element& capture(const Key& key) + { + Element& elem = m_map[key]; + if(elem.increment() == 1) + { + elem.set(CreationPolicy::construct(key)); + } + return elem; + } +#else + value_type& capture(const Key& key) + { + iterator i = m_map.find(key); + if(i == m_map.end()) + { + Element element; + element.set(CreationPolicy::construct(key)); + i = m_map.insert(key, element); + } + (*i).value.increment(); + return (*i); + } +#endif + void release(const Key& key) + { + iterator i = m_map.find(key); + ASSERT_MESSAGE(i != m_map.end(), "releasing a non-existent object\n"); + release(i); + } + + void clear() + { + m_map.clear(); + } +}; + + +#endif diff --git a/tools/urt/libs/container/container.cpp b/tools/urt/libs/container/container.cpp new file mode 100644 index 00000000..6cf60ed4 --- /dev/null +++ b/tools/urt/libs/container/container.cpp @@ -0,0 +1,3 @@ + +#include "container.h" + diff --git a/tools/urt/libs/container/container.h b/tools/urt/libs/container/container.h new file mode 100644 index 00000000..73ebc0a2 --- /dev/null +++ b/tools/urt/libs/container/container.h @@ -0,0 +1,326 @@ + +#if !defined(INCLUDED_CONTAINER_CONTAINER_H) +#define INCLUDED_CONTAINER_CONTAINER_H + +#include +#include + +#include "generic/static.h" + +/// \brief A single-value container, which can either be empty or full. +template +class Single +{ + Type* m_value; +public: + Single() : m_value(0) + { + } + bool empty() + { + return m_value == 0; + } + Type* insert(const Type& other) + { + m_value = new Type(other); + return m_value; + } + void clear() + { + delete m_value; + m_value = 0; + } + Type& get() + { + //ASSERT_MESSAGE(!empty(), "Single: must be initialised before being accessed"); + return *m_value; + } + const Type& get() const + { + //ASSERT_MESSAGE(!empty(), "Single: must be initialised before being accessed"); + return *m_value; + } +}; + + +/// \brief An adaptor to make std::list into a Unique Sequence - which cannot contain the same value more than once. +/// \param Value Uniquely identifies itself. Must provide a copy-constructor and an equality operator. +template +class UnsortedSet +{ + typedef typename std::list Values; + Values m_values; +public: + typedef typename Values::iterator iterator; + typedef typename Values::const_iterator const_iterator; + typedef typename Values::reverse_iterator reverse_iterator; + typedef typename Values::const_reverse_iterator const_reverse_iterator; + + iterator begin() + { + return m_values.begin(); + } + const_iterator begin() const + { + return m_values.begin(); + } + iterator end() + { + return m_values.end(); + } + const_iterator end() const + { + return m_values.end(); + } + reverse_iterator rbegin() + { + return m_values.rbegin(); + } + const_reverse_iterator rbegin() const + { + return m_values.rbegin(); + } + reverse_iterator rend() + { + return m_values.rend(); + } + const_reverse_iterator rend() const + { + return m_values.rend(); + } + + bool empty() const + { + return m_values.empty(); + } + std::size_t size() const + { + return m_values.size(); + } + void clear() + { + m_values.clear(); + } + + void swap(UnsortedSet& other) + { + std::swap(m_values, other.m_values); + } + iterator insert(const Value& value) + { + ASSERT_MESSAGE(find(value) == end(), "UnsortedSet::insert: already added"); + m_values.push_back(value); + return --end(); + } + void erase(const Value& value) + { + iterator i = find(value); + ASSERT_MESSAGE(i != end(), "UnsortedSet::erase: not found"); + m_values.erase(i); + } + iterator find(const Value& value) + { + return std::find(begin(), end(), value); + } +}; + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap. + template + inline void swap(UnsortedSet& self, UnsortedSet& other) + { + self.swap(other); + } +} + +/// An adaptor to make std::list into a Unique Associative Sequence - which cannot contain the same value more than once. +/// Key: Uniquely identifies a value. Must provide a copy-constructor and an equality operator. +/// Value: Must provide a copy-constructor. +template +class UnsortedMap +{ + typedef typename std::list< std::pair > Values; + Values m_values; +public: + typedef typename Values::value_type value_type; + typedef typename Values::iterator iterator; + typedef typename Values::const_iterator const_iterator; + + iterator begin() + { + return m_values.begin(); + } + const_iterator begin() const + { + return m_values.begin(); + } + iterator end() + { + return m_values.end(); + } + const_iterator end() const + { + return m_values.end(); + } + + bool empty() const + { + return m_values.empty(); + } + std::size_t size() const + { + return m_values.size(); + } + void clear() + { + m_values.clear(); + } + + iterator insert(const value_type& value) + { + ASSERT_MESSAGE(find(value.first) == end(), "UnsortedMap::insert: already added"); + m_values.push_back(value); + return --m_values.end(); + } + void erase(const Key& key) + { + iterator i = find(key); + ASSERT_MESSAGE(i != end(), "UnsortedMap::erase: not found"); + erase(i); + } + void erase(iterator i) + { + m_values.erase(i); + } + iterator find(const Key& key) + { + for(iterator i = m_values.begin(); i != m_values.end(); ++i) + { + if((*i).first == key) + { + return i; + } + } + return m_values.end(); + } + const_iterator find(const Key& key) const + { + for(const_iterator i = m_values.begin(); i != m_values.end(); ++i) + { + if((*i).first == key) + { + return i; + } + } + return m_values.end(); + } + + Value& operator[](const Key& key) + { + iterator i = find(key); + if(i != end()) + { + return (*i).second; + } + + m_values.push_back(Values::value_type(key, Value())); + return m_values.back().second; + } +}; + +/// An adaptor to assert when duplicate values are added, or non-existent values removed from a std::set. +template +class UniqueSet +{ + typedef std::set Values; + Values m_values; +public: + typedef typename Values::iterator iterator; + typedef typename Values::const_iterator const_iterator; + typedef typename Values::reverse_iterator reverse_iterator; + typedef typename Values::const_reverse_iterator const_reverse_iterator; + + + iterator begin() + { + return m_values.begin(); + } + const_iterator begin() const + { + return m_values.begin(); + } + iterator end() + { + return m_values.end(); + } + const_iterator end() const + { + return m_values.end(); + } + reverse_iterator rbegin() + { + return m_values.rbegin(); + } + const_reverse_iterator rbegin() const + { + return m_values.rbegin(); + } + reverse_iterator rend() + { + return m_values.rend(); + } + const_reverse_iterator rend() const + { + return m_values.rend(); + } + + bool empty() const + { + return m_values.empty(); + } + std::size_t size() const + { + return m_values.size(); + } + void clear() + { + m_values.clear(); + } + + void swap(UniqueSet& other) + { + std::swap(m_values, other.m_values); + } + iterator insert(const Value& value) + { + std::pair result = m_values.insert(value); + ASSERT_MESSAGE(result.second, "UniqueSet::insert: already added"); + return result.first; + } + void erase(const Value& value) + { + iterator i = find(value); + ASSERT_MESSAGE(i != end(), "UniqueSet::erase: not found"); + m_values.erase(i); + } + iterator find(const Value& value) + { + return std::find(begin(), end(), value); + } +}; + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap. + template + inline void swap(UniqueSet& self, UniqueSet& other) + { + self.swap(other); + } +} + + +#endif diff --git a/tools/urt/libs/container/hashfunc.cpp b/tools/urt/libs/container/hashfunc.cpp new file mode 100644 index 00000000..712de39a --- /dev/null +++ b/tools/urt/libs/container/hashfunc.cpp @@ -0,0 +1,2 @@ + +#include "hashfunc.h" diff --git a/tools/urt/libs/container/hashfunc.h b/tools/urt/libs/container/hashfunc.h new file mode 100644 index 00000000..37f18d6b --- /dev/null +++ b/tools/urt/libs/container/hashfunc.h @@ -0,0 +1,413 @@ + +#if !defined(INCLUDED_CONTAINER_HASHFUNC_H) +#define INCLUDED_CONTAINER_HASHFUNC_H + +/* +-------------------------------------------------------------------- +lookup2.c, by Bob Jenkins, December 1996, Public Domain. +hash(), hash2(), hash3, and mix() are externally useful functions. +Routines to test the hash are included if SELF_TEST is defined. +You can use this free for any purpose. It has no warranty. +-------------------------------------------------------------------- +*/ + +#include +#include "string/string.h" +#include "container/array.h" +typedef unsigned long int ub4; /* unsigned 4-byte quantities */ +typedef unsigned char ub1; + +inline ub1 ub1_as_ub1_nocase(ub1 byte) +{ + return std::tolower(byte); +} + +inline ub4 ub1x4_as_ub4_nocase(const ub1 bytes[4]) +{ + ub4 result; + reinterpret_cast(&result)[0] = ub1_as_ub1_nocase(bytes[0]); + reinterpret_cast(&result)[1] = ub1_as_ub1_nocase(bytes[1]); + reinterpret_cast(&result)[2] = ub1_as_ub1_nocase(bytes[2]); + reinterpret_cast(&result)[3] = ub1_as_ub1_nocase(bytes[3]); + return result; +} + +class ub1_default_traits +{ +public: + static ub1 as_ub1(ub1 byte) + { + return byte; + } +}; + +class ub1_nocase_traits +{ +public: + static ub1 as_ub1(ub1 byte) + { + return ub1_as_ub1_nocase(byte); + } +}; + +class ub1x4_default_traits +{ +public: + static ub4 as_ub4(const ub1 bytes[4]) + { + return *reinterpret_cast(bytes); + } +}; + +class ub1x4_nocase_traits +{ +public: + static ub4 as_ub4(const ub1 bytes[4]) + { + return ub1x4_as_ub4_nocase(bytes); + } +}; + +class ub4_default_traits +{ +public: + static ub4 as_ub4(ub4 i) + { + return i; + } +}; + +class ub4_nocase_traits +{ +public: + static ub4 as_ub4(ub4 i) + { + return ub1x4_as_ub4_nocase(reinterpret_cast(&i)); + } +}; + +#define hashsize(n) ((ub4)1<<(n)) +#define hashmask(n) (hashsize(n)-1) + +/* +-------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +For every delta with one or two bit set, and the deltas of all three + high bits or all three low bits, whether the original value of a,b,c + is almost all zero or is uniformly distributed, +* If mix() is run forward or backward, at least 32 bits in a,b,c + have at least 1/4 probability of changing. +* If mix() is run forward, every bit of c will change between 1/3 and + 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) +mix() was built out of 36 single-cycle latency instructions in a + structure that could supported 2x parallelism, like so: + a -= b; + a -= c; x = (c>>13); + b -= c; a ^= x; + b -= a; x = (a<<8); + c -= a; b ^= x; + c -= b; x = (b>>13); + ... + Unfortunately, superscalar Pentiums and Sparcs can't take advantage + of that parallelism. They've also turned some of those single-cycle + latency instructions into multi-cycle latency instructions. Still, + this is the fastest good hash I could find. There were about 2^^68 + to choose from. I only looked at a billion or so. +-------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +/* same, but slower, works on systems that might have 8 byte ub4's */ +#define mix2(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<< 8); \ + c -= a; c -= b; c ^= ((b&0xffffffff)>>13); \ + a -= b; a -= c; a ^= ((c&0xffffffff)>>12); \ + b -= c; b -= a; b = (b ^ (a<<16)) & 0xffffffff; \ + c -= a; c -= b; c = (c ^ (b>> 5)) & 0xffffffff; \ + a -= b; a -= c; a = (a ^ (c>> 3)) & 0xffffffff; \ + b -= c; b -= a; b = (b ^ (a<<10)) & 0xffffffff; \ + c -= a; c -= b; c = (c ^ (b>>15)) & 0xffffffff; \ +} + +/* +-------------------------------------------------------------------- +hash() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + len : the length of the key, counting by bytes + level : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Every 1-bit and 2-bit delta achieves avalanche. +About 36+6len instructions. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (ub1 **)k, do it like this: + for (i=0, h=0; i +inline ub4 hash( +const ub1 *k, /* the key */ +ub4 length, /* the length of the key */ +ub4 initval, /* the previous hash, or an arbitrary value */ +const UB1Traits& ub1traits, +const UB4x1Traits& ub4x1traits +) +{ + register ub4 a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) + { + a += (k[0] +((ub4)UB1Traits::as_ub1(k[1])<<8) +((ub4)UB1Traits::as_ub1(k[2])<<16) +((ub4)UB1Traits::as_ub1(k[3])<<24)); + b += (k[4] +((ub4)UB1Traits::as_ub1(k[5])<<8) +((ub4)UB1Traits::as_ub1(k[6])<<16) +((ub4)UB1Traits::as_ub1(k[7])<<24)); + c += (k[8] +((ub4)UB1Traits::as_ub1(k[9])<<8) +((ub4)UB1Traits::as_ub1(k[10])<<16)+((ub4)UB1Traits::as_ub1(k[11])<<24)); + mix(a,b,c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 11: c += ((ub4)UB1Traits::as_ub1(k[10]) << 24); + case 10: c += ((ub4)UB1Traits::as_ub1(k[9]) << 16); + case 9 : c += ((ub4)UB1Traits::as_ub1(k[8]) << 8); + /* the first byte of c is reserved for the length */ + case 8 : b += ((ub4)UB1Traits::as_ub1(k[7]) << 24); + case 7 : b += ((ub4)UB1Traits::as_ub1(k[6]) << 16); + case 6 : b += ((ub4)UB1Traits::as_ub1(k[5]) << 8); + case 5 : b += UB1Traits::as_ub1(k[4]); + case 4 : a += ((ub4)UB1Traits::as_ub1(k[3]) << 24); + case 3 : a += ((ub4)UB1Traits::as_ub1(k[2]) << 16); + case 2 : a += ((ub4)UB1Traits::as_ub1(k[1]) << 8); + case 1 : a += UB1Traits::as_ub1(k[0]); + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +/* +-------------------------------------------------------------------- + This works on all machines. hash2() is identical to hash() on + little-endian machines, except that the length has to be measured + in ub4s instead of bytes. It is much faster than hash(). It + requires + -- that the key be an array of ub4's, and + -- that all your machines have the same endianness, and + -- that the length be the number of ub4's in the key +-------------------------------------------------------------------- +*/ +template +inline ub4 hash2( +const ub4 *k, /* the key */ +ub4 length, /* the length of the key, in ub4s */ +ub4 initval, /* the previous hash, or an arbitrary value */ +const UB4Traits& ub4traits +) +{ + register ub4 a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 3) + { + a += UB4Traits::as_ub4(k[0]); + b += UB4Traits::as_ub4(k[1]); + c += UB4Traits::as_ub4(k[2]); + mix(a,b,c); + k += 3; len -= 3; + } + + /*-------------------------------------- handle the last 2 ub4's */ + c += length; + switch(len) /* all the case statements fall through */ + { + /* c is reserved for the length */ + case 2 : b += UB4Traits::as_ub4(k[1]); + case 1 : a += UB4Traits::as_ub4(k[0]); + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +typedef ub4 hash_t; + +inline hash_t hash_ub1(const ub1* key, std::size_t len, hash_t previous = 0) +{ + return hash(key, ub4(len), previous, ub1_default_traits(), ub1x4_default_traits()); +} + +inline hash_t hash_ub1_nocase(const ub1* key, std::size_t len, hash_t previous = 0) +{ + return hash(key, ub4(len), previous, ub1_nocase_traits(), ub1x4_nocase_traits()); +} + +template +inline hash_t hash_ub4(const ub4* key, std::size_t len, const UB4Traits& traits, hash_t previous = 0) +{ + return hash2(key,ub4(len), previous, traits); +} + +inline ub4 hash_combine(ub4 left, ub4 right) +{ + return hash_ub1(reinterpret_cast(&left), 4, right); +} + +template +inline hash_t pod_hash(const POD& pod) +{ + return hash_ub1(reinterpret_cast(&pod), sizeof(POD)); +} + +inline hash_t string_hash(const char* string, hash_t previous = 0) +{ + return hash_ub1(reinterpret_cast(string), string_length(string), previous); +} + +inline hash_t string_hash_nocase(const char* string, hash_t previous = 0) +{ + return hash_ub1_nocase(reinterpret_cast(string), string_length(string), previous); +} + +struct HashString +{ + typedef hash_t hash_type; + hash_type operator()(const CopiedString& string) const + { + return string_hash(string.c_str()); + } +}; + +struct HashStringNoCase +{ + typedef hash_t hash_type; + hash_type operator()(const CopiedString& string) const + { + return string_hash_nocase(string.c_str()); + } +}; + +/// \brief Length of a string in ub4. +/// "wibble" (6) gives 2, +/// "and" (3) gives 1, +/// "bleh" (4) gives 2 +inline std::size_t string_length_ub4(const char* string) +{ + return ((string_length(string)>>2)+1)<<2; +} + +/// \brief Hashable key type that stores a string as an array of ub4 - making hashing faster. +/// Also caches the 32-bit result of the hash to speed up comparison of keys. +template +class HashKey +{ + Array m_key; + hash_t m_hash; + + void copy(const HashKey& other) + { + std::copy(other.m_key.begin(), other.m_key.end(), m_key.begin()); + m_hash = other.m_hash; + } + void copy(const char* string) + { + strncpy(reinterpret_cast(m_key.data()), string, m_key.size()); + for(Array::iterator i = m_key.begin(); i != m_key.end(); ++i) + { + *i = UB4Traits::as_ub4(*i); + } + m_hash = hash_ub4(m_key.data(), m_key.size(), ub4_default_traits()); + } + bool equal(const HashKey& other) const + { + return m_hash == other.m_hash && m_key.size() == other.m_key.size() + && std::equal(m_key.begin(), m_key.end(), other.m_key.begin()); + } + +public: + HashKey(const HashKey& other) : m_key(other.m_key.size()) + { + copy(other); + } + HashKey(const char* string) : m_key(string_length_ub4(string)) + { + copy(string); + } + HashKey& operator=(const char* string) + { + m_key.resize(string_length_ub4(string)); + copy(string); + return *this; + } + bool operator==(const HashKey& other) const + { + return equal(other); + } + bool operator!=(const HashKey& other) const + { + return !equal(other); + } + hash_t hash() const + { + return m_hash; + } +#if 0 + const char* c_str() const + { + return reinterpret_cast(m_key.data()); + } +#endif +}; + +/// \brief Hash function to use with HashKey. +struct HashKeyHasher +{ + typedef hash_t hash_type; + hash_type operator()(const HashKey& key) const + { + return key.hash(); + } +}; + + + +#endif diff --git a/tools/urt/libs/container/hashtable.cpp b/tools/urt/libs/container/hashtable.cpp new file mode 100644 index 00000000..bcad750d --- /dev/null +++ b/tools/urt/libs/container/hashtable.cpp @@ -0,0 +1,45 @@ + +#include "hashtable.h" + +#if defined(_DEBUG) || defined(DOXYGEN) + +#include "hashfunc.h" + +namespace ExampleHashTable +{ + void testStuff() + { + // HashTable example + typedef HashTable MyHashTable; + MyHashTable hashtable; + hashtable["bleh"] = 5; + hashtable.insert("blah", 17); + hashtable["foo"] = 99; + hashtable.insert("bar", 23); + + int bleh = (*hashtable.find("bleh")).value; // 5 + int blah = hashtable["blah"]; // 17 + hashtable.erase("foo"); + MyHashTable::iterator barIter = hashtable.find("bar"); + hashtable.erase(barIter); + + for(MyHashTable::iterator i = hashtable.begin(); i != hashtable.end(); ++i) + { + if((*i).key != "bleh") + { + ++hashtable["count"]; // insertion does not invalidate iterators + } + } + // end example + } + + struct Always + { + Always() + { + testStuff(); + } + } always; +} + +#endif diff --git a/tools/urt/libs/container/hashtable.h b/tools/urt/libs/container/hashtable.h new file mode 100644 index 00000000..7b5cc15a --- /dev/null +++ b/tools/urt/libs/container/hashtable.h @@ -0,0 +1,455 @@ + +#if !defined(INCLUDED_CONTAINER_HASHTABLE_H) +#define INCLUDED_CONTAINER_HASHTABLE_H + +#include +#include +#include +#include "debugging/debugging.h" + + +namespace HashTableDetail +{ + inline std::size_t next_power_of_two(std::size_t size) + { + std::size_t result = 1; + while(result < size) + { + result <<= 1; + } + return result; + } + + struct BucketNodeBase + { + BucketNodeBase* next; + BucketNodeBase* prev; + }; + + inline void list_initialise(BucketNodeBase& self) + { + self.next = self.prev = &self; + } + + inline void list_swap(BucketNodeBase& self, BucketNodeBase& other) + { + BucketNodeBase tmp(self); + if(other.next == &other) + { + list_initialise(self); + } + else + { + self = other; + self.next->prev = self.prev->next = &self; + } + if(tmp.next == &self) + { + list_initialise(other); + } + else + { + other = tmp; + other.next->prev = other.prev->next = &other; + } + } + + inline void node_link(BucketNodeBase* node, BucketNodeBase* next) + { + node->next = next; + node->prev = next->prev; + next->prev = node; + node->prev->next = node; + } + inline void node_unlink(BucketNodeBase* node) + { + node->prev->next = node->next; + node->next->prev = node->prev; + } + + template + struct KeyValue + { + const Key key; + Value value; + + KeyValue(const Key& key_, const Value& value_) + : key(key_), value(value_) + { + } + }; + + template + struct BucketNode : public BucketNodeBase + { + Hash m_hash; + KeyValue m_value; + + BucketNode(Hash hash, const Key& key, const Value& value) + : m_hash(hash), m_value(key, value) + { + } + BucketNode* getNext() + { + return static_cast(next); + } + BucketNode* getPrev() + { + return static_cast(prev); + } + }; + + template + class BucketIterator + { + typedef BucketNode Node; + Node* m_node; + + void increment() + { + m_node = m_node->getNext(); + } + + public: + typedef std::forward_iterator_tag iterator_category; + typedef std::ptrdiff_t difference_type; + typedef difference_type distance_type; + typedef KeyValue value_type; + typedef value_type* pointer; + typedef value_type& reference; + + BucketIterator(Node* node) : m_node(node) + { + } + + Node* node() + { + return m_node; + } + + bool operator==(const BucketIterator& other) const + { + return m_node == other.m_node; + } + bool operator!=(const BucketIterator& other) const + { + return !operator==(other); + } + BucketIterator& operator++() + { + increment(); + return *this; + } + BucketIterator operator++(int) + { + BucketIterator tmp = *this; + increment(); + return tmp; + } + value_type& operator*() + { + return m_node->m_value; + } + value_type* operator->() + { + return &(operator*()); + } + }; +} + + +/// A hash-table container which maps keys to values. +/// +/// - Inserting or removing elements does not invalidate iterators. +/// - Inserting or retrieving an element for a given key takes O(1) time on average. +/// - Elements are stored in no particular order. +/// +/// \param Key Uniquely identifies a value. Must provide a copy-constructor. +/// \param Value The value to be stored . Must provide a default-constructor and a copy-constructor. +/// \param Hasher Must provide 'std::size_t operator()(const Key&) const' which always returns the same result if the same argument is given. +/// \param KeyEqual Must provide 'bool operator==(const Key&, const Key&) const' which returns true only if both arguments are equal. +/// +/// \dontinclude container/hashtable.cpp +/// \skipline HashTable example +/// \until end example +template > +class HashTable : private KeyEqual, private Hasher +{ + typedef typename Hasher::hash_type hash_type; + typedef HashTableDetail::KeyValue KeyValue; + typedef HashTableDetail::BucketNode BucketNode; + + inline BucketNode* node_create(hash_type hash, const Key& key, const Value& value) + { + return new BucketNode(hash, key, value); + } + inline void node_destroy(BucketNode* node) + { + delete node; + } + + typedef BucketNode* Bucket; + + static Bucket* buckets_new(std::size_t count) + { + Bucket* buckets = new Bucket[count]; + std::uninitialized_fill(buckets, buckets + count, Bucket(0)); + return buckets; + } + static void buckets_delete(Bucket* buckets) + { + delete[] buckets; + } + + std::size_t m_bucketCount; + Bucket* m_buckets; + std::size_t m_size; + HashTableDetail::BucketNodeBase m_list; + + BucketNode* getFirst() + { + return static_cast(m_list.next); + } + BucketNode* getLast() + { + return static_cast(&m_list); + } + +public: + + typedef KeyValue value_type; + typedef HashTableDetail::BucketIterator iterator; + +private: + + void initialise() + { + list_initialise(m_list); + } + hash_type hashKey(const Key& key) + { + return Hasher::operator()(key); + } + + std::size_t getBucketId(hash_type hash) const + { + return hash & (m_bucketCount - 1); + } + Bucket& getBucket(hash_type hash) + { + return m_buckets[getBucketId(hash)]; + } + BucketNode* bucket_find(Bucket bucket, hash_type hash, const Key& key) + { + std::size_t bucketId = getBucketId(hash); + for(iterator i(bucket); i != end(); ++i) + { + hash_type nodeHash = i.node()->m_hash; + + if(getBucketId(nodeHash) != bucketId) + { + return 0; + } + + if(nodeHash == hash && KeyEqual::operator()((*i).key, key)) + { + return i.node(); + } + } + return 0; + } + BucketNode* bucket_insert(Bucket& bucket, BucketNode* node) + { + // link node into list + node_link(node, bucket_next(bucket)); + bucket = node; + return node; + } + BucketNode* bucket_next(Bucket& bucket) + { + Bucket* end = m_buckets + m_bucketCount; + for(Bucket* i = &bucket; i != end; ++i) + { + if(*i != 0) + { + return *i; + } + } + return getLast(); + } + + void buckets_resize(std::size_t count) + { + BucketNode* first = getFirst(); + BucketNode* last = getLast(); + + buckets_delete(m_buckets); + + m_bucketCount = count; + + m_buckets = buckets_new(m_bucketCount); + initialise(); + + for(BucketNode* i = first; i != last;) + { + BucketNode* node = i; + i = i->getNext(); + bucket_insert(getBucket((*node).m_hash), node); + } + } + void size_increment() + { + if(m_size == m_bucketCount) + { + buckets_resize(m_bucketCount == 0 ? 8 : m_bucketCount << 1); + } + ++m_size; + } + void size_decrement() + { + --m_size; + } + + HashTable(const HashTable& other); + HashTable& operator=(const HashTable& other); +public: + HashTable() : m_bucketCount(0), m_buckets(0), m_size(0) + { + initialise(); + } + HashTable(std::size_t bucketCount) : m_bucketCount(HashTableDetail::next_power_of_two(bucketCount)), m_buckets(buckets_new(m_bucketCount)), m_size(0) + { + initialise(); + } + ~HashTable() + { + for(BucketNode* i = getFirst(); i != getLast();) + { + BucketNode* node = i; + i = i->getNext(); + node_destroy(node); + } + buckets_delete(m_buckets); + } + + iterator begin() + { + return iterator(getFirst()); + } + iterator end() + { + return iterator(getLast()); + } + + bool empty() const + { + return m_size == 0; + } + std::size_t size() const + { + return m_size; + } + + /// \brief Returns an iterator pointing to the value associated with \p key if it is contained by the hash-table, else \c end(). + iterator find(const Key& key) + { + hash_type hash = hashKey(key); + if(m_bucketCount != 0) + { + Bucket bucket = getBucket(hash); + if(bucket != 0) + { + BucketNode* node = bucket_find(bucket, hash, key); + if(node != 0) + { + return iterator(node); + } + } + } + + return end(); + } + /// \brief Adds \p value to the hash-table associated with \p key if it does not exist, or replaces the current value associated with \p key. + iterator insert(const Key& key, const Value& value) + { + hash_type hash = hashKey(key); + if(m_bucketCount != 0) + { + Bucket& bucket = getBucket(hash); + if(bucket != 0) + { + BucketNode* node = bucket_find(bucket, hash, key); + if(node != 0) + { + node->m_value.value = value; + return iterator(node); + } + } + } + + size_increment(); + return iterator(bucket_insert(getBucket(hash), node_create(hash, key, value))); + } + + /// \brief Removes the value pointed to by \p i from the hash-table. + /// + /// \p i must be a deferenceable iterator into the hash-table. + void erase(iterator i) + { + Bucket& bucket = getBucket(i.node()->m_hash); + BucketNode* node = i.node(); + + // if this was the last node in the bucket + if(bucket == node) + { + bucket = (node->getNext() == getLast() || &getBucket(node->getNext()->m_hash) != &bucket) ? 0 : node->getNext(); + } + + node_unlink(node); + ASSERT_MESSAGE(node != 0, "tried to erase a non-existent key/value"); + node_destroy(node); + + size_decrement(); + } + + /// \brief Returns the value identified by \p key if it is contained by the hash-table, else inserts and returns a new default-constructed value associated with \p key. + Value& operator[](const Key& key) + { + hash_type hash = hashKey(key); + if(m_bucketCount != 0) + { + Bucket& bucket = getBucket(hash); + if(bucket != 0) + { + BucketNode* node = bucket_find(bucket, hash, key); + if(node != 0) + { + return node->m_value.value; + } + } + } + size_increment(); + return bucket_insert(getBucket(hash), node_create(hash, key, Value()))->m_value.value; + } + /// \brief Removes the value associated with \p key from the hash-table. + void erase(const Key& key) + { + erase(find(key)); + } + /// \brief Swaps the contents of the hash-table with \p other. + void swap(HashTable& other) + { + std::swap(m_buckets, other.m_buckets); + std::swap(m_bucketCount, other.m_bucketCount); + std::swap(m_size, other.m_size); + HashTableDetail::list_swap(m_list, other.m_list); + } + /// \brief Removes all values from the hash-table. + void clear() + { + HashTable tmp; + tmp.swap(*this); + } +}; + +#endif diff --git a/tools/urt/libs/container/stack.cpp b/tools/urt/libs/container/stack.cpp new file mode 100644 index 00000000..8bfe65f7 --- /dev/null +++ b/tools/urt/libs/container/stack.cpp @@ -0,0 +1,3 @@ + +#include "stack.h" + diff --git a/tools/urt/libs/container/stack.h b/tools/urt/libs/container/stack.h new file mode 100644 index 00000000..73d9edb0 --- /dev/null +++ b/tools/urt/libs/container/stack.h @@ -0,0 +1,219 @@ + +#if !defined(INCLUDED_CONTAINER_STACK_H) +#define INCLUDED_CONTAINER_STACK_H + +#include "memory/allocator.h" +#include + +/// \brief A stack whose storage capacity is variable at run-time. Similar to std::vector. +/// +/// - Pushing or popping elements is a constant-time operation (on average). +/// - The storage capacity of the stack will grow when a new element is added beyond the current capacity. Iterators are invalidated when the storage capacity grows. +/// - DefaultConstructible, Copyable, Assignable. +/// - Compatible with the containers and algorithms in the Standard Template Library (STL) - http://www.sgi.com/tech/stl/ +/// +/// \param Type: The type to be stored in the stack. Must provide a copy-constructor. +template +class Stack : public DefaultAllocator +{ + typedef DefaultAllocator Allocator; + + enum + { + DEFAULT_CAPACITY = 4, + }; + + typedef Type* pointer; + typedef const Type* const_pointer; + +public: + typedef const_pointer const_iterator; +private: + + pointer m_data; + pointer m_end; + std::size_t m_capacity; + + + void insert(const Type& value) + { + Allocator::construct(m_end++, value); + } + void insert_overflow(const Type& value) + { + const std::size_t new_capacity = (m_capacity) ? m_capacity + m_capacity : std::size_t(DEFAULT_CAPACITY); + const pointer new_data = Allocator::allocate(new_capacity); + const pointer new_end = std::copy(m_data, m_end, new_data); + + destroy(); + Allocator::deallocate(m_data, m_capacity); + + m_capacity = new_capacity; + m_data = new_data; + m_end = new_end; + insert(value); + } + void destroy() + { + for(pointer p = m_data; p != m_end; ++p) + { + Allocator::destroy(p); + } + } + void construct(const Stack& other) + { + pointer p = m_data; + for(const_iterator i = other.begin(); i != other.end(); ++i) + { + Allocator::construct(p++, *i); + } + } + +public: + + Stack() : + m_data(0), + m_end(0), + m_capacity(0) + { + } + Stack(const Type& value) : + m_data(0), + m_end(0), + m_capacity(0) + { + push(value); + } + Stack(const Stack& other) : + DefaultAllocator(other) + { + m_capacity = other.m_capacity; + m_data = Allocator::allocate(m_capacity); + construct(other); + m_end = m_data + other.size(); + } + ~Stack() + { + destroy(); + Allocator::deallocate(m_data, m_capacity); + } + + const_iterator begin() const + { + return m_data; + } + const_iterator end() const + { + return m_end; + } + + bool empty() const + { + return end() == begin(); + } + void clear() + { + destroy(); + m_end = m_data; + } + + std::size_t size() const + { + return m_end - m_data; + } + Type operator[](const std::size_t i) const + { + return m_data[i]; + } + /// \brief Pushes \p value onto the stack at the top element. If reserved storage is insufficient for the new element, this will invalidate all iterators. + void push(const Type& value) + { + if(size() == m_capacity) + { + insert_overflow(value); + } + else + { + insert(value); + } + } + /// \brief Removes the top element of the stack. + void pop() + { + Allocator::destroy(--m_end); + } + /// \brief Returns the top element of the mutable stack. + Type& top() + { + return *(m_end-1); + } + /// \brief Returns the top element of the non-mutable stack. + const Type& top() const + { + return *(m_end-1); + } + /// \brief Returns the element below the top element of the mutable stack. + Type& parent() + { + return *(m_end-2); + } + /// \brief Returns the element below the top element of the non-mutable stack. + const Type& parent() const + { + return *(m_end-2); + } + /// \brief Swaps the values of this stack and \p other. + void swap(Stack& other) + { + std::swap(m_data, other.m_data); + std::swap(m_end, other.m_end); + std::swap(m_capacity, other.m_capacity); + } +#if 1 // use copy-swap technique + Stack& operator=(const Stack& other) + { + Stack temp(other); + temp.swap(*this); + return *this; + } +#else // avoids memory allocation if capacity is already sufficient. + Stack& operator=(const Stack& other) + { + if(&other != this) + { + destroy(); + + if(other.size() > m_capacity) + { + Allocator::deallocate(m_data, m_capacity); + m_capacity = other.m_capacity; + m_data = Allocator::allocate(m_capacity); + } + m_end = m_data + other.size(); + + construct(other); + } + return *this; + } +#endif +}; + +/// \brief Returns true if \p self is lexicographically less than \p other. +template +inline bool operator<(const Stack& self, const Stack& other) +{ + return std::lexicographical_compare(self.begin(), self.end(), other.begin(), other.end()); +} + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap(). + template + inline void swap(Stack& self, Stack& other) + { + self.swap(other); + } +} + +#endif diff --git a/tools/urt/libs/convert.cpp b/tools/urt/libs/convert.cpp new file mode 100644 index 00000000..73eacfea --- /dev/null +++ b/tools/urt/libs/convert.cpp @@ -0,0 +1,3 @@ + +#include "convert.h" + diff --git a/tools/urt/libs/convert.h b/tools/urt/libs/convert.h new file mode 100644 index 00000000..5e1ac8a8 --- /dev/null +++ b/tools/urt/libs/convert.h @@ -0,0 +1,285 @@ + +#if !defined(INCLUDED_CONVERT_H) +#define INCLUDED_CONVERT_H + +/// \file +/// \brief Character encoding conversion. + +#include "debugging/debugging.h" +#include +#include +#include + +#include "character.h" + +/// \brief Returns the number of bytes required to represent \p character in UTF-8 encoding. +inline std::size_t utf8_character_length(const char* character) +{ + if((*character & 0xE0) == 0xC0) // 110xxxxx + { + return 2; + } + else if((*character & 0xF0) == 0xE0) // 1110xxxx + { + return 3; + } + else if((*character & 0xF8) == 0xF0) // 11110xxx + { + return 4; + } + else if((*character & 0xFC) == 0xF8) // 111110xx + { + return 5; + } + else if((*character & 0xFE) == 0xFC) // 1111110x + { + return 6; + } + ERROR_MESSAGE(""); + return 0; +} + +struct UTF8Character +{ + const char* buffer; + std::size_t length; + UTF8Character() : buffer(0), length(0) + { + } + UTF8Character(const char* bytes) : buffer(bytes), length(utf8_character_length(bytes)) + { + } +}; + +inline bool operator<(const UTF8Character& self, const UTF8Character& other) +{ + return std::lexicographical_compare(self.buffer, self.buffer + self.length, other.buffer, other.buffer + other.length); +} + +/// \brief Writes \p c to \p ostream in Hex form. Useful for debugging. +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const UTF8Character& c) +{ + for(const char* p = c.buffer; p != c.buffer + c.length; ++p) + { + ostream << HexChar(*p); + } + return ostream; +} + + + +/// \brief The character-set encoding for the current C locale. +/// +/// Obtain the global instance with globalCharacterSet(). +class CharacterSet +{ + const char* m_charSet; +public: + CharacterSet() + { + if(g_get_charset(&m_charSet) != FALSE) + { + m_charSet = 0; + } + } + bool isUTF8() const + { + return m_charSet == 0; + } + const char* get() const + { + return m_charSet; + } +}; + +typedef LazyStatic GlobalCharacterSet; + +/// \brief Returns the global instance of CharacterSet. +inline CharacterSet& globalCharacterSet() +{ + return GlobalCharacterSet::instance(); +} + + +class UTF8CharacterToExtendedASCII +{ +public: + UTF8Character m_utf8; + char m_c; + UTF8CharacterToExtendedASCII() : m_c('\0') + { + } + UTF8CharacterToExtendedASCII(const UTF8Character& utf8, char c) : m_utf8(utf8), m_c(c) + { + } +}; + +inline bool operator<(const UTF8CharacterToExtendedASCII& self, const UTF8CharacterToExtendedASCII& other) +{ + return self.m_utf8 < other.m_utf8; +} + +inline std::size_t extended_ascii_to_index(char c) +{ + return static_cast(c & 0x7F); +} + +inline char extended_ascii_for_index(std::size_t i) +{ + return static_cast(i | 0x80); +} + +/// \brief The active extended-ascii character set encoding. +/// Performs UTF-8 encoding and decoding of extended-ascii characters. +/// +/// Obtain the global instance with globalExtendedASCIICharacterSet(). +class ExtendedASCIICharacterSet +{ + typedef char UTF8CharBuffer[6]; + UTF8CharBuffer m_converted[128]; + UTF8Character m_decodeMap[128]; + UTF8CharacterToExtendedASCII m_encodeMap[128]; +public: + ExtendedASCIICharacterSet() + { + if(!globalCharacterSet().isUTF8()) + { + GIConv descriptor = g_iconv_open("UTF-8", globalCharacterSet().get()); + for(std::size_t i = 1; i < 128; ++i) + { + char c = extended_ascii_for_index(i); + char* inbuf = &c; + std::size_t inbytesleft = 1; + char* outbuf = m_converted[i]; + std::size_t outbytesleft = 6; + if(g_iconv(descriptor, &inbuf, &inbytesleft, &outbuf, &outbytesleft) != (size_t)(-1)) + { + UTF8Character utf8(m_converted[i]); + m_decodeMap[i] = utf8; + m_encodeMap[i] = UTF8CharacterToExtendedASCII(utf8, c); + } + } + g_iconv_close(descriptor); + std::sort(m_encodeMap, m_encodeMap + 128); + } + } + /// \brief Prints the (up to) 128 characters in the current extended-ascii character set. + /// Useful for debugging. + void print() const + { + globalOutputStream() << "UTF-8 conversion required from charset: " << globalCharacterSet().get() << "\n"; + for(std::size_t i = 1; i < 128; ++i) + { + if(m_decodeMap[i].buffer != 0) + { + globalOutputStream() << extended_ascii_for_index(i) << " = " << m_decodeMap[i] << "\n"; + } + } + } + /// \brief Returns \p c decoded from extended-ascii to UTF-8. + /// \p c must be an extended-ascii character. + const UTF8Character& decode(char c) const + { + ASSERT_MESSAGE(!globalCharacterSet().isUTF8(), "locale is utf8, no conversion required"); + ASSERT_MESSAGE(!char_is_ascii(c), "decode: ascii character"); + ASSERT_MESSAGE(m_decodeMap[extended_ascii_to_index(c)].buffer != 0, "decode: invalid character: " << HexChar(c)); + return m_decodeMap[extended_ascii_to_index(c)]; + } + /// \brief Returns \p c encoded to extended-ascii from UTF-8. + /// \p c must map to an extended-ascii character. + char encode(const UTF8Character& c) const + { + ASSERT_MESSAGE(!globalCharacterSet().isUTF8(), "locale is utf8, no conversion required"); + ASSERT_MESSAGE(!char_is_ascii(*c.buffer), "encode: ascii character"); + std::pair range + = std::equal_range(m_encodeMap, m_encodeMap + 128, UTF8CharacterToExtendedASCII(c, 0)); + ASSERT_MESSAGE(range.first != range.second, "encode: invalid character: " << c); + return (*range.first).m_c; + } +}; + +typedef LazyStatic GlobalExtendedASCIICharacterSet; + +/// \brief Returns the global instance of ExtendedASCIICharacterSet. +inline ExtendedASCIICharacterSet& globalExtendedASCIICharacterSet() +{ + return GlobalExtendedASCIICharacterSet::instance(); +} + +class ConvertUTF8ToLocale +{ +public: + StringRange m_range; + ConvertUTF8ToLocale(const char* string) : m_range(StringRange(string, string + strlen(string))) + { + } + ConvertUTF8ToLocale(const StringRange& range) : m_range(range) + { + } +}; + +/// \brief Writes \p convert to \p ostream after encoding each character to extended-ascii from UTF-8. +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const ConvertUTF8ToLocale& convert) +{ + if(globalCharacterSet().isUTF8()) + { + return ostream << convert.m_range; + } + + for(const char* p = convert.m_range.begin; p != convert.m_range.end;) + { + if(!char_is_ascii(*p)) + { + UTF8Character c(p); + ostream << globalExtendedASCIICharacterSet().encode(c); + p += c.length; + } + else + { + ostream << *p++; + } + } + return ostream; +} + + +class ConvertLocaleToUTF8 +{ +public: + StringRange m_range; + ConvertLocaleToUTF8(const char* string) : m_range(StringRange(string, string + strlen(string))) + { + } + ConvertLocaleToUTF8(const StringRange& range) : m_range(range) + { + } +}; + +/// \brief Writes \p convert to \p ostream after decoding each character from extended-ascii to UTF-8. +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const ConvertLocaleToUTF8& convert) +{ + if(globalCharacterSet().isUTF8()) + { + return ostream << convert.m_range; + } + + for(const char* p = convert.m_range.begin; p != convert.m_range.end; ++p) + { + if(!char_is_ascii(*p)) + { + UTF8Character c(globalExtendedASCIICharacterSet().decode(*p)); + ostream.write(c.buffer, c.length); + } + else + { + ostream << *p; + } + } + return ostream; +} + + +#endif diff --git a/tools/urt/libs/ddslib.h b/tools/urt/libs/ddslib.h new file mode 100644 index 00000000..246e31be --- /dev/null +++ b/tools/urt/libs/ddslib.h @@ -0,0 +1,250 @@ +/* ----------------------------------------------------------------------------- + +DDS Library + +Based on code from Nvidia's DDS example: +http://www.nvidia.com/object/dxtc_decompression_code.html + +Copyright (c) 2003 Randy Reddig +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef DDSLIB_H +#define DDSLIB_H + + + +/* dependencies */ +#include +#include + + + +/* c++ marker */ +#ifdef __cplusplus +extern "C" +{ +#endif + + + +/* dds definition */ +typedef enum +{ + DDS_PF_ARGB8888, + DDS_PF_DXT1, + DDS_PF_DXT2, + DDS_PF_DXT3, + DDS_PF_DXT4, + DDS_PF_DXT5, + DDS_PF_UNKNOWN +} +ddsPF_t; + + +/* 16bpp stuff */ +#define DDS_LOW_5 0x001F; +#define DDS_MID_6 0x07E0; +#define DDS_HIGH_5 0xF800; +#define DDS_MID_555 0x03E0; +#define DDS_HI_555 0x7C00; + + +/* structures */ +typedef struct ddsColorKey_s +{ + unsigned int colorSpaceLowValue; + unsigned int colorSpaceHighValue; +} +ddsColorKey_t; + + +typedef struct ddsCaps_s +{ + unsigned int caps1; + unsigned int caps2; + unsigned int caps3; + unsigned int caps4; +} +ddsCaps_t; + + +typedef struct ddsMultiSampleCaps_s +{ + unsigned short flipMSTypes; + unsigned short bltMSTypes; +} +ddsMultiSampleCaps_t; + + +typedef struct ddsPixelFormat_s +{ + unsigned int size; + unsigned int flags; + unsigned int fourCC; + union + { + unsigned int rgbBitCount; + unsigned int yuvBitCount; + unsigned int zBufferBitDepth; + unsigned int alphaBitDepth; + unsigned int luminanceBitCount; + unsigned int bumpBitCount; + unsigned int privateFormatBitCount; + }; + union + { + unsigned int rBitMask; + unsigned int yBitMask; + unsigned int stencilBitDepth; + unsigned int luminanceBitMask; + unsigned int bumpDuBitMask; + unsigned int operations; + }; + union + { + unsigned int gBitMask; + unsigned int uBitMask; + unsigned int zBitMask; + unsigned int bumpDvBitMask; + ddsMultiSampleCaps_t multiSampleCaps; + }; + union + { + unsigned int bBitMask; + unsigned int vBitMask; + unsigned int stencilBitMask; + unsigned int bumpLuminanceBitMask; + }; + union + { + unsigned int rgbAlphaBitMask; + unsigned int yuvAlphaBitMask; + unsigned int luminanceAlphaBitMask; + unsigned int rgbZBitMask; + unsigned int yuvZBitMask; + }; +} +ddsPixelFormat_t; + + +typedef struct ddsBuffer_s +{ + /* magic: 'dds ' */ + char magic[ 4 ]; + + /* directdraw surface */ + unsigned int size; + unsigned int flags; + unsigned int height; + unsigned int width; + union + { + int pitch; + unsigned int linearSize; + }; + unsigned int backBufferCount; + union + { + unsigned int mipMapCount; + unsigned int refreshRate; + unsigned int srcVBHandle; + }; + unsigned int alphaBitDepth; + unsigned int reserved; + void *surface; + union + { + ddsColorKey_t ckDestOverlay; + unsigned int emptyFaceColor; + }; + ddsColorKey_t ckDestBlt; + ddsColorKey_t ckSrcOverlay; + ddsColorKey_t ckSrcBlt; + union + { + ddsPixelFormat_t pixelFormat; + unsigned int fvf; + }; + ddsCaps_t ddsCaps; + unsigned int textureStage; + + /* data (Varying size) */ + unsigned char data[ 4 ]; +} +ddsBuffer_t; + + +typedef struct ddsColorBlock_s +{ + unsigned short colors[ 2 ]; + unsigned char row[ 4 ]; +} +ddsColorBlock_t; + + +typedef struct ddsAlphaBlockExplicit_s +{ + unsigned short row[ 4 ]; +} +ddsAlphaBlockExplicit_t; + + +typedef struct ddsAlphaBlock3BitLinear_s +{ + unsigned char alpha0; + unsigned char alpha1; + unsigned char stuff[ 6 ]; +} +ddsAlphaBlock3BitLinear_t; + + +typedef struct ddsColor_s +{ + unsigned char r, g, b, a; +} +ddsColor_t; + + + +/* public functions */ +int DDSGetInfo( ddsBuffer_t *dds, int *width, int *height, ddsPF_t *pf ); +int DDSDecompress( ddsBuffer_t *dds, unsigned char *pixels ); + + + +/* end marker */ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/urt/libs/ddslib/ddslib.c b/tools/urt/libs/ddslib/ddslib.c new file mode 100644 index 00000000..e7bfeb2b --- /dev/null +++ b/tools/urt/libs/ddslib/ddslib.c @@ -0,0 +1,781 @@ +/* ----------------------------------------------------------------------------- + +DDS Library + +Based on code from Nvidia's DDS example: +http://www.nvidia.com/object/dxtc_decompression_code.html + +Copyright (c) 2003 Randy Reddig +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define DDSLIB_C + + + +/* dependencies */ +#include "ddslib.h" + + + +/* endian tomfoolery */ +typedef union +{ + float f; + char c[ 4 ]; +} +floatSwapUnion; + + +#ifndef __BIG_ENDIAN__ + #ifdef _SGI_SOURCE + #define __BIG_ENDIAN__ + #endif +#endif + + +#ifdef __BIG_ENDIAN__ + + int DDSBigLong( int src ) { return src; } + short DDSBigShort( short src ) { return src; } + float DDSBigFloat( float src ) { return src; } + + int DDSLittleLong( int src ) + { + return ((src & 0xFF000000) >> 24) | + ((src & 0x00FF0000) >> 8) | + ((src & 0x0000FF00) << 8) | + ((src & 0x000000FF) << 24); + } + + short DDSLittleShort( short src ) + { + return ((src & 0xFF00) >> 8) | + ((src & 0x00FF) << 8); + } + + float DDSLittleFloat( float src ) + { + floatSwapUnion in,out; + in.f = src; + out.c[ 0 ] = in.c[ 3 ]; + out.c[ 1 ] = in.c[ 2 ]; + out.c[ 2 ] = in.c[ 1 ]; + out.c[ 3 ] = in.c[ 0 ]; + return out.f; + } + +#else /*__BIG_ENDIAN__*/ + + int DDSLittleLong( int src ) { return src; } + short DDSLittleShort( short src ) { return src; } + float DDSLittleFloat( float src ) { return src; } + + int DDSBigLong( int src ) + { + return ((src & 0xFF000000) >> 24) | + ((src & 0x00FF0000) >> 8) | + ((src & 0x0000FF00) << 8) | + ((src & 0x000000FF) << 24); + } + + short DDSBigShort( short src ) + { + return ((src & 0xFF00) >> 8) | + ((src & 0x00FF) << 8); + } + + float DDSBigFloat( float src ) + { + floatSwapUnion in,out; + in.f = src; + out.c[ 0 ] = in.c[ 3 ]; + out.c[ 1 ] = in.c[ 2 ]; + out.c[ 2 ] = in.c[ 1 ]; + out.c[ 3 ] = in.c[ 0 ]; + return out.f; + } + +#endif /*__BIG_ENDIAN__*/ + + + +/* +DDSDecodePixelFormat() +determines which pixel format the dds texture is in +*/ + +static void DDSDecodePixelFormat( ddsBuffer_t *dds, ddsPF_t *pf ) +{ + unsigned int fourCC; + + + /* dummy check */ + if( dds == NULL || pf == NULL ) + return; + + /* extract fourCC */ + fourCC = dds->pixelFormat.fourCC; + + /* test it */ + if( fourCC == 0 ) + *pf = DDS_PF_ARGB8888; + else if( fourCC == *((unsigned int*) "DXT1") ) + *pf = DDS_PF_DXT1; + else if( fourCC == *((unsigned int*) "DXT2") ) + *pf = DDS_PF_DXT2; + else if( fourCC == *((unsigned int*) "DXT3") ) + *pf = DDS_PF_DXT3; + else if( fourCC == *((unsigned int*) "DXT4") ) + *pf = DDS_PF_DXT4; + else if( fourCC == *((unsigned int*) "DXT5") ) + *pf = DDS_PF_DXT5; + else + *pf = DDS_PF_UNKNOWN; +} + + + +/* +DDSGetInfo() +extracts relevant info from a dds texture, returns 0 on success +*/ + +int DDSGetInfo( ddsBuffer_t *dds, int *width, int *height, ddsPF_t *pf ) +{ + /* dummy test */ + if( dds == NULL ) + return -1; + + /* test dds header */ + if( *((int*) dds->magic) != *((int*) "DDS ") ) + return -1; + if( DDSLittleLong( dds->size ) != 124 ) + return -1; + + /* extract width and height */ + if( width != NULL ) + *width = DDSLittleLong( dds->width ); + if( height != NULL ) + *height = DDSLittleLong( dds->height ); + + /* get pixel format */ + DDSDecodePixelFormat( dds, pf ); + + /* return ok */ + return 0; +} + + + +/* +DDSGetColorBlockColors() +extracts colors from a dds color block +*/ + +static void DDSGetColorBlockColors( ddsColorBlock_t *block, ddsColor_t colors[ 4 ] ) +{ + unsigned short word; + + + /* color 0 */ + word = DDSLittleShort( block->colors[ 0 ] ); + colors[ 0 ].a = 0xff; + + /* extract rgb bits */ + colors[ 0 ].b = (unsigned char) word; + colors[ 0 ].b <<= 3; + colors[ 0 ].b |= (colors[ 0 ].b >> 5); + word >>= 5; + colors[ 0 ].g = (unsigned char) word; + colors[ 0 ].g <<= 2; + colors[ 0 ].g |= (colors[ 0 ].g >> 5); + word >>= 6; + colors[ 0 ].r = (unsigned char) word; + colors[ 0 ].r <<= 3; + colors[ 0 ].r |= (colors[ 0 ].r >> 5); + + /* same for color 1 */ + word = DDSLittleShort( block->colors[ 1 ] ); + colors[ 1 ].a = 0xff; + + /* extract rgb bits */ + colors[ 1 ].b = (unsigned char) word; + colors[ 1 ].b <<= 3; + colors[ 1 ].b |= (colors[ 1 ].b >> 5); + word >>= 5; + colors[ 1 ].g = (unsigned char) word; + colors[ 1 ].g <<= 2; + colors[ 1 ].g |= (colors[ 1 ].g >> 5); + word >>= 6; + colors[ 1 ].r = (unsigned char) word; + colors[ 1 ].r <<= 3; + colors[ 1 ].r |= (colors[ 1 ].r >> 5); + + /* use this for all but the super-freak math method */ + if( block->colors[ 0 ] > block->colors[ 1 ] ) + { + /* four-color block: derive the other two colors. + 00 = color 0, 01 = color 1, 10 = color 2, 11 = color 3 + these two bit codes correspond to the 2-bit fields + stored in the 64-bit block. */ + + word = ((unsigned short) colors[ 0 ].r * 2 + (unsigned short) colors[ 1 ].r ) / 3; + /* no +1 for rounding */ + /* as bits have been shifted to 888 */ + colors[ 2 ].r = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].g * 2 + (unsigned short) colors[ 1 ].g) / 3; + colors[ 2 ].g = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].b * 2 + (unsigned short) colors[ 1 ].b) / 3; + colors[ 2 ].b = (unsigned char) word; + colors[ 2 ].a = 0xff; + + word = ((unsigned short) colors[ 0 ].r + (unsigned short) colors[ 1 ].r * 2) / 3; + colors[ 3 ].r = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].g + (unsigned short) colors[ 1 ].g * 2) / 3; + colors[ 3 ].g = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].b + (unsigned short) colors[ 1 ].b * 2) / 3; + colors[ 3 ].b = (unsigned char) word; + colors[ 3 ].a = 0xff; + } + else + { + /* three-color block: derive the other color. + 00 = color 0, 01 = color 1, 10 = color 2, + 11 = transparent. + These two bit codes correspond to the 2-bit fields + stored in the 64-bit block */ + + word = ((unsigned short) colors[ 0 ].r + (unsigned short) colors[ 1 ].r) / 2; + colors[ 2 ].r = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].g + (unsigned short) colors[ 1 ].g) / 2; + colors[ 2 ].g = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].b + (unsigned short) colors[ 1 ].b) / 2; + colors[ 2 ].b = (unsigned char) word; + colors[ 2 ].a = 0xff; + + /* random color to indicate alpha */ + colors[ 3 ].r = 0x00; + colors[ 3 ].g = 0xff; + colors[ 3 ].b = 0xff; + colors[ 3 ].a = 0x00; + } +} + + + +/* +DDSDecodeColorBlock() +decodes a dds color block +fixme: make endian-safe +*/ + +static void DDSDecodeColorBlock( unsigned int *pixel, ddsColorBlock_t *block, int width, unsigned int colors[ 4 ] ) +{ + int r, n; + unsigned int bits; + unsigned int masks[] = { 3, 12, 3 << 4, 3 << 6 }; /* bit masks = 00000011, 00001100, 00110000, 11000000 */ + int shift[] = { 0, 2, 4, 6 }; + + + /* r steps through lines in y */ + for( r = 0; r < 4; r++, pixel += (width - 4) ) /* no width * 4 as unsigned int ptr inc will * 4 */ + { + /* width * 4 bytes per pixel per line, each j dxtc row is 4 lines of pixels */ + + /* n steps through pixels */ + for( n = 0; n < 4; n++ ) + { + bits = block->row[ r ] & masks[ n ]; + bits >>= shift[ n ]; + + switch( bits ) + { + case 0: + *pixel = colors[ 0 ]; + pixel++; + break; + + case 1: + *pixel = colors[ 1 ]; + pixel++; + break; + + case 2: + *pixel = colors[ 2 ]; + pixel++; + break; + + case 3: + *pixel = colors[ 3 ]; + pixel++; + break; + + default: + /* invalid */ + pixel++; + break; + } + } + } +} + + + +/* +DDSDecodeAlphaExplicit() +decodes a dds explicit alpha block +*/ + +static void DDSDecodeAlphaExplicit( unsigned int *pixel, ddsAlphaBlockExplicit_t *alphaBlock, int width, unsigned int alphaZero ) +{ + int row, pix; + unsigned short word; + ddsColor_t color; + + + /* clear color */ + color.r = 0; + color.g = 0; + color.b = 0; + + /* walk rows */ + for( row = 0; row < 4; row++, pixel += (width - 4) ) + { + word = DDSLittleShort( alphaBlock->row[ row ] ); + + /* walk pixels */ + for( pix = 0; pix < 4; pix++ ) + { + /* zero the alpha bits of image pixel */ + *pixel &= alphaZero; + color.a = word & 0x000F; + color.a = color.a | (color.a << 4); + *pixel |= *((unsigned int*) &color); + word >>= 4; /* move next bits to lowest 4 */ + pixel++; /* move to next pixel in the row */ + + } + } +} + + + +/* +DDSDecodeAlpha3BitLinear() +decodes interpolated alpha block +*/ + +static void DDSDecodeAlpha3BitLinear( unsigned int *pixel, ddsAlphaBlock3BitLinear_t *alphaBlock, int width, unsigned int alphaZero ) +{ + + int row, pix; + unsigned int stuff; + unsigned char bits[ 4 ][ 4 ]; + unsigned short alphas[ 8 ]; + ddsColor_t aColors[ 4 ][ 4 ]; + + + /* get initial alphas */ + alphas[ 0 ] = alphaBlock->alpha0; + alphas[ 1 ] = alphaBlock->alpha1; + + /* 8-alpha block */ + if( alphas[ 0 ] > alphas[ 1 ] ) + { + /* 000 = alpha_0, 001 = alpha_1, others are interpolated */ + alphas[ 2 ] = ( 6 * alphas[ 0 ] + alphas[ 1 ]) / 7; /* bit code 010 */ + alphas[ 3 ] = ( 5 * alphas[ 0 ] + 2 * alphas[ 1 ]) / 7; /* bit code 011 */ + alphas[ 4 ] = ( 4 * alphas[ 0 ] + 3 * alphas[ 1 ]) / 7; /* bit code 100 */ + alphas[ 5 ] = ( 3 * alphas[ 0 ] + 4 * alphas[ 1 ]) / 7; /* bit code 101 */ + alphas[ 6 ] = ( 2 * alphas[ 0 ] + 5 * alphas[ 1 ]) / 7; /* bit code 110 */ + alphas[ 7 ] = ( alphas[ 0 ] + 6 * alphas[ 1 ]) / 7; /* bit code 111 */ + } + + /* 6-alpha block */ + else + { + /* 000 = alpha_0, 001 = alpha_1, others are interpolated */ + alphas[ 2 ] = (4 * alphas[ 0 ] + alphas[ 1 ]) / 5; /* bit code 010 */ + alphas[ 3 ] = (3 * alphas[ 0 ] + 2 * alphas[ 1 ]) / 5; /* bit code 011 */ + alphas[ 4 ] = (2 * alphas[ 0 ] + 3 * alphas[ 1 ]) / 5; /* bit code 100 */ + alphas[ 5 ] = ( alphas[ 0 ] + 4 * alphas[ 1 ]) / 5; /* bit code 101 */ + alphas[ 6 ] = 0; /* bit code 110 */ + alphas[ 7 ] = 255; /* bit code 111 */ + } + + /* decode 3-bit fields into array of 16 bytes with same value */ + + /* first two rows of 4 pixels each */ + stuff = *((unsigned int*) &(alphaBlock->stuff[ 0 ])); + + bits[ 0 ][ 0 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 0 ][ 1 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 0 ][ 2 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 0 ][ 3 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 1 ][ 0 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 1 ][ 1 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 1 ][ 2 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 1 ][ 3 ] = (unsigned char) (stuff & 0x00000007); + + /* last two rows */ + stuff = *((unsigned int*) &(alphaBlock->stuff[ 3 ])); /* last 3 bytes */ + + bits[ 2 ][ 0 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 2 ][ 1 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 2 ][ 2 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 2 ][ 3 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 3 ][ 0 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 3 ][ 1 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 3 ][ 2 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 3 ][ 3 ] = (unsigned char) (stuff & 0x00000007); + + /* decode the codes into alpha values */ + for( row = 0; row < 4; row++ ) + { + for( pix=0; pix < 4; pix++ ) + { + aColors[ row ][ pix ].r = 0; + aColors[ row ][ pix ].g = 0; + aColors[ row ][ pix ].b = 0; + aColors[ row ][ pix ].a = (unsigned char) alphas[ bits[ row ][ pix ] ]; + } + } + + /* write out alpha values to the image bits */ + for( row = 0; row < 4; row++, pixel += width-4 ) + { + for( pix = 0; pix < 4; pix++ ) + { + /* zero the alpha bits of image pixel */ + *pixel &= alphaZero; + + /* or the bits into the prev. nulled alpha */ + *pixel |= *((unsigned int*) &(aColors[ row ][ pix ])); + pixel++; + } + } +} + + + +/* +DDSDecompressDXT1() +decompresses a dxt1 format texture +*/ + +static int DDSDecompressDXT1( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int x, y, xBlocks, yBlocks; + unsigned int *pixel; + ddsColorBlock_t *block; + ddsColor_t colors[ 4 ]; + + + /* setup */ + xBlocks = width / 4; + yBlocks = height / 4; + + /* walk y */ + for( y = 0; y < yBlocks; y++ ) + { + /* 8 bytes per block */ + block = (ddsColorBlock_t*) ((unsigned int) dds->data + y * xBlocks * 8); + + /* walk x */ + for( x = 0; x < xBlocks; x++, block++ ) + { + DDSGetColorBlockColors( block, colors ); + pixel = (unsigned int*) (pixels + x * 16 + (y * 4) * width * 4); + DDSDecodeColorBlock( pixel, block, width, (unsigned int*) colors ); + } + } + + /* return ok */ + return 0; +} + + + +/* +DDSDecompressDXT3() +decompresses a dxt3 format texture +*/ + +static int DDSDecompressDXT3( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int x, y, xBlocks, yBlocks; + unsigned int *pixel, alphaZero; + ddsColorBlock_t *block; + ddsAlphaBlockExplicit_t *alphaBlock; + ddsColor_t colors[ 4 ]; + + + /* setup */ + xBlocks = width / 4; + yBlocks = height / 4; + + /* create zero alpha */ + colors[ 0 ].a = 0; + colors[ 0 ].r = 0xFF; + colors[ 0 ].g = 0xFF; + colors[ 0 ].b = 0xFF; + alphaZero = *((unsigned int*) &colors[ 0 ]); + + /* walk y */ + for( y = 0; y < yBlocks; y++ ) + { + /* 8 bytes per block, 1 block for alpha, 1 block for color */ + block = (ddsColorBlock_t*) ((unsigned int) dds->data + y * xBlocks * 16); + + /* walk x */ + for( x = 0; x < xBlocks; x++, block++ ) + { + /* get alpha block */ + alphaBlock = (ddsAlphaBlockExplicit_t*) block; + + /* get color block */ + block++; + DDSGetColorBlockColors( block, colors ); + + /* decode color block */ + pixel = (unsigned int*) (pixels + x * 16 + (y * 4) * width * 4); + DDSDecodeColorBlock( pixel, block, width, (unsigned int*) colors ); + + /* overwrite alpha bits with alpha block */ + DDSDecodeAlphaExplicit( pixel, alphaBlock, width, alphaZero ); + } + } + + /* return ok */ + return 0; +} + + + +/* +DDSDecompressDXT5() +decompresses a dxt5 format texture +*/ + +static int DDSDecompressDXT5( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int x, y, xBlocks, yBlocks; + unsigned int *pixel, alphaZero; + ddsColorBlock_t *block; + ddsAlphaBlock3BitLinear_t *alphaBlock; + ddsColor_t colors[ 4 ]; + + + /* setup */ + xBlocks = width / 4; + yBlocks = height / 4; + + /* create zero alpha */ + colors[ 0 ].a = 0; + colors[ 0 ].r = 0xFF; + colors[ 0 ].g = 0xFF; + colors[ 0 ].b = 0xFF; + alphaZero = *((unsigned int*) &colors[ 0 ]); + + /* walk y */ + for( y = 0; y < yBlocks; y++ ) + { + /* 8 bytes per block, 1 block for alpha, 1 block for color */ + block = (ddsColorBlock_t*) ((unsigned int) dds->data + y * xBlocks * 16); + + /* walk x */ + for( x = 0; x < xBlocks; x++, block++ ) + { + /* get alpha block */ + alphaBlock = (ddsAlphaBlock3BitLinear_t*) block; + + /* get color block */ + block++; + DDSGetColorBlockColors( block, colors ); + + /* decode color block */ + pixel = (unsigned int*) (pixels + x * 16 + (y * 4) * width * 4); + DDSDecodeColorBlock( pixel, block, width, (unsigned int*) colors ); + + /* overwrite alpha bits with alpha block */ + DDSDecodeAlpha3BitLinear( pixel, alphaBlock, width, alphaZero ); + } + } + + /* return ok */ + return 0; +} + + + +/* +DDSDecompressDXT2() +decompresses a dxt2 format texture (fixme: un-premultiply alpha) +*/ + +static int DDSDecompressDXT2( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int r; + + + /* decompress dxt3 first */ + r = DDSDecompressDXT3( dds, width, height, pixels ); + + /* return to sender */ + return r; +} + + + +/* +DDSDecompressDXT4() +decompresses a dxt4 format texture (fixme: un-premultiply alpha) +*/ + +static int DDSDecompressDXT4( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int r; + + + /* decompress dxt5 first */ + r = DDSDecompressDXT5( dds, width, height, pixels ); + + /* return to sender */ + return r; +} + + + +/* +DDSDecompressARGB8888() +decompresses an argb 8888 format texture +*/ + +static int DDSDecompressARGB8888( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int x, y; + unsigned char *in, *out; + + + /* setup */ + in = dds->data; + out = pixels; + + /* walk y */ + for( y = 0; y < height; y++ ) + { + /* walk x */ + for( x = 0; x < width; x++ ) + { + *out++ = *in++; + *out++ = *in++; + *out++ = *in++; + *out++ = *in++; + } + } + + /* return ok */ + return 0; +} + + + +/* +DDSDecompress() +decompresses a dds texture into an rgba image buffer, returns 0 on success +*/ + +int DDSDecompress( ddsBuffer_t *dds, unsigned char *pixels ) +{ + int width, height, r; + ddsPF_t pf; + + + /* get dds info */ + r = DDSGetInfo( dds, &width, &height, &pf ); + if( r ) + return r; + + /* decompress */ + switch( pf ) + { + case DDS_PF_ARGB8888: + /* fixme: support other [a]rgb formats */ + r = DDSDecompressARGB8888( dds, width, height, pixels ); + break; + + case DDS_PF_DXT1: + r = DDSDecompressDXT1( dds, width, height, pixels ); + break; + + case DDS_PF_DXT2: + r = DDSDecompressDXT2( dds, width, height, pixels ); + break; + + case DDS_PF_DXT3: + r = DDSDecompressDXT3( dds, width, height, pixels ); + break; + + case DDS_PF_DXT4: + r = DDSDecompressDXT4( dds, width, height, pixels ); + break; + + case DDS_PF_DXT5: + r = DDSDecompressDXT5( dds, width, height, pixels ); + break; + + default: + case DDS_PF_UNKNOWN: + memset( pixels, 0xFF, width * height * 4 ); + r = -1; + break; + } + + /* return to sender */ + return r; +} + diff --git a/tools/urt/libs/ddslib/ddslib.dsp b/tools/urt/libs/ddslib/ddslib.dsp new file mode 100644 index 00000000..1b684d90 --- /dev/null +++ b/tools/urt/libs/ddslib/ddslib.dsp @@ -0,0 +1,106 @@ +# Microsoft Developer Studio Project File - Name="ddslib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=ddslib - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "ddslib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "ddslib.mak" CFG="ddslib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "ddslib - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "ddslib - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "Perforce Project" +# PROP Scc_LocalPath ".." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "ddslib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +F90=df.exe +MTL=midl.exe +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "ddslib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +F90=df.exe +MTL=midl.exe +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "ddslib - Win32 Release" +# Name "ddslib - Win32 Debug" +# Begin Group "src" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\ddslib.c +# End Source File +# End Group +# Begin Group "include" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\ddslib.h +# End Source File +# End Group +# End Target +# End Project diff --git a/tools/urt/libs/ddslib/ddslib.plg b/tools/urt/libs/ddslib/ddslib.plg new file mode 100644 index 00000000..d6ec3b91 --- /dev/null +++ b/tools/urt/libs/ddslib/ddslib.plg @@ -0,0 +1,27 @@ + + +
+

Build Log

+

+--------------------Configuration: ddslib - Win32 Debug-------------------- +

+

Command Lines

+Creating temporary file "C:\DOCUME~1\TWENTY~1\LOCALS~1\Temp\RSP5F1.tmp" with contents +[ +/nologo /MDd /W3 /Gm /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR"Debug/" /Fo"Debug/" /Fd"Debug/" /FD /GZ /c +"C:\Program Files\Subversion\GtkRadiant\libs\ddslib\ddslib.c" +] +Creating command line "cl.exe @C:\DOCUME~1\TWENTY~1\LOCALS~1\Temp\RSP5F1.tmp" +Creating command line "link.exe -lib /nologo /out:"Debug\ddslib.lib" ".\Debug\ddslib.obj" " +

Output Window

+Compiling... +ddslib.c +Creating library... + + + +

Results

+ddslib.lib - 0 error(s), 0 warning(s) +
+ + diff --git a/tools/urt/libs/ddslib/ddslib.vcproj b/tools/urt/libs/ddslib/ddslib.vcproj new file mode 100644 index 00000000..66683739 --- /dev/null +++ b/tools/urt/libs/ddslib/ddslib.vcproj @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/urt/libs/debugging/debugging.cpp b/tools/urt/libs/debugging/debugging.cpp new file mode 100644 index 00000000..b0bae9a1 --- /dev/null +++ b/tools/urt/libs/debugging/debugging.cpp @@ -0,0 +1,8 @@ + +#include "debugging.h" + +void TEST_ASSERT() +{ + ERROR_MESSAGE("test"); + ASSERT_NOTNULL(0); +} diff --git a/tools/urt/libs/debugging/debugging.h b/tools/urt/libs/debugging/debugging.h new file mode 100644 index 00000000..d39dd6df --- /dev/null +++ b/tools/urt/libs/debugging/debugging.h @@ -0,0 +1,115 @@ + +#if !defined(INCLUDED_DEBUGGING_DEBUGGING_H) +#define INCLUDED_DEBUGGING_DEBUGGING_H + +/// \file +/// \brief Debugging macros for fatal error/assert messages. + +#include "stream/textstream.h" +#include "warnings.h" +#include "generic/static.h" + +#if defined(_MSC_VER) && defined(_M_IX86) +#define DEBUGGER_BREAKPOINT() __asm { int 3 } +#elif defined (__i386__) && defined (__GNUC__) && __GNUC__ >= 2 +#define DEBUGGER_BREAKPOINT() __asm__ __volatile__ ("int $03") +#else +#include + +#define DEBUGGER_BREAKPOINT() raise(SIGTRAP); +#endif + + +#define FILE_LINE __FILE__ ":" << __LINE__ + +#if defined(_DEBUG) || 1 +#define DEBUG_ASSERTS +#endif + +class DebugMessageHandler +{ +public: + virtual TextOutputStream& getOutputStream() = 0; + virtual bool handleMessage() = 0; +}; + +class NullDebugMessageHandler : public NullOutputStream, public DebugMessageHandler +{ +public: + virtual TextOutputStream& getOutputStream() + { + return *this; + } + virtual bool handleMessage() + { + return false; + } +}; + +class DefaultDebugMessageHandler : public DebugMessageHandler +{ +public: + virtual TextOutputStream& getOutputStream() + { + return globalErrorStream(); + } + virtual bool handleMessage() + { +#if defined(_DEBUG) + return false; // send debug-break +#else + return true; +#endif + } +}; + +class DebugMessageHandlerRef : public DefaultDebugMessageHandler +{ + DebugMessageHandler* m_handler; +public: + DebugMessageHandlerRef() + : m_handler(this) + { + } + void setHandler(DebugMessageHandler& handler) + { + m_handler = &handler; + } + DebugMessageHandler& getHandler() + { + return *m_handler; + } +}; + +typedef Static GlobalDebugMessageHandler; + +inline DebugMessageHandler& globalDebugMessageHandler() +{ + return GlobalDebugMessageHandler::instance().getHandler(); +} + +#if defined(DEBUG_ASSERTS) + +/// \brief Sends a \p message to the current debug-message-handler text-output-stream if \p condition evaluates to false. +#define ASSERT_MESSAGE(condition, message)\ +if(!(condition))\ +{\ + globalDebugMessageHandler().getOutputStream() << FILE_LINE << "\nassertion failure: " << message << "\n";\ + if(!globalDebugMessageHandler().handleMessage()) { DEBUGGER_BREAKPOINT(); }\ +} else\ + +/// \brief Sends a \p message to the current debug-message-handler text-output-stream. +#define ERROR_MESSAGE(message)\ +globalDebugMessageHandler().getOutputStream() << FILE_LINE << "\nruntime error: " << message << "\n";\ +if(!globalDebugMessageHandler().handleMessage()) { DEBUGGER_BREAKPOINT(); } else\ + +#define ASSERT_NOTNULL(ptr) ASSERT_MESSAGE(ptr != 0, "pointer \"" #ptr "\" is null") + +#else + +#define ASSERT_MESSAGE(condition, message) +#define ASSERT_NOTNULL(ptr) + +#endif + +#endif diff --git a/tools/urt/libs/dragplanes.cpp b/tools/urt/libs/dragplanes.cpp new file mode 100644 index 00000000..0aed24ba --- /dev/null +++ b/tools/urt/libs/dragplanes.cpp @@ -0,0 +1,3 @@ + +#include "dragplanes.h" + diff --git a/tools/urt/libs/dragplanes.h b/tools/urt/libs/dragplanes.h new file mode 100644 index 00000000..94f1fbe4 --- /dev/null +++ b/tools/urt/libs/dragplanes.h @@ -0,0 +1,233 @@ + +#if !defined(INCLUDED_DRAGPLANES_H) +#define INCLUDED_DRAGPLANES_H + +#include "selectable.h" +#include "selectionlib.h" +#include "math/aabb.h" +#include "math/line.h" + +class DragPlanes +{ +public: + ObservedSelectable m_selectable_right; // +x + ObservedSelectable m_selectable_left; // -x + ObservedSelectable m_selectable_front; // +y + ObservedSelectable m_selectable_back; // -y + ObservedSelectable m_selectable_top; // +z + ObservedSelectable m_selectable_bottom; // -z + Vector3 m_dragPlanesMin; + Vector3 m_dragPlanesMax; + Vector3 m_dragPlanesOrigin; + Vector3 m_dragPlanesExtents; + + DragPlanes(const SelectionChangeCallback& onchanged) : + m_selectable_right(onchanged), + m_selectable_left(onchanged), + m_selectable_front(onchanged), + m_selectable_back(onchanged), + m_selectable_top(onchanged), + m_selectable_bottom(onchanged) + { + } + bool isSelected() const + { + return m_selectable_right.isSelected() + || m_selectable_left.isSelected() + || m_selectable_front.isSelected() + || m_selectable_back.isSelected() + || m_selectable_top.isSelected() + || m_selectable_bottom.isSelected(); + } + void setSelected(bool selected) + { + m_selectable_right.setSelected(selected); + m_selectable_left.setSelected(selected); + m_selectable_front.setSelected(selected); + m_selectable_back.setSelected(selected); + m_selectable_top.setSelected(selected); + m_selectable_bottom.setSelected(selected); + } + void selectPlanes(const AABB& aabb, Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback) + { + Line line(test.getNear(), test.getFar()); + Vector3 corners[8]; + aabb_corners(aabb, corners); + Plane3 planes[6]; + aabb_planes(aabb, planes); + + for(Vector3* i = corners; i != corners + 8; ++i) + { + *i = vector3_subtracted(line_closest_point(line, *i), *i); + } + + if(vector3_dot(planes[0].normal(), corners[1]) > 0 + && vector3_dot(planes[0].normal(), corners[2]) > 0 + && vector3_dot(planes[0].normal(), corners[5]) > 0 + && vector3_dot(planes[0].normal(), corners[6]) > 0) + { + Selector_add(selector, m_selectable_right); + selectedPlaneCallback(planes[0]); + //globalOutputStream() << "right\n"; + } + if(vector3_dot(planes[1].normal(), corners[0]) > 0 + && vector3_dot(planes[1].normal(), corners[3]) > 0 + && vector3_dot(planes[1].normal(), corners[4]) > 0 + && vector3_dot(planes[1].normal(), corners[7]) > 0) + { + Selector_add(selector, m_selectable_left); + selectedPlaneCallback(planes[1]); + //globalOutputStream() << "left\n"; + } + if(vector3_dot(planes[2].normal(), corners[0]) > 0 + && vector3_dot(planes[2].normal(), corners[1]) > 0 + && vector3_dot(planes[2].normal(), corners[4]) > 0 + && vector3_dot(planes[2].normal(), corners[5]) > 0) + { + Selector_add(selector, m_selectable_front); + selectedPlaneCallback(planes[2]); + //globalOutputStream() << "front\n"; + } + if(vector3_dot(planes[3].normal(), corners[2]) > 0 + && vector3_dot(planes[3].normal(), corners[3]) > 0 + && vector3_dot(planes[3].normal(), corners[6]) > 0 + && vector3_dot(planes[3].normal(), corners[7]) > 0) + { + Selector_add(selector, m_selectable_back); + selectedPlaneCallback(planes[3]); + //globalOutputStream() << "back\n"; + } + if(vector3_dot(planes[4].normal(), corners[0]) > 0 + && vector3_dot(planes[4].normal(), corners[1]) > 0 + && vector3_dot(planes[4].normal(), corners[2]) > 0 + && vector3_dot(planes[4].normal(), corners[3]) > 0) + { + Selector_add(selector, m_selectable_top); + selectedPlaneCallback(planes[4]); + //globalOutputStream() << "top\n"; + } + if(vector3_dot(planes[5].normal(), corners[4]) > 0 + && vector3_dot(planes[5].normal(), corners[5]) > 0 + && vector3_dot(planes[5].normal(), corners[6]) > 0 + && vector3_dot(planes[5].normal(), corners[7]) > 0) + { + Selector_add(selector, m_selectable_bottom); + //globalOutputStream() << "bottom\n"; + selectedPlaneCallback(planes[5]); + } + + m_dragPlanesMin = aabb.origin - aabb.extents; + m_dragPlanesMax = aabb.origin + aabb.extents; + m_dragPlanesOrigin = aabb.origin; + m_dragPlanesExtents = aabb.extents; + } + void selectReversedPlanes(const AABB& aabb, Selector& selector, const SelectedPlanes& selectedPlanes) + { + Plane3 planes[6]; + aabb_planes(aabb, planes); + + if(selectedPlanes.contains(plane3_flipped(planes[0]))) + { + Selector_add(selector, m_selectable_right); + } + if(selectedPlanes.contains(plane3_flipped(planes[1]))) + { + Selector_add(selector, m_selectable_left); + } + if(selectedPlanes.contains(plane3_flipped(planes[2]))) + { + Selector_add(selector, m_selectable_front); + } + if(selectedPlanes.contains(plane3_flipped(planes[3]))) + { + Selector_add(selector, m_selectable_back); + } + if(selectedPlanes.contains(plane3_flipped(planes[4]))) + { + Selector_add(selector, m_selectable_top); + } + if(selectedPlanes.contains(plane3_flipped(planes[5]))) + { + Selector_add(selector, m_selectable_bottom); + } + } + void translate(const Vector3& translation) + { + if(m_dragPlanesExtents[0] != 0) + { + if(m_selectable_right.isSelected()) + { + m_dragPlanesMax[0] += translation[0]; + //globalOutputStream() << "moving right\n"; + } + if(m_selectable_left.isSelected()) + { + m_dragPlanesMin[0] += translation[0]; + //globalOutputStream() << "moving left\n"; + } + } + if(m_dragPlanesExtents[1] != 0) + { + if(m_selectable_front.isSelected()) + { + m_dragPlanesMax[1] += translation[1]; + //globalOutputStream() << "moving front\n"; + } + if(m_selectable_back.isSelected()) + { + m_dragPlanesMin[1] += translation[1]; + //globalOutputStream() << "moving back\n"; + } + } + if(m_dragPlanesExtents[2] != 0) + { + if(m_selectable_top.isSelected()) + { + m_dragPlanesMax[2] += translation[2]; + //globalOutputStream() << "moving top\n"; + } + if(m_selectable_bottom.isSelected()) + { + m_dragPlanesMin[2] += translation[2]; + //globalOutputStream() << "moving bottom\n"; + } + } + } + Matrix4 evaluateTransform() const + { + Vector3 originTransformed(vector3_mid(m_dragPlanesMin, m_dragPlanesMax)); + Vector3 scale(vector3_scaled(vector3_subtracted(m_dragPlanesMax, m_dragPlanesMin), 0.5)); + + if(m_dragPlanesExtents[0] != 0) + { + scale[0] /= m_dragPlanesExtents[0]; + } + else + { + scale[0] = 1; + } + if(m_dragPlanesExtents[1] != 0) + { + scale[1] /= m_dragPlanesExtents[1]; + } + else + { + scale[1] = 1; + } + if(m_dragPlanesExtents[2] != 0) + { + scale[2] /= m_dragPlanesExtents[2]; + } + else + { + scale[2] = 1; + } + + Matrix4 matrix(matrix4_translation_for_vec3(originTransformed - m_dragPlanesOrigin)); + matrix4_pivoted_scale_by_vec3(matrix, scale, m_dragPlanesOrigin); + + return matrix; + } +}; + +#endif diff --git a/tools/urt/libs/eclasslib.cpp b/tools/urt/libs/eclasslib.cpp new file mode 100644 index 00000000..2a841873 --- /dev/null +++ b/tools/urt/libs/eclasslib.cpp @@ -0,0 +1,2 @@ + +#include "eclasslib.h" diff --git a/tools/urt/libs/eclasslib.h b/tools/urt/libs/eclasslib.h new file mode 100644 index 00000000..83d4fb13 --- /dev/null +++ b/tools/urt/libs/eclasslib.h @@ -0,0 +1,321 @@ + +#if !defined (INCLUDED_ECLASSLIB_H) +#define INCLUDED_ECLASSLIB_H + +#include +#include +#include +#include +#include + +#include "ieclass.h" +#include "irender.h" + +#include "math/vector.h" +#include "string/string.h" + +typedef Vector3 Colour3; + +class ListAttributeType +{ + typedef std::pair ListItem; + typedef std::vector ListItems; + ListItems m_items; +public: + + typedef ListItems::const_iterator const_iterator; + const_iterator begin() const + { + return m_items.begin(); + } + const_iterator end() const + { + return m_items.end(); + } + + const ListItem& operator[](std::size_t i) const + { + return m_items[i]; + } + const_iterator findValue(const char* value) const + { + for(ListItems::const_iterator i = m_items.begin(); i != m_items.end(); ++i) + { + if(string_equal(value, (*i).second.c_str())) + { + return i; + } + } + return m_items.end(); + } + + void push_back(const char* name, const char* value) + { + m_items.push_back(ListItems::value_type(name, value)); + } +}; + +class EntityClassAttribute +{ +public: + CopiedString m_type; + CopiedString m_name; + CopiedString m_value; + CopiedString m_description; + EntityClassAttribute() + { + } + EntityClassAttribute(const char* type, const char* name, const char* value = "", const char* description = "") : m_type(type), m_name(name), m_value(value), m_description(description) + { + } +}; + +typedef std::pair EntityClassAttributePair; +typedef std::list EntityClassAttributes; +typedef std::list StringList; + +inline const char* EntityClassAttributePair_getName(const EntityClassAttributePair& attributePair) +{ + if(!string_empty(attributePair.second.m_name.c_str())) + { + return attributePair.second.m_name.c_str(); + } + return attributePair.first.c_str(); +} + +inline const char* EntityClassAttributePair_getDescription(const EntityClassAttributePair& attributePair) +{ + if(!string_empty(attributePair.second.m_description.c_str())) + { + return attributePair.second.m_description.c_str(); + } + return EntityClassAttributePair_getName(attributePair); +} + +class EntityClass +{ +public: + CopiedString m_name; + StringList m_parent; + bool fixedsize; + bool unknown; // wasn't found in source + Vector3 mins; + Vector3 maxs; + + Colour3 color; + Shader* m_state_fill; + Shader* m_state_wire; + Shader* m_state_blend; + + CopiedString m_comments; + char flagnames[MAX_FLAGS][32]; + + CopiedString m_modelpath; + CopiedString m_skin; + + void (*free)(EntityClass*); + + EntityClassAttributes m_attributes; + + bool inheritanceResolved; + bool sizeSpecified; + bool colorSpecified; + + const char* name() const + { + return m_name.c_str(); + } + const char* comments() const + { + return m_comments.c_str(); + } + const char* modelpath() const + { + return m_modelpath.c_str(); + } + const char* skin() const + { + return m_skin.c_str(); + } +}; + +inline const char* EntityClass_valueForKey(const EntityClass& entityClass, const char* key) +{ + for(EntityClassAttributes::const_iterator i = entityClass.m_attributes.begin(); i != entityClass.m_attributes.end(); ++i) + { + if(string_equal(key, (*i).first.c_str())) + { + return (*i).second.m_value.c_str(); + } + } + return ""; +} + +inline EntityClassAttributePair& EntityClass_insertAttribute(EntityClass& entityClass, const char* key, const EntityClassAttribute& attribute = EntityClassAttribute()) +{ + entityClass.m_attributes.push_back(EntityClassAttributePair(key, attribute)); + return entityClass.m_attributes.back(); +} + + +inline void buffer_write_colour_fill(char buffer[128], const Colour3& colour) +{ + sprintf(buffer, "(%g %g %g)", colour[0], colour[1], colour[2]); +} + +inline void buffer_write_colour_wire(char buffer[128], const Colour3& colour) +{ + sprintf(buffer, "<%g %g %g>", colour[0], colour[1], colour[2]); +} + +inline void buffer_write_colour_blend(char buffer[128], const Colour3& colour) +{ + sprintf(buffer, "[%g %g %g]", colour[0], colour[1], colour[2]); +} + +inline Shader* colour_capture_state_fill(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_fill(buffer, colour); + return GlobalShaderCache().capture(buffer); +} + +inline void colour_release_state_fill(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_fill(buffer, colour); + GlobalShaderCache().release(buffer); +} + +inline Shader* colour_capture_state_wire(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_wire(buffer, colour); + return GlobalShaderCache().capture(buffer); +} + +inline void colour_release_state_wire(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_wire(buffer, colour); + GlobalShaderCache().release(buffer); +} + +inline Shader* colour_capture_state_blend(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_blend(buffer, colour); + return GlobalShaderCache().capture(buffer); +} + +inline void colour_release_state_blend(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_blend(buffer, colour); + GlobalShaderCache().release(buffer); +} + +inline void eclass_capture_state(EntityClass* eclass) +{ + eclass->m_state_fill = colour_capture_state_fill(eclass->color); + eclass->m_state_wire = colour_capture_state_wire(eclass->color); + eclass->m_state_blend = colour_capture_state_blend(eclass->color); +} + +inline void eclass_release_state(EntityClass* eclass) +{ + colour_release_state_fill(eclass->color); + colour_release_state_wire(eclass->color); + colour_release_state_blend(eclass->color); +} + +// eclass constructor +inline EntityClass* Eclass_Alloc() +{ + EntityClass* e = new EntityClass; + + e->fixedsize = false; + e->unknown = false; + memset(e->flagnames, 0, MAX_FLAGS*32); + + e->maxs = Vector3(-1,-1,-1); + e->mins = Vector3(1, 1, 1); + + e->free = 0; + + e->inheritanceResolved = true; + e->sizeSpecified = false; + e->colorSpecified = false; + + return e; +} + +// eclass destructor +inline void Eclass_Free(EntityClass* e) +{ + eclass_release_state(e); + + delete e; +} + +inline bool classname_equal(const char* classname, const char* other) +{ + return string_equal(classname, other); +} + +inline EntityClass* EClass_Create(const char* name, const Vector3& colour, const char* comments) +{ + EntityClass *e = Eclass_Alloc(); + e->free = &Eclass_Free; + + e->m_name = name; + + e->color = colour; + eclass_capture_state(e); + + if (comments) + e->m_comments = comments; + + return e; +} + +inline EntityClass* EClass_Create_FixedSize(const char* name, const Vector3& colour, const Vector3& mins, const Vector3& maxs, const char* comments) +{ + EntityClass *e = Eclass_Alloc(); + e->free = &Eclass_Free; + + e->m_name = name; + + e->color = colour; + eclass_capture_state(e); + + e->fixedsize = true; + + e->mins = mins; + e->maxs = maxs; + + if (comments) + e->m_comments = comments; + + return e; +} + +const Vector3 smallbox[2] = { + Vector3(-8,-8,-8), + Vector3( 8, 8, 8), +}; + +inline EntityClass *EntityClass_Create_Default(const char *name, bool has_brushes) +{ + // create a new class for it + if (has_brushes) + { + return EClass_Create(name, Vector3(0.0f, 0.5f, 0.0f), "Not found in source."); + } + else + { + return EClass_Create_FixedSize(name, Vector3(0.0f, 0.5f, 0.0f), smallbox[0], smallbox[1], "Not found in source."); + } +} + +#endif diff --git a/tools/urt/libs/entitylib.cpp b/tools/urt/libs/entitylib.cpp new file mode 100644 index 00000000..c58a7dcd --- /dev/null +++ b/tools/urt/libs/entitylib.cpp @@ -0,0 +1,2 @@ + +#include "entitylib.h" diff --git a/tools/urt/libs/entitylib.h b/tools/urt/libs/entitylib.h new file mode 100644 index 00000000..aa6103b4 --- /dev/null +++ b/tools/urt/libs/entitylib.h @@ -0,0 +1,726 @@ + +#if !defined (INCLUDED_ENTITYLIB_H) +#define INCLUDED_ENTITYLIB_H + +#include "ireference.h" +#include "debugging/debugging.h" + +#include "ientity.h" +#include "irender.h" +#include "igl.h" +#include "selectable.h" + +#include "generic/callback.h" +#include "math/vector.h" +#include "math/aabb.h" +#include "undolib.h" +#include "string/string.h" +#include "generic/referencecounted.h" +#include "scenelib.h" +#include "container/container.h" +#include "eclasslib.h" + +#include +#include + +inline void arrow_draw(const Vector3& origin, const Vector3& direction) +{ + Vector3 up(0, 0, 1); + Vector3 left(-direction[1], direction[0], 0); + + Vector3 endpoint(vector3_added(origin, vector3_scaled(direction, 32.0))); + + Vector3 tip1(vector3_added(vector3_added(endpoint, vector3_scaled(direction, -8.0)), vector3_scaled(up, -4.0))); + Vector3 tip2(vector3_added(tip1, vector3_scaled(up, 8.0))); + Vector3 tip3(vector3_added(vector3_added(endpoint, vector3_scaled(direction, -8.0)), vector3_scaled(left, -4.0))); + Vector3 tip4(vector3_added(tip3, vector3_scaled(left, 8.0))); + + glBegin (GL_LINES); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(endpoint)); + + glVertex3fv(vector3_to_array(endpoint)); + glVertex3fv(vector3_to_array(tip1)); + + glVertex3fv(vector3_to_array(endpoint)); + glVertex3fv(vector3_to_array(tip2)); + + glVertex3fv(vector3_to_array(endpoint)); + glVertex3fv(vector3_to_array(tip3)); + + glVertex3fv(vector3_to_array(endpoint)); + glVertex3fv(vector3_to_array(tip4)); + + glVertex3fv(vector3_to_array(tip1)); + glVertex3fv(vector3_to_array(tip3)); + + glVertex3fv(vector3_to_array(tip3)); + glVertex3fv(vector3_to_array(tip2)); + + glVertex3fv(vector3_to_array(tip2)); + glVertex3fv(vector3_to_array(tip4)); + + glVertex3fv(vector3_to_array(tip4)); + glVertex3fv(vector3_to_array(tip1)); + + glEnd(); +} + +class SelectionIntersection; + +inline void aabb_testselect(const AABB& aabb, SelectionTest& test, SelectionIntersection& best) +{ + const IndexPointer::index_type indices[24] = { + 2, 1, 5, 6, + 1, 0, 4, 5, + 0, 1, 2, 3, + 3, 7, 4, 0, + 3, 2, 6, 7, + 7, 6, 5, 4, + }; + + Vector3 points[8]; + aabb_corners(aabb, points); + test.TestQuads(VertexPointer(reinterpret_cast(points), sizeof(Vector3)), IndexPointer(indices, 24), best); +} + +inline void aabb_draw_wire(const Vector3 points[8]) +{ + typedef std::size_t index_t; + index_t indices[24] = { + 0, 1, 1, 2, 2, 3, 3, 0, + 4, 5, 5, 6, 6, 7, 7, 4, + 0, 4, 1, 5, 2, 6, 3, 7, + }; +#if 1 + glVertexPointer(3, GL_FLOAT, 0, points); + glDrawElements(GL_LINES, sizeof(indices)/sizeof(index_t), GL_UNSIGNED_INT, indices); +#else + glBegin(GL_LINES); + for(std::size_t i = 0; i < sizeof(indices)/sizeof(index_t); ++i) + { + glVertex3fv(points[indices[i]]); + } + glEnd(); +#endif +} + +inline void aabb_draw_flatshade(const Vector3 points[8]) +{ + glBegin(GL_QUADS); + + glNormal3fv(vector3_to_array(aabb_normals[0])); + glVertex3fv(vector3_to_array(points[2])); + glVertex3fv(vector3_to_array(points[1])); + glVertex3fv(vector3_to_array(points[5])); + glVertex3fv(vector3_to_array(points[6])); + + glNormal3fv(vector3_to_array(aabb_normals[1])); + glVertex3fv(vector3_to_array(points[1])); + glVertex3fv(vector3_to_array(points[0])); + glVertex3fv(vector3_to_array(points[4])); + glVertex3fv(vector3_to_array(points[5])); + + glNormal3fv(vector3_to_array(aabb_normals[2])); + glVertex3fv(vector3_to_array(points[0])); + glVertex3fv(vector3_to_array(points[1])); + glVertex3fv(vector3_to_array(points[2])); + glVertex3fv(vector3_to_array(points[3])); + + glNormal3fv(vector3_to_array(aabb_normals[3])); + glVertex3fv(vector3_to_array(points[0])); + glVertex3fv(vector3_to_array(points[3])); + glVertex3fv(vector3_to_array(points[7])); + glVertex3fv(vector3_to_array(points[4])); + + glNormal3fv(vector3_to_array(aabb_normals[4])); + glVertex3fv(vector3_to_array(points[3])); + glVertex3fv(vector3_to_array(points[2])); + glVertex3fv(vector3_to_array(points[6])); + glVertex3fv(vector3_to_array(points[7])); + + glNormal3fv(vector3_to_array(aabb_normals[5])); + glVertex3fv(vector3_to_array(points[7])); + glVertex3fv(vector3_to_array(points[6])); + glVertex3fv(vector3_to_array(points[5])); + glVertex3fv(vector3_to_array(points[4])); + + glEnd(); +} + +inline void aabb_draw_wire(const AABB& aabb) +{ + Vector3 points[8]; + aabb_corners(aabb, points); + aabb_draw_wire(points); +} + +inline void aabb_draw_flatshade(const AABB& aabb) +{ + Vector3 points[8]; + aabb_corners(aabb, points); + aabb_draw_flatshade(points); +} + +inline void aabb_draw_textured(const AABB& aabb) +{ + Vector3 points[8]; + aabb_corners(aabb, points); + + glBegin(GL_QUADS); + + glNormal3fv(vector3_to_array(aabb_normals[0])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[2])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[1])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[5])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[6])); + + glNormal3fv(vector3_to_array(aabb_normals[1])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[1])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[0])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[4])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[5])); + + glNormal3fv(vector3_to_array(aabb_normals[2])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[0])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[1])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[2])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[3])); + + glNormal3fv(vector3_to_array(aabb_normals[3])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[0])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[3])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[7])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[4])); + + glNormal3fv(vector3_to_array(aabb_normals[4])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[3])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[2])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[6])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[7])); + + glNormal3fv(vector3_to_array(aabb_normals[5])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[7])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[6])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[5])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[4])); + + glEnd(); +} + +inline void aabb_draw_solid(const AABB& aabb, RenderStateFlags state) +{ + if(state & RENDER_TEXTURE) + { + aabb_draw_textured(aabb); + } + else + { + aabb_draw_flatshade(aabb); + } +} + +inline void aabb_draw(const AABB& aabb, RenderStateFlags state) +{ + if(state & RENDER_FILL) + { + aabb_draw_solid(aabb, state); + } + else + { + aabb_draw_wire(aabb); + } +} + +class RenderableSolidAABB : public OpenGLRenderable +{ + const AABB& m_aabb; +public: + RenderableSolidAABB(const AABB& aabb) : m_aabb(aabb) + { + } + void render(RenderStateFlags state) const + { + aabb_draw_solid(m_aabb, state); + } +}; + +class RenderableWireframeAABB : public OpenGLRenderable +{ + const AABB& m_aabb; +public: + RenderableWireframeAABB(const AABB& aabb) : m_aabb(aabb) + { + } + void render(RenderStateFlags state) const + { + aabb_draw_wire(m_aabb); + } +}; + + +typedef Callback1 KeyObserver; + +/// \brief A key/value pair of strings. +/// +/// - Notifies observers when value changes - value changes to "" on destruction. +/// - Provides undo support through the global undo system. +class KeyValue +{ + typedef UnsortedSet KeyObservers; + + std::size_t m_refcount; + KeyObservers m_observers; + CopiedString m_string; + const char* m_empty; + ObservedUndoableObject m_undo; + static EntityCreator::KeyValueChangedFunc m_entityKeyValueChanged; +public: + + KeyValue(const char* string, const char* empty) + : m_refcount(0), m_string(string), m_empty(empty), m_undo(m_string, UndoImportCaller(*this)) + { + notify(); + } + ~KeyValue() + { + ASSERT_MESSAGE(m_observers.empty(), "KeyValue::~KeyValue: observers still attached"); + } + + static void setKeyValueChangedFunc(EntityCreator::KeyValueChangedFunc func) + { + m_entityKeyValueChanged = func; + } + + void IncRef() + { + ++m_refcount; + } + void DecRef() + { + if(--m_refcount == 0) + { + delete this; + } + } + + void instanceAttach(MapFile* map) + { + m_undo.instanceAttach(map); + } + void instanceDetach(MapFile* map) + { + m_undo.instanceDetach(map); + } + + void attach(const KeyObserver& observer) + { + (*m_observers.insert(observer))(c_str()); + } + void detach(const KeyObserver& observer) + { + observer(m_empty); + m_observers.erase(observer); + } + const char* c_str() const + { + if(string_empty(m_string.c_str())) + { + return m_empty; + } + return m_string.c_str(); + } + void assign(const char* other) + { + if(!string_equal(m_string.c_str(), other)) + { + m_undo.save(); + m_string = other; + notify(); + } + } + + void notify() + { + m_entityKeyValueChanged(); + KeyObservers::reverse_iterator i = m_observers.rbegin(); + while(i != m_observers.rend()) + { + (*i++)(c_str()); + } + } + + void importState(const CopiedString& string) + { + m_string = string; + + notify(); + } + typedef MemberCaller1 UndoImportCaller; +}; + +/// \brief An unsorted list of key/value pairs. +/// +/// - Notifies observers when a pair is inserted or removed. +/// - Provides undo support through the global undo system. +/// - New keys are appended to the end of the list. +class EntityKeyValues : public Entity +{ +public: + typedef KeyValue Value; + + class Observer + { + public: + virtual void insert(const char* key, Value& value) = 0; + virtual void erase(const char* key, Value& value) = 0; + }; + +private: + static EntityCreator::KeyValueChangedFunc m_entityKeyValueChanged; + static Counter* m_counter; + + EntityClass* m_eclass; + + typedef SmartPointer KeyValuePtr; + typedef UnsortedMap KeyValues; + KeyValues m_keyValues; + + typedef UnsortedSet Observers; + Observers m_observers; + + ObservedUndoableObject m_undo; + bool m_instanced; + + bool m_observerMutex; + + void notifyInsert(const char* key, Value& value) + { + m_observerMutex = true; + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->insert(key, value); + } + m_observerMutex = false; + } + void notifyErase(const char* key, Value& value) + { + m_observerMutex = true; + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->erase(key, value); + } + m_observerMutex = false; + } + void forEachKeyValue_notifyInsert() + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + notifyInsert((*i).first.c_str(), *(*i).second); + } + } + void forEachKeyValue_notifyErase() + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + notifyErase((*i).first.c_str(), *(*i).second); + } + } + + void insert(const char* key, const KeyValuePtr& keyValue) + { + KeyValues::iterator i = m_keyValues.insert(KeyValues::value_type(key, keyValue)); + notifyInsert(key, *(*i).second); + + if(m_instanced) + { + (*i).second->instanceAttach(m_undo.map()); + } + } + + void insert(const char* key, const char* value) + { + KeyValues::iterator i = m_keyValues.find(key); + if(i != m_keyValues.end()) + { + (*i).second->assign(value); + } + else + { + m_undo.save(); + insert(key, KeyValuePtr(new KeyValue(value, EntityClass_valueForKey(*m_eclass, key)))); + } + } + + void erase(KeyValues::iterator i) + { + if(m_instanced) + { + (*i).second->instanceDetach(m_undo.map()); + } + + CopiedString key((*i).first); + KeyValuePtr value((*i).second); + m_keyValues.erase(i); + notifyErase(key.c_str(), *value); + } + + void erase(const char* key) + { + KeyValues::iterator i = m_keyValues.find(key); + if(i != m_keyValues.end()) + { + m_undo.save(); + erase(i); + } + } + +public: + EntityKeyValues(EntityClass* eclass) : + m_eclass(eclass), + m_undo(m_keyValues, UndoImportCaller(*this)), + m_instanced(false), + m_observerMutex(false) + { + } + EntityKeyValues(const EntityKeyValues& other) : + Entity(other), + m_eclass(&other.getEntityClass()), + m_undo(m_keyValues, UndoImportCaller(*this)), + m_instanced(false), + m_observerMutex(false) + { + for(KeyValues::const_iterator i = other.m_keyValues.begin(); i != other.m_keyValues.end(); ++i) + { + insert((*i).first.c_str(), (*i).second->c_str()); + } + } + ~EntityKeyValues() + { + ASSERT_MESSAGE(m_observers.empty(), "EntityKeyValues::~EntityKeyValues: observers still attached"); + } + + static void setKeyValueChangedFunc(EntityCreator::KeyValueChangedFunc func) + { + m_entityKeyValueChanged = func; + KeyValue::setKeyValueChangedFunc(func); + } + static void setCounter(Counter* counter) + { + m_counter = counter; + } + + void importState(const KeyValues& keyValues) + { + for(KeyValues::iterator i = m_keyValues.begin(); i != m_keyValues.end();) + { + erase(i++); + } + + for(KeyValues::const_iterator i = keyValues.begin(); i != keyValues.end(); ++i) + { + insert((*i).first.c_str(), (*i).second); + } + + m_entityKeyValueChanged(); + } + typedef MemberCaller1 UndoImportCaller; + + void attach(Observer& observer) + { + ASSERT_MESSAGE(!m_observerMutex, "observer cannot be attached during iteration"); + m_observers.insert(&observer); + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + observer.insert((*i).first.c_str(), *(*i).second); + } + } + void detach(Observer& observer) + { + ASSERT_MESSAGE(!m_observerMutex, "observer cannot be detached during iteration"); + m_observers.erase(&observer); + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + observer.erase((*i).first.c_str(), *(*i).second); + } + } + + void forEachKeyValue_instanceAttach(MapFile* map) + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + (*i).second->instanceAttach(map); + } + } + void forEachKeyValue_instanceDetach(MapFile* map) + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + (*i).second->instanceDetach(map); + } + } + + void instanceAttach(MapFile* map) + { + if(m_counter != 0) + { + m_counter->increment(); + } + + m_instanced = true; + forEachKeyValue_instanceAttach(map); + m_undo.instanceAttach(map); + } + void instanceDetach(MapFile* map) + { + if(m_counter != 0) + { + m_counter->decrement(); + } + + m_undo.instanceDetach(map); + forEachKeyValue_instanceDetach(map); + m_instanced = false; + } + + // entity + EntityClass& getEntityClass() const + { + return *m_eclass; + } + void forEachKeyValue(Visitor& visitor) const + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + visitor.visit((*i).first.c_str(), (*i).second->c_str()); + } + } + void setKeyValue(const char* key, const char* value) + { + if(value[0] == '\0' + /*|| string_equal(EntityClass_valueForKey(*m_eclass, key), value)*/) // don't delete values equal to default + { + erase(key); + } + else + { + insert(key, value); + } + m_entityKeyValueChanged(); + } + const char* getKeyValue(const char* key) const + { + KeyValues::const_iterator i = m_keyValues.find(key); + if(i != m_keyValues.end()) + { + return (*i).second->c_str(); + } + + return EntityClass_valueForKey(*m_eclass, key); + } +}; + +/// \brief A Resource reference with a controlled lifetime. +/// \brief The resource is released when the ResourceReference is destroyed. +class ResourceReference +{ + CopiedString m_name; + Resource* m_resource; +public: + ResourceReference(const char* name) + : m_name(name) + { + capture(); + } + ResourceReference(const ResourceReference& other) + : m_name(other.m_name) + { + capture(); + } + ResourceReference& operator=(const ResourceReference& other) + { + ResourceReference tmp(other); + tmp.swap(*this); + return *this; + } + ~ResourceReference() + { + release(); + } + + void capture() + { + m_resource = GlobalReferenceCache().capture(m_name.c_str()); + } + void release() + { + GlobalReferenceCache().release(m_name.c_str()); + } + + const char* getName() const + { + return m_name.c_str(); + } + void setName(const char* name) + { + ResourceReference tmp(name); + tmp.swap(*this); + } + + void swap(ResourceReference& other) + { + std::swap(m_resource, other.m_resource); + std::swap(m_name, other.m_name); + } + + void attach(ModuleObserver& observer) + { + m_resource->attach(observer); + } + void detach(ModuleObserver& observer) + { + m_resource->detach(observer); + } + + Resource* get() + { + return m_resource; + } +}; + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap. + inline void swap(ResourceReference& self, ResourceReference& other) + { + self.swap(other); + } +} + +#endif diff --git a/tools/urt/libs/entityxml.cpp b/tools/urt/libs/entityxml.cpp new file mode 100644 index 00000000..ccb9bcae --- /dev/null +++ b/tools/urt/libs/entityxml.cpp @@ -0,0 +1,2 @@ + +#include "entityxml.h" diff --git a/tools/urt/libs/entityxml.h b/tools/urt/libs/entityxml.h new file mode 100644 index 00000000..609a1b21 --- /dev/null +++ b/tools/urt/libs/entityxml.h @@ -0,0 +1,88 @@ + +#if !defined(INCLUDED_ENTITYXML_H) +#define INCLUDED_ENTITYXML_H + +#include "ientity.h" +#include "xml/ixml.h" +#include "xml/xmlelement.h" + +class entity_import : public XMLImporter +{ + Entity& m_entity; +public: + entity_import(Entity& entity) + : m_entity(entity) + { + } + void pushElement(const XMLElement& element) + { + if(strcmp(element.name(), "epair") == 0) + m_entity.setKeyValue(element.attribute("key"), element.attribute("value")); + } + void popElement(const char* name) + { + } + std::size_t write(const char* data, std::size_t length) + { + return length; + } +}; + +class entity_export : public XMLExporter +{ + class ExportXMLVisitor : public Entity::Visitor + { + XMLImporter& m_importer; + public: + ExportXMLVisitor(XMLImporter& importer) : m_importer(importer) + { + } + void visit(const char* key, const char* value) + { + StaticElement element("epair"); + element.insertAttribute("key", key); + element.insertAttribute("value", value); + m_importer.pushElement(element); + m_importer.popElement(element.name()); + } + }; + + const Entity& m_entity; + +public: + entity_export(const Entity& entity) : m_entity(entity) + { + } + void exportXML(XMLImporter& observer) + { + ExportXMLVisitor visitor(observer); + + m_entity.forEachKeyValue(visitor); + } +}; + +inline void entity_copy(Entity& entity, const Entity& other) +{ + entity_export exporter(other); + entity_import importer(entity); + exporter.exportXML(importer); +} + +template +class EntityConstruction +{ +public: + typedef EntityClass* type; + static type get(const EntityType& entity) + { + return &entity.getEntity().getEntityClass(); + } + static void copy(EntityType& entity, const EntityType& other) + { + entity_copy(entity.getEntity(), other.getEntity()); + } +}; + + + +#endif diff --git a/tools/urt/libs/fs_filesystem.cpp b/tools/urt/libs/fs_filesystem.cpp new file mode 100644 index 00000000..fac81413 --- /dev/null +++ b/tools/urt/libs/fs_filesystem.cpp @@ -0,0 +1,2 @@ + +#include "fs_filesystem.h" diff --git a/tools/urt/libs/fs_filesystem.h b/tools/urt/libs/fs_filesystem.h new file mode 100644 index 00000000..ffcef612 --- /dev/null +++ b/tools/urt/libs/fs_filesystem.h @@ -0,0 +1,160 @@ + +#if !defined(INCLUDED_FS_FILESYSTEM_H) +#define INCLUDED_FS_FILESYSTEM_H + +#include "string/string.h" +#include "os/path.h" + +#include + +inline unsigned int path_get_depth(const char* path) +{ + unsigned int depth = 0; + while(path != 0 && path[0] != '\0') + { + path = strchr(path, '/'); + if(path != 0) + { + ++path; + } + ++depth; + } + return depth; +} + +/// \brief A generic unix-style file-system which maps paths to files and directories. +/// Provides average O(log n) find and insert methods. +/// \param file_type The data type which represents a file. +template +class GenericFileSystem +{ + class Path + { + CopiedString m_path; + unsigned int m_depth; + public: + Path(const char* path) + : m_path(path), m_depth(path_get_depth(c_str())) + { + } + Path(const char* start, const char* finish) + : m_path(start, finish), m_depth(path_get_depth(c_str())) + { + } + bool operator<(const Path& other) const + { + return string_less_nocase(c_str(), other.c_str()); + } + unsigned int depth() const + { + return m_depth; + } + const char* c_str() const + { + return m_path.c_str(); + } + }; + + class Entry + { + file_type* m_file; + public: + Entry(file_type* file) + : m_file(file) + { + } + file_type* file() const + { + return m_file; + } + bool is_directory() const + { + return file() == 0; + } + }; + + typedef std::map Entries; + Entries m_entries; + +public: + typedef typename Entries::iterator iterator; + typedef typename Entries::value_type value_type; + + iterator begin() + { + return m_entries.begin(); + } + iterator end() + { + return m_entries.end(); + } + + /// \brief Adds the file \p entry at \p path. + /// Creates all directories below \p path if they do not exist. + /// O(log n) on average. + void insert(const Path& path, const Entry& entry) + { + { + const char* end = path_remove_directory(path.c_str()); + while(end[0] != '\0') + { + Path dir(path.c_str(), end); + m_entries.insert(value_type(dir, Entry(0))); + end = path_remove_directory(end); + } + } + + m_entries.insert(value_type(path, entry)); + } + + /// \brief Returns the file at \p path or end() if not found. + iterator find(const Path& path) + { + return m_entries.find(path); + } + + iterator begin(const char* root) + { + if(root[0] == '\0') + { + return m_entries.begin(); + } + iterator i = m_entries.find(root); + if(i == m_entries.end()) + { + return i; + } + return ++i; + } + + /// \brief Performs a depth-first traversal of the file-system subtree rooted at \p root. + /// Traverses the entire tree if \p root is "". + /// Calls \p visitor.file() with the path to each file relative to the filesystem root. + /// Calls \p visitor.directory() with the path to each directory relative to the filesystem root. + template + void traverse(visitor_type visitor, const char* root) + { + unsigned int start_depth = path_get_depth(root); + unsigned int skip_depth = 0; + for(iterator i = begin(root); i != end() && i->first.depth() > start_depth; ++i) + { + if(i->first.depth() == skip_depth) + { + skip_depth = 0; + } + if(skip_depth == 0) + { + if(!i->second.is_directory()) + { + visitor.file(i->first.c_str()); + } + else if(visitor.directory(i->first.c_str(), i->first.depth() - start_depth)) + { + skip_depth = i->first.depth(); + } + } + } + } +}; + +#endif diff --git a/tools/urt/libs/fs_path.cpp b/tools/urt/libs/fs_path.cpp new file mode 100644 index 00000000..4017c66f --- /dev/null +++ b/tools/urt/libs/fs_path.cpp @@ -0,0 +1,2 @@ + +#include "fs_path.h" diff --git a/tools/urt/libs/fs_path.h b/tools/urt/libs/fs_path.h new file mode 100644 index 00000000..a8fafa57 --- /dev/null +++ b/tools/urt/libs/fs_path.h @@ -0,0 +1,72 @@ + +#if !defined(INCLUDED_FS_PATH_H) +#define INCLUDED_FS_PATH_H + +#include "stream/stringstream.h" + +/// \brief A unix-style path string which can be modified at runtime. +/// +/// - Maintains a path ending in a path-separator. +/// - Provides a limited STL-style interface to push and pop file or directory names at the end of the path. +class UnixPath +{ + StringBuffer m_string; + + void check_separator() + { + if(!empty() && m_string.back() != '/') + { + m_string.push_back('/'); + } + } + +public: + /// \brief Constructs with the directory \p root. + UnixPath(const char* root) + : m_string(root) + { + check_separator(); + } + + bool empty() const + { + return m_string.empty(); + } + + const char* c_str() const + { + return m_string.c_str(); + } + + /// \brief Appends the directory \p name. + void push(const char* name) + { + m_string.push_string(name); + check_separator(); + } + /// \brief Appends the directory [\p first, \p last). + void push(const char* first, const char* last) + { + m_string.push_range(first, last); + check_separator(); + } + /// \brief Appends the filename \p name. + void push_filename(const char* name) + { + m_string.push_string(name); + } + /// \brief Removes the last directory or filename appended. + void pop() + { + if(m_string.back() == '/') + { + m_string.pop_back(); + } + while(!empty() && m_string.back() != '/') + { + m_string.pop_back(); + } + } +}; + +#endif diff --git a/tools/urt/libs/generic/arrayrange.cpp b/tools/urt/libs/generic/arrayrange.cpp new file mode 100644 index 00000000..58f717dc --- /dev/null +++ b/tools/urt/libs/generic/arrayrange.cpp @@ -0,0 +1,3 @@ + +#include "arrayrange.h" + diff --git a/tools/urt/libs/generic/arrayrange.h b/tools/urt/libs/generic/arrayrange.h new file mode 100644 index 00000000..e1eae81e --- /dev/null +++ b/tools/urt/libs/generic/arrayrange.h @@ -0,0 +1,52 @@ + +#if !defined(INCLUDED_GENERIC_ARRAYRANGE_H) +#define INCLUDED_GENERIC_ARRAYRANGE_H + +/// \file +/// \brief Macros for automatically converting a compile-time-sized array to a range. + +template +struct ArrayRange +{ + typedef Element* Iterator; + ArrayRange(Iterator _begin, Iterator _end) + : begin(_begin), end(_end) + { + } + Iterator begin; + Iterator end; +}; + +template +inline ArrayRange makeArrayRange(Element* begin, Element* end) +{ + return ArrayRange(begin, end); +} + +template +struct ArrayConstRange +{ + typedef const Element* Iterator; + ArrayConstRange(Iterator _begin, Iterator _end) + : begin(_begin), end(_end) + { + } + Iterator begin; + Iterator end; +}; + +template +inline ArrayConstRange makeArrayRange(const Element* begin, const Element* end) +{ + return ArrayConstRange(begin, end); +} + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(*array)) +#define ARRAY_END(array) (array + ARRAY_SIZE(array)) +#define ARRAY_RANGE(array) (makeArrayRange(array, ARRAY_END(array))) + + +typedef ArrayConstRange StringArrayRange; +#define STRING_ARRAY_RANGE(array) (StringArrayRange(array, ARRAY_END(array))) + +#endif diff --git a/tools/urt/libs/generic/bitfield.cpp b/tools/urt/libs/generic/bitfield.cpp new file mode 100644 index 00000000..eb1d3f42 --- /dev/null +++ b/tools/urt/libs/generic/bitfield.cpp @@ -0,0 +1,3 @@ + +#include "bitfield.h" + diff --git a/tools/urt/libs/generic/bitfield.h b/tools/urt/libs/generic/bitfield.h new file mode 100644 index 00000000..29aee840 --- /dev/null +++ b/tools/urt/libs/generic/bitfield.h @@ -0,0 +1,113 @@ + +#if !defined(INCLUDED_GENERIC_BITFIELD_H) +#define INCLUDED_GENERIC_BITFIELD_H + +/// \file +/// \brief Type safe bitfield. + +/// \brief A bit-field value. +/// +/// - Can be forward-declared when the definition of Enumeration is unknown. +/// - Can only be constructed from valid enumerated values. +/// - Can only be compared and combined with others of the same type. +/// +/// \param Enumeration A type that contains an enum \c Value of the bits that can be set in this field. +template +class BitFieldValue : public Enumeration +{ + unsigned m_value; +protected: + explicit BitFieldValue(unsigned value) : m_value(value) + { + } +public: + BitFieldValue() : m_value(0) + { + } + explicit BitFieldValue(typename Enumeration::Value value) : m_value(1 << value) + { + } + unsigned get() const + { + return m_value; + } +}; + +template +class BitFieldValueUnsafe : public BitFieldValue +{ +public: + explicit BitFieldValueUnsafe(unsigned value) : BitFieldValue(value) + { + } +}; + +template +inline bool operator==(BitFieldValue self, BitFieldValue other) +{ + return self.get() == other.get(); +} +template +inline bool operator!=(BitFieldValue self, BitFieldValue other) +{ + return !operator==(self, other); +} + +template +inline BitFieldValue operator|(BitFieldValue self, BitFieldValue other) +{ + return BitFieldValueUnsafe(self.get() | other.get()); +} +template +inline BitFieldValue& operator|=(BitFieldValue& self, BitFieldValue other) +{ + return self = self | other; +} +template +inline BitFieldValue operator&(BitFieldValue self, BitFieldValue other) +{ + return BitFieldValueUnsafe(self.get() & other.get()); +} +template +inline BitFieldValue& operator&=(BitFieldValue& self, BitFieldValue other) +{ + return self = self & other; +} +template +inline BitFieldValue operator~(BitFieldValue self) +{ + return BitFieldValueUnsafe(~self.get()); +} + + + +inline unsigned int bitfield_enable(unsigned int bitfield, unsigned int mask) +{ + return bitfield | mask; +} +inline unsigned int bitfield_disable(unsigned int bitfield, unsigned int mask) +{ + return bitfield & ~mask; +} +inline bool bitfield_enabled(unsigned int bitfield, unsigned int mask) +{ + return (bitfield & mask) != 0; +} + +template +inline BitFieldValue bitfield_enable(BitFieldValue bitfield, BitFieldValue mask) +{ + return bitfield | mask; +} +template +inline BitFieldValue bitfield_disable(BitFieldValue bitfield, BitFieldValue mask) +{ + return bitfield & ~mask; +} +template +inline bool bitfield_enabled(BitFieldValue bitfield, BitFieldValue mask) +{ + return (bitfield & mask).get() != 0; +} + +#endif diff --git a/tools/urt/libs/generic/callback.cpp b/tools/urt/libs/generic/callback.cpp new file mode 100644 index 00000000..11e3be89 --- /dev/null +++ b/tools/urt/libs/generic/callback.cpp @@ -0,0 +1,89 @@ + +#include "callback.h" + +#if defined(_DEBUG) || defined(DOXYGEN) + +namespace ExampleMemberCaller +{ + // MemberCaller example + class Integer + { + public: + int value; + + void printValue() const + { + // print this->value here; + } + + void setValue() + { + value = 3; + } + // a typedef to make things more readable + typedef MemberCaller SetValueCaller; + }; + + void example() + { + Integer foo = { 0 }; + + { + Callback bar = ConstMemberCaller(foo); + + // invoke the callback + bar(); // foo.printValue() + } + + + { + // use the typedef to improve readability + Callback bar = Integer::SetValueCaller(foo); + + // invoke the callback + bar(); // foo.setValue() + } + } + // end example +} + +namespace ExampleReferenceCaller +{ + // ReferenceCaller example + void Int_printValue(const int& value) + { + // print value here; + } + + void Int_setValue(int& value) + { + value = 3; + } + + // a typedef to make things more readable + typedef ReferenceCaller IntSetValueCaller; + + void example() + { + int foo = 0; + + { + Callback bar = ConstReferenceCaller(foo); + + // invoke the callback + bar(); // Int_printValue(foo) + } + + + { + // use the typedef to improve readability + Callback bar = IntSetValueCaller(foo); + + // invoke the callback + bar(); // Int_setValue(foo) + } + } + // end example +} + +#endif diff --git a/tools/urt/libs/generic/callback.h b/tools/urt/libs/generic/callback.h new file mode 100644 index 00000000..9913439f --- /dev/null +++ b/tools/urt/libs/generic/callback.h @@ -0,0 +1,517 @@ + +#if !defined(INCLUDED_GENERIC_CLOSURE_H) +#define INCLUDED_GENERIC_CLOSURE_H + +/// \file +/// \brief Type-safe techniques for binding the first argument of an anonymous callback. + +#include + +/// \brief Combines a void pointer with a pointer to a function which operates on a void pointer. +/// +/// Use with the callback constructors MemberCaller, ConstMemberCaller, ReferenceCaller, ConstReferenceCaller, PointerCaller, ConstPointerCaller and FreeCaller. +class Callback +{ + typedef void (*Thunk)(void*); + void* m_environment; + Thunk m_thunk; + + static void nullThunk(void*) + { + } + +public: + Callback() : m_environment(0), m_thunk(nullThunk) + { + } + Callback(void* environment, Thunk function) : m_environment(environment), m_thunk(function) + { + } + void* getEnvironment() const + { + return m_environment; + } + Thunk getThunk() const + { + return m_thunk; + } + void operator()() const + { + m_thunk(m_environment); + } +}; + +inline bool operator==(const Callback& self, const Callback& other) +{ + return self.getEnvironment() == other.getEnvironment() && self.getThunk() == other.getThunk(); +} +inline bool operator<(const Callback& self, const Callback& other) +{ + return self.getEnvironment() < other.getEnvironment() || + (!(other.getEnvironment() < self.getEnvironment()) && self.getThunk() < other.getThunk()); +} + +/// \brief Combines a void pointer with a pointer to a function which operates on a void pointer and one other argument. +/// +/// Use with the callback constructors MemberCaller1, ConstMemberCaller1, ReferenceCaller1, ConstReferenceCaller1, PointerCaller1, ConstPointerCaller1 and FreeCaller1. +template +class Callback1 +{ + typedef void (*Thunk)(void*, FirstArgument); + void* m_environment; + Thunk m_thunk; + + static void nullThunk(void*, FirstArgument) + { + } + +public: + typedef FirstArgument first_argument_type; + + Callback1() : m_environment(0), m_thunk(nullThunk) + { + } + Callback1(void* environment, Thunk function) : m_environment(environment), m_thunk(function) + { + } + void* getEnvironment() const + { + return m_environment; + } + Thunk getThunk() const + { + return m_thunk; + } + void operator()(FirstArgument firstArgument) const + { + m_thunk(m_environment, firstArgument); + } +}; + +template +inline bool operator==(const Callback1& self, const Callback1& other) +{ + return self.getEnvironment() == other.getEnvironment() && self.getThunk() == other.getThunk(); +} +template +inline bool operator<(const Callback1& self, const Callback1& other) +{ + return self.getEnvironment() < other.getEnvironment() || + (!(other.getEnvironment() < self.getEnvironment()) && self.getThunk() < other.getThunk()); +} + +template +class FunctorInvoke +{ +public: + inline void operator()(Functor functor) + { + functor(); + } +}; + +typedef FunctorInvoke CallbackInvoke; + + +template +class Functor1Invoke +{ + FirstArgument m_firstArgument; +public: + Functor1Invoke(FirstArgument firstArgument) : m_firstArgument(firstArgument) + { + } + inline void operator()(Functor functor) + { + functor(m_firstArgument); + } +}; + + +typedef Callback1 BoolImportCallback; +typedef Callback1 BoolExportCallback; + +typedef Callback1 IntImportCallback; +typedef Callback1 IntExportCallback; + +typedef Callback1 FloatImportCallback; +typedef Callback1 FloatExportCallback; + +typedef Callback1 StringImportCallback; +typedef Callback1 StringExportCallback; + +typedef Callback1 SizeImportCallback; +typedef Callback1 SizeExportCallback; + + +/// \brief Forms a Callback from a non-const Environment reference and a non-const Environment member-function. +/// +/// \dontinclude generic/callback.cpp +/// \skipline MemberCaller example +/// \until end example +template +class MemberCaller +{ + Environment& m_environment; +public: + MemberCaller(Environment& environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return &m_environment; + } + static void thunk(void* environment) + { + ((*reinterpret_cast(environment)).*member)(); + } + operator Callback() const + { + return Callback(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a const Environment reference and a const Environment member-function. +/// +/// \dontinclude generic/callback.cpp +/// \skipline MemberCaller example +/// \until end example +template +class ConstMemberCaller +{ + const Environment& m_environment; +public: + ConstMemberCaller(const Environment& environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return const_cast(&m_environment); + } + static void thunk(void* environment) + { + ((*reinterpret_cast(environment)).*member)(); + } + operator Callback() const + { + return Callback(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a non-const Environment reference and a const Environment member-function which takes one argument. +template +class MemberCaller1 +{ + Environment& m_environment; +public: + MemberCaller1(Environment& environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return &m_environment; + } + static void thunk(void* environment, FirstArgument firstArgument) + { + ((*reinterpret_cast(environment)).*member)(firstArgument); + } + operator Callback1() const + { + return Callback1(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a const Environment reference and a const Environment member-function which takes one argument. +template +class ConstMemberCaller1 +{ + const Environment& m_environment; +public: + ConstMemberCaller1(const Environment& environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return const_cast(&m_environment); + } + static void thunk(void* environment, FirstArgument firstArgument) + { + ((*reinterpret_cast(environment)).*member)(firstArgument); + } + operator Callback1() const + { + return Callback1(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a non-const Environment reference and a free function which operates on a non-const Environment reference. +/// +/// \dontinclude generic/callback.cpp +/// \skipline ReferenceCaller example +/// \until end example +template +class ReferenceCaller +{ + Environment& m_environment; +public: + ReferenceCaller(Environment& environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return &m_environment; + } + static void thunk(void* environment) + { + (func)(*reinterpret_cast(environment)); + } + operator Callback() const + { + return Callback(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a const Environment reference and a free function which operates on a const Environment reference. +/// +/// \dontinclude generic/callback.cpp +/// \skipline ReferenceCaller example +/// \until end example +template +class ConstReferenceCaller +{ + const Environment& m_environment; +public: + ConstReferenceCaller(const Environment& environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return const_cast(&m_environment); + } + static void thunk(void* environment) + { + (func)(*reinterpret_cast(environment)); + } + operator Callback() const + { + return Callback(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a non-const Environment reference and a free function which operates on a non-const Environment reference and one other argument. +template +class ReferenceCaller1 +{ + Environment& m_environment; +public: + ReferenceCaller1(Environment& environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return &m_environment; + } + static void thunk(void* environment, FirstArgument firstArgument) + { + (func)(*reinterpret_cast(environment), firstArgument); + } + operator Callback1() const + { + return Callback1(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a const Environment reference and a free function which operates on a const Environment reference and one other argument. +template +class ConstReferenceCaller1 +{ + const Environment& m_environment; +public: + ConstReferenceCaller1(const Environment& environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return const_cast(&m_environment); + } + static void thunk(void* environment, FirstArgument firstArgument) + { + (func)(*reinterpret_cast(environment), firstArgument); + } + operator Callback1() const + { + return Callback1(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a non-const Environment pointer and a free function which operates on a non-const Environment pointer. +template +class PointerCaller +{ + Environment* m_environment; +public: + PointerCaller(Environment* environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return m_environment; + } + static void thunk(void* environment) + { + (func)(reinterpret_cast(environment)); + } + operator Callback() const + { + return Callback(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a const Environment pointer and a free function which operates on a const Environment pointer. +template +class ConstPointerCaller +{ + const Environment* m_environment; +public: + ConstPointerCaller(const Environment* environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return const_cast(m_environment); + } + static void thunk(void* environment) + { + (func)(reinterpret_cast(environment)); + } + operator Callback() const + { + return Callback(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a non-const Environment pointer and a free function which operates on a non-const Environment pointer and one other argument. +template +class PointerCaller1 +{ + Environment* m_environment; +public: + PointerCaller1(Environment* environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return m_environment; + } + static void thunk(void* environment, FirstArgument firstArgument) + { + (func)(reinterpret_cast(environment), firstArgument); + } + operator Callback1() const + { + return Callback1(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a const Environment pointer and a free function which operates on a const Environment pointer and one other argument. +template +class ConstPointerCaller1 +{ + const Environment* m_environment; +public: + ConstPointerCaller1(const Environment* environment) : m_environment(environment) + { + } + void* getEnvironment() const + { + return const_cast(m_environment); + } + static void thunk(void* environment, FirstArgument firstArgument) + { + (func)(reinterpret_cast(environment), firstArgument); + } + operator Callback1() const + { + return Callback1(getEnvironment(), thunk); + } +}; + + +/// \brief Forms a Callback from a free function which takes no arguments. +template +class FreeCaller +{ +public: + void* getEnvironment() const + { + return 0; + } + static void thunk(void*) + { + (func)(); + } + operator Callback() const + { + return Callback(getEnvironment(), thunk); + } +}; + +/// \brief Forms a Callback from a free function which takes a single argument. +template +class FreeCaller1 +{ +public: + void* getEnvironment() const + { + return 0; + } + static void thunk(void*, FirstArgument firstArgument) + { + (func)(firstArgument); + } + operator Callback1() const + { + return Callback1(getEnvironment(), thunk); + } +}; + + +/// \brief Constructs a Callback from a non-const \p functor with zero arguments. +/// +/// \param Functor Must define \c operator()(). +template +inline Callback makeCallback(Functor& functor) +{ + return Callback(MemberCaller(functor)); +} + +/// \brief Constructs a Callback from a const \p functor with zero arguments. +/// +/// \param Functor Must define const \c operator()(). +template +inline Callback makeCallback(const Functor& functor) +{ + return Callback(ConstMemberCaller(functor)); +} + +/// \brief Constructs a Callback1 from a non-const \p functor with one argument. +/// +/// \param Functor Must define \c first_argument_type and \c operator()(first_argument_type). +template +inline Callback1 makeCallback1(Functor& functor) +{ + typedef typename Functor::first_argument_type FirstArgument; + return Callback1(MemberCaller1(functor)); +} + +/// \brief Constructs a Callback1 from a const \p functor with one argument. +/// +/// \param Functor Must define \c first_argument_type and const \c operator()(first_argument_type). +template +inline Callback1 makeCallback1(const Functor& functor) +{ + typedef typename Functor::first_argument_type FirstArgument; + return Callback1(ConstMemberCaller1(functor)); +} + +#endif diff --git a/tools/urt/libs/generic/enumeration.cpp b/tools/urt/libs/generic/enumeration.cpp new file mode 100644 index 00000000..babf692e --- /dev/null +++ b/tools/urt/libs/generic/enumeration.cpp @@ -0,0 +1,3 @@ + +#include "enumeration.h" + diff --git a/tools/urt/libs/generic/enumeration.h b/tools/urt/libs/generic/enumeration.h new file mode 100644 index 00000000..b78b40ab --- /dev/null +++ b/tools/urt/libs/generic/enumeration.h @@ -0,0 +1,40 @@ + +#if !defined(INCLUDED_GENERIC_ENUMERATION_H) +#define INCLUDED_GENERIC_ENUMERATION_H + +/// \file +/// \brief Type safe enumeration. + +/// \brief An enumerated value. +/// +/// - Can be forward-declared when the definition of Enumeration is unknown. +/// - Can only be constructed from valid enumerated values. +/// - Can only be compared with others of the same type. +/// +/// \param Enumeration A type that contains an enum \c Value of the allowed values of the enumeration. +template +class EnumeratedValue : public Enumeration +{ + typename Enumeration::Value m_value; +public: + explicit EnumeratedValue(typename Enumeration::Value value) : m_value(value) + { + } + typename Enumeration::Value get() const + { + return m_value; + } +}; + +template +inline bool operator==(EnumeratedValue self, EnumeratedValue other) +{ + return self.get() == other.get(); +} +template +inline bool operator!=(EnumeratedValue self, EnumeratedValue other) +{ + return !operator==(self, other); +} + +#endif diff --git a/tools/urt/libs/generic/object.cpp b/tools/urt/libs/generic/object.cpp new file mode 100644 index 00000000..57fd76ed --- /dev/null +++ b/tools/urt/libs/generic/object.cpp @@ -0,0 +1,21 @@ + +#include "object.h" + +namespace +{ + class Blah + { + int i; + public: + Blah() + { + i = 3; + } + }; + + void Test() + { + char storage[sizeof(Blah)]; + constructor(*reinterpret_cast(storage)); + } +} \ No newline at end of file diff --git a/tools/urt/libs/generic/object.h b/tools/urt/libs/generic/object.h new file mode 100644 index 00000000..12900911 --- /dev/null +++ b/tools/urt/libs/generic/object.h @@ -0,0 +1,78 @@ + +#if !defined(INCLUDED_GENERIC_OBJECT_H) +#define INCLUDED_GENERIC_OBJECT_H + +/// \file +/// \brief Convenience functions (syntactic sugar) to wrap explicit constructor (aka in-place 'new') and destructor calls. +/// +/// Use makeReference() to wrap non-const-reference constructor parameters. + +#if _MSC_VER > 1000 && defined(WIN32) +#pragma warning(disable:4345) // behavior change: an object of POD type constructed with an initializer of the form () will be default-initialized +#endif + +#include + +template +inline void constructor(Type& object) +{ + new(&object) Type(); +} + +template +inline void constructor(Type& object, const T1& t1) +{ + new(&object) Type(t1); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2) +{ + new(&object) Type(t1, t2); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3) +{ + new(&object) Type(t1, t2, t3); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4) +{ + new(&object) Type(t1, t2, t3, t4); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5) +{ + new(&object) Type(t1, t2, t3, t4, t5); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6) +{ + new(&object) Type(t1, t2, t3, t4, t5, t6); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7) +{ + new(&object) Type(t1, t2, t3, t4, t5, t6, t7); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7, const T8& t8) +{ + new(&object) Type(t1, t2, t3, t4, t5, t6, t7, t8); +} + +template +inline void destructor(Type& object) +{ + object.~Type(); +} + + + +#endif diff --git a/tools/urt/libs/generic/reference.cpp b/tools/urt/libs/generic/reference.cpp new file mode 100644 index 00000000..1df0e189 --- /dev/null +++ b/tools/urt/libs/generic/reference.cpp @@ -0,0 +1,2 @@ + +#include "reference.h" diff --git a/tools/urt/libs/generic/reference.h b/tools/urt/libs/generic/reference.h new file mode 100644 index 00000000..6a79c05f --- /dev/null +++ b/tools/urt/libs/generic/reference.h @@ -0,0 +1,111 @@ + +#if !defined(INCLUDED_GENERIC_REFERENCE_H) +#define INCLUDED_GENERIC_REFERENCE_H + +/// \file +/// \brief Wrappers to allow storing objects in templated containers using 'reference' semantics. + +/// \brief A reference to a mutable object. +/// Has 'reference' semantics, except for \c 'operator==' and \c 'operator.'. +/// \param Type The type of the referenced object. +template +class Reference +{ + Type* m_contained; +public: + explicit Reference(Type& contained) : m_contained(&contained) + { + } + Type& operator*() const + { + return *m_contained; + } + Type* operator->() const + { + return m_contained; + } + operator Type&() const + { + return *m_contained; + } + Type& get() const + { + return *m_contained; + } + Type* get_pointer() const + { + return m_contained; + } +}; + +template +bool operator<(const Reference& self, const Reference& other) +{ + return self.get() < other.get(); +} +template +bool operator==(const Reference& self, const Reference& other) +{ + return self.get() == other.get(); +} + +/// \brief construct a reference to a mutable object. +template +inline Reference makeReference(Type& value) +{ + return Reference(value); +} + +/// \brief A reference to a non-mutable object. +/// Has 'reference' semantics, except for \c 'operator==' and \c 'operator.'. +/// \param Type The type of the referenced object. +template +class ConstReference +{ + const Type* m_contained; +public: + explicit ConstReference(const Type& contained) : m_contained(&contained) + { + } + const Type& operator*() const + { + return *m_contained; + } + const Type* operator->() const + { + return m_contained; + } + operator const Type&() const + { + return *m_contained; + } + const Type& get() const + { + return *m_contained; + } + const Type* get_pointer() const + { + return m_contained; + } +}; + +template +bool operator<(const ConstReference& self, const ConstReference& other) +{ + return self.get() < other.get(); +} +template +bool operator==(const ConstReference& self, const ConstReference& other) +{ + return self.get() == other.get(); +} + +/// \brief construct a reference to a non-mutable object. +template +inline ConstReference makeReference(const Type& value) +{ + return ConstReference(value); +} + + +#endif diff --git a/tools/urt/libs/generic/referencecounted.cpp b/tools/urt/libs/generic/referencecounted.cpp new file mode 100644 index 00000000..7d8a3ef1 --- /dev/null +++ b/tools/urt/libs/generic/referencecounted.cpp @@ -0,0 +1,2 @@ + +#include "referencecounted.h" diff --git a/tools/urt/libs/generic/referencecounted.h b/tools/urt/libs/generic/referencecounted.h new file mode 100644 index 00000000..48ba446b --- /dev/null +++ b/tools/urt/libs/generic/referencecounted.h @@ -0,0 +1,187 @@ + +#if !defined(INCLUDED_GENERIC_REFERENCECOUNTED_H) +#define INCLUDED_GENERIC_REFERENCECOUNTED_H + +/// \file +/// \brief 'smart' pointers and references. + +#include + +template +class IncRefDecRefCounter +{ +public: + void increment(Type& value) + { + value.IncRef(); + } + void decrement(Type& value) + { + value.DecRef(); + } +}; + +/// \brief A smart-pointer that uses a counter stored in the object pointed-to. +template > +class SmartPointer : public Counter +{ + Type* m_value; +public: + + SmartPointer(const SmartPointer& other) + : m_value(other.m_value) + { + Counter::increment(*m_value); + } + explicit SmartPointer(Type* value) + : m_value(value) + { + Counter::increment(*m_value); + } + ~SmartPointer() + { + Counter::decrement(*m_value); + } + SmartPointer& operator=(const SmartPointer& other) + { + SmartPointer temp(other); + temp.swap(*this); + return *this; + } + SmartPointer& operator=(Type* value) + { + SmartPointer temp(value); + temp.swap(*this); + return *this; + } + void swap(SmartPointer& other) + { + std::swap(m_value, other.m_value); + } + + operator Type*() const + { + return m_value; + } + Type& operator*() const + { + return *m_value; + } + Type* operator->() const + { + return m_value; + } + Type* get() const + { + return m_value; + } +}; + +template +inline bool operator<(const SmartPointer& self, const SmartPointer& other) +{ + return self.get() < other.get(); +} +template +inline bool operator==(const SmartPointer& self, const SmartPointer& other) +{ + return self.get() == other.get(); +} +template +inline bool operator!=(const SmartPointer& self, const SmartPointer& other) +{ + return !::operator==(self, other); +} + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap(). + template + inline void swap(SmartPointer& self, SmartPointer& other) + { + self.swap(other); + } +} + + +/// \brief A smart-reference that uses a counter stored in the object pointed-to. +template > +class SmartReference : public Counter +{ + Type* m_value; +public: + + SmartReference(const SmartReference& other) + : m_value(other.m_value) + { + Counter::increment(*m_value); + } + explicit SmartReference(Type& value) + : m_value(&value) + { + Counter::increment(*m_value); + } + ~SmartReference() + { + Counter::decrement(*m_value); + } + SmartReference& operator=(const SmartReference& other) + { + SmartReference temp(other); + temp.swap(*this); + return *this; + } + SmartReference& operator=(Type& value) + { + SmartReference temp(value); + temp.swap(*this); + return *this; + } + void swap(SmartReference& other) + { + std::swap(m_value, other.m_value); + } + + operator Type&() const + { + return *m_value; + } + Type& get() const + { + return *m_value; + } + Type* get_pointer() const + { + return m_value; + } +}; + +template +inline bool operator<(const SmartReference& self, const SmartReference& other) +{ + return self.get() < other.get(); +} +template +inline bool operator==(const SmartReference& self, const SmartReference& other) +{ + return self.get() == other.get(); +} +template +inline bool operator!=(const SmartReference& self, const SmartReference& other) +{ + return !::operator==(self, other); +} + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap(). + template + inline void swap(SmartReference& self, SmartReference& other) + { + self.swap(other); + } +} + +#endif diff --git a/tools/urt/libs/generic/static.cpp b/tools/urt/libs/generic/static.cpp new file mode 100644 index 00000000..2a6ff8d5 --- /dev/null +++ b/tools/urt/libs/generic/static.cpp @@ -0,0 +1,113 @@ + +#include "static.h" + +#if defined(_DEBUG) || defined(DOXYGEN) + +namespace ExampleStatic +{ + // Static example + // ---- myclass.h + class MyClass + { + public: + int value; + MyClass() : value(3) + { + } + }; + + typedef Static StaticMyClass; + + // ---- main.cpp + class DynamicInitialisation + { + public: + DynamicInitialisation() + { + // StaticMyClass::instance() may be invalid here because construction order is undefined + } + }; + + DynamicInitialisation g_dynamicInitialisation; + + void duringMain() + { + int bar = StaticMyClass::instance().value; + } + // end example +} + +namespace ExampleLazyStatic +{ + // LazyStatic example + // ---- myclass.h + class MyClass + { + public: + int value; + MyClass() : value(3) + { + } + // destructor will never be called + }; + + typedef LazyStatic StaticMyClass; + + // ---- main.cpp + class DynamicInitialisation + { + public: + DynamicInitialisation() + { + int bar = StaticMyClass::instance().value; + } + }; + + DynamicInitialisation g_dynamicInitialisation; + + void duringMain() + { + int bar = StaticMyClass::instance().value; + } + // end example +} + +namespace ExampleSmartStatic +{ + // SmartStatic example + // ---- myclass.h + class MyClass + { + public: + int value; + MyClass() : value(3) + { + } + }; + + typedef CountedStatic StaticMyClass; + + // ---- main.cpp + class DynamicInitialisation + { + public: + DynamicInitialisation() + { + // StaticMyClass::instance() is invalid before the ref is constructed + SmartStatic ref; + int bar = ref.instance().value; + + SmartStatic ref2; // any number of instances are allowed. + } + }; + + DynamicInitialisation g_dynamicInitialisation; + + void duringMain() + { + int bar = SmartStatic().instance().value; // an instance can be a temporary + } + // end example +} + +#endif diff --git a/tools/urt/libs/generic/static.h b/tools/urt/libs/generic/static.h new file mode 100644 index 00000000..d51fbb45 --- /dev/null +++ b/tools/urt/libs/generic/static.h @@ -0,0 +1,123 @@ + +#if !defined(INCLUDED_GENERIC_STATIC_H) +#define INCLUDED_GENERIC_STATIC_H + +/// \file +/// \brief Template techniques for instantiating singletons. + +#include + +/// \brief A singleton which is statically initialised. +/// +/// \param Type The singleton object type. +/// +/// \dontinclude generic/static.cpp +/// \skipline Static example +/// \until end example +template +class Static +{ + static Type m_instance; +public: + static Type& instance() + { + return m_instance; + } +}; + +template +Type Static::m_instance; + + +/// \brief A singleton which is lazily initialised. +/// The instance is constructed the first time it is referenced, and is never destroyed. +/// +/// \param Type The singleton object type. +/// +/// \dontinclude generic/static.cpp +/// \skipline LazyStatic example +/// \until end example +template +class LazyStatic +{ + static Type* m_instance; // this will be initialised to 0 by the CRT, according to the c++ standard +public: + static Type& instance() + { + if(m_instance == 0) + { + m_instance = new Type; // allocate using 'new' to get the correct alignment + } + return *m_instance; + } +}; + +template +Type* LazyStatic::m_instance; + + +/// \brief A singleton which keeps a count of the number of times it is referenced. +/// +/// The instance is constructed when its reference count changes from 0 to 1 and destroyed when its reference count changes from 1 to 0. +/// Use with SmartStatic. +/// +/// \param Type The singleton object type. +template +class CountedStatic +{ + static std::size_t m_refcount; // this will be initialised to 0 by the CRT, according to the c++ standard + static Type* m_instance; +public: + static Type& instance() + { + return *m_instance; + } + static void capture() + { + if(++m_refcount == 1) + { + m_instance = new Type; // allocate using 'new' to get the correct alignment + } + } + static void release() + { + if(--m_refcount == 0) + { + delete m_instance; + } + } +}; + +template +std::size_t CountedStatic::m_refcount; // this will be initialised to 0 by the CRT, according to the c++ standard +template +Type* CountedStatic::m_instance; + +/// \brief A reference to a CountedStatic. +/// Guarantees that CountedStatic will be constructed for the lifetime of this object. +/// +/// \param Type The type parameter of the CountedStatic to reference. +/// +/// \dontinclude generic/static.cpp +/// \skipline SmartStatic example +/// \until end example +template +class SmartStatic +{ +public: + SmartStatic() + { + CountedStatic::capture(); + } + ~SmartStatic() + { + CountedStatic::release(); + } + Type& instance() + { + return CountedStatic::instance(); + } +}; + + +#endif diff --git a/tools/urt/libs/gtkutil/accelerator.cpp b/tools/urt/libs/gtkutil/accelerator.cpp new file mode 100644 index 00000000..539dee2a --- /dev/null +++ b/tools/urt/libs/gtkutil/accelerator.cpp @@ -0,0 +1,527 @@ + +#include "accelerator.h" + +#include "debugging/debugging.h" + +#include +#include +#include +#include + +#include "generic/callback.h" +#include "generic/bitfield.h" + +#include "pointer.h" +#include "closure.h" + + + +typedef std::map AcceleratorMap; + +void accelerator_map_insert(AcceleratorMap& acceleratorMap, Accelerator accelerator, const Callback& callback) +{ + if(accelerator.key != 0) + { + ASSERT_MESSAGE(acceleratorMap.find(accelerator) == acceleratorMap.end(), "failed to add accelerator"); + acceleratorMap.insert(AcceleratorMap::value_type(accelerator, callback)); + } +} + +void accelerator_map_erase(AcceleratorMap& acceleratorMap, Accelerator accelerator) +{ + if(accelerator.key != 0) + { + ASSERT_MESSAGE(acceleratorMap.find(accelerator) != acceleratorMap.end(), "failed to remove accelerator"); + acceleratorMap.erase(accelerator); + } +} + +Accelerator accelerator_for_event_key(guint keyval, guint state) +{ + keyval = gdk_keyval_to_upper(keyval); + if(keyval == GDK_ISO_Left_Tab) + keyval = GDK_Tab; + return Accelerator(keyval, (GdkModifierType)(state & gtk_accelerator_get_default_mod_mask())); +} + +bool AcceleratorMap_activate(const AcceleratorMap& acceleratorMap, const Accelerator& accelerator) +{ + AcceleratorMap::const_iterator i = acceleratorMap.find(accelerator); + if(i != acceleratorMap.end()) + { + (*i).second(); + return true; + } + + return false; +} + +static gboolean accelerator_key_event(GtkWindow* window, GdkEventKey* event, AcceleratorMap* acceleratorMap) +{ + return AcceleratorMap_activate(*acceleratorMap, accelerator_for_event_key(event->keyval, event->state)); +} + + +AcceleratorMap g_special_accelerators; + + +namespace MouseButton +{ + enum + { + Left = 1 << 0, + Right = 1 << 1, + Middle = 1 << 2, + }; +} + +typedef unsigned int ButtonMask; + +void print_buttons(ButtonMask mask) +{ + globalOutputStream() << "button state: "; + if((mask & MouseButton::Left) != 0) + { + globalOutputStream() << "Left "; + } + if((mask & MouseButton::Right) != 0) + { + globalOutputStream() << "Right "; + } + if((mask & MouseButton::Middle) != 0) + { + globalOutputStream() << "Middle "; + } + globalOutputStream() << "\n"; +} + +ButtonMask ButtonMask_for_event_button(guint button) +{ + switch(button) + { + case 1: + return MouseButton::Left; + case 2: + return MouseButton::Middle; + case 3: + return MouseButton::Right; + } + return 0; +} + +bool window_has_accel(GtkWindow* toplevel) +{ + return g_slist_length(gtk_accel_groups_from_object(G_OBJECT(toplevel))) != 0; +} + +namespace +{ + bool g_accel_enabled = true; +} + +bool global_accel_enabled() +{ + return g_accel_enabled; +} + + +AcceleratorMap g_queuedAccelerators; + +GClosure* accel_group_add_accelerator(GtkAccelGroup* group, Accelerator accelerator, const Callback& callback); + +void GlobalQueuedAccelerators_commit() +{ + for(AcceleratorMap::const_iterator i = g_queuedAccelerators.begin(); i != g_queuedAccelerators.end(); ++i) + { + accel_group_add_accelerator(global_accel, (*i).first, (*i).second); + } + g_queuedAccelerators.clear(); +} + +void GlobalQueuedAccelerators_add(Accelerator accelerator, const Callback& callback) +{ + g_queuedAccelerators.insert(AcceleratorMap::value_type(accelerator, callback)); +} + +void accel_group_test(GtkWindow* toplevel, GtkAccelGroup* accel) +{ + guint n_entries; + gtk_accel_group_query(accel, '4', (GdkModifierType)0, &n_entries); + globalOutputStream() << "grid4: " << n_entries << "\n"; + globalOutputStream() << "toplevel accelgroups: " << g_slist_length(gtk_accel_groups_from_object(G_OBJECT(toplevel))) << "\n"; +} + +typedef std::set WindowSet; +WindowSet g_accel_windows; + +bool Buttons_press(ButtonMask& buttons, guint button, guint state) +{ + if(buttons == 0 && bitfield_enable(buttons, ButtonMask_for_event_button(button)) != 0) + { + ASSERT_MESSAGE(g_accel_enabled, "Buttons_press: accelerators not enabled"); + g_accel_enabled = false; + for(WindowSet::iterator i = g_accel_windows.begin(); i != g_accel_windows.end(); ++i) + { + GtkWindow* toplevel = *i; + ASSERT_MESSAGE(window_has_accel(toplevel), "ERROR"); + ASSERT_MESSAGE(GTK_WIDGET_TOPLEVEL(toplevel), "disabling accel for non-toplevel window"); + gtk_window_remove_accel_group(toplevel, global_accel); +#if 0 + globalOutputStream() << reinterpret_cast(toplevel) << ": disabled global accelerators\n"; +#endif +#if 0 + accel_group_test(toplevel, global_accel); +#endif + } + } + buttons = bitfield_enable(buttons, ButtonMask_for_event_button(button)); +#if 0 + globalOutputStream() << "Buttons_press: "; + print_buttons(buttons); +#endif + return false; +} + +bool Buttons_release(ButtonMask& buttons, guint button, guint state) +{ + if(buttons != 0 && bitfield_disable(buttons, ButtonMask_for_event_button(button)) == 0) + { + ASSERT_MESSAGE(!g_accel_enabled, "Buttons_release: accelerators are enabled"); + g_accel_enabled = true; + for(WindowSet::iterator i = g_accel_windows.begin(); i != g_accel_windows.end(); ++i) + { + GtkWindow* toplevel = *i; + ASSERT_MESSAGE(!window_has_accel(toplevel), "ERROR"); + ASSERT_MESSAGE(GTK_WIDGET_TOPLEVEL(toplevel), "enabling accel for non-toplevel window"); + gtk_window_add_accel_group(toplevel, global_accel); +#if 0 + globalOutputStream() << reinterpret_cast(toplevel) << ": enabled global accelerators\n"; +#endif +#if 0 + accel_group_test(toplevel, global_accel); +#endif + } + GlobalQueuedAccelerators_commit(); + } + buttons = bitfield_disable(buttons, ButtonMask_for_event_button(button)); +#if 0 + globalOutputStream() << "Buttons_release: "; + print_buttons(buttons); +#endif + return false; +} + +bool Buttons_releaseAll(ButtonMask& buttons) +{ + Buttons_release(buttons, MouseButton::Left | MouseButton::Middle | MouseButton::Right, 0); + return false; +} + +struct PressedButtons +{ + ButtonMask buttons; + + PressedButtons() : buttons(0) + { + } +}; + +gboolean PressedButtons_button_press(GtkWidget* widget, GdkEventButton* event, PressedButtons* pressed) +{ + if(event->type == GDK_BUTTON_PRESS) + { + return Buttons_press(pressed->buttons, event->button, event->state); + } + return FALSE; +} + +gboolean PressedButtons_button_release(GtkWidget* widget, GdkEventButton* event, PressedButtons* pressed) +{ + if(event->type == GDK_BUTTON_RELEASE) + { + return Buttons_release(pressed->buttons, event->button, event->state); + } + return FALSE; +} + +gboolean PressedButtons_focus_out(GtkWidget* widget, GdkEventFocus* event, PressedButtons* pressed) +{ + Buttons_releaseAll(pressed->buttons); + return FALSE; +} + +void PressedButtons_connect(PressedButtons& pressedButtons, GtkWidget* widget) +{ + g_signal_connect(G_OBJECT(widget), "button_press_event", G_CALLBACK(PressedButtons_button_press), &pressedButtons); + g_signal_connect(G_OBJECT(widget), "button_release_event", G_CALLBACK(PressedButtons_button_release), &pressedButtons); + g_signal_connect(G_OBJECT(widget), "focus_out_event", G_CALLBACK(PressedButtons_focus_out), &pressedButtons); +} + +PressedButtons g_pressedButtons; + + +#include + +struct PressedKeys +{ + typedef std::set Keys; + Keys keys; + std::size_t refcount; + + PressedKeys() : refcount(0) + { + } +}; + +AcceleratorMap g_keydown_accelerators; +AcceleratorMap g_keyup_accelerators; + +bool Keys_press(PressedKeys::Keys& keys, guint keyval) +{ + if(keys.insert(keyval).second) + { + return AcceleratorMap_activate(g_keydown_accelerators, accelerator_for_event_key(keyval, 0)); + } + return g_keydown_accelerators.find(accelerator_for_event_key(keyval, 0)) != g_keydown_accelerators.end(); +} + +bool Keys_release(PressedKeys::Keys& keys, guint keyval) +{ + if(keys.erase(keyval) != 0) + { + return AcceleratorMap_activate(g_keyup_accelerators, accelerator_for_event_key(keyval, 0)); + } + return g_keyup_accelerators.find(accelerator_for_event_key(keyval, 0)) != g_keyup_accelerators.end(); +} + +void Keys_releaseAll(PressedKeys::Keys& keys, guint state) +{ + for(PressedKeys::Keys::iterator i = keys.begin(); i != keys.end(); ++i) + { + AcceleratorMap_activate(g_keyup_accelerators, accelerator_for_event_key(*i, state)); + } + keys.clear(); +} + +gboolean PressedKeys_key_press(GtkWidget* widget, GdkEventKey* event, PressedKeys* pressedKeys) +{ + //globalOutputStream() << "pressed: " << event->keyval << "\n"; + return event->state == 0 && Keys_press(pressedKeys->keys, event->keyval); +} + +gboolean PressedKeys_key_release(GtkWidget* widget, GdkEventKey* event, PressedKeys* pressedKeys) +{ + //globalOutputStream() << "released: " << event->keyval << "\n"; + return Keys_release(pressedKeys->keys, event->keyval); +} + +gboolean PressedKeys_focus_in(GtkWidget* widget, GdkEventFocus* event, PressedKeys* pressedKeys) +{ + ++pressedKeys->refcount; + return FALSE; +} + +gboolean PressedKeys_focus_out(GtkWidget* widget, GdkEventFocus* event, PressedKeys* pressedKeys) +{ + if(--pressedKeys->refcount == 0) + { + Keys_releaseAll(pressedKeys->keys, 0); + } + return FALSE; +} + +PressedKeys g_pressedKeys; + +void GlobalPressedKeys_releaseAll() +{ + Keys_releaseAll(g_pressedKeys.keys, 0); +} + +void GlobalPressedKeys_connect(GtkWindow* window) +{ + unsigned int key_press_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(PressedKeys_key_press), &g_pressedKeys); + unsigned int key_release_handler = g_signal_connect(G_OBJECT(window), "key_release_event", G_CALLBACK(PressedKeys_key_release), &g_pressedKeys); + g_object_set_data(G_OBJECT(window), "key_press_handler", gint_to_pointer(key_press_handler)); + g_object_set_data(G_OBJECT(window), "key_release_handler", gint_to_pointer(key_release_handler)); + unsigned int focus_in_handler = g_signal_connect(G_OBJECT(window), "focus_in_event", G_CALLBACK(PressedKeys_focus_in), &g_pressedKeys); + unsigned int focus_out_handler = g_signal_connect(G_OBJECT(window), "focus_out_event", G_CALLBACK(PressedKeys_focus_out), &g_pressedKeys); + g_object_set_data(G_OBJECT(window), "focus_in_handler", gint_to_pointer(focus_in_handler)); + g_object_set_data(G_OBJECT(window), "focus_out_handler", gint_to_pointer(focus_out_handler)); +} + +void GlobalPressedKeys_disconnect(GtkWindow* window) +{ + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_press_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_release_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "focus_in_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "focus_out_handler"))); +} + + + +void special_accelerators_add(Accelerator accelerator, const Callback& callback) +{ + accelerator_map_insert(g_special_accelerators, accelerator, callback); +} +void special_accelerators_remove(Accelerator accelerator) +{ + accelerator_map_erase(g_special_accelerators, accelerator); +} + +void keydown_accelerators_add(Accelerator accelerator, const Callback& callback) +{ + accelerator_map_insert(g_keydown_accelerators, accelerator, callback); +} +void keydown_accelerators_remove(Accelerator accelerator) +{ + accelerator_map_erase(g_keydown_accelerators, accelerator); +} + +void keyup_accelerators_add(Accelerator accelerator, const Callback& callback) +{ + accelerator_map_insert(g_keyup_accelerators, accelerator, callback); +} +void keyup_accelerators_remove(Accelerator accelerator) +{ + accelerator_map_erase(g_keyup_accelerators, accelerator); +} + + +gboolean accel_closure_callback(GtkAccelGroup* group, GtkWidget* widget, guint key, GdkModifierType modifiers, gpointer data) +{ + (*reinterpret_cast(data))(); + return TRUE; +} + +GClosure* accel_group_add_accelerator(GtkAccelGroup* group, Accelerator accelerator, const Callback& callback) +{ + if(accelerator.key != 0 && gtk_accelerator_valid(accelerator.key, accelerator.modifiers)) + { + //globalOutputStream() << "adding accelerator: " << accelerator.key << " " << accelerator.modifiers << "\n"; + GClosure* closure = create_cclosure(G_CALLBACK(accel_closure_callback), callback); + gtk_accel_group_connect(group, accelerator.key, accelerator.modifiers, GTK_ACCEL_VISIBLE, closure); + return closure; + } + else + { + special_accelerators_add(accelerator, callback); + return 0; + } +} + +void accel_group_remove_accelerator(GtkAccelGroup* group, Accelerator accelerator) +{ + if(accelerator.key != 0 && gtk_accelerator_valid(accelerator.key, accelerator.modifiers)) + { + gtk_accel_group_disconnect_key(group, accelerator.key, accelerator.modifiers); + } + else + { + special_accelerators_remove(accelerator); + } +} + +GtkAccelGroup* global_accel = 0; + +void global_accel_init() +{ + global_accel = gtk_accel_group_new(); +} + +void global_accel_destroy() +{ + g_object_unref(global_accel); +} + +GClosure* global_accel_group_add_accelerator(Accelerator accelerator, const Callback& callback) +{ + if(!global_accel_enabled()) + { + // workaround: cannot add to GtkAccelGroup while it is disabled + GlobalQueuedAccelerators_add(accelerator, callback); + return 0; + } + return accel_group_add_accelerator(global_accel, accelerator, callback); +} +void global_accel_group_remove_accelerator(Accelerator accelerator) +{ + //ASSERT_MESSAGE(global_accel_enabled(), "removing accelerator while global accel is disabled"); + accel_group_remove_accelerator(global_accel, accelerator); +} + +/// \brief Propagates key events to the focus-widget, overriding global accelerators. +static gboolean override_global_accelerators(GtkWindow* window, GdkEventKey* event, gpointer data) +{ + return gtk_window_propagate_key_event(window, event); +} + +void global_accel_connect_window(GtkWindow* window) +{ +#if 1 + unsigned int override_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(override_global_accelerators), 0); + g_object_set_data(G_OBJECT(window), "override_handler", gint_to_pointer(override_handler)); + + unsigned int special_key_press_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(accelerator_key_event), &g_special_accelerators); + g_object_set_data(G_OBJECT(window), "special_key_press_handler", gint_to_pointer(special_key_press_handler)); + + GlobalPressedKeys_connect(window); +#else + unsigned int key_press_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(accelerator_key_event), &g_keydown_accelerators); + unsigned int key_release_handler = g_signal_connect(G_OBJECT(window), "key_release_event", G_CALLBACK(accelerator_key_event), &g_keyup_accelerators); + g_object_set_data(G_OBJECT(window), "key_press_handler", gint_to_pointer(key_press_handler)); + g_object_set_data(G_OBJECT(window), "key_release_handler", gint_to_pointer(key_release_handler)); +#endif + g_accel_windows.insert(window); + gtk_window_add_accel_group(window, global_accel); +} +void global_accel_disconnect_window(GtkWindow* window) +{ +#if 1 + GlobalPressedKeys_disconnect(window); + + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "override_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "special_key_press_handler"))); +#else + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_press_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_release_handler"))); +#endif + gtk_window_remove_accel_group(window, global_accel); + std::size_t count = g_accel_windows.erase(window); + ASSERT_MESSAGE(count == 1, "failed to remove accel group\n"); +} + + +GClosure* global_accel_group_find(Accelerator accelerator) +{ + guint numEntries = 0; + GtkAccelGroupEntry* entry = gtk_accel_group_query(global_accel, accelerator.key, accelerator.modifiers, &numEntries); + if(numEntries != 0) + { + if(numEntries != 1) + { + char* name = gtk_accelerator_name(accelerator.key, accelerator.modifiers); + globalErrorStream() << "accelerator already in-use: " << name << "\n"; + g_free(name); + } + return entry->closure; + } + return 0; +} + +void command_connect_accelerator(const Accelerator& accelerator, const Callback& callback) +{ + if(accelerator.key != 0) + { + global_accel_group_add_accelerator(accelerator, callback); + } +} + +void command_disconnect_accelerator(const Accelerator& accelerator, const Callback& callback) +{ + if(accelerator.key != 0) + { + global_accel_group_remove_accelerator(accelerator); + } +} + + diff --git a/tools/urt/libs/gtkutil/accelerator.h b/tools/urt/libs/gtkutil/accelerator.h new file mode 100644 index 00000000..345904f0 --- /dev/null +++ b/tools/urt/libs/gtkutil/accelerator.h @@ -0,0 +1,97 @@ + +#if !defined(INCLUDED_GTKUTIL_ACCELERATOR_H) +#define INCLUDED_GTKUTIL_ACCELERATOR_H + +#include +#include + +#include "generic/callback.h" + + +struct Accelerator +{ + Accelerator(guint _key) + : key(_key), modifiers((GdkModifierType)0) + { + } + Accelerator(guint _key, GdkModifierType _modifiers) + : key(_key), modifiers(_modifiers) + { + } + bool operator<(const Accelerator& other) const + { + return key < other.key || (!(other.key < key) && modifiers < other.modifiers); + } + guint key; + GdkModifierType modifiers; +}; + +inline Accelerator accelerator_null() +{ + return Accelerator(0, (GdkModifierType)0); +} + + +void keydown_accelerators_add(Accelerator accelerator, const Callback& callback); +void keydown_accelerators_remove(Accelerator accelerator); +void keyup_accelerators_add(Accelerator accelerator, const Callback& callback); +void keyup_accelerators_remove(Accelerator accelerator); + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; +void global_accel_connect_window(GtkWindow* window); +void global_accel_disconnect_window(GtkWindow* window); + +void GlobalPressedKeys_releaseAll(); + +typedef struct _GtkAccelGroup GtkAccelGroup; +extern GtkAccelGroup* global_accel; +void global_accel_init(); +void global_accel_destroy(); + +GClosure* global_accel_group_find(Accelerator accelerator); + +void command_connect_accelerator(const Accelerator& accelerator, const Callback& callback); +void command_disconnect_accelerator(const Accelerator& accelerator, const Callback& callback); + + +class Command +{ +public: + Callback m_callback; + const Accelerator& m_accelerator; + Command(const Callback& callback, const Accelerator& accelerator) : m_callback(callback), m_accelerator(accelerator) + { + } +}; + +class Toggle +{ +public: + Command m_command; + BoolExportCallback m_exportCallback; + Toggle(const Callback& callback, const Accelerator& accelerator, const BoolExportCallback& exportCallback) : m_command(callback, accelerator), m_exportCallback(exportCallback) + { + } +}; + +class KeyEvent +{ +public: + const Accelerator& m_accelerator; + Callback m_keyDown; + Callback m_keyUp; + KeyEvent(const Accelerator& accelerator, const Callback& keyDown, const Callback& keyUp) : m_accelerator(accelerator), m_keyDown(keyDown), m_keyUp(keyUp) + { + } +}; + + + +struct PressedButtons; +typedef struct _GtkWidget GtkWidget; +void PressedButtons_connect(PressedButtons& pressedButtons, GtkWidget* widget); + +extern PressedButtons g_pressedButtons; + +#endif diff --git a/tools/urt/libs/gtkutil/button.cpp b/tools/urt/libs/gtkutil/button.cpp new file mode 100644 index 00000000..ba813701 --- /dev/null +++ b/tools/urt/libs/gtkutil/button.cpp @@ -0,0 +1,117 @@ + +#include "button.h" + +#include + +#include "stream/textstream.h" +#include "stream/stringstream.h" +#include "generic/callback.h" + +#include "image.h" +#include "pointer.h" + +void clicked_closure_callback(GtkWidget* widget, gpointer data) +{ + (*reinterpret_cast(data))(); +} + +void button_connect_callback(GtkButton* button, const Callback& callback) +{ +#if 1 + g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(callback.getThunk()), callback.getEnvironment()); +#else + g_signal_connect_closure(G_OBJECT(button), "clicked", create_cclosure(G_CALLBACK(clicked_closure_callback), callback), FALSE); +#endif +} + +guint toggle_button_connect_callback(GtkToggleButton* button, const Callback& callback) +{ +#if 1 + guint handler = g_signal_connect_swapped(G_OBJECT(button), "toggled", G_CALLBACK(callback.getThunk()), callback.getEnvironment()); +#else + guint handler = g_signal_connect_closure(G_OBJECT(button), "toggled", create_cclosure(G_CALLBACK(clicked_closure_callback), callback), TRUE); +#endif + g_object_set_data(G_OBJECT(button), "handler", gint_to_pointer(handler)); + return handler; +} + +void button_set_icon(GtkButton* button, const char* icon) +{ + GtkImage* image = new_local_image(icon); + gtk_widget_show(GTK_WIDGET(image)); + gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image)); +} + +void toggle_button_set_active_no_signal(GtkToggleButton* button, gboolean active) +{ + //globalOutputStream() << "set active: " << active << "\n"; + guint handler_id = gpointer_to_int(g_object_get_data(G_OBJECT(button), "handler")); + //guint signal_id = g_signal_lookup("toggled", G_OBJECT_TYPE (button)); + //globalOutputStream() << "signal_id: " << signal_id << "\n"; + //guint found = g_signal_handler_find(G_OBJECT(button), G_SIGNAL_MATCH_ID, signal_id, 0, 0, 0, 0); + //globalOutputStream() << " handler found: " << found << "\n"; + g_signal_handler_block(G_OBJECT(button), handler_id); + gtk_toggle_button_set_active(button, active); + g_signal_handler_unblock(G_OBJECT(button), handler_id); +} + + +void radio_button_print_state(GtkRadioButton* button) +{ + globalOutputStream() << "toggle button: "; + for(GSList* radio = gtk_radio_button_group(button); radio != 0; radio = g_slist_next(radio)) + { + globalOutputStream() << gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio->data)); + } + globalOutputStream() << "\n"; +} + +GtkToggleButton* radio_button_get_nth(GtkRadioButton* radio, int index) +{ + GSList *group = gtk_radio_button_group(radio); + return GTK_TOGGLE_BUTTON(g_slist_nth_data(group, g_slist_length(group) - index - 1)); +} + +void radio_button_set_active(GtkRadioButton* radio, int index) +{ + //radio_button_print_state(radio); + gtk_toggle_button_set_active(radio_button_get_nth(radio, index), TRUE); + //radio_button_print_state(radio); +} + +void radio_button_set_active_no_signal(GtkRadioButton* radio, int index) +{ + { + for(GSList* l = gtk_radio_button_get_group(radio); l != 0; l = g_slist_next(l)) + { + g_signal_handler_block(G_OBJECT(l->data), gpointer_to_int(g_object_get_data(G_OBJECT(l->data), "handler"))); + } + } + radio_button_set_active(radio, index); + { + for(GSList* l = gtk_radio_button_get_group(radio); l != 0; l = g_slist_next(l)) + { + g_signal_handler_unblock(G_OBJECT(l->data), gpointer_to_int(g_object_get_data(G_OBJECT(l->data), "handler"))); + } + } +} + +int radio_button_get_active(GtkRadioButton* radio) +{ + //radio_button_print_state(radio); + GSList *group = gtk_radio_button_group(radio); + int index = g_slist_length(group) - 1; + for (; group != 0; group = g_slist_next(group)) + { + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(group->data))) + { + break; + } + else + { + index--; + } + } + return index; +} + diff --git a/tools/urt/libs/gtkutil/button.h b/tools/urt/libs/gtkutil/button.h new file mode 100644 index 00000000..5916de6f --- /dev/null +++ b/tools/urt/libs/gtkutil/button.h @@ -0,0 +1,23 @@ + +#if !defined(INCLUDED_GTKUTIL_BUTTON_H) +#define INCLUDED_GTKUTIL_BUTTON_H + +typedef struct _GtkButton GtkButton; +typedef struct _GtkToggleButton GtkToggleButton; +typedef struct _GtkRadioButton GtkRadioButton; +typedef int gint; +typedef gint gboolean; +typedef unsigned int guint; +class Callback; + +void button_connect_callback(GtkButton* button, const Callback& callback); +guint toggle_button_connect_callback(GtkToggleButton* button, const Callback& callback); + +void button_set_icon(GtkButton* button, const char* icon); +void toggle_button_set_active_no_signal(GtkToggleButton* item, gboolean active); + +void radio_button_set_active(GtkRadioButton* radio, int index); +void radio_button_set_active_no_signal(GtkRadioButton* radio, int index); +int radio_button_get_active(GtkRadioButton* radio); + +#endif diff --git a/tools/urt/libs/gtkutil/clipboard.cpp b/tools/urt/libs/gtkutil/clipboard.cpp new file mode 100644 index 00000000..1f764b21 --- /dev/null +++ b/tools/urt/libs/gtkutil/clipboard.cpp @@ -0,0 +1,142 @@ + +#include "clipboard.h" + +#include "stream/memstream.h" +#include "stream/textstream.h" + + +/// \file +/// \brief Platform-independent GTK clipboard support. +/// \todo Using GDK_SELECTION_CLIPBOARD fails on win32, so we use the win32 API directly for now. +#if defined (__linux__) || defined (__APPLE__) + +#include + +enum +{ + RADIANT_CLIPPINGS = 23, +}; + +static const GtkTargetEntry clipboard_targets[] = { + { "RADIANT_CLIPPINGS", 0, RADIANT_CLIPPINGS, }, +}; + +static void clipboard_get (GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, gpointer data) +{ + std::size_t len = *reinterpret_cast(data); + const char* buffer = (len != 0) ? reinterpret_cast(data) + sizeof(std::size_t) : 0; + + GdkAtom type = GDK_NONE; + if(info == clipboard_targets[0].info) + { + type = gdk_atom_intern(clipboard_targets[0].target, FALSE); + } + + gtk_selection_data_set (selection_data, type, 8, reinterpret_cast(buffer), static_cast(len)); +} + +static void clipboard_clear (GtkClipboard *clipboard, gpointer data) +{ + delete [] reinterpret_cast(data); +} + +static void clipboard_received (GtkClipboard *clipboard, GtkSelectionData *data, gpointer user_data) +{ + if (data->length < 0) + { + globalErrorStream() << "Error retrieving selection\n"; + } + else if(strcmp(gdk_atom_name(data->type), clipboard_targets[0].target) == 0) + { + BufferInputStream istream(reinterpret_cast(data->data), data->length); + (*reinterpret_cast(user_data))(istream); + } +} + +void clipboard_copy(ClipboardCopyFunc copy) +{ + GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + + BufferOutputStream ostream; + copy(ostream); + std::size_t length = ostream.size(); + char* data = new char[length + sizeof(std::size_t)]; + *reinterpret_cast(data) = length; + memcpy(data + sizeof(std::size_t), ostream.data(), length); + + gtk_clipboard_set_with_data (clipboard, clipboard_targets, 1, clipboard_get, clipboard_clear, data); +} + +ClipboardPasteFunc g_clipboardPasteFunc = 0; +void clipboard_paste(ClipboardPasteFunc paste) +{ + GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + + g_clipboardPasteFunc = paste; + gtk_clipboard_request_contents (clipboard, gdk_atom_intern(clipboard_targets[0].target, FALSE), clipboard_received, &g_clipboardPasteFunc); +} + +#elif defined(WIN32) + +const char* c_clipboard_format = "RadiantClippings"; + +#include + +void clipboard_copy(ClipboardCopyFunc copy) +{ + BufferOutputStream ostream; + copy(ostream); + + bool bClipped = false; + UINT nClipboard = ::RegisterClipboardFormat(c_clipboard_format); + if (nClipboard > 0) + { + if (::OpenClipboard(0)) + { + EmptyClipboard(); + std::size_t length = ostream.size(); + HANDLE h = ::GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, length + sizeof(std::size_t)); + if (h != 0) + { + char *buffer = reinterpret_cast(::GlobalLock(h)); + *reinterpret_cast(buffer) = length; + buffer += sizeof(std::size_t); + memcpy(buffer, ostream.data(), length); + ::GlobalUnlock(h); + ::SetClipboardData(nClipboard, h); + ::CloseClipboard(); + bClipped = true; + } + } + } + + if (!bClipped) + { + globalOutputStream() << "Unable to register Windows clipboard formats, copy/paste between editors will not be possible\n"; + } +} + +void clipboard_paste(ClipboardPasteFunc paste) +{ + UINT nClipboard = ::RegisterClipboardFormat(c_clipboard_format); + if (nClipboard > 0 && ::OpenClipboard(0)) + { + if(IsClipboardFormatAvailable(nClipboard)) + { + HANDLE h = ::GetClipboardData(nClipboard); + if(h) + { + const char *buffer = reinterpret_cast(::GlobalLock(h)); + std::size_t length = *reinterpret_cast(buffer); + buffer += sizeof(std::size_t); + BufferInputStream istream(buffer, length); + paste(istream); + ::GlobalUnlock(h); + } + } + ::CloseClipboard(); + } +} + + +#endif diff --git a/tools/urt/libs/gtkutil/clipboard.h b/tools/urt/libs/gtkutil/clipboard.h new file mode 100644 index 00000000..1bdda04b --- /dev/null +++ b/tools/urt/libs/gtkutil/clipboard.h @@ -0,0 +1,13 @@ + +#if !defined(INCLUDED_GTKUTIL_CLIPBOARD_H) +#define INCLUDED_GTKUTIL_CLIPBOARD_H + +class TextOutputStream; +typedef void(*ClipboardCopyFunc)(TextOutputStream&); +void clipboard_copy(ClipboardCopyFunc copy); + +class TextInputStream; +typedef void(*ClipboardPasteFunc)(TextInputStream&); +void clipboard_paste(ClipboardPasteFunc paste); + +#endif diff --git a/tools/urt/libs/gtkutil/closure.cpp b/tools/urt/libs/gtkutil/closure.cpp new file mode 100644 index 00000000..cf960bb2 --- /dev/null +++ b/tools/urt/libs/gtkutil/closure.cpp @@ -0,0 +1,3 @@ + +#include "closure.h" + diff --git a/tools/urt/libs/gtkutil/closure.h b/tools/urt/libs/gtkutil/closure.h new file mode 100644 index 00000000..b1325e16 --- /dev/null +++ b/tools/urt/libs/gtkutil/closure.h @@ -0,0 +1,57 @@ + +#if !defined(INCLUDED_GTKUTIL_CLOSURE_H) +#define INCLUDED_GTKUTIL_CLOSURE_H + +#include +#include "generic/callback.h" + +inline void closure_destroy(gpointer data, GClosure* closure) +{ + delete reinterpret_cast(data); +} + +inline GClosure* create_cclosure(GCallback func, const Callback& callback) +{ + return g_cclosure_new(func, new Callback(callback), closure_destroy); +} + +inline GValue GValue_default() +{ + GValue value; + value.g_type = 0; + return value; +} + +inline gint object_get_int_property(GObject* object, const char* property) +{ + GValue gvalue = GValue_default(); + g_value_init(&gvalue, G_TYPE_INT); + g_object_get_property(object, property, &gvalue); + return g_value_get_int(&gvalue); +} + +inline void object_set_int_property(GObject* object, const char* property, gint value) +{ + GValue gvalue = GValue_default(); + g_value_init(&gvalue, G_TYPE_INT); + g_value_set_int(&gvalue, value); + g_object_set_property(object, property, &gvalue); +} + +inline gboolean object_get_boolean_property(GObject* object, const char* property) +{ + GValue gvalue = GValue_default(); + g_value_init(&gvalue, G_TYPE_BOOLEAN); + g_object_get_property(object, property, &gvalue); + return g_value_get_boolean(&gvalue); +} + +inline void object_set_boolean_property(GObject* object, const char* property, gboolean value) +{ + GValue gvalue = GValue_default(); + g_value_init(&gvalue, G_TYPE_BOOLEAN); + g_value_set_boolean(&gvalue, value); + g_object_set_property(object, property, &gvalue); +} + +#endif diff --git a/tools/urt/libs/gtkutil/container.cpp b/tools/urt/libs/gtkutil/container.cpp new file mode 100644 index 00000000..6cf60ed4 --- /dev/null +++ b/tools/urt/libs/gtkutil/container.cpp @@ -0,0 +1,3 @@ + +#include "container.h" + diff --git a/tools/urt/libs/gtkutil/container.h b/tools/urt/libs/gtkutil/container.h new file mode 100644 index 00000000..ddd5bfa4 --- /dev/null +++ b/tools/urt/libs/gtkutil/container.h @@ -0,0 +1,23 @@ + +#if !defined(INCLUDED_GTKUTIL_CONTAINER_H) +#define INCLUDED_GTKUTIL_CONTAINER_H + +#include + +inline GtkWidget* container_add_widget(GtkContainer* container, GtkWidget* widget) +{ + gtk_container_add(container, widget); + return widget; +} + +inline void container_remove(GtkWidget* item, gpointer data) +{ + gtk_container_remove(GTK_CONTAINER(data), item); +} + +inline void container_remove_all(GtkContainer* container) +{ + gtk_container_foreach(container, container_remove, container); +} + +#endif diff --git a/tools/urt/libs/gtkutil/cursor.cpp b/tools/urt/libs/gtkutil/cursor.cpp new file mode 100644 index 00000000..cff7c7e6 --- /dev/null +++ b/tools/urt/libs/gtkutil/cursor.cpp @@ -0,0 +1,71 @@ + +#include "cursor.h" + + +#include +#include +#include + + +GdkCursor* create_blank_cursor() +{ + GdkPixmap *pixmap; + GdkBitmap *mask; + char buffer [(32 * 32)/8]; + memset (buffer, 0, (32 * 32)/8); + GdkColor white = {0, 0xffff, 0xffff, 0xffff}; + GdkColor black = {0, 0x0000, 0x0000, 0x0000}; + pixmap = gdk_bitmap_create_from_data(0, buffer, 32, 32); + mask = gdk_bitmap_create_from_data(0, buffer, 32, 32); + GdkCursor *cursor = gdk_cursor_new_from_pixmap(pixmap, mask, &white, &black, 1, 1); + gdk_drawable_unref(pixmap); + gdk_drawable_unref(mask); + + return cursor; +} + +void blank_cursor(GtkWidget* widget) +{ + GdkCursor* cursor = create_blank_cursor(); + gdk_window_set_cursor (widget->window, cursor); + gdk_cursor_unref(cursor); +} + +void default_cursor(GtkWidget* widget) +{ + gdk_window_set_cursor(widget->window, 0); +} + + +#if defined(WIN32) + +#include + +void Sys_GetCursorPos (int *x, int *y) +{ + POINT pos; + GetCursorPos(&pos); + *x = pos.x; + *y = pos.y; +} + +void Sys_SetCursorPos (int x, int y) +{ + SetCursorPos (x, y); +} + +#else + +#include + +void Sys_GetCursorPos (int *x, int *y) +{ + gdk_display_get_pointer(gdk_display_get_default(), 0, x, y, 0); +} + +void Sys_SetCursorPos (int x, int y) +{ + XWarpPointer (GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y); +} + +#endif diff --git a/tools/urt/libs/gtkutil/cursor.h b/tools/urt/libs/gtkutil/cursor.h new file mode 100644 index 00000000..2782ef9c --- /dev/null +++ b/tools/urt/libs/gtkutil/cursor.h @@ -0,0 +1,173 @@ + +#if !defined(INCLUDED_GTKUTIL_CURSOR_H) +#define INCLUDED_GTKUTIL_CURSOR_H + +#include +#include +#include + +#include "debugging/debugging.h" + +typedef struct _GdkCursor GdkCursor; +typedef struct _GtkWidget GtkWidget; + +GdkCursor* create_blank_cursor(); +void blank_cursor(GtkWidget* widget); +void default_cursor(GtkWidget* widget); +void Sys_GetCursorPos (int *x, int *y); +void Sys_SetCursorPos (int x, int y); + + + +class DeferredMotion +{ + guint m_handler; + typedef void(*MotionFunction)(gdouble x, gdouble y, guint state, void* data); + MotionFunction m_function; + void* m_data; + gdouble m_x; + gdouble m_y; + guint m_state; + + static gboolean deferred(DeferredMotion* self) + { + self->m_handler = 0; + self->m_function(self->m_x, self->m_y, self->m_state, self->m_data); + return FALSE; + } +public: + DeferredMotion(MotionFunction function, void* data) : m_handler(0), m_function(function), m_data(data) + { + } + void motion(gdouble x, gdouble y, guint state) + { + m_x = x; + m_y = y; + m_state = state; + if(m_handler == 0) + { + m_handler = g_idle_add((GSourceFunc)deferred, this); + } + } + static gboolean gtk_motion(GtkWidget *widget, GdkEventMotion *event, DeferredMotion* self) + { + self->motion(event->x, event->y, event->state); + return FALSE; + } +}; + +class DeferredMotionDelta +{ + int m_delta_x; + int m_delta_y; + guint m_motion_handler; + typedef void (*MotionDeltaFunction)(int x, int y, void* data); + MotionDeltaFunction m_function; + void* m_data; + + static gboolean deferred_motion(gpointer data) + { + reinterpret_cast(data)->m_function( + reinterpret_cast(data)->m_delta_x, + reinterpret_cast(data)->m_delta_y, + reinterpret_cast(data)->m_data + ); + reinterpret_cast(data)->m_motion_handler = 0; + reinterpret_cast(data)->m_delta_x = 0; + reinterpret_cast(data)->m_delta_y = 0; + return FALSE; + } +public: + DeferredMotionDelta(MotionDeltaFunction function, void* data) : m_delta_x(0), m_delta_y(0), m_motion_handler(0), m_function(function), m_data(data) + { + } + void flush() + { + if(m_motion_handler != 0) + { + g_source_remove(m_motion_handler); + deferred_motion(this); + } + } + void motion_delta(int x, int y, unsigned int state) + { + m_delta_x += x; + m_delta_y += y; + if(m_motion_handler == 0) + { + m_motion_handler = g_idle_add(deferred_motion, this); + } + } +}; + +class FreezePointer +{ + unsigned int handle_motion; + int recorded_x, recorded_y; + typedef void (*MotionDeltaFunction)(int x, int y, unsigned int state, void* data); + MotionDeltaFunction m_function; + void* m_data; +public: + FreezePointer() : handle_motion(0), m_function(0), m_data(0) + { + } + static gboolean motion_delta(GtkWidget *widget, GdkEventMotion *event, FreezePointer* self) + { + int current_x, current_y; + Sys_GetCursorPos(¤t_x, ¤t_y); + int dx = current_x - self->recorded_x; + int dy = current_y - self->recorded_y; + if(dx != 0 || dy != 0) + { + //globalOutputStream() << "motion x: " << dx << ", y: " << dy << "\n"; + Sys_SetCursorPos(self->recorded_x, self->recorded_y); + self->m_function(dx, dy, event->state, self->m_data); + } + return FALSE; + } + + void freeze_pointer(GtkWidget* window, MotionDeltaFunction function, void* data) + { + ASSERT_MESSAGE(m_function == 0, "can't freeze pointer"); + + blank_cursor(window); + + const GdkEventMask mask = static_cast(GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK + | GDK_BUTTON_MOTION_MASK + | GDK_BUTTON1_MOTION_MASK + | GDK_BUTTON2_MOTION_MASK + | GDK_BUTTON3_MOTION_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_VISIBILITY_NOTIFY_MASK); + + //GdkGrabStatus status = + gdk_pointer_grab(window->window, TRUE, mask, window->window, 0, GDK_CURRENT_TIME); + + Sys_GetCursorPos(&recorded_x, &recorded_y); + + Sys_SetCursorPos(recorded_x, recorded_y); + + m_function = function; + m_data = data; + + handle_motion = g_signal_connect(G_OBJECT(window), "motion_notify_event", G_CALLBACK(motion_delta), this); + } + + void unfreeze_pointer(GtkWidget* window) + { + g_signal_handler_disconnect(G_OBJECT(window), handle_motion); + + m_function = 0; + m_data = 0; + + Sys_SetCursorPos(recorded_x, recorded_y); + + gdk_pointer_ungrab(GDK_CURRENT_TIME); + + default_cursor(window); + } +}; + +#endif diff --git a/tools/urt/libs/gtkutil/dialog.cpp b/tools/urt/libs/gtkutil/dialog.cpp new file mode 100644 index 00000000..5e5f8c88 --- /dev/null +++ b/tools/urt/libs/gtkutil/dialog.cpp @@ -0,0 +1,283 @@ + +#include "dialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "button.h" +#include "window.h" + +GtkVBox* create_dialog_vbox(int spacing, int border) +{ + GtkVBox* vbox = GTK_VBOX(gtk_vbox_new(FALSE, spacing)); + gtk_widget_show(GTK_WIDGET(vbox)); + gtk_container_set_border_width(GTK_CONTAINER(vbox), border); + return vbox; +} + +GtkHBox* create_dialog_hbox(int spacing, int border) +{ + GtkHBox* hbox = GTK_HBOX(gtk_hbox_new(FALSE, spacing)); + gtk_widget_show(GTK_WIDGET(hbox)); + gtk_container_set_border_width(GTK_CONTAINER(hbox), border); + return hbox; +} + +GtkFrame* create_dialog_frame(const char* label, GtkShadowType shadow) +{ + GtkFrame* frame = GTK_FRAME(gtk_frame_new(label)); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_frame_set_shadow_type(frame, shadow); + return frame; +} + +GtkTable* create_dialog_table(unsigned int rows, unsigned int columns, unsigned int row_spacing, unsigned int col_spacing, int border) +{ + GtkTable* table = GTK_TABLE(gtk_table_new(rows, columns, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_table_set_row_spacings(table, row_spacing); + gtk_table_set_col_spacings(table, col_spacing); + gtk_container_set_border_width(GTK_CONTAINER(table), border); + return table; +} + +GtkButton* create_dialog_button(const char* label, GCallback func, gpointer data) +{ + GtkButton* button = GTK_BUTTON(gtk_button_new_with_label(label)); + gtk_widget_set_size_request(GTK_WIDGET(button), 64, -1); + gtk_widget_show(GTK_WIDGET(button)); + g_signal_connect(G_OBJECT(button), "clicked", func, data); + return button; +} + +GtkWindow* create_dialog_window(GtkWindow* parent, const char* title, GCallback func, gpointer data, int default_w, int default_h) +{ + GtkWindow* window = create_floating_window(title, parent); + gtk_window_set_default_size(window, default_w, default_h); + gtk_window_set_position(window, GTK_WIN_POS_CENTER_ON_PARENT); + g_signal_connect(G_OBJECT(window), "delete_event", func, data); + + return window; +} + +gboolean modal_dialog_button_clicked(GtkWidget *widget, ModalDialogButton* button) +{ + button->m_dialog.loop = false; + button->m_dialog.ret = button->m_value; + return TRUE; +} + +gboolean modal_dialog_delete(GtkWidget *widget, GdkEvent* event, ModalDialog* dialog) +{ + dialog->loop = 0; + dialog->ret = eIDCANCEL; + return TRUE; +} + +EMessageBoxReturn modal_dialog_show(GtkWindow* window, ModalDialog& dialog) +{ + gtk_grab_add(GTK_WIDGET(window)); + gtk_widget_show(GTK_WIDGET(window)); + + dialog.loop = true; + while(dialog.loop) + { + gtk_main_iteration(); + } + + gtk_widget_hide(GTK_WIDGET(window)); + gtk_grab_remove(GTK_WIDGET(window)); + + return dialog.ret; +} + +GtkButton* create_modal_dialog_button(const char* label, ModalDialogButton& button) +{ + return create_dialog_button(label, G_CALLBACK(modal_dialog_button_clicked), &button); +} + +GtkWindow* create_modal_dialog_window(GtkWindow* parent, const char* title, ModalDialog& dialog, int default_w, int default_h) +{ + return create_dialog_window(parent, title, G_CALLBACK(modal_dialog_delete), &dialog, default_w, default_h); +} + +GtkWindow* create_fixedsize_modal_dialog_window(GtkWindow* parent, const char* title, ModalDialog& dialog, int width, int height) +{ + GtkWindow* window = create_modal_dialog_window(parent, title, dialog, width, height); + + gtk_window_set_resizable(window, FALSE); + gtk_window_set_modal(window, TRUE); + gtk_window_set_position(window, GTK_WIN_POS_CENTER); + + window_remove_minmax(window); + + //gtk_widget_set_size_request(GTK_WIDGET(window), width, height); + //gtk_window_set_default_size(window, width, height); + //gtk_window_resize(window, width, height); + //GdkGeometry geometry = { width, height, -1, -1, width, height, -1, -1, -1, -1, GDK_GRAVITY_STATIC, }; + //gtk_window_set_geometry_hints(window, GTK_WIDGET(window), &geometry, (GdkWindowHints)(GDK_HINT_POS|GDK_HINT_MIN_SIZE|GDK_HINT_BASE_SIZE)); + + return window; +} + +gboolean dialog_button_ok(GtkWidget *widget, ModalDialog* data) +{ + data->loop = false; + data->ret = eIDOK; + return TRUE; +} + +gboolean dialog_button_cancel(GtkWidget *widget, ModalDialog* data) +{ + data->loop = false; + data->ret = eIDCANCEL; + return TRUE; +} + +gboolean dialog_button_yes(GtkWidget *widget, ModalDialog* data) +{ + data->loop = false; + data->ret = eIDYES; + return TRUE; +} + +gboolean dialog_button_no(GtkWidget *widget, ModalDialog* data) +{ + data->loop = false; + data->ret = eIDNO; + return TRUE; +} + +gboolean dialog_delete_callback(GtkWidget *widget, GdkEventAny* event, ModalDialog* data) +{ + gtk_widget_hide(widget); + data->loop = false; + return TRUE; +} + +GtkWindow* create_simple_modal_dialog_window(const char* title, ModalDialog& dialog, GtkWidget* contents) +{ + GtkWindow* window = create_fixedsize_modal_dialog_window(0, title, dialog); + + GtkVBox* vbox1 = create_dialog_vbox(8, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox1)); + + gtk_container_add(GTK_CONTAINER(vbox1), contents); + + GtkAlignment* alignment = GTK_ALIGNMENT(gtk_alignment_new(0.5, 0.0, 0.0, 0.0)); + gtk_widget_show(GTK_WIDGET(alignment)); + gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(alignment), FALSE, FALSE, 0); + + GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog); + gtk_container_add(GTK_CONTAINER(alignment), GTK_WIDGET(button)); + + return window; +} + +RadioHBox RadioHBox_new(StringArrayRange names) +{ + GtkHBox* hbox = GTK_HBOX(gtk_hbox_new(TRUE, 4)); + gtk_widget_show(GTK_WIDGET(hbox)); + + GSList* group = 0; + GtkRadioButton* radio = 0; + for(StringArrayRange::Iterator i = names.begin; i != names.end; ++i) + { + radio = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label(group, *i)); + gtk_widget_show(GTK_WIDGET(radio)); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(radio), FALSE, FALSE, 0); + + group = gtk_radio_button_get_group(radio); + } + + return RadioHBox(hbox, radio); +} + + +PathEntry PathEntry_new() +{ + GtkFrame* frame = GTK_FRAME(gtk_frame_new(NULL)); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_frame_set_shadow_type(frame, GTK_SHADOW_IN); + + // path entry + GtkHBox* hbox = GTK_HBOX(gtk_hbox_new(FALSE, 0)); + gtk_widget_show(GTK_WIDGET(hbox)); + + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_entry_set_has_frame(entry, FALSE); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(entry), TRUE, TRUE, 0); + + // browse button + GtkButton* button = GTK_BUTTON(gtk_button_new()); + button_set_icon(button, "ellipsis.bmp"); + gtk_widget_show(GTK_WIDGET(button)); + gtk_box_pack_end(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0); + + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(hbox)); + + return PathEntry(frame, entry, button); +} + +void PathEntry_setPath(PathEntry& self, const char* path) +{ + gtk_entry_set_text(self.m_entry, path); +} +typedef ReferenceCaller1 PathEntrySetPathCaller; + +void BrowsedPathEntry_clicked(GtkWidget* widget, BrowsedPathEntry* self) +{ + self->m_browse(PathEntrySetPathCaller(self->m_entry)); +} + +BrowsedPathEntry::BrowsedPathEntry(const BrowseCallback& browse) : + m_entry(PathEntry_new()), + m_browse(browse) +{ + g_signal_connect(G_OBJECT(m_entry.m_button), "clicked", G_CALLBACK(BrowsedPathEntry_clicked), this); +} + + +GtkLabel* DialogLabel_new(const char* name) +{ + GtkLabel* label = GTK_LABEL(gtk_label_new(name)); + gtk_widget_show(GTK_WIDGET(label)); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + + return label; +} + +GtkTable* DialogRow_new(const char* name, GtkWidget* widget) +{ + GtkTable* table = GTK_TABLE(gtk_table_new(1, 3, TRUE)); + gtk_widget_show(GTK_WIDGET(table)); + + gtk_table_set_col_spacings(table, 4); + gtk_table_set_row_spacings(table, 0); + + gtk_table_attach(table, GTK_WIDGET(DialogLabel_new(name)), 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + gtk_table_attach(table, widget, 1, 3, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + return table; +} + +void DialogVBox_packRow(GtkVBox* vbox, GtkWidget* row) +{ + gtk_box_pack_start(GTK_BOX(vbox), row, FALSE, FALSE, 0); +} + diff --git a/tools/urt/libs/gtkutil/dialog.h b/tools/urt/libs/gtkutil/dialog.h new file mode 100644 index 00000000..d1117119 --- /dev/null +++ b/tools/urt/libs/gtkutil/dialog.h @@ -0,0 +1,125 @@ + +#if !defined(INCLUDED_GTKUTIL_DIALOG_H) +#define INCLUDED_GTKUTIL_DIALOG_H + +#include "generic/callback.h" +#include "generic/arrayrange.h" +#include "qerplugin.h" +#include + +typedef int gint; +typedef gint gboolean; +typedef struct _GdkEventAny GdkEventAny; +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkHBox GtkHBox; +typedef struct _GtkVBox GtkVBox; +typedef struct _GtkRadioButton GtkRadioButton; +typedef struct _GtkFrame GtkFrame; +typedef struct _GtkEntry GtkEntry; +typedef struct _GtkButton GtkButton; +typedef struct _GtkLabel GtkLabel; +typedef struct _GtkTable GtkTable; + + +struct ModalDialog +{ + ModalDialog() + : loop(true), ret(eIDCANCEL) + { + } + bool loop; + EMessageBoxReturn ret; +}; + +struct ModalDialogButton +{ + ModalDialogButton(ModalDialog& dialog, EMessageBoxReturn value) + : m_dialog(dialog), m_value(value) + { + } + ModalDialog& m_dialog; + EMessageBoxReturn m_value; +}; + +typedef void (*GCallback)(void); +typedef void* gpointer; +typedef struct _GtkWindow GtkWindow; +typedef struct _GtkTable GtkTable; +typedef struct _GtkButton GtkButton; +typedef struct _GtkVBox GtkVBox; +typedef struct _GtkHBox GtkHBox; +typedef struct _GtkFrame GtkFrame; + +GtkWindow* create_fixedsize_modal_window(GtkWindow* parent, const char* title, int width, int height); + +GtkWindow* create_dialog_window(GtkWindow* parent, const char* title, GCallback func, gpointer data, int default_w = -1, int default_h = -1); +GtkTable* create_dialog_table(unsigned int rows, unsigned int columns, unsigned int row_spacing, unsigned int col_spacing, int border = 0); +GtkButton* create_dialog_button(const char* label, GCallback func, gpointer data); +GtkVBox* create_dialog_vbox(int spacing, int border = 0); +GtkHBox* create_dialog_hbox(int spacing, int border = 0); +GtkFrame* create_dialog_frame(const char* label, GtkShadowType shadow = GTK_SHADOW_ETCHED_IN); + +GtkButton* create_modal_dialog_button(const char* label, ModalDialogButton& button); +GtkWindow* create_modal_dialog_window(GtkWindow* parent, const char* title, ModalDialog& dialog, int default_w = -1, int default_h = -1); +GtkWindow* create_fixedsize_modal_dialog_window(GtkWindow* parent, const char* title, ModalDialog& dialog, int width = -1, int height = -1); +EMessageBoxReturn modal_dialog_show(GtkWindow* window, ModalDialog& dialog); + + +gboolean dialog_button_ok(GtkWidget *widget, ModalDialog* data); +gboolean dialog_button_cancel(GtkWidget *widget, ModalDialog* data); +gboolean dialog_button_yes(GtkWidget *widget, ModalDialog* data); +gboolean dialog_button_no(GtkWidget *widget, ModalDialog* data); +gboolean dialog_delete_callback(GtkWidget *widget, GdkEventAny* event, ModalDialog* data); + +GtkWindow* create_simple_modal_dialog_window(const char* title, ModalDialog& dialog, GtkWidget* contents); + +class RadioHBox +{ +public: + GtkHBox* m_hbox; + GtkRadioButton* m_radio; + RadioHBox(GtkHBox* hbox, GtkRadioButton* radio) : + m_hbox(hbox), + m_radio(radio) + { + } +}; + +RadioHBox RadioHBox_new(StringArrayRange names); + + +class PathEntry +{ +public: + GtkFrame* m_frame; + GtkEntry* m_entry; + GtkButton* m_button; + PathEntry(GtkFrame* frame, GtkEntry* entry, GtkButton* button) : + m_frame(frame), + m_entry(entry), + m_button(button) + { + } +}; + +PathEntry PathEntry_new(); + +class BrowsedPathEntry +{ +public: + typedef Callback1 SetPathCallback; + typedef Callback1 BrowseCallback; + + PathEntry m_entry; + BrowseCallback m_browse; + + BrowsedPathEntry(const BrowseCallback& browse); +}; + +GtkLabel* DialogLabel_new(const char* name); +GtkTable* DialogRow_new(const char* name, GtkWidget* widget); +typedef struct _GtkVBox GtkVBox; +void DialogVBox_packRow(GtkVBox* vbox, GtkWidget* row); + + +#endif diff --git a/tools/urt/libs/gtkutil/entry.cpp b/tools/urt/libs/gtkutil/entry.cpp new file mode 100644 index 00000000..b8a33719 --- /dev/null +++ b/tools/urt/libs/gtkutil/entry.cpp @@ -0,0 +1,3 @@ + +#include "entry.h" + diff --git a/tools/urt/libs/gtkutil/entry.h b/tools/urt/libs/gtkutil/entry.h new file mode 100644 index 00000000..a07152f3 --- /dev/null +++ b/tools/urt/libs/gtkutil/entry.h @@ -0,0 +1,43 @@ + +#if !defined(INCLUDED_GTKUTIL_ENTRY_H) +#define INCLUDED_GTKUTIL_ENTRY_H + +#include +#include +#include + +inline void entry_set_string(GtkEntry* entry, const char* string) +{ + gtk_entry_set_text(entry, string); +} + +inline void entry_set_int(GtkEntry* entry, int i) +{ + char buf[32]; + sprintf(buf, "%d", i); + entry_set_string(entry, buf); +} + +inline void entry_set_float(GtkEntry* entry, float f) +{ + char buf[32]; + sprintf(buf, "%g", f); + entry_set_string(entry, buf); +} + +inline const char* entry_get_string(GtkEntry* entry) +{ + return gtk_entry_get_text(entry); +} + +inline int entry_get_int(GtkEntry* entry) +{ + return atoi(entry_get_string(entry)); +} + +inline double entry_get_float(GtkEntry* entry) +{ + return atof(entry_get_string(entry)); +} + +#endif diff --git a/tools/urt/libs/gtkutil/filechooser.cpp b/tools/urt/libs/gtkutil/filechooser.cpp new file mode 100644 index 00000000..676b907c --- /dev/null +++ b/tools/urt/libs/gtkutil/filechooser.cpp @@ -0,0 +1,463 @@ + +#include "filechooser.h" + +#include "ifiletypes.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "string/string.h" +#include "stream/stringstream.h" +#include "container/array.h" +#include "os/path.h" +#include "os/file.h" + +#include "messagebox.h" + + +struct filetype_pair_t +{ + filetype_pair_t() + : m_moduleName("") + { + } + filetype_pair_t(const char* moduleName, filetype_t type) + : m_moduleName(moduleName), m_type(type) + { + } + const char* m_moduleName; + filetype_t m_type; +}; + +class FileTypeList : public IFileTypeList +{ + struct filetype_copy_t + { + filetype_copy_t(const filetype_pair_t& other) + : m_moduleName(other.m_moduleName), m_name(other.m_type.name), m_pattern(other.m_type.pattern) + { + } + CopiedString m_moduleName; + CopiedString m_name; + CopiedString m_pattern; + }; + + typedef std::list Types; + Types m_types; +public: + + typedef Types::const_iterator const_iterator; + const_iterator begin() const + { + return m_types.begin(); + } + const_iterator end() const + { + return m_types.end(); + } + + std::size_t size() const + { + return m_types.size(); + } + + void addType(const char* moduleName, filetype_t type) + { + m_types.push_back(filetype_pair_t(moduleName, type)); + } +}; + +#ifdef WIN32 + +class Win32Filters +{ + const FileTypeList& m_types; + Array m_filters; +public: + Win32Filters(const FileTypeList& typeList) : m_types(typeList) + { + std::size_t len = 0; + for(FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i) + { + len = len + strlen((*i).m_name.c_str()) + strlen((*i).m_pattern.c_str()) * 2 + 5; + } + m_filters.resize(len + 1); // length + null char + char *w = m_filters.data(); + for(FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i) + { + for(const char *r = (*i).m_name.c_str(); *r!='\0'; r++, w++) + { + *w = *r; + } + *w++ = ' '; + *w++ = '('; + for(const char *r = (*i).m_pattern.c_str(); *r!='\0'; r++, w++) + { + *w = *r; + } + *w++ = ')'; + *w++ = '\0'; + for(const char *r = (*i).m_pattern.c_str(); *r!='\0'; r++, w++) + { + *w = (*r == ',') ? ';' : *r; + } + *w++ = '\0'; + } + m_filters[len] = '\0'; + } + filetype_pair_t getType(const char *filter) const + { + for(FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i) + { + if(string_equal((*i).m_pattern.c_str(), filter)) + { + return filetype_pair_t((*i).m_moduleName.c_str(), filetype_t((*i).m_name.c_str(), (*i).m_pattern.c_str())); + } + } + return filetype_pair_t(); + } + const char* getFilters() const + { + return m_filters.data(); + } +}; + +#define WIN32_LEAN_AND_MEAN +#include +#include + +static char szFile[MAX_PATH]; /* filename string */ + + +#define FILEDLG_CUSTOM_FILTER_LENGTH 64 +// to be used with the advanced file selector + +const char* file_dialog_show_win32(GtkWidget* parent, bool open, const char* title, const char* path, const char* pattern) +{ + const char* r; + char* w; + filetype_t type; + FileTypeList typelist; + + if(pattern == 0) + { + pattern = "*"; + } + + GlobalFiletypes().getTypeList(pattern, &typelist); + + Win32Filters filters(typelist); + + // win32 dialog stores the selected "save as type" extension in the second null-terminated string + char customfilter[FILEDLG_CUSTOM_FILTER_LENGTH]; + + static OPENFILENAME ofn; /* common dialog box structure */ + static char szDirName[MAX_PATH]; /* directory string */ + static char szFile[MAX_PATH]; /* filename string */ + static char szFileTitle[MAX_PATH]; /* file title string */ + static int i, cbString; /* integer count variables */ + static HANDLE hf; /* file handle */ + + // do that the native way + /* Place the terminating null character in the szFile. */ + szFile[0] = '\0'; + customfilter[0] = customfilter[1] = customfilter[2] = '\0'; + + /* Set the members of the OPENFILENAME structure. */ + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = (HWND)GDK_WINDOW_HWND(parent->window); + ofn.nFilterIndex = 0; + ofn.lpstrFilter = filters.getFilters(); + ofn.lpstrCustomFilter = customfilter; + ofn.nMaxCustFilter = sizeof(customfilter); + ofn.lpstrFile = szFile; + ofn.nMaxFile = sizeof(szFile); + ofn.lpstrFileTitle = 0; // we don't need to get the name of the file + if(path) + { + // szDirName: Radiant uses unix convention for paths internally + // Win32 (of course) and Gtk (who would have thought) expect the '\\' convention + // copy path, replacing dir separators as appropriate + for(r=path, w=szDirName; *r!='\0'; r++) + *w++ = (*r=='/') ? '\\' : *r; + // terminate string + *w = '\0'; + ofn.lpstrInitialDir = szDirName; + } + else ofn.lpstrInitialDir = 0; + ofn.lpstrTitle = title; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + + /* Display the Open dialog box. */ + // it's open or close depending on 'open' parameter + if (open) + { + if (!GetOpenFileName(&ofn)) + return 0; // canceled + } + else + { + if (!GetSaveFileName(&ofn)) + return 0; // canceled + } + + if(!string_equal(pattern, "*")) + { + type = filters.getType(customfilter+1).m_type; + } + + // don't return an empty filename + if(szFile[0] == '\0') return 0; + + // convert back to unix format + for(w=szFile; *w!='\0'; w++) + { + if(*w=='\\') + { + *w = '/'; + } + } + // when saving, force an extension depending on filetype + /* \todo SPoG - file_dialog should return filetype information separately.. not force file extension.. */ + if(!open && !string_equal(pattern, "*")) + { + // last ext separator + const char* extension = path_get_extension(szFile); + // no extension + if(string_empty(extension)) + { + strcat(szFile, type.pattern+1); + } + else + { + strcpy(szFile + (extension - szFile), type.pattern+2); + } + } + + return szFile; +} + +#endif + + +class GTKMasks +{ + const FileTypeList& m_types; +public: + std::vector m_filters; + std::vector m_masks; + + GTKMasks(const FileTypeList& types) : m_types(types) + { + m_masks.reserve(m_types.size()); + for(FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i) + { + std::size_t len = strlen((*i).m_name.c_str()) + strlen((*i).m_pattern.c_str()) + 3; + StringOutputStream buffer(len + 1); // length + null char + + buffer << (*i).m_name.c_str() << " <" << (*i).m_pattern.c_str() << ">"; + + m_masks.push_back(buffer.c_str()); + } + + m_filters.reserve(m_types.size()); + for(FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i) + { + m_filters.push_back((*i).m_pattern); + } + } + + filetype_pair_t GetTypeForGTKMask(const char *mask) const + { + std::vector::const_iterator j = m_masks.begin(); + for(FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i, ++j) + { + if(string_equal((*j).c_str(), mask)) + { + return filetype_pair_t((*i).m_moduleName.c_str(), filetype_t((*i).m_name.c_str(), (*i).m_pattern.c_str())); + } + } + return filetype_pair_t(); + } + +}; + +static char g_file_dialog_file[1024]; + +const char* file_dialog_show(GtkWidget* parent, bool open, const char* title, const char* path, const char* pattern) +{ + filetype_t type; + + if(pattern == 0) + { + pattern = "*"; + } + + FileTypeList typelist; + GlobalFiletypes().getTypeList(pattern, &typelist); + + GTKMasks masks(typelist); + + if (title == 0) + title = open ? "Open File" : "Save File"; + + GtkWidget* dialog; + if (open) + { + dialog = gtk_file_chooser_dialog_new(title, + GTK_WINDOW(parent), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + } + else + { + dialog = gtk_file_chooser_dialog_new(title, + GTK_WINDOW(parent), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "unnamed"); + } + + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT); + + // we expect an actual path below, if the path is 0 we might crash + if (path != 0 && !string_empty(path)) + { + ASSERT_MESSAGE(path_is_absolute(path), "file_dialog_show: path not absolute: " << makeQuoted(path)); + + Array new_path(strlen(path)+1); + + // copy path, replacing dir separators as appropriate + Array::iterator w = new_path.begin(); + for(const char* r = path; *r != '\0'; ++r) + { + *w++ = (*r == '/') ? G_DIR_SEPARATOR : *r; + } + // remove separator from end of path if required + if(*(w-1) == G_DIR_SEPARATOR) + { + --w; + } + // terminate string + *w = '\0'; + + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), new_path.data()); + } + + // we should add all important paths as shortcut folder... + // gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), "/tmp/", NULL); + + + for(std::size_t i = 0; i < masks.m_filters.size(); ++i) + { + GtkFileFilter* filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, masks.m_filters[i].c_str()); + gtk_file_filter_set_name(filter, masks.m_masks[i].c_str()); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + } + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) + { + strcpy(g_file_dialog_file, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))); + + if(!string_equal(pattern, "*")) + { + GtkFileFilter* filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); + type = masks.GetTypeForGTKMask(gtk_file_filter_get_name(filter)).m_type; + // last ext separator + const char* extension = path_get_extension(g_file_dialog_file); + // no extension + if(string_empty(extension)) + { + strcat(g_file_dialog_file, type.pattern+1); + } + else + { + strcpy(g_file_dialog_file + (extension - g_file_dialog_file), type.pattern+2); + } + } + + // convert back to unix format + for(char* w = g_file_dialog_file; *w!='\0'; w++) + { + if(*w=='\\') + { + *w = '/'; + } + } + } + else + { + g_file_dialog_file[0] = '\0'; + } + + gtk_widget_destroy(dialog); + + // don't return an empty filename + if(g_file_dialog_file[0] == '\0') return NULL; + + return g_file_dialog_file; +} + +char* dir_dialog(GtkWidget* parent, const char* title, const char* path) +{ + GtkWidget* dialog = gtk_file_chooser_dialog_new(title, + GTK_WINDOW(parent), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT); + + if(!string_empty(path)) + { + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + } + + char* filename = 0; + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) + { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + + gtk_widget_destroy(dialog); + + return filename; +} + + +#ifdef WIN32 +bool g_FileChooser_nativeGUI = true; +#endif + +const char* file_dialog (GtkWidget* parent, bool open, const char* title, const char* path, const char* pattern) +{ + for(;;) + { + const char* file = +#ifdef WIN32 + g_FileChooser_nativeGUI + ? file_dialog_show_win32(parent, open, title, path, pattern) : +#endif + file_dialog_show(parent, open, title, path, pattern); + + if(open + || !file_exists(file) + || gtk_MessageBox(parent, "The file specified already exists.\nDo you want to replace it?", title, eMB_NOYES, eMB_ICONQUESTION) == eIDYES) + { + return file; + } + } +} diff --git a/tools/urt/libs/gtkutil/filechooser.h b/tools/urt/libs/gtkutil/filechooser.h new file mode 100644 index 00000000..8503cfdd --- /dev/null +++ b/tools/urt/libs/gtkutil/filechooser.h @@ -0,0 +1,22 @@ + +#if !defined(INCLUDED_GTKUTIL_FILECHOOSER_H) +#define INCLUDED_GTKUTIL_FILECHOOSER_H + +/// \file +/// GTK+ file-chooser dialogs. + +#ifdef WIN32 +extern bool g_FileChooser_nativeGUI; +#endif + +typedef struct _GtkWidget GtkWidget; +const char* file_dialog(GtkWidget *parent, bool open, const char* title, const char* path = 0, const char* pattern = 0); + + +/// \brief Prompts the user to browse for a directory. +/// The prompt window will be transient to \p parent. +/// The directory will initially default to \p path, which must be an absolute path. +/// The returned string is allocated with \c g_malloc and must be freed with \c g_free. +char* dir_dialog(GtkWidget *parent, const char* title = "Choose Directory", const char* path = ""); + +#endif diff --git a/tools/urt/libs/gtkutil/frame.cpp b/tools/urt/libs/gtkutil/frame.cpp new file mode 100644 index 00000000..7e298177 --- /dev/null +++ b/tools/urt/libs/gtkutil/frame.cpp @@ -0,0 +1,15 @@ + +#include "frame.h" + +#include + +GtkFrame* create_framed_widget(GtkWidget* widget) +{ + GtkFrame* frame = GTK_FRAME(gtk_frame_new(0)); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_frame_set_shadow_type(frame, GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER(frame), widget); + gtk_widget_show(GTK_WIDGET(widget)); + return frame; +} + diff --git a/tools/urt/libs/gtkutil/frame.h b/tools/urt/libs/gtkutil/frame.h new file mode 100644 index 00000000..4aa2eb8b --- /dev/null +++ b/tools/urt/libs/gtkutil/frame.h @@ -0,0 +1,9 @@ + +#if !defined(INCLUDED_GTKUTIL_FRAME_H) +#define INCLUDED_GTKUTIL_FRAME_H + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkFrame GtkFrame; +GtkFrame* create_framed_widget(GtkWidget* widget); + +#endif diff --git a/tools/urt/libs/gtkutil/glfont.cpp b/tools/urt/libs/gtkutil/glfont.cpp new file mode 100644 index 00000000..f7d7346d --- /dev/null +++ b/tools/urt/libs/gtkutil/glfont.cpp @@ -0,0 +1,36 @@ + +#include "glfont.h" + +#include "igl.h" +#include + +GLFont glfont_create(const char* font_string) +{ + GLuint font_list_base = glGenLists (256); + gint font_height = 0; + + PangoFontDescription* font_desc = pango_font_description_from_string (font_string); + + PangoFont* font = gdk_gl_font_use_pango_font (font_desc, 0, 256, font_list_base); + + if(font != 0) + { + PangoFontMetrics* font_metrics = pango_font_get_metrics (font, 0); + + font_height = pango_font_metrics_get_ascent (font_metrics) + + pango_font_metrics_get_descent (font_metrics); + font_height = PANGO_PIXELS (font_height); + + pango_font_metrics_unref (font_metrics); + } + + pango_font_description_free (font_desc); + + return GLFont(font_list_base, font_height); +} + +void glfont_release(GLFont& font) +{ + glDeleteLists(font.getDisplayList(), 256); + font = GLFont(0, 0); +} diff --git a/tools/urt/libs/gtkutil/glfont.h b/tools/urt/libs/gtkutil/glfont.h new file mode 100644 index 00000000..133bf829 --- /dev/null +++ b/tools/urt/libs/gtkutil/glfont.h @@ -0,0 +1,28 @@ + +#if !defined(INCLUDED_GTKUTIL_GLFONT_H) +#define INCLUDED_GTKUTIL_GLFONT_H + +typedef unsigned int GLuint; + +class GLFont +{ + GLuint m_displayList; + int m_pixelHeight; +public: + GLFont(GLuint displayList, int pixelHeight) : m_displayList(displayList), m_pixelHeight(pixelHeight) + { + } + GLuint getDisplayList() const + { + return m_displayList; + } + int getPixelHeight() const + { + return m_pixelHeight; + } +}; + +GLFont glfont_create(const char* font_string); +void glfont_release(GLFont& font); + +#endif diff --git a/tools/urt/libs/gtkutil/glwidget.cpp b/tools/urt/libs/gtkutil/glwidget.cpp new file mode 100644 index 00000000..b03c480e --- /dev/null +++ b/tools/urt/libs/gtkutil/glwidget.cpp @@ -0,0 +1,254 @@ + +// OpenGL widget based on GtkGLExt + +#include "glwidget.h" + +#include "debugging/debugging.h" + +#include "igl.h" + +#include +#include + +#include "pointer.h" + +void (*GLWidget_sharedContextCreated)() = 0; +void (*GLWidget_sharedContextDestroyed)() = 0; + + +typedef int* attribs_t; +struct config_t +{ + const char* name; + attribs_t attribs; +}; +typedef const config_t* configs_iterator; + +int config_rgba32[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 16, + GDK_GL_ATTRIB_LIST_NONE, +}; + +const config_t configs[] = { + { + "colour-buffer = 32bpp, depth-buffer = none", + config_rgba32, + }, + { + "colour-buffer = 16bpp, depth-buffer = none", + config_rgba, + } +}; + +GdkGLConfig* glconfig_new() +{ + GdkGLConfig* glconfig = 0; + + for(configs_iterator i = configs, end = configs + 2; i != end; ++i) + { + glconfig = gdk_gl_config_new((*i).attribs); + if(glconfig != 0) + { + globalOutputStream() << "OpenGL window configuration: " << (*i).name << "\n"; + return glconfig; + } + } + + globalOutputStream() << "OpenGL window configuration: colour-buffer = auto, depth-buffer = none\n"; + return gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGBA | GDK_GL_MODE_DOUBLE)); +} + +int config_rgba32_depth32[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_DEPTH_SIZE, 32, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba32_depth24[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_DEPTH_SIZE, 24, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba32_depth16[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_DEPTH_SIZE, 16, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba32_depth[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_DEPTH_SIZE, 1, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba_depth16[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 16, + GDK_GL_DEPTH_SIZE, 16, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba_depth[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 16, + GDK_GL_DEPTH_SIZE, 1, + GDK_GL_ATTRIB_LIST_NONE, +}; + +const config_t configs_with_depth[] = +{ + { + "colour-buffer = 32bpp, depth-buffer = 32bpp", + config_rgba32_depth32, + }, + { + "colour-buffer = 32bpp, depth-buffer = 24bpp", + config_rgba32_depth24, + }, + { + "colour-buffer = 32bpp, depth-buffer = 16bpp", + config_rgba32_depth16, + }, + { + "colour-buffer = 32bpp, depth-buffer = auto", + config_rgba32_depth, + }, + { + "colour-buffer = 16bpp, depth-buffer = 16bpp", + config_rgba_depth16, + }, + { + "colour-buffer = auto, depth-buffer = auto", + config_rgba_depth, + }, +}; + +GdkGLConfig* glconfig_new_with_depth() +{ + GdkGLConfig* glconfig = 0; + + for(configs_iterator i = configs_with_depth, end = configs_with_depth + 6; i != end; ++i) + { + glconfig = gdk_gl_config_new((*i).attribs); + if(glconfig != 0) + { + globalOutputStream() << "OpenGL window configuration: " << (*i).name << "\n"; + return glconfig; + } + } + + globalOutputStream() << "OpenGL window configuration: colour-buffer = auto, depth-buffer = auto (fallback)\n"; + return gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGBA | GDK_GL_MODE_DOUBLE | GDK_GL_MODE_DEPTH)); +} + +unsigned int g_context_count = 0; + +namespace +{ + GtkWidget* g_shared = 0; +} + +gint glwidget_context_created(GtkWidget* widget, gpointer data) +{ + if(++g_context_count == 1) + { + g_shared = widget; + gtk_widget_ref(g_shared); + + glwidget_make_current(g_shared); + GlobalOpenGL().contextValid = true; + + GLWidget_sharedContextCreated(); + } + return FALSE; +} + +gint glwidget_context_destroyed(GtkWidget* widget, gpointer data) +{ + if(--g_context_count == 0) + { + GlobalOpenGL().contextValid = false; + + GLWidget_sharedContextDestroyed(); + + gtk_widget_unref(g_shared); + g_shared = 0; + } + return FALSE; +} + +gboolean glwidget_enable_gl(GtkWidget* widget, GtkWidget* widget2, gpointer data) +{ + if(widget2 == 0 && !gtk_widget_is_gl_capable(widget)) + { + GdkGLConfig* glconfig = (g_object_get_data(G_OBJECT(widget), "zbuffer")) ? glconfig_new_with_depth() : glconfig_new(); + ASSERT_MESSAGE(glconfig != 0, "failed to create OpenGL config"); + + gtk_widget_set_gl_capability(widget, glconfig, g_shared != 0 ? gtk_widget_get_gl_context(g_shared) : 0, TRUE, GDK_GL_RGBA_TYPE); + + gtk_widget_realize(widget); + if(g_shared == 0) + { + g_shared = widget; + } + + // free glconfig? + } + return FALSE; +} + +GtkWidget* glwidget_new(gboolean zbuffer) +{ + GtkWidget* widget = gtk_drawing_area_new(); + + g_object_set_data(G_OBJECT(widget), "zbuffer", gint_to_pointer(zbuffer)); + + g_signal_connect(G_OBJECT(widget), "hierarchy-changed", G_CALLBACK(glwidget_enable_gl), 0); + + g_signal_connect(G_OBJECT(widget), "realize", G_CALLBACK(glwidget_context_created), 0); + g_signal_connect(G_OBJECT(widget), "unrealize", G_CALLBACK(glwidget_context_destroyed), 0); + + return widget; +} + +void glwidget_destroy_context (GtkWidget *widget) +{ +} + +void glwidget_create_context (GtkWidget *widget) +{ +} + +void glwidget_swap_buffers (GtkWidget *widget) +{ + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); + gdk_gl_drawable_swap_buffers (gldrawable); +} + +gboolean glwidget_make_current (GtkWidget *widget) +{ + GdkGLContext *glcontext = gtk_widget_get_gl_context (widget); + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); + return gdk_gl_drawable_gl_begin (gldrawable, glcontext); +} + diff --git a/tools/urt/libs/gtkutil/glwidget.h b/tools/urt/libs/gtkutil/glwidget.h new file mode 100644 index 00000000..c50eb0f5 --- /dev/null +++ b/tools/urt/libs/gtkutil/glwidget.h @@ -0,0 +1,19 @@ + +#if !defined(INCLUDED_GTKUTIL_GLWIDGET_H) +#define INCLUDED_GTKUTIL_GLWIDGET_H + +typedef struct _GtkWidget GtkWidget; +typedef int gint; +typedef gint gboolean; + +GtkWidget* glwidget_new(gboolean zbuffer); +void glwidget_swap_buffers(GtkWidget* widget); +gboolean glwidget_make_current(GtkWidget* widget); +void glwidget_destroy_context(GtkWidget* widget); +void glwidget_create_context(GtkWidget* widget); + +extern void (*GLWidget_sharedContextCreated)(); +extern void (*GLWidget_sharedContextDestroyed)(); + + +#endif diff --git a/tools/urt/libs/gtkutil/gtkutil.vcproj b/tools/urt/libs/gtkutil/gtkutil.vcproj new file mode 100644 index 00000000..e5f548bf --- /dev/null +++ b/tools/urt/libs/gtkutil/gtkutil.vcproj @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/urt/libs/gtkutil/idledraw.cpp b/tools/urt/libs/gtkutil/idledraw.cpp new file mode 100644 index 00000000..108c7217 --- /dev/null +++ b/tools/urt/libs/gtkutil/idledraw.cpp @@ -0,0 +1,3 @@ + +#include "idledraw.h" + diff --git a/tools/urt/libs/gtkutil/idledraw.h b/tools/urt/libs/gtkutil/idledraw.h new file mode 100644 index 00000000..89492b6a --- /dev/null +++ b/tools/urt/libs/gtkutil/idledraw.h @@ -0,0 +1,49 @@ + +#if !defined(INCLUDED_GTKUTIL_IDLEDRAW_H) +#define INCLUDED_GTKUTIL_IDLEDRAW_H + +#include + +#include "generic/callback.h" + +class IdleDraw +{ + Callback m_draw; + unsigned int m_handler; + static gboolean draw(gpointer data) + { + reinterpret_cast(data)->m_draw(); + reinterpret_cast(data)->m_handler = 0; + return FALSE; + } +public: + IdleDraw(const Callback& draw) : m_draw(draw), m_handler(0) + { + } + ~IdleDraw() + { + if(m_handler != 0) + { + g_source_remove(m_handler); + } + } + void queueDraw() + { + if(m_handler == 0) + { + m_handler = g_idle_add(&draw, this); + } + } + typedef MemberCaller QueueDrawCaller; + + void flush() + { + if(m_handler != 0) + { + draw(this); + } + } +}; + + +#endif diff --git a/tools/urt/libs/gtkutil/image.cpp b/tools/urt/libs/gtkutil/image.cpp new file mode 100644 index 00000000..fb1da567 --- /dev/null +++ b/tools/urt/libs/gtkutil/image.cpp @@ -0,0 +1,76 @@ + +#include "image.h" + +#include +#include + +#include "string/string.h" +#include "stream/stringstream.h" +#include "stream/textstream.h" + + +namespace +{ + CopiedString g_bitmapsPath; +} + +void BitmapsPath_set(const char* path) +{ + g_bitmapsPath = path; +} + +GdkPixbuf* pixbuf_new_from_file_with_mask(const char* filename) +{ + GdkPixbuf* rgb = gdk_pixbuf_new_from_file(filename, 0); + if(rgb == 0) + { + return 0; + } + else + { + GdkPixbuf* rgba = gdk_pixbuf_add_alpha(rgb, TRUE, 255, 0, 255); + gdk_pixbuf_unref(rgb); + return rgba; + } +} + +GtkImage* image_new_from_file_with_mask(const char* filename) +{ + GdkPixbuf* rgba = pixbuf_new_from_file_with_mask(filename); + if(rgba == 0) + { + return 0; + } + else + { + GtkImage* image = GTK_IMAGE(gtk_image_new_from_pixbuf(rgba)); + gdk_pixbuf_unref(rgba); + return image; + } +} + +GtkImage* image_new_missing() +{ + return GTK_IMAGE(gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_SMALL_TOOLBAR)); +} + +GtkImage* new_image(const char* filename) +{ + { + GtkImage* image = image_new_from_file_with_mask(filename); + if(image != 0) + { + return image; + } + } + + return image_new_missing(); +} + +GtkImage* new_local_image(const char* filename) +{ + StringOutputStream fullPath(256); + fullPath << g_bitmapsPath.c_str() << filename; + return new_image(fullPath.c_str()); +} + diff --git a/tools/urt/libs/gtkutil/image.h b/tools/urt/libs/gtkutil/image.h new file mode 100644 index 00000000..448d0298 --- /dev/null +++ b/tools/urt/libs/gtkutil/image.h @@ -0,0 +1,16 @@ + +#if !defined(INCLUDED_GTKUTIL_IMAGE_H) +#define INCLUDED_GTKUTIL_IMAGE_H + +void BitmapsPath_set(const char* path); + +typedef struct _GtkImage GtkImage; +typedef struct _GdkPixbuf GdkPixbuf; + +GdkPixbuf* pixbuf_new_from_file_with_mask(const char* filename); +GtkImage* image_new_from_file_with_mask(const char* filename); +GtkImage* image_new_missing(); +GtkImage* new_image(const char* filename); // filename is full path to image file +GtkImage* new_local_image(const char* filename); // filename is relative to local bitmaps path + +#endif diff --git a/tools/urt/libs/gtkutil/menu.cpp b/tools/urt/libs/gtkutil/menu.cpp new file mode 100644 index 00000000..374b23ae --- /dev/null +++ b/tools/urt/libs/gtkutil/menu.cpp @@ -0,0 +1,293 @@ + +#include "menu.h" + +#include +#include +#include +#include +#include +#include + +#include "generic/callback.h" + +#include "accelerator.h" +#include "closure.h" +#include "container.h" +#include "pointer.h" + +void menu_add_item(GtkMenu* menu, GtkMenuItem* item) +{ + gtk_container_add(GTK_CONTAINER(menu), GTK_WIDGET(item)); +} + +GtkMenuItem* menu_separator(GtkMenu* menu) +{ + GtkMenuItem* menu_item = GTK_MENU_ITEM(gtk_menu_item_new()); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(menu_item)); + gtk_widget_set_sensitive(GTK_WIDGET(menu_item), FALSE); + gtk_widget_show(GTK_WIDGET(menu_item)); + return menu_item; +} + +GtkTearoffMenuItem* menu_tearoff(GtkMenu* menu) +{ + GtkTearoffMenuItem* menu_item = GTK_TEAROFF_MENU_ITEM(gtk_tearoff_menu_item_new()); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(menu_item)); +// gtk_widget_set_sensitive(GTK_WIDGET(menu_item), FALSE); -- controls whether menu is detachable + gtk_widget_show(GTK_WIDGET(menu_item)); + return menu_item; +} + +GtkMenuItem* new_sub_menu_item_with_mnemonic(const char* mnemonic) +{ + GtkMenuItem* item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(mnemonic)); + gtk_widget_show(GTK_WIDGET(item)); + + GtkWidget* sub_menu = gtk_menu_new(); + gtk_menu_item_set_submenu(item, sub_menu); + + return item; +} + +GtkMenu* create_sub_menu_with_mnemonic(GtkMenuShell* parent, const char* mnemonic) +{ + GtkMenuItem* item = new_sub_menu_item_with_mnemonic(mnemonic); + container_add_widget(GTK_CONTAINER(parent), GTK_WIDGET(item)); + return GTK_MENU(gtk_menu_item_get_submenu(item)); +} + +GtkMenu* create_sub_menu_with_mnemonic(GtkMenuBar* bar, const char* mnemonic) +{ + return create_sub_menu_with_mnemonic(GTK_MENU_SHELL(bar), mnemonic); +} + +GtkMenu* create_sub_menu_with_mnemonic(GtkMenu* parent, const char* mnemonic) +{ + return create_sub_menu_with_mnemonic(GTK_MENU_SHELL(parent), mnemonic); +} + +void activate_closure_callback(GtkWidget* widget, gpointer data) +{ + (*reinterpret_cast(data))(); +} + +guint menu_item_connect_callback(GtkMenuItem* item, const Callback& callback) +{ +#if 1 + return g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(callback.getThunk()), callback.getEnvironment()); +#else + return g_signal_connect_closure(G_OBJECT(item), "activate", create_cclosure(G_CALLBACK(activate_closure_callback), callback), FALSE); +#endif +} + +guint check_menu_item_connect_callback(GtkCheckMenuItem* item, const Callback& callback) +{ +#if 1 + guint handler = g_signal_connect_swapped(G_OBJECT(item), "toggled", G_CALLBACK(callback.getThunk()), callback.getEnvironment()); +#else + guint handler = g_signal_connect_closure(G_OBJECT(item), "toggled", create_cclosure(G_CALLBACK(activate_closure_callback), callback), TRUE); +#endif + g_object_set_data(G_OBJECT(item), "handler", gint_to_pointer(handler)); + return handler; +} + +GtkMenuItem* new_menu_item_with_mnemonic(const char *mnemonic, const Callback& callback) +{ + GtkMenuItem* item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(mnemonic)); + gtk_widget_show(GTK_WIDGET(item)); + menu_item_connect_callback(item, callback); + return item; +} + +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu* menu, const char *mnemonic, const Callback& callback) +{ + GtkMenuItem* item = new_menu_item_with_mnemonic(mnemonic, callback); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(item)); + return item; +} + +GtkCheckMenuItem* new_check_menu_item_with_mnemonic(const char* mnemonic, const Callback& callback) +{ + GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(gtk_check_menu_item_new_with_mnemonic(mnemonic)); + gtk_widget_show(GTK_WIDGET(item)); + check_menu_item_connect_callback(item, callback); + return item; +} + +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Callback& callback) +{ + GtkCheckMenuItem* item = new_check_menu_item_with_mnemonic(mnemonic, callback); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(item)); + return item; +} + +GtkRadioMenuItem* new_radio_menu_item_with_mnemonic(GSList** group, const char* mnemonic, const Callback& callback) +{ + GtkRadioMenuItem* item = GTK_RADIO_MENU_ITEM(gtk_radio_menu_item_new_with_mnemonic(*group, mnemonic)); + if(*group == 0) + { + gtk_check_menu_item_set_state(GTK_CHECK_MENU_ITEM(item), TRUE); + } + *group = gtk_radio_menu_item_group(item); + gtk_widget_show(GTK_WIDGET(item)); + check_menu_item_connect_callback(GTK_CHECK_MENU_ITEM(item), callback); + return item; +} + +GtkRadioMenuItem* create_radio_menu_item_with_mnemonic(GtkMenu* menu, GSList** group, const char* mnemonic, const Callback& callback) +{ + GtkRadioMenuItem* item = new_radio_menu_item_with_mnemonic(group, mnemonic, callback); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(item)); + return item; +} + +void check_menu_item_set_active_no_signal(GtkCheckMenuItem* item, gboolean active) +{ + guint handler_id = gpointer_to_int(g_object_get_data(G_OBJECT(item), "handler")); + g_signal_handler_block(G_OBJECT(item), handler_id); + gtk_check_menu_item_set_active(item, active); + g_signal_handler_unblock(G_OBJECT(item), handler_id); +} + + + +void radio_menu_item_set_active_no_signal(GtkRadioMenuItem* item, gboolean active) +{ + { + for(GSList* l = gtk_radio_menu_item_get_group(item); l != 0; l = g_slist_next(l)) + { + g_signal_handler_block(G_OBJECT(l->data), gpointer_to_int(g_object_get_data(G_OBJECT(l->data), "handler"))); + } + } + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), active); + { + for(GSList* l = gtk_radio_menu_item_get_group(item); l != 0; l = g_slist_next(l)) + { + g_signal_handler_unblock(G_OBJECT(l->data), gpointer_to_int(g_object_get_data(G_OBJECT(l->data), "handler"))); + } + } +} + + +void menu_item_set_accelerator(GtkMenuItem* item, GClosure* closure) +{ + GtkAccelLabel* accel_label = GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(item))); + gtk_accel_label_set_accel_closure(accel_label, closure); +} + +void accelerator_name(const Accelerator& accelerator, GString* gstring) +{ + gboolean had_mod = FALSE; + if (accelerator.modifiers & GDK_SHIFT_MASK) + { + g_string_append (gstring, "Shift"); + had_mod = TRUE; + } + if (accelerator.modifiers & GDK_CONTROL_MASK) + { + if (had_mod) + g_string_append (gstring, "+"); + g_string_append (gstring, "Ctrl"); + had_mod = TRUE; + } + if (accelerator.modifiers & GDK_MOD1_MASK) + { + if (had_mod) + g_string_append (gstring, "+"); + g_string_append (gstring, "Alt"); + had_mod = TRUE; + } + + if (had_mod) + g_string_append (gstring, "+"); + if (accelerator.key < 0x80 || (accelerator.key > 0x80 && accelerator.key <= 0xff)) + { + switch (accelerator.key) + { + case ' ': + g_string_append (gstring, "Space"); + break; + case '\\': + g_string_append (gstring, "Backslash"); + break; + default: + g_string_append_c (gstring, gchar(toupper(accelerator.key))); + break; + } + } + else + { + gchar *tmp; + + tmp = gtk_accelerator_name (accelerator.key, (GdkModifierType)0); + if (tmp[0] != 0 && tmp[1] == 0) + tmp[0] = gchar(toupper(tmp[0])); + g_string_append (gstring, tmp); + g_free (tmp); + } +} + +void menu_item_set_accelerator(GtkMenuItem* item, Accelerator accelerator) +{ + GtkAccelLabel* accel_label = GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(item))); + + g_free (accel_label->accel_string); + accel_label->accel_string = 0; + + GString* gstring = g_string_new (accel_label->accel_string); + g_string_append (gstring, " "); + + accelerator_name(accelerator, gstring); + + g_free (accel_label->accel_string); + accel_label->accel_string = gstring->str; + g_string_free (gstring, FALSE); + + if (!accel_label->accel_string) + accel_label->accel_string = g_strdup (""); + + gtk_widget_queue_resize (GTK_WIDGET (accel_label)); +} + +void menu_item_add_accelerator(GtkMenuItem* item, Accelerator accelerator) +{ + if(accelerator.key != 0) + { + GClosure* closure = global_accel_group_find(accelerator); + if(closure != 0) + { + menu_item_set_accelerator(item, closure); + } + else + { + menu_item_set_accelerator(item, accelerator); + } + } +} + +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Command& command) +{ + command_connect_accelerator(command.m_accelerator, command.m_callback); + GtkMenuItem* item = create_menu_item_with_mnemonic(menu, mnemonic, command.m_callback); + menu_item_add_accelerator(item, command.m_accelerator); + return item; +} + +void check_menu_item_set_active_callback(GtkCheckMenuItem& item, bool enabled) +{ + check_menu_item_set_active_no_signal(&item, enabled); +} +typedef ReferenceCaller1 CheckMenuItemSetActiveCaller; + +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Toggle& toggle) +{ + command_connect_accelerator(toggle.m_command.m_accelerator, toggle.m_command.m_callback); + GtkCheckMenuItem* item = create_check_menu_item_with_mnemonic(menu, mnemonic, toggle.m_command.m_callback); + menu_item_add_accelerator(GTK_MENU_ITEM(item), toggle.m_command.m_accelerator); + toggle.m_exportCallback(CheckMenuItemSetActiveCaller(*item)); + return item; +} + + + + diff --git a/tools/urt/libs/gtkutil/menu.h b/tools/urt/libs/gtkutil/menu.h new file mode 100644 index 00000000..355dbdb3 --- /dev/null +++ b/tools/urt/libs/gtkutil/menu.h @@ -0,0 +1,37 @@ + +#if !defined(INCLUDED_GTKUTIL_MENU_H) +#define INCLUDED_GTKUTIL_MENU_H + +class Callback; +typedef int gint; +typedef gint gboolean; +typedef struct _GSList GSList; +typedef struct _GtkMenu GtkMenu; +typedef struct _GtkMenuBar GtkMenuBar; +typedef struct _GtkMenuItem GtkMenuItem; +typedef struct _GtkCheckMenuItem GtkCheckMenuItem; +typedef struct _GtkRadioMenuItem GtkRadioMenuItem; +typedef struct _GtkTearoffMenuItem GtkTearoffMenuItem; + +void menu_add_item(GtkMenu* menu, GtkMenuItem* item); +GtkMenuItem* menu_separator(GtkMenu* menu); +GtkTearoffMenuItem* menu_tearoff(GtkMenu* menu); +GtkMenuItem* new_sub_menu_item_with_mnemonic(const char* mnemonic); +GtkMenu* create_sub_menu_with_mnemonic(GtkMenuBar* bar, const char* mnemonic); +GtkMenu* create_sub_menu_with_mnemonic(GtkMenu* parent, const char* mnemonic); +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Callback& callback); +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Callback& callback); +GtkRadioMenuItem* create_radio_menu_item_with_mnemonic(GtkMenu* menu, GSList** group, const char* mnemonic, const Callback& callback); + +class Command; +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Command& command); +class Toggle; +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Toggle& toggle); + + +typedef struct _GtkCheckMenuItem GtkCheckMenuItem; +void check_menu_item_set_active_no_signal(GtkCheckMenuItem* item, gboolean active); +typedef struct _GtkRadioMenuItem GtkRadioMenuItem; +void radio_menu_item_set_active_no_signal(GtkRadioMenuItem* item, gboolean active); + +#endif diff --git a/tools/urt/libs/gtkutil/messagebox.cpp b/tools/urt/libs/gtkutil/messagebox.cpp new file mode 100644 index 00000000..dd7e0e4e --- /dev/null +++ b/tools/urt/libs/gtkutil/messagebox.cpp @@ -0,0 +1,193 @@ + +#include "messagebox.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dialog.h" +#include "widget.h" + +GtkWidget* create_padding(int width, int height) +{ + GtkWidget* widget = gtk_alignment_new(0.0, 0.0, 0.0, 0.0); + gtk_widget_show(widget); + gtk_widget_set_size_request(widget, width, height); + return widget; +} + +const char* messagebox_stock_icon(EMessageBoxIcon type) +{ + switch(type) + { + default: + case eMB_ICONDEFAULT: + return GTK_STOCK_DIALOG_INFO; + case eMB_ICONERROR: + return GTK_STOCK_DIALOG_ERROR; + case eMB_ICONWARNING: + return GTK_STOCK_DIALOG_WARNING; + case eMB_ICONQUESTION: + return GTK_STOCK_DIALOG_QUESTION; + case eMB_ICONASTERISK: + return GTK_STOCK_DIALOG_INFO; + } +} + +EMessageBoxReturn gtk_MessageBox (GtkWidget *parent, const char* text, const char* title, EMessageBoxType type, EMessageBoxIcon icon) +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + ModalDialogButton yes_button(dialog, eIDYES); + ModalDialogButton no_button(dialog, eIDNO); + + GtkWindow* parentWindow = parent != 0 ? GTK_WINDOW(parent) : 0; + + GtkWindow* window = create_fixedsize_modal_dialog_window(parentWindow, title, dialog, 400, 100); + + if(parentWindow != 0) + { + //g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(floating_window_delete_present), parent); + gtk_window_deiconify(parentWindow); + } + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel); + + GtkVBox* vbox = create_dialog_vbox(8, 8); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox)); + + + GtkHBox* hboxDummy = create_dialog_hbox(0, 0); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hboxDummy), FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(hboxDummy), create_padding(0, 50), FALSE, FALSE, 0); // HACK to force minimum height + + GtkHBox* iconBox = create_dialog_hbox(16, 0); + gtk_box_pack_start(GTK_BOX(hboxDummy), GTK_WIDGET(iconBox), FALSE, FALSE, 0); + + GtkImage* image = GTK_IMAGE(gtk_image_new_from_stock(messagebox_stock_icon(icon), GTK_ICON_SIZE_DIALOG)); + gtk_widget_show(GTK_WIDGET(image)); + gtk_box_pack_start(GTK_BOX(iconBox), GTK_WIDGET(image), FALSE, FALSE, 0); + + GtkLabel* label = GTK_LABEL(gtk_label_new(text)); + gtk_widget_show(GTK_WIDGET(label)); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + gtk_label_set_line_wrap(label, TRUE); + gtk_box_pack_start(GTK_BOX(iconBox), GTK_WIDGET(label), TRUE, TRUE, 0); + + + GtkVBox* vboxDummy = create_dialog_vbox(0, 0); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(vboxDummy), FALSE, FALSE, 0); + + GtkAlignment* alignment = GTK_ALIGNMENT(gtk_alignment_new(0.5, 0.0, 0.0, 0.0)); + gtk_widget_show(GTK_WIDGET(alignment)); + gtk_box_pack_start(GTK_BOX(vboxDummy), GTK_WIDGET(alignment), FALSE, FALSE, 0); + + GtkHBox* hbox = create_dialog_hbox(8, 0); + gtk_container_add(GTK_CONTAINER(alignment), GTK_WIDGET(hbox)); + + gtk_box_pack_start(GTK_BOX(vboxDummy), create_padding(400, 0), FALSE, FALSE, 0); // HACK to force minimum width + + + if (type == eMB_OK) + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + + dialog.ret = eIDOK; + } + else if (type == eMB_OKCANCEL) + { + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + } + + { + GtkButton* button = create_modal_dialog_button("OK", cancel_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + gtk_widget_show(GTK_WIDGET(button)); + } + + dialog.ret = eIDCANCEL; + } + else if (type == eMB_YESNOCANCEL) + { + { + GtkButton* button = create_modal_dialog_button("Yes", yes_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + } + + { + GtkButton* button = create_modal_dialog_button("No", no_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_show(GTK_WIDGET(button)); + } + { + GtkButton* button = create_modal_dialog_button("Cancel", cancel_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_show(GTK_WIDGET(button)); + } + + dialog.ret = eIDCANCEL; + } + else if (type == eMB_NOYES) + { + { + GtkButton* button = create_modal_dialog_button("No", no_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + } + { + GtkButton* button = create_modal_dialog_button("Yes", yes_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_show(GTK_WIDGET(button)); + } + + dialog.ret = eIDNO; + } + else /* if (type == eMB_YESNO) */ + { + { + GtkButton* button = create_modal_dialog_button("Yes", yes_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + } + + { + GtkButton* button = create_modal_dialog_button("No", no_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_show(GTK_WIDGET(button)); + } + dialog.ret = eIDNO; + } + + modal_dialog_show(window, dialog); + + gtk_widget_destroy(GTK_WIDGET(window)); + + return dialog.ret; +} + diff --git a/tools/urt/libs/gtkutil/messagebox.h b/tools/urt/libs/gtkutil/messagebox.h new file mode 100644 index 00000000..5edcbfde --- /dev/null +++ b/tools/urt/libs/gtkutil/messagebox.h @@ -0,0 +1,11 @@ + +#if !defined(INCLUDED_GTKUTIL_MESSAGEBOX_H) +#define INCLUDED_GTKUTIL_MESSAGEBOX_H + +#include "qerplugin.h" + +typedef struct _GtkWidget GtkWidget; +/// \brief Shows a modal message-box. +EMessageBoxReturn gtk_MessageBox(GtkWidget *parent, const char* text, const char* title = "GtkRadiant", EMessageBoxType type = eMB_OK, EMessageBoxIcon icon = eMB_ICONDEFAULT); + +#endif diff --git a/tools/urt/libs/gtkutil/nonmodal.cpp b/tools/urt/libs/gtkutil/nonmodal.cpp new file mode 100644 index 00000000..ec51bfea --- /dev/null +++ b/tools/urt/libs/gtkutil/nonmodal.cpp @@ -0,0 +1,3 @@ + +#include "nonmodal.h" + diff --git a/tools/urt/libs/gtkutil/nonmodal.h b/tools/urt/libs/gtkutil/nonmodal.h new file mode 100644 index 00000000..eba98e70 --- /dev/null +++ b/tools/urt/libs/gtkutil/nonmodal.h @@ -0,0 +1,166 @@ + +#if !defined(INCLUDED_GTKUTIL_NONMODAL_H) +#define INCLUDED_GTKUTIL_NONMODAL_H + +#include +#include +#include +#include + +#include "generic/callback.h" + +#include "pointer.h" +#include "button.h" + +typedef struct _GtkEntry GtkEntry; + + +inline gboolean escape_clear_focus_widget(GtkWidget* widget, GdkEventKey* event, gpointer data) +{ + if(event->keyval == GDK_Escape) + { + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(widget))), NULL); + return TRUE; + } + return FALSE; +} + +inline void widget_connect_escape_clear_focus_widget(GtkWidget* widget) +{ + g_signal_connect(G_OBJECT(widget), "key_press_event", G_CALLBACK(escape_clear_focus_widget), 0); +} + + +class NonModalEntry +{ + bool m_editing; + Callback m_apply; + Callback m_cancel; + + static gboolean focus_in(GtkEntry* entry, GdkEventFocus *event, NonModalEntry* self) + { + self->m_editing = false; + return FALSE; + } + + static gboolean focus_out(GtkEntry* entry, GdkEventFocus *event, NonModalEntry* self) + { + if(self->m_editing && GTK_WIDGET_VISIBLE(entry)) + { + self->m_apply(); + } + self->m_editing = false; + return FALSE; + } + + static gboolean changed(GtkEntry* entry, NonModalEntry* self) + { + self->m_editing = true; + return FALSE; + } + + static gboolean enter(GtkEntry* entry, GdkEventKey* event, NonModalEntry* self) + { + if(event->keyval == GDK_Return) + { + self->m_apply(); + self->m_editing = false; + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(entry))), NULL); + return TRUE; + } + return FALSE; + } + + static gboolean escape(GtkEntry* entry, GdkEventKey* event, NonModalEntry* self) + { + if(event->keyval == GDK_Escape) + { + self->m_cancel(); + self->m_editing = false; + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(entry))), NULL); + return TRUE; + } + return FALSE; + } + +public: + NonModalEntry(const Callback& apply, const Callback& cancel) : m_editing(false), m_apply(apply), m_cancel(cancel) + { + } + void connect(GtkEntry* entry) + { + g_signal_connect(G_OBJECT(entry), "focus_in_event", G_CALLBACK(focus_in), this); + g_signal_connect(G_OBJECT(entry), "focus_out_event", G_CALLBACK(focus_out), this); + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(enter), this); + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(escape), this); + g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(changed), this); + } +}; + + +class NonModalSpinner +{ + Callback m_apply; + Callback m_cancel; + + static gboolean changed(GtkSpinButton* spin, NonModalSpinner* self) + { + self->m_apply(); + return FALSE; + } + + static gboolean enter(GtkSpinButton* spin, GdkEventKey* event, NonModalSpinner* self) + { + if(event->keyval == GDK_Return) + { + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(spin))), NULL); + return TRUE; + } + return FALSE; + } + + static gboolean escape(GtkSpinButton* spin, GdkEventKey* event, NonModalSpinner* self) + { + if(event->keyval == GDK_Escape) + { + self->m_cancel(); + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(spin))), NULL); + return TRUE; + } + return FALSE; + } + +public: + NonModalSpinner(const Callback& apply, const Callback& cancel) : m_apply(apply), m_cancel(cancel) + { + } + void connect(GtkSpinButton* spin) + { + guint handler = g_signal_connect(G_OBJECT(gtk_spin_button_get_adjustment(spin)), "value_changed", G_CALLBACK(changed), this); + g_object_set_data(G_OBJECT(spin), "handler", gint_to_pointer(handler)); + g_signal_connect(G_OBJECT(spin), "key_press_event", G_CALLBACK(enter), this); + g_signal_connect(G_OBJECT(spin), "key_press_event", G_CALLBACK(escape), this); + } +}; + + +class NonModalRadio +{ + Callback m_changed; + +public: + NonModalRadio(const Callback& changed) : m_changed(changed) + { + } + void connect(GtkRadioButton* radio) + { + GSList* group = gtk_radio_button_group(radio); + for(; group != 0; group = g_slist_next(group)) + { + toggle_button_connect_callback(GTK_TOGGLE_BUTTON(group->data), m_changed); + } + } +}; + + +#endif diff --git a/tools/urt/libs/gtkutil/paned.cpp b/tools/urt/libs/gtkutil/paned.cpp new file mode 100644 index 00000000..bbf0f5ef --- /dev/null +++ b/tools/urt/libs/gtkutil/paned.cpp @@ -0,0 +1,80 @@ + +#include "paned.h" + +#include +#include + +#include "frame.h" + + +class PanedState +{ +public: + float position; + int size; +}; + +gboolean hpaned_allocate(GtkWidget* widget, GtkAllocation* allocation, PanedState* paned) +{ + if(paned->size != allocation->width) + { + paned->size = allocation->width; + gtk_paned_set_position (GTK_PANED (widget), static_cast(paned->size * paned->position)); + } + return FALSE; +} + +gboolean vpaned_allocate(GtkWidget* widget, GtkAllocation* allocation, PanedState* paned) +{ + if(paned->size != allocation->height) + { + paned->size = allocation->height; + gtk_paned_set_position (GTK_PANED (widget), static_cast(paned->size * paned->position)); + } + return FALSE; +} + +gboolean paned_position(GtkWidget* widget, gpointer dummy, PanedState* paned) +{ + if(paned->size != -1) + paned->position = gtk_paned_get_position (GTK_PANED (widget)) / static_cast(paned->size); + return FALSE; +} + +PanedState g_hpaned = { 0.5f, -1, }; +PanedState g_vpaned1 = { 0.5f, -1, }; +PanedState g_vpaned2 = { 0.5f, -1, }; + +GtkHPaned* create_split_views(GtkWidget* topleft, GtkWidget* topright, GtkWidget* botleft, GtkWidget* botright) +{ + GtkHPaned* hsplit = GTK_HPANED(gtk_hpaned_new()); + gtk_widget_show(GTK_WIDGET(hsplit)); + + g_signal_connect(G_OBJECT(hsplit), "size_allocate", G_CALLBACK(hpaned_allocate), &g_hpaned); + g_signal_connect(G_OBJECT(hsplit), "notify::position", G_CALLBACK(paned_position), &g_hpaned); + + { + GtkVPaned* vsplit = GTK_VPANED(gtk_vpaned_new()); + gtk_paned_add1(GTK_PANED(hsplit), GTK_WIDGET(vsplit)); + gtk_widget_show(GTK_WIDGET(vsplit)); + + g_signal_connect(G_OBJECT(vsplit), "size_allocate", G_CALLBACK(vpaned_allocate), &g_vpaned1); + g_signal_connect(G_OBJECT(vsplit), "notify::position", G_CALLBACK(paned_position), &g_vpaned1); + + gtk_paned_add1(GTK_PANED(vsplit), GTK_WIDGET(create_framed_widget(topleft))); + gtk_paned_add2(GTK_PANED(vsplit), GTK_WIDGET(create_framed_widget(topright))); + } + { + GtkVPaned* vsplit = GTK_VPANED(gtk_vpaned_new()); + gtk_paned_add2(GTK_PANED(hsplit), GTK_WIDGET(vsplit)); + gtk_widget_show(GTK_WIDGET(vsplit)); + + g_signal_connect(G_OBJECT(vsplit), "size_allocate", G_CALLBACK(vpaned_allocate), &g_vpaned2); + g_signal_connect(G_OBJECT(vsplit), "notify::position", G_CALLBACK(paned_position), &g_vpaned2); + + gtk_paned_add1(GTK_PANED(vsplit), GTK_WIDGET(create_framed_widget(botleft))); + gtk_paned_add2(GTK_PANED(vsplit), GTK_WIDGET(create_framed_widget(botright))); + } + return hsplit; +} + diff --git a/tools/urt/libs/gtkutil/paned.h b/tools/urt/libs/gtkutil/paned.h new file mode 100644 index 00000000..2b40676a --- /dev/null +++ b/tools/urt/libs/gtkutil/paned.h @@ -0,0 +1,9 @@ + +#if !defined(INCLUDED_GTKUTIL_PANED_H) +#define INCLUDED_GTKUTIL_PANED_H + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkHPaned GtkHPaned; +GtkHPaned* create_split_views(GtkWidget* topleft, GtkWidget* topright, GtkWidget* botleft, GtkWidget* botright); + +#endif diff --git a/tools/urt/libs/gtkutil/pointer.cpp b/tools/urt/libs/gtkutil/pointer.cpp new file mode 100644 index 00000000..cbfb4697 --- /dev/null +++ b/tools/urt/libs/gtkutil/pointer.cpp @@ -0,0 +1,3 @@ + +#include "pointer.h" + diff --git a/tools/urt/libs/gtkutil/pointer.h b/tools/urt/libs/gtkutil/pointer.h new file mode 100644 index 00000000..59cbe69d --- /dev/null +++ b/tools/urt/libs/gtkutil/pointer.h @@ -0,0 +1,20 @@ + +#if !defined(INCLUDED_GTKUTIL_POINTER_H) +#define INCLUDED_GTKUTIL_POINTER_H + +typedef int gint; +typedef void* gpointer; + +#include + +inline gint gpointer_to_int(gpointer p) +{ + return gint(std::size_t(p)); +} + +inline gpointer gint_to_pointer(gint i) +{ + return gpointer(std::size_t(i)); +} + +#endif diff --git a/tools/urt/libs/gtkutil/toolbar.cpp b/tools/urt/libs/gtkutil/toolbar.cpp new file mode 100644 index 00000000..8dd22ff6 --- /dev/null +++ b/tools/urt/libs/gtkutil/toolbar.cpp @@ -0,0 +1,58 @@ + +#include "toolbar.h" + +#include +#include + +#include "generic/callback.h" + +#include "accelerator.h" +#include "button.h" +#include "closure.h" +#include "pointer.h" + + +void toolbar_append(GtkToolbar* toolbar, GtkButton* button, const char* description) +{ + gtk_widget_show(GTK_WIDGET(button)); + gtk_button_set_relief(button, GTK_RELIEF_NONE); + GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(button), GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(button), GTK_CAN_DEFAULT); + gtk_toolbar_append_element(toolbar, GTK_TOOLBAR_CHILD_WIDGET, GTK_WIDGET(button), "", description, "", 0, 0, 0); +} + +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const Callback& callback) +{ + GtkButton* button = GTK_BUTTON(gtk_button_new()); + button_set_icon(button, icon); + button_connect_callback(button, callback); + toolbar_append(toolbar, button, description); + return button; +} + +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const Callback& callback) +{ + GtkToggleButton* button = GTK_TOGGLE_BUTTON(gtk_toggle_button_new()); + button_set_icon(GTK_BUTTON(button), icon); + toggle_button_connect_callback(button, callback); + toolbar_append(toolbar, GTK_BUTTON(button), description); + return button; +} + +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const Command& command) +{ + return toolbar_append_button(toolbar, description, icon, command.m_callback); +} + +void toggle_button_set_active_callback(GtkToggleButton& button, bool active) +{ + toggle_button_set_active_no_signal(&button, active); +} +typedef ReferenceCaller1 ToggleButtonSetActiveCaller; + +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const Toggle& toggle) +{ + GtkToggleButton* button = toolbar_append_toggle_button(toolbar, description, icon, toggle.m_command.m_callback); + toggle.m_exportCallback(ToggleButtonSetActiveCaller(*button)); + return button; +} diff --git a/tools/urt/libs/gtkutil/toolbar.h b/tools/urt/libs/gtkutil/toolbar.h new file mode 100644 index 00000000..05c5e24d --- /dev/null +++ b/tools/urt/libs/gtkutil/toolbar.h @@ -0,0 +1,17 @@ + +#if !defined(INCLUDED_GTKUTIL_TOOLBAR_H) +#define INCLUDED_GTKUTIL_TOOLBAR_H + +class Callback; +typedef struct _GtkButton GtkButton; +typedef struct _GtkToggleButton GtkToggleButton; +typedef struct _GtkToolbar GtkToolbar; +class Command; +class Toggle; + +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const Callback& callback); +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const Command& command); +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const Callback& callback); +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const Toggle& toggle); + +#endif diff --git a/tools/urt/libs/gtkutil/widget.cpp b/tools/urt/libs/gtkutil/widget.cpp new file mode 100644 index 00000000..23c24a65 --- /dev/null +++ b/tools/urt/libs/gtkutil/widget.cpp @@ -0,0 +1,3 @@ + +#include "widget.h" + diff --git a/tools/urt/libs/gtkutil/widget.h b/tools/urt/libs/gtkutil/widget.h new file mode 100644 index 00000000..0e6f6866 --- /dev/null +++ b/tools/urt/libs/gtkutil/widget.h @@ -0,0 +1,145 @@ + +#if !defined(INCLUDED_GTKUTIL_WIDGET_H) +#define INCLUDED_GTKUTIL_WIDGET_H + +#include +#include +#include "generic/callback.h" +#include "warnings.h" + +inline void widget_set_visible(GtkWidget* widget, bool shown) +{ + if(shown) + { + gtk_widget_show(widget); + } + else + { + gtk_widget_hide(widget); + } +} + +inline bool widget_is_visible(GtkWidget* widget) +{ + return GTK_WIDGET_VISIBLE(widget) != FALSE; +} + +inline void widget_toggle_visible(GtkWidget* widget) +{ + widget_set_visible(widget, !widget_is_visible(widget)); +} + +class ToggleItem +{ + BoolExportCallback m_exportCallback; + typedef std::list ImportCallbacks; + ImportCallbacks m_importCallbacks; +public: + ToggleItem(const BoolExportCallback& exportCallback) : m_exportCallback(exportCallback) + { + } + + void update() + { + for(ImportCallbacks::iterator i = m_importCallbacks.begin(); i != m_importCallbacks.end(); ++i) + { + m_exportCallback(*i); + } + } + + void addCallback(const BoolImportCallback& callback) + { + m_importCallbacks.push_back(callback); + m_exportCallback(callback); + } + typedef MemberCaller1 AddCallbackCaller; +}; + +class ToggleShown +{ + bool m_shownDeferred; + + ToggleShown(const ToggleShown& other); // NOT COPYABLE + ToggleShown& operator=(const ToggleShown& other); // NOT ASSIGNABLE + + static gboolean notify_visible(GtkWidget* widget, gpointer dummy, ToggleShown* self) + { + self->update(); + return FALSE; + } + static gboolean destroy(GtkWidget* widget, ToggleShown* self) + { + self->m_shownDeferred = GTK_WIDGET_VISIBLE(self->m_widget) != FALSE; + self->m_widget = 0; + return FALSE; + } +public: + GtkWidget* m_widget; + ToggleItem m_item; + + ToggleShown(bool shown) + : m_shownDeferred(shown), m_widget(0), m_item(ActiveCaller(*this)) + { + } + void update() + { + m_item.update(); + } + bool active() const + { + if(m_widget == 0) + { + return m_shownDeferred; + } + else + { + return GTK_WIDGET_VISIBLE(m_widget) != FALSE; + } + } + void exportActive(const BoolImportCallback& importCallback) + { + importCallback(active()); + } + typedef MemberCaller1 ActiveCaller; + void set(bool shown) + { + if(m_widget == 0) + { + m_shownDeferred = shown; + } + else + { + widget_set_visible(m_widget, shown); + } + } + void toggle() + { + widget_toggle_visible(m_widget); + } + typedef MemberCaller ToggleCaller; + void connect(GtkWidget* widget) + { + m_widget = widget; + widget_set_visible(m_widget, m_shownDeferred); + g_signal_connect(G_OBJECT(m_widget), "notify::visible", G_CALLBACK(notify_visible), this); + g_signal_connect(G_OBJECT(m_widget), "destroy", G_CALLBACK(destroy), this); + update(); + } +}; + + +inline void widget_queue_draw(GtkWidget& widget) +{ + gtk_widget_queue_draw(&widget); +} +typedef ReferenceCaller WidgetQueueDrawCaller; + + +inline void widget_make_default(GtkWidget* widget) +{ + GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_DEFAULT); + gtk_widget_grab_default(widget); +} + + +#endif diff --git a/tools/urt/libs/gtkutil/window.cpp b/tools/urt/libs/gtkutil/window.cpp new file mode 100644 index 00000000..47c44f8d --- /dev/null +++ b/tools/urt/libs/gtkutil/window.cpp @@ -0,0 +1,150 @@ + +#include "window.h" + +#include + +#include "pointer.h" +#include "accelerator.h" + +inline void CHECK_RESTORE(GtkWidget* w) +{ + if(gpointer_to_int(g_object_get_data(G_OBJECT(w), "was_mapped")) != 0) + { + gtk_widget_show(w); + } +} + +inline void CHECK_MINIMIZE(GtkWidget* w) +{ + g_object_set_data(G_OBJECT(w), "was_mapped", gint_to_pointer(GTK_WIDGET_VISIBLE(w))); + gtk_widget_hide(w); +} + +static gboolean main_window_iconified(GtkWidget* widget, GdkEventWindowState* event, gpointer data) +{ + if((event->changed_mask & (GDK_WINDOW_STATE_ICONIFIED|GDK_WINDOW_STATE_WITHDRAWN)) != 0) + { + if((event->new_window_state & (GDK_WINDOW_STATE_ICONIFIED|GDK_WINDOW_STATE_WITHDRAWN)) != 0) + { + CHECK_MINIMIZE(GTK_WIDGET(data)); + } + else + { + CHECK_RESTORE(GTK_WIDGET(data)); + } + } + return FALSE; +} + +unsigned int connect_floating(GtkWindow* main_window, GtkWindow* floating) +{ + return g_signal_connect(G_OBJECT(main_window), "window_state_event", G_CALLBACK(main_window_iconified), floating); +} + +gboolean destroy_disconnect_floating(GtkWindow* widget, gpointer data) +{ + g_signal_handler_disconnect(G_OBJECT(data), gpointer_to_int(g_object_get_data(G_OBJECT(widget), "floating_handler"))); + return FALSE; +} + +gboolean floating_window_delete_present(GtkWindow* floating, GdkEventFocus *event, GtkWindow* main_window) +{ + if(gtk_window_is_active(floating) || gtk_window_is_active(main_window)) + { + gtk_window_present(main_window); + } + return FALSE; +} + +guint connect_floating_window_delete_present(GtkWindow* floating, GtkWindow* main_window) +{ + return g_signal_connect(G_OBJECT(floating), "delete_event", G_CALLBACK(floating_window_delete_present), main_window); +} + +gboolean floating_window_destroy_present(GtkWindow* floating, GtkWindow* main_window) +{ + if(gtk_window_is_active(floating) || gtk_window_is_active(main_window)) + { + gtk_window_present(main_window); + } + return FALSE; +} + +guint connect_floating_window_destroy_present(GtkWindow* floating, GtkWindow* main_window) +{ + return g_signal_connect(G_OBJECT(floating), "destroy", G_CALLBACK(floating_window_destroy_present), main_window); +} + +GtkWindow* create_floating_window(const char* title, GtkWindow* parent) +{ + GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + gtk_window_set_title(window, title); + + if(parent != 0) + { + gtk_window_set_transient_for(window, parent); + connect_floating_window_destroy_present(window, parent); + g_object_set_data(G_OBJECT(window), "floating_handler", gint_to_pointer(connect_floating(parent, window))); + g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy_disconnect_floating), parent); + } + + return window; +} + +void destroy_floating_window(GtkWindow* window) +{ + gtk_widget_destroy(GTK_WIDGET(window)); +} + +gint window_realize_remove_sysmenu(GtkWidget* widget, gpointer data) +{ + gdk_window_set_decorations(widget->window, (GdkWMDecoration)(GDK_DECOR_ALL|GDK_DECOR_MENU)); + return FALSE; +} + +gboolean persistent_floating_window_delete(GtkWindow* floating, GdkEvent *event, GtkWindow* main_window) +{ + gtk_widget_hide(GTK_WIDGET(floating)); + return TRUE; +} + +GtkWindow* create_persistent_floating_window(const char* title, GtkWindow* main_window) +{ + GtkWindow* window = GTK_WINDOW(create_floating_window(title, main_window)); + + gtk_widget_set_events(GTK_WIDGET(window), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); + + connect_floating_window_delete_present(window, main_window); + g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(persistent_floating_window_delete), 0); + +#if 0 + if(g_multimon_globals.m_bStartOnPrimMon && g_multimon_globals.m_bNoSysMenuPopups) + g_signal_connect(G_OBJECT(window), "realize", G_CALLBACK(window_realize_remove_sysmenu), 0); +#endif + + return window; +} + +gint window_realize_remove_minmax(GtkWidget* widget, gpointer data) +{ + gdk_window_set_decorations(widget->window, (GdkWMDecoration)(GDK_DECOR_ALL|GDK_DECOR_MINIMIZE|GDK_DECOR_MAXIMIZE)); + return FALSE; +} + +void window_remove_minmax(GtkWindow* window) +{ + g_signal_connect(G_OBJECT(window), "realize", G_CALLBACK(window_realize_remove_minmax), 0); +} + + +GtkScrolledWindow* create_scrolled_window(GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy, int border) +{ + GtkScrolledWindow* scr = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(0, 0)); + gtk_widget_show(GTK_WIDGET(scr)); + gtk_scrolled_window_set_policy(scr, hscrollbar_policy, vscrollbar_policy); + gtk_scrolled_window_set_shadow_type(scr, GTK_SHADOW_IN); + gtk_container_set_border_width(GTK_CONTAINER(scr), border); + return scr; +} + + diff --git a/tools/urt/libs/gtkutil/window.h b/tools/urt/libs/gtkutil/window.h new file mode 100644 index 00000000..263aca39 --- /dev/null +++ b/tools/urt/libs/gtkutil/window.h @@ -0,0 +1,154 @@ + +#if !defined(INCLUDED_GTKUTIL_WINDOW_H) +#define INCLUDED_GTKUTIL_WINDOW_H + +#include + +#include "debugging/debugging.h" +#include "generic/callback.h" +#include "widget.h" + +inline gboolean window_focus_in_clear_focus_widget(GtkWidget* widget, GdkEventKey* event, gpointer data) +{ + gtk_window_set_focus(GTK_WINDOW(widget), NULL); + return FALSE; +} + +inline guint window_connect_focus_in_clear_focus_widget(GtkWindow* window) +{ + return g_signal_connect(G_OBJECT(window), "focus_in_event", G_CALLBACK(window_focus_in_clear_focus_widget), NULL); +} + + +unsigned int connect_floating(GtkWindow* main_window, GtkWindow* floating); +GtkWindow* create_floating_window(const char* title, GtkWindow* parent); +void destroy_floating_window(GtkWindow* window); + +GtkWindow* create_persistent_floating_window(const char* title, GtkWindow* main_window); +gboolean persistent_floating_window_delete(GtkWindow* floating, GdkEvent *event, GtkWindow* main_window); + +void window_remove_minmax(GtkWindow* window); + +typedef struct _GtkScrolledWindow GtkScrolledWindow; +GtkScrolledWindow* create_scrolled_window(GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy, int border = 0); + + +struct WindowPosition +{ + int x, y, w, h; + + WindowPosition() + { + } + WindowPosition(int _x, int _y, int _w, int _h) + : x(_x), y(_y), w(_w), h(_h) + { + } +}; + +const WindowPosition c_default_window_pos(50, 25, 400, 300); + +inline void window_get_position(GtkWindow* window, WindowPosition& position) +{ + ASSERT_MESSAGE(window != 0, "error saving window position"); + + gtk_window_get_position(window, &position.x, &position.y); + gtk_window_get_size(window, &position.w, &position.h); +} + +inline void window_set_position(GtkWindow* window, const WindowPosition& position) +{ + gtk_window_set_gravity(window, GDK_GRAVITY_STATIC); + + GdkScreen* screen = gdk_screen_get_default(); + if(position.x < 0 + || position.y < 0 + || position.x > gdk_screen_get_width(screen) + || position.y > gdk_screen_get_height(screen)) + { + gtk_window_set_position(window, GTK_WIN_POS_CENTER_ON_PARENT); + } + else + { + gtk_window_move(window, position.x, position.y); + } + + gtk_window_set_default_size(window, position.w, position.h); +} + +inline void WindowPosition_Parse(WindowPosition& position, const char* value) +{ + if(sscanf(value, "%d %d %d %d", &position.x, &position.y, &position.w, &position.h) != 4) + { + position = WindowPosition(c_default_window_pos); // ensure sane default value for window position + } +} +typedef ReferenceCaller1 WindowPositionImportStringCaller; + +inline void WindowPosition_Write(const WindowPosition& position, const StringImportCallback& importCallback) +{ + char buffer[64]; + sprintf(buffer, "%d %d %d %d", position.x, position.y, position.w, position.h); + importCallback(buffer); +} +typedef ConstReferenceCaller1 WindowPositionExportStringCaller; + + + +class WindowPositionTracker +{ + WindowPosition m_position; + + static gboolean configure(GtkWidget* widget, GdkEventConfigure *event, WindowPositionTracker* self) + { + self->m_position = WindowPosition(event->x, event->y, event->width, event->height); + return FALSE; + } + +public: + WindowPositionTracker() + : m_position(c_default_window_pos) + { + } + + void sync(GtkWindow* window) + { + window_set_position(window, m_position); + } + + void connect(GtkWindow* window) + { + sync(window); + g_signal_connect(G_OBJECT(window), "configure_event", G_CALLBACK(configure), this); + } + + const WindowPosition& getPosition() const + { + return m_position; + } + + //hack + void setPosition(const WindowPosition& position) + { + m_position = position; + } +}; + + +inline void WindowPositionTracker_importString(WindowPositionTracker& self, const char* value) +{ + WindowPosition position; + WindowPosition_Parse(position, value); + self.setPosition(position); +} +typedef ReferenceCaller1 WindowPositionTrackerImportStringCaller; + +inline void WindowPositionTracker_exportString(const WindowPositionTracker& self, const StringImportCallback& importer) +{ + WindowPosition_Write(self.getPosition(), importer); +} +typedef ConstReferenceCaller1 WindowPositionTrackerExportStringCaller; + + + +#endif diff --git a/tools/urt/libs/gtkutil/xorrectangle.cpp b/tools/urt/libs/gtkutil/xorrectangle.cpp new file mode 100644 index 00000000..5d759416 --- /dev/null +++ b/tools/urt/libs/gtkutil/xorrectangle.cpp @@ -0,0 +1,3 @@ + +#include "xorrectangle.h" + diff --git a/tools/urt/libs/gtkutil/xorrectangle.h b/tools/urt/libs/gtkutil/xorrectangle.h new file mode 100644 index 00000000..58ad77f5 --- /dev/null +++ b/tools/urt/libs/gtkutil/xorrectangle.h @@ -0,0 +1,105 @@ + +#if !defined (INCLUDED_XORRECTANGLE_H) +#define INCLUDED_XORRECTANGLE_H + +#include +#include "math/vector.h" + +class rectangle_t +{ +public: + rectangle_t() + : x(0), y(0), w(0), h(0) + {} + rectangle_t(float _x, float _y, float _w, float _h) + : x(_x), y(_y), w(_w), h(_h) + {} + float x; + float y; + float w; + float h; +}; + +struct Coord2D +{ + float x, y; + Coord2D(float _x, float _y) + : x(_x), y(_y) + { + } +}; + +inline Coord2D coord2d_device2screen(const Coord2D& coord, unsigned int width, unsigned int height) +{ + return Coord2D(((coord.x + 1.0f) * 0.5f) * width, ((coord.y + 1.0f) * 0.5f) * height); +} + +inline rectangle_t rectangle_from_area(const float min[2], const float max[2], unsigned int width, unsigned int height) +{ + Coord2D botleft(coord2d_device2screen(Coord2D(min[0], min[1]), width, height)); + Coord2D topright(coord2d_device2screen(Coord2D(max[0], max[1]), width, height)); + return rectangle_t(botleft.x, botleft.y, topright.x - botleft.x, topright.y - botleft.y); +} + +class XORRectangle +{ + + rectangle_t m_rectangle; + + GtkWidget* m_widget; + GdkGC* m_gc; + + bool initialised() const + { + return m_gc != 0; + } + void lazy_init() + { + if(!initialised()) + { + m_gc = gdk_gc_new(m_widget->window); + + GdkColor color = { 0, 0xffff, 0xffff, 0xffff, }; + GdkColormap* colormap = gdk_window_get_colormap(m_widget->window); + gdk_colormap_alloc_color (colormap, &color, FALSE, TRUE); + gdk_gc_copy(m_gc, m_widget->style->white_gc); + gdk_gc_set_foreground(m_gc, &color); + gdk_gc_set_background(m_gc, &color); + + gdk_gc_set_function(m_gc, GDK_INVERT); + } + } + void draw() const + { + const int x = float_to_integer(m_rectangle.x); + const int y = float_to_integer(m_rectangle.y); + const int w = float_to_integer(m_rectangle.w); + const int h = float_to_integer(m_rectangle.h); + gdk_draw_rectangle(m_widget->window, m_gc, FALSE, x, -(h) - (y - m_widget->allocation.height), w, h); + } + +public: + XORRectangle(GtkWidget* widget) : m_widget(widget), m_gc(0) + { + } + ~XORRectangle() + { + if(initialised()) + { + gdk_gc_unref(m_gc); + } + } + void set(rectangle_t rectangle) + { + if(GTK_WIDGET_REALIZED(m_widget)) + { + lazy_init(); + draw(); + m_rectangle = rectangle; + draw(); + } + } +}; + + +#endif diff --git a/tools/urt/libs/imagelib.cpp b/tools/urt/libs/imagelib.cpp new file mode 100644 index 00000000..05181ce0 --- /dev/null +++ b/tools/urt/libs/imagelib.cpp @@ -0,0 +1,3 @@ + +#include "imagelib.h" + diff --git a/tools/urt/libs/imagelib.h b/tools/urt/libs/imagelib.h new file mode 100644 index 00000000..5822ce50 --- /dev/null +++ b/tools/urt/libs/imagelib.h @@ -0,0 +1,132 @@ + +#if !defined(INCLUDED_IMAGELIB_H) +#define INCLUDED_IMAGELIB_H + +#include "iimage.h" +#include "iarchive.h" +#include "idatastream.h" +#include + +struct RGBAPixel +{ + unsigned char red, green, blue, alpha; +}; + +class RGBAImage : public Image +{ + RGBAImage(const RGBAImage& other); + RGBAImage& operator=(const RGBAImage& other); +public: + RGBAPixel* pixels; + unsigned int width, height; + + RGBAImage(unsigned int _width, unsigned int _height) + : pixels(new RGBAPixel[_width * _height]), width(_width), height(_height) + { + } + ~RGBAImage() + { + delete pixels; + } + + void release() + { + delete this; + } + byte* getRGBAPixels() const + { + return reinterpret_cast(pixels); + } + unsigned int getWidth() const + { + return width; + } + unsigned int getHeight() const + { + return height; + } +}; + +class RGBAImageFlags : public RGBAImage +{ +public: + int m_surfaceFlags; + int m_contentFlags; + int m_value; + RGBAImageFlags(unsigned short _width, unsigned short _height, int surfaceFlags, short contentFlags, int value) : + RGBAImage(_width, _height), m_surfaceFlags(surfaceFlags), m_contentFlags(contentFlags), m_value(value) + { + } + + int getSurfaceFlags() const + { + return m_surfaceFlags; + } + int getContentFlags() const + { + return m_contentFlags; + } + int getValue() const + { + return m_value; + } +}; + + +inline InputStream::byte_type* ArchiveFile_loadBuffer(ArchiveFile& file, std::size_t& length) +{ + InputStream::byte_type* buffer = (InputStream::byte_type*)malloc(file.size() + 1); + length = file.getInputStream().read(buffer, file.size()); + buffer[file.size()] = 0; + return buffer; +} + +inline void ArchiveFile_freeBuffer(InputStream::byte_type* buffer) +{ + free(buffer); +} + +class ScopedArchiveBuffer +{ +public: + std::size_t length; + InputStream::byte_type* buffer; + + ScopedArchiveBuffer(ArchiveFile& file) + { + buffer = ArchiveFile_loadBuffer(file, length); + } + ~ScopedArchiveBuffer() + { + ArchiveFile_freeBuffer(buffer); + } +}; + +class PointerInputStream : public InputStream +{ + const byte* m_read; +public: + PointerInputStream(const byte* pointer) + : m_read(pointer) + { + } + std::size_t read(byte* buffer, std::size_t length) + { + const byte* end = m_read + length; + while(m_read != end) + { + *buffer++ = *m_read++; + } + return length; + } + void seek(std::size_t offset) + { + m_read += offset; + } + const byte* get() + { + return m_read; + } +}; + +#endif diff --git a/tools/urt/libs/instancelib.cpp b/tools/urt/libs/instancelib.cpp new file mode 100644 index 00000000..98878ceb --- /dev/null +++ b/tools/urt/libs/instancelib.cpp @@ -0,0 +1,2 @@ + +#include "instancelib.h" diff --git a/tools/urt/libs/instancelib.h b/tools/urt/libs/instancelib.h new file mode 100644 index 00000000..a8e46ac3 --- /dev/null +++ b/tools/urt/libs/instancelib.h @@ -0,0 +1,143 @@ + +#if !defined (INCLUDED_INSTANCELIB_H) +#define INCLUDED_INSTANCELIB_H + +#include "debugging/debugging.h" + +#include "iscenegraph.h" + +#include "scenelib.h" +#include "generic/reference.h" +#include "generic/callback.h" +#include + +class InstanceSubgraphWalker : public scene::Traversable::Walker +{ + scene::Instantiable::Observer* m_observer; + mutable scene::Path m_path; + mutable Stack m_parent; +public: + InstanceSubgraphWalker(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* parent) + : m_observer(observer), m_path(path), m_parent(parent) + { + } + bool pre(scene::Node& node) const + { + m_path.push(makeReference(node)); + scene::Instance* instance = Node_getInstantiable(node)->create(m_path, m_parent.top()); + m_observer->insert(instance); + Node_getInstantiable(node)->insert(m_observer, m_path, instance); + m_parent.push(instance); + return true; + } + void post(scene::Node& node) const + { + m_path.pop(); + m_parent.pop(); + } +}; + +class UninstanceSubgraphWalker : public scene::Traversable::Walker +{ + scene::Instantiable::Observer* m_observer; + mutable scene::Path m_path; +public: + UninstanceSubgraphWalker(scene::Instantiable::Observer* observer, const scene::Path& parent) + : m_observer(observer), m_path(parent) + { + } + bool pre(scene::Node& node) const + { + m_path.push(makeReference(node)); + return true; + } + void post(scene::Node& node) const + { + scene::Instance* instance = Node_getInstantiable(node)->erase(m_observer, m_path); + m_observer->erase(instance); + delete instance; + m_path.pop(); + } +}; + +class Instances : public scene::Traversable::Observer +{ + typedef std::pair CachePath; + + typedef CachePath key_type; + + typedef std::map InstanceMap; + InstanceMap m_instances; +public: + + typedef InstanceMap::iterator iterator; + + iterator begin() + { + return m_instances.begin(); + } + iterator end() + { + return m_instances.end(); + } + + // traverse observer + void insert(scene::Node& child) + { + for(iterator i = begin(); i != end(); ++i) + { + Node_traverseSubgraph(child, InstanceSubgraphWalker((*i).first.first, (*i).first.second, (*i).second)); + (*i).second->boundsChanged(); + } + } + void erase(scene::Node& child) + { + for(iterator i = begin(); i != end(); ++i) + { + Node_traverseSubgraph(child, UninstanceSubgraphWalker((*i).first.first, (*i).first.second)); + (*i).second->boundsChanged(); + } + } + + // instance + void forEachInstance(const scene::Instantiable::Visitor& visitor) + { + for(iterator i = begin(); i != end(); ++i) + { + visitor.visit(*(*i).second); + } + } + + void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance) + { + ASSERT_MESSAGE(m_instances.find(key_type(observer, PathConstReference(instance->path()))) == m_instances.end(), "Instances::insert - element already exists"); + m_instances.insert(InstanceMap::value_type(key_type(observer, PathConstReference(instance->path())), instance)); + } + scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path) + { + ASSERT_MESSAGE(m_instances.find(key_type(observer, PathConstReference(path))) != m_instances.end(), "Instances::erase - failed to find element"); + InstanceMap::iterator i = m_instances.find(key_type(observer, PathConstReference(path))); + scene::Instance* instance = i->second; + m_instances.erase(i); + return instance; + } + + void transformChanged() + { + for(InstanceMap::iterator i = m_instances.begin(); i != m_instances.end(); ++i) + { + (*i).second->transformChanged(); + } + } + typedef MemberCaller TransformChangedCaller; + void boundsChanged() + { + for(InstanceMap::iterator i = m_instances.begin(); i != m_instances.end(); ++i) + { + (*i).second->boundsChanged(); + } + } + typedef MemberCaller BoundsChangedCaller; +}; + +#endif diff --git a/tools/urt/libs/jpeg6/.cvsignore b/tools/urt/libs/jpeg6/.cvsignore new file mode 100644 index 00000000..face6ad3 --- /dev/null +++ b/tools/urt/libs/jpeg6/.cvsignore @@ -0,0 +1,8 @@ +Debug +Release +*.ncb +*.opt +*.plg +*.001 +*.BAK +.consign diff --git a/tools/urt/libs/jpeg6/.cvswrappers b/tools/urt/libs/jpeg6/.cvswrappers new file mode 100644 index 00000000..cdfd6d4a --- /dev/null +++ b/tools/urt/libs/jpeg6/.cvswrappers @@ -0,0 +1,3 @@ +*.dsp -m 'COPY' -k 'b' +*.dsw -m 'COPY' -k 'b' +*.scc -m 'COPY' -k 'b' diff --git a/tools/urt/libs/jpeg6/jchuff.h b/tools/urt/libs/jpeg6/jchuff.h new file mode 100644 index 00000000..7ae05e85 --- /dev/null +++ b/tools/urt/libs/jpeg6/jchuff.h @@ -0,0 +1,68 @@ +/* + + * jchuff.h + + * + + * Copyright (C) 1991-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains declarations for Huffman entropy encoding routines + + * that are shared between the sequential encoder (jchuff.c) and the + + * progressive encoder (jcphuff.c). No other modules need to see these. + + */ + + + +/* Derived data constructed for each Huffman table */ + + + +typedef struct { + + unsigned int ehufco[256]; /* code for each symbol */ + + char ehufsi[256]; /* length of code for each symbol */ + + /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ + +} c_derived_tbl; + + + +/* Short forms of external names for systems with brain-damaged linkers. */ + + + +#ifdef NEED_SHORT_EXTERNAL_NAMES + +#define jpeg_make_c_derived_tbl jMkCDerived + +#define jpeg_gen_optimal_table jGenOptTbl + +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + + +/* Expand a Huffman table definition into the derived format */ + +EXTERN void jpeg_make_c_derived_tbl JPP((j_compress_ptr cinfo, + + JHUFF_TBL * htbl, c_derived_tbl ** pdtbl)); + + + +/* Generate an optimal table definition given the specified counts */ + +EXTERN void jpeg_gen_optimal_table JPP((j_compress_ptr cinfo, + + JHUFF_TBL * htbl, long freq[])); + diff --git a/tools/urt/libs/jpeg6/jcomapi.cpp b/tools/urt/libs/jpeg6/jcomapi.cpp new file mode 100644 index 00000000..ebf2d791 --- /dev/null +++ b/tools/urt/libs/jpeg6/jcomapi.cpp @@ -0,0 +1,188 @@ +/* + + * jcomapi.c + + * + + * Copyright (C) 1994, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains application interface routines that are used for both + + * compression and decompression. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* + + * Abort processing of a JPEG compression or decompression operation, + + * but don't destroy the object itself. + + * + + * For this, we merely clean up all the nonpermanent memory pools. + + * Note that temp files (virtual arrays) are not allowed to belong to + + * the permanent pool, so we will be able to close all temp files here. + + * Closing a data source or destination, if necessary, is the application's + + * responsibility. + + */ + + + +GLOBAL void + +jpeg_abort (j_common_ptr cinfo) + +{ + + int pool; + + + + /* Releasing pools in reverse order might help avoid fragmentation + + * with some (brain-damaged) malloc libraries. + + */ + + for (pool = JPOOL_NUMPOOLS-1; pool > JPOOL_PERMANENT; pool--) { + + (*cinfo->mem->free_pool) (cinfo, pool); + + } + + + + /* Reset overall state for possible reuse of object */ + + cinfo->global_state = (cinfo->is_decompressor ? DSTATE_START : CSTATE_START); + +} + + + + + +/* + + * Destruction of a JPEG object. + + * + + * Everything gets deallocated except the master jpeg_compress_struct itself + + * and the error manager struct. Both of these are supplied by the application + + * and must be freed, if necessary, by the application. (Often they are on + + * the stack and so don't need to be freed anyway.) + + * Closing a data source or destination, if necessary, is the application's + + * responsibility. + + */ + + + +GLOBAL void + +jpeg_destroy (j_common_ptr cinfo) + +{ + + /* We need only tell the memory manager to release everything. */ + + /* NB: mem pointer is NULL if memory mgr failed to initialize. */ + + if (cinfo->mem != NULL) + + (*cinfo->mem->self_destruct) (cinfo); + + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + + cinfo->global_state = 0; /* mark it destroyed */ + +} + + + + + +/* + + * Convenience routines for allocating quantization and Huffman tables. + + * (Would jutils.c be a more reasonable place to put these?) + + */ + + + +GLOBAL JQUANT_TBL * + +jpeg_alloc_quant_table (j_common_ptr cinfo) + +{ + + JQUANT_TBL *tbl; + + + + tbl = (JQUANT_TBL *) + + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JQUANT_TBL)); + + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + + return tbl; + +} + + + + + +GLOBAL JHUFF_TBL * + +jpeg_alloc_huff_table (j_common_ptr cinfo) + +{ + + JHUFF_TBL *tbl; + + + + tbl = (JHUFF_TBL *) + + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JHUFF_TBL)); + + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + + return tbl; + +} + diff --git a/tools/urt/libs/jpeg6/jconfig.h b/tools/urt/libs/jpeg6/jconfig.h new file mode 100644 index 00000000..c727519b --- /dev/null +++ b/tools/urt/libs/jpeg6/jconfig.h @@ -0,0 +1,82 @@ +/* jconfig.wat --- jconfig.h for Watcom C/C++ on MS-DOS or OS/2. */ + +/* see jconfig.doc for explanations */ + + + +#define HAVE_PROTOTYPES + +#define HAVE_UNSIGNED_CHAR + +#define HAVE_UNSIGNED_SHORT + +/* #define void char */ + +/* #define const */ + +#define CHAR_IS_UNSIGNED + +#define HAVE_STDDEF_H + +#define HAVE_STDLIB_H + +#undef NEED_BSD_STRINGS + +#undef NEED_SYS_TYPES_H + +#undef NEED_FAR_POINTERS /* Watcom uses flat 32-bit addressing */ + +#undef NEED_SHORT_EXTERNAL_NAMES + +#undef INCOMPLETE_TYPES_BROKEN + + + +#define JDCT_DEFAULT JDCT_FLOAT + +#define JDCT_FASTEST JDCT_FLOAT + + + +#ifdef JPEG_INTERNALS + + + +#undef RIGHT_SHIFT_IS_UNSIGNED + + + +#endif /* JPEG_INTERNALS */ + + + +#ifdef JPEG_CJPEG_DJPEG + + + +#define BMP_SUPPORTED /* BMP image file format */ + +#define GIF_SUPPORTED /* GIF image file format */ + +#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ + +#undef RLE_SUPPORTED /* Utah RLE image file format */ + +#define TARGA_SUPPORTED /* Targa image file format */ + + + +#undef TWO_FILE_COMMANDLINE /* optional */ + +#define USE_SETMODE /* Needed to make one-file style work in Watcom */ + +#undef NEED_SIGNAL_CATCHER /* Define this if you use jmemname.c */ + +#undef DONT_USE_B_MODE + +#undef PROGRESS_REPORT /* optional */ + + + +#endif /* JPEG_CJPEG_DJPEG */ + diff --git a/tools/urt/libs/jpeg6/jdapimin.cpp b/tools/urt/libs/jpeg6/jdapimin.cpp new file mode 100644 index 00000000..9086b490 --- /dev/null +++ b/tools/urt/libs/jpeg6/jdapimin.cpp @@ -0,0 +1,800 @@ +/* + + * jdapimin.c + + * + + * Copyright (C) 1994-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains application interface code for the decompression half + + * of the JPEG library. These are the "minimum" API routines that may be + + * needed in either the normal full-decompression case or the + + * transcoding-only case. + + * + + * Most of the routines intended to be called directly by an application + + * are in this file or in jdapistd.c. But also see jcomapi.c for routines + + * shared by compression and decompression, and jdtrans.c for the transcoding + + * case. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* + + * Initialization of a JPEG decompression object. + + * The error manager must already be set up (in case memory manager fails). + + */ + + + +GLOBAL void + +jpeg_create_decompress (j_decompress_ptr cinfo) + +{ + + int i; + + + + /* For debugging purposes, zero the whole master structure. + + * But error manager pointer is already there, so save and restore it. + + */ + + { + + struct jpeg_error_mgr * err = cinfo->err; + + i = sizeof(struct jpeg_decompress_struct); + + i = SIZEOF(struct jpeg_decompress_struct); + + MEMZERO(cinfo, SIZEOF(struct jpeg_decompress_struct)); + + cinfo->err = err; + + } + + cinfo->is_decompressor = TRUE; + + + + /* Initialize a memory manager instance for this object */ + + jinit_memory_mgr((j_common_ptr) cinfo); + + + + /* Zero out pointers to permanent structures. */ + + cinfo->progress = NULL; + + cinfo->src = NULL; + + + + for (i = 0; i < NUM_QUANT_TBLS; i++) + + cinfo->quant_tbl_ptrs[i] = NULL; + + + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + + cinfo->dc_huff_tbl_ptrs[i] = NULL; + + cinfo->ac_huff_tbl_ptrs[i] = NULL; + + } + + + + /* Initialize marker processor so application can override methods + + * for COM, APPn markers before calling jpeg_read_header. + + */ + + jinit_marker_reader(cinfo); + + + + /* And initialize the overall input controller. */ + + jinit_input_controller(cinfo); + + + + /* OK, I'm ready */ + + cinfo->global_state = DSTATE_START; + +} + + + + + +/* + + * Destruction of a JPEG decompression object + + */ + + + +GLOBAL void + +jpeg_destroy_decompress (j_decompress_ptr cinfo) + +{ + + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ + +} + + + + + +/* + + * Abort processing of a JPEG decompression operation, + + * but don't destroy the object itself. + + */ + + + +GLOBAL void + +jpeg_abort_decompress (j_decompress_ptr cinfo) + +{ + + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ + +} + + + + + +/* + + * Install a special processing method for COM or APPn markers. + + */ + + + +GLOBAL void + +jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code, + + jpeg_marker_parser_method routine) + +{ + + if (marker_code == JPEG_COM) + + cinfo->marker->process_COM = routine; + + else if (marker_code >= JPEG_APP0 && marker_code <= JPEG_APP0+15) + + cinfo->marker->process_APPn[marker_code-JPEG_APP0] = routine; + + else + + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); + +} + + + + + +/* + + * Set default decompression parameters. + + */ + + + +LOCAL void + +default_decompress_parms (j_decompress_ptr cinfo) + +{ + + /* Guess the input colorspace, and set output colorspace accordingly. */ + + /* (Wish JPEG committee had provided a real way to specify this...) */ + + /* Note application may override our guesses. */ + + switch (cinfo->num_components) { + + case 1: + + cinfo->jpeg_color_space = JCS_GRAYSCALE; + + cinfo->out_color_space = JCS_GRAYSCALE; + + break; + + + + case 3: + + if (cinfo->saw_JFIF_marker) { + + cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */ + + } else if (cinfo->saw_Adobe_marker) { + + switch (cinfo->Adobe_transform) { + + case 0: + + cinfo->jpeg_color_space = JCS_RGB; + + break; + + case 1: + + cinfo->jpeg_color_space = JCS_YCbCr; + + break; + + default: + + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + + break; + + } + + } else { + + /* Saw no special markers, try to guess from the component IDs */ + + int cid0 = cinfo->comp_info[0].component_id; + + int cid1 = cinfo->comp_info[1].component_id; + + int cid2 = cinfo->comp_info[2].component_id; + + + + if (cid0 == 1 && cid1 == 2 && cid2 == 3) + + cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ + + else if (cid0 == 82 && cid1 == 71 && cid2 == 66) + + cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ + + else { + + TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2); + + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + + } + + } + + /* Always guess RGB is proper output colorspace. */ + + cinfo->out_color_space = JCS_RGB; + + break; + + + + case 4: + + if (cinfo->saw_Adobe_marker) { + + switch (cinfo->Adobe_transform) { + + case 0: + + cinfo->jpeg_color_space = JCS_CMYK; + + break; + + case 2: + + cinfo->jpeg_color_space = JCS_YCCK; + + break; + + default: + + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + + cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ + + break; + + } + + } else { + + /* No special markers, assume straight CMYK. */ + + cinfo->jpeg_color_space = JCS_CMYK; + + } + + cinfo->out_color_space = JCS_CMYK; + + break; + + + + default: + + cinfo->jpeg_color_space = JCS_UNKNOWN; + + cinfo->out_color_space = JCS_UNKNOWN; + + break; + + } + + + + /* Set defaults for other decompression parameters. */ + + cinfo->scale_num = 1; /* 1:1 scaling */ + + cinfo->scale_denom = 1; + + cinfo->output_gamma = 1.0; + + cinfo->buffered_image = FALSE; + + cinfo->raw_data_out = FALSE; + + cinfo->dct_method = JDCT_DEFAULT; + + cinfo->do_fancy_upsampling = TRUE; + + cinfo->do_block_smoothing = TRUE; + + cinfo->quantize_colors = FALSE; + + /* We set these in case application only sets quantize_colors. */ + + cinfo->dither_mode = JDITHER_FS; + +#ifdef QUANT_2PASS_SUPPORTED + + cinfo->two_pass_quantize = TRUE; + +#else + + cinfo->two_pass_quantize = FALSE; + +#endif + + cinfo->desired_number_of_colors = 256; + + cinfo->colormap = NULL; + + /* Initialize for no mode change in buffered-image mode. */ + + cinfo->enable_1pass_quant = FALSE; + + cinfo->enable_external_quant = FALSE; + + cinfo->enable_2pass_quant = FALSE; + +} + + + + + +/* + + * Decompression startup: read start of JPEG datastream to see what's there. + + * Need only initialize JPEG object and supply a data source before calling. + + * + + * This routine will read as far as the first SOS marker (ie, actual start of + + * compressed data), and will save all tables and parameters in the JPEG + + * object. It will also initialize the decompression parameters to default + + * values, and finally return JPEG_HEADER_OK. On return, the application may + + * adjust the decompression parameters and then call jpeg_start_decompress. + + * (Or, if the application only wanted to determine the image parameters, + + * the data need not be decompressed. In that case, call jpeg_abort or + + * jpeg_destroy to release any temporary space.) + + * If an abbreviated (tables only) datastream is presented, the routine will + + * return JPEG_HEADER_TABLES_ONLY upon reaching EOI. The application may then + + * re-use the JPEG object to read the abbreviated image datastream(s). + + * It is unnecessary (but OK) to call jpeg_abort in this case. + + * The JPEG_SUSPENDED return code only occurs if the data source module + + * requests suspension of the decompressor. In this case the application + + * should load more source data and then re-call jpeg_read_header to resume + + * processing. + + * If a non-suspending data source is used and require_image is TRUE, then the + + * return code need not be inspected since only JPEG_HEADER_OK is possible. + + * + + * This routine is now just a front end to jpeg_consume_input, with some + + * extra error checking. + + */ + + + +GLOBAL int + +jpeg_read_header (j_decompress_ptr cinfo, boolean require_image) + +{ + + int retcode; + + + + if (cinfo->global_state != DSTATE_START && + + cinfo->global_state != DSTATE_INHEADER) + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + + + retcode = jpeg_consume_input(cinfo); + + + + switch (retcode) { + + case JPEG_REACHED_SOS: + + retcode = JPEG_HEADER_OK; + + break; + + case JPEG_REACHED_EOI: + + if (require_image) /* Complain if application wanted an image */ + + ERREXIT(cinfo, JERR_NO_IMAGE); + + /* Reset to start state; it would be safer to require the application to + + * call jpeg_abort, but we can't change it now for compatibility reasons. + + * A side effect is to free any temporary memory (there shouldn't be any). + + */ + + jpeg_abort((j_common_ptr) cinfo); /* sets state = DSTATE_START */ + + retcode = JPEG_HEADER_TABLES_ONLY; + + break; + + case JPEG_SUSPENDED: + + /* no work */ + + break; + + } + + + + return retcode; + +} + + + + + +/* + + * Consume data in advance of what the decompressor requires. + + * This can be called at any time once the decompressor object has + + * been created and a data source has been set up. + + * + + * This routine is essentially a state machine that handles a couple + + * of critical state-transition actions, namely initial setup and + + * transition from header scanning to ready-for-start_decompress. + + * All the actual input is done via the input controller's consume_input + + * method. + + */ + + + +GLOBAL int + +jpeg_consume_input (j_decompress_ptr cinfo) + +{ + + int retcode = JPEG_SUSPENDED; + + + + /* NB: every possible DSTATE value should be listed in this switch */ + + switch (cinfo->global_state) { + + case DSTATE_START: + + /* Start-of-datastream actions: reset appropriate modules */ + + (*cinfo->inputctl->reset_input_controller) (cinfo); + + /* Initialize application's data source module */ + + (*cinfo->src->init_source) (cinfo); + + cinfo->global_state = DSTATE_INHEADER; + + /*FALLTHROUGH*/ + + case DSTATE_INHEADER: + + retcode = (*cinfo->inputctl->consume_input) (cinfo); + + if (retcode == JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */ + + /* Set up default parameters based on header data */ + + default_decompress_parms(cinfo); + + /* Set global state: ready for start_decompress */ + + cinfo->global_state = DSTATE_READY; + + } + + break; + + case DSTATE_READY: + + /* Can't advance past first SOS until start_decompress is called */ + + retcode = JPEG_REACHED_SOS; + + break; + + case DSTATE_PRELOAD: + + case DSTATE_PRESCAN: + + case DSTATE_SCANNING: + + case DSTATE_RAW_OK: + + case DSTATE_BUFIMAGE: + + case DSTATE_BUFPOST: + + case DSTATE_STOPPING: + + retcode = (*cinfo->inputctl->consume_input) (cinfo); + + break; + + default: + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + } + + return retcode; + +} + + + + + +/* + + * Have we finished reading the input file? + + */ + + + +GLOBAL boolean + +jpeg_input_complete (j_decompress_ptr cinfo) + +{ + + /* Check for valid jpeg object */ + + if (cinfo->global_state < DSTATE_START || + + cinfo->global_state > DSTATE_STOPPING) + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + return cinfo->inputctl->eoi_reached; + +} + + + + + +/* + + * Is there more than one scan? + + */ + + + +GLOBAL boolean + +jpeg_has_multiple_scans (j_decompress_ptr cinfo) + +{ + + /* Only valid after jpeg_read_header completes */ + + if (cinfo->global_state < DSTATE_READY || + + cinfo->global_state > DSTATE_STOPPING) + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + return cinfo->inputctl->has_multiple_scans; + +} + + + + + +/* + + * Finish JPEG decompression. + + * + + * This will normally just verify the file trailer and release temp storage. + + * + + * Returns FALSE if suspended. The return value need be inspected only if + + * a suspending data source is used. + + */ + + + +GLOBAL boolean + +jpeg_finish_decompress (j_decompress_ptr cinfo) + +{ + + if ((cinfo->global_state == DSTATE_SCANNING || + + cinfo->global_state == DSTATE_RAW_OK) && ! cinfo->buffered_image) { + + /* Terminate final pass of non-buffered mode */ + + if (cinfo->output_scanline < cinfo->output_height) + + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + + (*cinfo->master->finish_output_pass) (cinfo); + + cinfo->global_state = DSTATE_STOPPING; + + } else if (cinfo->global_state == DSTATE_BUFIMAGE) { + + /* Finishing after a buffered-image operation */ + + cinfo->global_state = DSTATE_STOPPING; + + } else if (cinfo->global_state != DSTATE_STOPPING) { + + /* STOPPING = repeat call after a suspension, anything else is error */ + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + } + + /* Read until EOI */ + + while (! cinfo->inputctl->eoi_reached) { + + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + + return FALSE; /* Suspend, come back later */ + + } + + /* Do final cleanup */ + + (*cinfo->src->term_source) (cinfo); + + /* We can use jpeg_abort to release memory and reset global_state */ + + jpeg_abort((j_common_ptr) cinfo); + + return TRUE; + +} + diff --git a/tools/urt/libs/jpeg6/jdapistd.cpp b/tools/urt/libs/jpeg6/jdapistd.cpp new file mode 100644 index 00000000..0718fb3b --- /dev/null +++ b/tools/urt/libs/jpeg6/jdapistd.cpp @@ -0,0 +1,550 @@ +/* + + * jdapistd.c + + * + + * Copyright (C) 1994-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains application interface code for the decompression half + + * of the JPEG library. These are the "standard" API routines that are + + * used in the normal full-decompression case. They are not used by a + + * transcoding-only application. Note that if an application links in + + * jpeg_start_decompress, it will end up linking in the entire decompressor. + + * We thus must separate this file from jdapimin.c to avoid linking the + + * whole decompression library into a transcoder. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* Forward declarations */ + +LOCAL boolean output_pass_setup JPP((j_decompress_ptr cinfo)); + + + + + +/* + + * Decompression initialization. + + * jpeg_read_header must be completed before calling this. + + * + + * If a multipass operating mode was selected, this will do all but the + + * last pass, and thus may take a great deal of time. + + * + + * Returns FALSE if suspended. The return value need be inspected only if + + * a suspending data source is used. + + */ + + + +GLOBAL boolean + +jpeg_start_decompress (j_decompress_ptr cinfo) + +{ + + if (cinfo->global_state == DSTATE_READY) { + + /* First call: initialize master control, select active modules */ + + jinit_master_decompress(cinfo); + + if (cinfo->buffered_image) { + + /* No more work here; expecting jpeg_start_output next */ + + cinfo->global_state = DSTATE_BUFIMAGE; + + return TRUE; + + } + + cinfo->global_state = DSTATE_PRELOAD; + + } + + if (cinfo->global_state == DSTATE_PRELOAD) { + + /* If file has multiple scans, absorb them all into the coef buffer */ + + if (cinfo->inputctl->has_multiple_scans) { + +#ifdef D_MULTISCAN_FILES_SUPPORTED + + for (;;) { + + int retcode; + + /* Call progress monitor hook if present */ + + if (cinfo->progress != NULL) + + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + + /* Absorb some more input */ + + retcode = (*cinfo->inputctl->consume_input) (cinfo); + + if (retcode == JPEG_SUSPENDED) + + return FALSE; + + if (retcode == JPEG_REACHED_EOI) + + break; + + /* Advance progress counter if appropriate */ + + if (cinfo->progress != NULL && + + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + + /* jdmaster underestimated number of scans; ratchet up one scan */ + + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + + } + + } + + } + +#else + + ERREXIT(cinfo, JERR_NOT_COMPILED); + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + } + + cinfo->output_scan_number = cinfo->input_scan_number; + + } else if (cinfo->global_state != DSTATE_PRESCAN) + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Perform any dummy output passes, and set up for the final pass */ + + return output_pass_setup(cinfo); + +} + + + + + +/* + + * Set up for an output pass, and perform any dummy pass(es) needed. + + * Common subroutine for jpeg_start_decompress and jpeg_start_output. + + * Entry: global_state = DSTATE_PRESCAN only if previously suspended. + + * Exit: If done, returns TRUE and sets global_state for proper output mode. + + * If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN. + + */ + + + +LOCAL boolean + +output_pass_setup (j_decompress_ptr cinfo) + +{ + + if (cinfo->global_state != DSTATE_PRESCAN) { + + /* First call: do pass setup */ + + (*cinfo->master->prepare_for_output_pass) (cinfo); + + cinfo->output_scanline = 0; + + cinfo->global_state = DSTATE_PRESCAN; + + } + + /* Loop over any required dummy passes */ + + while (cinfo->master->is_dummy_pass) { + +#ifdef QUANT_2PASS_SUPPORTED + + /* Crank through the dummy pass */ + + while (cinfo->output_scanline < cinfo->output_height) { + + JDIMENSION last_scanline; + + /* Call progress monitor hook if present */ + + if (cinfo->progress != NULL) { + + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + + cinfo->progress->pass_limit = (long) cinfo->output_height; + + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + + } + + /* Process some data */ + + last_scanline = cinfo->output_scanline; + + (*cinfo->main->process_data) (cinfo, (JSAMPARRAY) NULL, + + &cinfo->output_scanline, (JDIMENSION) 0); + + if (cinfo->output_scanline == last_scanline) + + return FALSE; /* No progress made, must suspend */ + + } + + /* Finish up dummy pass, and set up for another one */ + + (*cinfo->master->finish_output_pass) (cinfo); + + (*cinfo->master->prepare_for_output_pass) (cinfo); + + cinfo->output_scanline = 0; + +#else + + ERREXIT(cinfo, JERR_NOT_COMPILED); + +#endif /* QUANT_2PASS_SUPPORTED */ + + } + + /* Ready for application to drive output pass through + + * jpeg_read_scanlines or jpeg_read_raw_data. + + */ + + cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING; + + return TRUE; + +} + + + + + +/* + + * Read some scanlines of data from the JPEG decompressor. + + * + + * The return value will be the number of lines actually read. + + * This may be less than the number requested in several cases, + + * including bottom of image, data source suspension, and operating + + * modes that emit multiple scanlines at a time. + + * + + * Note: we warn about excess calls to jpeg_read_scanlines() since + + * this likely signals an application programmer error. However, + + * an oversize buffer (max_lines > scanlines remaining) is not an error. + + */ + + + +GLOBAL JDIMENSION + +jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, + + JDIMENSION max_lines) + +{ + + JDIMENSION row_ctr; + + + + if (cinfo->global_state != DSTATE_SCANNING) + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (cinfo->output_scanline >= cinfo->output_height) { + + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + + return 0; + + } + + + + /* Call progress monitor hook if present */ + + if (cinfo->progress != NULL) { + + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + + cinfo->progress->pass_limit = (long) cinfo->output_height; + + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + + } + + + + /* Process some data */ + + row_ctr = 0; + + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines); + + cinfo->output_scanline += row_ctr; + + return row_ctr; + +} + + + + + +/* + + * Alternate entry point to read raw data. + + * Processes exactly one iMCU row per call, unless suspended. + + */ + + + +GLOBAL JDIMENSION + +jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data, + + JDIMENSION max_lines) + +{ + + JDIMENSION lines_per_iMCU_row; + + + + if (cinfo->global_state != DSTATE_RAW_OK) + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (cinfo->output_scanline >= cinfo->output_height) { + + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + + return 0; + + } + + + + /* Call progress monitor hook if present */ + + if (cinfo->progress != NULL) { + + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + + cinfo->progress->pass_limit = (long) cinfo->output_height; + + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + + } + + + + /* Verify that at least one iMCU row can be returned. */ + + lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size; + + if (max_lines < lines_per_iMCU_row) + + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + + + /* Decompress directly into user's buffer. */ + + if (! (*cinfo->coef->decompress_data) (cinfo, data)) + + return 0; /* suspension forced, can do nothing more */ + + + + /* OK, we processed one iMCU row. */ + + cinfo->output_scanline += lines_per_iMCU_row; + + return lines_per_iMCU_row; + +} + + + + + +/* Additional entry points for buffered-image mode. */ + + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + + + +/* + + * Initialize for an output pass in buffered-image mode. + + */ + + + +GLOBAL boolean + +jpeg_start_output (j_decompress_ptr cinfo, int scan_number) + +{ + + if (cinfo->global_state != DSTATE_BUFIMAGE && + + cinfo->global_state != DSTATE_PRESCAN) + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Limit scan number to valid range */ + + if (scan_number <= 0) + + scan_number = 1; + + if (cinfo->inputctl->eoi_reached && + + scan_number > cinfo->input_scan_number) + + scan_number = cinfo->input_scan_number; + + cinfo->output_scan_number = scan_number; + + /* Perform any dummy output passes, and set up for the real pass */ + + return output_pass_setup(cinfo); + +} + + + + + +/* + + * Finish up after an output pass in buffered-image mode. + + * + + * Returns FALSE if suspended. The return value need be inspected only if + + * a suspending data source is used. + + */ + + + +GLOBAL boolean + +jpeg_finish_output (j_decompress_ptr cinfo) + +{ + + if ((cinfo->global_state == DSTATE_SCANNING || + + cinfo->global_state == DSTATE_RAW_OK) && cinfo->buffered_image) { + + /* Terminate this pass. */ + + /* We do not require the whole pass to have been completed. */ + + (*cinfo->master->finish_output_pass) (cinfo); + + cinfo->global_state = DSTATE_BUFPOST; + + } else if (cinfo->global_state != DSTATE_BUFPOST) { + + /* BUFPOST = repeat call after a suspension, anything else is error */ + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + } + + /* Read markers looking for SOS or EOI */ + + while (cinfo->input_scan_number <= cinfo->output_scan_number && + + ! cinfo->inputctl->eoi_reached) { + + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + + return FALSE; /* Suspend, come back later */ + + } + + cinfo->global_state = DSTATE_BUFIMAGE; + + return TRUE; + +} + + + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + diff --git a/tools/urt/libs/jpeg6/jdatasrc.cpp b/tools/urt/libs/jpeg6/jdatasrc.cpp new file mode 100644 index 00000000..ae95d214 --- /dev/null +++ b/tools/urt/libs/jpeg6/jdatasrc.cpp @@ -0,0 +1,215 @@ +/* + * jdatasrc.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains decompression data source routines for the case of + * reading JPEG data from a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * source manager. + * IMPORTANT: we assume that fread() will correctly transcribe an array of + * JOCTETs from 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jerror.h" + +//extern int leo_buf_size; // FIXME ? merged in from Alpha - replaced by my_source_mgr->src_size + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + int src_size; // FIXME ? merged from Alpha + unsigned char *infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +METHODDEF void +init_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +METHODDEF boolean +// FIXME ? merged in from Alpha +fill_input_buffer (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + size_t nbytes; + + if (src->src_size > INPUT_BUF_SIZE) + nbytes = INPUT_BUF_SIZE; + else + nbytes = src->src_size; + + memcpy (src->buffer, src->infile, nbytes); + + src->infile += nbytes; + src->src_size -= nbytes; + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = nbytes; + src->start_of_file = FALSE; + + return TRUE; +} + + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF void +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->pub.bytes_in_buffer) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) fill_input_buffer(cinfo); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ + +GLOBAL void +jpeg_stdio_src (j_decompress_ptr cinfo, unsigned char *infile, int bufsize) +{ + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_source_mgr)); + src = (my_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * SIZEOF(JOCTET)); + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->src_size = bufsize; // FIXME ? merged from Alpha + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} + diff --git a/tools/urt/libs/jpeg6/jdcoefct.cpp b/tools/urt/libs/jpeg6/jdcoefct.cpp new file mode 100644 index 00000000..f9a1f7ec --- /dev/null +++ b/tools/urt/libs/jpeg6/jdcoefct.cpp @@ -0,0 +1,1450 @@ +/* + + * jdcoefct.c + + * + + * Copyright (C) 1994-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains the coefficient buffer controller for decompression. + + * This controller is the top level of the JPEG decompressor proper. + + * The coefficient buffer lies between entropy decoding and inverse-DCT steps. + + * + + * In buffered-image mode, this controller is the interface between + + * input-oriented processing and output-oriented processing. + + * Also, the input side (only) is used when reading a file for transcoding. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + +/* Block smoothing is only applicable for progressive JPEG, so: */ + +#ifndef D_PROGRESSIVE_SUPPORTED + +#undef BLOCK_SMOOTHING_SUPPORTED + +#endif + + + +/* Private buffer controller object */ + + + +typedef struct { + + struct jpeg_d_coef_controller pub; /* public fields */ + + + + /* These variables keep track of the current location of the input side. */ + + /* cinfo->input_iMCU_row is also used for this. */ + + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + + + /* The output side's location is represented by cinfo->output_iMCU_row. */ + + + + /* In single-pass modes, it's sufficient to buffer just one MCU. + + * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks, + + * and let the entropy decoder write into that workspace each time. + + * (On 80x86, the workspace is FAR even though it's not really very big; + + * this is to keep the module interfaces unchanged when a large coefficient + + * buffer is necessary.) + + * In multi-pass modes, this array points to the current MCU's blocks + + * within the virtual arrays; it is used only by the input side. + + */ + + JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU]; + + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + + /* In multi-pass modes, we need a virtual block array for each component. */ + + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; + +#endif + + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + + /* When doing block smoothing, we latch coefficient Al values here */ + + int * coef_bits_latch; + +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ + +#endif + +} my_coef_controller; + + + +typedef my_coef_controller * my_coef_ptr; + + + +/* Forward declarations */ + +METHODDEF int decompress_onepass + + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +METHODDEF int decompress_data + + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); + +#endif + +#ifdef BLOCK_SMOOTHING_SUPPORTED + +LOCAL boolean smoothing_ok JPP((j_decompress_ptr cinfo)); + +METHODDEF int decompress_smooth_data + + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); + +#endif + + + + + +LOCAL void + +start_iMCU_row (j_decompress_ptr cinfo) + +/* Reset within-iMCU-row counters for a new row (input side) */ + +{ + + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + + * But at the bottom of the image, process only what's left. + + */ + + if (cinfo->comps_in_scan > 1) { + + coef->MCU_rows_per_iMCU_row = 1; + + } else { + + if (cinfo->input_iMCU_row < (cinfo->total_iMCU_rows-1)) + + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + + else + + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + + } + + + + coef->MCU_ctr = 0; + + coef->MCU_vert_offset = 0; + +} + + + + + +/* + + * Initialize for an input processing pass. + + */ + + + +METHODDEF void + +start_input_pass (j_decompress_ptr cinfo) + +{ + + cinfo->input_iMCU_row = 0; + + start_iMCU_row(cinfo); + +} + + + + + +/* + + * Initialize for an output processing pass. + + */ + + + +METHODDEF void + +start_output_pass (j_decompress_ptr cinfo) + +{ + +#ifdef BLOCK_SMOOTHING_SUPPORTED + + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + + + /* If multipass, check to see whether to use block smoothing on this pass */ + + if (coef->pub.coef_arrays != NULL) { + + if (cinfo->do_block_smoothing && smoothing_ok(cinfo)) + + coef->pub.decompress_data = decompress_smooth_data; + + else + + coef->pub.decompress_data = decompress_data; + + } + +#endif + + cinfo->output_iMCU_row = 0; + +} + + + + + +/* + + * Decompress and return some data in the single-pass case. + + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + + * Input and output must run in lockstep since we have only a one-MCU buffer. + + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + + * + + * NB: output_buf contains a plane for each component in image. + + * For single pass, this is the same as the components in the scan. + + */ + + + +METHODDEF int + +decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) + +{ + + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + JDIMENSION MCU_col_num; /* index of current MCU within row */ + + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + + int blkn, ci, xindex, yindex, yoffset, useful_width; + + JSAMPARRAY output_ptr; + + JDIMENSION start_col, output_col; + + jpeg_component_info *compptr; + + inverse_DCT_method_ptr inverse_DCT; + + + + /* Loop to process as much as one whole iMCU row */ + + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + + yoffset++) { + + for (MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; + + MCU_col_num++) { + + /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ + + jzero_far((void FAR *) coef->MCU_buffer[0], + + (size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK))); + + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + + /* Suspension forced; update state counters and exit */ + + coef->MCU_vert_offset = yoffset; + + coef->MCU_ctr = MCU_col_num; + + return JPEG_SUSPENDED; + + } + + /* Determine where data should go in output_buf and do the IDCT thing. + + * We skip dummy blocks at the right and bottom edges (but blkn gets + + * incremented past them!). Note the inner loop relies on having + + * allocated the MCU_buffer[] blocks sequentially. + + */ + + blkn = 0; /* index of current DCT block within MCU */ + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + + compptr = cinfo->cur_comp_info[ci]; + + /* Don't bother to IDCT an uninteresting component. */ + + if (! compptr->component_needed) { + + blkn += compptr->MCU_blocks; + + continue; + + } + + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + + useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + + : compptr->last_col_width; + + output_ptr = output_buf[ci] + yoffset * compptr->DCT_scaled_size; + + start_col = MCU_col_num * compptr->MCU_sample_width; + + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + + if (cinfo->input_iMCU_row < last_iMCU_row || + + yoffset+yindex < compptr->last_row_height) { + + output_col = start_col; + + for (xindex = 0; xindex < useful_width; xindex++) { + + (*inverse_DCT) (cinfo, compptr, + + (JCOEFPTR) coef->MCU_buffer[blkn+xindex], + + output_ptr, output_col); + + output_col += compptr->DCT_scaled_size; + + } + + } + + blkn += compptr->MCU_width; + + output_ptr += compptr->DCT_scaled_size; + + } + + } + + } + + /* Completed an MCU row, but perhaps not an iMCU row */ + + coef->MCU_ctr = 0; + + } + + /* Completed the iMCU row, advance counters for next one */ + + cinfo->output_iMCU_row++; + + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + + start_iMCU_row(cinfo); + + return JPEG_ROW_COMPLETED; + + } + + /* Completed the scan */ + + (*cinfo->inputctl->finish_input_pass) (cinfo); + + return JPEG_SCAN_COMPLETED; + +} + + + + + +/* + + * Dummy consume-input routine for single-pass operation. + + */ + + + +METHODDEF int + +dummy_consume_data (j_decompress_ptr cinfo) + +{ + + return JPEG_SUSPENDED; /* Always indicate nothing was done */ + +} + + + + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + + + +/* + + * Consume input data and store it in the full-image coefficient buffer. + + * We read as much as one fully interleaved MCU row ("iMCU" row) per call, + + * ie, v_samp_factor block rows for each component in the scan. + + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + + */ + + + +METHODDEF int + +consume_data (j_decompress_ptr cinfo) + +{ + + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + JDIMENSION MCU_col_num; /* index of current MCU within row */ + + int blkn, ci, xindex, yindex, yoffset; + + JDIMENSION start_col; + + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + + JBLOCKROW buffer_ptr; + + jpeg_component_info *compptr; + + + + /* Align the virtual buffers for the components used in this scan. */ + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + + compptr = cinfo->cur_comp_info[ci]; + + buffer[ci] = (*cinfo->mem->access_virt_barray) + + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + + cinfo->input_iMCU_row * compptr->v_samp_factor, + + (JDIMENSION) compptr->v_samp_factor, TRUE); + + /* Note: entropy decoder expects buffer to be zeroed, + + * but this is handled automatically by the memory manager + + * because we requested a pre-zeroed array. + + */ + + } + + + + /* Loop to process one whole iMCU row */ + + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + + yoffset++) { + + for (MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; + + MCU_col_num++) { + + /* Construct list of pointers to DCT blocks belonging to this MCU */ + + blkn = 0; /* index of current DCT block within MCU */ + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + + compptr = cinfo->cur_comp_info[ci]; + + start_col = MCU_col_num * compptr->MCU_width; + + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + + coef->MCU_buffer[blkn++] = buffer_ptr++; + + } + + } + + } + + /* Try to fetch the MCU. */ + + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + + /* Suspension forced; update state counters and exit */ + + coef->MCU_vert_offset = yoffset; + + coef->MCU_ctr = MCU_col_num; + + return JPEG_SUSPENDED; + + } + + } + + /* Completed an MCU row, but perhaps not an iMCU row */ + + coef->MCU_ctr = 0; + + } + + /* Completed the iMCU row, advance counters for next one */ + + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + + start_iMCU_row(cinfo); + + return JPEG_ROW_COMPLETED; + + } + + /* Completed the scan */ + + (*cinfo->inputctl->finish_input_pass) (cinfo); + + return JPEG_SCAN_COMPLETED; + +} + + + + + +/* + + * Decompress and return some data in the multi-pass case. + + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + + * + + * NB: output_buf contains a plane for each component in image. + + */ + + + +METHODDEF int + +decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) + +{ + + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + + JDIMENSION block_num; + + int ci, block_row, block_rows; + + JBLOCKARRAY buffer; + + JBLOCKROW buffer_ptr; + + JSAMPARRAY output_ptr; + + JDIMENSION output_col; + + jpeg_component_info *compptr; + + inverse_DCT_method_ptr inverse_DCT; + + + + /* Force some input to be done if we are getting ahead of the input. */ + + while (cinfo->input_scan_number < cinfo->output_scan_number || + + (cinfo->input_scan_number == cinfo->output_scan_number && + + cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) { + + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + + return JPEG_SUSPENDED; + + } + + + + /* OK, output from the virtual arrays. */ + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + /* Don't bother to IDCT an uninteresting component. */ + + if (! compptr->component_needed) + + continue; + + /* Align the virtual buffer for this component. */ + + buffer = (*cinfo->mem->access_virt_barray) + + ((j_common_ptr) cinfo, coef->whole_image[ci], + + cinfo->output_iMCU_row * compptr->v_samp_factor, + + (JDIMENSION) compptr->v_samp_factor, FALSE); + + /* Count non-dummy DCT block rows in this iMCU row. */ + + if (cinfo->output_iMCU_row < last_iMCU_row) + + block_rows = compptr->v_samp_factor; + + else { + + /* NB: can't use last_row_height here; it is input-side-dependent! */ + + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + + if (block_rows == 0) block_rows = compptr->v_samp_factor; + + } + + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + + output_ptr = output_buf[ci]; + + /* Loop over all DCT blocks to be processed. */ + + for (block_row = 0; block_row < block_rows; block_row++) { + + buffer_ptr = buffer[block_row]; + + output_col = 0; + + for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) { + + (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr, + + output_ptr, output_col); + + buffer_ptr++; + + output_col += compptr->DCT_scaled_size; + + } + + output_ptr += compptr->DCT_scaled_size; + + } + + } + + + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + + return JPEG_ROW_COMPLETED; + + return JPEG_SCAN_COMPLETED; + +} + + + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + + + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + + + +/* + + * This code applies interblock smoothing as described by section K.8 + + * of the JPEG standard: the first 5 AC coefficients are estimated from + + * the DC values of a DCT block and its 8 neighboring blocks. + + * We apply smoothing only for progressive JPEG decoding, and only if + + * the coefficients it can estimate are not yet known to full precision. + + */ + + + +/* + + * Determine whether block smoothing is applicable and safe. + + * We also latch the current states of the coef_bits[] entries for the + + * AC coefficients; otherwise, if the input side of the decompressor + + * advances into a new scan, we might think the coefficients are known + + * more accurately than they really are. + + */ + + + +LOCAL boolean + +smoothing_ok (j_decompress_ptr cinfo) + +{ + + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + boolean smoothing_useful = FALSE; + + int ci, coefi; + + jpeg_component_info *compptr; + + JQUANT_TBL * qtable; + + int * coef_bits; + + int * coef_bits_latch; + + + + if (! cinfo->progressive_mode || cinfo->coef_bits == NULL) + + return FALSE; + + + + /* Allocate latch area if not already done */ + + if (coef->coef_bits_latch == NULL) + + coef->coef_bits_latch = (int *) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + cinfo->num_components * + + (SAVED_COEFS * SIZEOF(int))); + + coef_bits_latch = coef->coef_bits_latch; + + + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + /* All components' quantization values must already be latched. */ + + if ((qtable = compptr->quant_table) == NULL) + + return FALSE; + + /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ + + for (coefi = 0; coefi <= 5; coefi++) { + + if (qtable->quantval[coefi] == 0) + + return FALSE; + + } + + /* DC values must be at least partly known for all components. */ + + coef_bits = cinfo->coef_bits[ci]; + + if (coef_bits[0] < 0) + + return FALSE; + + /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ + + for (coefi = 1; coefi <= 5; coefi++) { + + coef_bits_latch[coefi] = coef_bits[coefi]; + + if (coef_bits[coefi] != 0) + + smoothing_useful = TRUE; + + } + + coef_bits_latch += SAVED_COEFS; + + } + + + + return smoothing_useful; + +} + + + + + +/* + + * Variant of decompress_data for use when doing block smoothing. + + */ + + + +METHODDEF int + +decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) + +{ + + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + + JDIMENSION block_num, last_block_column; + + int ci, block_row, block_rows, access_rows; + + JBLOCKARRAY buffer; + + JBLOCKROW buffer_ptr, prev_block_row, next_block_row; + + JSAMPARRAY output_ptr; + + JDIMENSION output_col; + + jpeg_component_info *compptr; + + inverse_DCT_method_ptr inverse_DCT; + + boolean first_row, last_row; + + JBLOCK workspace; + + int *coef_bits; + + JQUANT_TBL *quanttbl; + + INT32 Q00,Q01,Q02,Q10,Q11,Q20, num; + + int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9; + + int Al, pred; + + + + /* Force some input to be done if we are getting ahead of the input. */ + + while (cinfo->input_scan_number <= cinfo->output_scan_number && + + ! cinfo->inputctl->eoi_reached) { + + if (cinfo->input_scan_number == cinfo->output_scan_number) { + + /* If input is working on current scan, we ordinarily want it to + + * have completed the current row. But if input scan is DC, + + * we want it to keep one row ahead so that next block row's DC + + * values are up to date. + + */ + + JDIMENSION delta = (cinfo->Ss == 0) ? 1 : 0; + + if (cinfo->input_iMCU_row > cinfo->output_iMCU_row+delta) + + break; + + } + + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + + return JPEG_SUSPENDED; + + } + + + + /* OK, output from the virtual arrays. */ + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + /* Don't bother to IDCT an uninteresting component. */ + + if (! compptr->component_needed) + + continue; + + /* Count non-dummy DCT block rows in this iMCU row. */ + + if (cinfo->output_iMCU_row < last_iMCU_row) { + + block_rows = compptr->v_samp_factor; + + access_rows = block_rows * 2; /* this and next iMCU row */ + + last_row = FALSE; + + } else { + + /* NB: can't use last_row_height here; it is input-side-dependent! */ + + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + + if (block_rows == 0) block_rows = compptr->v_samp_factor; + + access_rows = block_rows; /* this iMCU row only */ + + last_row = TRUE; + + } + + /* Align the virtual buffer for this component. */ + + if (cinfo->output_iMCU_row > 0) { + + access_rows += compptr->v_samp_factor; /* prior iMCU row too */ + + buffer = (*cinfo->mem->access_virt_barray) + + ((j_common_ptr) cinfo, coef->whole_image[ci], + + (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor, + + (JDIMENSION) access_rows, FALSE); + + buffer += compptr->v_samp_factor; /* point to current iMCU row */ + + first_row = FALSE; + + } else { + + buffer = (*cinfo->mem->access_virt_barray) + + ((j_common_ptr) cinfo, coef->whole_image[ci], + + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE); + + first_row = TRUE; + + } + + /* Fetch component-dependent info */ + + coef_bits = coef->coef_bits_latch + (ci * SAVED_COEFS); + + quanttbl = compptr->quant_table; + + Q00 = quanttbl->quantval[0]; + + Q01 = quanttbl->quantval[1]; + + Q10 = quanttbl->quantval[2]; + + Q20 = quanttbl->quantval[3]; + + Q11 = quanttbl->quantval[4]; + + Q02 = quanttbl->quantval[5]; + + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + + output_ptr = output_buf[ci]; + + /* Loop over all DCT blocks to be processed. */ + + for (block_row = 0; block_row < block_rows; block_row++) { + + buffer_ptr = buffer[block_row]; + + if (first_row && block_row == 0) + + prev_block_row = buffer_ptr; + + else + + prev_block_row = buffer[block_row-1]; + + if (last_row && block_row == block_rows-1) + + next_block_row = buffer_ptr; + + else + + next_block_row = buffer[block_row+1]; + + /* We fetch the surrounding DC values using a sliding-register approach. + + * Initialize all nine here so as to do the right thing on narrow pics. + + */ + + DC1 = DC2 = DC3 = (int) prev_block_row[0][0]; + + DC4 = DC5 = DC6 = (int) buffer_ptr[0][0]; + + DC7 = DC8 = DC9 = (int) next_block_row[0][0]; + + output_col = 0; + + last_block_column = compptr->width_in_blocks - 1; + + for (block_num = 0; block_num <= last_block_column; block_num++) { + + /* Fetch current DCT block into workspace so we can modify it. */ + + jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1); + + /* Update DC values */ + + if (block_num < last_block_column) { + + DC3 = (int) prev_block_row[1][0]; + + DC6 = (int) buffer_ptr[1][0]; + + DC9 = (int) next_block_row[1][0]; + + } + + /* Compute coefficient estimates per K.8. + + * An estimate is applied only if coefficient is still zero, + + * and is not known to be fully accurate. + + */ + + /* AC01 */ + + if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) { + + num = 36 * Q00 * (DC4 - DC6); + + if (num >= 0) { + + pred = (int) (((Q01<<7) + num) / (Q01<<8)); + + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + + pred = (int) (((Q10<<7) + num) / (Q10<<8)); + + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + + pred = (int) (((Q20<<7) + num) / (Q20<<8)); + + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + + pred = (int) (((Q11<<7) + num) / (Q11<<8)); + + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + + pred = (int) (((Q02<<7) + num) / (Q02<<8)); + + if (Al > 0 && pred >= (1< 0 && pred >= (1<DCT_scaled_size; + + } + + output_ptr += compptr->DCT_scaled_size; + + } + + } + + + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + + return JPEG_ROW_COMPLETED; + + return JPEG_SCAN_COMPLETED; + +} + + + +#endif /* BLOCK_SMOOTHING_SUPPORTED */ + + + + + +/* + + * Initialize coefficient buffer controller. + + */ + + + +GLOBAL void + +jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) + +{ + + my_coef_ptr coef; + + + + coef = (my_coef_ptr) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + SIZEOF(my_coef_controller)); + + cinfo->coef = (struct jpeg_d_coef_controller *) coef; + + coef->pub.start_input_pass = start_input_pass; + + coef->pub.start_output_pass = start_output_pass; + +#ifdef BLOCK_SMOOTHING_SUPPORTED + + coef->coef_bits_latch = NULL; + +#endif + + + + /* Create the coefficient buffer. */ + + if (need_full_buffer) { + +#ifdef D_MULTISCAN_FILES_SUPPORTED + + /* Allocate a full-image virtual array for each component, */ + + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + + /* Note we ask for a pre-zeroed array. */ + + int ci, access_rows; + + jpeg_component_info *compptr; + + + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + access_rows = compptr->v_samp_factor; + +#ifdef BLOCK_SMOOTHING_SUPPORTED + + /* If block smoothing could be used, need a bigger window */ + + if (cinfo->progressive_mode) + + access_rows *= 3; + +#endif + + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + + ((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + + (long) compptr->h_samp_factor), + + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + + (long) compptr->v_samp_factor), + + (JDIMENSION) access_rows); + + } + + coef->pub.consume_data = consume_data; + + coef->pub.decompress_data = decompress_data; + + coef->pub.coef_arrays = coef->whole_image; /* link to virtual arrays */ + +#else + + ERREXIT(cinfo, JERR_NOT_COMPILED); + +#endif + + } else { + + /* We only need a single-MCU buffer. */ + + JBLOCKROW buffer; + + int i; + + + + buffer = (JBLOCKROW) + + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + + for (i = 0; i < D_MAX_BLOCKS_IN_MCU; i++) { + + coef->MCU_buffer[i] = buffer + i; + + } + + coef->pub.consume_data = dummy_consume_data; + + coef->pub.decompress_data = decompress_onepass; + + coef->pub.coef_arrays = NULL; /* flag for no virtual arrays */ + + } + +} + diff --git a/tools/urt/libs/jpeg6/jdcolor.cpp b/tools/urt/libs/jpeg6/jdcolor.cpp new file mode 100644 index 00000000..5c173597 --- /dev/null +++ b/tools/urt/libs/jpeg6/jdcolor.cpp @@ -0,0 +1,734 @@ +/* + + * jdcolor.c + + * + + * Copyright (C) 1991-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains output colorspace conversion routines. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* Private subobject */ + + + +typedef struct { + + struct jpeg_color_deconverter pub; /* public fields */ + + + + /* Private state for YCC->RGB conversion */ + + int * Cr_r_tab; /* => table for Cr to R conversion */ + + int * Cb_b_tab; /* => table for Cb to B conversion */ + + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ + +} my_color_deconverter; + + + +typedef my_color_deconverter * my_cconvert_ptr; + + + + + +/**************** YCbCr -> RGB conversion: most common case **************/ + + + +/* + + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + + * The conversion equations to be implemented are therefore + + * R = Y + 1.40200 * Cr + + * G = Y - 0.34414 * Cb - 0.71414 * Cr + + * B = Y + 1.77200 * Cb + + * where Cb and Cr represent the incoming values less CENTERJSAMPLE. + + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + + * + + * To avoid floating-point arithmetic, we represent the fractional constants + + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + + * the products by 2^16, with appropriate rounding, to get the correct answer. + + * Notice that Y, being an integral input, does not contribute any fraction + + * so it need not participate in the rounding. + + * + + * For even more speed, we avoid doing any multiplications in the inner loop + + * by precalculating the constants times Cb and Cr for all possible values. + + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + + * for 12-bit samples it is still acceptable. It's not very reasonable for + + * 16-bit samples, but if you want lossless storage you shouldn't be changing + + * colorspace anyway. + + * The Cr=>R and Cb=>B values can be rounded to integers in advance; the + + * values for the G calculation are left scaled up, since we must add them + + * together before rounding. + + */ + + + +#define SCALEBITS 16 /* speediest right-shift on some machines */ + +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) + +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + + */ + + + +LOCAL void + +build_ycc_rgb_table (j_decompress_ptr cinfo) + +{ + + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + + int i; + + INT32 x; + + SHIFT_TEMPS + + + + cconvert->Cr_r_tab = (int *) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + (MAXJSAMPLE+1) * SIZEOF(int)); + + cconvert->Cb_b_tab = (int *) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + (MAXJSAMPLE+1) * SIZEOF(int)); + + cconvert->Cr_g_tab = (INT32 *) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + cconvert->Cb_g_tab = (INT32 *) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + + /* Cr=>R value is nearest int to 1.40200 * x */ + + cconvert->Cr_r_tab[i] = (int) + + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + + /* Cb=>B value is nearest int to 1.77200 * x */ + + cconvert->Cb_b_tab[i] = (int) + + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + + /* Cr=>G value is scaled-up -0.71414 * x */ + + cconvert->Cr_g_tab[i] = (- FIX(0.71414)) * x; + + /* Cb=>G value is scaled-up -0.34414 * x */ + + /* We also add in ONE_HALF so that need not do it in inner loop */ + + cconvert->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + + } + +} + + + + + +/* + + * Convert some rows of samples to the output colorspace. + + * + + * Note that we change from noninterleaved, one-plane-per-component format + + * to interleaved-pixel format. The output buffer is therefore three times + + * as wide as the input buffer. + + * A starting row offset is provided only for the input buffer. The caller + + * can easily adjust the passed output_buf value to accommodate any row + + * offset required on that side. + + */ + + + +METHODDEF void + +ycc_rgb_convert (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION input_row, + + JSAMPARRAY output_buf, int num_rows) + +{ + + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + + register int y, cb, cr; + + register JSAMPROW outptr; + + register JSAMPROW inptr0, inptr1, inptr2; + + register JDIMENSION col; + + JDIMENSION num_cols = cinfo->output_width; + + /* copy these pointers into registers if possible */ + + register JSAMPLE * range_limit = cinfo->sample_range_limit; + + register int * Crrtab = cconvert->Cr_r_tab; + + register int * Cbbtab = cconvert->Cb_b_tab; + + register INT32 * Crgtab = cconvert->Cr_g_tab; + + register INT32 * Cbgtab = cconvert->Cb_g_tab; + + SHIFT_TEMPS + + + + while (--num_rows >= 0) { + + inptr0 = input_buf[0][input_row]; + + inptr1 = input_buf[1][input_row]; + + inptr2 = input_buf[2][input_row]; + + input_row++; + + outptr = *output_buf++; + + for (col = 0; col < num_cols; col++) { + + y = GETJSAMPLE(inptr0[col]); + + cb = GETJSAMPLE(inptr1[col]); + + cr = GETJSAMPLE(inptr2[col]); + + /* Range-limiting is essential due to noise introduced by DCT losses. */ + + outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; + + outptr[RGB_GREEN] = range_limit[y + + + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + + SCALEBITS))]; + + outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; + + outptr += RGB_PIXELSIZE; + + } + + } + +} + + + + + +/**************** Cases other than YCbCr -> RGB **************/ + + + + + +/* + + * Color conversion for no colorspace change: just copy the data, + + * converting from separate-planes to interleaved representation. + + */ + + + +METHODDEF void + +null_convert (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION input_row, + + JSAMPARRAY output_buf, int num_rows) + +{ + + register JSAMPROW inptr, outptr; + + register JDIMENSION count; + + register int num_components = cinfo->num_components; + + JDIMENSION num_cols = cinfo->output_width; + + int ci; + + + + while (--num_rows >= 0) { + + for (ci = 0; ci < num_components; ci++) { + + inptr = input_buf[ci][input_row]; + + outptr = output_buf[0] + ci; + + for (count = num_cols; count > 0; count--) { + + *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ + + outptr += num_components; + + } + + } + + input_row++; + + output_buf++; + + } + +} + + + + + +/* + + * Color conversion for grayscale: just copy the data. + + * This also works for YCbCr -> grayscale conversion, in which + + * we just copy the Y (luminance) component and ignore chrominance. + + */ + + + +METHODDEF void + +grayscale_convert (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION input_row, + + JSAMPARRAY output_buf, int num_rows) + +{ + + jcopy_sample_rows(input_buf[0], (int) input_row, output_buf, 0, + + num_rows, cinfo->output_width); + +} + + + + + +/* + + * Adobe-style YCCK->CMYK conversion. + + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + + * conversion as above, while passing K (black) unchanged. + + * We assume build_ycc_rgb_table has been called. + + */ + + + +METHODDEF void + +ycck_cmyk_convert (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION input_row, + + JSAMPARRAY output_buf, int num_rows) + +{ + + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + + register int y, cb, cr; + + register JSAMPROW outptr; + + register JSAMPROW inptr0, inptr1, inptr2, inptr3; + + register JDIMENSION col; + + JDIMENSION num_cols = cinfo->output_width; + + /* copy these pointers into registers if possible */ + + register JSAMPLE * range_limit = cinfo->sample_range_limit; + + register int * Crrtab = cconvert->Cr_r_tab; + + register int * Cbbtab = cconvert->Cb_b_tab; + + register INT32 * Crgtab = cconvert->Cr_g_tab; + + register INT32 * Cbgtab = cconvert->Cb_g_tab; + + SHIFT_TEMPS + + + + while (--num_rows >= 0) { + + inptr0 = input_buf[0][input_row]; + + inptr1 = input_buf[1][input_row]; + + inptr2 = input_buf[2][input_row]; + + inptr3 = input_buf[3][input_row]; + + input_row++; + + outptr = *output_buf++; + + for (col = 0; col < num_cols; col++) { + + y = GETJSAMPLE(inptr0[col]); + + cb = GETJSAMPLE(inptr1[col]); + + cr = GETJSAMPLE(inptr2[col]); + + /* Range-limiting is essential due to noise introduced by DCT losses. */ + + outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])]; /* red */ + + outptr[1] = range_limit[MAXJSAMPLE - (y + /* green */ + + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + + SCALEBITS)))]; + + outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])]; /* blue */ + + /* K passes through unchanged */ + + outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ + + outptr += 4; + + } + + } + +} + + + + + +/* + + * Empty method for start_pass. + + */ + + + +METHODDEF void + +start_pass_dcolor (j_decompress_ptr cinfo) + +{ + + /* no work needed */ + +} + + + + + +/* + + * Module initialization routine for output colorspace conversion. + + */ + + + +GLOBAL void + +jinit_color_deconverter (j_decompress_ptr cinfo) + +{ + + my_cconvert_ptr cconvert; + + int ci; + + + + cconvert = (my_cconvert_ptr) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + SIZEOF(my_color_deconverter)); + + cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; + + cconvert->pub.start_pass = start_pass_dcolor; + + + + /* Make sure num_components agrees with jpeg_color_space */ + + switch (cinfo->jpeg_color_space) { + + case JCS_GRAYSCALE: + + if (cinfo->num_components != 1) + + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + + break; + + + + case JCS_RGB: + + case JCS_YCbCr: + + if (cinfo->num_components != 3) + + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + + break; + + + + case JCS_CMYK: + + case JCS_YCCK: + + if (cinfo->num_components != 4) + + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + + break; + + + + default: /* JCS_UNKNOWN can be anything */ + + if (cinfo->num_components < 1) + + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + + break; + + } + + + + /* Set out_color_components and conversion method based on requested space. + + * Also clear the component_needed flags for any unused components, + + * so that earlier pipeline stages can avoid useless computation. + + */ + + + + switch (cinfo->out_color_space) { + + case JCS_GRAYSCALE: + + cinfo->out_color_components = 1; + + if (cinfo->jpeg_color_space == JCS_GRAYSCALE || + + cinfo->jpeg_color_space == JCS_YCbCr) { + + cconvert->pub.color_convert = grayscale_convert; + + /* For color->grayscale conversion, only the Y (0) component is needed */ + + for (ci = 1; ci < cinfo->num_components; ci++) + + cinfo->comp_info[ci].component_needed = FALSE; + + } else + + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + + break; + + + + case JCS_RGB: + + cinfo->out_color_components = RGB_PIXELSIZE; + + if (cinfo->jpeg_color_space == JCS_YCbCr) { + + cconvert->pub.color_convert = ycc_rgb_convert; + + build_ycc_rgb_table(cinfo); + + } else if (cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3) { + + cconvert->pub.color_convert = null_convert; + + } else + + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + + break; + + + + case JCS_CMYK: + + cinfo->out_color_components = 4; + + if (cinfo->jpeg_color_space == JCS_YCCK) { + + cconvert->pub.color_convert = ycck_cmyk_convert; + + build_ycc_rgb_table(cinfo); + + } else if (cinfo->jpeg_color_space == JCS_CMYK) { + + cconvert->pub.color_convert = null_convert; + + } else + + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + + break; + + + + default: + + /* Permit null conversion to same output space */ + + if (cinfo->out_color_space == cinfo->jpeg_color_space) { + + cinfo->out_color_components = cinfo->num_components; + + cconvert->pub.color_convert = null_convert; + + } else /* unsupported non-null conversion */ + + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + + break; + + } + + + + if (cinfo->quantize_colors) + + cinfo->output_components = 1; /* single colormapped output component */ + + else + + cinfo->output_components = cinfo->out_color_components; + +} + diff --git a/tools/urt/libs/jpeg6/jdct.h b/tools/urt/libs/jpeg6/jdct.h new file mode 100644 index 00000000..cebb118e --- /dev/null +++ b/tools/urt/libs/jpeg6/jdct.h @@ -0,0 +1,352 @@ +/* + + * jdct.h + + * + + * Copyright (C) 1994, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This include file contains common declarations for the forward and + + * inverse DCT modules. These declarations are private to the DCT managers + + * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. + + * The individual DCT algorithms are kept in separate files to ease + + * machine-dependent tuning (e.g., assembly coding). + + */ + + + + + +/* + + * A forward DCT routine is given a pointer to a work area of type DCTELEM[]; + + * the DCT is to be performed in-place in that buffer. Type DCTELEM is int + + * for 8-bit samples, INT32 for 12-bit samples. (NOTE: Floating-point DCT + + * implementations use an array of type FAST_FLOAT, instead.) + + * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE). + + * The DCT outputs are returned scaled up by a factor of 8; they therefore + + * have a range of +-8K for 8-bit data, +-128K for 12-bit data. This + + * convention improves accuracy in integer implementations and saves some + + * work in floating-point ones. + + * Quantization of the output coefficients is done by jcdctmgr.c. + + */ + + + +#if BITS_IN_JSAMPLE == 8 + +typedef int DCTELEM; /* 16 or 32 bits is fine */ + +#else + +typedef INT32 DCTELEM; /* must have 32 bits */ + +#endif + + + +typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data)); + +typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data)); + + + + + +/* + + * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer + + * to an output sample array. The routine must dequantize the input data as + + * well as perform the IDCT; for dequantization, it uses the multiplier table + + * pointed to by compptr->dct_table. The output data is to be placed into the + + * sample array starting at a specified column. (Any row offset needed will + + * be applied to the array pointer before it is passed to the IDCT code.) + + * Note that the number of samples emitted by the IDCT routine is + + * DCT_scaled_size * DCT_scaled_size. + + */ + + + +/* typedef inverse_DCT_method_ptr is declared in jpegint.h */ + + + +/* + + * Each IDCT routine has its own ideas about the best dct_table element type. + + */ + + + +typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ + +#if BITS_IN_JSAMPLE == 8 + +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ + +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ + +#else + +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ + +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ + +#endif + +typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ + + + + + +/* + + * Each IDCT routine is responsible for range-limiting its results and + + * converting them to unsigned form (0..MAXJSAMPLE). The raw outputs could + + * be quite far out of range if the input data is corrupt, so a bulletproof + + * range-limiting step is required. We use a mask-and-table-lookup method + + * to do the combined operations quickly. See the comments with + + * prepare_range_limit_table (in jdmaster.c) for more info. + + */ + + + +#define IDCT_range_limit(cinfo) ((cinfo)->sample_range_limit + CENTERJSAMPLE) + + + +#define RANGE_MASK (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */ + + + + + +/* Short forms of external names for systems with brain-damaged linkers. */ + + + +#ifdef NEED_SHORT_EXTERNAL_NAMES + +#define jpeg_fdct_islow jFDislow + +#define jpeg_fdct_ifast jFDifast + +#define jpeg_fdct_float jFDfloat + +#define jpeg_idct_islow jRDislow + +#define jpeg_idct_ifast jRDifast + +#define jpeg_idct_float jRDfloat + +#define jpeg_idct_4x4 jRD4x4 + +#define jpeg_idct_2x2 jRD2x2 + +#define jpeg_idct_1x1 jRD1x1 + +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + + +/* Extern declarations for the forward and inverse DCT routines. */ + + + +EXTERN void jpeg_fdct_islow JPP((DCTELEM * data)); + +EXTERN void jpeg_fdct_ifast JPP((DCTELEM * data)); + +EXTERN void jpeg_fdct_float JPP((FAST_FLOAT * data)); + + + +EXTERN void jpeg_idct_islow + + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + +EXTERN void jpeg_idct_ifast + + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + +EXTERN void jpeg_idct_float + + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + +EXTERN void jpeg_idct_4x4 + + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + +EXTERN void jpeg_idct_2x2 + + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + +EXTERN void jpeg_idct_1x1 + + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + + + + + +/* + + * Macros for handling fixed-point arithmetic; these are used by many + + * but not all of the DCT/IDCT modules. + + * + + * All values are expected to be of type INT32. + + * Fractional constants are scaled left by CONST_BITS bits. + + * CONST_BITS is defined within each module using these macros, + + * and may differ from one module to the next. + + */ + + + +#define ONE ((INT32) 1) + +#define CONST_SCALE (ONE << CONST_BITS) + + + +/* Convert a positive real constant to an integer scaled by CONST_SCALE. + + * Caution: some C compilers fail to reduce "FIX(constant)" at compile time, + + * thus causing a lot of useless floating-point operations at run time. + + */ + + + +#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) + + + +/* Descale and correctly round an INT32 value that's scaled by N bits. + + * We assume RIGHT_SHIFT rounds towards minus infinity, so adding + + * the fudge factor is correct for either sign of X. + + */ + + + +#define DESCALE(x,n) RIGHT_SHIFT((x) + (ONE << ((n)-1)), n) + + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + + * This macro is used only when the two inputs will actually be no more than + + * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a + + * full 32x32 multiply. This provides a useful speedup on many machines. + + * Unfortunately there is no way to specify a 16x16->32 multiply portably + + * in C, but some C compilers will do the right thing if you provide the + + * correct combination of casts. + + */ + + + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ + +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT16) (const))) + +#endif + +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ + +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT32) (const))) + +#endif + + + +#ifndef MULTIPLY16C16 /* default definition */ + +#define MULTIPLY16C16(var,const) ((var) * (const)) + +#endif + + + +/* Same except both inputs are variables. */ + + + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ + +#define MULTIPLY16V16(var1,var2) (((INT16) (var1)) * ((INT16) (var2))) + +#endif + + + +#ifndef MULTIPLY16V16 /* default definition */ + +#define MULTIPLY16V16(var1,var2) ((var1) * (var2)) + +#endif + diff --git a/tools/urt/libs/jpeg6/jddctmgr.cpp b/tools/urt/libs/jpeg6/jddctmgr.cpp new file mode 100644 index 00000000..cdf107e0 --- /dev/null +++ b/tools/urt/libs/jpeg6/jddctmgr.cpp @@ -0,0 +1,540 @@ +/* + + * jddctmgr.c + + * + + * Copyright (C) 1994-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains the inverse-DCT management logic. + + * This code selects a particular IDCT implementation to be used, + + * and it performs related housekeeping chores. No code in this file + + * is executed per IDCT step, only during output pass setup. + + * + + * Note that the IDCT routines are responsible for performing coefficient + + * dequantization as well as the IDCT proper. This module sets up the + + * dequantization multiplier table needed by the IDCT routine. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + +#include "jdct.h" /* Private declarations for DCT subsystem */ + + + + + +/* + + * The decompressor input side (jdinput.c) saves away the appropriate + + * quantization table for each component at the start of the first scan + + * involving that component. (This is necessary in order to correctly + + * decode files that reuse Q-table slots.) + + * When we are ready to make an output pass, the saved Q-table is converted + + * to a multiplier table that will actually be used by the IDCT routine. + + * The multiplier table contents are IDCT-method-dependent. To support + + * application changes in IDCT method between scans, we can remake the + + * multiplier tables if necessary. + + * In buffered-image mode, the first output pass may occur before any data + + * has been seen for some components, and thus before their Q-tables have + + * been saved away. To handle this case, multiplier tables are preset + + * to zeroes; the result of the IDCT will be a neutral gray level. + + */ + + + + + +/* Private subobject for this module */ + + + +typedef struct { + + struct jpeg_inverse_dct pub; /* public fields */ + + + + /* This array contains the IDCT method code that each multiplier table + + * is currently set up for, or -1 if it's not yet set up. + + * The actual multiplier tables are pointed to by dct_table in the + + * per-component comp_info structures. + + */ + + int cur_method[MAX_COMPONENTS]; + +} my_idct_controller; + + + +typedef my_idct_controller * my_idct_ptr; + + + + + +/* Allocated multiplier tables: big enough for any supported variant */ + + + +typedef union { + + ISLOW_MULT_TYPE islow_array[DCTSIZE2]; + +#ifdef DCT_IFAST_SUPPORTED + + IFAST_MULT_TYPE ifast_array[DCTSIZE2]; + +#endif + +#ifdef DCT_FLOAT_SUPPORTED + + FLOAT_MULT_TYPE float_array[DCTSIZE2]; + +#endif + +} multiplier_table; + + + + + +/* The current scaled-IDCT routines require ISLOW-style multiplier tables, + + * so be sure to compile that code if either ISLOW or SCALING is requested. + + */ + +#ifdef DCT_ISLOW_SUPPORTED + +#define PROVIDE_ISLOW_TABLES + +#else + +#ifdef IDCT_SCALING_SUPPORTED + +#define PROVIDE_ISLOW_TABLES + +#endif + +#endif + + + + + +/* + + * Prepare for an output pass. + + * Here we select the proper IDCT routine for each component and build + + * a matching multiplier table. + + */ + + + +METHODDEF void + +start_pass (j_decompress_ptr cinfo) + +{ + + my_idct_ptr idct = (my_idct_ptr) cinfo->idct; + + int ci, i; + + jpeg_component_info *compptr; + + int method = 0; + + inverse_DCT_method_ptr method_ptr = NULL; + + JQUANT_TBL * qtbl; + + + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + /* Select the proper IDCT routine for this component's scaling */ + + switch (compptr->DCT_scaled_size) { + +#ifdef IDCT_SCALING_SUPPORTED + + case 1: + + method_ptr = jpeg_idct_1x1; + + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + + break; + + case 2: + + method_ptr = jpeg_idct_2x2; + + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + + break; + + case 4: + + method_ptr = jpeg_idct_4x4; + + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + + break; + +#endif + + case DCTSIZE: + + switch (cinfo->dct_method) { + +#ifdef DCT_ISLOW_SUPPORTED + + case JDCT_ISLOW: + + method_ptr = jpeg_idct_islow; + + method = JDCT_ISLOW; + + break; + +#endif + +#ifdef DCT_IFAST_SUPPORTED + + case JDCT_IFAST: + + method_ptr = jpeg_idct_ifast; + + method = JDCT_IFAST; + + break; + +#endif + +#ifdef DCT_FLOAT_SUPPORTED + + case JDCT_FLOAT: + + method_ptr = jpeg_idct_float; + + method = JDCT_FLOAT; + + break; + +#endif + + default: + + ERREXIT(cinfo, JERR_NOT_COMPILED); + + break; + + } + + break; + + default: + + ERREXIT1(cinfo, JERR_BAD_DCTSIZE, compptr->DCT_scaled_size); + + break; + + } + + idct->pub.inverse_DCT[ci] = method_ptr; + + /* Create multiplier table from quant table. + + * However, we can skip this if the component is uninteresting + + * or if we already built the table. Also, if no quant table + + * has yet been saved for the component, we leave the + + * multiplier table all-zero; we'll be reading zeroes from the + + * coefficient controller's buffer anyway. + + */ + + if (! compptr->component_needed || idct->cur_method[ci] == method) + + continue; + + qtbl = compptr->quant_table; + + if (qtbl == NULL) /* happens if no data yet for component */ + + continue; + + idct->cur_method[ci] = method; + + switch (method) { + +#ifdef PROVIDE_ISLOW_TABLES + + case JDCT_ISLOW: + + { + + /* For LL&M IDCT method, multipliers are equal to raw quantization + + * coefficients, but are stored in natural order as ints. + + */ + + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + + for (i = 0; i < DCTSIZE2; i++) { + + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[jpeg_zigzag_order[i]]; + + } + + } + + break; + +#endif + +#ifdef DCT_IFAST_SUPPORTED + + case JDCT_IFAST: + + { + + /* For AA&N IDCT method, multipliers are equal to quantization + + * coefficients scaled by scalefactor[row]*scalefactor[col], where + + * scalefactor[0] = 1 + + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + + * For integer operation, the multiplier table is to be scaled by + + * IFAST_SCALE_BITS. The multipliers are stored in natural order. + + */ + + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; + +#define CONST_BITS 14 + + static const INT16 aanscales[DCTSIZE2] = { + + /* precomputed values scaled up by 14 bits */ + + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + + }; + + SHIFT_TEMPS + + + + for (i = 0; i < DCTSIZE2; i++) { + + ifmtbl[i] = (IFAST_MULT_TYPE) + + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[jpeg_zigzag_order[i]], + + (INT32) aanscales[i]), + + CONST_BITS-IFAST_SCALE_BITS); + + } + + } + + break; + +#endif + +#ifdef DCT_FLOAT_SUPPORTED + + case JDCT_FLOAT: + + { + + /* For float AA&N IDCT method, multipliers are equal to quantization + + * coefficients scaled by scalefactor[row]*scalefactor[col], where + + * scalefactor[0] = 1 + + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + + * The multipliers are stored in natural order. + + */ + + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + + int row, col; + + static const double aanscalefactor[DCTSIZE] = { + + 1.0, 1.387039845, 1.306562965, 1.175875602, + + 1.0, 0.785694958, 0.541196100, 0.275899379 + + }; + + + + i = 0; + + for (row = 0; row < DCTSIZE; row++) { + + for (col = 0; col < DCTSIZE; col++) { + + fmtbl[i] = (FLOAT_MULT_TYPE) + + ((double) qtbl->quantval[jpeg_zigzag_order[i]] * + + aanscalefactor[row] * aanscalefactor[col]); + + i++; + + } + + } + + } + + break; + +#endif + + default: + + ERREXIT(cinfo, JERR_NOT_COMPILED); + + break; + + } + + } + +} + + + + + +/* + + * Initialize IDCT manager. + + */ + + + +GLOBAL void + +jinit_inverse_dct (j_decompress_ptr cinfo) + +{ + + my_idct_ptr idct; + + int ci; + + jpeg_component_info *compptr; + + + + idct = (my_idct_ptr) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + SIZEOF(my_idct_controller)); + + cinfo->idct = (struct jpeg_inverse_dct *) idct; + + idct->pub.start_pass = start_pass; + + + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + /* Allocate and pre-zero a multiplier table for each component */ + + compptr->dct_table = + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + SIZEOF(multiplier_table)); + + MEMZERO(compptr->dct_table, SIZEOF(multiplier_table)); + + /* Mark multiplier table not yet set up for any method */ + + idct->cur_method[ci] = -1; + + } + +} + diff --git a/tools/urt/libs/jpeg6/jdhuff.cpp b/tools/urt/libs/jpeg6/jdhuff.cpp new file mode 100644 index 00000000..4ed8bc3a --- /dev/null +++ b/tools/urt/libs/jpeg6/jdhuff.cpp @@ -0,0 +1,574 @@ +/* + * jdhuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdphuff.c */ + + +/* + * Expanded entropy decoder object for Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; +} huff_entropy_decoder; + +typedef huff_entropy_decoder * huff_entropy_ptr; + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF void +start_pass_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning because + * there are some baseline files out there with all zeroes in these bytes. + */ + if (cinfo->Ss != 0 || cinfo->Se != DCTSIZE2-1 || + cinfo->Ah != 0 || cinfo->Al != 0) + WARNMS(cinfo, JWRN_NOT_SEQUENTIAL); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS || + cinfo->dc_huff_tbl_ptrs[dctbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl); + if (actbl < 0 || actbl >= NUM_HUFF_TBLS || + cinfo->ac_huff_tbl_ptrs[actbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, actbl); + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_d_derived_tbl(cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_d_derived_tbl(cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + & entropy->ac_derived_tbls[actbl]); + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->bitstate.printed_eod = FALSE; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jdphuff.c. + */ + +GLOBAL void +jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, JHUFF_TBL * htbl, + d_derived_tbl ** pdtbl) +{ + d_derived_tbl *dtbl; + int p, i, l, si; + int lookbits, ctr; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (d_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(d_derived_tbl)); + dtbl = *pdtbl; + dtbl->pub = htbl; /* fill in back link */ + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure F.15: generate decoding tables for bit-sequential decoding */ + + p = 0; + for (l = 1; l <= 16; l++) { + if (htbl->bits[l]) { + dtbl->valptr[l] = p; /* huffval[] index of 1st symbol of code length l */ + dtbl->mincode[l] = huffcode[p]; /* minimum code of length l */ + p += htbl->bits[l]; + dtbl->maxcode[l] = huffcode[p-1]; /* maximum code of length l */ + } else { + dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ + } + } + dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */ + + /* Compute lookahead tables to speed up decoding. + * First we set all the table entries to 0, indicating "too long"; + * then we iterate through the Huffman codes that are short enough and + * fill in all the entries that correspond to bit sequences starting + * with that code. + */ + + MEMZERO(dtbl->look_nbits, SIZEOF(dtbl->look_nbits)); + + p = 0; + for (l = 1; l <= HUFF_LOOKAHEAD; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++, p++) { + /* l = current code's length, p = its index in huffcode[] & huffval[]. */ + /* Generate left-justified code followed by all possible bit sequences */ + lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l); + for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) { + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; + } + } + } +} + + +/* + * Out-of-line code for bit fetching (shared with jdphuff.c). + * See jdhuff.h for info about usage. + * Note: current values of get_buffer and bits_left are passed as parameters, + * but are returned in the corresponding fields of the state struct. + * + * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width + * of get_buffer to be used. (On machines with wider words, an even larger + * buffer could be used.) However, on some machines 32-bit shifts are + * quite slow and take time proportional to the number of places shifted. + * (This is true with most PC compilers, for instance.) In this case it may + * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the + * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. + */ + +#ifdef SLOW_SHIFT_32 +#define MIN_GET_BITS 15 /* minimum allowable value */ +#else +#define MIN_GET_BITS (BIT_BUF_SIZE-7) +#endif + + +GLOBAL boolean +jpeg_fill_bit_buffer (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits) +/* Load up the bit buffer to a depth of at least nbits */ +{ + /* Copy heavily used state fields into locals (hopefully registers) */ + register const JOCTET * next_input_byte = state->next_input_byte; + register size_t bytes_in_buffer = state->bytes_in_buffer; + register int c; + + /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ + /* (It is assumed that no request will be for more than that many bits.) */ + + while (bits_left < MIN_GET_BITS) { + /* Attempt to read a byte */ + if (state->unread_marker != 0) + goto no_more_data; /* can't advance past a marker */ + + if (bytes_in_buffer == 0) { + if (! (*state->cinfo->src->fill_input_buffer) (state->cinfo)) + return FALSE; + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + + /* If it's 0xFF, check and discard stuffed zero byte */ + if (c == 0xFF) { + do { + if (bytes_in_buffer == 0) { + if (! (*state->cinfo->src->fill_input_buffer) (state->cinfo)) + return FALSE; + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + } while (c == 0xFF); + + if (c == 0) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. */ + /* Better put it back for use later */ + state->unread_marker = c; + + no_more_data: + /* There should be enough bits still left in the data segment; */ + /* if so, just break out of the outer while loop. */ + if (bits_left >= nbits) + break; + /* Uh-oh. Report corrupted data to user and stuff zeroes into + * the data stream, so that we can produce some kind of image. + * Note that this code will be repeated for each byte demanded + * for the rest of the segment. We use a nonvolatile flag to ensure + * that only one warning message appears. + */ + if (! *(state->printed_eod_ptr)) { + WARNMS(state->cinfo, JWRN_HIT_MARKER); + *(state->printed_eod_ptr) = TRUE; + } + c = 0; /* insert a zero byte into bit buffer */ + } + } + + /* OK, load c into get_buffer */ + get_buffer = (get_buffer << 8) | c; + bits_left += 8; + } + + /* Unload the local registers */ + state->next_input_byte = next_input_byte; + state->bytes_in_buffer = bytes_in_buffer; + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + return TRUE; +} + + +/* + * Out-of-line code for Huffman code decoding. + * See jdhuff.h for info about usage. + */ + +GLOBAL int +jpeg_huff_decode (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits) +{ + register int l = min_bits; + register INT32 code; + + /* HUFF_DECODE has determined that the code is at least min_bits */ + /* bits long, so fetch that many bits in one swoop. */ + + CHECK_BIT_BUFFER(*state, l, return -1); + code = GET_BITS(l); + + /* Collect the rest of the Huffman code one bit at a time. */ + /* This is per Figure F.16 in the JPEG spec. */ + + while (code > htbl->maxcode[l]) { + code <<= 1; + CHECK_BIT_BUFFER(*state, 1, return -1); + code |= GET_BITS(1); + l++; + } + + /* Unload the local registers */ + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + /* With garbage input we may reach the sentinel value l = 17. */ + + if (l > 16) { + WARNMS(state->cinfo, JWRN_HUFF_BAD_CODE); + return 0; /* fake a zero as the safest result */ + } + + return htbl->pub->huffval[ htbl->valptr[l] + + ((int) (code - htbl->mincode[l])) ]; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x)) + +#else + +#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ + { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ + { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, + ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, + ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, + ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL boolean +process_restart (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Next segment can get another out-of-data warning */ + entropy->bitstate.printed_eod = FALSE; + + return TRUE; +} + + +/* + * Decode and return one MCU's worth of Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA HAS BEEN ZEROED BY THE CALLER. + * (Wholesale zeroing is usually a little faster than retail...) + * + * Returns FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * this module, since we'll just re-assign them on the next call.) + */ + +METHODDEF boolean +decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int s, k, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * dctbl; + d_derived_tbl * actbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + dctbl = entropy->dc_derived_tbls[compptr->dc_tbl_no]; + actbl = entropy->ac_derived_tbls[compptr->ac_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, dctbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + /* Shortcut if component's values are not interesting */ + if (! compptr->component_needed) + goto skip_ACs; + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ + (*block)[0] = (JCOEF) s; + + /* Do we need to decode the AC coefficients for this component? */ + if (compptr->DCT_scaled_size > 1) { + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label2); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + (*block)[jpeg_natural_order[k]] = (JCOEF) s; + } else { + if (r != 15) + break; + k += 15; + } + } + + } else { +skip_ACs: + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label3); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } else { + if (r != 15) + break; + k += 15; + } + } + + } + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Module initialization routine for Huffman entropy decoding. + */ + +GLOBAL void +jinit_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_huff_decoder; + entropy->pub.decode_mcu = decode_mcu; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + } +} diff --git a/tools/urt/libs/jpeg6/jdhuff.h b/tools/urt/libs/jpeg6/jdhuff.h new file mode 100644 index 00000000..65f3054f --- /dev/null +++ b/tools/urt/libs/jpeg6/jdhuff.h @@ -0,0 +1,202 @@ +/* + * jdhuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy decoding routines + * that are shared between the sequential decoder (jdhuff.c) and the + * progressive decoder (jdphuff.c). No other modules need to see these. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_d_derived_tbl jMkDDerived +#define jpeg_fill_bit_buffer jFilBitBuf +#define jpeg_huff_decode jHufDecode +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Derived data constructed for each Huffman table */ + +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ + +typedef struct { + /* Basic tables: (element [0] of each array is unused) */ + INT32 mincode[17]; /* smallest code of length k */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ + int valptr[17]; /* huffval[] index of 1st symbol of length k */ + + /* Link to public Huffman table (needed only in jpeg_huff_decode) */ + JHUFF_TBL *pub; + + /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of + * the input data stream. If the next Huffman code is no more + * than HUFF_LOOKAHEAD bits long, we can obtain its length and + * the corresponding symbol directly from these tables. + */ + int look_nbits[1< 32 bits on your machine, and shifting/masking longs is + * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE + * appropriately should be a win. Unfortunately we can't do this with + * something like #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8) + * because not all machines measure sizeof in 8-bit bytes. + */ + +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + boolean printed_eod; /* flag to suppress multiple warning msgs */ +} bitread_perm_state; + +typedef struct { /* Bitreading working state within an MCU */ + /* current data source state */ + const JOCTET * next_input_byte; /* => next byte to read from source */ + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + int unread_marker; /* nonzero if we have hit a marker */ + /* bit input buffer --- note these values are kept in register variables, + * not in this struct, inside the inner loops. + */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + /* pointers needed by jpeg_fill_bit_buffer */ + j_decompress_ptr cinfo; /* back link to decompress master record */ + boolean * printed_eod_ptr; /* => flag in permanent state */ +} bitread_working_state; + +/* Macros to declare and load/save bitread local variables. */ +#define BITREAD_STATE_VARS \ + register bit_buf_type get_buffer; \ + register int bits_left; \ + bitread_working_state br_state + +#define BITREAD_LOAD_STATE(cinfop,permstate) \ + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + br_state.unread_marker = cinfop->unread_marker; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; \ + br_state.printed_eod_ptr = & permstate.printed_eod + +#define BITREAD_SAVE_STATE(cinfop,permstate) \ + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + cinfop->unread_marker = br_state.unread_marker; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left + +/* + * These macros provide the in-line portion of bit fetching. + * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer + * before using GET_BITS, PEEK_BITS, or DROP_BITS. + * The variables get_buffer and bits_left are assumed to be locals, + * but the state struct might not be (jpeg_huff_decode needs this). + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. + * val = GET_BITS(n); + * Fetch next N bits. + * val = PEEK_BITS(n); + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. + * The value N should be a simple variable, not an expression, because it + * is evaluated multiple times. + */ + +#define CHECK_BIT_BUFFER(state,nbits,action) \ + { if (bits_left < (nbits)) { \ + if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ + { action; } \ + get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } + +#define GET_BITS(nbits) \ + (((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1)) + +#define PEEK_BITS(nbits) \ + (((int) (get_buffer >> (bits_left - (nbits)))) & ((1<<(nbits))-1)) + +#define DROP_BITS(nbits) \ + (bits_left -= (nbits)) + +/* Load up the bit buffer to a depth of at least nbits */ +EXTERN boolean jpeg_fill_bit_buffer JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits)); + + +/* + * Code for extracting next Huffman-coded symbol from input bit stream. + * Again, this is time-critical and we make the main paths be macros. + * + * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits + * without looping. Usually, more than 95% of the Huffman codes will be 8 + * or fewer bits long. The few overlength codes are handled with a loop, + * which need not be inline code. + * + * Notes about the HUFF_DECODE macro: + * 1. Near the end of the data segment, we may fail to get enough bits + * for a lookahead. In that case, we do it the hard way. + * 2. If the lookahead table contains no entry, the next code must be + * more than HUFF_LOOKAHEAD bits long. + * 3. jpeg_huff_decode returns -1 if forced to suspend. + */ + +#define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \ +{ register int nb, look; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + nb = 1; goto slowlabel; \ + } \ + } \ + look = PEEK_BITS(HUFF_LOOKAHEAD); \ + if ((nb = htbl->look_nbits[look]) != 0) { \ + DROP_BITS(nb); \ + result = htbl->look_sym[look]; \ + } else { \ + nb = HUFF_LOOKAHEAD+1; \ +slowlabel: \ + if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \ + { failaction; } \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + } \ +} + +/* Out-of-line case for Huffman code fetching */ +EXTERN int jpeg_huff_decode JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits)); diff --git a/tools/urt/libs/jpeg6/jdinput.cpp b/tools/urt/libs/jpeg6/jdinput.cpp new file mode 100644 index 00000000..4def2162 --- /dev/null +++ b/tools/urt/libs/jpeg6/jdinput.cpp @@ -0,0 +1,762 @@ +/* + + * jdinput.c + + * + + * Copyright (C) 1991-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains input control logic for the JPEG decompressor. + + * These routines are concerned with controlling the decompressor's input + + * processing (marker reading and coefficient decoding). The actual input + + * reading is done in jdmarker.c, jdhuff.c, and jdphuff.c. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* Private state */ + + + +typedef struct { + + struct jpeg_input_controller pub; /* public fields */ + + + + boolean inheaders; /* TRUE until first SOS is reached */ + +} my_input_controller; + + + +typedef my_input_controller * my_inputctl_ptr; + + + + + +/* Forward declarations */ + +METHODDEF int consume_markers JPP((j_decompress_ptr cinfo)); + + + + + +/* + + * Routines to calculate various quantities related to the size of the image. + + */ + + + +LOCAL void + +initial_setup (j_decompress_ptr cinfo) + +/* Called once, when first SOS marker is reached */ + +{ + + int ci; + + jpeg_component_info *compptr; + + + + /* Make sure image isn't bigger than I can handle */ + + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + + + /* For now, precision must match compiled-in value... */ + + if (cinfo->data_precision != BITS_IN_JSAMPLE) + + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + + + /* Check that number of components won't exceed internal array sizes */ + + if (cinfo->num_components > MAX_COMPONENTS) + + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + + MAX_COMPONENTS); + + + + /* Compute maximum sampling factors; check factor validity */ + + cinfo->max_h_samp_factor = 1; + + cinfo->max_v_samp_factor = 1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + + ERREXIT(cinfo, JERR_BAD_SAMPLING); + + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + + compptr->h_samp_factor); + + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + + compptr->v_samp_factor); + + } + + + + /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE. + + * In the full decompressor, this will be overridden by jdmaster.c; + + * but in the transcoder, jdmaster.c is not used, so we must do it here. + + */ + + cinfo->min_DCT_scaled_size = DCTSIZE; + + + + /* Compute dimensions of components */ + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + compptr->DCT_scaled_size = DCTSIZE; + + /* Size in DCT blocks */ + + compptr->width_in_blocks = (JDIMENSION) + + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + + compptr->height_in_blocks = (JDIMENSION) + + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + + /* downsampled_width and downsampled_height will also be overridden by + + * jdmaster.c if we are doing full decompression. The transcoder library + + * doesn't use these values, but the calling application might. + + */ + + /* Size in samples */ + + compptr->downsampled_width = (JDIMENSION) + + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + + (long) cinfo->max_h_samp_factor); + + compptr->downsampled_height = (JDIMENSION) + + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + + (long) cinfo->max_v_samp_factor); + + /* Mark component needed, until color conversion says otherwise */ + + compptr->component_needed = TRUE; + + /* Mark no quantization table yet saved for component */ + + compptr->quant_table = NULL; + + } + + + + /* Compute number of fully interleaved MCU rows. */ + + cinfo->total_iMCU_rows = (JDIMENSION) + + jdiv_round_up((long) cinfo->image_height, + + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + + + /* Decide whether file contains multiple scans */ + + if (cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode) + + cinfo->inputctl->has_multiple_scans = TRUE; + + else + + cinfo->inputctl->has_multiple_scans = FALSE; + +} + + + + + +LOCAL void + +per_scan_setup (j_decompress_ptr cinfo) + +/* Do computations that are needed before processing a JPEG scan */ + +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */ + +{ + + int ci, mcublks, tmp; + + jpeg_component_info *compptr; + + + + if (cinfo->comps_in_scan == 1) { + + + + /* Noninterleaved (single-component) scan */ + + compptr = cinfo->cur_comp_info[0]; + + + + /* Overall image size in MCUs */ + + cinfo->MCUs_per_row = compptr->width_in_blocks; + + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + + + /* For noninterleaved scan, always one block per MCU */ + + compptr->MCU_width = 1; + + compptr->MCU_height = 1; + + compptr->MCU_blocks = 1; + + compptr->MCU_sample_width = compptr->DCT_scaled_size; + + compptr->last_col_width = 1; + + /* For noninterleaved scans, it is convenient to define last_row_height + + * as the number of block rows present in the last iMCU row. + + */ + + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + + if (tmp == 0) tmp = compptr->v_samp_factor; + + compptr->last_row_height = tmp; + + + + /* Prepare array describing MCU composition */ + + cinfo->blocks_in_MCU = 1; + + cinfo->MCU_membership[0] = 0; + + + + } else { + + + + /* Interleaved (multi-component) scan */ + + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + + MAX_COMPS_IN_SCAN); + + + + /* Overall image size in MCUs */ + + cinfo->MCUs_per_row = (JDIMENSION) + + jdiv_round_up((long) cinfo->image_width, + + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + + cinfo->MCU_rows_in_scan = (JDIMENSION) + + jdiv_round_up((long) cinfo->image_height, + + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + + + cinfo->blocks_in_MCU = 0; + + + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + + compptr = cinfo->cur_comp_info[ci]; + + /* Sampling factors give # of blocks of component in each MCU */ + + compptr->MCU_width = compptr->h_samp_factor; + + compptr->MCU_height = compptr->v_samp_factor; + + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_scaled_size; + + /* Figure number of non-dummy blocks in last MCU column & row */ + + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + + if (tmp == 0) tmp = compptr->MCU_width; + + compptr->last_col_width = tmp; + + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + + if (tmp == 0) tmp = compptr->MCU_height; + + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + + mcublks = compptr->MCU_blocks; + + if (cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU) + + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + + while (mcublks-- > 0) { + + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + + } + + } + + + + } + +} + + + + + +/* + + * Save away a copy of the Q-table referenced by each component present + + * in the current scan, unless already saved during a prior scan. + + * + + * In a multiple-scan JPEG file, the encoder could assign different components + + * the same Q-table slot number, but change table definitions between scans + + * so that each component uses a different Q-table. (The IJG encoder is not + + * currently capable of doing this, but other encoders might.) Since we want + + * to be able to dequantize all the components at the end of the file, this + + * means that we have to save away the table actually used for each component. + + * We do this by copying the table at the start of the first scan containing + + * the component. + + * The JPEG spec prohibits the encoder from changing the contents of a Q-table + + * slot between scans of a component using that slot. If the encoder does so + + * anyway, this decoder will simply use the Q-table values that were current + + * at the start of the first scan for the component. + + * + + * The decompressor output side looks only at the saved quant tables, + + * not at the current Q-table slots. + + */ + + + +LOCAL void + +latch_quant_tables (j_decompress_ptr cinfo) + +{ + + int ci, qtblno; + + jpeg_component_info *compptr; + + JQUANT_TBL * qtbl; + + + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + + compptr = cinfo->cur_comp_info[ci]; + + /* No work if we already saved Q-table for this component */ + + if (compptr->quant_table != NULL) + + continue; + + /* Make sure specified quantization table is present */ + + qtblno = compptr->quant_tbl_no; + + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + + cinfo->quant_tbl_ptrs[qtblno] == NULL) + + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + + /* OK, save away the quantization table */ + + qtbl = (JQUANT_TBL *) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + SIZEOF(JQUANT_TBL)); + + MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF(JQUANT_TBL)); + + compptr->quant_table = qtbl; + + } + +} + + + + + +/* + + * Initialize the input modules to read a scan of compressed data. + + * The first call to this is done by jdmaster.c after initializing + + * the entire decompressor (during jpeg_start_decompress). + + * Subsequent calls come from consume_markers, below. + + */ + + + +METHODDEF void + +start_input_pass (j_decompress_ptr cinfo) + +{ + + per_scan_setup(cinfo); + + latch_quant_tables(cinfo); + + (*cinfo->entropy->start_pass) (cinfo); + + (*cinfo->coef->start_input_pass) (cinfo); + + cinfo->inputctl->consume_input = cinfo->coef->consume_data; + +} + + + + + +/* + + * Finish up after inputting a compressed-data scan. + + * This is called by the coefficient controller after it's read all + + * the expected data of the scan. + + */ + + + +METHODDEF void + +finish_input_pass (j_decompress_ptr cinfo) + +{ + + cinfo->inputctl->consume_input = consume_markers; + +} + + + + + +/* + + * Read JPEG markers before, between, or after compressed-data scans. + + * Change state as necessary when a new scan is reached. + + * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + + * + + * The consume_input method pointer points either here or to the + + * coefficient controller's consume_data routine, depending on whether + + * we are reading a compressed data segment or inter-segment markers. + + */ + + + +METHODDEF int + +consume_markers (j_decompress_ptr cinfo) + +{ + + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + int val; + + + + if (inputctl->pub.eoi_reached) /* After hitting EOI, read no further */ + + return JPEG_REACHED_EOI; + + + + val = (*cinfo->marker->read_markers) (cinfo); + + + + switch (val) { + + case JPEG_REACHED_SOS: /* Found SOS */ + + if (inputctl->inheaders) { /* 1st SOS */ + + initial_setup(cinfo); + + inputctl->inheaders = FALSE; + + /* Note: start_input_pass must be called by jdmaster.c + + * before any more input can be consumed. jdapi.c is + + * responsible for enforcing this sequencing. + + */ + + } else { /* 2nd or later SOS marker */ + + if (! inputctl->pub.has_multiple_scans) + + ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */ + + start_input_pass(cinfo); + + } + + break; + + case JPEG_REACHED_EOI: /* Found EOI */ + + inputctl->pub.eoi_reached = TRUE; + + if (inputctl->inheaders) { /* Tables-only datastream, apparently */ + + if (cinfo->marker->saw_SOF) + + ERREXIT(cinfo, JERR_SOF_NO_SOS); + + } else { + + /* Prevent infinite loop in coef ctlr's decompress_data routine + + * if user set output_scan_number larger than number of scans. + + */ + + if (cinfo->output_scan_number > cinfo->input_scan_number) + + cinfo->output_scan_number = cinfo->input_scan_number; + + } + + break; + + case JPEG_SUSPENDED: + + break; + + } + + + + return val; + +} + + + + + +/* + + * Reset state to begin a fresh datastream. + + */ + + + +METHODDEF void + +reset_input_controller (j_decompress_ptr cinfo) + +{ + + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + + + inputctl->pub.consume_input = consume_markers; + + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + + inputctl->pub.eoi_reached = FALSE; + + inputctl->inheaders = TRUE; + + /* Reset other modules */ + + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + + (*cinfo->marker->reset_marker_reader) (cinfo); + + /* Reset progression state -- would be cleaner if entropy decoder did this */ + + cinfo->coef_bits = NULL; + +} + + + + + +/* + + * Initialize the input controller module. + + * This is called only once, when the decompression object is created. + + */ + + + +GLOBAL void + +jinit_input_controller (j_decompress_ptr cinfo) + +{ + + my_inputctl_ptr inputctl; + + + + /* Create subobject in permanent pool */ + + inputctl = (my_inputctl_ptr) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + + SIZEOF(my_input_controller)); + + cinfo->inputctl = (struct jpeg_input_controller *) inputctl; + + /* Initialize method pointers */ + + inputctl->pub.consume_input = consume_markers; + + inputctl->pub.reset_input_controller = reset_input_controller; + + inputctl->pub.start_input_pass = start_input_pass; + + inputctl->pub.finish_input_pass = finish_input_pass; + + /* Initialize state: can't use reset_input_controller since we don't + + * want to try to reset other modules yet. + + */ + + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + + inputctl->pub.eoi_reached = FALSE; + + inputctl->inheaders = TRUE; + +} + diff --git a/tools/urt/libs/jpeg6/jdmainct.cpp b/tools/urt/libs/jpeg6/jdmainct.cpp new file mode 100644 index 00000000..7b4c2559 --- /dev/null +++ b/tools/urt/libs/jpeg6/jdmainct.cpp @@ -0,0 +1,1024 @@ +/* + + * jdmainct.c + + * + + * Copyright (C) 1994-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains the main buffer controller for decompression. + + * The main buffer lies between the JPEG decompressor proper and the + + * post-processor; it holds downsampled data in the JPEG colorspace. + + * + + * Note that this code is bypassed in raw-data mode, since the application + + * supplies the equivalent of the main buffer in that case. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* + + * In the current system design, the main buffer need never be a full-image + + * buffer; any full-height buffers will be found inside the coefficient or + + * postprocessing controllers. Nonetheless, the main controller is not + + * trivial. Its responsibility is to provide context rows for upsampling/ + + * rescaling, and doing this in an efficient fashion is a bit tricky. + + * + + * Postprocessor input data is counted in "row groups". A row group + + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + + * sample rows of each component. (We require DCT_scaled_size values to be + + * chosen such that these numbers are integers. In practice DCT_scaled_size + + * values will likely be powers of two, so we actually have the stronger + + * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) + + * Upsampling will typically produce max_v_samp_factor pixel rows from each + + * row group (times any additional scale factor that the upsampler is + + * applying). + + * + + * The coefficient controller will deliver data to us one iMCU row at a time; + + * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or + + * exactly min_DCT_scaled_size row groups. (This amount of data corresponds + + * to one row of MCUs when the image is fully interleaved.) Note that the + + * number of sample rows varies across components, but the number of row + + * groups does not. Some garbage sample rows may be included in the last iMCU + + * row at the bottom of the image. + + * + + * Depending on the vertical scaling algorithm used, the upsampler may need + + * access to the sample row(s) above and below its current input row group. + + * The upsampler is required to set need_context_rows TRUE at global selection + + * time if so. When need_context_rows is FALSE, this controller can simply + + * obtain one iMCU row at a time from the coefficient controller and dole it + + * out as row groups to the postprocessor. + + * + + * When need_context_rows is TRUE, this controller guarantees that the buffer + + * passed to postprocessing contains at least one row group's worth of samples + + * above and below the row group(s) being processed. Note that the context + + * rows "above" the first passed row group appear at negative row offsets in + + * the passed buffer. At the top and bottom of the image, the required + + * context rows are manufactured by duplicating the first or last real sample + + * row; this avoids having special cases in the upsampling inner loops. + + * + + * The amount of context is fixed at one row group just because that's a + + * convenient number for this controller to work with. The existing + + * upsamplers really only need one sample row of context. An upsampler + + * supporting arbitrary output rescaling might wish for more than one row + + * group of context when shrinking the image; tough, we don't handle that. + + * (This is justified by the assumption that downsizing will be handled mostly + + * by adjusting the DCT_scaled_size values, so that the actual scale factor at + + * the upsample step needn't be much less than one.) + + * + + * To provide the desired context, we have to retain the last two row groups + + * of one iMCU row while reading in the next iMCU row. (The last row group + + * can't be processed until we have another row group for its below-context, + + * and so we have to save the next-to-last group too for its above-context.) + + * We could do this most simply by copying data around in our buffer, but + + * that'd be very slow. We can avoid copying any data by creating a rather + + * strange pointer structure. Here's how it works. We allocate a workspace + + * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number + + * of row groups per iMCU row). We create two sets of redundant pointers to + + * the workspace. Labeling the physical row groups 0 to M+1, the synthesized + + * pointer lists look like this: + + * M+1 M-1 + + * master pointer --> 0 master pointer --> 0 + + * 1 1 + + * ... ... + + * M-3 M-3 + + * M-2 M + + * M-1 M+1 + + * M M-2 + + * M+1 M-1 + + * 0 0 + + * We read alternate iMCU rows using each master pointer; thus the last two + + * row groups of the previous iMCU row remain un-overwritten in the workspace. + + * The pointer lists are set up so that the required context rows appear to + + * be adjacent to the proper places when we pass the pointer lists to the + + * upsampler. + + * + + * The above pictures describe the normal state of the pointer lists. + + * At top and bottom of the image, we diddle the pointer lists to duplicate + + * the first or last sample row as necessary (this is cheaper than copying + + * sample rows around). + + * + + * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1. In that + + * situation each iMCU row provides only one row group so the buffering logic + + * must be different (eg, we must read two iMCU rows before we can emit the + + * first row group). For now, we simply do not support providing context + + * rows when min_DCT_scaled_size is 1. That combination seems unlikely to + + * be worth providing --- if someone wants a 1/8th-size preview, they probably + + * want it quick and dirty, so a context-free upsampler is sufficient. + + */ + + + + + +/* Private buffer controller object */ + + + +typedef struct { + + struct jpeg_d_main_controller pub; /* public fields */ + + + + /* Pointer to allocated workspace (M or M+2 row groups). */ + + JSAMPARRAY buffer[MAX_COMPONENTS]; + + + + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + + JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ + + + + /* Remaining fields are only used in the context case. */ + + + + /* These are the master pointers to the funny-order pointer lists. */ + + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + + + + int whichptr; /* indicates which pointer set is now in use */ + + int context_state; /* process_data state machine status */ + + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + + JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ + +} my_main_controller; + + + +typedef my_main_controller * my_main_ptr; + + + +/* context_state values: */ + +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ + +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ + +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ + + + + + +/* Forward declarations */ + +METHODDEF void process_data_simple_main + + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); + +METHODDEF void process_data_context_main + + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF void process_data_crank_post + + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); + +#endif + + + + + +LOCAL void + +alloc_funny_pointers (j_decompress_ptr cinfo) + +/* Allocate space for the funny pointer lists. + + * This is done only once, not once per pass. + + */ + +{ + + my_main_ptr main = (my_main_ptr) cinfo->main; + + int ci, rgroup; + + int M = cinfo->min_DCT_scaled_size; + + jpeg_component_info *compptr; + + JSAMPARRAY xbuf; + + + + /* Get top-level space for component array pointers. + + * We alloc both arrays with one call to save a few cycles. + + */ + + main->xbuffer[0] = (JSAMPIMAGE) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + cinfo->num_components * 2 * SIZEOF(JSAMPARRAY)); + + main->xbuffer[1] = main->xbuffer[0] + cinfo->num_components; + + + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + + /* Get space for pointer lists --- M+4 row groups in each list. + + * We alloc both pointer lists with one call to save a few cycles. + + */ + + xbuf = (JSAMPARRAY) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + 2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW)); + + xbuf += rgroup; /* want one row group at negative offsets */ + + main->xbuffer[0][ci] = xbuf; + + xbuf += rgroup * (M + 4); + + main->xbuffer[1][ci] = xbuf; + + } + +} + + + + + +LOCAL void + +make_funny_pointers (j_decompress_ptr cinfo) + +/* Create the funny pointer lists discussed in the comments above. + + * The actual workspace is already allocated (in main->buffer), + + * and the space for the pointer lists is allocated too. + + * This routine just fills in the curiously ordered lists. + + * This will be repeated at the beginning of each pass. + + */ + +{ + + my_main_ptr main = (my_main_ptr) cinfo->main; + + int ci, i, rgroup; + + int M = cinfo->min_DCT_scaled_size; + + jpeg_component_info *compptr; + + JSAMPARRAY buf, xbuf0, xbuf1; + + + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + + xbuf0 = main->xbuffer[0][ci]; + + xbuf1 = main->xbuffer[1][ci]; + + /* First copy the workspace pointers as-is */ + + buf = main->buffer[ci]; + + for (i = 0; i < rgroup * (M + 2); i++) { + + xbuf0[i] = xbuf1[i] = buf[i]; + + } + + /* In the second list, put the last four row groups in swapped order */ + + for (i = 0; i < rgroup * 2; i++) { + + xbuf1[rgroup*(M-2) + i] = buf[rgroup*M + i]; + + xbuf1[rgroup*M + i] = buf[rgroup*(M-2) + i]; + + } + + /* The wraparound pointers at top and bottom will be filled later + + * (see set_wraparound_pointers, below). Initially we want the "above" + + * pointers to duplicate the first actual data line. This only needs + + * to happen in xbuffer[0]. + + */ + + for (i = 0; i < rgroup; i++) { + + xbuf0[i - rgroup] = xbuf0[0]; + + } + + } + +} + + + + + +LOCAL void + +set_wraparound_pointers (j_decompress_ptr cinfo) + +/* Set up the "wraparound" pointers at top and bottom of the pointer lists. + + * This changes the pointer list state from top-of-image to the normal state. + + */ + +{ + + my_main_ptr main = (my_main_ptr) cinfo->main; + + int ci, i, rgroup; + + int M = cinfo->min_DCT_scaled_size; + + jpeg_component_info *compptr; + + JSAMPARRAY xbuf0, xbuf1; + + + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + + xbuf0 = main->xbuffer[0][ci]; + + xbuf1 = main->xbuffer[1][ci]; + + for (i = 0; i < rgroup; i++) { + + xbuf0[i - rgroup] = xbuf0[rgroup*(M+1) + i]; + + xbuf1[i - rgroup] = xbuf1[rgroup*(M+1) + i]; + + xbuf0[rgroup*(M+2) + i] = xbuf0[i]; + + xbuf1[rgroup*(M+2) + i] = xbuf1[i]; + + } + + } + +} + + + + + +LOCAL void + +set_bottom_pointers (j_decompress_ptr cinfo) + +/* Change the pointer lists to duplicate the last sample row at the bottom + + * of the image. whichptr indicates which xbuffer holds the final iMCU row. + + * Also sets rowgroups_avail to indicate number of nondummy row groups in row. + + */ + +{ + + my_main_ptr main = (my_main_ptr) cinfo->main; + + int ci, i, rgroup, iMCUheight, rows_left; + + jpeg_component_info *compptr; + + JSAMPARRAY xbuf; + + + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + /* Count sample rows in one iMCU row and in one row group */ + + iMCUheight = compptr->v_samp_factor * compptr->DCT_scaled_size; + + rgroup = iMCUheight / cinfo->min_DCT_scaled_size; + + /* Count nondummy sample rows remaining for this component */ + + rows_left = (int) (compptr->downsampled_height % (JDIMENSION) iMCUheight); + + if (rows_left == 0) rows_left = iMCUheight; + + /* Count nondummy row groups. Should get same answer for each component, + + * so we need only do it once. + + */ + + if (ci == 0) { + + main->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1); + + } + + /* Duplicate the last real sample row rgroup*2 times; this pads out the + + * last partial rowgroup and ensures at least one full rowgroup of context. + + */ + + xbuf = main->xbuffer[main->whichptr][ci]; + + for (i = 0; i < rgroup * 2; i++) { + + xbuf[rows_left + i] = xbuf[rows_left-1]; + + } + + } + +} + + + + + +/* + + * Initialize for a processing pass. + + */ + + + +METHODDEF void + +start_pass_main (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) + +{ + + my_main_ptr main = (my_main_ptr) cinfo->main; + + + + switch (pass_mode) { + + case JBUF_PASS_THRU: + + if (cinfo->upsample->need_context_rows) { + + main->pub.process_data = process_data_context_main; + + make_funny_pointers(cinfo); /* Create the xbuffer[] lists */ + + main->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ + + main->context_state = CTX_PREPARE_FOR_IMCU; + + main->iMCU_row_ctr = 0; + + } else { + + /* Simple case with no context needed */ + + main->pub.process_data = process_data_simple_main; + + } + + main->buffer_full = FALSE; /* Mark buffer empty */ + + main->rowgroup_ctr = 0; + + break; + +#ifdef QUANT_2PASS_SUPPORTED + + case JBUF_CRANK_DEST: + + /* For last pass of 2-pass quantization, just crank the postprocessor */ + + main->pub.process_data = process_data_crank_post; + + break; + +#endif + + default: + + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + break; + + } + +} + + + + + +/* + + * Process some data. + + * This handles the simple case where no context is required. + + */ + + + +METHODDEF void + +process_data_simple_main (j_decompress_ptr cinfo, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail) + +{ + + my_main_ptr main = (my_main_ptr) cinfo->main; + + JDIMENSION rowgroups_avail; + + + + /* Read input data if we haven't filled the main buffer yet */ + + if (! main->buffer_full) { + + if (! (*cinfo->coef->decompress_data) (cinfo, main->buffer)) + + return; /* suspension forced, can do nothing more */ + + main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + + } + + + + /* There are always min_DCT_scaled_size row groups in an iMCU row. */ + + rowgroups_avail = (JDIMENSION) cinfo->min_DCT_scaled_size; + + /* Note: at the bottom of the image, we may pass extra garbage row groups + + * to the postprocessor. The postprocessor has to check for bottom + + * of image anyway (at row resolution), so no point in us doing it too. + + */ + + + + /* Feed the postprocessor */ + + (*cinfo->post->post_process_data) (cinfo, main->buffer, + + &main->rowgroup_ctr, rowgroups_avail, + + output_buf, out_row_ctr, out_rows_avail); + + + + /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ + + if (main->rowgroup_ctr >= rowgroups_avail) { + + main->buffer_full = FALSE; + + main->rowgroup_ctr = 0; + + } + +} + + + + + +/* + + * Process some data. + + * This handles the case where context rows must be provided. + + */ + + + +METHODDEF void + +process_data_context_main (j_decompress_ptr cinfo, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail) + +{ + + my_main_ptr main = (my_main_ptr) cinfo->main; + + + + /* Read input data if we haven't filled the main buffer yet */ + + if (! main->buffer_full) { + + if (! (*cinfo->coef->decompress_data) (cinfo, + + main->xbuffer[main->whichptr])) + + return; /* suspension forced, can do nothing more */ + + main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + + main->iMCU_row_ctr++; /* count rows received */ + + } + + + + /* Postprocessor typically will not swallow all the input data it is handed + + * in one call (due to filling the output buffer first). Must be prepared + + * to exit and restart. This switch lets us keep track of how far we got. + + * Note that each case falls through to the next on successful completion. + + */ + + switch (main->context_state) { + + case CTX_POSTPONED_ROW: + + /* Call postprocessor using previously set pointers for postponed row */ + + (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], + + &main->rowgroup_ctr, main->rowgroups_avail, + + output_buf, out_row_ctr, out_rows_avail); + + if (main->rowgroup_ctr < main->rowgroups_avail) + + return; /* Need to suspend */ + + main->context_state = CTX_PREPARE_FOR_IMCU; + + if (*out_row_ctr >= out_rows_avail) + + return; /* Postprocessor exactly filled output buf */ + + /*FALLTHROUGH*/ + + case CTX_PREPARE_FOR_IMCU: + + /* Prepare to process first M-1 row groups of this iMCU row */ + + main->rowgroup_ctr = 0; + + main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size - 1); + + /* Check for bottom of image: if so, tweak pointers to "duplicate" + + * the last sample row, and adjust rowgroups_avail to ignore padding rows. + + */ + + if (main->iMCU_row_ctr == cinfo->total_iMCU_rows) + + set_bottom_pointers(cinfo); + + main->context_state = CTX_PROCESS_IMCU; + + /*FALLTHROUGH*/ + + case CTX_PROCESS_IMCU: + + /* Call postprocessor using previously set pointers */ + + (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], + + &main->rowgroup_ctr, main->rowgroups_avail, + + output_buf, out_row_ctr, out_rows_avail); + + if (main->rowgroup_ctr < main->rowgroups_avail) + + return; /* Need to suspend */ + + /* After the first iMCU, change wraparound pointers to normal state */ + + if (main->iMCU_row_ctr == 1) + + set_wraparound_pointers(cinfo); + + /* Prepare to load new iMCU row using other xbuffer list */ + + main->whichptr ^= 1; /* 0=>1 or 1=>0 */ + + main->buffer_full = FALSE; + + /* Still need to process last row group of this iMCU row, */ + + /* which is saved at index M+1 of the other xbuffer */ + + main->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_scaled_size + 1); + + main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size + 2); + + main->context_state = CTX_POSTPONED_ROW; + + } + +} + + + + + +/* + + * Process some data. + + * Final pass of two-pass quantization: just call the postprocessor. + + * Source data will be the postprocessor controller's internal buffer. + + */ + + + +#ifdef QUANT_2PASS_SUPPORTED + + + +METHODDEF void + +process_data_crank_post (j_decompress_ptr cinfo, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail) + +{ + + (*cinfo->post->post_process_data) (cinfo, (JSAMPIMAGE) NULL, + + (JDIMENSION *) NULL, (JDIMENSION) 0, + + output_buf, out_row_ctr, out_rows_avail); + +} + + + +#endif /* QUANT_2PASS_SUPPORTED */ + + + + + +/* + + * Initialize main buffer controller. + + */ + + + +GLOBAL void + +jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer) + +{ + + my_main_ptr main; + + int ci, rgroup, ngroups; + + jpeg_component_info *compptr; + + + + main = (my_main_ptr) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + SIZEOF(my_main_controller)); + + cinfo->main = (struct jpeg_d_main_controller *) main; + + main->pub.start_pass = start_pass_main; + + + + if (need_full_buffer) /* shouldn't happen */ + + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + + + /* Allocate the workspace. + + * ngroups is the number of row groups we need. + + */ + + if (cinfo->upsample->need_context_rows) { + + if (cinfo->min_DCT_scaled_size < 2) /* unsupported, see comments above */ + + ERREXIT(cinfo, JERR_NOTIMPL); + + alloc_funny_pointers(cinfo); /* Alloc space for xbuffer[] lists */ + + ngroups = cinfo->min_DCT_scaled_size + 2; + + } else { + + ngroups = cinfo->min_DCT_scaled_size; + + } + + + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + + main->buffer[ci] = (*cinfo->mem->alloc_sarray) + + ((j_common_ptr) cinfo, JPOOL_IMAGE, + + compptr->width_in_blocks * compptr->DCT_scaled_size, + + (JDIMENSION) (rgroup * ngroups)); + + } + +} + diff --git a/tools/urt/libs/jpeg6/jdmarker.cpp b/tools/urt/libs/jpeg6/jdmarker.cpp new file mode 100644 index 00000000..15760981 --- /dev/null +++ b/tools/urt/libs/jpeg6/jdmarker.cpp @@ -0,0 +1,1052 @@ +/* + * jdmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to decode JPEG datastream markers. + * Most of the complexity arises from our desire to support input + * suspension: if not all of the data for a marker is available, + * we must exit back to the application. On resumption, we reprocess + * the marker. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Macros for fetching data from the data source module. + * + * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect + * the current restart point; we update them only when we have reached a + * suitable place to restart if a suspension occurs. + */ + +/* Declare and initialize local copies of input pointer/count */ +#define INPUT_VARS(cinfo) \ + struct jpeg_source_mgr * datasrc = (cinfo)->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer + +/* Unload the local copies --- do this only at a restart boundary */ +#define INPUT_SYNC(cinfo) \ + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) + +/* Reload the local copies --- seldom used except in MAKE_BYTE_AVAIL */ +#define INPUT_RELOAD(cinfo) \ + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) + +/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. + * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, + * but we must reload the local copies after a successful fill. + */ +#define MAKE_BYTE_AVAIL(cinfo,action) \ + if (bytes_in_buffer == 0) { \ + if (! (*datasrc->fill_input_buffer) (cinfo)) \ + { action; } \ + INPUT_RELOAD(cinfo); \ + } \ + bytes_in_buffer-- + +/* Read a byte into variable V. + * If must suspend, take the specified action (typically "return FALSE"). + */ +#define INPUT_BYTE(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + V = GETJOCTET(*next_input_byte++); ) + +/* As above, but read two bytes interpreted as an unsigned 16-bit integer. + * V should be declared unsigned int or perhaps INT32. + */ +#define INPUT_2BYTES(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \ + MAKE_BYTE_AVAIL(cinfo,action); \ + V += GETJOCTET(*next_input_byte++); ) + + +/* + * Routines to process JPEG markers. + * + * Entry condition: JPEG marker itself has been read and its code saved + * in cinfo->unread_marker; input restart point is just after the marker. + * + * Exit: if return TRUE, have read and processed any parameters, and have + * updated the restart point to point after the parameters. + * If return FALSE, was forced to suspend before reaching end of + * marker parameters; restart point has not been moved. Same routine + * will be called again after application supplies more input data. + * + * This approach to suspension assumes that all of a marker's parameters can + * fit into a single input bufferload. This should hold for "normal" + * markers. Some COM/APPn markers might have large parameter segments, + * but we use skip_input_data to get past those, and thereby put the problem + * on the source manager's shoulders. + * + * Note that we don't bother to avoid duplicate trace messages if a + * suspension occurs within marker parameters. Other side effects + * require more care. + */ + + +LOCAL boolean +get_soi (j_decompress_ptr cinfo) +/* Process an SOI marker */ +{ + int i; + + TRACEMS(cinfo, 1, JTRC_SOI); + + if (cinfo->marker->saw_SOI) + ERREXIT(cinfo, JERR_SOI_DUPLICATE); + + /* Reset all parameters that are defined to be reset by SOI */ + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + cinfo->restart_interval = 0; + + /* Set initial assumptions for colorspace etc */ + + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->CCIR601_sampling = FALSE; /* Assume non-CCIR sampling??? */ + + cinfo->saw_JFIF_marker = FALSE; + cinfo->density_unit = 0; /* set default JFIF APP0 values */ + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + + cinfo->marker->saw_SOI = TRUE; + + return TRUE; +} + + +LOCAL boolean +get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith) +/* Process a SOFn marker */ +{ + INT32 length; + int c, ci; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + cinfo->progressive_mode = is_prog; + cinfo->arith_code = is_arith; + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, cinfo->data_precision, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_height, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_width, return FALSE); + INPUT_BYTE(cinfo, cinfo->num_components, return FALSE); + + length -= 8; + + TRACEMS4(cinfo, 1, JTRC_SOF, cinfo->unread_marker, + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components); + + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_DUPLICATE); + + /* We don't support files in which the image height is initially specified */ + /* as 0 and is later redefined by DNL. As long as we have to check that, */ + /* might as well have a general sanity check. */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + if (length != (cinfo->num_components * 3)) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + if (cinfo->comp_info == NULL) /* do only once, even if suspend */ + cinfo->comp_info = (jpeg_component_info *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF(jpeg_component_info)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->component_index = ci; + INPUT_BYTE(cinfo, compptr->component_id, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + compptr->h_samp_factor = (c >> 4) & 15; + compptr->v_samp_factor = (c ) & 15; + INPUT_BYTE(cinfo, compptr->quant_tbl_no, return FALSE); + + TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT, + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no); + } + + cinfo->marker->saw_SOF = TRUE; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_sos (j_decompress_ptr cinfo) +/* Process a SOS marker */ +{ + INT32 length; + int i, ci, n, c, cc; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + if (! cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOS_NO_SOF); + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, n, return FALSE); /* Number of components */ + + if (length != (n * 2 + 6) || n < 1 || n > MAX_COMPS_IN_SCAN) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + TRACEMS1(cinfo, 1, JTRC_SOS, n); + + cinfo->comps_in_scan = n; + + /* Collect the component-spec parameters */ + + for (i = 0; i < n; i++) { + INPUT_BYTE(cinfo, cc, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (cc == compptr->component_id) + goto id_found; + } + + ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc); + + id_found: + + cinfo->cur_comp_info[i] = compptr; + compptr->dc_tbl_no = (c >> 4) & 15; + compptr->ac_tbl_no = (c ) & 15; + + TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc, + compptr->dc_tbl_no, compptr->ac_tbl_no); + } + + /* Collect the additional scan parameters Ss, Se, Ah/Al. */ + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ss = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Se = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ah = (c >> 4) & 15; + cinfo->Al = (c ) & 15; + + TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, + cinfo->Ah, cinfo->Al); + + /* Prepare to scan data & restart markers */ + cinfo->marker->next_restart_num = 0; + + /* Count another SOS marker */ + cinfo->input_scan_number++; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +METHODDEF boolean +get_app0 (j_decompress_ptr cinfo) +/* Process an APP0 marker */ +{ +#define JFIF_LEN 14 + INT32 length; + UINT8 b[JFIF_LEN]; + int buffp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* See if a JFIF APP0 marker is present */ + + if (length >= JFIF_LEN) { + for (buffp = 0; buffp < JFIF_LEN; buffp++) + INPUT_BYTE(cinfo, b[buffp], return FALSE); + length -= JFIF_LEN; + + if (b[0]==0x4A && b[1]==0x46 && b[2]==0x49 && b[3]==0x46 && b[4]==0) { + /* Found JFIF APP0 marker: check version */ + /* Major version must be 1, anything else signals an incompatible change. + * We used to treat this as an error, but now it's a nonfatal warning, + * because some bozo at Hijaak couldn't read the spec. + * Minor version should be 0..2, but process anyway if newer. + */ + if (b[5] != 1) + WARNMS2(cinfo, JWRN_JFIF_MAJOR, b[5], b[6]); + else if (b[6] > 2) + TRACEMS2(cinfo, 1, JTRC_JFIF_MINOR, b[5], b[6]); + /* Save info */ + cinfo->saw_JFIF_marker = TRUE; + cinfo->density_unit = b[7]; + cinfo->X_density = (b[8] << 8) + b[9]; + cinfo->Y_density = (b[10] << 8) + b[11]; + TRACEMS3(cinfo, 1, JTRC_JFIF, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit); + if (b[12] | b[13]) + TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL, b[12], b[13]); + if (length != ((INT32) b[12] * (INT32) b[13] * (INT32) 3)) + TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) length); + } else { + /* Start of APP0 does not match "JFIF" */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) length + JFIF_LEN); + } + } else { + /* Too short to be JFIF marker */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) length); + } + + INPUT_SYNC(cinfo); + if (length > 0) /* skip any remaining data -- could be lots */ + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +METHODDEF boolean +get_app14 (j_decompress_ptr cinfo) +/* Process an APP14 marker */ +{ +#define ADOBE_LEN 12 + INT32 length; + UINT8 b[ADOBE_LEN]; + int buffp; + unsigned int version, flags0, flags1, transform; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* See if an Adobe APP14 marker is present */ + + if (length >= ADOBE_LEN) { + for (buffp = 0; buffp < ADOBE_LEN; buffp++) + INPUT_BYTE(cinfo, b[buffp], return FALSE); + length -= ADOBE_LEN; + + if (b[0]==0x41 && b[1]==0x64 && b[2]==0x6F && b[3]==0x62 && b[4]==0x65) { + /* Found Adobe APP14 marker */ + version = (b[5] << 8) + b[6]; + flags0 = (b[7] << 8) + b[8]; + flags1 = (b[9] << 8) + b[10]; + transform = b[11]; + TRACEMS4(cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform); + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = (UINT8) transform; + } else { + /* Start of APP14 does not match "Adobe" */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) length + ADOBE_LEN); + } + } else { + /* Too short to be Adobe marker */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) length); + } + + INPUT_SYNC(cinfo); + if (length > 0) /* skip any remaining data -- could be lots */ + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +LOCAL boolean +get_dac (j_decompress_ptr cinfo) +/* Process a DAC marker */ +{ + INT32 length; + int index, val; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + INPUT_BYTE(cinfo, val, return FALSE); + + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_DAC, index, val); + + if (index < 0 || index >= (2*NUM_ARITH_TBLS)) + ERREXIT1(cinfo, JERR_DAC_INDEX, index); + + if (index >= NUM_ARITH_TBLS) { /* define AC table */ + cinfo->arith_ac_K[index-NUM_ARITH_TBLS] = (UINT8) val; + } else { /* define DC table */ + cinfo->arith_dc_L[index] = (UINT8) (val & 0x0F); + cinfo->arith_dc_U[index] = (UINT8) (val >> 4); + if (cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index]) + ERREXIT1(cinfo, JERR_DAC_VALUE, val); + } + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dht (j_decompress_ptr cinfo) +/* Process a DHT marker */ +{ + INT32 length; + UINT8 bits[17]; + UINT8 huffval[256]; + int i, index, count; + JHUFF_TBL **htblptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DHT, index); + + bits[0] = 0; + count = 0; + for (i = 1; i <= 16; i++) { + INPUT_BYTE(cinfo, bits[i], return FALSE); + count += bits[i]; + } + + length -= 1 + 16; + + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8]); + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16]); + + if (count > 256 || ((INT32) count) > length) + ERREXIT(cinfo, JERR_DHT_COUNTS); + + for (i = 0; i < count; i++) + INPUT_BYTE(cinfo, huffval[i], return FALSE); + + length -= count; + + if (index & 0x10) { /* AC table definition */ + index -= 0x10; + htblptr = &cinfo->ac_huff_tbl_ptrs[index]; + } else { /* DC table definition */ + htblptr = &cinfo->dc_huff_tbl_ptrs[index]; + } + + if (index < 0 || index >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_DHT_INDEX, index); + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + MEMCOPY((*htblptr)->huffval, huffval, SIZEOF((*htblptr)->huffval)); + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dqt (j_decompress_ptr cinfo) +/* Process a DQT marker */ +{ + INT32 length; + int n, i, prec; + unsigned int tmp; + JQUANT_TBL *quant_ptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, n, return FALSE); + prec = n >> 4; + n &= 0x0F; + + TRACEMS2(cinfo, 1, JTRC_DQT, n, prec); + + if (n >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, n); + + if (cinfo->quant_tbl_ptrs[n] == NULL) + cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table((j_common_ptr) cinfo); + quant_ptr = cinfo->quant_tbl_ptrs[n]; + + for (i = 0; i < DCTSIZE2; i++) { + if (prec) + INPUT_2BYTES(cinfo, tmp, return FALSE); + else + INPUT_BYTE(cinfo, tmp, return FALSE); + quant_ptr->quantval[i] = (UINT16) tmp; + } + + for (i = 0; i < DCTSIZE2; i += 8) { + TRACEMS8(cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i ], quant_ptr->quantval[i+1], + quant_ptr->quantval[i+2], quant_ptr->quantval[i+3], + quant_ptr->quantval[i+4], quant_ptr->quantval[i+5], + quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]); + } + + length -= DCTSIZE2+1; + if (prec) length -= DCTSIZE2; + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dri (j_decompress_ptr cinfo) +/* Process a DRI marker */ +{ + INT32 length; + unsigned int tmp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + if (length != 4) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_2BYTES(cinfo, tmp, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DRI, tmp); + + cinfo->restart_interval = tmp; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +METHODDEF boolean +skip_variable (j_decompress_ptr cinfo) +/* Skip over an unknown or uninteresting variable-length marker */ +{ + INT32 length; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length); + + INPUT_SYNC(cinfo); /* do before skip_input_data */ + (*cinfo->src->skip_input_data) (cinfo, (long) length - 2L); + + return TRUE; +} + + +/* + * Find the next JPEG marker, save it in cinfo->unread_marker. + * Returns FALSE if had to suspend before reaching a marker; + * in that case cinfo->unread_marker is unchanged. + * + * Note that the result might not be a valid marker code, + * but it will never be 0 or FF. + */ + +LOCAL boolean +next_marker (j_decompress_ptr cinfo) +{ + int c; + INPUT_VARS(cinfo); + + for (;;) { + INPUT_BYTE(cinfo, c, return FALSE); + /* Skip any non-FF bytes. + * This may look a bit inefficient, but it will not occur in a valid file. + * We sync after each discarded byte so that a suspending data source + * can discard the byte from its buffer. + */ + while (c != 0xFF) { + cinfo->marker->discarded_bytes++; + INPUT_SYNC(cinfo); + INPUT_BYTE(cinfo, c, return FALSE); + } + /* This loop swallows any duplicate FF bytes. Extra FFs are legal as + * pad bytes, so don't count them in discarded_bytes. We assume there + * will not be so many consecutive FF bytes as to overflow a suspending + * data source's input buffer. + */ + do { + INPUT_BYTE(cinfo, c, return FALSE); + } while (c == 0xFF); + if (c != 0) + break; /* found a valid marker, exit loop */ + /* Reach here if we found a stuffed-zero data sequence (FF/00). + * Discard it and loop back to try again. + */ + cinfo->marker->discarded_bytes += 2; + INPUT_SYNC(cinfo); + } + + if (cinfo->marker->discarded_bytes != 0) { + WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c); + cinfo->marker->discarded_bytes = 0; + } + + cinfo->unread_marker = c; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +first_marker (j_decompress_ptr cinfo) +/* Like next_marker, but used to obtain the initial SOI marker. */ +/* For this marker, we do not allow preceding garbage or fill; otherwise, + * we might well scan an entire input file before realizing it ain't JPEG. + * If an application wants to process non-JFIF files, it must seek to the + * SOI before calling the JPEG library. + */ +{ + int c, c2; + INPUT_VARS(cinfo); + + INPUT_BYTE(cinfo, c, return FALSE); + INPUT_BYTE(cinfo, c2, return FALSE); + if (c != 0xFF || c2 != (int) M_SOI) + ERREXIT2(cinfo, JERR_NO_SOI, c, c2); + + cinfo->unread_marker = c2; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Read markers until SOS or EOI. + * + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + +METHODDEF int +read_markers (j_decompress_ptr cinfo) +{ + /* Outer loop repeats once for each marker. */ + for (;;) { + /* Collect the marker proper, unless we already did. */ + /* NB: first_marker() enforces the requirement that SOI appear first. */ + if (cinfo->unread_marker == 0) { + if (! cinfo->marker->saw_SOI) { + if (! first_marker(cinfo)) + return JPEG_SUSPENDED; + } else { + if (! next_marker(cinfo)) + return JPEG_SUSPENDED; + } + } + /* At this point cinfo->unread_marker contains the marker code and the + * input point is just past the marker proper, but before any parameters. + * A suspension will cause us to return with this state still true. + */ + switch (cinfo->unread_marker) { + case M_SOI: + if (! get_soi(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_SOF0: /* Baseline */ + case M_SOF1: /* Extended sequential, Huffman */ + if (! get_sof(cinfo, FALSE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF2: /* Progressive, Huffman */ + if (! get_sof(cinfo, TRUE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF9: /* Extended sequential, arithmetic */ + if (! get_sof(cinfo, FALSE, TRUE)) + return JPEG_SUSPENDED; + break; + + case M_SOF10: /* Progressive, arithmetic */ + if (! get_sof(cinfo, TRUE, TRUE)) + return JPEG_SUSPENDED; + break; + + /* Currently unsupported SOFn types */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ + ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker); + break; + + case M_SOS: + if (! get_sos(cinfo)) + return JPEG_SUSPENDED; + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_SOS; + + case M_EOI: + TRACEMS(cinfo, 1, JTRC_EOI); + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_EOI; + + case M_DAC: + if (! get_dac(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DHT: + if (! get_dht(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DQT: + if (! get_dqt(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DRI: + if (! get_dri(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if (! (*cinfo->marker->process_APPn[cinfo->unread_marker - (int) M_APP0]) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_COM: + if (! (*cinfo->marker->process_COM) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_RST0: /* these are all parameterless */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker); + break; + + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + if (! skip_variable(cinfo)) + return JPEG_SUSPENDED; + break; + + default: /* must be DHP, EXP, JPGn, or RESn */ + /* For now, we treat the reserved markers as fatal errors since they are + * likely to be used to signal incompatible JPEG Part 3 extensions. + * Once the JPEG 3 version-number marker is well defined, this code + * ought to change! + */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + /* Successfully processed marker, so reset state variable */ + cinfo->unread_marker = 0; + } /* end loop */ +} + + +/* + * Read a restart marker, which is expected to appear next in the datastream; + * if the marker is not there, take appropriate recovery action. + * Returns FALSE if suspension is required. + * + * This is called by the entropy decoder after it has read an appropriate + * number of MCUs. cinfo->unread_marker may be nonzero if the entropy decoder + * has already read a marker from the data source. Under normal conditions + * cinfo->unread_marker will be reset to 0 before returning; if not reset, + * it holds a marker which the decoder will be unable to read past. + */ + +METHODDEF boolean +read_restart_marker (j_decompress_ptr cinfo) +{ + /* Obtain a marker unless we already did. */ + /* Note that next_marker will complain if it skips any data. */ + if (cinfo->unread_marker == 0) { + if (! next_marker(cinfo)) + return FALSE; + } + + if (cinfo->unread_marker == + ((int) M_RST0 + cinfo->marker->next_restart_num)) { + /* Normal case --- swallow the marker and let entropy decoder continue */ + TRACEMS1(cinfo, 2, JTRC_RST, cinfo->marker->next_restart_num); + cinfo->unread_marker = 0; + } else { + /* Uh-oh, the restart markers have been messed up. */ + /* Let the data source manager determine how to resync. */ + if (! (*cinfo->src->resync_to_restart) (cinfo, + cinfo->marker->next_restart_num)) + return FALSE; + } + + /* Update next-restart state */ + cinfo->marker->next_restart_num = (cinfo->marker->next_restart_num + 1) & 7; + + return TRUE; +} + + +/* + * This is the default resync_to_restart method for data source managers + * to use if they don't have any better approach. Some data source managers + * may be able to back up, or may have additional knowledge about the data + * which permits a more intelligent recovery strategy; such managers would + * presumably supply their own resync method. + * + * read_restart_marker calls resync_to_restart if it finds a marker other than + * the restart marker it was expecting. (This code is *not* used unless + * a nonzero restart interval has been declared.) cinfo->unread_marker is + * the marker code actually found (might be anything, except 0 or FF). + * The desired restart marker number (0..7) is passed as a parameter. + * This routine is supposed to apply whatever error recovery strategy seems + * appropriate in order to position the input stream to the next data segment. + * Note that cinfo->unread_marker is treated as a marker appearing before + * the current data-source input point; usually it should be reset to zero + * before returning. + * Returns FALSE if suspension is required. + * + * This implementation is substantially constrained by wanting to treat the + * input as a data stream; this means we can't back up. Therefore, we have + * only the following actions to work with: + * 1. Simply discard the marker and let the entropy decoder resume at next + * byte of file. + * 2. Read forward until we find another marker, discarding intervening + * data. (In theory we could look ahead within the current bufferload, + * without having to discard data if we don't find the desired marker. + * This idea is not implemented here, in part because it makes behavior + * dependent on buffer size and chance buffer-boundary positions.) + * 3. Leave the marker unread (by failing to zero cinfo->unread_marker). + * This will cause the entropy decoder to process an empty data segment, + * inserting dummy zeroes, and then we will reprocess the marker. + * + * #2 is appropriate if we think the desired marker lies ahead, while #3 is + * appropriate if the found marker is a future restart marker (indicating + * that we have missed the desired restart marker, probably because it got + * corrupted). + * We apply #2 or #3 if the found marker is a restart marker no more than + * two counts behind or ahead of the expected one. We also apply #2 if the + * found marker is not a legal JPEG marker code (it's certainly bogus data). + * If the found marker is a restart marker more than 2 counts away, we do #1 + * (too much risk that the marker is erroneous; with luck we will be able to + * resync at some future point). + * For any valid non-restart JPEG marker, we apply #3. This keeps us from + * overrunning the end of a scan. An implementation limited to single-scan + * files might find it better to apply #2 for markers other than EOI, since + * any other marker would have to be bogus data in that case. + */ + +GLOBAL boolean +jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired) +{ + int marker = cinfo->unread_marker; + int action = 1; + + /* Always put up a warning. */ + WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired); + + /* Outer loop handles repeated decision after scanning forward. */ + for (;;) { + if (marker < (int) M_SOF0) + action = 2; /* invalid marker */ + else if (marker < (int) M_RST0 || marker > (int) M_RST7) + action = 3; /* valid non-restart marker */ + else { + if (marker == ((int) M_RST0 + ((desired+1) & 7)) || + marker == ((int) M_RST0 + ((desired+2) & 7))) + action = 3; /* one of the next two expected restarts */ + else if (marker == ((int) M_RST0 + ((desired-1) & 7)) || + marker == ((int) M_RST0 + ((desired-2) & 7))) + action = 2; /* a prior restart, so advance */ + else + action = 1; /* desired restart or too far away */ + } + TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action); + switch (action) { + case 1: + /* Discard marker and let entropy decoder resume processing. */ + cinfo->unread_marker = 0; + return TRUE; + case 2: + /* Scan to the next marker, and repeat the decision loop. */ + if (! next_marker(cinfo)) + return FALSE; + marker = cinfo->unread_marker; + break; + case 3: + /* Return without advancing past this marker. */ + /* Entropy decoder will be forced to process an empty segment. */ + return TRUE; + } + } /* end loop */ +} + + +/* + * Reset marker processing state to begin a fresh datastream. + */ + +METHODDEF void +reset_marker_reader (j_decompress_ptr cinfo) +{ + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + cinfo->marker->saw_SOI = FALSE; /* set internal state too */ + cinfo->marker->saw_SOF = FALSE; + cinfo->marker->discarded_bytes = 0; +} + + +/* + * Initialize the marker reader module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_marker_reader (j_decompress_ptr cinfo) +{ + int i; + + /* Create subobject in permanent pool */ + cinfo->marker = (struct jpeg_marker_reader *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(struct jpeg_marker_reader)); + /* Initialize method pointers */ + cinfo->marker->reset_marker_reader = reset_marker_reader; + cinfo->marker->read_markers = read_markers; + cinfo->marker->read_restart_marker = read_restart_marker; + cinfo->marker->process_COM = skip_variable; + for (i = 0; i < 16; i++) + cinfo->marker->process_APPn[i] = skip_variable; + cinfo->marker->process_APPn[0] = get_app0; + cinfo->marker->process_APPn[14] = get_app14; + /* Reset marker processing state */ + reset_marker_reader(cinfo); +} diff --git a/tools/urt/libs/jpeg6/jdmaster.cpp b/tools/urt/libs/jpeg6/jdmaster.cpp new file mode 100644 index 00000000..9d88bb2f --- /dev/null +++ b/tools/urt/libs/jpeg6/jdmaster.cpp @@ -0,0 +1,558 @@ +/* + * jdmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG decompressor. + * These routines are concerned with selecting the modules to be executed + * and with determining the number of passes and the work to be done in each + * pass. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_decomp_master pub; /* public fields */ + + int pass_number; /* # of passes completed */ + + boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */ + + /* Saved references to initialized quantizer modules, + * in case we need to switch modes. + */ + struct jpeg_color_quantizer * quantizer_1pass; + struct jpeg_color_quantizer * quantizer_2pass; +} my_decomp_master; + +typedef my_decomp_master * my_master_ptr; + + +/* + * Determine whether merged upsample/color conversion should be used. + * CRUCIAL: this must match the actual capabilities of jdmerge.c! + */ + +LOCAL boolean +use_merged_upsample (j_decompress_ptr cinfo) +{ +#ifdef UPSAMPLE_MERGING_SUPPORTED + /* Merging is the equivalent of plain box-filter upsampling */ + if (cinfo->do_fancy_upsampling || cinfo->CCIR601_sampling) + return FALSE; + /* jdmerge.c only supports YCC=>RGB color conversion */ + if (cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components != 3 || + cinfo->out_color_space != JCS_RGB || + cinfo->out_color_components != RGB_PIXELSIZE) + return FALSE; + /* and it only handles 2h1v or 2h2v sampling ratios */ + if (cinfo->comp_info[0].h_samp_factor != 2 || + cinfo->comp_info[1].h_samp_factor != 1 || + cinfo->comp_info[2].h_samp_factor != 1 || + cinfo->comp_info[0].v_samp_factor > 2 || + cinfo->comp_info[1].v_samp_factor != 1 || + cinfo->comp_info[2].v_samp_factor != 1) + return FALSE; + /* furthermore, it doesn't work if we've scaled the IDCTs differently */ + if (cinfo->comp_info[0].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[1].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size) + return FALSE; + /* ??? also need to test for upsample-time rescaling, when & if supported */ + return TRUE; /* by golly, it'll work... */ +#else + return FALSE; +#endif +} + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + * Also note that it may be called before the master module is initialized! + */ + +GLOBAL void +jpeg_calc_output_dimensions (j_decompress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ +#if 0 // JDC: commented out to remove warning + int ci; + jpeg_component_info *compptr; +#endif + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_READY) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + +#ifdef IDCT_SCALING_SUPPORTED + + /* Compute actual output image dimensions and DCT scaling choices. */ + if (cinfo->scale_num * 8 <= cinfo->scale_denom) { + /* Provide 1/8 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 8L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 8L); + cinfo->min_DCT_scaled_size = 1; + } else if (cinfo->scale_num * 4 <= cinfo->scale_denom) { + /* Provide 1/4 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 4L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 4L); + cinfo->min_DCT_scaled_size = 2; + } else if (cinfo->scale_num * 2 <= cinfo->scale_denom) { + /* Provide 1/2 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 2L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 2L); + cinfo->min_DCT_scaled_size = 4; + } else { + /* Provide 1/1 scaling */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + cinfo->min_DCT_scaled_size = DCTSIZE; + } + /* In selecting the actual DCT scaling for each component, we try to + * scale up the chroma components via IDCT scaling rather than upsampling. + * This saves time if the upsampler gets to use 1:1 scaling. + * Note this code assumes that the supported DCT scalings are powers of 2. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + int ssize = cinfo->min_DCT_scaled_size; + while (ssize < DCTSIZE && + (compptr->h_samp_factor * ssize * 2 <= + cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size) && + (compptr->v_samp_factor * ssize * 2 <= + cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size)) { + ssize = ssize * 2; + } + compptr->DCT_scaled_size = ssize; + } + + /* Recompute downsampled dimensions of components; + * application needs to know these if using raw downsampled data. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Size in samples, after IDCT scaling */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * + (long) (compptr->h_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * + (long) (compptr->v_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + } + +#else /* !IDCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE, + * and has computed unscaled downsampled_width and downsampled_height. + */ + +#endif /* IDCT_SCALING_SUPPORTED */ + + /* Report number of components in selected colorspace. */ + /* Probably this should be in the color conversion module... */ + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + cinfo->out_color_components = RGB_PIXELSIZE; + break; +#endif /* else share code with YCbCr */ + case JCS_YCbCr: + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->out_color_components = 4; + break; + default: /* else must be same colorspace as in file */ + cinfo->out_color_components = cinfo->num_components; + break; + } + cinfo->output_components = (cinfo->quantize_colors ? 1 : + cinfo->out_color_components); + + /* See if upsampler will want to emit more than one row at a time */ + if (use_merged_upsample(cinfo)) + cinfo->rec_outbuf_height = cinfo->max_v_samp_factor; + else + cinfo->rec_outbuf_height = 1; +} + + +/* + * Several decompression processes need to range-limit values to the range + * 0..MAXJSAMPLE; the input value may fall somewhat outside this range + * due to noise introduced by quantization, roundoff error, etc. These + * processes are inner loops and need to be as fast as possible. On most + * machines, particularly CPUs with pipelines or instruction prefetch, + * a (subscript-check-less) C table lookup + * x = sample_range_limit[x]; + * is faster than explicit tests + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * These processes all use a common table prepared by the routine below. + * + * For most steps we can mathematically guarantee that the initial value + * of x is within MAXJSAMPLE+1 of the legal range, so a table running from + * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial + * limiting step (just after the IDCT), a wildly out-of-range value is + * possible if the input data is corrupt. To avoid any chance of indexing + * off the end of memory and getting a bad-pointer trap, we perform the + * post-IDCT limiting thus: + * x = range_limit[x & MASK]; + * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit + * samples. Under normal circumstances this is more than enough range and + * a correct output will be generated; with bogus input data the mask will + * cause wraparound, and we will safely generate a bogus-but-in-range output. + * For the post-IDCT step, we want to convert the data from signed to unsigned + * representation by adding CENTERJSAMPLE at the same time that we limit it. + * So the post-IDCT limiting table ends up looking like this: + * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE, + * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0,1,...,CENTERJSAMPLE-1 + * Negative inputs select values from the upper half of the table after + * masking. + * + * We can save some space by overlapping the start of the post-IDCT table + * with the simpler range limiting table. The post-IDCT table begins at + * sample_range_limit + CENTERJSAMPLE. + * + * Note that the table is allocated in near data space on PCs; it's small + * enough and used often enough to justify this. + */ + +LOCAL void +prepare_range_limit_table (j_decompress_ptr cinfo) +/* Allocate and fill in the sample_range_limit table */ +{ + JSAMPLE * table; + int i; + + table = (JSAMPLE *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */ + cinfo->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE)); + /* Main part of "simple" table: limit[x] = x */ + for (i = 0; i <= MAXJSAMPLE; i++) + table[i] = (JSAMPLE) i; + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++) + table[i] = MAXJSAMPLE; + /* Second half of post-IDCT table */ + MEMZERO(table + (2 * (MAXJSAMPLE+1)), + (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE), + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE)); +} + + +/* + * Master selection of decompression modules. + * This is done once at jpeg_start_decompress time. We determine + * which modules will be used and give them appropriate initialization calls. + * We also initialize the decompressor input side to begin consuming data. + * + * Since jpeg_read_header has finished, we know what is in the SOF + * and (first) SOS markers. We also have all the application parameter + * settings. + */ + +LOCAL void +master_selection (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + boolean use_c_buffer; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Initialize dimensions and other stuff */ + jpeg_calc_output_dimensions(cinfo); + prepare_range_limit_table(cinfo); + + /* Width of an output scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* Initialize my private state */ + master->pass_number = 0; + master->using_merged_upsample = use_merged_upsample(cinfo); + + /* Color quantizer selection */ + master->quantizer_1pass = NULL; + master->quantizer_2pass = NULL; + /* No mode changes if not using buffered-image mode. */ + if (! cinfo->quantize_colors || ! cinfo->buffered_image) { + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + } + if (cinfo->quantize_colors) { + if (cinfo->raw_data_out) + ERREXIT(cinfo, JERR_NOTIMPL); + /* 2-pass quantizer only works in 3-component color space. */ + if (cinfo->out_color_components != 3) { + cinfo->enable_1pass_quant = TRUE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->colormap = NULL; + } else if (cinfo->colormap != NULL) { + cinfo->enable_external_quant = TRUE; + } else if (cinfo->two_pass_quantize) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + + if (cinfo->enable_1pass_quant) { +#ifdef QUANT_1PASS_SUPPORTED + jinit_1pass_quantizer(cinfo); + master->quantizer_1pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + + /* We use the 2-pass code to map to external colormaps. */ + if (cinfo->enable_2pass_quant || cinfo->enable_external_quant) { +#ifdef QUANT_2PASS_SUPPORTED + jinit_2pass_quantizer(cinfo); + master->quantizer_2pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + /* If both quantizers are initialized, the 2-pass one is left active; + * this is necessary for starting with quantization to an external map. + */ + } + + /* Post-processing: in particular, color conversion first */ + if (! cinfo->raw_data_out) { + if (master->using_merged_upsample) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + jinit_merged_upsampler(cinfo); /* does color conversion too */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + jinit_color_deconverter(cinfo); + jinit_upsampler(cinfo); + } + jinit_d_post_controller(cinfo, cinfo->enable_2pass_quant); + } + /* Inverse DCT */ + jinit_inverse_dct(cinfo); + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NO_PROGRESSIVE); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Initialize principal buffer controllers. */ + use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image; + jinit_d_coef_controller(cinfo, use_c_buffer); + + if (! cinfo->raw_data_out) + jinit_d_main_controller(cinfo, FALSE /* never need full buffer here */); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* If jpeg_start_decompress will read the whole file, initialize + * progress monitoring appropriately. The input step is counted + * as one pass. + */ + if (cinfo->progress != NULL && ! cinfo->buffered_image && + cinfo->inputctl->has_multiple_scans) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = (cinfo->enable_2pass_quant ? 3 : 2); + /* Count the input pass as done */ + master->pass_number++; + } +#endif /* D_MULTISCAN_FILES_SUPPORTED */ +} + + +/* + * Per-pass setup. + * This is called at the beginning of each output pass. We determine which + * modules will be active during this pass and give them appropriate + * start_pass calls. We also set is_dummy_pass to indicate whether this + * is a "real" output pass or a dummy pass for color quantization. + * (In the latter case, jdapi.c will crank the pass to completion.) + */ + +METHODDEF void +prepare_for_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (master->pub.is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Final pass of 2-pass quantization */ + master->pub.is_dummy_pass = FALSE; + (*cinfo->cquantize->start_pass) (cinfo, FALSE); + (*cinfo->post->start_pass) (cinfo, JBUF_CRANK_DEST); + (*cinfo->main->start_pass) (cinfo, JBUF_CRANK_DEST); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + if (cinfo->quantize_colors && cinfo->colormap == NULL) { + /* Select new quantization method */ + if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) { + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; + } else if (cinfo->enable_1pass_quant) { + cinfo->cquantize = master->quantizer_1pass; + } else { + ERREXIT(cinfo, JERR_MODE_CHANGE); + } + } + (*cinfo->idct->start_pass) (cinfo); + (*cinfo->coef->start_output_pass) (cinfo); + if (! cinfo->raw_data_out) { + if (! master->using_merged_upsample) + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->upsample->start_pass) (cinfo); + if (cinfo->quantize_colors) + (*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass); + (*cinfo->post->start_pass) (cinfo, + (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + } + } + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->pass_number + + (master->pub.is_dummy_pass ? 2 : 1); + /* In buffered-image mode, we assume one more output pass if EOI not + * yet reached, but no more passes if EOI has been reached. + */ + if (cinfo->buffered_image && ! cinfo->inputctl->eoi_reached) { + cinfo->progress->total_passes += (cinfo->enable_2pass_quant ? 2 : 1); + } + } +} + + +/* + * Finish up at end of an output pass. + */ + +METHODDEF void +finish_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (cinfo->quantize_colors) + (*cinfo->cquantize->finish_pass) (cinfo); + master->pass_number++; +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Switch to a new external colormap between output passes. + */ + +GLOBAL void +jpeg_new_colormap (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_BUFIMAGE) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (cinfo->quantize_colors && cinfo->enable_external_quant && + cinfo->colormap != NULL) { + /* Select 2-pass quantizer for external colormap use */ + cinfo->cquantize = master->quantizer_2pass; + /* Notify quantizer of colormap change */ + (*cinfo->cquantize->new_color_map) (cinfo); + master->pub.is_dummy_pass = FALSE; /* just in case */ + } else + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +/* + * Initialize master decompression control and select active modules. + * This is performed at the start of jpeg_start_decompress. + */ + +GLOBAL void +jinit_master_decompress (j_decompress_ptr cinfo) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_decomp_master)); + cinfo->master = (struct jpeg_decomp_master *) master; + master->pub.prepare_for_output_pass = prepare_for_output_pass; + master->pub.finish_output_pass = finish_output_pass; + + master->pub.is_dummy_pass = FALSE; + + master_selection(cinfo); +} + diff --git a/tools/urt/libs/jpeg6/jdpostct.cpp b/tools/urt/libs/jpeg6/jdpostct.cpp new file mode 100644 index 00000000..3aba0a38 --- /dev/null +++ b/tools/urt/libs/jpeg6/jdpostct.cpp @@ -0,0 +1,580 @@ +/* + + * jdpostct.c + + * + + * Copyright (C) 1994-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains the decompression postprocessing controller. + + * This controller manages the upsampling, color conversion, and color + + * quantization/reduction steps; specifically, it controls the buffering + + * between upsample/color conversion and color quantization/reduction. + + * + + * If no color quantization/reduction is required, then this module has no + + * work to do, and it just hands off to the upsample/color conversion code. + + * An integrated upsample/convert/quantize process would replace this module + + * entirely. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* Private buffer controller object */ + + + +typedef struct { + + struct jpeg_d_post_controller pub; /* public fields */ + + + + /* Color quantization source buffer: this holds output data from + + * the upsample/color conversion step to be passed to the quantizer. + + * For two-pass color quantization, we need a full-image buffer; + + * for one-pass operation, a strip buffer is sufficient. + + */ + + jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ + + JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ + + JDIMENSION strip_height; /* buffer size in rows */ + + /* for two-pass mode only: */ + + JDIMENSION starting_row; /* row # of first row in current strip */ + + JDIMENSION next_row; /* index of next row to fill/empty in strip */ + +} my_post_controller; + + + +typedef my_post_controller * my_post_ptr; + + + + + +/* Forward declarations */ + +METHODDEF void post_process_1pass + + JPP((j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + + JDIMENSION in_row_groups_avail, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail)); + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF void post_process_prepass + + JPP((j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + + JDIMENSION in_row_groups_avail, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail)); + +METHODDEF void post_process_2pass + + JPP((j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + + JDIMENSION in_row_groups_avail, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail)); + +#endif + + + + + +/* + + * Initialize for a processing pass. + + */ + + + +METHODDEF void + +start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) + +{ + + my_post_ptr post = (my_post_ptr) cinfo->post; + + + + switch (pass_mode) { + + case JBUF_PASS_THRU: + + if (cinfo->quantize_colors) { + + /* Single-pass processing with color quantization. */ + + post->pub.post_process_data = post_process_1pass; + + /* We could be doing buffered-image output before starting a 2-pass + + * color quantization; in that case, jinit_d_post_controller did not + + * allocate a strip buffer. Use the virtual-array buffer as workspace. + + */ + + if (post->buffer == NULL) { + + post->buffer = (*cinfo->mem->access_virt_sarray) + + ((j_common_ptr) cinfo, post->whole_image, + + (JDIMENSION) 0, post->strip_height, TRUE); + + } + + } else { + + /* For single-pass processing without color quantization, + + * I have no work to do; just call the upsampler directly. + + */ + + post->pub.post_process_data = cinfo->upsample->upsample; + + } + + break; + +#ifdef QUANT_2PASS_SUPPORTED + + case JBUF_SAVE_AND_PASS: + + /* First pass of 2-pass quantization */ + + if (post->whole_image == NULL) + + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + post->pub.post_process_data = post_process_prepass; + + break; + + case JBUF_CRANK_DEST: + + /* Second pass of 2-pass quantization */ + + if (post->whole_image == NULL) + + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + post->pub.post_process_data = post_process_2pass; + + break; + +#endif /* QUANT_2PASS_SUPPORTED */ + + default: + + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + break; + + } + + post->starting_row = post->next_row = 0; + +} + + + + + +/* + + * Process some data in the one-pass (strip buffer) case. + + * This is used for color precision reduction as well as one-pass quantization. + + */ + + + +METHODDEF void + +post_process_1pass (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + + JDIMENSION in_row_groups_avail, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail) + +{ + + my_post_ptr post = (my_post_ptr) cinfo->post; + + JDIMENSION num_rows, max_rows; + + + + /* Fill the buffer, but not more than what we can dump out in one go. */ + + /* Note we rely on the upsampler to detect bottom of image. */ + + max_rows = out_rows_avail - *out_row_ctr; + + if (max_rows > post->strip_height) + + max_rows = post->strip_height; + + num_rows = 0; + + (*cinfo->upsample->upsample) (cinfo, + + input_buf, in_row_group_ctr, in_row_groups_avail, + + post->buffer, &num_rows, max_rows); + + /* Quantize and emit data. */ + + (*cinfo->cquantize->color_quantize) (cinfo, + + post->buffer, output_buf + *out_row_ctr, (int) num_rows); + + *out_row_ctr += num_rows; + +} + + + + + +#ifdef QUANT_2PASS_SUPPORTED + + + +/* + + * Process some data in the first pass of 2-pass quantization. + + */ + + + +METHODDEF void + +post_process_prepass (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + + JDIMENSION in_row_groups_avail, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail) + +{ + + my_post_ptr post = (my_post_ptr) cinfo->post; + + JDIMENSION old_next_row, num_rows; + + + + /* Reposition virtual buffer if at start of strip. */ + + if (post->next_row == 0) { + + post->buffer = (*cinfo->mem->access_virt_sarray) + + ((j_common_ptr) cinfo, post->whole_image, + + post->starting_row, post->strip_height, TRUE); + + } + + + + /* Upsample some data (up to a strip height's worth). */ + + old_next_row = post->next_row; + + (*cinfo->upsample->upsample) (cinfo, + + input_buf, in_row_group_ctr, in_row_groups_avail, + + post->buffer, &post->next_row, post->strip_height); + + + + /* Allow quantizer to scan new data. No data is emitted, */ + + /* but we advance out_row_ctr so outer loop can tell when we're done. */ + + if (post->next_row > old_next_row) { + + num_rows = post->next_row - old_next_row; + + (*cinfo->cquantize->color_quantize) (cinfo, post->buffer + old_next_row, + + (JSAMPARRAY) NULL, (int) num_rows); + + *out_row_ctr += num_rows; + + } + + + + /* Advance if we filled the strip. */ + + if (post->next_row >= post->strip_height) { + + post->starting_row += post->strip_height; + + post->next_row = 0; + + } + +} + + + + + +/* + + * Process some data in the second pass of 2-pass quantization. + + */ + + + +METHODDEF void + +post_process_2pass (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + + JDIMENSION in_row_groups_avail, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail) + +{ + + my_post_ptr post = (my_post_ptr) cinfo->post; + + JDIMENSION num_rows, max_rows; + + + + /* Reposition virtual buffer if at start of strip. */ + + if (post->next_row == 0) { + + post->buffer = (*cinfo->mem->access_virt_sarray) + + ((j_common_ptr) cinfo, post->whole_image, + + post->starting_row, post->strip_height, FALSE); + + } + + + + /* Determine number of rows to emit. */ + + num_rows = post->strip_height - post->next_row; /* available in strip */ + + max_rows = out_rows_avail - *out_row_ctr; /* available in output area */ + + if (num_rows > max_rows) + + num_rows = max_rows; + + /* We have to check bottom of image here, can't depend on upsampler. */ + + max_rows = cinfo->output_height - post->starting_row; + + if (num_rows > max_rows) + + num_rows = max_rows; + + + + /* Quantize and emit data. */ + + (*cinfo->cquantize->color_quantize) (cinfo, + + post->buffer + post->next_row, output_buf + *out_row_ctr, + + (int) num_rows); + + *out_row_ctr += num_rows; + + + + /* Advance if we filled the strip. */ + + post->next_row += num_rows; + + if (post->next_row >= post->strip_height) { + + post->starting_row += post->strip_height; + + post->next_row = 0; + + } + +} + + + +#endif /* QUANT_2PASS_SUPPORTED */ + + + + + +/* + + * Initialize postprocessing controller. + + */ + + + +GLOBAL void + +jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) + +{ + + my_post_ptr post; + + + + post = (my_post_ptr) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + SIZEOF(my_post_controller)); + + cinfo->post = (struct jpeg_d_post_controller *) post; + + post->pub.start_pass = start_pass_dpost; + + post->whole_image = NULL; /* flag for no virtual arrays */ + + post->buffer = NULL; /* flag for no strip buffer */ + + + + /* Create the quantization buffer, if needed */ + + if (cinfo->quantize_colors) { + + /* The buffer strip height is max_v_samp_factor, which is typically + + * an efficient number of rows for upsampling to return. + + * (In the presence of output rescaling, we might want to be smarter?) + + */ + + post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor; + + if (need_full_buffer) { + + /* Two-pass color quantization: need full-image storage. */ + + /* We round up the number of rows to a multiple of the strip height. */ + +#ifdef QUANT_2PASS_SUPPORTED + + post->whole_image = (*cinfo->mem->request_virt_sarray) + + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + + cinfo->output_width * cinfo->out_color_components, + + (JDIMENSION) jround_up((long) cinfo->output_height, + + (long) post->strip_height), + + post->strip_height); + +#else + + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + +#endif /* QUANT_2PASS_SUPPORTED */ + + } else { + + /* One-pass color quantization: just make a strip buffer. */ + + post->buffer = (*cinfo->mem->alloc_sarray) + + ((j_common_ptr) cinfo, JPOOL_IMAGE, + + cinfo->output_width * cinfo->out_color_components, + + post->strip_height); + + } + + } + +} + diff --git a/tools/urt/libs/jpeg6/jdsample.cpp b/tools/urt/libs/jpeg6/jdsample.cpp new file mode 100644 index 00000000..a15e252c --- /dev/null +++ b/tools/urt/libs/jpeg6/jdsample.cpp @@ -0,0 +1,956 @@ +/* + + * jdsample.c + + * + + * Copyright (C) 1991-1994, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains upsampling routines. + + * + + * Upsampling input data is counted in "row groups". A row group + + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + + * sample rows of each component. Upsampling will normally produce + + * max_v_samp_factor pixel rows from each row group (but this could vary + + * if the upsampler is applying a scale factor of its own). + + * + + * An excellent reference for image resampling is + + * Digital Image Warping, George Wolberg, 1990. + + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* Pointer to routine to upsample a single component */ + +typedef JMETHOD(void, upsample1_ptr, + + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)); + + + +/* Private subobject */ + + + +typedef struct { + + struct jpeg_upsampler pub; /* public fields */ + + + + /* Color conversion buffer. When using separate upsampling and color + + * conversion steps, this buffer holds one upsampled row group until it + + * has been color converted and output. + + * Note: we do not allocate any storage for component(s) which are full-size, + + * ie do not need rescaling. The corresponding entry of color_buf[] is + + * simply set to point to the input data array, thereby avoiding copying. + + */ + + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + + + /* Per-component upsampling method pointers */ + + upsample1_ptr methods[MAX_COMPONENTS]; + + + + int next_row_out; /* counts rows emitted from color_buf */ + + JDIMENSION rows_to_go; /* counts rows remaining in image */ + + + + /* Height of an input row group for each component. */ + + int rowgroup_height[MAX_COMPONENTS]; + + + + /* These arrays save pixel expansion factors so that int_expand need not + + * recompute them each time. They are unused for other upsampling methods. + + */ + + UINT8 h_expand[MAX_COMPONENTS]; + + UINT8 v_expand[MAX_COMPONENTS]; + +} my_upsampler; + + + +typedef my_upsampler * my_upsample_ptr; + + + + + +/* + + * Initialize for an upsampling pass. + + */ + + + +METHODDEF void + +start_pass_upsample (j_decompress_ptr cinfo) + +{ + + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + + + /* Mark the conversion buffer empty */ + + upsample->next_row_out = cinfo->max_v_samp_factor; + + /* Initialize total-height counter for detecting bottom of image */ + + upsample->rows_to_go = cinfo->output_height; + +} + + + + + +/* + + * Control routine to do upsampling (and color conversion). + + * + + * In this version we upsample each component independently. + + * We upsample one row group into the conversion buffer, then apply + + * color conversion a row at a time. + + */ + + + +METHODDEF void + +sep_upsample (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + + JDIMENSION in_row_groups_avail, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail) + +{ + + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + int ci; + + jpeg_component_info * compptr; + + JDIMENSION num_rows; + + + + /* Fill the conversion buffer, if it's empty */ + + if (upsample->next_row_out >= cinfo->max_v_samp_factor) { + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + /* Invoke per-component upsample method. Notice we pass a POINTER + + * to color_buf[ci], so that fullsize_upsample can change it. + + */ + + (*upsample->methods[ci]) (cinfo, compptr, + + input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]), + + upsample->color_buf + ci); + + } + + upsample->next_row_out = 0; + + } + + + + /* Color-convert and emit rows */ + + + + /* How many we have in the buffer: */ + + num_rows = (JDIMENSION) (cinfo->max_v_samp_factor - upsample->next_row_out); + + /* Not more than the distance to the end of the image. Need this test + + * in case the image height is not a multiple of max_v_samp_factor: + + */ + + if (num_rows > upsample->rows_to_go) + + num_rows = upsample->rows_to_go; + + /* And not more than what the client can accept: */ + + out_rows_avail -= *out_row_ctr; + + if (num_rows > out_rows_avail) + + num_rows = out_rows_avail; + + + + (*cinfo->cconvert->color_convert) (cinfo, upsample->color_buf, + + (JDIMENSION) upsample->next_row_out, + + output_buf + *out_row_ctr, + + (int) num_rows); + + + + /* Adjust counts */ + + *out_row_ctr += num_rows; + + upsample->rows_to_go -= num_rows; + + upsample->next_row_out += num_rows; + + /* When the buffer is emptied, declare this input row group consumed */ + + if (upsample->next_row_out >= cinfo->max_v_samp_factor) + + (*in_row_group_ctr)++; + +} + + + + + +/* + + * These are the routines invoked by sep_upsample to upsample pixel values + + * of a single component. One row group is processed per call. + + */ + + + + + +/* + + * For full-size components, we just make color_buf[ci] point at the + + * input buffer, and thus avoid copying any data. Note that this is + + * safe only because sep_upsample doesn't declare the input row group + + * "consumed" until we are done color converting and emitting it. + + */ + + + +METHODDEF void + +fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + +{ + + *output_data_ptr = input_data; + +} + + + + + +/* + + * This is a no-op version used for "uninteresting" components. + + * These components will not be referenced by color conversion. + + */ + + + +METHODDEF void + +noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + +{ + + *output_data_ptr = NULL; /* safety check */ + +} + + + + + +/* + + * This version handles any integral sampling ratios. + + * This is not used for typical JPEG files, so it need not be fast. + + * Nor, for that matter, is it particularly accurate: the algorithm is + + * simple replication of the input pixel onto the corresponding output + + * pixels. The hi-falutin sampling literature refers to this as a + + * "box filter". A box filter tends to introduce visible artifacts, + + * so if you are actually going to use 3:1 or 4:1 sampling ratios + + * you would be well advised to improve this code. + + */ + + + +METHODDEF void + +int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + +{ + + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + JSAMPARRAY output_data = *output_data_ptr; + + register JSAMPROW inptr, outptr; + + register JSAMPLE invalue; + + register int h; + + JSAMPROW outend; + + int h_expand, v_expand; + + int inrow, outrow; + + + + h_expand = upsample->h_expand[compptr->component_index]; + + v_expand = upsample->v_expand[compptr->component_index]; + + + + inrow = outrow = 0; + + while (outrow < cinfo->max_v_samp_factor) { + + /* Generate one output row with proper horizontal expansion */ + + inptr = input_data[inrow]; + + outptr = output_data[outrow]; + + outend = outptr + cinfo->output_width; + + while (outptr < outend) { + + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + + for (h = h_expand; h > 0; h--) { + + *outptr++ = invalue; + + } + + } + + /* Generate any additional output rows by duplicating the first one */ + + if (v_expand > 1) { + + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + + v_expand-1, cinfo->output_width); + + } + + inrow++; + + outrow += v_expand; + + } + +} + + + + + +/* + + * Fast processing for the common case of 2:1 horizontal and 1:1 vertical. + + * It's still a box filter. + + */ + + + +METHODDEF void + +h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + +{ + + JSAMPARRAY output_data = *output_data_ptr; + + register JSAMPROW inptr, outptr; + + register JSAMPLE invalue; + + JSAMPROW outend; + + int inrow; + + + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + + inptr = input_data[inrow]; + + outptr = output_data[inrow]; + + outend = outptr + cinfo->output_width; + + while (outptr < outend) { + + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + + *outptr++ = invalue; + + *outptr++ = invalue; + + } + + } + +} + + + + + +/* + + * Fast processing for the common case of 2:1 horizontal and 2:1 vertical. + + * It's still a box filter. + + */ + + + +METHODDEF void + +h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + +{ + + JSAMPARRAY output_data = *output_data_ptr; + + register JSAMPROW inptr, outptr; + + register JSAMPLE invalue; + + JSAMPROW outend; + + int inrow, outrow; + + + + inrow = outrow = 0; + + while (outrow < cinfo->max_v_samp_factor) { + + inptr = input_data[inrow]; + + outptr = output_data[outrow]; + + outend = outptr + cinfo->output_width; + + while (outptr < outend) { + + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + + *outptr++ = invalue; + + *outptr++ = invalue; + + } + + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + + 1, cinfo->output_width); + + inrow++; + + outrow += 2; + + } + +} + + + + + +/* + + * Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. + + * + + * The upsampling algorithm is linear interpolation between pixel centers, + + * also known as a "triangle filter". This is a good compromise between + + * speed and visual quality. The centers of the output pixels are 1/4 and 3/4 + + * of the way between input pixel centers. + + * + + * A note about the "bias" calculations: when rounding fractional values to + + * integer, we do not want to always round 0.5 up to the next integer. + + * If we did that, we'd introduce a noticeable bias towards larger values. + + * Instead, this code is arranged so that 0.5 will be rounded up or down at + + * alternate pixel locations (a simple ordered dither pattern). + + */ + + + +METHODDEF void + +h2v1_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + +{ + + JSAMPARRAY output_data = *output_data_ptr; + + register JSAMPROW inptr, outptr; + + register int invalue; + + register JDIMENSION colctr; + + int inrow; + + + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + + inptr = input_data[inrow]; + + outptr = output_data[inrow]; + + /* Special case for first column */ + + invalue = GETJSAMPLE(*inptr++); + + *outptr++ = (JSAMPLE) invalue; + + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(*inptr) + 2) >> 2); + + + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + + /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ + + invalue = GETJSAMPLE(*inptr++) * 3; + + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(inptr[-2]) + 1) >> 2); + + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(*inptr) + 2) >> 2); + + } + + + + /* Special case for last column */ + + invalue = GETJSAMPLE(*inptr); + + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(inptr[-1]) + 1) >> 2); + + *outptr++ = (JSAMPLE) invalue; + + } + +} + + + + + +/* + + * Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. + + * Again a triangle filter; see comments for h2v1 case, above. + + * + + * It is OK for us to reference the adjacent input rows because we demanded + + * context from the main buffer controller (see initialization code). + + */ + + + +METHODDEF void + +h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + +{ + + JSAMPARRAY output_data = *output_data_ptr; + + register JSAMPROW inptr0, inptr1, outptr; + +#if BITS_IN_JSAMPLE == 8 + + register int thiscolsum, lastcolsum, nextcolsum; + +#else + + register INT32 thiscolsum, lastcolsum, nextcolsum; + +#endif + + register JDIMENSION colctr; + + int inrow, outrow, v; + + + + inrow = outrow = 0; + + while (outrow < cinfo->max_v_samp_factor) { + + for (v = 0; v < 2; v++) { + + /* inptr0 points to nearest input row, inptr1 points to next nearest */ + + inptr0 = input_data[inrow]; + + if (v == 0) /* next nearest is row above */ + + inptr1 = input_data[inrow-1]; + + else /* next nearest is row below */ + + inptr1 = input_data[inrow+1]; + + outptr = output_data[outrow++]; + + + + /* Special case for first column */ + + thiscolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 8) >> 4); + + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + + + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + + /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ + + /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ + + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + + } + + + + /* Special case for last column */ + + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 7) >> 4); + + } + + inrow++; + + } + +} + + + + + +/* + + * Module initialization routine for upsampling. + + */ + + + +GLOBAL void + +jinit_upsampler (j_decompress_ptr cinfo) + +{ + + my_upsample_ptr upsample; + + int ci; + + jpeg_component_info * compptr; + + boolean need_buffer, do_fancy; + + int h_in_group, v_in_group, h_out_group, v_out_group; + + + + upsample = (my_upsample_ptr) + + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + + SIZEOF(my_upsampler)); + + cinfo->upsample = (struct jpeg_upsampler *) upsample; + + upsample->pub.start_pass = start_pass_upsample; + + upsample->pub.upsample = sep_upsample; + + upsample->pub.need_context_rows = FALSE; /* until we find out differently */ + + + + if (cinfo->CCIR601_sampling) /* this isn't supported */ + + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + + + /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1, + + * so don't ask for it. + + */ + + do_fancy = cinfo->do_fancy_upsampling && cinfo->min_DCT_scaled_size > 1; + + + + /* Verify we can handle the sampling factors, select per-component methods, + + * and create storage as needed. + + */ + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + + ci++, compptr++) { + + /* Compute size of an "input group" after IDCT scaling. This many samples + + * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. + + */ + + h_in_group = (compptr->h_samp_factor * compptr->DCT_scaled_size) / + + cinfo->min_DCT_scaled_size; + + v_in_group = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + + cinfo->min_DCT_scaled_size; + + h_out_group = cinfo->max_h_samp_factor; + + v_out_group = cinfo->max_v_samp_factor; + + upsample->rowgroup_height[ci] = v_in_group; /* save for use later */ + + need_buffer = TRUE; + + if (! compptr->component_needed) { + + /* Don't bother to upsample an uninteresting component. */ + + upsample->methods[ci] = noop_upsample; + + need_buffer = FALSE; + + } else if (h_in_group == h_out_group && v_in_group == v_out_group) { + + /* Fullsize components can be processed without any work. */ + + upsample->methods[ci] = fullsize_upsample; + + need_buffer = FALSE; + + } else if (h_in_group * 2 == h_out_group && + + v_in_group == v_out_group) { + + /* Special cases for 2h1v upsampling */ + + if (do_fancy && compptr->downsampled_width > 2) + + upsample->methods[ci] = h2v1_fancy_upsample; + + else + + upsample->methods[ci] = h2v1_upsample; + + } else if (h_in_group * 2 == h_out_group && + + v_in_group * 2 == v_out_group) { + + /* Special cases for 2h2v upsampling */ + + if (do_fancy && compptr->downsampled_width > 2) { + + upsample->methods[ci] = h2v2_fancy_upsample; + + upsample->pub.need_context_rows = TRUE; + + } else + + upsample->methods[ci] = h2v2_upsample; + + } else if ((h_out_group % h_in_group) == 0 && + + (v_out_group % v_in_group) == 0) { + + /* Generic integral-factors upsampling method */ + + upsample->methods[ci] = int_upsample; + + upsample->h_expand[ci] = (UINT8) (h_out_group / h_in_group); + + upsample->v_expand[ci] = (UINT8) (v_out_group / v_in_group); + + } else + + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + + if (need_buffer) { + + upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray) + + ((j_common_ptr) cinfo, JPOOL_IMAGE, + + (JDIMENSION) jround_up((long) cinfo->output_width, + + (long) cinfo->max_h_samp_factor), + + (JDIMENSION) cinfo->max_v_samp_factor); + + } + + } + +} + diff --git a/tools/urt/libs/jpeg6/jdtrans.cpp b/tools/urt/libs/jpeg6/jdtrans.cpp new file mode 100644 index 00000000..70e67379 --- /dev/null +++ b/tools/urt/libs/jpeg6/jdtrans.cpp @@ -0,0 +1,244 @@ +/* + + * jdtrans.c + + * + + * Copyright (C) 1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains library routines for transcoding decompression, + + * that is, reading raw DCT coefficient arrays from an input JPEG file. + + * The routines in jdapimin.c will also be needed by a transcoder. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* Forward declarations */ + +LOCAL void transdecode_master_selection JPP((j_decompress_ptr cinfo)); + + + + + +/* + + * Read the coefficient arrays from a JPEG file. + + * jpeg_read_header must be completed before calling this. + + * + + * The entire image is read into a set of virtual coefficient-block arrays, + + * one per component. The return value is a pointer to the array of + + * virtual-array descriptors. These can be manipulated directly via the + + * JPEG memory manager, or handed off to jpeg_write_coefficients(). + + * To release the memory occupied by the virtual arrays, call + + * jpeg_finish_decompress() when done with the data. + + * + + * Returns NULL if suspended. This case need be checked only if + + * a suspending data source is used. + + */ + + + +GLOBAL jvirt_barray_ptr * + +jpeg_read_coefficients (j_decompress_ptr cinfo) + +{ + + if (cinfo->global_state == DSTATE_READY) { + + /* First call: initialize active modules */ + + transdecode_master_selection(cinfo); + + cinfo->global_state = DSTATE_RDCOEFS; + + } else if (cinfo->global_state != DSTATE_RDCOEFS) + + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Absorb whole file into the coef buffer */ + + for (;;) { + + int retcode; + + /* Call progress monitor hook if present */ + + if (cinfo->progress != NULL) + + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + + /* Absorb some more input */ + + retcode = (*cinfo->inputctl->consume_input) (cinfo); + + if (retcode == JPEG_SUSPENDED) + + return NULL; + + if (retcode == JPEG_REACHED_EOI) + + break; + + /* Advance progress counter if appropriate */ + + if (cinfo->progress != NULL && + + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + + /* startup underestimated number of scans; ratchet up one scan */ + + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + + } + + } + + } + + /* Set state so that jpeg_finish_decompress does the right thing */ + + cinfo->global_state = DSTATE_STOPPING; + + return cinfo->coef->coef_arrays; + +} + + + + + +/* + + * Master selection of decompression modules for transcoding. + + * This substitutes for jdmaster.c's initialization of the full decompressor. + + */ + + + +LOCAL void + +transdecode_master_selection (j_decompress_ptr cinfo) + +{ + + /* Entropy decoding: either Huffman or arithmetic coding. */ + + if (cinfo->arith_code) { + + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + + } else { + + if (cinfo->progressive_mode) { + +#ifdef D_PROGRESSIVE_SUPPORTED + + jinit_phuff_decoder(cinfo); + +#else + + ERREXIT(cinfo, JERR_NOT_COMPILED); + +#endif + + } else + + jinit_huff_decoder(cinfo); + + } + + + + /* Always get a full-image coefficient buffer. */ + + jinit_d_coef_controller(cinfo, TRUE); + + + + /* We can now tell the memory manager to allocate virtual arrays. */ + + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + + + /* Initialize input side of decompressor to consume first scan. */ + + (*cinfo->inputctl->start_input_pass) (cinfo); + + + + /* Initialize progress monitoring. */ + + if (cinfo->progress != NULL) { + + int nscans; + + /* Estimate number of scans to set pass_limit. */ + + if (cinfo->progressive_mode) { + + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + + nscans = 2 + 3 * cinfo->num_components; + + } else if (cinfo->inputctl->has_multiple_scans) { + + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + + nscans = cinfo->num_components; + + } else { + + nscans = 1; + + } + + cinfo->progress->pass_counter = 0L; + + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + + cinfo->progress->completed_passes = 0; + + cinfo->progress->total_passes = 1; + + } + +} + diff --git a/tools/urt/libs/jpeg6/jerror.cpp b/tools/urt/libs/jpeg6/jerror.cpp new file mode 100644 index 00000000..e4f65f78 --- /dev/null +++ b/tools/urt/libs/jpeg6/jerror.cpp @@ -0,0 +1,233 @@ +/* + * jerror.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains simple error-reporting and trace-message routines. + * These are suitable for Unix-like systems and others where writing to + * stderr is the right thing to do. Many applications will want to replace + * some or all of these routines. + * + * These routines are used by both the compression and decompression code. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jversion.h" +#include "jerror.h" + +#ifndef EXIT_FAILURE /* define exit() codes if not provided */ +#define EXIT_FAILURE 1 +#endif + + +/* + * Create the message string table. + * We do this from the master message list in jerror.h by re-reading + * jerror.h with a suitable definition for macro JMESSAGE. + * The message table is made an external symbol just in case any applications + * want to refer to it directly. + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_message_table jMsgTable +#endif + +#define JMESSAGE(code,string) string , + +const char * const jpeg_std_message_table[] = { +#include "jerror.h" + NULL +}; + +// Rad additions, longjmp out of the LoadJPGBuff +GLOBAL jmp_buf rad_loadfailed; +GLOBAL char rad_errormsg[JMSG_LENGTH_MAX]; + +/* + * Error exit handler: must not return to caller. + * + * Applications may override this if they want to get control back after + * an error. Typically one would longjmp somewhere instead of exiting. + * The setjmp buffer can be made a private field within an expanded error + * handler object. Note that the info needed to generate an error message + * is stored in the error object, so you can generate the message now or + * later, at your convenience. + * You should make sure that the JPEG object is cleaned up (with jpeg_abort + * or jpeg_destroy) at some point. + */ + +METHODDEF void +error_exit (j_common_ptr cinfo) +{ +// char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo,rad_errormsg); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + longjmp( rad_loadfailed, -1 ); +} + + +/* + * Actual output of an error or trace message. + * Applications may override this method to send JPEG messages somewhere + * other than stderr. + */ + +METHODDEF void +output_message (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + /* Send it to stderr, adding a newline */ + printf("%s\n", buffer); +} + + +/* + * Decide whether to emit a trace or warning message. + * msg_level is one of: + * -1: recoverable corrupt-data warning, may want to abort. + * 0: important advisory messages (always display to user). + * 1: first level of tracing detail. + * 2,3,...: successively more detailed tracing messages. + * An application might override this method if it wanted to abort on warnings + * or change the policy about which messages to display. + */ + +METHODDEF void +emit_message (j_common_ptr cinfo, int msg_level) +{ + struct jpeg_error_mgr * err = cinfo->err; + + if (msg_level < 0) { + /* It's a warning message. Since corrupt files may generate many warnings, + * the policy implemented here is to show only the first warning, + * unless trace_level >= 3. + */ + if (err->num_warnings == 0 || err->trace_level >= 3) + (*err->output_message) (cinfo); + /* Always count warnings in num_warnings. */ + err->num_warnings++; + } else { + /* It's a trace message. Show it if trace_level >= msg_level. */ + if (err->trace_level >= msg_level) + (*err->output_message) (cinfo); + } +} + + +/* + * Format a message string for the most recent JPEG error or message. + * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX + * characters. Note that no '\n' character is added to the string. + * Few applications should need to override this method. + */ + +METHODDEF void +format_message (j_common_ptr cinfo, char * buffer) +{ + struct jpeg_error_mgr * err = cinfo->err; + int msg_code = err->msg_code; + const char * msgtext = NULL; + const char * msgptr; + char ch; + boolean isstring; + + /* Look up message string in proper table */ + if (msg_code > 0 && msg_code <= err->last_jpeg_message) { + msgtext = err->jpeg_message_table[msg_code]; + } else if (err->addon_message_table != NULL && + msg_code >= err->first_addon_message && + msg_code <= err->last_addon_message) { + msgtext = err->addon_message_table[msg_code - err->first_addon_message]; + } + + /* Defend against bogus message number */ + if (msgtext == NULL) { + err->msg_parm.i[0] = msg_code; + msgtext = err->jpeg_message_table[0]; + } + + /* Check for string parameter, as indicated by %s in the message text */ + isstring = FALSE; + msgptr = msgtext; + while ((ch = *msgptr++) != '\0') { + if (ch == '%') { + if (*msgptr == 's') isstring = TRUE; + break; + } + } + + /* Format the message into the passed buffer */ + if (isstring) + sprintf(buffer, msgtext, err->msg_parm.s); + else + sprintf(buffer, msgtext, + err->msg_parm.i[0], err->msg_parm.i[1], + err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], + err->msg_parm.i[6], err->msg_parm.i[7]); +} + + +/* + * Reset error state variables at start of a new image. + * This is called during compression startup to reset trace/error + * processing to default state, without losing any application-specific + * method pointers. An application might possibly want to override + * this method if it has additional error processing state. + */ + +METHODDEF void +reset_error_mgr (j_common_ptr cinfo) +{ + cinfo->err->num_warnings = 0; + /* trace_level is not reset since it is an application-supplied parameter */ + cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ +} + + +/* + * Fill in the standard error-handling methods in a jpeg_error_mgr object. + * Typical call is: + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; + * + * cinfo.err = jpeg_std_error(&err); + * after which the application may override some of the methods. + */ + +GLOBAL struct jpeg_error_mgr * +jpeg_std_error (struct jpeg_error_mgr * err) +{ + err->error_exit = error_exit; + err->emit_message = emit_message; + err->output_message = output_message; + err->format_message = format_message; + err->reset_error_mgr = reset_error_mgr; + + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ + + /* Initialize message table pointers */ + err->jpeg_message_table = jpeg_std_message_table; + err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1; + + err->addon_message_table = NULL; + err->first_addon_message = 0; /* for safety */ + err->last_addon_message = 0; + + return err; +} diff --git a/tools/urt/libs/jpeg6/jerror.h b/tools/urt/libs/jpeg6/jerror.h new file mode 100644 index 00000000..d23dfb0c --- /dev/null +++ b/tools/urt/libs/jpeg6/jerror.h @@ -0,0 +1,278 @@ +/* + * jerror.h + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code,string) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code,string) code , + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +JMESSAGE(JERR_ARITH_NOTIMPL, + "Sorry, there are legal restrictions on arithmetic coding") +JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") +JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") +JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") +JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS") +JMESSAGE(JERR_BAD_DCTSIZE, "IDCT output block size %d not supported") +JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") +JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") +JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") +JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") +JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") +JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") +JMESSAGE(JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") +JMESSAGE(JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d") +JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") +JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") +JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") +JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") +JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") +JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") +JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet") +JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d") +JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request") +JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d") +JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x") +JMESSAGE(JERR_DHT_COUNTS, "Bogus DHT counts") +JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d") +JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d") +JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)") +JMESSAGE(JERR_EMS_READ, "Read from EMS failed") +JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed") +JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan") +JMESSAGE(JERR_FILE_READ, "Input file read error") +JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?") +JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet") +JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow") +JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry") +JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") +JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") +JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") +JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d") +JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") +JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") +JMESSAGE(JERR_NOTIMPL, "Not implemented yet") +JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time") +JMESSAGE(JERR_NO_PROGRESSIVE, "Progressive JPEGs not supported, use regular JPEG instead") +JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported") +JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined") +JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image") +JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") +JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") +JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") +JMESSAGE(JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components") +JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") +JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") +JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") +JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker") +JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x") +JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers") +JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF") +JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") +JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") +JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") +JMESSAGE(JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?") +JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") +JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") +JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") +JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation") +JMESSAGE(JERR_XMS_READ, "Read from XMS failed") +JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") +JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT) +JMESSAGE(JMSG_VERSION, JVERSION) +JMESSAGE(JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG") +JMESSAGE(JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") +JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") +JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") +JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") +JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x") +JMESSAGE(JTRC_DQT, "Define Quantization Table %d precision %d") +JMESSAGE(JTRC_DRI, "Define Restart Interval %u") +JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u") +JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u") +JMESSAGE(JTRC_EOI, "End Of Image") +JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") +JMESSAGE(JTRC_JFIF, "JFIF APP0 marker, density %dx%d %d") +JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u") +JMESSAGE(JTRC_JFIF_MINOR, "Unknown JFIF minor revision number %d.%02d") +JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") +JMESSAGE(JTRC_MISC_MARKER, "Skipping marker 0x%02x, length %u") +JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") +JMESSAGE(JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u") +JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors") +JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors") +JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") +JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") +JMESSAGE(JTRC_RST, "RST%d") +JMESSAGE(JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios") +JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") +JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") +JMESSAGE(JTRC_SOI, "Start of Image") +JMESSAGE(JTRC_SOS, "Start Of Scan: %d components") +JMESSAGE(JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d") +JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") +JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") +JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") +JMESSAGE(JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr") +JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") +JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") +JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") +JMESSAGE(JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d") +JMESSAGE(JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") +JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") +JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") +JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") +JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") +JMESSAGE(JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") +JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") +JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + +#ifndef JERROR_H +#define JERROR_H + +// Rad additions, using longjmp to recover from errors +#include +EXTERN jmp_buf rad_loadfailed; +EXTERN char rad_errormsg[JMSG_LENGTH_MAX]; + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT3(cinfo,code,p1,p2,p3) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT4(cinfo,code,p1,p2,p3,p4) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXITS(cinfo,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) + +#define MAKESTMT(stuff) do { stuff } while (0) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) + +/* Informational/debugging messages */ +#define TRACEMS(cinfo,lvl,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS1(cinfo,lvl,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS2(cinfo,lvl,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS3(cinfo,lvl,code,p1,p2,p3) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMSS(cinfo,lvl,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) + +#endif /* JERROR_H */ diff --git a/tools/urt/libs/jpeg6/jfdctflt.cpp b/tools/urt/libs/jpeg6/jfdctflt.cpp new file mode 100644 index 00000000..f41bff82 --- /dev/null +++ b/tools/urt/libs/jpeg6/jfdctflt.cpp @@ -0,0 +1,336 @@ +/* + + * jfdctflt.c + + * + + * Copyright (C) 1994, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains a floating-point implementation of the + + * forward DCT (Discrete Cosine Transform). + + * + + * This implementation should be more accurate than either of the integer + + * DCT implementations. However, it may not give the same results on all + + * machines because of differences in roundoff behavior. Speed will depend + + * on the hardware's floating point capacity. + + * + + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + + * on each column. Direct algorithms are also available, but they are + + * much more complex and seem not to be any faster when reduced to code. + + * + + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + + * JPEG textbook (see REFERENCES section in file README). The following code + + * is based directly on figure 4-8 in P&M. + + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + + * possible to arrange the computation so that many of the multiplies are + + * simple scalings of the final outputs. These multiplies can then be + + * folded into the multiplications or divisions by the JPEG quantization + + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + + * to be done in the DCT itself. + + * The primary disadvantage of this method is that with a fixed-point + + * implementation, accuracy is lost due to imprecise representation of the + + * scaled quantization values. However, that problem does not arise if + + * we use floating point arithmetic. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + +#include "jdct.h" /* Private declarations for DCT subsystem */ + + + +#ifdef DCT_FLOAT_SUPPORTED + + + + + +/* + + * This module is specialized to the case DCTSIZE = 8. + + */ + + + +#if DCTSIZE != 8 + + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ + +#endif + + + + + +/* + + * Perform the forward DCT on one block of samples. + + */ + + + +GLOBAL void + +jpeg_fdct_float (FAST_FLOAT * data) + +{ + + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + + FAST_FLOAT z1, z2, z3, z4, z5, z11, z13; + + FAST_FLOAT *dataptr; + + int ctr; + + + + /* Pass 1: process rows. */ + + + + dataptr = data; + + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + + tmp0 = dataptr[0] + dataptr[7]; + + tmp7 = dataptr[0] - dataptr[7]; + + tmp1 = dataptr[1] + dataptr[6]; + + tmp6 = dataptr[1] - dataptr[6]; + + tmp2 = dataptr[2] + dataptr[5]; + + tmp5 = dataptr[2] - dataptr[5]; + + tmp3 = dataptr[3] + dataptr[4]; + + tmp4 = dataptr[3] - dataptr[4]; + + + + /* Even part */ + + + + tmp10 = tmp0 + tmp3; /* phase 2 */ + + tmp13 = tmp0 - tmp3; + + tmp11 = tmp1 + tmp2; + + tmp12 = tmp1 - tmp2; + + + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + + dataptr[4] = tmp10 - tmp11; + + + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + + dataptr[2] = tmp13 + z1; /* phase 5 */ + + dataptr[6] = tmp13 - z1; + + + + /* Odd part */ + + + + tmp10 = tmp4 + tmp5; /* phase 2 */ + + tmp11 = tmp5 + tmp6; + + tmp12 = tmp6 + tmp7; + + + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + + + z11 = tmp7 + z3; /* phase 5 */ + + z13 = tmp7 - z3; + + + + dataptr[5] = z13 + z2; /* phase 6 */ + + dataptr[3] = z13 - z2; + + dataptr[1] = z11 + z4; + + dataptr[7] = z11 - z4; + + + + dataptr += DCTSIZE; /* advance pointer to next row */ + + } + + + + /* Pass 2: process columns. */ + + + + dataptr = data; + + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + + + /* Even part */ + + + + tmp10 = tmp0 + tmp3; /* phase 2 */ + + tmp13 = tmp0 - tmp3; + + tmp11 = tmp1 + tmp2; + + tmp12 = tmp1 - tmp2; + + + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + + dataptr[DCTSIZE*6] = tmp13 - z1; + + + + /* Odd part */ + + + + tmp10 = tmp4 + tmp5; /* phase 2 */ + + tmp11 = tmp5 + tmp6; + + tmp12 = tmp6 + tmp7; + + + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + + + z11 = tmp7 + z3; /* phase 5 */ + + z13 = tmp7 - z3; + + + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + + dataptr[DCTSIZE*3] = z13 - z2; + + dataptr[DCTSIZE*1] = z11 + z4; + + dataptr[DCTSIZE*7] = z11 - z4; + + + + dataptr++; /* advance pointer to next column */ + + } + +} + + + +#endif /* DCT_FLOAT_SUPPORTED */ + diff --git a/tools/urt/libs/jpeg6/jidctflt.cpp b/tools/urt/libs/jpeg6/jidctflt.cpp new file mode 100644 index 00000000..9e966daf --- /dev/null +++ b/tools/urt/libs/jpeg6/jidctflt.cpp @@ -0,0 +1,482 @@ +/* + + * jidctflt.c + + * + + * Copyright (C) 1994, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains a floating-point implementation of the + + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + + * must also perform dequantization of the input coefficients. + + * + + * This implementation should be more accurate than either of the integer + + * IDCT implementations. However, it may not give the same results on all + + * machines because of differences in roundoff behavior. Speed will depend + + * on the hardware's floating point capacity. + + * + + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + + * on each row (or vice versa, but it's more convenient to emit a row at + + * a time). Direct algorithms are also available, but they are much more + + * complex and seem not to be any faster when reduced to code. + + * + + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + + * JPEG textbook (see REFERENCES section in file README). The following code + + * is based directly on figure 4-8 in P&M. + + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + + * possible to arrange the computation so that many of the multiplies are + + * simple scalings of the final outputs. These multiplies can then be + + * folded into the multiplications or divisions by the JPEG quantization + + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + + * to be done in the DCT itself. + + * The primary disadvantage of this method is that with a fixed-point + + * implementation, accuracy is lost due to imprecise representation of the + + * scaled quantization values. However, that problem does not arise if + + * we use floating point arithmetic. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + +#include "jdct.h" /* Private declarations for DCT subsystem */ + + + +#ifdef DCT_FLOAT_SUPPORTED + + + + + +/* + + * This module is specialized to the case DCTSIZE = 8. + + */ + + + +#if DCTSIZE != 8 + + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ + +#endif + + + + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + + * entry; produce a float result. + + */ + + + +#define DEQUANTIZE(coef,quantval) (((FAST_FLOAT) (coef)) * (quantval)) + + + + + +/* + + * Perform dequantization and inverse DCT on one block of coefficients. + + */ + + + +GLOBAL void + +jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JCOEFPTR coef_block, + + JSAMPARRAY output_buf, JDIMENSION output_col) + +{ + + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + + FAST_FLOAT z5, z10, z11, z12, z13; + + JCOEFPTR inptr; + + FLOAT_MULT_TYPE * quantptr; + + FAST_FLOAT * wsptr; + + JSAMPROW outptr; + + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + + int ctr; + + FAST_FLOAT workspace[DCTSIZE2]; /* buffers data between passes */ + + SHIFT_TEMPS + + + + /* Pass 1: process columns from input, store into work array. */ + + + + inptr = coef_block; + + quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table; + + wsptr = workspace; + + for (ctr = DCTSIZE; ctr > 0; ctr--) { + + /* Due to quantization, we will usually find that many of the input + + * coefficients are zero, especially the AC terms. We can exploit this + + * by short-circuiting the IDCT calculation for any column in which all + + * the AC terms are zero. In that case each output is equal to the + + * DC coefficient (with scale factor as needed). + + * With typical images and quantization tables, half or more of the + + * column DCT calculations can be simplified this way. + + */ + + + + if ((inptr[DCTSIZE*1] | inptr[DCTSIZE*2] | inptr[DCTSIZE*3] | + + inptr[DCTSIZE*4] | inptr[DCTSIZE*5] | inptr[DCTSIZE*6] | + + inptr[DCTSIZE*7]) == 0) { + + /* AC terms all zero */ + + FAST_FLOAT dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + + + wsptr[DCTSIZE*0] = dcval; + + wsptr[DCTSIZE*1] = dcval; + + wsptr[DCTSIZE*2] = dcval; + + wsptr[DCTSIZE*3] = dcval; + + wsptr[DCTSIZE*4] = dcval; + + wsptr[DCTSIZE*5] = dcval; + + wsptr[DCTSIZE*6] = dcval; + + wsptr[DCTSIZE*7] = dcval; + + + + inptr++; /* advance pointers to next column */ + + quantptr++; + + wsptr++; + + continue; + + } + + + + /* Even part */ + + + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + + + tmp10 = tmp0 + tmp2; /* phase 3 */ + + tmp11 = tmp0 - tmp2; + + + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + + tmp12 = (tmp1 - tmp3) * ((FAST_FLOAT) 1.414213562) - tmp13; /* 2*c4 */ + + + + tmp0 = tmp10 + tmp13; /* phase 2 */ + + tmp3 = tmp10 - tmp13; + + tmp1 = tmp11 + tmp12; + + tmp2 = tmp11 - tmp12; + + + + /* Odd part */ + + + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + + + z13 = tmp6 + tmp5; /* phase 6 */ + + z10 = tmp6 - tmp5; + + z11 = tmp4 + tmp7; + + z12 = tmp4 - tmp7; + + + + tmp7 = z11 + z13; /* phase 5 */ + + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); /* 2*c4 */ + + + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + + + tmp6 = tmp12 - tmp7; /* phase 2 */ + + tmp5 = tmp11 - tmp6; + + tmp4 = tmp10 + tmp5; + + + + wsptr[DCTSIZE*0] = tmp0 + tmp7; + + wsptr[DCTSIZE*7] = tmp0 - tmp7; + + wsptr[DCTSIZE*1] = tmp1 + tmp6; + + wsptr[DCTSIZE*6] = tmp1 - tmp6; + + wsptr[DCTSIZE*2] = tmp2 + tmp5; + + wsptr[DCTSIZE*5] = tmp2 - tmp5; + + wsptr[DCTSIZE*4] = tmp3 + tmp4; + + wsptr[DCTSIZE*3] = tmp3 - tmp4; + + + + inptr++; /* advance pointers to next column */ + + quantptr++; + + wsptr++; + + } + + + + /* Pass 2: process rows from work array, store into output array. */ + + /* Note that we must descale the results by a factor of 8 == 2**3. */ + + + + wsptr = workspace; + + for (ctr = 0; ctr < DCTSIZE; ctr++) { + + outptr = output_buf[ctr] + output_col; + + /* Rows of zeroes can be exploited in the same way as we did with columns. + + * However, the column calculation has created many nonzero AC terms, so + + * the simplification applies less often (typically 5% to 10% of the time). + + * And testing floats for zero is relatively expensive, so we don't bother. + + */ + + + + /* Even part */ + + + + tmp10 = wsptr[0] + wsptr[4]; + + tmp11 = wsptr[0] - wsptr[4]; + + + + tmp13 = wsptr[2] + wsptr[6]; + + tmp12 = (wsptr[2] - wsptr[6]) * ((FAST_FLOAT) 1.414213562) - tmp13; + + + + tmp0 = tmp10 + tmp13; + + tmp3 = tmp10 - tmp13; + + tmp1 = tmp11 + tmp12; + + tmp2 = tmp11 - tmp12; + + + + /* Odd part */ + + + + z13 = wsptr[5] + wsptr[3]; + + z10 = wsptr[5] - wsptr[3]; + + z11 = wsptr[1] + wsptr[7]; + + z12 = wsptr[1] - wsptr[7]; + + + + tmp7 = z11 + z13; + + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); + + + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + + + tmp6 = tmp12 - tmp7; + + tmp5 = tmp11 - tmp6; + + tmp4 = tmp10 + tmp5; + + + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + + + outptr[0] = range_limit[(int) DESCALE((INT32) (tmp0 + tmp7), 3) + + & RANGE_MASK]; + + outptr[7] = range_limit[(int) DESCALE((INT32) (tmp0 - tmp7), 3) + + & RANGE_MASK]; + + outptr[1] = range_limit[(int) DESCALE((INT32) (tmp1 + tmp6), 3) + + & RANGE_MASK]; + + outptr[6] = range_limit[(int) DESCALE((INT32) (tmp1 - tmp6), 3) + + & RANGE_MASK]; + + outptr[2] = range_limit[(int) DESCALE((INT32) (tmp2 + tmp5), 3) + + & RANGE_MASK]; + + outptr[5] = range_limit[(int) DESCALE((INT32) (tmp2 - tmp5), 3) + + & RANGE_MASK]; + + outptr[4] = range_limit[(int) DESCALE((INT32) (tmp3 + tmp4), 3) + + & RANGE_MASK]; + + outptr[3] = range_limit[(int) DESCALE((INT32) (tmp3 - tmp4), 3) + + & RANGE_MASK]; + + + + wsptr += DCTSIZE; /* advance pointer to next row */ + + } + +} + + + +#endif /* DCT_FLOAT_SUPPORTED */ + diff --git a/tools/urt/libs/jpeg6/jinclude.h b/tools/urt/libs/jpeg6/jinclude.h new file mode 100644 index 00000000..5ff60fed --- /dev/null +++ b/tools/urt/libs/jpeg6/jinclude.h @@ -0,0 +1,91 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/tools/urt/libs/jpeg6/jmemmgr.cpp b/tools/urt/libs/jpeg6/jmemmgr.cpp new file mode 100644 index 00000000..9204b324 --- /dev/null +++ b/tools/urt/libs/jpeg6/jmemmgr.cpp @@ -0,0 +1,2230 @@ +/* + + * jmemmgr.c + + * + + * Copyright (C) 1991-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains the JPEG system-independent memory management + + * routines. This code is usable across a wide variety of machines; most + + * of the system dependencies have been isolated in a separate file. + + * The major functions provided here are: + + * * pool-based allocation and freeing of memory; + + * * policy decisions about how to divide available memory among the + + * virtual arrays; + + * * control logic for swapping virtual arrays between main memory and + + * backing storage. + + * The separate system-dependent file provides the actual backing-storage + + * access code, and it contains the policy decision about how much total + + * main memory to use. + + * This file is system-dependent in the sense that some of its functions + + * are unnecessary in some systems. For example, if there is enough virtual + + * memory so that backing storage will never be used, much of the virtual + + * array control logic could be removed. (Of course, if you have that much + + * memory then you shouldn't care about a little bit of unused code...) + + */ + + + +#define JPEG_INTERNALS + +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + +#include "jmemsys.h" /* import the system-dependent declarations */ + + + +#ifndef NO_GETENV + +#ifndef HAVE_STDLIB_H /* should declare getenv() */ + +extern char * getenv JPP((const char * name)); + +#endif + +#endif + + + + + +/* + + * Some important notes: + + * The allocation routines provided here must never return NULL. + + * They should exit to error_exit if unsuccessful. + + * + + * It's not a good idea to try to merge the sarray and barray routines, + + * even though they are textually almost the same, because samples are + + * usually stored as bytes while coefficients are shorts or ints. Thus, + + * in machines where byte pointers have a different representation from + + * word pointers, the resulting machine code could not be the same. + + */ + + + + + +/* + + * Many machines require storage alignment: longs must start on 4-byte + + * boundaries, doubles on 8-byte boundaries, etc. On such machines, malloc() + + * always returns pointers that are multiples of the worst-case alignment + + * requirement, and we had better do so too. + + * There isn't any really portable way to determine the worst-case alignment + + * requirement. This module assumes that the alignment requirement is + + * multiples of sizeof(ALIGN_TYPE). + + * By default, we define ALIGN_TYPE as double. This is necessary on some + + * workstations (where doubles really do need 8-byte alignment) and will work + + * fine on nearly everything. If your machine has lesser alignment needs, + + * you can save a few bytes by making ALIGN_TYPE smaller. + + * The only place I know of where this will NOT work is certain Macintosh + + * 680x0 compilers that define double as a 10-byte IEEE extended float. + + * Doing 10-byte alignment is counterproductive because longwords won't be + + * aligned well. Put "#define ALIGN_TYPE long" in jconfig.h if you have + + * such a compiler. + + */ + + + +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ + +#define ALIGN_TYPE double + +#endif + + + + + +/* + + * We allocate objects from "pools", where each pool is gotten with a single + + * request to jpeg_get_small() or jpeg_get_large(). There is no per-object + + * overhead within a pool, except for alignment padding. Each pool has a + + * header with a link to the next pool of the same class. + + * Small and large pool headers are identical except that the latter's + + * link pointer must be FAR on 80x86 machines. + + * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE + + * field. This forces the compiler to make SIZEOF(small_pool_hdr) a multiple + + * of the alignment requirement of ALIGN_TYPE. + + */ + + + +typedef union small_pool_struct * small_pool_ptr; + + + +typedef union small_pool_struct { + + struct { + + small_pool_ptr next; /* next in list of pools */ + + size_t bytes_used; /* how many bytes already used within pool */ + + size_t bytes_left; /* bytes still available in this pool */ + + } hdr; + + ALIGN_TYPE dummy; /* included in union to ensure alignment */ + +} small_pool_hdr; + + + +typedef union large_pool_struct FAR * large_pool_ptr; + + + +typedef union large_pool_struct { + + struct { + + large_pool_ptr next; /* next in list of pools */ + + size_t bytes_used; /* how many bytes already used within pool */ + + size_t bytes_left; /* bytes still available in this pool */ + + } hdr; + + ALIGN_TYPE dummy; /* included in union to ensure alignment */ + +} large_pool_hdr; + + + + + +/* + + * Here is the full definition of a memory manager object. + + */ + + + +typedef struct { + + struct jpeg_memory_mgr pub; /* public fields */ + + + + /* Each pool identifier (lifetime class) names a linked list of pools. */ + + small_pool_ptr small_list[JPOOL_NUMPOOLS]; + + large_pool_ptr large_list[JPOOL_NUMPOOLS]; + + + + /* Since we only have one lifetime class of virtual arrays, only one + + * linked list is necessary (for each datatype). Note that the virtual + + * array control blocks being linked together are actually stored somewhere + + * in the small-pool list. + + */ + + jvirt_sarray_ptr virt_sarray_list; + + jvirt_barray_ptr virt_barray_list; + + + + /* This counts total space obtained from jpeg_get_small/large */ + + long total_space_allocated; + + + + /* alloc_sarray and alloc_barray set this value for use by virtual + + * array routines. + + */ + + JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ + +} my_memory_mgr; + + + +typedef my_memory_mgr * my_mem_ptr; + + + + + +/* + + * The control blocks for virtual arrays. + + * Note that these blocks are allocated in the "small" pool area. + + * System-dependent info for the associated backing store (if any) is hidden + + * inside the backing_store_info struct. + + */ + + + +struct jvirt_sarray_control { + + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + + JDIMENSION rows_in_array; /* total virtual array height */ + + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + + JDIMENSION rows_in_mem; /* height of memory buffer */ + + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + + boolean pre_zero; /* pre-zero mode requested? */ + + boolean dirty; /* do current buffer contents need written? */ + + boolean b_s_open; /* is backing-store data valid? */ + + jvirt_sarray_ptr next; /* link to next virtual sarray control block */ + + backing_store_info b_s_info; /* System-dependent control info */ + +}; + + + +struct jvirt_barray_control { + + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + + JDIMENSION rows_in_array; /* total virtual array height */ + + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + + JDIMENSION rows_in_mem; /* height of memory buffer */ + + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + + boolean pre_zero; /* pre-zero mode requested? */ + + boolean dirty; /* do current buffer contents need written? */ + + boolean b_s_open; /* is backing-store data valid? */ + + jvirt_barray_ptr next; /* link to next virtual barray control block */ + + backing_store_info b_s_info; /* System-dependent control info */ + +}; + + + + + +#ifdef MEM_STATS /* optional extra stuff for statistics */ + + + +LOCAL void + +print_mem_stats (j_common_ptr cinfo, int pool_id) + +{ + + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + + small_pool_ptr shdr_ptr; + + large_pool_ptr lhdr_ptr; + + + + /* Since this is only a debugging stub, we can cheat a little by using + + * fprintf directly rather than going through the trace message code. + + * This is helpful because message parm array can't handle longs. + + */ + + fprintf(stderr, "Freeing pool %d, total space = %ld\n", + + pool_id, mem->total_space_allocated); + + + + for (lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; + + lhdr_ptr = lhdr_ptr->hdr.next) { + + fprintf(stderr, " Large chunk used %ld\n", + + (long) lhdr_ptr->hdr.bytes_used); + + } + + + + for (shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; + + shdr_ptr = shdr_ptr->hdr.next) { + + fprintf(stderr, " Small chunk used %ld free %ld\n", + + (long) shdr_ptr->hdr.bytes_used, + + (long) shdr_ptr->hdr.bytes_left); + + } + +} + + + +#endif /* MEM_STATS */ + + + + + +LOCAL void + +out_of_memory (j_common_ptr cinfo, int which) + +/* Report an out-of-memory error and stop execution */ + +/* If we compiled MEM_STATS support, report alloc requests before dying */ + +{ + +#ifdef MEM_STATS + + cinfo->err->trace_level = 2; /* force self_destruct to report stats */ + +#endif + + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, which); + +} + + + + + +/* + + * Allocation of "small" objects. + + * + + * For these, we use pooled storage. When a new pool must be created, + + * we try to get enough space for the current request plus a "slop" factor, + + * where the slop will be the amount of leftover space in the new pool. + + * The speed vs. space tradeoff is largely determined by the slop values. + + * A different slop value is provided for each pool class (lifetime), + + * and we also distinguish the first pool of a class from later ones. + + * NOTE: the values given work fairly well on both 16- and 32-bit-int + + * machines, but may be too small if longs are 64 bits or more. + + */ + + + +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = + +{ + + 1600, /* first PERMANENT pool */ + + 16000 /* first IMAGE pool */ + +}; + + + +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = + +{ + + 0, /* additional PERMANENT pools */ + + 5000 /* additional IMAGE pools */ + +}; + + + +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ + + + + + +METHODDEF void * + +alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) + +/* Allocate a "small" object */ + +{ + + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + + small_pool_ptr hdr_ptr, prev_hdr_ptr; + + char * data_ptr; + + size_t odd_bytes, min_request, slop; + + + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(small_pool_hdr))) + + out_of_memory(cinfo, 1); /* request exceeds malloc's ability */ + + + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + + if (odd_bytes > 0) + + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + + + /* See if space is available in any existing pool */ + + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + prev_hdr_ptr = NULL; + + hdr_ptr = mem->small_list[pool_id]; + + while (hdr_ptr != NULL) { + + if (hdr_ptr->hdr.bytes_left >= sizeofobject) + + break; /* found pool with enough space */ + + prev_hdr_ptr = hdr_ptr; + + hdr_ptr = hdr_ptr->hdr.next; + + } + + + + /* Time to make a new pool? */ + + if (hdr_ptr == NULL) { + + /* min_request is what we need now, slop is what will be leftover */ + + min_request = sizeofobject + SIZEOF(small_pool_hdr); + + if (prev_hdr_ptr == NULL) /* first pool in class? */ + + slop = first_pool_slop[pool_id]; + + else + + slop = extra_pool_slop[pool_id]; + + /* Don't ask for more than MAX_ALLOC_CHUNK */ + + if (slop > (size_t) (MAX_ALLOC_CHUNK-min_request)) + + slop = (size_t) (MAX_ALLOC_CHUNK-min_request); + + /* Try to get space, if fail reduce slop and try again */ + + for (;;) { + + hdr_ptr = (small_pool_ptr) jpeg_get_small(cinfo, min_request + slop); + + if (hdr_ptr != NULL) + + break; + + slop /= 2; + + if (slop < MIN_SLOP) /* give up when it gets real small */ + + out_of_memory(cinfo, 2); /* jpeg_get_small failed */ + + } + + mem->total_space_allocated += min_request + slop; + + /* Success, initialize the new pool header and add to end of list */ + + hdr_ptr->hdr.next = NULL; + + hdr_ptr->hdr.bytes_used = 0; + + hdr_ptr->hdr.bytes_left = sizeofobject + slop; + + if (prev_hdr_ptr == NULL) /* first pool in class? */ + + mem->small_list[pool_id] = hdr_ptr; + + else + + prev_hdr_ptr->hdr.next = hdr_ptr; + + } + + + + /* OK, allocate the object from the current pool */ + + data_ptr = (char *) (hdr_ptr + 1); /* point to first data byte in pool */ + + data_ptr += hdr_ptr->hdr.bytes_used; /* point to place for object */ + + hdr_ptr->hdr.bytes_used += sizeofobject; + + hdr_ptr->hdr.bytes_left -= sizeofobject; + + + + return (void *) data_ptr; + +} + + + + + +/* + + * Allocation of "large" objects. + + * + + * The external semantics of these are the same as "small" objects, + + * except that FAR pointers are used on 80x86. However the pool + + * management heuristics are quite different. We assume that each + + * request is large enough that it may as well be passed directly to + + * jpeg_get_large; the pool management just links everything together + + * so that we can free it all on demand. + + * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY + + * structures. The routines that create these structures (see below) + + * deliberately bunch rows together to ensure a large request size. + + */ + + + +METHODDEF void FAR * + +alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) + +/* Allocate a "large" object */ + +{ + + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + + large_pool_ptr hdr_ptr; + + size_t odd_bytes; + + + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr))) + + out_of_memory(cinfo, 3); /* request exceeds malloc's ability */ + + + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + + if (odd_bytes > 0) + + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + + + /* Always make a new pool */ + + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + + + hdr_ptr = (large_pool_ptr) jpeg_get_large(cinfo, sizeofobject + + + SIZEOF(large_pool_hdr)); + + if (hdr_ptr == NULL) + + out_of_memory(cinfo, 4); /* jpeg_get_large failed */ + + mem->total_space_allocated += sizeofobject + SIZEOF(large_pool_hdr); + + + + /* Success, initialize the new pool header and add to list */ + + hdr_ptr->hdr.next = mem->large_list[pool_id]; + + /* We maintain space counts in each pool header for statistical purposes, + + * even though they are not needed for allocation. + + */ + + hdr_ptr->hdr.bytes_used = sizeofobject; + + hdr_ptr->hdr.bytes_left = 0; + + mem->large_list[pool_id] = hdr_ptr; + + + + return (void FAR *) (hdr_ptr + 1); /* point to first data byte in pool */ + +} + + + + + +/* + + * Creation of 2-D sample arrays. + + * The pointers are in near heap, the samples themselves in FAR heap. + + * + + * To minimize allocation overhead and to allow I/O of large contiguous + + * blocks, we allocate the sample rows in groups of as many rows as possible + + * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request. + + * NB: the virtual array control routines, later in this file, know about + + * this chunking of rows. The rowsperchunk value is left in the mem manager + + * object so that it can be saved away if this sarray is the workspace for + + * a virtual array. + + */ + + + +METHODDEF JSAMPARRAY + +alloc_sarray (j_common_ptr cinfo, int pool_id, + + JDIMENSION samplesperrow, JDIMENSION numrows) + +/* Allocate a 2-D sample array */ + +{ + + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + + JSAMPARRAY result; + + JSAMPROW workspace; + + JDIMENSION rowsperchunk, currow, i; + + long ltemp; + + + + /* Calculate max # of rows allowed in one allocation chunk */ + + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + + ((long) samplesperrow * SIZEOF(JSAMPLE)); + + if (ltemp <= 0) + + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + if (ltemp < (long) numrows) + + rowsperchunk = (JDIMENSION) ltemp; + + else + + rowsperchunk = numrows; + + mem->last_rowsperchunk = rowsperchunk; + + + + /* Get space for row pointers (small object) */ + + result = (JSAMPARRAY) alloc_small(cinfo, pool_id, + + (size_t) (numrows * SIZEOF(JSAMPROW))); + + + + /* Get the rows themselves (large objects) */ + + currow = 0; + + while (currow < numrows) { + + rowsperchunk = MIN(rowsperchunk, numrows - currow); + + workspace = (JSAMPROW) alloc_large(cinfo, pool_id, + + (size_t) ((size_t) rowsperchunk * (size_t) samplesperrow + + * SIZEOF(JSAMPLE))); + + for (i = rowsperchunk; i > 0; i--) { + + result[currow++] = workspace; + + workspace += samplesperrow; + + } + + } + + + + return result; + +} + + + + + +/* + + * Creation of 2-D coefficient-block arrays. + + * This is essentially the same as the code for sample arrays, above. + + */ + + + +METHODDEF JBLOCKARRAY + +alloc_barray (j_common_ptr cinfo, int pool_id, + + JDIMENSION blocksperrow, JDIMENSION numrows) + +/* Allocate a 2-D coefficient-block array */ + +{ + + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + + JBLOCKARRAY result; + + JBLOCKROW workspace; + + JDIMENSION rowsperchunk, currow, i; + + long ltemp; + + + + /* Calculate max # of rows allowed in one allocation chunk */ + + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + + ((long) blocksperrow * SIZEOF(JBLOCK)); + + if (ltemp <= 0) + + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + if (ltemp < (long) numrows) + + rowsperchunk = (JDIMENSION) ltemp; + + else + + rowsperchunk = numrows; + + mem->last_rowsperchunk = rowsperchunk; + + + + /* Get space for row pointers (small object) */ + + result = (JBLOCKARRAY) alloc_small(cinfo, pool_id, + + (size_t) (numrows * SIZEOF(JBLOCKROW))); + + + + /* Get the rows themselves (large objects) */ + + currow = 0; + + while (currow < numrows) { + + rowsperchunk = MIN(rowsperchunk, numrows - currow); + + workspace = (JBLOCKROW) alloc_large(cinfo, pool_id, + + (size_t) ((size_t) rowsperchunk * (size_t) blocksperrow + + * SIZEOF(JBLOCK))); + + for (i = rowsperchunk; i > 0; i--) { + + result[currow++] = workspace; + + workspace += blocksperrow; + + } + + } + + + + return result; + +} + + + + + +/* + + * About virtual array management: + + * + + * The above "normal" array routines are only used to allocate strip buffers + + * (as wide as the image, but just a few rows high). Full-image-sized buffers + + * are handled as "virtual" arrays. The array is still accessed a strip at a + + * time, but the memory manager must save the whole array for repeated + + * accesses. The intended implementation is that there is a strip buffer in + + * memory (as high as is possible given the desired memory limit), plus a + + * backing file that holds the rest of the array. + + * + + * The request_virt_array routines are told the total size of the image and + + * the maximum number of rows that will be accessed at once. The in-memory + + * buffer must be at least as large as the maxaccess value. + + * + + * The request routines create control blocks but not the in-memory buffers. + + * That is postponed until realize_virt_arrays is called. At that time the + + * total amount of space needed is known (approximately, anyway), so free + + * memory can be divided up fairly. + + * + + * The access_virt_array routines are responsible for making a specific strip + + * area accessible (after reading or writing the backing file, if necessary). + + * Note that the access routines are told whether the caller intends to modify + + * the accessed strip; during a read-only pass this saves having to rewrite + + * data to disk. The access routines are also responsible for pre-zeroing + + * any newly accessed rows, if pre-zeroing was requested. + + * + + * In current usage, the access requests are usually for nonoverlapping + + * strips; that is, successive access start_row numbers differ by exactly + + * num_rows = maxaccess. This means we can get good performance with simple + + * buffer dump/reload logic, by making the in-memory buffer be a multiple + + * of the access height; then there will never be accesses across bufferload + + * boundaries. The code will still work with overlapping access requests, + + * but it doesn't handle bufferload overlaps very efficiently. + + */ + + + + + +METHODDEF jvirt_sarray_ptr + +request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + + JDIMENSION samplesperrow, JDIMENSION numrows, + + JDIMENSION maxaccess) + +/* Request a virtual 2-D sample array */ + +{ + + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + + jvirt_sarray_ptr result; + + + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + + if (pool_id != JPOOL_IMAGE) + + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + + + /* get control block */ + + result = (jvirt_sarray_ptr) alloc_small(cinfo, pool_id, + + SIZEOF(struct jvirt_sarray_control)); + + + + result->mem_buffer = NULL; /* marks array not yet realized */ + + result->rows_in_array = numrows; + + result->samplesperrow = samplesperrow; + + result->maxaccess = maxaccess; + + result->pre_zero = pre_zero; + + result->b_s_open = FALSE; /* no associated backing-store object */ + + result->next = mem->virt_sarray_list; /* add to list of virtual arrays */ + + mem->virt_sarray_list = result; + + + + return result; + +} + + + + + +METHODDEF jvirt_barray_ptr + +request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + + JDIMENSION blocksperrow, JDIMENSION numrows, + + JDIMENSION maxaccess) + +/* Request a virtual 2-D coefficient-block array */ + +{ + + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + + jvirt_barray_ptr result; + + + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + + if (pool_id != JPOOL_IMAGE) + + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + + + /* get control block */ + + result = (jvirt_barray_ptr) alloc_small(cinfo, pool_id, + + SIZEOF(struct jvirt_barray_control)); + + + + result->mem_buffer = NULL; /* marks array not yet realized */ + + result->rows_in_array = numrows; + + result->blocksperrow = blocksperrow; + + result->maxaccess = maxaccess; + + result->pre_zero = pre_zero; + + result->b_s_open = FALSE; /* no associated backing-store object */ + + result->next = mem->virt_barray_list; /* add to list of virtual arrays */ + + mem->virt_barray_list = result; + + + + return result; + +} + + + + + +METHODDEF void + +realize_virt_arrays (j_common_ptr cinfo) + +/* Allocate the in-memory buffers for any unrealized virtual arrays */ + +{ + + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + + long space_per_minheight, maximum_space, avail_mem; + + long minheights, max_minheights; + + jvirt_sarray_ptr sptr; + + jvirt_barray_ptr bptr; + + + + /* Compute the minimum space needed (maxaccess rows in each buffer) + + * and the maximum space needed (full image height in each buffer). + + * These may be of use to the system-dependent jpeg_mem_available routine. + + */ + + space_per_minheight = 0; + + maximum_space = 0; + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + + space_per_minheight += (long) sptr->maxaccess * + + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + + maximum_space += (long) sptr->rows_in_array * + + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + + } + + } + + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + + space_per_minheight += (long) bptr->maxaccess * + + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + + maximum_space += (long) bptr->rows_in_array * + + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + + } + + } + + + + if (space_per_minheight <= 0) + + return; /* no unrealized arrays, no work */ + + + + /* Determine amount of memory to actually use; this is system-dependent. */ + + avail_mem = jpeg_mem_available(cinfo, space_per_minheight, maximum_space, + + mem->total_space_allocated); + + + + /* If the maximum space needed is available, make all the buffers full + + * height; otherwise parcel it out with the same number of minheights + + * in each buffer. + + */ + + if (avail_mem >= maximum_space) + + max_minheights = 1000000000L; + + else { + + max_minheights = avail_mem / space_per_minheight; + + /* If there doesn't seem to be enough space, try to get the minimum + + * anyway. This allows a "stub" implementation of jpeg_mem_available(). + + */ + + if (max_minheights <= 0) + + max_minheights = 1; + + } + + + + /* Allocate the in-memory buffers and initialize backing store as needed. */ + + + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + + minheights = ((long) sptr->rows_in_array - 1L) / sptr->maxaccess + 1L; + + if (minheights <= max_minheights) { + + /* This buffer fits in memory */ + + sptr->rows_in_mem = sptr->rows_in_array; + + } else { + + /* It doesn't fit in memory, create backing store. */ + + sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess); + + jpeg_open_backing_store(cinfo, & sptr->b_s_info, + + (long) sptr->rows_in_array * + + (long) sptr->samplesperrow * + + (long) SIZEOF(JSAMPLE)); + + sptr->b_s_open = TRUE; + + } + + sptr->mem_buffer = alloc_sarray(cinfo, JPOOL_IMAGE, + + sptr->samplesperrow, sptr->rows_in_mem); + + sptr->rowsperchunk = mem->last_rowsperchunk; + + sptr->cur_start_row = 0; + + sptr->first_undef_row = 0; + + sptr->dirty = FALSE; + + } + + } + + + + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + + minheights = ((long) bptr->rows_in_array - 1L) / bptr->maxaccess + 1L; + + if (minheights <= max_minheights) { + + /* This buffer fits in memory */ + + bptr->rows_in_mem = bptr->rows_in_array; + + } else { + + /* It doesn't fit in memory, create backing store. */ + + bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess); + + jpeg_open_backing_store(cinfo, & bptr->b_s_info, + + (long) bptr->rows_in_array * + + (long) bptr->blocksperrow * + + (long) SIZEOF(JBLOCK)); + + bptr->b_s_open = TRUE; + + } + + bptr->mem_buffer = alloc_barray(cinfo, JPOOL_IMAGE, + + bptr->blocksperrow, bptr->rows_in_mem); + + bptr->rowsperchunk = mem->last_rowsperchunk; + + bptr->cur_start_row = 0; + + bptr->first_undef_row = 0; + + bptr->dirty = FALSE; + + } + + } + +} + + + + + +LOCAL void + +do_sarray_io (j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing) + +/* Do backing store read or write of a virtual sample array */ + +{ + + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + + + bytesperrow = (long) ptr->samplesperrow * SIZEOF(JSAMPLE); + + file_offset = ptr->cur_start_row * bytesperrow; + + /* Loop to read or write each allocation chunk in mem_buffer */ + + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + + /* One chunk, but check for short chunk at end of buffer */ + + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + + /* Transfer no more than is currently defined */ + + thisrow = (long) ptr->cur_start_row + i; + + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + + /* Transfer no more than fits in file */ + + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + + if (rows <= 0) /* this chunk might be past end of file! */ + + break; + + byte_count = rows * bytesperrow; + + if (writing) + + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + + (void FAR *) ptr->mem_buffer[i], + + file_offset, byte_count); + + else + + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + + (void FAR *) ptr->mem_buffer[i], + + file_offset, byte_count); + + file_offset += byte_count; + + } + +} + + + + + +LOCAL void + +do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing) + +/* Do backing store read or write of a virtual coefficient-block array */ + +{ + + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + + + bytesperrow = (long) ptr->blocksperrow * SIZEOF(JBLOCK); + + file_offset = ptr->cur_start_row * bytesperrow; + + /* Loop to read or write each allocation chunk in mem_buffer */ + + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + + /* One chunk, but check for short chunk at end of buffer */ + + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + + /* Transfer no more than is currently defined */ + + thisrow = (long) ptr->cur_start_row + i; + + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + + /* Transfer no more than fits in file */ + + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + + if (rows <= 0) /* this chunk might be past end of file! */ + + break; + + byte_count = rows * bytesperrow; + + if (writing) + + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + + (void FAR *) ptr->mem_buffer[i], + + file_offset, byte_count); + + else + + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + + (void FAR *) ptr->mem_buffer[i], + + file_offset, byte_count); + + file_offset += byte_count; + + } + +} + + + + + +METHODDEF JSAMPARRAY + +access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, + + JDIMENSION start_row, JDIMENSION num_rows, + + boolean writable) + +/* Access the part of a virtual sample array starting at start_row */ + +/* and extending for num_rows rows. writable is true if */ + +/* caller intends to modify the accessed area. */ + +{ + + JDIMENSION end_row = start_row + num_rows; + + JDIMENSION undef_row; + + + + /* debugging check */ + + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + + ptr->mem_buffer == NULL) + + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + + + /* Make the desired part of the virtual array accessible */ + + if (start_row < ptr->cur_start_row || + + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + + if (! ptr->b_s_open) + + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + + /* Flush old buffer contents if necessary */ + + if (ptr->dirty) { + + do_sarray_io(cinfo, ptr, TRUE); + + ptr->dirty = FALSE; + + } + + /* Decide what part of virtual array to access. + + * Algorithm: if target address > current window, assume forward scan, + + * load starting at target address. If target address < current window, + + * assume backward scan, load so that target area is top of window. + + * Note that when switching from forward write to forward read, will have + + * start_row = 0, so the limiting case applies and we load from 0 anyway. + + */ + + if (start_row > ptr->cur_start_row) { + + ptr->cur_start_row = start_row; + + } else { + + /* use long arithmetic here to avoid overflow & unsigned problems */ + + long ltemp; + + + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + + if (ltemp < 0) + + ltemp = 0; /* don't fall off front end of file */ + + ptr->cur_start_row = (JDIMENSION) ltemp; + + } + + /* Read in the selected part of the array. + + * During the initial write pass, we will do no actual read + + * because the selected part is all undefined. + + */ + + do_sarray_io(cinfo, ptr, FALSE); + + } + + /* Ensure the accessed part of the array is defined; prezero if needed. + + * To improve locality of access, we only prezero the part of the array + + * that the caller is about to access, not the entire in-memory array. + + */ + + if (ptr->first_undef_row < end_row) { + + if (ptr->first_undef_row < start_row) { + + if (writable) /* writer skipped over a section of array */ + + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + undef_row = start_row; /* but reader is allowed to read ahead */ + + } else { + + undef_row = ptr->first_undef_row; + + } + + if (writable) + + ptr->first_undef_row = end_row; + + if (ptr->pre_zero) { + + size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF(JSAMPLE); + + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + + end_row -= ptr->cur_start_row; + + while (undef_row < end_row) { + + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + + undef_row++; + + } + + } else { + + if (! writable) /* reader looking at undefined data */ + + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + } + + } + + /* Flag the buffer dirty if caller will write in it */ + + if (writable) + + ptr->dirty = TRUE; + + /* Return address of proper part of the buffer */ + + return ptr->mem_buffer + (start_row - ptr->cur_start_row); + +} + + + + + +METHODDEF JBLOCKARRAY + +access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, + + JDIMENSION start_row, JDIMENSION num_rows, + + boolean writable) + +/* Access the part of a virtual block array starting at start_row */ + +/* and extending for num_rows rows. writable is true if */ + +/* caller intends to modify the accessed area. */ + +{ + + JDIMENSION end_row = start_row + num_rows; + + JDIMENSION undef_row; + + + + /* debugging check */ + + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + + ptr->mem_buffer == NULL) + + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + + + /* Make the desired part of the virtual array accessible */ + + if (start_row < ptr->cur_start_row || + + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + + if (! ptr->b_s_open) + + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + + /* Flush old buffer contents if necessary */ + + if (ptr->dirty) { + + do_barray_io(cinfo, ptr, TRUE); + + ptr->dirty = FALSE; + + } + + /* Decide what part of virtual array to access. + + * Algorithm: if target address > current window, assume forward scan, + + * load starting at target address. If target address < current window, + + * assume backward scan, load so that target area is top of window. + + * Note that when switching from forward write to forward read, will have + + * start_row = 0, so the limiting case applies and we load from 0 anyway. + + */ + + if (start_row > ptr->cur_start_row) { + + ptr->cur_start_row = start_row; + + } else { + + /* use long arithmetic here to avoid overflow & unsigned problems */ + + long ltemp; + + + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + + if (ltemp < 0) + + ltemp = 0; /* don't fall off front end of file */ + + ptr->cur_start_row = (JDIMENSION) ltemp; + + } + + /* Read in the selected part of the array. + + * During the initial write pass, we will do no actual read + + * because the selected part is all undefined. + + */ + + do_barray_io(cinfo, ptr, FALSE); + + } + + /* Ensure the accessed part of the array is defined; prezero if needed. + + * To improve locality of access, we only prezero the part of the array + + * that the caller is about to access, not the entire in-memory array. + + */ + + if (ptr->first_undef_row < end_row) { + + if (ptr->first_undef_row < start_row) { + + if (writable) /* writer skipped over a section of array */ + + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + undef_row = start_row; /* but reader is allowed to read ahead */ + + } else { + + undef_row = ptr->first_undef_row; + + } + + if (writable) + + ptr->first_undef_row = end_row; + + if (ptr->pre_zero) { + + size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF(JBLOCK); + + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + + end_row -= ptr->cur_start_row; + + while (undef_row < end_row) { + + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + + undef_row++; + + } + + } else { + + if (! writable) /* reader looking at undefined data */ + + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + } + + } + + /* Flag the buffer dirty if caller will write in it */ + + if (writable) + + ptr->dirty = TRUE; + + /* Return address of proper part of the buffer */ + + return ptr->mem_buffer + (start_row - ptr->cur_start_row); + +} + + + + + +/* + + * Release all objects belonging to a specified pool. + + */ + + + +METHODDEF void + +free_pool (j_common_ptr cinfo, int pool_id) + +{ + + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + + small_pool_ptr shdr_ptr; + + large_pool_ptr lhdr_ptr; + + size_t space_freed; + + + + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + + +#ifdef MEM_STATS + + if (cinfo->err->trace_level > 1) + + print_mem_stats(cinfo, pool_id); /* print pool's memory usage statistics */ + +#endif + + + + /* If freeing IMAGE pool, close any virtual arrays first */ + + if (pool_id == JPOOL_IMAGE) { + + jvirt_sarray_ptr sptr; + + jvirt_barray_ptr bptr; + + + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + + if (sptr->b_s_open) { /* there may be no backing store */ + + sptr->b_s_open = FALSE; /* prevent recursive close if error */ + + (*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info); + + } + + } + + mem->virt_sarray_list = NULL; + + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + + if (bptr->b_s_open) { /* there may be no backing store */ + + bptr->b_s_open = FALSE; /* prevent recursive close if error */ + + (*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info); + + } + + } + + mem->virt_barray_list = NULL; + + } + + + + /* Release large objects */ + + lhdr_ptr = mem->large_list[pool_id]; + + mem->large_list[pool_id] = NULL; + + + + while (lhdr_ptr != NULL) { + + large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; + + space_freed = lhdr_ptr->hdr.bytes_used + + + lhdr_ptr->hdr.bytes_left + + + SIZEOF(large_pool_hdr); + + jpeg_free_large(cinfo, (void FAR *) lhdr_ptr, space_freed); + + mem->total_space_allocated -= space_freed; + + lhdr_ptr = next_lhdr_ptr; + + } + + + + /* Release small objects */ + + shdr_ptr = mem->small_list[pool_id]; + + mem->small_list[pool_id] = NULL; + + + + while (shdr_ptr != NULL) { + + small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; + + space_freed = shdr_ptr->hdr.bytes_used + + + shdr_ptr->hdr.bytes_left + + + SIZEOF(small_pool_hdr); + + jpeg_free_small(cinfo, (void *) shdr_ptr, space_freed); + + mem->total_space_allocated -= space_freed; + + shdr_ptr = next_shdr_ptr; + + } + +} + + + + + +/* + + * Close up shop entirely. + + * Note that this cannot be called unless cinfo->mem is non-NULL. + + */ + + + +METHODDEF void + +self_destruct (j_common_ptr cinfo) + +{ + + int pool; + + + + /* Close all backing store, release all memory. + + * Releasing pools in reverse order might help avoid fragmentation + + * with some (brain-damaged) malloc libraries. + + */ + + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + + free_pool(cinfo, pool); + + } + + + + /* Release the memory manager control block too. */ + + jpeg_free_small(cinfo, (void *) cinfo->mem, SIZEOF(my_memory_mgr)); + + cinfo->mem = NULL; /* ensures I will be called only once */ + + + + jpeg_mem_term(cinfo); /* system-dependent cleanup */ + +} + + + + + +/* + + * Memory manager initialization. + + * When this is called, only the error manager pointer is valid in cinfo! + + */ + + + +GLOBAL void + +jinit_memory_mgr (j_common_ptr cinfo) + +{ + + my_mem_ptr mem; + + long max_to_use; + + int pool; + + size_t test_mac; + + + + cinfo->mem = NULL; /* for safety if init fails */ + + + + /* Check for configuration errors. + + * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably + + * doesn't reflect any real hardware alignment requirement. + + * The test is a little tricky: for X>0, X and X-1 have no one-bits + + * in common if and only if X is a power of 2, ie has only one one-bit. + + * Some compilers may give an "unreachable code" warning here; ignore it. + + */ + + if ((SIZEOF(ALIGN_TYPE) & (SIZEOF(ALIGN_TYPE)-1)) != 0) + + ERREXIT(cinfo, JERR_BAD_ALIGN_TYPE); + + /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be + + * a multiple of SIZEOF(ALIGN_TYPE). + + * Again, an "unreachable code" warning may be ignored here. + + * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK. + + */ + + test_mac = (size_t) MAX_ALLOC_CHUNK; + + if ((long) test_mac != MAX_ALLOC_CHUNK || + + (MAX_ALLOC_CHUNK % SIZEOF(ALIGN_TYPE)) != 0) + + ERREXIT(cinfo, JERR_BAD_ALLOC_CHUNK); + + + + max_to_use = jpeg_mem_init(cinfo); /* system-dependent initialization */ + + + + /* Attempt to allocate memory manager's control block */ + + mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr)); + + + + if (mem == NULL) { + + jpeg_mem_term(cinfo); /* system-dependent cleanup */ + + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0); + + } + + + + /* OK, fill in the method pointers */ + + mem->pub.alloc_small = alloc_small; + + mem->pub.alloc_large = alloc_large; + + mem->pub.alloc_sarray = alloc_sarray; + + mem->pub.alloc_barray = alloc_barray; + + mem->pub.request_virt_sarray = request_virt_sarray; + + mem->pub.request_virt_barray = request_virt_barray; + + mem->pub.realize_virt_arrays = realize_virt_arrays; + + mem->pub.access_virt_sarray = access_virt_sarray; + + mem->pub.access_virt_barray = access_virt_barray; + + mem->pub.free_pool = free_pool; + + mem->pub.self_destruct = self_destruct; + + + + /* Initialize working state */ + + mem->pub.max_memory_to_use = max_to_use; + + + + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + + mem->small_list[pool] = NULL; + + mem->large_list[pool] = NULL; + + } + + mem->virt_sarray_list = NULL; + + mem->virt_barray_list = NULL; + + + + mem->total_space_allocated = SIZEOF(my_memory_mgr); + + + + /* Declare ourselves open for business */ + + cinfo->mem = & mem->pub; + + + + /* Check for an environment variable JPEGMEM; if found, override the + + * default max_memory setting from jpeg_mem_init. Note that the + + * surrounding application may again override this value. + + * If your system doesn't support getenv(), define NO_GETENV to disable + + * this feature. + + */ + +#ifndef NO_GETENV + + { char * memenv; + + + + if ((memenv = getenv("JPEGMEM")) != NULL) { + + char ch = 'x'; + + + + if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) { + + if (ch == 'm' || ch == 'M') + + max_to_use *= 1000L; + + mem->pub.max_memory_to_use = max_to_use * 1000L; + + } + + } + + } + +#endif + + + +} + diff --git a/tools/urt/libs/jpeg6/jmemnobs.cpp b/tools/urt/libs/jpeg6/jmemnobs.cpp new file mode 100644 index 00000000..bd236e54 --- /dev/null +++ b/tools/urt/libs/jpeg6/jmemnobs.cpp @@ -0,0 +1,206 @@ +/* + + * jmemnobs.c + + * + + * Copyright (C) 1992-1994, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file provides a really simple implementation of the system- + + * dependent portion of the JPEG memory manager. This implementation + + * assumes that no backing-store files are needed: all required space + + * can be obtained from ri.Malloc(). + + * This is very portable in the sense that it'll compile on almost anything, + + * but you'd better have lots of main memory (or virtual memory) if you want + + * to process big images. + + * Note that the max_memory_to_use option is ignored by this implementation. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + +#include "jmemsys.h" /* import the system-dependent declarations */ + + + +/* + + * Memory allocation and ri.Freeing are controlled by the regular library + + * routines ri.Malloc() and ri.Free(). + + */ + + + +GLOBAL void * + +jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject) + +{ + + return (void *) malloc(sizeofobject); + +} + + + +GLOBAL void + +jpeg_free_small (j_common_ptr cinfo, void * object, size_t sizeofobject) + +{ + + free(object); + +} + + + + + +/* + + * "Large" objects are treated the same as "small" ones. + + * NB: although we include FAR keywords in the routine declarations, + + * this file won't actually work in 80x86 small/medium model; at least, + + * you probably won't be able to process useful-size images in only 64KB. + + */ + + + +GLOBAL void FAR * + +jpeg_get_large (j_common_ptr cinfo, size_t sizeofobject) + +{ + + return (void FAR *) malloc(sizeofobject); + +} + + + +GLOBAL void + +jpeg_free_large (j_common_ptr cinfo, void FAR * object, size_t sizeofobject) + +{ + + free(object); + +} + + + + + +/* + + * This routine computes the total memory space available for allocation. + + * Here we always say, "we got all you want bud!" + + */ + + + +GLOBAL long + +jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed, + + long max_bytes_needed, long already_allocated) + +{ + + return max_bytes_needed; + +} + + + + + +/* + + * Backing store (temporary file) management. + + * Since jpeg_mem_available always promised the moon, + + * this should never be called and we can just error out. + + */ + + + +GLOBAL void + +jpeg_open_backing_store (j_common_ptr cinfo, backing_store_ptr info, + + long total_bytes_needed) + +{ + + ERREXIT(cinfo, JERR_NO_BACKING_STORE); + +} + + + + + +/* + + * These routines take care of any system-dependent initialization and + + * cleanup required. Here, there isn't any. + + */ + + + +GLOBAL long + +jpeg_mem_init (j_common_ptr cinfo) + +{ + + return 0; /* just set max_memory_to_use to 0 */ + +} + + + +GLOBAL void + +jpeg_mem_term (j_common_ptr cinfo) + +{ + + /* no work */ + +} + diff --git a/tools/urt/libs/jpeg6/jmemsys.h b/tools/urt/libs/jpeg6/jmemsys.h new file mode 100644 index 00000000..9c8028bc --- /dev/null +++ b/tools/urt/libs/jpeg6/jmemsys.h @@ -0,0 +1,364 @@ +/* + + * jmemsys.h + + * + + * Copyright (C) 1992-1994, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This include file defines the interface between the system-independent + + * and system-dependent portions of the JPEG memory manager. No other + + * modules need include it. (The system-independent portion is jmemmgr.c; + + * there are several different versions of the system-dependent portion.) + + * + + * This file works as-is for the system-dependent memory managers supplied + + * in the IJG distribution. You may need to modify it if you write a + + * custom memory manager. If system-dependent changes are needed in + + * this file, the best method is to #ifdef them based on a configuration + + * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR. + + */ + + + + + +/* Short forms of external names for systems with brain-damaged linkers. */ + + + +#ifdef NEED_SHORT_EXTERNAL_NAMES + +#define jpeg_get_small jGetSmall + +#define jpeg_free_small jFreeSmall + +#define jpeg_get_large jGetLarge + +#define jpeg_free_large jFreeLarge + +#define jpeg_mem_available jMemAvail + +#define jpeg_open_backing_store jOpenBackStore + +#define jpeg_mem_init jMemInit + +#define jpeg_mem_term jMemTerm + +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + + + + +/* + + * These two functions are used to allocate and release small chunks of + + * memory. (Typically the total amount requested through jpeg_get_small is + + * no more than 20K or so; this will be requested in chunks of a few K each.) + + * Behavior should be the same as for the standard library functions malloc + + * and free; in particular, jpeg_get_small must return NULL on failure. + + * On most systems, these ARE malloc and free. jpeg_free_small is passed the + + * size of the object being freed, just in case it's needed. + + * On an 80x86 machine using small-data memory model, these manage near heap. + + */ + + + +EXTERN void * jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject)); + +EXTERN void jpeg_free_small JPP((j_common_ptr cinfo, void * object, + + size_t sizeofobject)); + + + +/* + + * These two functions are used to allocate and release large chunks of + + * memory (up to the total free space designated by jpeg_mem_available). + + * The interface is the same as above, except that on an 80x86 machine, + + * far pointers are used. On most other machines these are identical to + + * the jpeg_get/free_small routines; but we keep them separate anyway, + + * in case a different allocation strategy is desirable for large chunks. + + */ + + + +EXTERN void FAR * jpeg_get_large JPP((j_common_ptr cinfo,size_t sizeofobject)); + +EXTERN void jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, + + size_t sizeofobject)); + + + +/* + + * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may + + * be requested in a single call to jpeg_get_large (and jpeg_get_small for that + + * matter, but that case should never come into play). This macro is needed + + * to model the 64Kb-segment-size limit of far addressing on 80x86 machines. + + * On those machines, we expect that jconfig.h will provide a proper value. + + * On machines with 32-bit flat address spaces, any large constant may be used. + + * + + * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type + + * size_t and will be a multiple of sizeof(align_type). + + */ + + + +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ + +#define MAX_ALLOC_CHUNK 1000000000L + +#endif + + + +/* + + * This routine computes the total space still available for allocation by + + * jpeg_get_large. If more space than this is needed, backing store will be + + * used. NOTE: any memory already allocated must not be counted. + + * + + * There is a minimum space requirement, corresponding to the minimum + + * feasible buffer sizes; jmemmgr.c will request that much space even if + + * jpeg_mem_available returns zero. The maximum space needed, enough to hold + + * all working storage in memory, is also passed in case it is useful. + + * Finally, the total space already allocated is passed. If no better + + * method is available, cinfo->mem->max_memory_to_use - already_allocated + + * is often a suitable calculation. + + * + + * It is OK for jpeg_mem_available to underestimate the space available + + * (that'll just lead to more backing-store access than is really necessary). + + * However, an overestimate will lead to failure. Hence it's wise to subtract + + * a slop factor from the true available space. 5% should be enough. + + * + + * On machines with lots of virtual memory, any large constant may be returned. + + * Conversely, zero may be returned to always use the minimum amount of memory. + + */ + + + +EXTERN long jpeg_mem_available JPP((j_common_ptr cinfo, + + long min_bytes_needed, + + long max_bytes_needed, + + long already_allocated)); + + + + + +/* + + * This structure holds whatever state is needed to access a single + + * backing-store object. The read/write/close method pointers are called + + * by jmemmgr.c to manipulate the backing-store object; all other fields + + * are private to the system-dependent backing store routines. + + */ + + + +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ + + + +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ + + + +typedef unsigned short XMSH; /* type of extended-memory handles */ + +typedef unsigned short EMSH; /* type of expanded-memory handles */ + + + +typedef union { + + short file_handle; /* DOS file handle if it's a temp file */ + + XMSH xms_handle; /* handle if it's a chunk of XMS */ + + EMSH ems_handle; /* handle if it's a chunk of EMS */ + +} handle_union; + + + +#endif /* USE_MSDOS_MEMMGR */ + + + +typedef struct backing_store_struct * backing_store_ptr; + + + +typedef struct backing_store_struct { + + /* Methods for reading/writing/closing this backing-store object */ + + JMETHOD(void, read_backing_store, (j_common_ptr cinfo, + + backing_store_ptr info, + + void FAR * buffer_address, + + long file_offset, long byte_count)); + + JMETHOD(void, write_backing_store, (j_common_ptr cinfo, + + backing_store_ptr info, + + void FAR * buffer_address, + + long file_offset, long byte_count)); + + JMETHOD(void, close_backing_store, (j_common_ptr cinfo, + + backing_store_ptr info)); + + + + /* Private fields for system-dependent backing-store management */ + +#ifdef USE_MSDOS_MEMMGR + + /* For the MS-DOS manager (jmemdos.c), we need: */ + + handle_union handle; /* reference to backing-store storage object */ + + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ + +#else + + /* For a typical implementation with temp files, we need: */ + + FILE * temp_file; /* stdio reference to temp file */ + + char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ + +#endif + +} backing_store_info; + + + +/* + + * Initial opening of a backing-store object. This must fill in the + + * read/write/close pointers in the object. The read/write routines + + * may take an error exit if the specified maximum file size is exceeded. + + * (If jpeg_mem_available always returns a large value, this routine can + + * just take an error exit.) + + */ + + + +EXTERN void jpeg_open_backing_store JPP((j_common_ptr cinfo, + + backing_store_ptr info, + + long total_bytes_needed)); + + + + + +/* + + * These routines take care of any system-dependent initialization and + + * cleanup required. jpeg_mem_init will be called before anything is + + * allocated (and, therefore, nothing in cinfo is of use except the error + + * manager pointer). It should return a suitable default value for + + * max_memory_to_use; this may subsequently be overridden by the surrounding + + * application. (Note that max_memory_to_use is only important if + + * jpeg_mem_available chooses to consult it ... no one else will.) + + * jpeg_mem_term may assume that all requested memory has been freed and that + + * all opened backing-store objects have been closed. + + */ + + + +EXTERN long jpeg_mem_init JPP((j_common_ptr cinfo)); + +EXTERN void jpeg_mem_term JPP((j_common_ptr cinfo)); + diff --git a/tools/urt/libs/jpeg6/jmorecfg.h b/tools/urt/libs/jpeg6/jmorecfg.h new file mode 100644 index 00000000..afb6e26c --- /dev/null +++ b/tools/urt/libs/jpeg6/jmorecfg.h @@ -0,0 +1,693 @@ +/* + + * jmorecfg.h + + * + + * Copyright (C) 1991-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains additional configuration options that customize the + + * JPEG software for special applications or support machine-dependent + + * optimizations. Most users will not need to touch this file. + + */ + + + + + +/* + + * Define BITS_IN_JSAMPLE as either + + * 8 for 8-bit sample values (the usual setting) + + * 12 for 12-bit sample values + + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + + * JPEG standard, and the IJG code does not support anything else! + + * We do not support run-time selection of data precision, sorry. + + */ + + + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + + + + + +/* + + * Maximum number of components (color channels) allowed in JPEG image. + + * To meet the letter of the JPEG spec, set this to 255. However, darn + + * few applications need more than 4 channels (maybe 5 for CMYK + alpha + + * mask). We recommend 10 as a reasonable compromise; use 4 if you are + + * really short on memory. (Each allowed component costs a hundred or so + + * bytes of storage, whether actually used in an image or not.) + + */ + + + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + + + + +/* + + * Basic data types. + + * You may need to change these if you have a machine with unusual data + + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + + * but it had better be at least 16. + + */ + + + +/* Representation of a single sample (pixel element value). + + * We frequently allocate large arrays of these, so it's important to keep + + * them small. But if you have memory to burn and access to char or short + + * arrays is very slow on your hardware, you might want to change these. + + */ + + + +#if BITS_IN_JSAMPLE == 8 + +/* JSAMPLE should be the smallest type that will hold the values 0..255. + + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + + */ + + + +#ifdef HAVE_UNSIGNED_CHAR + + + +typedef unsigned char JSAMPLE; + +#define GETJSAMPLE(value) ((int) (value)) + + + +#else /* not HAVE_UNSIGNED_CHAR */ + + + +typedef char JSAMPLE; + +#ifdef CHAR_IS_UNSIGNED + +#define GETJSAMPLE(value) ((int) (value)) + +#else + +#define GETJSAMPLE(value) ((int) (value) & 0xFF) + +#endif /* CHAR_IS_UNSIGNED */ + + + +#endif /* HAVE_UNSIGNED_CHAR */ + + + +#define MAXJSAMPLE 255 + +#define CENTERJSAMPLE 128 + + + +#endif /* BITS_IN_JSAMPLE == 8 */ + + + + + +#if BITS_IN_JSAMPLE == 12 + +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + + * On nearly all machines "short" will do nicely. + + */ + + + +typedef short JSAMPLE; + +#define GETJSAMPLE(value) ((int) (value)) + + + +#define MAXJSAMPLE 4095 + +#define CENTERJSAMPLE 2048 + + + +#endif /* BITS_IN_JSAMPLE == 12 */ + + + + + +/* Representation of a DCT frequency coefficient. + + * This should be a signed value of at least 16 bits; "short" is usually OK. + + * Again, we allocate large arrays of these, but you can change to int + + * if you have memory to burn and "short" is really slow. + + */ + + + +typedef short JCOEF; + + + + + +/* Compressed datastreams are represented as arrays of JOCTET. + + * These must be EXACTLY 8 bits wide, at least once they are written to + + * external storage. Note that when using the stdio data source/destination + + * managers, this is also the data type passed to fread/fwrite. + + */ + + + +#ifdef HAVE_UNSIGNED_CHAR + + + +typedef unsigned char JOCTET; + +#define GETJOCTET(value) (value) + + + +#else /* not HAVE_UNSIGNED_CHAR */ + + + +typedef char JOCTET; + +#ifdef CHAR_IS_UNSIGNED + +#define GETJOCTET(value) (value) + +#else + +#define GETJOCTET(value) ((value) & 0xFF) + +#endif /* CHAR_IS_UNSIGNED */ + + + +#endif /* HAVE_UNSIGNED_CHAR */ + + + + + +/* These typedefs are used for various table entries and so forth. + + * They must be at least as wide as specified; but making them too big + + * won't cost a huge amount of memory, so we don't provide special + + * extraction code like we did for JSAMPLE. (In other words, these + + * typedefs live at a different point on the speed/space tradeoff curve.) + + */ + + + +/* UINT8 must hold at least the values 0..255. */ + + + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char UINT8; + +#else /* not HAVE_UNSIGNED_CHAR */ + +#ifdef CHAR_IS_UNSIGNED + +typedef char UINT8; + +#else /* not CHAR_IS_UNSIGNED */ + +typedef short UINT8; + +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + + +/* UINT16 must hold at least the values 0..65535. */ + + + +#ifdef HAVE_UNSIGNED_SHORT + +typedef unsigned short UINT16; + +#else /* not HAVE_UNSIGNED_SHORT */ + +typedef unsigned int UINT16; + +#endif /* HAVE_UNSIGNED_SHORT */ + + + +/* INT16 must hold at least the values -32768..32767. */ + + + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ + +typedef short INT16; + +#endif + + + +/* INT32 must hold at least signed 32-bit values. */ + + + +//#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ + +//typedef long INT32; + +//#endif + + + +/* Datatype used for image dimensions. The JPEG standard only supports + + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + + * "unsigned int" is sufficient on all machines. However, if you need to + + * handle larger images and you don't mind deviating from the spec, you + + * can change this datatype. + + */ + + + +typedef unsigned int JDIMENSION; + + + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + + + + +/* These defines are used in all function definitions and extern declarations. + + * You could modify them if you need to change function linkage conventions. + + * Another application is to make all functions global for use with debuggers + + * or code profilers that require it. + + */ + + + +#define METHODDEF static /* a function called through method pointers */ + +#define LOCAL static /* a function used only in its module */ + +#define GLOBAL /* a function referenced thru EXTERNs */ + +#define EXTERN extern /* a reference to a GLOBAL function */ + + + + + +/* Here is the pseudo-keyword for declaring pointers that must be "far" + + * on 80x86 machines. Most of the specialized coding for 80x86 is handled + + * by just saying "FAR *" where such a pointer is needed. In a few places + + * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol. + + */ + + + +#ifdef NEED_FAR_POINTERS + +#undef FAR + +#define FAR far + +#else + +#undef FAR + +#define FAR + +#endif + + + + + +/* + + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + + * in standard header files. Or you may have conflicts with application- + + * specific header files that you want to include together with these files. + + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + + */ + + + +//#ifndef HAVE_BOOLEAN + +//typedef int boolean; + +//#endif + +#ifndef FALSE /* in case these macros already exist */ + +#define FALSE 0 /* values of boolean */ + +#endif + +#ifndef TRUE + +#define TRUE 1 + +#endif + + + + + +/* + + * The remaining options affect code selection within the JPEG library, + + * but they don't need to be visible to most applications using the library. + + * To minimize application namespace pollution, the symbols won't be + + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + + */ + + + +#ifdef JPEG_INTERNALS + +#define JPEG_INTERNAL_OPTIONS + +#endif + + + +#ifdef JPEG_INTERNAL_OPTIONS + + + + + +/* + + * These defines indicate whether to include various optional functions. + + * Undefining some of these symbols will produce a smaller but less capable + + * library. Note that you can leave certain source files out of the + + * compilation/linking process if you've #undef'd the corresponding symbols. + + * (You may HAVE to do that if your compiler doesn't like null source files.) + + */ + + + +/* Arithmetic coding is unsupported for legal reasons. Complaints to IBM. */ + + + +/* Capability options common to encoder and decoder: */ + + + +#undef DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ + +#undef DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ + +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + + + +/* Encoder capability options: */ + + + +#undef C_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ + +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ + +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ + +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ + +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + + * precision, so jchuff.c normally uses entropy optimization to compute + + * usable tables for higher precision. If you don't want to do optimization, + + * you'll have to supply different default Huffman tables. + + * The exact same statements apply for progressive JPEG: the default tables + + * don't work for progressive mode. (This may get fixed, however.) + + */ + +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + + + +/* Decoder capability options: */ + + + +#undef D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ + +#undef D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ + +#undef D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ + +#undef BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ + +#undef IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ + +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ + +#undef UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ + +#undef QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ + +#undef QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + + + +/* more capability options later, no doubt */ + + + + + +/* + + * Ordering of RGB data in scanlines passed to or from the application. + + * If your application wants to deal with data in the order B,G,R, just + + * change these macros. You can also deal with formats such as R,G,B,X + + * (one extra byte per pixel) by changing RGB_PIXELSIZE. Note that changing + + * the offsets will also change the order in which colormap data is organized. + + * RESTRICTIONS: + + * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats. + + * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not + + * useful if you are using JPEG color spaces other than YCbCr or grayscale. + + * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE + + * is not 3 (they don't understand about dummy color components!). So you + + * can't use color quantization if you change that value. + + */ + + + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ + +#define RGB_GREEN 1 /* Offset of Green */ + +#define RGB_BLUE 2 /* Offset of Blue */ + +// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=900 +// ydnar: setting this fucks jpeg loading in q3map2, disabling "fix" (3) +#define RGB_PIXELSIZE 4 /* JSAMPLEs per RGB scanline element */ + + + + + +/* Definitions for speed-related optimizations. */ + + + + + +/* If your compiler supports inline functions, define INLINE + + * as the inline keyword; otherwise define it as empty. + + */ + + + +#ifndef INLINE + +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ + +#define INLINE __inline__ + +#endif + +#ifndef INLINE + +#define INLINE /* default is to define it as empty */ + +#endif + +#endif + + + + + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + + */ + + + +#ifndef MULTIPLIER + +#define MULTIPLIER int /* type for fastest integer multiply */ + +#endif + + + + + +/* FAST_FLOAT should be either float or double, whichever is done faster + + * by your compiler. (Note that this type is only used in the floating point + + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + + * Typically, float is faster in ANSI C compilers, while double is faster in + + * pre-ANSI compilers (because they insist on converting to double anyway). + + * The code below therefore chooses float if we have ANSI-style prototypes. + + */ + + + +#ifndef FAST_FLOAT + +#ifdef HAVE_PROTOTYPES + +#define FAST_FLOAT float + +#else + +#define FAST_FLOAT double + +#endif + +#endif + + + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/tools/urt/libs/jpeg6/jpeg6.dsp b/tools/urt/libs/jpeg6/jpeg6.dsp new file mode 100644 index 00000000..99b49748 --- /dev/null +++ b/tools/urt/libs/jpeg6/jpeg6.dsp @@ -0,0 +1,218 @@ +# Microsoft Developer Studio Project File - Name="jpeg6" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=jpeg6 - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "jpeg6.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "jpeg6.mak" CFG="jpeg6 - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "jpeg6 - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "jpeg6 - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "jpeg6" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "jpeg6 - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /O2 /I "../" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "jpeg6 - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE CPP /nologo /W3 /GX /Z7 /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MDd /W3 /Z7 /Od /I "../" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 +# ADD RSC /l 0x409 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "jpeg6 - Win32 Release" +# Name "jpeg6 - Win32 Debug" +# Begin Source File + +SOURCE=.\Jchuff.h +# End Source File +# Begin Source File + +SOURCE=.\JCOMAPI.cpp +# End Source File +# Begin Source File + +SOURCE=.\Jconfig.h +# End Source File +# Begin Source File + +SOURCE=.\JDAPIMIN.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDAPISTD.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDATASRC.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDCOEFCT.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDCOLOR.cpp +# End Source File +# Begin Source File + +SOURCE=.\Jdct.h +# End Source File +# Begin Source File + +SOURCE=.\JDDCTMGR.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDHUFF.cpp +# End Source File +# Begin Source File + +SOURCE=.\Jdhuff.h +# End Source File +# Begin Source File + +SOURCE=.\JDINPUT.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDMAINCT.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDMARKER.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDMASTER.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDPOSTCT.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDSAMPLE.cpp +# End Source File +# Begin Source File + +SOURCE=.\JDTRANS.cpp +# End Source File +# Begin Source File + +SOURCE=.\JERROR.cpp +# End Source File +# Begin Source File + +SOURCE=.\Jerror.h +# End Source File +# Begin Source File + +SOURCE=.\JFDCTFLT.cpp +# End Source File +# Begin Source File + +SOURCE=.\JIDCTFLT.cpp +# End Source File +# Begin Source File + +SOURCE=.\Jinclude.h +# End Source File +# Begin Source File + +SOURCE=.\JMEMMGR.cpp +# End Source File +# Begin Source File + +SOURCE=.\JMEMNOBS.cpp +# End Source File +# Begin Source File + +SOURCE=.\Jmemsys.h +# End Source File +# Begin Source File + +SOURCE=.\Jmorecfg.h +# End Source File +# Begin Source File + +SOURCE=.\Jpegint.h +# End Source File +# Begin Source File + +SOURCE=.\JPGLOAD.cpp +# End Source File +# Begin Source File + +SOURCE=.\JUTILS.cpp +# End Source File +# Begin Source File + +SOURCE=.\Jversion.h +# End Source File +# End Target +# End Project diff --git a/tools/urt/libs/jpeg6/jpeg6.vcproj b/tools/urt/libs/jpeg6/jpeg6.vcproj new file mode 100644 index 00000000..a0d973f7 --- /dev/null +++ b/tools/urt/libs/jpeg6/jpeg6.vcproj @@ -0,0 +1,692 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/urt/libs/jpeg6/jpegint.h b/tools/urt/libs/jpeg6/jpegint.h new file mode 100644 index 00000000..9b02525d --- /dev/null +++ b/tools/urt/libs/jpeg6/jpegint.h @@ -0,0 +1,776 @@ +/* + + * jpegint.h + + * + + * Copyright (C) 1991-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file provides common declarations for the various JPEG modules. + + * These declarations are considered internal to the JPEG library; most + + * applications using the library shouldn't need to include this file. + + */ + + + + + +/* Declarations for both compression & decompression */ + + + +typedef enum { /* Operating modes for buffer controllers */ + + JBUF_PASS_THRU, /* Plain stripwise operation */ + + /* Remaining modes require a full-image buffer to have been created */ + + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ + +} J_BUF_MODE; + + + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ + +#define CSTATE_START 100 /* after create_compress */ + +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ + +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ + +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ + +#define DSTATE_START 200 /* after create_decompress */ + +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ + +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ + +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ + +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ + +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ + +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ + +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ + +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ + +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ + +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + + + + +/* Declarations for compression modules */ + + + +/* Master control module */ + +struct jpeg_comp_master { + + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + + + /* State variables made visible to other modules */ + + boolean call_pass_startup; /* True if pass_startup must be called */ + + boolean is_last_pass; /* True during last pass */ + +}; + + + +/* Main buffer control (downsampled-data buffer) */ + +struct jpeg_c_main_controller { + + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + + JMETHOD(void, process_data, (j_compress_ptr cinfo, + + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + + JDIMENSION in_rows_avail)); + +}; + + + +/* Compression preprocessing (downsampling input buffer control) */ + +struct jpeg_c_prep_controller { + + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + + JSAMPARRAY input_buf, + + JDIMENSION *in_row_ctr, + + JDIMENSION in_rows_avail, + + JSAMPIMAGE output_buf, + + JDIMENSION *out_row_group_ctr, + + JDIMENSION out_row_groups_avail)); + +}; + + + +/* Coefficient buffer control */ + +struct jpeg_c_coef_controller { + + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + + JSAMPIMAGE input_buf)); + +}; + + + +/* Colorspace conversion */ + +struct jpeg_color_converter { + + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + + JDIMENSION output_row, int num_rows)); + +}; + + + +/* Downsampling */ + +struct jpeg_downsampler { + + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + + JMETHOD(void, downsample, (j_compress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + + JSAMPIMAGE output_buf, + + JDIMENSION out_row_group_index)); + + + + boolean need_context_rows; /* TRUE if need rows above & below */ + +}; + + + +/* Forward DCT (also controls coefficient quantization) */ + +struct jpeg_forward_dct { + + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + + /* perhaps this should be an array??? */ + + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + + jpeg_component_info * compptr, + + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + + JDIMENSION start_row, JDIMENSION start_col, + + JDIMENSION num_blocks)); + +}; + + + +/* Entropy encoding */ + +struct jpeg_entropy_encoder { + + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + +}; + + + +/* Marker writing */ + +struct jpeg_marker_writer { + + /* write_any_marker is exported for use by applications */ + + /* Probably only COM and APPn markers should be written */ + + JMETHOD(void, write_any_marker, (j_compress_ptr cinfo, int marker, + + const JOCTET *dataptr, unsigned int datalen)); + + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); + +}; + + + + + +/* Declarations for decompression modules */ + + + +/* Master control module */ + +struct jpeg_decomp_master { + + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + + + /* State variables made visible to other modules */ + + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ + +}; + + + +/* Input control module */ + +struct jpeg_input_controller { + + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + + + /* State variables made visible to other modules */ + + boolean has_multiple_scans; /* True if file has multiple scans */ + + boolean eoi_reached; /* True when EOI has been consumed */ + +}; + + + +/* Main buffer control (downsampled-data buffer) */ + +struct jpeg_d_main_controller { + + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail)); + +}; + + + +/* Coefficient buffer control */ + +struct jpeg_d_coef_controller { + + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + + JSAMPIMAGE output_buf)); + + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + + jvirt_barray_ptr *coef_arrays; + +}; + + + +/* Decompression postprocessing (color quantization buffer control) */ + +struct jpeg_d_post_controller { + + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, + + JDIMENSION *in_row_group_ctr, + + JDIMENSION in_row_groups_avail, + + JSAMPARRAY output_buf, + + JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail)); + +}; + + + +/* Marker reading & parsing */ + +struct jpeg_marker_reader { + + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + + /* Read markers until SOS or EOI. + + * Returns same codes as are defined for jpeg_consume_input: + + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + + */ + + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + + /* Read a restart marker --- exported for use by entropy decoder only */ + + jpeg_marker_parser_method read_restart_marker; + + /* Application-overridable marker processing methods */ + + jpeg_marker_parser_method process_COM; + + jpeg_marker_parser_method process_APPn[16]; + + + + /* State of marker reader --- nominally internal, but applications + + * supplying COM or APPn handlers might like to know the state. + + */ + + boolean saw_SOI; /* found SOI? */ + + boolean saw_SOF; /* found SOF? */ + + int next_restart_num; /* next restart number expected (0-7) */ + + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ + +}; + + + +/* Entropy decoding */ + +struct jpeg_entropy_decoder { + + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + + JBLOCKROW *MCU_data)); + +}; + + + +/* Inverse DCT (also performs dequantization) */ + +typedef JMETHOD(void, inverse_DCT_method_ptr, + + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + + JCOEFPTR coef_block, + + JSAMPARRAY output_buf, JDIMENSION output_col)); + + + +struct jpeg_inverse_dct { + + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + + /* It is useful to allow each component to have a separate IDCT method. */ + + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; + +}; + + + +/* Upsampling (note that upsampler must also call color converter) */ + +struct jpeg_upsampler { + + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, + + JDIMENSION *in_row_group_ctr, + + JDIMENSION in_row_groups_avail, + + JSAMPARRAY output_buf, + + JDIMENSION *out_row_ctr, + + JDIMENSION out_rows_avail)); + + + + boolean need_context_rows; /* TRUE if need rows above & below */ + +}; + + + +/* Colorspace conversion */ + +struct jpeg_color_deconverter { + + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + + JSAMPIMAGE input_buf, JDIMENSION input_row, + + JSAMPARRAY output_buf, int num_rows)); + +}; + + + +/* Color quantization or color precision reduction */ + +struct jpeg_color_quantizer { + + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + + int num_rows)); + + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); + +}; + + + + + +/* Miscellaneous useful macros */ + + + +#undef MAX + +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +#undef MIN + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + + + + +/* We assume that right shift corresponds to signed division by 2 with + + * rounding towards minus infinity. This is correct for typical "arithmetic + + * shift" instructions that shift in copies of the sign bit. But some + + * C compilers implement >> with an unsigned shift. For these machines you + + * must define RIGHT_SHIFT_IS_UNSIGNED. + + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + + * It is only applied with constant shift counts. SHIFT_TEMPS must be + + * included in the variables of any routine using RIGHT_SHIFT. + + */ + + + +#ifdef RIGHT_SHIFT_IS_UNSIGNED + +#define SHIFT_TEMPS INT32 shift_temp; + +#define RIGHT_SHIFT(x,shft) \ + + ((shift_temp = (x)) < 0 ? \ + + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + + (shift_temp >> (shft))) + +#else + +#define SHIFT_TEMPS + +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) + +#endif + + + + + +/* Short forms of external names for systems with brain-damaged linkers. */ + + + +#ifdef NEED_SHORT_EXTERNAL_NAMES + +#define jinit_compress_master jICompress + +#define jinit_c_master_control jICMaster + +#define jinit_c_main_controller jICMainC + +#define jinit_c_prep_controller jICPrepC + +#define jinit_c_coef_controller jICCoefC + +#define jinit_color_converter jICColor + +#define jinit_downsampler jIDownsampler + +#define jinit_forward_dct jIFDCT + +#define jinit_huff_encoder jIHEncoder + +#define jinit_phuff_encoder jIPHEncoder + +#define jinit_marker_writer jIMWriter + +#define jinit_master_decompress jIDMaster + +#define jinit_d_main_controller jIDMainC + +#define jinit_d_coef_controller jIDCoefC + +#define jinit_d_post_controller jIDPostC + +#define jinit_input_controller jIInCtlr + +#define jinit_marker_reader jIMReader + +#define jinit_huff_decoder jIHDecoder + +#define jinit_phuff_decoder jIPHDecoder + +#define jinit_inverse_dct jIIDCT + +#define jinit_upsampler jIUpsampler + +#define jinit_color_deconverter jIDColor + +#define jinit_1pass_quantizer jI1Quant + +#define jinit_2pass_quantizer jI2Quant + +#define jinit_merged_upsampler jIMUpsampler + +#define jinit_memory_mgr jIMemMgr + +#define jdiv_round_up jDivRound + +#define jround_up jRound + +#define jcopy_sample_rows jCopySamples + +#define jcopy_block_row jCopyBlocks + +#define jzero_far jZeroFar + +#define jpeg_zigzag_order jZIGTable + +#define jpeg_natural_order jZAGTable + +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + + + + +/* Compression module initialization routines */ + +EXTERN void jinit_compress_master JPP((j_compress_ptr cinfo)); + +EXTERN void jinit_c_master_control JPP((j_compress_ptr cinfo, + + boolean transcode_only)); + +EXTERN void jinit_c_main_controller JPP((j_compress_ptr cinfo, + + boolean need_full_buffer)); + +EXTERN void jinit_c_prep_controller JPP((j_compress_ptr cinfo, + + boolean need_full_buffer)); + +EXTERN void jinit_c_coef_controller JPP((j_compress_ptr cinfo, + + boolean need_full_buffer)); + +EXTERN void jinit_color_converter JPP((j_compress_ptr cinfo)); + +EXTERN void jinit_downsampler JPP((j_compress_ptr cinfo)); + +EXTERN void jinit_forward_dct JPP((j_compress_ptr cinfo)); + +EXTERN void jinit_huff_encoder JPP((j_compress_ptr cinfo)); + +EXTERN void jinit_phuff_encoder JPP((j_compress_ptr cinfo)); + +EXTERN void jinit_marker_writer JPP((j_compress_ptr cinfo)); + +/* Decompression module initialization routines */ + +EXTERN void jinit_master_decompress JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_d_main_controller JPP((j_decompress_ptr cinfo, + + boolean need_full_buffer)); + +EXTERN void jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + + boolean need_full_buffer)); + +EXTERN void jinit_d_post_controller JPP((j_decompress_ptr cinfo, + + boolean need_full_buffer)); + +EXTERN void jinit_input_controller JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_marker_reader JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_huff_decoder JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_inverse_dct JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_upsampler JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_color_deconverter JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); + +EXTERN void jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); + +/* Memory manager initialization */ + +EXTERN void jinit_memory_mgr JPP((j_common_ptr cinfo)); + + + +/* Utility routines in jutils.c */ + +EXTERN long jdiv_round_up JPP((long a, long b)); + +EXTERN long jround_up JPP((long a, long b)); + +EXTERN void jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + + JSAMPARRAY output_array, int dest_row, + + int num_rows, JDIMENSION num_cols)); + +EXTERN void jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + + JDIMENSION num_blocks)); + +EXTERN void jzero_far JPP((void FAR * target, size_t bytestozero)); + +/* Constant tables in jutils.c */ + +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ + +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + + + +/* Suppress undefined-structure complaints if necessary. */ + + + +#ifdef INCOMPLETE_TYPES_BROKEN + +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ + +struct jvirt_sarray_control { long dummy; }; + +struct jvirt_barray_control { long dummy; }; + +#endif + +#endif /* INCOMPLETE_TYPES_BROKEN */ + diff --git a/tools/urt/libs/jpeg6/jpgload.cpp b/tools/urt/libs/jpeg6/jpgload.cpp new file mode 100644 index 00000000..686799fa --- /dev/null +++ b/tools/urt/libs/jpeg6/jpgload.cpp @@ -0,0 +1,166 @@ + + +#include "radiant_jpeglib.h" +#include "jerror.h" +#include + +GLOBAL int LoadJPGBuff(unsigned char *fbuffer, int bufsize, unsigned char **pic, int *width, int *height ) +{ + + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + unsigned char *out, *bbuf; + int nSize; + int jmpret; + + // Rad additions: initialize the longjmp buffer + jmpret = setjmp( rad_loadfailed ); + if (jmpret != 0) + { + *pic = (unsigned char *)rad_errormsg; + return -1; + } + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src(&cinfo, fbuffer, bufsize); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, TRUE); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* ydnar: radiant only handles RGB, non-progressive format jpegs */ + if( cinfo.output_components != 4 ) + { + *pic = const_cast(reinterpret_cast("Non-RGB JPEG encountered (unsupported)")); + return -1; + } + if( cinfo.progressive_mode ) + { + *pic = const_cast(reinterpret_cast("Progressive JPEG encountered (unsupported)")); + return -1; + } + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + nSize = cinfo.output_width*cinfo.output_height*cinfo.output_components; + + out = reinterpret_cast( malloc( nSize+ 1 ) ); + memset( out, 255, nSize + 1 ); + + *pic = out; + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) + { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + bbuf = out + row_stride * cinfo.output_scanline; + buffer = &bbuf; + (void) jpeg_read_scanlines( &cinfo, buffer, 1 ); + } + + // clear all the alphas to 255 + { + int i, j; + unsigned char *buf; + + buf = *pic; + + j = cinfo.output_width * cinfo.output_height * 4; + for ( i = 3 ; i < j ; i+=4 ) { + buf[i] = 255; + } + } + + /* Step 7: Finish decompression */ + + (void) jpeg_finish_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress(&cinfo); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + //free (fbuffer); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ + return 0; +} diff --git a/tools/urt/libs/jpeg6/jutils.cpp b/tools/urt/libs/jpeg6/jutils.cpp new file mode 100644 index 00000000..f5454a6e --- /dev/null +++ b/tools/urt/libs/jpeg6/jutils.cpp @@ -0,0 +1,350 @@ +/* + + * jutils.c + + * + + * Copyright (C) 1991-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains tables and miscellaneous utility routines needed + + * for both compression and decompression. + + * Note we prefix all global names with "j" to minimize conflicts with + + * a surrounding application. + + */ + + + +#define JPEG_INTERNALS + +#include "jinclude.h" + +#include "radiant_jpeglib.h" + + + + + +/* + + * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element + + * of a DCT block read in natural order (left to right, top to bottom). + + */ + + + +const int jpeg_zigzag_order[DCTSIZE2] = { + + 0, 1, 5, 6, 14, 15, 27, 28, + + 2, 4, 7, 13, 16, 26, 29, 42, + + 3, 8, 12, 17, 25, 30, 41, 43, + + 9, 11, 18, 24, 31, 40, 44, 53, + + 10, 19, 23, 32, 39, 45, 52, 54, + + 20, 22, 33, 38, 46, 51, 55, 60, + + 21, 34, 37, 47, 50, 56, 59, 61, + + 35, 36, 48, 49, 57, 58, 62, 63 + +}; + + + +/* + + * jpeg_natural_order[i] is the natural-order position of the i'th element + + * of zigzag order. + + * + + * When reading corrupted data, the Huffman decoders could attempt + + * to reference an entry beyond the end of this array (if the decoded + + * zero run length reaches past the end of the block). To prevent + + * wild stores without adding an inner-loop test, we put some extra + + * "63"s after the real entries. This will cause the extra coefficient + + * to be stored in location 63 of the block, not somewhere random. + + * The worst case would be a run-length of 15, which means we need 16 + + * fake entries. + + */ + + + +const int jpeg_natural_order[DCTSIZE2+16] = { + + 0, 1, 8, 16, 9, 2, 3, 10, + + 17, 24, 32, 25, 18, 11, 4, 5, + + 12, 19, 26, 33, 40, 48, 41, 34, + + 27, 20, 13, 6, 7, 14, 21, 28, + + 35, 42, 49, 56, 57, 50, 43, 36, + + 29, 22, 15, 23, 30, 37, 44, 51, + + 58, 59, 52, 45, 38, 31, 39, 46, + + 53, 60, 61, 54, 47, 55, 62, 63, + + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + + 63, 63, 63, 63, 63, 63, 63, 63 + +}; + + + + + +/* + + * Arithmetic utilities + + */ + + + +GLOBAL long + +jdiv_round_up (long a, long b) + +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ + +/* Assumes a >= 0, b > 0 */ + +{ + + return (a + b - 1L) / b; + +} + + + + + +GLOBAL long + +jround_up (long a, long b) + +/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */ + +/* Assumes a >= 0, b > 0 */ + +{ + + a += b - 1L; + + return a - (a % b); + +} + + + + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + + * and coefficient-block arrays. This won't work on 80x86 because the arrays + + * are FAR and we're assuming a small-pointer memory model. However, some + + * DOS compilers provide far-pointer versions of memcpy() and memset() even + + * in the small-model libraries. These will be used if USE_FMEM is defined. + + * Otherwise, the routines below do it the hard way. (The performance cost + + * is not all that great, because these routines aren't very heavily used.) + + */ + + + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ + +#define FMEMCOPY(dest,src,size) MEMCOPY(dest,src,size) + +#define FMEMZERO(target,size) MEMZERO(target,size) + +#else /* 80x86 case, define if we can */ + +#ifdef USE_FMEM + +#define FMEMCOPY(dest,src,size) _fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size)) + +#define FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) + +#endif + +#endif + + + + + +GLOBAL void + +jcopy_sample_rows (JSAMPARRAY input_array, int source_row, + + JSAMPARRAY output_array, int dest_row, + + int num_rows, JDIMENSION num_cols) + +/* Copy some rows of samples from one place to another. + + * num_rows rows are copied from input_array[source_row++] + + * to output_array[dest_row++]; these areas may overlap for duplication. + + * The source and destination arrays must be at least as wide as num_cols. + + */ + +{ + + register JSAMPROW inptr, outptr; + +#ifdef FMEMCOPY + + register size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE)); + +#else + + register JDIMENSION count; + +#endif + + register int row; + + + + input_array += source_row; + + output_array += dest_row; + + + + for (row = num_rows; row > 0; row--) { + + inptr = *input_array++; + + outptr = *output_array++; + +#ifdef FMEMCOPY + + FMEMCOPY(outptr, inptr, count); + +#else + + for (count = num_cols; count > 0; count--) + + *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ + +#endif + + } + +} + + + + + +GLOBAL void + +jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, + + JDIMENSION num_blocks) + +/* Copy a row of coefficient blocks from one place to another. */ + +{ + +#ifdef FMEMCOPY + + FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF))); + +#else + + register JCOEFPTR inptr, outptr; + + register long count; + + + + inptr = (JCOEFPTR) input_row; + + outptr = (JCOEFPTR) output_row; + + for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) { + + *outptr++ = *inptr++; + + } + +#endif + +} + + + + + +GLOBAL void + +jzero_far (void FAR * target, size_t bytestozero) + +/* Zero out a chunk of FAR memory. */ + +/* This might be sample-array data, block-array data, or alloc_large data. */ + +{ + +#ifdef FMEMZERO + + FMEMZERO(target, bytestozero); + +#else + + register char FAR * ptr = (char FAR *) target; + + register size_t count; + + + + for (count = bytestozero; count > 0; count--) { + + *ptr++ = 0; + + } + +#endif + +} + diff --git a/tools/urt/libs/jpeg6/jversion.h b/tools/urt/libs/jpeg6/jversion.h new file mode 100644 index 00000000..784a088c --- /dev/null +++ b/tools/urt/libs/jpeg6/jversion.h @@ -0,0 +1,28 @@ +/* + + * jversion.h + + * + + * Copyright (C) 1991-1995, Thomas G. Lane. + + * This file is part of the Independent JPEG Group's software. + + * For conditions of distribution and use, see the accompanying README file. + + * + + * This file contains software version identification. + + */ + + + + + +#define JVERSION "6 2-Aug-95" + + + +#define JCOPYRIGHT "Copyright (C) 1995, Thomas G. Lane" + diff --git a/tools/urt/libs/jpeglib.h b/tools/urt/libs/jpeglib.h new file mode 100644 index 00000000..fda176bb --- /dev/null +++ b/tools/urt/libs/jpeglib.h @@ -0,0 +1,1126 @@ +/* +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + * jpeglib.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +// LZ: linux stuff +#if defined (__linux__) || defined (__APPLE__) + +#include +#include + +#ifndef boolean +#ifdef __cplusplus +#define boolean bool +#else +typedef int boolean; +#endif +#endif + +#endif + +#ifdef __MACOS__ + +// JDC: stuff to make mac version compile +#define boolean qboolean +#define register +#define INT32 int + +#endif + +// rad additions +// 11.29.99 + +//#include "cmdlib.h" +#ifdef _WIN32 +#include "windows.h" +#include "stdio.h" +#endif + +#ifndef INT32 +#define INT32 int +#endif + +// TTimo: if LoadJPGBuff returns -1, *pic is the error message +extern int LoadJPGBuff(unsigned char *fbuffer, int bufsize, unsigned char **pic, int *width, int *height ); +// rad end + + +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "jpeg6/jconfig.h" /* widely used configuration options */ +#endif +#include "jpeg6/jmorecfg.h" /* seldom changed options */ + + +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ + +#define JPEG_LIB_VERSION 60 /* Version 6 */ + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* This macro is used to declare a "method", that is, a function pointer. + * We want to supply prototype parameters if the compiler can cope. + * Note that the arglist parameter must be parenthesized! + */ + +#ifdef HAVE_PROTOTYPES +#define JMETHOD(type,methodname,arglist) type (*methodname) arglist +#else +#define JMETHOD(type,methodname,arglist) type (*methodname) () +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + * On 80x86 machines, the image arrays are too big for near pointers, + * but the pointer arrays can fit in near memory. + */ + +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This field directly represents the contents of a JPEG DQT marker. + * Note: the values are always given in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is not currently used by the compressor. + */ + JQUANT_TBL * quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + + +/* Known color spaces. */ + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr * err; /* Error handler module */\ + struct jpeg_memory_mgr * mem; /* Memory manager module */\ + struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ + boolean is_decompressor; /* so common code can tell which is which */\ + int global_state /* for checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct * j_common_ptr; +typedef struct jpeg_compress_struct * j_compress_ptr; +typedef struct jpeg_decompress_struct * j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr * dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master * master; + struct jpeg_c_main_controller * main; + struct jpeg_c_prep_controller * prep; + struct jpeg_c_coef_controller * coef; + struct jpeg_marker_writer * marker; + struct jpeg_color_converter * cconvert; + struct jpeg_downsampler * downsample; + struct jpeg_forward_dct * fdct; + struct jpeg_entropy_encoder * entropy; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr * src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker: */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE * sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master * master; + struct jpeg_d_main_controller * main; + struct jpeg_d_coef_controller * coef; + struct jpeg_d_post_controller * post; + struct jpeg_input_controller * inputctl; + struct jpeg_marker_reader * marker; + struct jpeg_entropy_decoder * entropy; + struct jpeg_inverse_dct * idct; + struct jpeg_upsampler * upsample; + struct jpeg_color_deconverter * cconvert; + struct jpeg_color_quantizer * cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + JMETHOD(void, error_exit, (j_common_ptr cinfo)); + /* Conditionally emit a trace or warning message */ + JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level)); + /* Routine that actually outputs a trace or error message */ + JMETHOD(void, output_message, (j_common_ptr cinfo)); + /* Format a message string for the most recent JPEG error or message */ + JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer)); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo)); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const * jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const * addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + JMETHOD(void, progress_monitor, (j_common_ptr cinfo)); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + JMETHOD(void, init_destination, (j_compress_ptr cinfo)); + JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo)); + JMETHOD(void, term_destination, (j_compress_ptr cinfo)); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET * next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + JMETHOD(void, init_source, (j_decompress_ptr cinfo)); + JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); + JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes)); + JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired)); + JMETHOD(void, term_source, (j_decompress_ptr cinfo)); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control * jvirt_sarray_ptr; +typedef struct jvirt_barray_control * jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, + JDIMENSION numrows)); + JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, + JDIMENSION numrows)); + JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo)); + JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo, + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo, + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id)); + JMETHOD(void, self_destruct, (j_common_ptr cinfo)); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo)); + + +/* Declarations for routines called by application. + * The JPP macro hides prototype parameters from compilers that can't cope. + * Note JPP requires double parentheses. + */ + +#ifdef HAVE_PROTOTYPES +#define JPP(arglist) arglist +#else +#define JPP(arglist) () +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. + * We shorten external names to be unique in the first six letters, which + * is good enough for all known systems. + * (If your compiler itself needs names to be unique in less than 15 + * characters, you are out of luck. Get a better compiler.) + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_error jStdError +#define jpeg_create_compress jCreaCompress +#define jpeg_create_decompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Default error-management setup */ +EXTERN struct jpeg_error_mgr *jpeg_std_error JPP((struct jpeg_error_mgr *err)); + +/* Initialization and destruction of JPEG compression objects */ +/* NB: you must set up the error-manager BEFORE calling jpeg_create_xxx */ +EXTERN void jpeg_create_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_create_decompress JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_destroy_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_destroy_decompress JPP((j_decompress_ptr cinfo)); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN void jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); +EXTERN void jpeg_stdio_src JPP((j_decompress_ptr cinfo, unsigned char *infile, int bufsize)); + +/* Default parameter setup for compression */ +EXTERN void jpeg_set_defaults JPP((j_compress_ptr cinfo)); +/* Compression parameter setup aids */ +EXTERN void jpeg_set_colorspace JPP((j_compress_ptr cinfo, + J_COLOR_SPACE colorspace)); +EXTERN void jpeg_default_colorspace JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_set_quality JPP((j_compress_ptr cinfo, int quality, + boolean force_baseline)); +EXTERN void jpeg_set_linear_quality JPP((j_compress_ptr cinfo, + int scale_factor, + boolean force_baseline)); +EXTERN void jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline)); +EXTERN int jpeg_quality_scaling JPP((int quality)); +EXTERN void jpeg_simple_progression JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_suppress_tables JPP((j_compress_ptr cinfo, + boolean suppress)); +EXTERN JQUANT_TBL * jpeg_alloc_quant_table JPP((j_common_ptr cinfo)); +EXTERN JHUFF_TBL * jpeg_alloc_huff_table JPP((j_common_ptr cinfo)); + +/* Main entry points for compression */ +EXTERN void jpeg_start_compress JPP((j_compress_ptr cinfo, + boolean write_all_tables)); +EXTERN JDIMENSION jpeg_write_scanlines JPP((j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines)); +EXTERN void jpeg_finish_compress JPP((j_compress_ptr cinfo)); + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN JDIMENSION jpeg_write_raw_data JPP((j_compress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION num_lines)); + +/* Write a special marker. See libjpeg.doc concerning safe usage. */ +EXTERN void jpeg_write_marker JPP((j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen)); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN void jpeg_write_tables JPP((j_compress_ptr cinfo)); + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN int jpeg_read_header JPP((j_decompress_ptr cinfo, + boolean require_image)); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN boolean jpeg_start_decompress JPP((j_decompress_ptr cinfo)); +EXTERN JDIMENSION jpeg_read_scanlines JPP((j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines)); +EXTERN boolean jpeg_finish_decompress JPP((j_decompress_ptr cinfo)); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN JDIMENSION jpeg_read_raw_data JPP((j_decompress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION max_lines)); + +/* Additional entry points for buffered-image mode. */ +EXTERN boolean jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_start_output JPP((j_decompress_ptr cinfo, + int scan_number)); +EXTERN boolean jpeg_finish_output JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_input_complete JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_new_colormap JPP((j_decompress_ptr cinfo)); +EXTERN int jpeg_consume_input JPP((j_decompress_ptr cinfo)); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +EXTERN void jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo)); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN void jpeg_set_marker_processor JPP((j_decompress_ptr cinfo, + int marker_code, + jpeg_marker_parser_method routine)); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN jvirt_barray_ptr * jpeg_read_coefficients JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_write_coefficients JPP((j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays)); +EXTERN void jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo, + j_compress_ptr dstinfo)); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN void jpeg_abort_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_abort_decompress JPP((j_decompress_ptr cinfo)); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN void jpeg_abort JPP((j_common_ptr cinfo)); +EXTERN void jpeg_destroy JPP((j_common_ptr cinfo)); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN boolean jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, + int desired)); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "jpegint.h" /* fetch private declarations */ +#include "jerror.h" /* fetch error codes too */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* JPEGLIB_H */ diff --git a/tools/urt/libs/l_net/.cvsignore b/tools/urt/libs/l_net/.cvsignore new file mode 100644 index 00000000..877bc8c4 --- /dev/null +++ b/tools/urt/libs/l_net/.cvsignore @@ -0,0 +1,5 @@ +Debug +Release +*.plg +*.BAK +.consign diff --git a/tools/urt/libs/l_net/l_net.c b/tools/urt/libs/l_net/l_net.c new file mode 100644 index 00000000..6740f6e5 --- /dev/null +++ b/tools/urt/libs/l_net/l_net.c @@ -0,0 +1,630 @@ +/* +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//==================================================================== +// +// Name: l_net.c +// Function: - +// Programmer: MrElusive +// Last update: - +// Tab size: 3 +// Notes: +//==================================================================== + +#include +#include +#include +#include +#include "l_net.h" +#include "l_net_wins.h" + +#define GetMemory malloc +#define FreeMemory free + +#define qtrue 1 +#define qfalse 0 + +#ifdef _DEBUG +void WinPrint(char *str, ...) +{ + va_list argptr; + char text[4096]; + + va_start (argptr,str); + vsprintf (text, str, argptr); + va_end (argptr); + + printf(text); +} +#else +void WinPrint(char *str, ...) +{ +} +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_SetAddressPort(address_t *address, int port) +{ + sockaddr_t addr; + + WINS_StringToAddr(address->ip, &addr); + WINS_SetSocketPort(&addr, port); + strcpy(address->ip, WINS_AddrToString(&addr)); +} //end of the function Net_SetAddressPort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Net_AddressCompare(address_t *addr1, address_t *addr2) +{ +#ifdef WIN32 + return stricmp(addr1->ip, addr2->ip); +#endif +#ifdef __linux__ + return strcasecmp(addr1->ip, addr2->ip); +#endif +} //end of the function Net_AddressCompare +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_SocketToAddress(socket_t *sock, address_t *address) +{ + strcpy(address->ip, WINS_AddrToString(&sock->addr)); +} //end of the function Net_SocketToAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Net_Send(socket_t *sock, netmessage_t *msg) +{ + int size; + + size = msg->size; + msg->size = 0; + NMSG_WriteLong(msg, size-4); + msg->size = size; + //WinPrint("Net_Send: message of size %d\n", sendmsg.size); + return WINS_Write(sock->socket, msg->data, msg->size, NULL); +} //end of the function Net_SendSocketReliable +//=========================================================================== +// returns the number of bytes recieved +// -1 on error +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Net_Receive(socket_t *sock, netmessage_t *msg) +{ + int curread; + + if (sock->remaining > 0) + { + curread = WINS_Read(sock->socket, &sock->msg.data[sock->msg.size], sock->remaining, NULL); + if (curread == -1) + { + WinPrint("Net_Receive: read error\n"); + return -1; + } //end if + sock->remaining -= curread; + sock->msg.size += curread; + if (sock->remaining <= 0) + { + sock->remaining = 0; + memcpy(msg, &sock->msg, sizeof(netmessage_t)); + sock->msg.size = 0; + return msg->size - 4; + } //end if + return 0; + } //end if + sock->msg.size = WINS_Read(sock->socket, sock->msg.data, 4, NULL); + if (sock->msg.size == 0) return 0; + if (sock->msg.size == -1) + { + WinPrint("Net_Receive: size header read error\n"); + return -1; + } //end if + //WinPrint("Net_Receive: message size header %d\n", msg->size); + sock->msg.read = 0; + sock->remaining = NMSG_ReadLong(&sock->msg); + if (sock->remaining == 0) return 0; + if (sock->remaining < 0 || sock->remaining > MAX_NETMESSAGE) + { + WinPrint("Net_Receive: invalid message size %d\n", sock->remaining); + return -1; + } //end if + //try to read the message + curread = WINS_Read(sock->socket, &sock->msg.data[sock->msg.size], sock->remaining, NULL); + if (curread == -1) + { + WinPrint("Net_Receive: read error\n"); + return -1; + } //end if + sock->remaining -= curread; + sock->msg.size += curread; + if (sock->remaining <= 0) + { + sock->remaining = 0; + memcpy(msg, &sock->msg, sizeof(netmessage_t)); + sock->msg.size = 0; + return msg->size - 4; + } //end if + //the message has not been completely read yet +#ifdef _DEBUG + printf("++timo TODO: debug the Net_Receive on big size messages\n"); +#endif + return 0; +} //end of the function Net_Receive +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +socket_t *Net_AllocSocket(void) +{ + socket_t *sock; + + sock = (socket_t *) GetMemory(sizeof(socket_t)); + memset(sock, 0, sizeof(socket_t)); + return sock; +} //end of the function Net_AllocSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_FreeSocket(socket_t *sock) +{ + FreeMemory(sock); +} //end of the function Net_FreeSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +socket_t *Net_Connect(address_t *address, int port) +{ + int newsock; + socket_t *sock; + sockaddr_t sendaddr; + + // see if we can resolve the host name + WINS_StringToAddr(address->ip, &sendaddr); + + newsock = WINS_OpenReliableSocket(port); + if (newsock == -1) return NULL; + + sock = Net_AllocSocket(); + if (sock == NULL) + { + WINS_CloseSocket(newsock); + return NULL; + } //end if + sock->socket = newsock; + + //connect to the host + if (WINS_Connect(newsock, &sendaddr) == -1) + { + Net_FreeSocket(sock); + WINS_CloseSocket(newsock); + WinPrint("Net_Connect: error connecting\n"); + return NULL; + } //end if + + memcpy(&sock->addr, &sendaddr, sizeof(sockaddr_t)); + //now we can send messages + // + return sock; +} //end of the function Net_Connect + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +socket_t *Net_ListenSocket(int port) +{ + int newsock; + socket_t *sock; + + newsock = WINS_OpenReliableSocket(port); + if (newsock == -1) return NULL; + + if (WINS_Listen(newsock) == -1) + { + WINS_CloseSocket(newsock); + return NULL; + } //end if + sock = Net_AllocSocket(); + if (sock == NULL) + { + WINS_CloseSocket(newsock); + return NULL; + } //end if + sock->socket = newsock; + WINS_GetSocketAddr(newsock, &sock->addr); + WinPrint("listen socket opened at %s\n", WINS_AddrToString(&sock->addr)); + // + return sock; +} //end of the function Net_ListenSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +socket_t *Net_Accept(socket_t *sock) +{ + int newsocket; + sockaddr_t sendaddr; + socket_t *newsock; + + newsocket = WINS_Accept(sock->socket, &sendaddr); + if (newsocket == -1) return NULL; + + newsock = Net_AllocSocket(); + if (newsock == NULL) + { + WINS_CloseSocket(newsocket); + return NULL; + } //end if + newsock->socket = newsocket; + memcpy(&newsock->addr, &sendaddr, sizeof(sockaddr_t)); + // + return newsock; +} //end of the function Net_Accept +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_Disconnect(socket_t *sock) +{ + WINS_CloseSocket(sock->socket); + Net_FreeSocket(sock); +} //end of the function Net_Disconnect +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_StringToAddress(char *string, address_t *address) +{ + strcpy(address->ip, string); +} //end of the function Net_StringToAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_MyAddress(address_t *address) +{ + strcpy(address->ip, WINS_MyAddress()); +} //end of the function Net_MyAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Net_Setup(void) +{ + WINS_Init(); + // + WinPrint("my address is %s\n", WINS_MyAddress()); + // + return qtrue; +} //end of the function Net_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_Shutdown(void) +{ + WINS_Shutdown(); +} //end of the function Net_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_Clear(netmessage_t *msg) +{ + msg->size = 4; +} //end of the function NMSG_Clear +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteChar (netmessage_t *msg, int c) +{ + if (c < -128 || c > 127) + WinPrint("NMSG_WriteChar: range error\n"); + + if (msg->size >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteChar: overflow\n"); + return; + } //end if + msg->data[msg->size] = c; + msg->size++; +} //end of the function NMSG_WriteChar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteByte(netmessage_t *msg, int c) +{ + if (c < -128 || c > 127) + WinPrint("NMSG_WriteByte: range error\n"); + + if (msg->size + 1 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteByte: overflow\n"); + return; + } //end if + msg->data[msg->size] = c; + msg->size++; +} //end of the function NMSG_WriteByte +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteShort(netmessage_t *msg, int c) +{ + if (c < ((short)0x8000) || c > (short)0x7fff) + WinPrint("NMSG_WriteShort: range error"); + + if (msg->size + 2 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteShort: overflow\n"); + return; + } //end if + msg->data[msg->size] = c&0xff; + msg->data[msg->size+1] = c>>8; + msg->size += 2; +} //end of the function NMSG_WriteShort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteLong(netmessage_t *msg, int c) +{ + if (msg->size + 4 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteLong: overflow\n"); + return; + } //end if + msg->data[msg->size] = c&0xff; + msg->data[msg->size+1] = (c>>8)&0xff; + msg->data[msg->size+2] = (c>>16)&0xff; + msg->data[msg->size+3] = c>>24; + msg->size += 4; +} //end of the function NMSG_WriteLong +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteFloat(netmessage_t *msg, float c) +{ + if (msg->size + 4 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteLong: overflow\n"); + return; + } //end if + msg->data[msg->size] = *((int *)&c)&0xff; + msg->data[msg->size+1] = (*((int *)&c)>>8)&0xff; + msg->data[msg->size+2] = (*((int *)&c)>>16)&0xff; + msg->data[msg->size+3] = *((int *)&c)>>24; + msg->size += 4; +} //end of the function NMSG_WriteFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteString(netmessage_t *msg, char *string) +{ + if (msg->size + strlen(string) + 1 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteString: overflow\n"); + return; + } //end if + strcpy(&msg->data[msg->size], string); + msg->size += strlen(string) + 1; +} //end of the function NMSG_WriteString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_ReadStart(netmessage_t *msg) +{ + msg->readoverflow = qfalse; + msg->read = 4; +} //end of the function NMSG_ReadStart +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NMSG_ReadChar(netmessage_t *msg) +{ + if (msg->size + 1 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadChar: read overflow\n"); + return 0; + } //end if + msg->read++; + return msg->data[msg->read-1]; +} //end of the function NMSG_ReadChar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NMSG_ReadByte(netmessage_t *msg) +{ + if (msg->read + 1 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadByte: read overflow\n"); + return 0; + } //end if + msg->read++; + return msg->data[msg->read-1]; +} //end of the function NMSG_ReadByte +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NMSG_ReadShort(netmessage_t *msg) +{ + int c; + + if (msg->read + 2 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadShort: read overflow\n"); + return 0; + } //end if + c = (short)(msg->data[msg->read] + (msg->data[msg->read+1]<<8)); + msg->read += 2; + return c; +} //end of the function NMSG_ReadShort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NMSG_ReadLong(netmessage_t *msg) +{ + int c; + + if (msg->read + 4 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadLong: read overflow\n"); + return 0; + } //end if + c = msg->data[msg->read] + + (msg->data[msg->read+1]<<8) + + (msg->data[msg->read+2]<<16) + + (msg->data[msg->read+3]<<24); + msg->read += 4; + return c; +} //end of the function NMSG_ReadLong +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float NMSG_ReadFloat(netmessage_t *msg) +{ + int c; + + if (msg->read + 4 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadLong: read overflow\n"); + return 0; + } //end if + c = msg->data[msg->read] + + (msg->data[msg->read+1]<<8) + + (msg->data[msg->read+2]<<16) + + (msg->data[msg->read+3]<<24); + msg->read += 4; + return *(float *)&c; +} //end of the function NMSG_ReadFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *NMSG_ReadString(netmessage_t *msg) +{ + static char string[2048]; + int l, c; + + l = 0; + do + { + if (msg->read + 1 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadString: read overflow\n"); + string[l] = 0; + return string; + } //end if + c = msg->data[msg->read]; + msg->read++; + if (c == 0) break; + string[l] = c; + l++; + } while (l < sizeof(string)-1); + string[l] = 0; + return string; +} //end of the function NMSG_ReadString diff --git a/tools/urt/libs/l_net/l_net.dsp b/tools/urt/libs/l_net/l_net.dsp new file mode 100644 index 00000000..36a13189 --- /dev/null +++ b/tools/urt/libs/l_net/l_net.dsp @@ -0,0 +1,118 @@ +# Microsoft Developer Studio Project File - Name="l_net" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=l_net - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "l_net.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "l_net.mak" CFG="l_net - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "l_net - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "l_net - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "l_net" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "l_net - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE F90 /compile_only /include:"Release/" /nologo /warn:nofileopt +# ADD F90 /compile_only /include:"Release/" /nologo /warn:nofileopt +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "l_net - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE F90 /check:bounds /compile_only /debug:full /include:"Debug/" /nologo /warn:argument_checking /warn:nofileopt +# ADD F90 /check:bounds /compile_only /debug:full /include:"Debug/" /nologo /warn:argument_checking /warn:nofileopt +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "l_net - Win32 Release" +# Name "l_net - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;f90;for;f;fpp" +# Begin Source File + +SOURCE=.\l_net.c +# End Source File +# Begin Source File + +SOURCE=.\l_net_wins.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\l_net.h +# End Source File +# Begin Source File + +SOURCE=.\l_net_wins.h +# End Source File +# End Group +# End Target +# End Project diff --git a/tools/urt/libs/l_net/l_net.h b/tools/urt/libs/l_net/l_net.h new file mode 100644 index 00000000..2c0f9fff --- /dev/null +++ b/tools/urt/libs/l_net/l_net.h @@ -0,0 +1,128 @@ +/* +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//==================================================================== +// +// Name: l_net.h +// Function: - +// Programmer: MrElusive +// Last update: TTimo: cross-platform version, l_net library +// Tab size: 2 +// Notes: +//==================================================================== + +//++timo FIXME: the l_net code understands that as the max size for the netmessage_s structure +// we have defined unsigned char data[MAX_NETMESSAGE] in netmessage_s but actually it cannot be filled completely +// we need to introduce a new #define and adapt to data[MAX_NETBUFFER] +#define MAX_NETMESSAGE 1024 +#define MAX_NETADDRESS 32 + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __BYTEBOOL__ +#define __BYTEBOOL__ +typedef enum { qfalse, qtrue } qboolean; +typedef unsigned char byte; +#endif + +typedef struct address_s +{ + char ip[MAX_NETADDRESS]; +} address_t; + +typedef struct sockaddr_s +{ + short sa_family; + unsigned char sa_data[14]; +} sockaddr_t; + +typedef struct netmessage_s +{ + unsigned char data[MAX_NETMESSAGE]; + int size; + int read; + int readoverflow; +} netmessage_t; + +typedef struct socket_s +{ + int socket; //socket number + sockaddr_t addr; //socket address + netmessage_t msg; //current message being read + int remaining; //remaining bytes to read for the current message + struct socket_s *prev, *next; //prev and next socket in a list +} socket_t; + +//compare addresses +int Net_AddressCompare(address_t *addr1, address_t *addr2); +//gives the address of a socket +void Net_SocketToAddress(socket_t *sock, address_t *address); +//converts a string to an address +void Net_StringToAddress(char *string, address_t *address); +//set the address ip port +void Net_SetAddressPort(address_t *address, int port); +//send a message to the given socket +int Net_Send(socket_t *sock, netmessage_t *msg); +//recieve a message from the given socket +int Net_Receive(socket_t *sock, netmessage_t *msg); +//connect to a host +// NOTE: port is the localhost port, usually 0 +// ex: Net_Connect( "192.168.0.1:39000", 0 ) +socket_t *Net_Connect(address_t *address, int port); +//disconnect from a host +void Net_Disconnect(socket_t *sock); +//returns the local address +void Net_MyAddress(address_t *address); +//listen at the given port +socket_t *Net_ListenSocket(int port); +//accept new connections at the given socket +socket_t *Net_Accept(socket_t *sock); +//setup networking +int Net_Setup(void); +//shutdown networking +void Net_Shutdown(void); +//message handling +void NMSG_Clear(netmessage_t *msg); +void NMSG_WriteChar(netmessage_t *msg, int c); +void NMSG_WriteByte(netmessage_t *msg, int c); +void NMSG_WriteShort(netmessage_t *msg, int c); +void NMSG_WriteLong(netmessage_t *msg, int c); +void NMSG_WriteFloat(netmessage_t *msg, float c); +void NMSG_WriteString(netmessage_t *msg, char *string); +void NMSG_ReadStart(netmessage_t *msg); +int NMSG_ReadChar(netmessage_t *msg); +int NMSG_ReadByte(netmessage_t *msg); +int NMSG_ReadShort(netmessage_t *msg); +int NMSG_ReadLong(netmessage_t *msg); +float NMSG_ReadFloat(netmessage_t *msg); +char *NMSG_ReadString(netmessage_t *msg); + +//++timo FIXME: the WINS_ things are not necessary, they can be made portable arther easily +char *WINS_ErrorMessage(int error); + +#ifdef __cplusplus +} +#endif diff --git a/tools/urt/libs/l_net/l_net.vcproj b/tools/urt/libs/l_net/l_net.vcproj new file mode 100644 index 00000000..e9d1c473 --- /dev/null +++ b/tools/urt/libs/l_net/l_net.vcproj @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/urt/libs/l_net/l_net_berkley.c b/tools/urt/libs/l_net/l_net_berkley.c new file mode 100644 index 00000000..2c03373f --- /dev/null +++ b/tools/urt/libs/l_net/l_net_berkley.c @@ -0,0 +1,769 @@ +/* +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//=========================================================================== +// +// Name: l_net_wins.c +// Function: WinSock +// Programmer: MrElusive +// Last update: TTimo: cross-platform version, l_net library +// Tab Size: 2 +// Notes: +//=========================================================================== + +//#include +#include +#include +#include +#include "l_net.h" +#include "l_net_wins.h" + +#include +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 + +#define WinError WinPrint + +#define qtrue 1 +#define qfalse 0 + +#define ioctlsocket ioctl +#define closesocket close + +int WSAGetLastError() +{ + return errno; +} + +/* +typedef struct tag_error_struct +{ + int errnum; + LPSTR errstr; +} ERROR_STRUCT; +*/ + +typedef struct tag_error_struct +{ + int errnum; + const char *errstr; +} ERROR_STRUCT; + +#define NET_NAMELEN 64 + +static char my_tcpip_address[NET_NAMELEN]; + +#define DEFAULTnet_hostport 26000 + +#define MAXHOSTNAMELEN 256 + +static int net_acceptsocket = -1; // socket for fielding new connections +static int net_controlsocket; +static int net_hostport; // udp port number for acceptsocket +static int net_broadcastsocket = 0; +//static qboolean ifbcastinit = qfalse; +//static struct sockaddr_s broadcastaddr; +static struct sockaddr_s broadcastaddr; + +static unsigned long myAddr; + +ERROR_STRUCT errlist[] = { + {EACCES,"EACCES - The address is protected, user is not root"}, + {EAGAIN,"EAGAIN - Operation on non-blocking socket that cannot return immediatly"}, + {EBADF, "EBADF - sockfd is not a valid descriptor"}, + {EFAULT, "EFAULT - The parameter is not in a writable part of the user address space"}, + {EINVAL,"EINVAL - The socket is already bound to an address"}, + {ENOBUFS,"ENOBUFS - not enough memory"}, + {ENOMEM, "ENOMEM - not enough memory"}, + {ENOTCONN, "ENOTCONN - not connected"}, + {ENOTSOCK,"ENOTSOCK - Argument is file descriptor not a socket"}, + {EOPNOTSUPP,"ENOTSUPP - The referenced socket is not of type SOCK_STREAM"}, + {EPERM, "EPERM - Firewall rules forbid connection"}, + {-1, NULL} +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_ErrorMessage(int error) +{ + int search = 0; + + if (!error) return "No error occurred"; + + for (search = 0; errlist[search].errstr; search++) + { + if (error == errlist[search].errnum) + return (char *)errlist[search].errstr; + } //end for + + return "Unknown error"; +} //end of the function WINS_ErrorMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Init(void) +{ + int i; + struct hostent *local; + char buff[MAXHOSTNAMELEN]; + struct sockaddr_s addr; + char *p; + int r; +/* + linux doesn't have anything to initialize for the net + "Windows .. built for the internet .. the internet .. built with unix" + */ +#if 0 + WORD wVersionRequested; + + wVersionRequested = MAKEWORD(2, 2); + + r = WSAStartup (wVersionRequested, &winsockdata); + + if (r) + { + WinPrint("Winsock initialization failed.\n"); + return -1; + } +#endif + /* + i = COM_CheckParm ("-udpport"); + if (i == 0)*/ + net_hostport = DEFAULTnet_hostport; + /* + else if (i < com_argc-1) + net_hostport = Q_atoi (com_argv[i+1]); + else + Sys_Error ("WINS_Init: you must specify a number after -udpport"); + */ + + // determine my name & address + gethostname(buff, MAXHOSTNAMELEN); + local = gethostbyname(buff); + myAddr = *(int *)local->h_addr_list[0]; + + // if the quake hostname isn't set, set it to the machine name +// if (Q_strcmp(hostname.string, "UNNAMED") == 0) + { + // see if it's a text IP address (well, close enough) + for (p = buff; *p; p++) + if ((*p < '0' || *p > '9') && *p != '.') + break; + + // if it is a real name, strip off the domain; we only want the host + if (*p) + { + for (i = 0; i < 15; i++) + if (buff[i] == '.') + break; + buff[i] = 0; + } +// Cvar_Set ("hostname", buff); + } + + //++timo WTF is that net_controlsocket? it's sole purpose is to retrieve the local IP? + if ((net_controlsocket = WINS_OpenSocket (0)) == SOCKET_ERROR) + WinError("WINS_Init: Unable to open control socket\n"); + + ((struct sockaddr_in *)&broadcastaddr)->sin_family = AF_INET; + ((struct sockaddr_in *)&broadcastaddr)->sin_addr.s_addr = INADDR_BROADCAST; + ((struct sockaddr_in *)&broadcastaddr)->sin_port = htons((u_short)net_hostport); + + WINS_GetSocketAddr (net_controlsocket, &addr); + strcpy(my_tcpip_address, WINS_AddrToString (&addr)); + p = strrchr (my_tcpip_address, ':'); + if (p) *p = 0; + WinPrint("Winsock Initialized\n"); + + return net_controlsocket; +} //end of the function WINS_Init +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_MyAddress(void) +{ + return my_tcpip_address; +} //end of the function WINS_MyAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WINS_Shutdown(void) +{ + //WINS_Listen(0); + WINS_CloseSocket(net_controlsocket); +// WSACleanup(); + // + //WinPrint("Winsock Shutdown\n"); +} //end of the function WINS_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void WINS_Listen(int state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != -1) + return; + if ((net_acceptsocket = WINS_OpenSocket (net_hostport)) == -1) + WinError ("WINS_Listen: Unable to open accept socket\n"); + return; + } + + // disable listening + if (net_acceptsocket == -1) + return; + WINS_CloseSocket (net_acceptsocket); + net_acceptsocket = -1; +} //end of the function WINS_Listen*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_OpenSocket(int port) +{ + int newsocket; + struct sockaddr_in address; + u_long _true = 1; + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == SOCKET_ERROR) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons((u_short)port); + if( bind (newsocket, (void *)&address, sizeof(address)) == -1) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + return newsocket; +} //end of the function WINS_OpenSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_OpenReliableSocket(int port) +{ + int newsocket; + struct sockaddr_in address; + qboolean _true = 0xFFFFFFFF; + + //IPPROTO_TCP + // + if ((newsocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_ANY); + address.sin_port = htons((u_short)port); + if (bind(newsocket, (void *)&address, sizeof(address)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + // + if (setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (void *) &_true, sizeof(int)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + WinPrint("setsockopt error\n"); + } //end if + + return newsocket; +} //end of the function WINS_OpenReliableSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Listen(int socket) +{ + u_long _true = 1; + + if (ioctlsocket(socket, FIONBIO, &_true) == -1) + { + WinPrint("WINS_Listen: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + if (listen(socket, SOMAXCONN) == SOCKET_ERROR) + { + WinPrint("WINS_Listen: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + return 0; +} //end of the function WINS_Listen +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Accept(int socket, struct sockaddr_s *addr) +{ + int addrlen = sizeof (struct sockaddr_s); + int newsocket; + qboolean _true = 1; + + newsocket = accept(socket, (struct sockaddr *)addr, &addrlen); + if (newsocket == INVALID_SOCKET) + { + if (errno == EAGAIN) return -1; + WinPrint("WINS_Accept: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + // + if (setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (void *) &_true, sizeof(int)) == SOCKET_ERROR) + { + WinPrint("WINS_Accept: %s\n", WINS_ErrorMessage(WSAGetLastError())); + WinPrint("setsockopt error\n"); + } //end if + return newsocket; +} //end of the function WINS_Accept +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_CloseSocket(int socket) +{ + /* + if (socket == net_broadcastsocket) + net_broadcastsocket = 0; + */ +// shutdown(socket, SD_SEND); + + if (closesocket(socket) == SOCKET_ERROR) + { + WinPrint("WINS_CloseSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return SOCKET_ERROR; + } //end if + return 0; +} //end of the function WINS_CloseSocket +//=========================================================================== +// this lets you type only as much of the net address as required, using +// the local network components to fill in the rest +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static int PartialIPAddress (char *in, struct sockaddr_s *hostaddr) +{ + char buff[256]; + char *b; + int addr; + int num; + int mask; + + buff[0] = '.'; + b = buff; + strcpy(buff+1, in); + if (buff[1] == '.') b++; + + addr = 0; + mask=-1; + while (*b == '.') + { + num = 0; + if (*++b < '0' || *b > '9') return -1; + while (!( *b < '0' || *b > '9')) + num = num*10 + *(b++) - '0'; + mask<<=8; + addr = (addr<<8) + num; + } + + hostaddr->sa_family = AF_INET; + ((struct sockaddr_in *)hostaddr)->sin_port = htons((u_short)net_hostport); + ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = (myAddr & htonl(mask)) | htonl(addr); + + return 0; +} //end of the function PartialIPAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Connect(int socket, struct sockaddr_s *addr) +{ + int ret; + u_long _true2 = 0xFFFFFFFF; + + ret = connect(socket, (struct sockaddr *)addr, sizeof(struct sockaddr_s)); + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Connect: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + if (ioctlsocket(socket, FIONBIO, &_true2) == -1) + { + WinPrint("WINS_Connect: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + return 0; +} //end of the function WINS_Connect +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_CheckNewConnections(void) +{ + char buf[4]; + + if (net_acceptsocket == -1) + return -1; + + if (recvfrom(net_acceptsocket, buf, 4, MSG_PEEK, NULL, NULL) > 0) + return net_acceptsocket; + return -1; +} //end of the function WINS_CheckNewConnections +//=========================================================================== +// returns the number of bytes read +// 0 if no bytes available +// -1 on failure +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Read(int socket, byte *buf, int len, struct sockaddr_s *addr) +{ + int addrlen = sizeof (struct sockaddr_s); + int ret; + + if (addr) + { + ret = recvfrom(socket, buf, len, 0, (struct sockaddr *)addr, &addrlen); + if (ret == -1) + { +// errno = WSAGetLastError(); + + if (errno == EAGAIN || errno == ENOTCONN) + return 0; + } //end if + } //end if + else + { + ret = recv(socket, buf, len, 0); + // if there's no data on the socket ret == -1 and errno == EAGAIN + // MSDN states that if ret == 0 the socket has been closed + // man recv doesn't say anything + if (ret == 0) + return -1; + if (ret == SOCKET_ERROR) + { +// errno = WSAGetLastError(); + + if (errno == EAGAIN || errno == ENOTCONN) + return 0; + } //end if + } //end else + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Read: %s\n", WINS_ErrorMessage(WSAGetLastError())); + } //end if + return ret; +} //end of the function WINS_Read +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_MakeSocketBroadcastCapable (int socket) +{ + int i = 1; + + // make this socket broadcast capable + if (setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) < 0) + return -1; + net_broadcastsocket = socket; + + return 0; +} //end of the function WINS_MakeSocketBroadcastCapable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Broadcast (int socket, byte *buf, int len) +{ + int ret; + + if (socket != net_broadcastsocket) + { + if (net_broadcastsocket != 0) + WinError("Attempted to use multiple broadcasts sockets\n"); + ret = WINS_MakeSocketBroadcastCapable (socket); + if (ret == -1) + { + WinPrint("Unable to make socket broadcast capable\n"); + return ret; + } + } + + return WINS_Write (socket, buf, len, &broadcastaddr); +} //end of the function WINS_Broadcast +//=========================================================================== +// returns qtrue on success or qfalse on failure +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Write(int socket, byte *buf, int len, struct sockaddr_s *addr) +{ + int ret, written; + + if (addr) + { + written = 0; + while(written < len) + { + ret = sendto (socket, &buf[written], len-written, 0, (struct sockaddr *)addr, sizeof(struct sockaddr_s)); + if (ret == SOCKET_ERROR) + { + if (WSAGetLastError() != EAGAIN) + return qfalse; + //++timo FIXME: what is this used for? +// Sleep(1000); + } //end if + else + { + written += ret; + } + } + } //end if + else + { + written = 0; + while(written < len) + { + ret = send(socket, buf, len, 0); + if (ret == SOCKET_ERROR) + { + if (WSAGetLastError() != EAGAIN) + return qfalse; + //++timo FIXME: what is this used for? +// Sleep(1000); + } //end if + else + { + written += ret; + } + } + } //end else + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Write: %s\n", WINS_ErrorMessage(WSAGetLastError())); + } //end if + return (ret == len); +} //end of the function WINS_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_AddrToString (struct sockaddr_s *addr) +{ + static char buffer[22]; + int haddr; + + haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, ntohs(((struct sockaddr_in *)addr)->sin_port)); + return buffer; +} //end of the function WINS_AddrToString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_StringToAddr(char *string, struct sockaddr_s *addr) +{ + int ha1, ha2, ha3, ha4, hp; + int ipaddr; + + sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp); + ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4; + + addr->sa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr); + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)hp); + return 0; +} //end of the function WINS_StringToAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetSocketAddr(int socket, struct sockaddr_s *addr) +{ + int addrlen = sizeof(struct sockaddr_s); + unsigned int a; + + memset(addr, 0, sizeof(struct sockaddr_s)); + getsockname(socket, (struct sockaddr *)addr, &addrlen); + a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == inet_addr("127.0.0.1")) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + + return 0; +} //end of the function WINS_GetSocketAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetNameFromAddr (struct sockaddr_s *addr, char *name) +{ + struct hostent *hostentry; + + hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, sizeof(struct in_addr), AF_INET); + if (hostentry) + { + strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); + return 0; + } + + strcpy (name, WINS_AddrToString (addr)); + return 0; +} //end of the function WINS_GetNameFromAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetAddrFromName(char *name, struct sockaddr_s *addr) +{ + struct hostent *hostentry; + + if (name[0] >= '0' && name[0] <= '9') + return PartialIPAddress (name, addr); + + hostentry = gethostbyname (name); + if (!hostentry) + return -1; + + addr->sa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)net_hostport); + ((struct sockaddr_in *)addr)->sin_addr.s_addr = *(int *)hostentry->h_addr_list[0]; + + return 0; +} //end of the function WINS_GetAddrFromName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_AddrCompare (struct sockaddr_s *addr1, struct sockaddr_s *addr2) +{ + if (addr1->sa_family != addr2->sa_family) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != ((struct sockaddr_in *)addr2)->sin_port) + return 1; + + return 0; +} //end of the function WINS_AddrCompare +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetSocketPort (struct sockaddr_s *addr) +{ + return ntohs(((struct sockaddr_in *)addr)->sin_port); +} //end of the function WINS_GetSocketPort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_SetSocketPort (struct sockaddr_s *addr, int port) +{ + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)port); + return 0; +} //end of the function WINS_SetSocketPort diff --git a/tools/urt/libs/l_net/l_net_wins.c b/tools/urt/libs/l_net/l_net_wins.c new file mode 100644 index 00000000..25c42989 --- /dev/null +++ b/tools/urt/libs/l_net/l_net_wins.c @@ -0,0 +1,792 @@ +/* +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//=========================================================================== +// +// Name: l_net_wins.c +// Function: WinSock +// Programmer: MrElusive +// Last update: - +// Tab Size: 3 +// Notes: +//=========================================================================== + +#include +#include +#include +#include +#include "l_net.h" +#include "l_net_wins.h" +//#include +//#include "mpdosock.h" + +#define WinError WinPrint + +#define qtrue 1 +#define qfalse 0 + +typedef struct tag_error_struct +{ + int errnum; + LPSTR errstr; +} ERROR_STRUCT; + +#define NET_NAMELEN 64 + +char my_tcpip_address[NET_NAMELEN]; + +#define DEFAULTnet_hostport 26000 + +#define MAXHOSTNAMELEN 256 + +static int net_acceptsocket = -1; // socket for fielding new connections +static int net_controlsocket; +static int net_hostport; // udp port number for acceptsocket +static int net_broadcastsocket = 0; +//static qboolean ifbcastinit = qfalse; +static struct sockaddr_s broadcastaddr; + +static unsigned long myAddr; + +WSADATA winsockdata; + +ERROR_STRUCT errlist[] = { + {WSAEINTR, "WSAEINTR - Interrupted"}, + {WSAEBADF, "WSAEBADF - Bad file number"}, + {WSAEFAULT, "WSAEFAULT - Bad address"}, + {WSAEINVAL, "WSAEINVAL - Invalid argument"}, + {WSAEMFILE, "WSAEMFILE - Too many open files"}, + +/* +* Windows Sockets definitions of regular Berkeley error constants +*/ + + {WSAEWOULDBLOCK, "WSAEWOULDBLOCK - Socket marked as non-blocking"}, + {WSAEINPROGRESS, "WSAEINPROGRESS - Blocking call in progress"}, + {WSAEALREADY, "WSAEALREADY - Command already completed"}, + {WSAENOTSOCK, "WSAENOTSOCK - Descriptor is not a socket"}, + {WSAEDESTADDRREQ, "WSAEDESTADDRREQ - Destination address required"}, + {WSAEMSGSIZE, "WSAEMSGSIZE - Data size too large"}, + {WSAEPROTOTYPE, "WSAEPROTOTYPE - Protocol is of wrong type for this socket"}, + {WSAENOPROTOOPT, "WSAENOPROTOOPT - Protocol option not supported for this socket type"}, + {WSAEPROTONOSUPPORT, "WSAEPROTONOSUPPORT - Protocol is not supported"}, + {WSAESOCKTNOSUPPORT, "WSAESOCKTNOSUPPORT - Socket type not supported by this address family"}, + {WSAEOPNOTSUPP, "WSAEOPNOTSUPP - Option not supported"}, + {WSAEPFNOSUPPORT, "WSAEPFNOSUPPORT - "}, + {WSAEAFNOSUPPORT, "WSAEAFNOSUPPORT - Address family not supported by this protocol"}, + {WSAEADDRINUSE, "WSAEADDRINUSE - Address is in use"}, + {WSAEADDRNOTAVAIL, "WSAEADDRNOTAVAIL - Address not available from local machine"}, + {WSAENETDOWN, "WSAENETDOWN - Network subsystem is down"}, + {WSAENETUNREACH, "WSAENETUNREACH - Network cannot be reached"}, + {WSAENETRESET, "WSAENETRESET - Connection has been dropped"}, + {WSAECONNABORTED, "WSAECONNABORTED - Connection aborted"}, + {WSAECONNRESET, "WSAECONNRESET - Connection reset"}, + {WSAENOBUFS, "WSAENOBUFS - No buffer space available"}, + {WSAEISCONN, "WSAEISCONN - Socket is already connected"}, + {WSAENOTCONN, "WSAENOTCONN - Socket is not connected"}, + {WSAESHUTDOWN, "WSAESHUTDOWN - Socket has been shut down"}, + {WSAETOOMANYREFS, "WSAETOOMANYREFS - Too many references"}, + {WSAETIMEDOUT, "WSAETIMEDOUT - Command timed out"}, + {WSAECONNREFUSED, "WSAECONNREFUSED - Connection refused"}, + {WSAELOOP, "WSAELOOP - "}, + {WSAENAMETOOLONG, "WSAENAMETOOLONG - "}, + {WSAEHOSTDOWN, "WSAEHOSTDOWN - Host is down"}, + {WSAEHOSTUNREACH, "WSAEHOSTUNREACH - "}, + {WSAENOTEMPTY, "WSAENOTEMPTY - "}, + {WSAEPROCLIM, "WSAEPROCLIM - "}, + {WSAEUSERS, "WSAEUSERS - "}, + {WSAEDQUOT, "WSAEDQUOT - "}, + {WSAESTALE, "WSAESTALE - "}, + {WSAEREMOTE, "WSAEREMOTE - "}, + +/* +* Extended Windows Sockets error constant definitions +*/ + + {WSASYSNOTREADY, "WSASYSNOTREADY - Network subsystem not ready"}, + {WSAVERNOTSUPPORTED, "WSAVERNOTSUPPORTED - Version not supported"}, + {WSANOTINITIALISED, "WSANOTINITIALISED - WSAStartup() has not been successfully called"}, + +/* +* Other error constants. +*/ + + {WSAHOST_NOT_FOUND, "WSAHOST_NOT_FOUND - Host not found"}, + {WSATRY_AGAIN, "WSATRY_AGAIN - Host not found or SERVERFAIL"}, + {WSANO_RECOVERY, "WSANO_RECOVERY - Non-recoverable error"}, + {WSANO_DATA, "WSANO_DATA - (or WSANO_ADDRESS) - No data record of requested type"}, + {-1, NULL} +}; + +#ifdef _DEBUG +void WinPrint(char *str, ...); +#else +void WinPrint(char *str, ...); +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_ErrorMessage(int error) +{ + int search = 0; + + if (!error) return "No error occurred"; + + for (search = 0; errlist[search].errstr; search++) + { + if (error == errlist[search].errnum) + return errlist[search].errstr; + } //end for + + return "Unknown error"; +} //end of the function WINS_ErrorMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Init(void) +{ + int i; + struct hostent *local; + char buff[MAXHOSTNAMELEN]; + struct sockaddr_s addr; + char *p; + int r; + WORD wVersionRequested; + + wVersionRequested = MAKEWORD(1, 1); + + r = WSAStartup (wVersionRequested, &winsockdata); + + if (r) + { + WinPrint("Winsock initialization failed.\n"); + return -1; + } + + /* + i = COM_CheckParm ("-udpport"); + if (i == 0)*/ + net_hostport = DEFAULTnet_hostport; + /* + else if (i < com_argc-1) + net_hostport = Q_atoi (com_argv[i+1]); + else + Sys_Error ("WINS_Init: you must specify a number after -udpport"); + */ + + // determine my name & address + gethostname(buff, MAXHOSTNAMELEN); + local = gethostbyname(buff); + myAddr = *(int *)local->h_addr_list[0]; + + // if the quake hostname isn't set, set it to the machine name +// if (Q_strcmp(hostname.string, "UNNAMED") == 0) + { + // see if it's a text IP address (well, close enough) + for (p = buff; *p; p++) + if ((*p < '0' || *p > '9') && *p != '.') + break; + + // if it is a real name, strip off the domain; we only want the host + if (*p) + { + for (i = 0; i < 15; i++) + if (buff[i] == '.') + break; + buff[i] = 0; + } +// Cvar_Set ("hostname", buff); + } + + if ((net_controlsocket = WINS_OpenSocket (0)) == -1) + WinError("WINS_Init: Unable to open control socket\n"); + + ((struct sockaddr_in *)&broadcastaddr)->sin_family = AF_INET; + ((struct sockaddr_in *)&broadcastaddr)->sin_addr.s_addr = INADDR_BROADCAST; + ((struct sockaddr_in *)&broadcastaddr)->sin_port = htons((u_short)net_hostport); + + WINS_GetSocketAddr (net_controlsocket, &addr); + strcpy(my_tcpip_address, WINS_AddrToString (&addr)); + p = strrchr (my_tcpip_address, ':'); + if (p) *p = 0; + WinPrint("Winsock Initialized\n"); + + return net_controlsocket; +} //end of the function WINS_Init +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_MyAddress(void) +{ + return my_tcpip_address; +} //end of the function WINS_MyAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WINS_Shutdown(void) +{ + //WINS_Listen(0); + WINS_CloseSocket(net_controlsocket); + WSACleanup(); + // + //WinPrint("Winsock Shutdown\n"); +} //end of the function WINS_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void WINS_Listen(int state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != -1) + return; + if ((net_acceptsocket = WINS_OpenSocket (net_hostport)) == -1) + WinError ("WINS_Listen: Unable to open accept socket\n"); + return; + } + + // disable listening + if (net_acceptsocket == -1) + return; + WINS_CloseSocket (net_acceptsocket); + net_acceptsocket = -1; +} //end of the function WINS_Listen*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_OpenSocket(int port) +{ + int newsocket; + struct sockaddr_in address; + u_long _true = 1; + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + + if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons((u_short)port); + if( bind (newsocket, (void *)&address, sizeof(address)) == -1) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + return newsocket; +} //end of the function WINS_OpenSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_OpenReliableSocket(int port) +{ + int newsocket; + struct sockaddr_in address; + BOOL _true = 0xFFFFFFFF; + + //IPPROTO_TCP + // + if ((newsocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_ANY); + address.sin_port = htons((u_short)port); + if (bind(newsocket, (void *)&address, sizeof(address)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + // + if (setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (void *) &_true, sizeof(int)) == SOCKET_ERROR) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + WinPrint("setsockopt error\n"); + } //end if + + return newsocket; +} //end of the function WINS_OpenReliableSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Listen(int socket) +{ + u_long _true = 1; + + if (ioctlsocket(socket, FIONBIO, &_true) == -1) + { + WinPrint("WINS_Listen: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + if (listen(socket, SOMAXCONN) == SOCKET_ERROR) + { + WinPrint("WINS_Listen: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + return 0; +} //end of the function WINS_Listen +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Accept(int socket, struct sockaddr_s *addr) +{ + int addrlen = sizeof (struct sockaddr_s); + int newsocket; + BOOL _true = 1; + + newsocket = accept(socket, (struct sockaddr *)addr, &addrlen); + if (newsocket == INVALID_SOCKET) + { + if (WSAGetLastError() == WSAEWOULDBLOCK) return -1; + WinPrint("WINS_Accept: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + // + if (setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (void *) &_true, sizeof(int)) == SOCKET_ERROR) + { + WinPrint("WINS_Accept: %s\n", WINS_ErrorMessage(WSAGetLastError())); + WinPrint("setsockopt error\n"); + } //end if + return newsocket; +} //end of the function WINS_Accept +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_CloseSocket(int socket) +{ + /* + if (socket == net_broadcastsocket) + net_broadcastsocket = 0; + */ +// shutdown(socket, SD_SEND); + + if (closesocket(socket) == SOCKET_ERROR) + { + WinPrint("WINS_CloseSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return SOCKET_ERROR; + } //end if + return 0; +} //end of the function WINS_CloseSocket +//=========================================================================== +// this lets you type only as much of the net address as required, using +// the local network components to fill in the rest +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static int PartialIPAddress (char *in, struct sockaddr_s *hostaddr) +{ + char buff[256]; + char *b; + int addr; + int num; + int mask; + + buff[0] = '.'; + b = buff; + strcpy(buff+1, in); + if (buff[1] == '.') b++; + + addr = 0; + mask=-1; + while (*b == '.') + { + num = 0; + if (*++b < '0' || *b > '9') return -1; + while (!( *b < '0' || *b > '9')) + num = num*10 + *(b++) - '0'; + mask<<=8; + addr = (addr<<8) + num; + } + + hostaddr->sa_family = AF_INET; + ((struct sockaddr_in *)hostaddr)->sin_port = htons((u_short)net_hostport); + ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = (myAddr & htonl(mask)) | htonl(addr); + + return 0; +} //end of the function PartialIPAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Connect(int socket, struct sockaddr_s *addr) +{ + int ret; + u_long _true2 = 0xFFFFFFFF; + + ret = connect(socket, (struct sockaddr *)addr, sizeof(struct sockaddr_s)); + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Connect: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + if (ioctlsocket(socket, FIONBIO, &_true2) == -1) + { + WinPrint("WINS_Connect: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + return 0; +} //end of the function WINS_Connect +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_CheckNewConnections(void) +{ + char buf[4]; + + if (net_acceptsocket == -1) + return -1; + + if (recvfrom(net_acceptsocket, buf, 4, MSG_PEEK, NULL, NULL) > 0) + return net_acceptsocket; + return -1; +} //end of the function WINS_CheckNewConnections +//=========================================================================== +// returns the number of bytes read +// 0 if no bytes available +// -1 on failure +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Read(int socket, byte *buf, int len, struct sockaddr_s *addr) +{ + int addrlen = sizeof (struct sockaddr_s); + int ret, errno; + + if (addr) + { + ret = recvfrom(socket, buf, len, 0, (struct sockaddr *)addr, &addrlen); + if (ret == -1) + { + errno = WSAGetLastError(); + + if (errno == WSAEWOULDBLOCK || errno == WSAECONNREFUSED) + return 0; + } //end if + } //end if + else + { + ret = recv(socket, buf, len, 0); + if (ret == SOCKET_ERROR) + { + errno = WSAGetLastError(); + + if (errno == WSAEWOULDBLOCK || errno == WSAECONNREFUSED) + return 0; + } //end if + } //end else + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Read: %s\n", WINS_ErrorMessage(WSAGetLastError())); + } //end if + return ret; +} //end of the function WINS_Read +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_MakeSocketBroadcastCapable (int socket) +{ + int i = 1; + + // make this socket broadcast capable + if (setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) < 0) + return -1; + net_broadcastsocket = socket; + + return 0; +} //end of the function WINS_MakeSocketBroadcastCapable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Broadcast (int socket, byte *buf, int len) +{ + int ret; + + if (socket != net_broadcastsocket) + { + if (net_broadcastsocket != 0) + WinError("Attempted to use multiple broadcasts sockets\n"); + ret = WINS_MakeSocketBroadcastCapable (socket); + if (ret == -1) + { + WinPrint("Unable to make socket broadcast capable\n"); + return ret; + } + } + + return WINS_Write (socket, buf, len, &broadcastaddr); +} //end of the function WINS_Broadcast +//=========================================================================== +// returns qtrue on success or qfalse on failure +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Write(int socket, byte *buf, int len, struct sockaddr_s *addr) +{ + int ret, written; + + if (addr) + { + written = 0; + while(written < len) + { + ret = sendto (socket, &buf[written], len-written, 0, (struct sockaddr *)addr, sizeof(struct sockaddr_s)); + if (ret == SOCKET_ERROR) + { + if (WSAGetLastError() != WSAEWOULDBLOCK) + return qfalse; + Sleep(1000); + } //end if + else + { + written += ret; + } + } + } //end if + else + { + written = 0; + while(written < len) + { + ret = send(socket, buf, len, 0); + if (ret == SOCKET_ERROR) + { + if (WSAGetLastError() != WSAEWOULDBLOCK) + return qfalse; + Sleep(1000); + } //end if + else + { + written += ret; + } + } + } //end else + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Write: %s\n", WINS_ErrorMessage(WSAGetLastError())); + } //end if + return (ret == len); +} //end of the function WINS_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_AddrToString (struct sockaddr_s *addr) +{ + static char buffer[22]; + int haddr; + + haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, ntohs(((struct sockaddr_in *)addr)->sin_port)); + return buffer; +} //end of the function WINS_AddrToString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_StringToAddr(char *string, struct sockaddr_s *addr) +{ + int ha1, ha2, ha3, ha4, hp; + int ipaddr; + + sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp); + ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4; + + addr->sa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr); + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)hp); + return 0; +} //end of the function WINS_StringToAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetSocketAddr(int socket, struct sockaddr_s *addr) +{ + int addrlen = sizeof(struct sockaddr_s); + unsigned int a; + + memset(addr, 0, sizeof(struct sockaddr_s)); + getsockname(socket, (struct sockaddr *)addr, &addrlen); + a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == inet_addr("127.0.0.1")) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + + return 0; +} //end of the function WINS_GetSocketAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetNameFromAddr (struct sockaddr_s *addr, char *name) +{ + struct hostent *hostentry; + + hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, sizeof(struct in_addr), AF_INET); + if (hostentry) + { + strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); + return 0; + } + + strcpy (name, WINS_AddrToString (addr)); + return 0; +} //end of the function WINS_GetNameFromAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetAddrFromName(char *name, struct sockaddr_s *addr) +{ + struct hostent *hostentry; + + if (name[0] >= '0' && name[0] <= '9') + return PartialIPAddress (name, addr); + + hostentry = gethostbyname (name); + if (!hostentry) + return -1; + + addr->sa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)net_hostport); + ((struct sockaddr_in *)addr)->sin_addr.s_addr = *(int *)hostentry->h_addr_list[0]; + + return 0; +} //end of the function WINS_GetAddrFromName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_AddrCompare (struct sockaddr_s *addr1, struct sockaddr_s *addr2) +{ + if (addr1->sa_family != addr2->sa_family) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != ((struct sockaddr_in *)addr2)->sin_port) + return 1; + + return 0; +} //end of the function WINS_AddrCompare +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetSocketPort (struct sockaddr_s *addr) +{ + return ntohs(((struct sockaddr_in *)addr)->sin_port); +} //end of the function WINS_GetSocketPort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_SetSocketPort (struct sockaddr_s *addr, int port) +{ + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)port); + return 0; +} //end of the function WINS_SetSocketPort diff --git a/tools/urt/libs/l_net/l_net_wins.h b/tools/urt/libs/l_net/l_net_wins.h new file mode 100644 index 00000000..e9680888 --- /dev/null +++ b/tools/urt/libs/l_net/l_net_wins.h @@ -0,0 +1,55 @@ +/* +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//=========================================================================== +// +// Name: l_net_wins.h +// Function: WinSock +// Programmer: MrElusive +// Last update: TTimo: cross-platform version, l_net library +// Tab Size: 3 +// Notes: +//=========================================================================== + +int WINS_Init(void); +void WINS_Shutdown(void); +char *WINS_MyAddress(void); +int WINS_Listen(int socket); +int WINS_Accept(int socket, struct sockaddr_s *addr); +int WINS_OpenSocket(int port); +int WINS_OpenReliableSocket(int port); +int WINS_CloseSocket(int socket); +int WINS_Connect (int socket, struct sockaddr_s *addr); +int WINS_CheckNewConnections(void); +int WINS_Read(int socket, byte *buf, int len, struct sockaddr_s *addr); +int WINS_Write(int socket, byte *buf, int len, struct sockaddr_s *addr); +int WINS_Broadcast (int socket, byte *buf, int len); +char *WINS_AddrToString (struct sockaddr_s *addr); +int WINS_StringToAddr (char *string, struct sockaddr_s *addr); +int WINS_GetSocketAddr (int socket, struct sockaddr_s *addr); +int WINS_GetNameFromAddr (struct sockaddr_s *addr, char *name); +int WINS_GetAddrFromName (char *name, struct sockaddr_s *addr); +int WINS_AddrCompare (struct sockaddr_s *addr1, struct sockaddr_s *addr2); +int WINS_GetSocketPort (struct sockaddr_s *addr); +int WINS_SetSocketPort (struct sockaddr_s *addr, int port); diff --git a/tools/urt/libs/libs.vcproj b/tools/urt/libs/libs.vcproj new file mode 100644 index 00000000..71b155a3 --- /dev/null +++ b/tools/urt/libs/libs.vcproj @@ -0,0 +1,592 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/urt/libs/maplib.cpp b/tools/urt/libs/maplib.cpp new file mode 100644 index 00000000..2dfa6985 --- /dev/null +++ b/tools/urt/libs/maplib.cpp @@ -0,0 +1,2 @@ + +#include "maplib.h" diff --git a/tools/urt/libs/maplib.h b/tools/urt/libs/maplib.h new file mode 100644 index 00000000..94950c5b --- /dev/null +++ b/tools/urt/libs/maplib.h @@ -0,0 +1,262 @@ + +#if !defined (INCLUDED_MAPLIB_H) +#define INCLUDED_MAPLIB_H + +#include "nameable.h" +#include "mapfile.h" + +#include "traverselib.h" +#include "transformlib.h" +#include "scenelib.h" +#include "string/string.h" +#include "instancelib.h" +#include "selectionlib.h" +#include "generic/callback.h" + + +class NameableString : public Nameable +{ + CopiedString m_name; +public: + NameableString(const char* name) + : m_name(name) + { + } + + const char* name() const + { + return m_name.c_str(); + } + void attach(const NameCallback& callback) + { + } + void detach(const NameCallback& callback) + { + } +}; + + +class UndoFileChangeTracker : public UndoTracker, public MapFile +{ + std::size_t m_size; + std::size_t m_saved; + typedef void (UndoFileChangeTracker::*Pending)(); + Pending m_pending; + Callback m_changed; + +public: + UndoFileChangeTracker() : m_size(0), m_saved(MAPFILE_MAX_CHANGES), m_pending(0) + { + } + void print() + { + globalOutputStream() << "saved: " << Unsigned(m_saved) << " size: " << Unsigned(m_size) << "\n"; + } + + void push() + { + ++m_size; + m_changed(); + //print(); + } + void pop() + { + --m_size; + m_changed(); + //print(); + } + void pushOperation() + { + if(m_size < m_saved) + { + // redo queue has been flushed.. it is now impossible to get back to the saved state via undo/redo + m_saved = MAPFILE_MAX_CHANGES; + } + push(); + } + void clear() + { + m_size = 0; + m_changed(); + //print(); + } + void begin() + { + m_pending = Pending(&UndoFileChangeTracker::pushOperation); + } + void undo() + { + m_pending = Pending(&UndoFileChangeTracker::pop); + } + void redo() + { + m_pending = Pending(&UndoFileChangeTracker::push); + } + + void changed() + { + if(m_pending != 0) + { + ((*this).*m_pending)(); + m_pending = 0; + } + } + + void save() + { + m_saved = m_size; + m_changed(); + } + bool saved() const + { + return m_saved == m_size; + } + + void setChangedCallback(const Callback& changed) + { + m_changed = changed; + m_changed(); + } + + std::size_t changes() const + { + return m_size; + } +}; + + +class MapRoot : public scene::Node::Symbiot, public scene::Instantiable, public scene::Traversable::Observer +{ + class TypeCasts + { + NodeTypeCastTable m_casts; + public: + TypeCasts() + { + NodeStaticCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + } + NodeTypeCastTable& get() + { + return m_casts; + } + }; + + scene::Node m_node; + IdentityTransform m_transform; + TraversableNodeSet m_traverse; + Instances m_instances; + typedef SelectableInstance Instance; + NameableString m_name; + UndoFileChangeTracker m_changeTracker; +public: + typedef LazyStatic StaticTypeCasts; + + scene::Traversable& get(NullType) + { + return m_traverse; + } + Transformable& get(NullType) + { + return m_transform; + } + Nameable& get(NullType) + { + return m_name; + } + MapFile& get(NullType) + { + return m_changeTracker; + } + + MapRoot(const char* name) : m_node(this, this, StaticTypeCasts::instance().get()), m_name(name) + { + m_node.m_isRoot = true; + + m_traverse.attach(this); + + GlobalUndoSystem().trackerAttach(m_changeTracker); + } + ~MapRoot() + { + } + void release() + { + GlobalUndoSystem().trackerDetach(m_changeTracker); + + m_traverse.detach(this); + delete this; + } + scene::Node& node() + { + return m_node; + } + + InstanceCounter m_instanceCounter; + void instanceAttach(const scene::Path& path) + { + if(++m_instanceCounter.m_count == 1) + { + m_traverse.instanceAttach(path_find_mapfile(path.begin(), path.end())); + } + } + void instanceDetach(const scene::Path& path) + { + if(--m_instanceCounter.m_count == 0) + { + m_traverse.instanceDetach(path_find_mapfile(path.begin(), path.end())); + } + } + + void insert(scene::Node& child) + { + m_instances.insert(child); + } + void erase(scene::Node& child) + { + m_instances.erase(child); + } + + scene::Node& clone() const + { + return (new MapRoot(*this))->node(); + } + + scene::Instance* create(const scene::Path& path, scene::Instance* parent) + { + return new Instance(path, parent); + } + void forEachInstance(const scene::Instantiable::Visitor& visitor) + { + m_instances.forEachInstance(visitor); + } + void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance) + { + m_instances.insert(observer, path, instance); + instanceAttach(path); + } + scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path) + { + instanceDetach(path); + return m_instances.erase(observer, path); + } +}; + +inline void MapRoot_construct() +{ +} + +inline void MapRoot_destroy() +{ +} + +inline NodeSmartReference NewMapRoot(const char* name) +{ + return NodeSmartReference((new MapRoot(name))->node()); +} + + +#endif diff --git a/tools/urt/libs/math/aabb.cpp b/tools/urt/libs/math/aabb.cpp new file mode 100644 index 00000000..c53006ec --- /dev/null +++ b/tools/urt/libs/math/aabb.cpp @@ -0,0 +1,3 @@ + +#include "aabb.h" + diff --git a/tools/urt/libs/math/aabb.h b/tools/urt/libs/math/aabb.h new file mode 100644 index 00000000..ea6cc9f0 --- /dev/null +++ b/tools/urt/libs/math/aabb.h @@ -0,0 +1,281 @@ + +#if !defined(INCLUDED_MATH_AABB_H) +#define INCLUDED_MATH_AABB_H + +/// \file +/// \brief Axis-aligned bounding-box data types and related operations. + +#include "math/matrix.h" +#include "math/plane.h" + +class AABB +{ +public: + Vector3 origin, extents; + + AABB() : origin(0, 0, 0), extents(-1,-1,-1) + { + } + AABB(const Vector3& origin_, const Vector3& extents_) : + origin(origin_), extents(extents_) + { + } +}; + +const float c_aabb_max = FLT_MAX; + +inline bool extents_valid(float f) +{ + return f >= 0.0f && f <= c_aabb_max; +} + +inline bool origin_valid(float f) +{ + return f >= -c_aabb_max && f <= c_aabb_max; +} + +inline bool aabb_valid(const AABB& aabb) +{ + return origin_valid(aabb.origin[0]) + && origin_valid(aabb.origin[1]) + && origin_valid(aabb.origin[2]) + && extents_valid(aabb.extents[0]) + && extents_valid(aabb.extents[1]) + && extents_valid(aabb.extents[2]); +} + +inline AABB aabb_for_minmax(const Vector3& min, const Vector3& max) +{ + AABB aabb; + aabb.origin = vector3_mid(min, max); + aabb.extents = vector3_subtracted(max, aabb.origin); + return aabb; +} + +template +class AABBExtend +{ +public: + static void apply(AABB& aabb, const Vector3& point) + { + float displacement = point[Index::value] - aabb.origin[Index::value]; + float half_difference = static_cast(0.5 * (fabs(displacement) - aabb.extents[Index::value])); + if(half_difference > 0.0f) + { + aabb.origin[Index::value] += (displacement >= 0.0f) ? half_difference : -half_difference; + aabb.extents[Index::value] += half_difference; + } + } + static void apply(AABB& aabb, const AABB& other) + { + float displacement = other.origin[Index::value] - aabb.origin[Index::value]; + float difference = other.extents[Index::value] - aabb.extents[Index::value]; + if(fabs(displacement) > fabs(difference)) + { + float half_difference = static_cast(0.5 * (fabs(displacement) + difference)); + if(half_difference > 0.0f) + { + aabb.origin[Index::value] += (displacement >= 0.0f) ? half_difference : -half_difference; + aabb.extents[Index::value] += half_difference; + } + } + else if(difference > 0.0f) + { + aabb.origin[Index::value] = other.origin[Index::value]; + aabb.extents[Index::value] = other.extents[Index::value]; + } + } +}; + +inline void aabb_extend_by_point(AABB& aabb, const Vector3& point) +{ + AABBExtend< ct_int<0> >::apply(aabb, point); + AABBExtend< ct_int<1> >::apply(aabb, point); + AABBExtend< ct_int<2> >::apply(aabb, point); +} + +inline void aabb_extend_by_point_safe(AABB& aabb, const Vector3& point) +{ + if(aabb_valid(aabb)) + { + aabb_extend_by_point(aabb, point); + } + else + { + aabb.origin = point; + aabb.extents = Vector3(0, 0, 0); + } +} + +class AABBExtendByPoint +{ + AABB& m_aabb; +public: + AABBExtendByPoint(AABB& aabb) : m_aabb(aabb) + { + } + void operator()(const Vector3& point) const + { + aabb_extend_by_point_safe(m_aabb, point); + } +}; + +inline void aabb_extend_by_aabb(AABB& aabb, const AABB& other) +{ + AABBExtend< ct_int<0> >::apply(aabb, other); + AABBExtend< ct_int<1> >::apply(aabb, other); + AABBExtend< ct_int<2> >::apply(aabb, other); +} + +inline void aabb_extend_by_aabb_safe(AABB& aabb, const AABB& other) +{ + if(aabb_valid(aabb) && aabb_valid(other)) + { + aabb_extend_by_aabb(aabb, other); + } + else if(aabb_valid(other)) + { + aabb = other; + } +} + +inline void aabb_extend_by_vec3(AABB& aabb, const Vector3& extension) +{ + vector3_add(aabb.extents, extension); +} + + + + +template +inline bool aabb_intersects_point_dimension(const AABB& aabb, const Vector3& point) +{ + return fabs(point[Index::value] - aabb.origin[Index::value]) < aabb.extents[Index::value]; +} + +inline bool aabb_intersects_point(const AABB& aabb, const Vector3& point) +{ + return aabb_intersects_point_dimension< ct_int<0> >(aabb, point) + && aabb_intersects_point_dimension< ct_int<1> >(aabb, point) + && aabb_intersects_point_dimension< ct_int<2> >(aabb, point); +} + +template +inline bool aabb_intersects_aabb_dimension(const AABB& aabb, const AABB& other) +{ + return fabs(other.origin[Index::value] - aabb.origin[Index::value]) < (aabb.extents[Index::value] + other.extents[Index::value]); +} + +inline bool aabb_intersects_aabb(const AABB& aabb, const AABB& other) +{ + return aabb_intersects_aabb_dimension< ct_int<0> >(aabb, other) + && aabb_intersects_aabb_dimension< ct_int<1> >(aabb, other) + && aabb_intersects_aabb_dimension< ct_int<2> >(aabb, other); +} + +inline unsigned int aabb_classify_plane(const AABB& aabb, const Plane3& plane) +{ + double distance_origin = vector3_dot(plane.normal(), aabb.origin) + plane.dist(); + + if(fabs(distance_origin) < (fabs(plane.a * aabb.extents[0]) + + fabs(plane.b * aabb.extents[1]) + + fabs(plane.c * aabb.extents[2]))) + { + return 1; // partially inside + } + else if (distance_origin < 0) + { + return 2; // totally inside + } + return 0; // totally outside +} + +inline unsigned int aabb_oriented_classify_plane(const AABB& aabb, const Matrix4& transform, const Plane3& plane) +{ + double distance_origin = vector3_dot(plane.normal(), aabb.origin) + plane.dist(); + + if(fabs(distance_origin) < (fabs(aabb.extents[0] * vector3_dot(plane.normal(), vector4_to_vector3(transform.x()))) + + fabs(aabb.extents[1] * vector3_dot(plane.normal(), vector4_to_vector3(transform.y()))) + + fabs(aabb.extents[2] * vector3_dot(plane.normal(), vector4_to_vector3(transform.z()))))) + { + return 1; // partially inside + } + else if (distance_origin < 0) + { + return 2; // totally inside + } + return 0; // totally outside +} + +inline void aabb_corners(const AABB& aabb, Vector3 corners[8]) +{ + Vector3 min(vector3_subtracted(aabb.origin, aabb.extents)); + Vector3 max(vector3_added(aabb.origin, aabb.extents)); + corners[0] = Vector3(min[0], max[1], max[2]); + corners[1] = Vector3(max[0], max[1], max[2]); + corners[2] = Vector3(max[0], min[1], max[2]); + corners[3] = Vector3(min[0], min[1], max[2]); + corners[4] = Vector3(min[0], max[1], min[2]); + corners[5] = Vector3(max[0], max[1], min[2]); + corners[6] = Vector3(max[0], min[1], min[2]); + corners[7] = Vector3(min[0], min[1], min[2]); +} + +inline void aabb_planes(const AABB& aabb, Plane3 planes[6]) +{ + planes[0] = Plane3(g_vector3_axes[0], aabb.origin[0] + aabb.extents[0]); + planes[1] = Plane3(vector3_negated(g_vector3_axes[0]), -(aabb.origin[0] - aabb.extents[0])); + planes[2] = Plane3(g_vector3_axes[1], aabb.origin[1] + aabb.extents[1]); + planes[3] = Plane3(vector3_negated(g_vector3_axes[1]), -(aabb.origin[1] - aabb.extents[1])); + planes[4] = Plane3(g_vector3_axes[2], aabb.origin[2] + aabb.extents[2]); + planes[5] = Plane3(vector3_negated(g_vector3_axes[2]), -(aabb.origin[2] - aabb.extents[2])); +} + +const Vector3 aabb_normals[6] = { + Vector3( 1, 0, 0 ), + Vector3( 0, 1, 0 ), + Vector3( 0, 0, 1 ), + Vector3(-1, 0, 0 ), + Vector3( 0,-1, 0 ), + Vector3( 0, 0,-1 ), +}; + +const float aabb_texcoord_topleft[2] = { 0, 0 }; +const float aabb_texcoord_topright[2] = { 1, 0 }; +const float aabb_texcoord_botleft[2] = { 0, 1 }; +const float aabb_texcoord_botright[2] = { 1, 1 }; + + +inline AABB aabb_for_oriented_aabb(const AABB& aabb, const Matrix4& transform) +{ + return AABB( + matrix4_transformed_point(transform, aabb.origin), + Vector3( + static_cast(fabs(transform[0] * aabb.extents[0]) + + fabs(transform[4] * aabb.extents[1]) + + fabs(transform[8] * aabb.extents[2])), + static_cast(fabs(transform[1] * aabb.extents[0]) + + fabs(transform[5] * aabb.extents[1]) + + fabs(transform[9] * aabb.extents[2])), + static_cast(fabs(transform[2] * aabb.extents[0]) + + fabs(transform[6] * aabb.extents[1]) + + fabs(transform[10] * aabb.extents[2])) + ) + ); +} + +inline AABB aabb_for_oriented_aabb_safe(const AABB& aabb, const Matrix4& transform) +{ + if(aabb_valid(aabb)) + { + return aabb_for_oriented_aabb(aabb, transform); + } + return aabb; +} + +inline AABB aabb_infinite() +{ + return AABB(Vector3(0, 0, 0), Vector3(c_aabb_max, c_aabb_max, c_aabb_max)); +} + +#endif diff --git a/tools/urt/libs/math/curve.cpp b/tools/urt/libs/math/curve.cpp new file mode 100644 index 00000000..263ca5a3 --- /dev/null +++ b/tools/urt/libs/math/curve.cpp @@ -0,0 +1,3 @@ + +#include "curve.h" + diff --git a/tools/urt/libs/math/curve.h b/tools/urt/libs/math/curve.h new file mode 100644 index 00000000..17de838b --- /dev/null +++ b/tools/urt/libs/math/curve.h @@ -0,0 +1,253 @@ + +#if !defined(INCLUDED_MATH_CURVE_H) +#define INCLUDED_MATH_CURVE_H + +/// \file +/// \brief Curve data types and related operations. + +#include "debugging/debugging.h" +#include "container/array.h" +#include + + +template +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 1; // general case not implemented + } +}; + +typedef ct_int<0> Zero; +typedef ct_int<1> One; +typedef ct_int<2> Two; +typedef ct_int<3> Three; +typedef ct_int<4> Four; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 1; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 1 - t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return (1 - t) * (1 - t); + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 2 * (1 - t) * t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return t * t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return (1 - t) * (1 - t) * (1 - t); + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 3 * (1 - t) * (1 - t) * t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 3 * (1 - t) * t * t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return t * t * t; + } +}; + +typedef Array ControlPoints; + +inline Vector3 CubicBezier_evaluate(const Vector3* firstPoint, double t) +{ + Vector3 result(0, 0, 0); + double denominator = 0; + + { + double weight = BernsteinPolynomial::apply(t); + result += vector3_scaled(*firstPoint++, weight); + denominator += weight; + } + { + double weight = BernsteinPolynomial::apply(t); + result += vector3_scaled(*firstPoint++, weight); + denominator += weight; + } + { + double weight = BernsteinPolynomial::apply(t); + result += vector3_scaled(*firstPoint++, weight); + denominator += weight; + } + { + double weight = BernsteinPolynomial::apply(t); + result += vector3_scaled(*firstPoint++, weight); + denominator += weight; + } + + return result / denominator; +} + +inline Vector3 CubicBezier_evaluateMid(const Vector3* firstPoint) +{ + return vector3_scaled(firstPoint[0], 0.125) + + vector3_scaled(firstPoint[1], 0.375) + + vector3_scaled(firstPoint[2], 0.375) + + vector3_scaled(firstPoint[3], 0.125); +} + +inline Vector3 CatmullRom_evaluate(const ControlPoints& controlPoints, double t) +{ + // scale t to be segment-relative + t *= double(controlPoints.size() - 1); + + // subtract segment index; + std::size_t segment = 0; + for(std::size_t i = 0; i < controlPoints.size() - 1; ++i) + { + if(t <= double(i+1)) + { + segment = i; + break; + } + } + t -= segment; + + const double reciprocal_alpha = 1.0 / 3.0; + + Vector3 bezierPoints[4]; + bezierPoints[0] = controlPoints[segment]; + bezierPoints[1] = (segment > 0) + ? controlPoints[segment] + vector3_scaled(controlPoints[segment + 1] - controlPoints[segment - 1], reciprocal_alpha * 0.5) + : controlPoints[segment] + vector3_scaled(controlPoints[segment + 1] - controlPoints[segment], reciprocal_alpha); + bezierPoints[2] = (segment < controlPoints.size() - 2) + ? controlPoints[segment + 1] + vector3_scaled(controlPoints[segment] - controlPoints[segment + 2], reciprocal_alpha * 0.5) + : controlPoints[segment + 1] + vector3_scaled(controlPoints[segment] - controlPoints[segment + 1], reciprocal_alpha); + bezierPoints[3] = controlPoints[segment + 1]; + return CubicBezier_evaluate(bezierPoints, t); +} + +typedef Array Knots; + +inline double BSpline_basis(const Knots& knots, std::size_t i, std::size_t degree, double t) +{ + if(degree == 0) + { + if(knots[i] <= t + && t < knots[i + 1] + && knots[i] < knots[i + 1]) + { + return 1; + } + return 0; + } + double leftDenom = knots[i + degree] - knots[i]; + double left = (leftDenom == 0) ? 0 : ((t - knots[i]) / leftDenom) * BSpline_basis(knots, i, degree - 1, t); + double rightDenom = knots[i + degree + 1] - knots[i + 1]; + double right = (rightDenom == 0) ? 0 : ((knots[i + degree + 1] - t) / rightDenom) * BSpline_basis(knots, i + 1, degree - 1, t); + return left + right; +} + +inline Vector3 BSpline_evaluate(const ControlPoints& controlPoints, const Knots& knots, std::size_t degree, double t) +{ + Vector3 result(0, 0, 0); + for(std::size_t i = 0; i < controlPoints.size(); ++i) + { + result += vector3_scaled(controlPoints[i], BSpline_basis(knots, i, degree, t)); + } + return result; +} + +typedef Array NURBSWeights; + +inline Vector3 NURBS_evaluate(const ControlPoints& controlPoints, const NURBSWeights& weights, const Knots& knots, std::size_t degree, double t) +{ + Vector3 result(0, 0, 0); + double denominator = 0; + for(std::size_t i = 0; i < controlPoints.size(); ++i) + { + double weight = weights[i] * BSpline_basis(knots, i, degree, t); + result += vector3_scaled(controlPoints[i], weight); + denominator += weight; + } + return result / denominator; +} + +inline void KnotVector_openUniform(Knots& knots, std::size_t count, std::size_t degree) +{ + knots.resize(count + degree + 1); + + std::size_t equalKnots = 1; + + for(std::size_t i = 0; i < equalKnots; ++i) + { + knots[i] = 0; + knots[knots.size() - (i + 1)] = 1; + } + + std::size_t difference = knots.size() - 2 * (equalKnots); + for(std::size_t i = 0; i < difference; ++i) + { + knots[i + equalKnots] = Knots::value_type(double(i + 1) * 1.0 / double(difference + 1)); + } +} + +#endif diff --git a/tools/urt/libs/math/expression.cpp b/tools/urt/libs/math/expression.cpp new file mode 100644 index 00000000..ba0cc3c8 --- /dev/null +++ b/tools/urt/libs/math/expression.cpp @@ -0,0 +1,223 @@ + +#include "expression.h" + +Vector3 testAdded1(const Vector3& a, const Vector3& b) +{ + return vector3_added(a, vector3_added(a, b)); +} + +Vector3 testAdded2(const Vector3& a, const Vector3& b) +{ + return vector3_for_expression( vector_added( vector3_identity(a), vector_added( vector3_identity(a), vector3_identity(b) ) ) ); +} + +Vector3 testMultiplied1(const Vector3& a, const Vector3& b) +{ + return vector3_scaled(a, b); +} + +Vector3 testMultiplied2(const Vector3& a, const Vector3& b) +{ + return vector3_for_expression( vector_multiplied( vector3_identity(a), vector3_identity(b) ) ); +} + +Vector3 testCross1(const Vector3& a, const Vector3& b) +{ + return vector3_cross(a, b); +} + +Vector3 testCross2(const Vector3& a, const Vector3& b) +{ + return vector3_for_expression( vector_cross( vector3_identity(a), vector3_identity(b) ) ); +} + +double testDot1(const Vector3& a, const Vector3& b) +{ + return vector3_dot(a, b); +} + +double testDot2(const Vector3& a, const Vector3& b) +{ + return float_for_expression( vector_dot( vector3_identity(a), vector3_identity(b) ) ); +} + +double testLength1(const Vector3& a) +{ + return vector3_length(a); +} + +double testLength2(const Vector3& a) +{ + return float_for_expression( vector_length( vector3_identity(a) ) ); +} + +Vector3 testNormalised1(const Vector3& a) +{ + return vector3_normalised(a); +} + +Vector3 testNormalised2(const Vector3& a) +{ + return vector3_for_expression( vector_normalised( vector3_identity(a) ) ); +} + +Vector3 testNegated1(const Vector3& a) +{ + return vector3_negated(a); +} + +Vector3 testNegated2(const Vector3& a) +{ + return vector3_for_expression( vector_negated( vector3_identity(a) ) ); +} + +Vector3 testScaled1(const Vector3& a, const double& b) +{ + return vector3_scaled(a, b); +} + +Vector3 testScaled2(const Vector3& a, const double& b) +{ + return vector3_for_expression( vector_scaled( vector3_identity(a), float_literal(b) ) ); +} + +Vector3 testMatrixMultiplied1(const Vector3& a, const Matrix4& b) +{ + return matrix4_transformed_point(b, vector3_added(a, Vector3(1, 0, 0))); +} + +Vector3 testMatrixMultiplied2(const Vector3& a, const Matrix4& b) +{ + return vector3_for_expression( + point_multiplied( + vector_added( + vector3_identity(a), + vector3_literal(Vector3(1, 0, 0)) + ), + matrix4_identity(b) + ) + ); +} + +Matrix4 testMatrix4Multiplied1(const Matrix4& a, const Matrix4& b) +{ + return matrix4_multiplied_by_matrix4(a, matrix4_multiplied_by_matrix4(a, b)); +} + +Matrix4 testMatrix4Multiplied2(const Matrix4& a, const Matrix4& b) +{ + return matrix4_for_expression( + matrix4_multiplied( + matrix4_identity(a), + matrix4_identity(b) + ) + ); +} + +Matrix4 testMatrix4AffineMultiplied1(const Matrix4& a, const Matrix4& b) +{ + return matrix4_affine_multiplied_by_matrix4(a, b); +} + +Matrix4 testMatrix4AffineMultiplied2(const Matrix4& a, const Matrix4& b) +{ + return matrix4_affine_for_expression( + matrix4_multiplied( + matrix4_identity(a), + matrix4_identity(b) + ) + ); +} + +Matrix4 testMatrix4MultipliedConstant1(const Matrix4& a) +{ + return matrix4_multiplied_by_matrix4(a, g_matrix4_identity); +} + +Matrix4 testMatrix4MultipliedConstant2(const Matrix4& a) +{ + return matrix4_for_expression( + matrix4_multiplied( + matrix4_identity(a), + matrix4_identity(g_matrix4_identity) + ) + ); +} +Matrix4 testMatrix4Transposed1(const Matrix4& a) +{ + return matrix4_transposed(a); +} + +Matrix4 testMatrix4Transposed2(const Matrix4& a) +{ + return matrix4_for_expression( matrix_transposed( matrix4_identity(a) ) ); +} + +Vector3 testMulti1(const Matrix4& a, const Vector3& b, const Vector3& c) +{ + return vector3_added(matrix4_transformed_point(matrix4_transposed(a), b), c); +} + +Vector3 testMulti2(const Matrix4& a, const Vector3& b, const Vector3& c) +{ + return vector3_for_expression( + vector_added( + point_multiplied( + vector3_identity(b), + matrix_transposed(matrix4_identity(a)) + ), + vector3_identity(c) + ) + ); +} + +template +class TestBinaryFunction +{ + typedef Value(*Function)(const First&, const Second&); + Function m_function; +public: + + TestBinaryFunction(Function function) : m_function(function) + { + } + Value run(const First& first, const Second& second) const + { + return m_function(first, second); + } +}; + +template +class TestUnaryFunction +{ + typedef Value(*Function)(const First&); + Function m_function; +public: + + TestUnaryFunction(Function function) : m_function(function) + { + } + Value run(const First& first) const + { + return m_function(first); + } +}; + +class TestAll +{ +public: + TestAll() + { + Vector3 result1 = TestBinaryFunction(testAdded1).run(Vector3(0, 0, 0), Vector3(1, 1, 1)); + Vector3 result2 = TestBinaryFunction(testAdded2).run(Vector3(0, 0, 0), Vector3(1, 1, 1)); + Vector3 result3 = TestBinaryFunction(testMultiplied1).run(Vector3(1, 2, 3), Vector3(2, 1, 0.5f)); + Vector3 result4 = TestBinaryFunction(testMultiplied2).run(Vector3(1, 2, 3), Vector3(2, 1, 0.5f)); + Vector3 result5 = TestBinaryFunction(testScaled1).run(Vector3(1, 2, 3), 2.0); + Vector3 result6 = TestBinaryFunction(testScaled2).run(Vector3(1, 2, 3), 2.0); + Vector3 result7 = TestBinaryFunction(testMatrixMultiplied1).run(Vector3(1, 2, 3), matrix4_rotation_for_x_degrees(90)); + Vector3 result8 = TestBinaryFunction(testMatrixMultiplied2).run(Vector3(1, 2, 3), matrix4_rotation_for_x_degrees(90)); + Vector3 result9 = TestUnaryFunction(testNormalised1).run(Vector3(1, 2, 3)); + Vector3 result10 = TestUnaryFunction(testNormalised2).run(Vector3(1, 2, 3)); + } +} g_testAll; + diff --git a/tools/urt/libs/math/expression.h b/tools/urt/libs/math/expression.h new file mode 100644 index 00000000..c48204d4 --- /dev/null +++ b/tools/urt/libs/math/expression.h @@ -0,0 +1,597 @@ + +#if !defined (INCLUDED_EXPRESSION_H) +#define INCLUDED_EXPRESSION_H + +#include + +template +class Literal +{ + Value m_value; +public: + typedef Value value_type; + + Literal(const Value& value) + : m_value(value) + { + } + const value_type& eval() const + { + return m_value; + } +}; + +template +inline Literal float_literal(const Value& value) +{ + return Literal(value); +} + +template +inline float float_for_expression(const Expression& expression) +{ + return expression.eval(); +} + + +template +class ScalarDivided +{ + First first; + Second second; +public: + typedef typename First::value_type value_type; + + ScalarDivided(const First& first_, const Second& second_) : first(first_), second(second_) + { + } + value_type eval() const + { + return static_cast(first.eval() / second.eval()); + } +}; + +template +inline ScalarDivided float_divided(const First& first, const Second& second) +{ + return ScalarDivided(first, second); +} + +template +inline ScalarDivided, First> float_reciprocal(const First& first) +{ + typedef typename First::value_type first_value_type; + return ScalarDivided, First>(float_literal(first_value_type(1.0)), first); +} + +template +class SquareRoot +{ + First first; +public: + typedef typename First::value_type value_type; + + SquareRoot(const First& first_) : first(first_) + { + } + value_type eval() const + { + return static_cast(sqrt(first.eval())); + } +}; + +template +inline SquareRoot float_square_root(const First& first) +{ + return SquareRoot(first); +} + + +template +class BasicVector3Literal +{ + const BasicVector3 m_value; +public: + typedef Element value_type; + typedef ct_int<3> dimension; + + BasicVector3Literal(const BasicVector3& value) + : m_value(value) + { + } + const value_type& eval(unsigned int i) const + { + return m_value[i]; + } +}; + +template +inline BasicVector3Literal vector3_literal(const BasicVector3& value) +{ + return BasicVector3Literal(value); +} + +typedef BasicVector3Literal Vector3Literal; + +template +class BasicVector3Identity +{ + const BasicVector3& m_value; +public: + typedef Element value_type; + typedef ct_int<3> dimension; + + BasicVector3Identity(const BasicVector3& value) + : m_value(value) + { + } + const value_type& eval(unsigned int i) const + { + return m_value[i]; + } +}; + +template +inline BasicVector3Identity vector3_identity(const BasicVector3& value) +{ + return BasicVector3Identity(value); +} + +typedef BasicVector3Identity Vector3Identity; + +template +inline BasicVector3 vector3_for_expression(const Expression& expression) +{ + return Vector3(expression.eval(0), expression.eval(1), expression.eval(2)); +} + + +template +class VectorScalar +{ + First first; + Literal second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorScalar(const First& first_, const Second& second_) + : first(first_), second(second_.eval()) + { + } + value_type eval(unsigned int i) const + { + return Operation::apply( first.eval(i), second.eval() ); + } +}; + + + +template +class VectorVector +{ + First first; + Second second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorVector(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + value_type eval(unsigned int i) const + { + return Operation::apply(first.eval(i), second.eval(i)); + } +}; + +template +class Added +{ +public: + typedef First value_type; + + static value_type apply(const First& first, const Second& second) + { + return static_cast(first + second); + } +}; + +template +inline VectorVector, First, Second> +vector_added(const First& first, const Second& second) +{ + typedef typename First::value_type first_value_type; + typedef typename Second::value_type second_value_type; + return VectorVector, First, Second>(first, second); +} + +template +class Multiplied +{ +public: + typedef First value_type; + + static value_type apply(const First& first, const Second& second) + { + return static_cast(first * second); + } +}; + +template +inline VectorVector, First, Second> +vector_multiplied(const First& first, const Second& second) +{ + typedef typename First::value_type first_value_type; + typedef typename Second::value_type second_value_type; + return VectorVector, First, Second>(first, second); +} + +template +inline VectorScalar, First, Second> +vector_scaled(const First& first, const Second& second) +{ + typedef typename First::value_type first_value_type; + typedef typename Second::value_type second_value_type; + return VectorScalar, First, Second>(first, second); +} + +template +class Negated +{ +public: + typedef First value_type; + + static value_type apply(const First& first) + { + return -first; + } +}; + +template +class VectorUnary +{ + First first; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorUnary(const First& first_) : first(first_) + { + } + value_type eval(unsigned int i) const + { + return Operation::apply(first.eval(i)); + } +}; + +template +inline VectorUnary > +vector_negated(const First& first) +{ + typedef typename First::value_type first_value_type; + return VectorUnary >(first); +} + +template +class VectorCross +{ + First first; + Second second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorCross(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + value_type eval(unsigned int i) const + { + return first.eval((i+1)%3) * second.eval((i+2)%3) - first.eval((i+2)%3) * second.eval((i+1)%3); + } +}; + +template +inline VectorCross +vector_cross(const First& first, const Second& second) +{ + return VectorCross(first, second); +} + + +template +class VectorDot +{ + First first; + Second second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorDot(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + + template + struct eval_dot + { + static value_type apply(const First& first, const Second& second) + { + return static_cast( + first.eval(Index::value) * second.eval(Index::value) + + eval_dot< ct_int >::apply(first, second) + ); + } + }; + + template<> + struct eval_dot< ct_int<0> > + { + static value_type apply(const First& first, const Second& second) + { + return first.eval(0) * second.eval(0); + } + }; + + value_type eval() const + { + return eval_dot< ct_int >::apply(first, second); + } +}; + + +template +inline VectorDot vector_dot(const First& first, const Second& second) +{ + return VectorDot(first, second); +} + +template +class VectorLengthSquared +{ + First first; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorLengthSquared(const First& first_) + : first(first_) + { + } + + static value_type squared(const value_type& value) + { + return value * value; + } + + template + struct eval_squared + { + static value_type apply(const First& first) + { + return static_cast( + squared(first.eval(Index::value)) + + eval_squared< ct_int >::apply(first) + ); + } + }; + + template<> + struct eval_squared< ct_int<0> > + { + static value_type apply(const First& first) + { + return squared(first.eval(0)); + } + }; + + value_type eval() const + { + return eval_squared< ct_int >::apply(first); + } +}; + +template +inline VectorLengthSquared vector_length_squared(const First& first) +{ + return VectorLengthSquared(first); +} + +template +inline SquareRoot< VectorLengthSquared > vector_length(const First& first) +{ + return float_square_root(vector_length_squared(first)); +} + +#if 1 +template +inline VectorScalar< + Multiplied, + First, + // multiple evaulations of subexpression + ScalarDivided< + Literal, + SquareRoot< + VectorLengthSquared + > + > +> vector_normalised(const First& first) +{ + typedef typename First::value_type first_value_type; + return vector_scaled(first, float_reciprocal(vector_length(first))); +} +#else +template +inline VectorScalar< + Multiplied, + First, + // single evaluation of subexpression + Literal +> +vector_normalised(const First& first) +{ + typedef typename First::value_type first_value_type; + return vector_scaled(first, float_literal(static_cast(first_value_type(1.0) / vector_length(first).eval()))); +} +#endif + + +class Matrix4Literal +{ + const Matrix4 m_value; +public: + typedef float value_type; + typedef ct_int<4> dimension0; + typedef ct_int<4> dimension1; + + Matrix4Literal(const Matrix4& value) + : m_value(value) + { + } + const value_type& eval(unsigned int r, unsigned int c) const + { + return m_value[r*4+c]; + } +}; + +inline Matrix4Literal matrix4_literal(const Matrix4& value) +{ + return Matrix4Literal(value); +} + +class Matrix4Identity +{ + const Matrix4& m_value; +public: + typedef float value_type; + typedef ct_int<4> dimension0; + typedef ct_int<4> dimension1; + + Matrix4Identity(const Matrix4& value) + : m_value(value) + { + } + const value_type& eval(unsigned int r, unsigned int c) const + { + return m_value[r*4+c]; + } +}; + +inline Matrix4Identity matrix4_identity(const Matrix4& value) +{ + return Matrix4Identity(value); +} + +template +inline Matrix4 matrix4_for_expression(const Expression& expression) +{ + return Matrix4( + expression.eval(0, 0), expression.eval(0, 1), expression.eval(0, 2), expression.eval(0, 3), + expression.eval(1, 0), expression.eval(1, 1), expression.eval(1, 2), expression.eval(1, 3), + expression.eval(2, 0), expression.eval(2, 1), expression.eval(2, 2), expression.eval(2, 3), + expression.eval(3, 0), expression.eval(3, 1), expression.eval(3, 2), expression.eval(3, 3) + ); +} + +template +inline Matrix4 matrix4_affine_for_expression(const Expression& expression) +{ + return Matrix4( + expression.eval(0, 0), expression.eval(0, 1), expression.eval(0, 2), 0, + expression.eval(1, 0), expression.eval(1, 1), expression.eval(1, 2), 0, + expression.eval(2, 0), expression.eval(2, 1), expression.eval(2, 2), 0, + expression.eval(3, 0), expression.eval(3, 1), expression.eval(3, 2), 1 + ); +} + + +template +class PointMultiplied +{ + const First& first; + const Second& second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + PointMultiplied(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + value_type eval(unsigned int i) const + { + return static_cast(second.eval(0, i) * first.eval(0) + + second.eval(1, i) * first.eval(1) + + second.eval(2, i) * first.eval(2) + + second.eval(3, i)); + } +}; + +template +inline PointMultiplied point_multiplied(const First& point, const Second& matrix) +{ + return PointMultiplied(point, matrix); +} + +template +class Matrix4Multiplied +{ + const First& first; + const Second& second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension0 dimension0; + typedef typename First::dimension1 dimension1; + + Matrix4Multiplied(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + + value_type eval(unsigned int r, unsigned int c) const + { + return static_cast( + second.eval(r, 0) * first.eval(0, c) + + second.eval(r, 1) * first.eval(1, c) + + second.eval(r, 2) * first.eval(2, c) + + second.eval(r, 3) * first.eval(3, c) + ); + } +}; + +template +inline Matrix4Multiplied matrix4_multiplied(const First& first, const Second& second) +{ + return Matrix4Multiplied(first, second); +} + +template +class MatrixTransposed +{ + const First& first; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension0 dimension0; + typedef typename First::dimension1 dimension1; + + MatrixTransposed(const First& first_) + : first(first_) + { + } + + value_type eval(unsigned int r, unsigned int c) const + { + return first.eval(c, r); + } +}; + +template +inline MatrixTransposed matrix_transposed(const First& first) +{ + return MatrixTransposed(first); +} + +#endif diff --git a/tools/urt/libs/math/frustum.cpp b/tools/urt/libs/math/frustum.cpp new file mode 100644 index 00000000..00c2b6e7 --- /dev/null +++ b/tools/urt/libs/math/frustum.cpp @@ -0,0 +1,3 @@ + +#include "frustum.h" + diff --git a/tools/urt/libs/math/frustum.h b/tools/urt/libs/math/frustum.h new file mode 100644 index 00000000..3d5b023d --- /dev/null +++ b/tools/urt/libs/math/frustum.h @@ -0,0 +1,609 @@ + +#if !defined(INCLUDED_MATH_FRUSTUM_H) +#define INCLUDED_MATH_FRUSTUM_H + +/// \file +/// \brief View-frustum data types and related operations. + +#include "generic/enumeration.h" +#include "math/matrix.h" +#include "math/plane.h" +#include "math/aabb.h" +#include "math/line.h" + +inline Matrix4 matrix4_frustum(float left, float right, float bottom, float top, float nearval, float farval) +{ + return Matrix4( + static_cast( (2*nearval) / (right-left) ), + 0, + 0, + 0, + 0, + static_cast( (2*nearval) / (top-bottom) ), + 0, + 0, + static_cast( (right+left) / (right-left) ), + static_cast( (top+bottom) / (top-bottom) ), + static_cast( -(farval+nearval) / (farval-nearval) ), + -1, + 0, + 0, + static_cast( -(2*farval*nearval) / (farval-nearval) ), + 0 + ); +} + + + +typedef unsigned char ClipResult; +const ClipResult c_CLIP_PASS = 0x00; // 000000 +const ClipResult c_CLIP_LT_X = 0x01; // 000001 +const ClipResult c_CLIP_GT_X = 0x02; // 000010 +const ClipResult c_CLIP_LT_Y = 0x04; // 000100 +const ClipResult c_CLIP_GT_Y = 0x08; // 001000 +const ClipResult c_CLIP_LT_Z = 0x10; // 010000 +const ClipResult c_CLIP_GT_Z = 0x20; // 100000 +const ClipResult c_CLIP_FAIL = 0x3F; // 111111 + +template +class Vector4ClipLT +{ +public: + static bool compare(const Vector4& self) + { + return self[Index::value] < self[3]; + } + static double scale(const Vector4& self, const Vector4& other) + { + return (self[Index::value] - self[3]) / (other[3] - other[Index::value]); + } +}; + +template +class Vector4ClipGT +{ +public: + static bool compare(const Vector4& self) + { + return self[Index::value] > -self[3]; + } + static double scale(const Vector4& self, const Vector4& other) + { + return (self[Index::value] + self[3]) / (-other[3] - other[Index::value]); + } +}; + +template +class Vector4ClipPolygon +{ +public: + typedef Vector4* iterator; + typedef const Vector4* const_iterator; + + static std::size_t apply(const_iterator first, const_iterator last, iterator out) + { + const_iterator next = first, i = last - 1; + iterator tmp(out); + bool b0 = ClipPlane::compare(*i); + while(next != last) + { + bool b1 = ClipPlane::compare(*next); + if(b0 ^ b1) + { + *out = vector4_subtracted(*next, *i); + + double scale = ClipPlane::scale(*i, *out); + + (*out)[0] = static_cast((*i)[0] + scale*((*out)[0])); + (*out)[1] = static_cast((*i)[1] + scale*((*out)[1])); + (*out)[2] = static_cast((*i)[2] + scale*((*out)[2])); + (*out)[3] = static_cast((*i)[3] + scale*((*out)[3])); + + ++out; + } + + if(b1) + { + *out = *next; + ++out; + } + + i = next; + ++next; + b0 = b1; + } + + return out - tmp; + } +}; + +#define CLIP_X_LT_W(p) (Vector4ClipLT< ct_int<0> >::compare(p)) +#define CLIP_X_GT_W(p) (Vector4ClipGT< ct_int<0> >::compare(p)) +#define CLIP_Y_LT_W(p) (Vector4ClipLT< ct_int<1> >::compare(p)) +#define CLIP_Y_GT_W(p) (Vector4ClipGT< ct_int<1> >::compare(p)) +#define CLIP_Z_LT_W(p) (Vector4ClipLT< ct_int<2> >::compare(p)) +#define CLIP_Z_GT_W(p) (Vector4ClipGT< ct_int<2> >::compare(p)) + +inline ClipResult homogenous_clip_point(const Vector4& clipped) +{ + ClipResult result = c_CLIP_FAIL; + if(CLIP_X_LT_W(clipped)) result &= ~c_CLIP_LT_X; // X < W + if(CLIP_X_GT_W(clipped)) result &= ~c_CLIP_GT_X; // X > -W + if(CLIP_Y_LT_W(clipped)) result &= ~c_CLIP_LT_Y; // Y < W + if(CLIP_Y_GT_W(clipped)) result &= ~c_CLIP_GT_Y; // Y > -W + if(CLIP_Z_LT_W(clipped)) result &= ~c_CLIP_LT_Z; // Z < W + if(CLIP_Z_GT_W(clipped)) result &= ~c_CLIP_GT_Z; // Z > -W + return result; +} + +/// \brief Clips \p point by canonical matrix \p self. +/// Stores the result in \p clipped. +/// Returns a bitmask indicating which clip-planes the point was outside. +inline ClipResult matrix4_clip_point(const Matrix4& self, const Vector3& point, Vector4& clipped) +{ + clipped[0] = point[0]; + clipped[1] = point[1]; + clipped[2] = point[2]; + clipped[3] = 1; + matrix4_transform_vector4(self, clipped); + return homogenous_clip_point(clipped); +} + + +inline std::size_t homogenous_clip_triangle(Vector4 clipped[9]) +{ + Vector4 buffer[9]; + std::size_t count = 3; + count = Vector4ClipPolygon< Vector4ClipLT< ct_int<0> > >::apply(clipped, clipped + count, buffer); + count = Vector4ClipPolygon< Vector4ClipGT< ct_int<0> > >::apply(buffer, buffer + count, clipped); + count = Vector4ClipPolygon< Vector4ClipLT< ct_int<1> > >::apply(clipped, clipped + count, buffer); + count = Vector4ClipPolygon< Vector4ClipGT< ct_int<1> > >::apply(buffer, buffer + count, clipped); + count = Vector4ClipPolygon< Vector4ClipLT< ct_int<2> > >::apply(clipped, clipped + count, buffer); + return Vector4ClipPolygon< Vector4ClipGT< ct_int<2> > >::apply(buffer, buffer + count, clipped); +} + +/// \brief Transforms and clips the triangle formed by \p p0, \p p1, \p p2 by the canonical matrix \p self. +/// Stores the resulting polygon in \p clipped. +/// Returns the number of points in the resulting polygon. +inline std::size_t matrix4_clip_triangle(const Matrix4& self, const Vector3& p0, const Vector3& p1, const Vector3& p2, Vector4 clipped[9]) +{ + clipped[0][0] = p0[0]; + clipped[0][1] = p0[1]; + clipped[0][2] = p0[2]; + clipped[0][3] = 1; + clipped[1][0] = p1[0]; + clipped[1][1] = p1[1]; + clipped[1][2] = p1[2]; + clipped[1][3] = 1; + clipped[2][0] = p2[0]; + clipped[2][1] = p2[1]; + clipped[2][2] = p2[2]; + clipped[2][3] = 1; + + matrix4_transform_vector4(self, clipped[0]); + matrix4_transform_vector4(self, clipped[1]); + matrix4_transform_vector4(self, clipped[2]); + + return homogenous_clip_triangle(clipped); +} + +inline std::size_t homogenous_clip_line(Vector4 clipped[2]) +{ + const Vector4& p0 = clipped[0]; + const Vector4& p1 = clipped[1]; + + // early out + { + ClipResult mask0 = homogenous_clip_point(clipped[0]); + ClipResult mask1 = homogenous_clip_point(clipped[1]); + + if((mask0 | mask1) == c_CLIP_PASS) // both points passed all planes + return 2; + + if(mask0 & mask1) // both points failed any one plane + return 0; + } + + { + const bool index = CLIP_X_LT_W(p0); + if(index ^ CLIP_X_LT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[0] - p0[3]) / (clip[3] - clip[0]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_X_GT_W(p0); + if(index ^ CLIP_X_GT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[0] + p0[3]) / (-clip[3] - clip[0]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_Y_LT_W(p0); + if(index ^ CLIP_Y_LT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[1] - p0[3]) / (clip[3] - clip[1]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_Y_GT_W(p0); + if(index ^ CLIP_Y_GT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[1] + p0[3]) / (-clip[3] - clip[1]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_Z_LT_W(p0); + if(index ^ CLIP_Z_LT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[2] - p0[3]) / (clip[3] - clip[2]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_Z_GT_W(p0); + if(index ^ CLIP_Z_GT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[2] + p0[3]) / (-clip[3] - clip[2]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + return 2; +} + +/// \brief Transforms and clips the line formed by \p p0, \p p1 by the canonical matrix \p self. +/// Stores the resulting line in \p clipped. +/// Returns the number of points in the resulting line. +inline std::size_t matrix4_clip_line(const Matrix4& self, const Vector3& p0, const Vector3& p1, Vector4 clipped[2]) +{ + clipped[0][0] = p0[0]; + clipped[0][1] = p0[1]; + clipped[0][2] = p0[2]; + clipped[0][3] = 1; + clipped[1][0] = p1[0]; + clipped[1][1] = p1[1]; + clipped[1][2] = p1[2]; + clipped[1][3] = 1; + + matrix4_transform_vector4(self, clipped[0]); + matrix4_transform_vector4(self, clipped[1]); + + return homogenous_clip_line(clipped); +} + + + + +struct Frustum +{ + Plane3 right, left, bottom, top, back, front; + + Frustum() + { + } + Frustum(const Plane3& _right, + const Plane3& _left, + const Plane3& _bottom, + const Plane3& _top, + const Plane3& _back, + const Plane3& _front) + : right(_right), left(_left), bottom(_bottom), top(_top), back(_back), front(_front) + { + } +}; + +inline Frustum frustum_transformed(const Frustum& frustum, const Matrix4& transform) +{ + return Frustum( + plane3_transformed(frustum.right, transform), + plane3_transformed(frustum.left, transform), + plane3_transformed(frustum.bottom, transform), + plane3_transformed(frustum.top, transform), + plane3_transformed(frustum.back, transform), + plane3_transformed(frustum.front, transform) + ); +} + +inline Frustum frustum_inverse_transformed(const Frustum& frustum, const Matrix4& transform) +{ + return Frustum( + plane3_inverse_transformed(frustum.right, transform), + plane3_inverse_transformed(frustum.left, transform), + plane3_inverse_transformed(frustum.bottom, transform), + plane3_inverse_transformed(frustum.top, transform), + plane3_inverse_transformed(frustum.back, transform), + plane3_inverse_transformed(frustum.front, transform) + ); +} + +inline bool viewproj_test_point(const Matrix4& viewproj, const Vector3& point) +{ + Vector4 hpoint(matrix4_transformed_vector4(viewproj, Vector4(point, 1.0f))); + if(fabs(hpoint[0]) < fabs(hpoint[3]) + && fabs(hpoint[1]) < fabs(hpoint[3]) + && fabs(hpoint[2]) < fabs(hpoint[3])) + return true; + return false; +} + +inline bool viewproj_test_transformed_point(const Matrix4& viewproj, const Vector3& point, const Matrix4& localToWorld) +{ + return viewproj_test_point(viewproj, matrix4_transformed_point(localToWorld, point)); +} + +inline Frustum frustum_from_viewproj(const Matrix4& viewproj) +{ + return Frustum + ( + plane3_normalised(Plane3(viewproj[ 3] - viewproj[ 0], viewproj[ 7] - viewproj[ 4], viewproj[11] - viewproj[ 8], viewproj[15] - viewproj[12])), + plane3_normalised(Plane3(viewproj[ 3] + viewproj[ 0], viewproj[ 7] + viewproj[ 4], viewproj[11] + viewproj[ 8], viewproj[15] + viewproj[12])), + plane3_normalised(Plane3(viewproj[ 3] + viewproj[ 1], viewproj[ 7] + viewproj[ 5], viewproj[11] + viewproj[ 9], viewproj[15] + viewproj[13])), + plane3_normalised(Plane3(viewproj[ 3] - viewproj[ 1], viewproj[ 7] - viewproj[ 5], viewproj[11] - viewproj[ 9], viewproj[15] - viewproj[13])), + plane3_normalised(Plane3(viewproj[ 3] - viewproj[ 2], viewproj[ 7] - viewproj[ 6], viewproj[11] - viewproj[10], viewproj[15] - viewproj[14])), + plane3_normalised(Plane3(viewproj[ 3] + viewproj[ 2], viewproj[ 7] + viewproj[ 6], viewproj[11] + viewproj[10], viewproj[15] + viewproj[14])) + ); +} + +struct VolumeIntersection +{ + enum Value + { + OUTSIDE, + INSIDE, + PARTIAL + }; +}; + +typedef EnumeratedValue VolumeIntersectionValue; + +const VolumeIntersectionValue c_volumeOutside(VolumeIntersectionValue::OUTSIDE); +const VolumeIntersectionValue c_volumeInside(VolumeIntersectionValue::INSIDE); +const VolumeIntersectionValue c_volumePartial(VolumeIntersectionValue::PARTIAL); + +inline VolumeIntersectionValue frustum_test_aabb(const Frustum& frustum, const AABB& aabb) +{ + VolumeIntersectionValue result = c_volumeInside; + + switch(aabb_classify_plane(aabb, frustum.right)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.left)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.bottom)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.top)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.back)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.front)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + return result; +} + +inline double plane_distance_to_point(const Plane3& plane, const Vector3& point) +{ + return vector3_dot(plane.normal(), point) + plane.d; +} + +inline double plane_distance_to_oriented_extents(const Plane3& plane, const Vector3& extents, const Matrix4& orientation) +{ + return fabs(extents[0] * vector3_dot(plane.normal(), vector4_to_vector3(orientation.x()))) + + fabs(extents[1] * vector3_dot(plane.normal(), vector4_to_vector3(orientation.y()))) + + fabs(extents[2] * vector3_dot(plane.normal(), vector4_to_vector3(orientation.z()))); +} + +/// \brief Return false if \p aabb with \p orientation is partially or completely outside \p plane. +inline bool plane_contains_oriented_aabb(const Plane3& plane, const AABB& aabb, const Matrix4& orientation) +{ + double dot = plane_distance_to_point(plane, aabb.origin); + return !(dot > 0 || -dot < plane_distance_to_oriented_extents(plane, aabb.extents, orientation)); +} + +inline VolumeIntersectionValue frustum_intersects_transformed_aabb(const Frustum& frustum, const AABB& aabb, const Matrix4& localToWorld) +{ + AABB aabb_world(aabb); + matrix4_transform_point(localToWorld, aabb_world.origin); + + if(plane_contains_oriented_aabb(frustum.right, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.left, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.bottom, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.top, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.back, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.front, aabb_world, localToWorld)) + return c_volumeOutside; + return c_volumeInside; +} + +inline bool plane3_test_point(const Plane3& plane, const Vector3& point) +{ + return vector3_dot(point, plane.normal()) + plane.dist() <= 0; +} + +inline bool plane3_test_line(const Plane3& plane, const Segment& segment) +{ + return segment_classify_plane(segment, plane) == 2; +} + +inline bool frustum_test_point(const Frustum& frustum, const Vector3& point) +{ + return !plane3_test_point(frustum.right, point) + && !plane3_test_point(frustum.left, point) + && !plane3_test_point(frustum.bottom, point) + && !plane3_test_point(frustum.top, point) + && !plane3_test_point(frustum.back, point) + && !plane3_test_point(frustum.front, point); +} + +inline bool frustum_test_line(const Frustum& frustum, const Segment& segment) +{ + return !plane3_test_line(frustum.right, segment) + && !plane3_test_line(frustum.left, segment) + && !plane3_test_line(frustum.bottom, segment) + && !plane3_test_line(frustum.top, segment) + && !plane3_test_line(frustum.back, segment) + && !plane3_test_line(frustum.front, segment); +} + +inline bool viewer_test_plane(const Vector4& viewer, const Plane3& plane) +{ + return ((plane.a * viewer[0]) + + (plane.b * viewer[1]) + + (plane.c * viewer[2]) + + (plane.d * viewer[3])) > 0; +} + +inline Vector3 triangle_cross(const Vector3& p0, const Vector3& p1, const Vector3& p2) +{ + return vector3_cross(vector3_subtracted(p1, p0), vector3_subtracted(p1, p2)); +} + +inline bool viewer_test_triangle(const Vector4& viewer, const Vector3& p0, const Vector3& p1, const Vector3& p2) +{ + Vector3 cross(triangle_cross(p0, p1, p2)); + return ((viewer[0] * cross[0]) + + (viewer[1] * cross[1]) + + (viewer[2] * cross[2]) + + (viewer[3] * 0)) > 0; +} + +inline Vector4 viewer_from_transformed_viewer(const Vector4& viewer, const Matrix4& transform) +{ + if(viewer[3] == 0) + { + return Vector4(matrix4_transformed_direction(transform, vector4_to_vector3(viewer)), 0); + } + else + { + return Vector4(matrix4_transformed_point(transform, vector4_to_vector3(viewer)), viewer[3]); + } +} + +inline bool viewer_test_transformed_plane(const Vector4& viewer, const Plane3& plane, const Matrix4& localToWorld) +{ +#if 0 + return viewer_test_plane(viewer_from_transformed_viewer(viewer, matrix4_affine_inverse(localToWorld)), plane); +#else + return viewer_test_plane(viewer, plane3_transformed(plane, localToWorld)); +#endif +} + +inline Vector4 viewer_from_viewproj(const Matrix4& viewproj) +{ + // get viewer pos in object coords + Vector4 viewer(matrix4_transformed_vector4(matrix4_full_inverse(viewproj), Vector4(0, 0, -1, 0))); + if(viewer[3] != 0) // non-affine matrix + { + viewer[0] /= viewer[3]; + viewer[1] /= viewer[3]; + viewer[2] /= viewer[3]; + viewer[3] /= viewer[3]; + } + return viewer; +} + +#endif diff --git a/tools/urt/libs/math/line.cpp b/tools/urt/libs/math/line.cpp new file mode 100644 index 00000000..5ab6459b --- /dev/null +++ b/tools/urt/libs/math/line.cpp @@ -0,0 +1,3 @@ + +#include "line.h" + diff --git a/tools/urt/libs/math/line.h b/tools/urt/libs/math/line.h new file mode 100644 index 00000000..334f64c5 --- /dev/null +++ b/tools/urt/libs/math/line.h @@ -0,0 +1,131 @@ + +#if !defined(INCLUDED_MATH_LINE_H) +#define INCLUDED_MATH_LINE_H + +/// \file +/// \brief Line data types and related operations. + +#include "math/vector.h" +#include "math/plane.h" + +/// \brief A line segment defined by a start point and and end point. +class Line +{ +public: + Vector3 start, end; + + Line() + { + } + Line(const Vector3& start_, const Vector3& end_) : start(start_), end(end_) + { + } +}; + +inline Vector3 line_closest_point(const Line& line, const Vector3& point) +{ + Vector3 v = line.end - line.start; + Vector3 w = point - line.start; + + double c1 = vector3_dot(w,v); + if ( c1 <= 0 ) + return line.start; + + double c2 = vector3_dot(v,v); + if ( c2 <= c1 ) + return line.end; + + return Vector3(line.start + v * (c1 / c2)); +} + + +class Segment +{ +public: + Vector3 origin, extents; + + Segment() + { + } + Segment(const Vector3& origin_, const Vector3& extents_) : + origin(origin_), extents(extents_) + { + } +}; + + +inline Segment segment_for_startend(const Vector3& start, const Vector3& end) +{ + Segment segment; + segment.origin = vector3_mid(start, end); + segment.extents = vector3_subtracted(end, segment.origin); + return segment; +} + +inline unsigned int segment_classify_plane(const Segment& segment, const Plane3& plane) +{ + double distance_origin = vector3_dot(plane.normal(), segment.origin) + plane.dist(); + + if (fabs(distance_origin) < fabs(vector3_dot(plane.normal(), segment.extents))) + { + return 1; // partially inside + } + else if (distance_origin < 0) + { + return 2; // totally inside + } + return 0; // totally outside +} + + +class Ray +{ +public: + Vector3 origin, direction; + + Ray() + { + } + Ray(const Vector3& origin_, const Vector3& direction_) : + origin(origin_), direction(direction_) + { + } +}; + +inline Ray ray_for_points(const Vector3& origin, const Vector3& p2) +{ + return Ray(origin, vector3_normalised(vector3_subtracted(p2, origin))); +} + +inline void ray_transform(Ray& ray, const Matrix4& matrix) +{ + matrix4_transform_point(matrix, ray.origin); + matrix4_transform_direction(matrix, ray.direction); +} + +// closest-point-on-line +inline double ray_squared_distance_to_point(const Ray& ray, const Vector3& point) +{ + return vector3_length_squared( + vector3_subtracted( + point, + vector3_added( + ray.origin, + vector3_scaled( + ray.direction, + vector3_dot( + vector3_subtracted(point, ray.origin), + ray.direction + ) + ) + ) + ) + ); +} + +inline double ray_distance_to_plane(const Ray& ray, const Plane3& plane) +{ + return -(vector3_dot(plane.normal(), ray.origin) - plane.dist()) / vector3_dot(ray.direction, plane.normal()); +} + +#endif diff --git a/tools/urt/libs/math/matrix.cpp b/tools/urt/libs/math/matrix.cpp new file mode 100644 index 00000000..0a63d78f --- /dev/null +++ b/tools/urt/libs/math/matrix.cpp @@ -0,0 +1,3 @@ + +#include "matrix.h" + diff --git a/tools/urt/libs/math/matrix.h b/tools/urt/libs/math/matrix.h new file mode 100644 index 00000000..429aa040 --- /dev/null +++ b/tools/urt/libs/math/matrix.h @@ -0,0 +1,1290 @@ + +#if !defined(INCLUDED_MATH_MATRIX_H) +#define INCLUDED_MATH_MATRIX_H + +/// \file +/// \brief Matrix data types and related operations. + +#include "math/vector.h" + +/// \brief A 4x4 matrix stored in single-precision floating-point. +class Matrix4 +{ + float m_elements[16]; +public: + + Matrix4() + { + } + Matrix4(float xx_, float xy_, float xz_, float xw_, + float yx_, float yy_, float yz_, float yw_, + float zx_, float zy_, float zz_, float zw_, + float tx_, float ty_, float tz_, float tw_) + { + xx() = xx_; + xy() = xy_; + xz() = xz_; + xw() = xw_; + yx() = yx_; + yy() = yy_; + yz() = yz_; + yw() = yw_; + zx() = zx_; + zy() = zy_; + zz() = zz_; + zw() = zw_; + tx() = tx_; + ty() = ty_; + tz() = tz_; + tw() = tw_; + } + + float& xx() + { + return m_elements[0]; + } + const float& xx() const + { + return m_elements[0]; + } + float& xy() + { + return m_elements[1]; + } + const float& xy() const + { + return m_elements[1]; + } + float& xz() + { + return m_elements[2]; + } + const float& xz() const + { + return m_elements[2]; + } + float& xw() + { + return m_elements[3]; + } + const float& xw() const + { + return m_elements[3]; + } + float& yx() + { + return m_elements[4]; + } + const float& yx() const + { + return m_elements[4]; + } + float& yy() + { + return m_elements[5]; + } + const float& yy() const + { + return m_elements[5]; + } + float& yz() + { + return m_elements[6]; + } + const float& yz() const + { + return m_elements[6]; + } + float& yw() + { + return m_elements[7]; + } + const float& yw() const + { + return m_elements[7]; + } + float& zx() + { + return m_elements[8]; + } + const float& zx() const + { + return m_elements[8]; + } + float& zy() + { + return m_elements[9]; + } + const float& zy() const + { + return m_elements[9]; + } + float& zz() + { + return m_elements[10]; + } + const float& zz() const + { + return m_elements[10]; + } + float& zw() + { + return m_elements[11]; + } + const float& zw() const + { + return m_elements[11]; + } + float& tx() + { + return m_elements[12]; + } + const float& tx() const + { + return m_elements[12]; + } + float& ty() + { + return m_elements[13]; + } + const float& ty() const + { + return m_elements[13]; + } + float& tz() + { + return m_elements[14]; + } + const float& tz() const + { + return m_elements[14]; + } + float& tw() + { + return m_elements[15]; + } + const float& tw() const + { + return m_elements[15]; + } + + Vector4& x() + { + return reinterpret_cast(xx()); + } + const Vector4& x() const + { + return reinterpret_cast(xx()); + } + Vector4& y() + { + return reinterpret_cast(yx()); + } + const Vector4& y() const + { + return reinterpret_cast(yx()); + } + Vector4& z() + { + return reinterpret_cast(zx()); + } + const Vector4& z() const + { + return reinterpret_cast(zx()); + } + Vector4& t() + { + return reinterpret_cast(tx()); + } + const Vector4& t() const + { + return reinterpret_cast(tx()); + } + + const float& index(std::size_t i) const + { + return m_elements[i]; + } + float& index(std::size_t i) + { + return m_elements[i]; + } + const float& operator[](std::size_t i) const + { + return m_elements[i]; + } + float& operator[](std::size_t i) + { + return m_elements[i]; + } + const float& index(std::size_t r, std::size_t c) const + { + return m_elements[(r << 2) + c]; + } + float& index(std::size_t r, std::size_t c) + { + return m_elements[(r << 2) + c]; + } +}; + +/// \brief The 4x4 identity matrix. +const Matrix4 g_matrix4_identity( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 +); + + +/// \brief Returns true if \p self and \p other are exactly element-wise equal. +inline bool operator==(const Matrix4& self, const Matrix4& other) +{ + return self.xx() == other.xx() && self.xy() == other.xy() && self.xz() == other.xz() && self.xw() == other.xw() + && self.yx() == other.yx() && self.yy() == other.yy() && self.yz() == other.yz() && self.yw() == other.yw() + && self.zx() == other.zx() && self.zy() == other.zy() && self.zz() == other.zz() && self.zw() == other.zw() + && self.tx() == other.tx() && self.ty() == other.ty() && self.tz() == other.tz() && self.tw() == other.tw(); +} + +/// \brief Returns true if \p self and \p other are exactly element-wise equal. +inline bool matrix4_equal(const Matrix4& self, const Matrix4& other) +{ + return self == other; +} + +/// \brief Returns true if \p self and \p other are element-wise equal within \p epsilon. +inline bool matrix4_equal_epsilon(const Matrix4& self, const Matrix4& other, float epsilon) +{ + return float_equal_epsilon(self.xx(), other.xx(), epsilon) + && float_equal_epsilon(self.xy(), other.xy(), epsilon) + && float_equal_epsilon(self.xz(), other.xz(), epsilon) + && float_equal_epsilon(self.xw(), other.xw(), epsilon) + && float_equal_epsilon(self.yx(), other.yx(), epsilon) + && float_equal_epsilon(self.yy(), other.yy(), epsilon) + && float_equal_epsilon(self.yz(), other.yz(), epsilon) + && float_equal_epsilon(self.yw(), other.yw(), epsilon) + && float_equal_epsilon(self.zx(), other.zx(), epsilon) + && float_equal_epsilon(self.zy(), other.zy(), epsilon) + && float_equal_epsilon(self.zz(), other.zz(), epsilon) + && float_equal_epsilon(self.zw(), other.zw(), epsilon) + && float_equal_epsilon(self.tx(), other.tx(), epsilon) + && float_equal_epsilon(self.ty(), other.ty(), epsilon) + && float_equal_epsilon(self.tz(), other.tz(), epsilon) + && float_equal_epsilon(self.tw(), other.tw(), epsilon); +} + +/// \brief Returns true if \p self and \p other are exactly element-wise equal. +/// \p self and \p other must be affine. +inline bool matrix4_affine_equal(const Matrix4& self, const Matrix4& other) +{ + return self[0] == other[0] + && self[1] == other[1] + && self[2] == other[2] + && self[4] == other[4] + && self[5] == other[5] + && self[6] == other[6] + && self[8] == other[8] + && self[9] == other[9] + && self[10] == other[10] + && self[12] == other[12] + && self[13] == other[13] + && self[14] == other[14]; +} + +enum Matrix4Handedness +{ + MATRIX4_RIGHTHANDED = 0, + MATRIX4_LEFTHANDED = 1, +}; + +/// \brief Returns MATRIX4_RIGHTHANDED if \p self is right-handed, else returns MATRIX4_LEFTHANDED. +inline Matrix4Handedness matrix4_handedness(const Matrix4& self) +{ + return ( + vector3_dot( + vector3_cross(vector4_to_vector3(self.x()), vector4_to_vector3(self.y())), + vector4_to_vector3(self.z()) + ) + < 0.0 + ) ? MATRIX4_LEFTHANDED : MATRIX4_RIGHTHANDED; +} + + + + + +/// \brief Returns \p self post-multiplied by \p other. +inline Matrix4 matrix4_multiplied_by_matrix4(const Matrix4& self, const Matrix4& other) +{ + return Matrix4( + other[0] * self[0] + other[1] * self[4] + other[2] * self[8] + other[3] * self[12], + other[0] * self[1] + other[1] * self[5] + other[2] * self[9] + other[3] * self[13], + other[0] * self[2] + other[1] * self[6] + other[2] * self[10]+ other[3] * self[14], + other[0] * self[3] + other[1] * self[7] + other[2] * self[11]+ other[3] * self[15], + other[4] * self[0] + other[5] * self[4] + other[6] * self[8] + other[7] * self[12], + other[4] * self[1] + other[5] * self[5] + other[6] * self[9] + other[7] * self[13], + other[4] * self[2] + other[5] * self[6] + other[6] * self[10]+ other[7] * self[14], + other[4] * self[3] + other[5] * self[7] + other[6] * self[11]+ other[7] * self[15], + other[8] * self[0] + other[9] * self[4] + other[10]* self[8] + other[11]* self[12], + other[8] * self[1] + other[9] * self[5] + other[10]* self[9] + other[11]* self[13], + other[8] * self[2] + other[9] * self[6] + other[10]* self[10]+ other[11]* self[14], + other[8] * self[3] + other[9] * self[7] + other[10]* self[11]+ other[11]* self[15], + other[12]* self[0] + other[13]* self[4] + other[14]* self[8] + other[15]* self[12], + other[12]* self[1] + other[13]* self[5] + other[14]* self[9] + other[15]* self[13], + other[12]* self[2] + other[13]* self[6] + other[14]* self[10]+ other[15]* self[14], + other[12]* self[3] + other[13]* self[7] + other[14]* self[11]+ other[15]* self[15] + ); +} + +/// \brief Post-multiplies \p self by \p other in-place. +inline void matrix4_multiply_by_matrix4(Matrix4& self, const Matrix4& other) +{ + self = matrix4_multiplied_by_matrix4(self, other); +} + + +/// \brief Returns \p self pre-multiplied by \p other. +inline Matrix4 matrix4_premultiplied_by_matrix4(const Matrix4& self, const Matrix4& other) +{ +#if 1 + return matrix4_multiplied_by_matrix4(other, self); +#else + return Matrix4( + self[0] * other[0] + self[1] * other[4] + self[2] * other[8] + self[3] * other[12], + self[0] * other[1] + self[1] * other[5] + self[2] * other[9] + self[3] * other[13], + self[0] * other[2] + self[1] * other[6] + self[2] * other[10]+ self[3] * other[14], + self[0] * other[3] + self[1] * other[7] + self[2] * other[11]+ self[3] * other[15], + self[4] * other[0] + self[5] * other[4] + self[6] * other[8] + self[7] * other[12], + self[4] * other[1] + self[5] * other[5] + self[6] * other[9] + self[7] * other[13], + self[4] * other[2] + self[5] * other[6] + self[6] * other[10]+ self[7] * other[14], + self[4] * other[3] + self[5] * other[7] + self[6] * other[11]+ self[7] * other[15], + self[8] * other[0] + self[9] * other[4] + self[10]* other[8] + self[11]* other[12], + self[8] * other[1] + self[9] * other[5] + self[10]* other[9] + self[11]* other[13], + self[8] * other[2] + self[9] * other[6] + self[10]* other[10]+ self[11]* other[14], + self[8] * other[3] + self[9] * other[7] + self[10]* other[11]+ self[11]* other[15], + self[12]* other[0] + self[13]* other[4] + self[14]* other[8] + self[15]* other[12], + self[12]* other[1] + self[13]* other[5] + self[14]* other[9] + self[15]* other[13], + self[12]* other[2] + self[13]* other[6] + self[14]* other[10]+ self[15]* other[14], + self[12]* other[3] + self[13]* other[7] + self[14]* other[11]+ self[15]* other[15] + ); +#endif +} + +/// \brief Pre-multiplies \p self by \p other in-place. +inline void matrix4_premultiply_by_matrix4(Matrix4& self, const Matrix4& other) +{ + self = matrix4_premultiplied_by_matrix4(self, other); +} + +/// \brief returns true if \p transform is affine. +inline bool matrix4_is_affine(const Matrix4& transform) +{ + return transform[3] == 0 && transform[7] == 0 && transform[11] == 0 && transform[15] == 1; +} + +/// \brief Returns \p self post-multiplied by \p other. +/// \p self and \p other must be affine. +inline Matrix4 matrix4_affine_multiplied_by_matrix4(const Matrix4& self, const Matrix4& other) +{ + return Matrix4( + other[0] * self[0] + other[1] * self[4] + other[2] * self[8], + other[0] * self[1] + other[1] * self[5] + other[2] * self[9], + other[0] * self[2] + other[1] * self[6] + other[2] * self[10], + 0, + other[4] * self[0] + other[5] * self[4] + other[6] * self[8], + other[4] * self[1] + other[5] * self[5] + other[6] * self[9], + other[4] * self[2] + other[5] * self[6] + other[6] * self[10], + 0, + other[8] * self[0] + other[9] * self[4] + other[10]* self[8], + other[8] * self[1] + other[9] * self[5] + other[10]* self[9], + other[8] * self[2] + other[9] * self[6] + other[10]* self[10], + 0, + other[12]* self[0] + other[13]* self[4] + other[14]* self[8] + self[12], + other[12]* self[1] + other[13]* self[5] + other[14]* self[9] + self[13], + other[12]* self[2] + other[13]* self[6] + other[14]* self[10]+ self[14], + 1 + ); +} + +/// \brief Post-multiplies \p self by \p other in-place. +/// \p self and \p other must be affine. +inline void matrix4_affine_multiply_by_matrix4(Matrix4& self, const Matrix4& other) +{ + self = matrix4_affine_multiplied_by_matrix4(self, other); +} + +/// \brief Returns \p self pre-multiplied by \p other. +/// \p self and \p other must be affine. +inline Matrix4 matrix4_affine_premultiplied_by_matrix4(const Matrix4& self, const Matrix4& other) +{ +#if 1 + return matrix4_affine_multiplied_by_matrix4(other, self); +#else + return Matrix4( + self[0] * other[0] + self[1] * other[4] + self[2] * other[8], + self[0] * other[1] + self[1] * other[5] + self[2] * other[9], + self[0] * other[2] + self[1] * other[6] + self[2] * other[10], + 0, + self[4] * other[0] + self[5] * other[4] + self[6] * other[8], + self[4] * other[1] + self[5] * other[5] + self[6] * other[9], + self[4] * other[2] + self[5] * other[6] + self[6] * other[10], + 0, + self[8] * other[0] + self[9] * other[4] + self[10]* other[8], + self[8] * other[1] + self[9] * other[5] + self[10]* other[9], + self[8] * other[2] + self[9] * other[6] + self[10]* other[10], + 0, + self[12]* other[0] + self[13]* other[4] + self[14]* other[8] + other[12], + self[12]* other[1] + self[13]* other[5] + self[14]* other[9] + other[13], + self[12]* other[2] + self[13]* other[6] + self[14]* other[10]+ other[14], + 1 + ) + ); +#endif +} + +/// \brief Pre-multiplies \p self by \p other in-place. +/// \p self and \p other must be affine. +inline void matrix4_affine_premultiply_by_matrix4(Matrix4& self, const Matrix4& other) +{ + self = matrix4_affine_premultiplied_by_matrix4(self, other); +} + +/// \brief Returns \p point transformed by \p self. +template +inline BasicVector3 matrix4_transformed_point(const Matrix4& self, const BasicVector3& point) +{ + return BasicVector3( + static_cast(self[0] * point[0] + self[4] * point[1] + self[8] * point[2] + self[12]), + static_cast(self[1] * point[0] + self[5] * point[1] + self[9] * point[2] + self[13]), + static_cast(self[2] * point[0] + self[6] * point[1] + self[10] * point[2] + self[14]) + ); +} + +/// \brief Transforms \p point by \p self in-place. +template +inline void matrix4_transform_point(const Matrix4& self, BasicVector3& point) +{ + point = matrix4_transformed_point(self, point); +} + +/// \brief Returns \p vector4 transformed by \p self. +template +inline BasicVector3 matrix4_transformed_direction(const Matrix4& self, const BasicVector3& direction) +{ + return BasicVector3( + static_cast(self[0] * direction[0] + self[4] * direction[1] + self[8] * direction[2]), + static_cast(self[1] * direction[0] + self[5] * direction[1] + self[9] * direction[2]), + static_cast(self[2] * direction[0] + self[6] * direction[1] + self[10] * direction[2]) + ); +} + +/// \brief Transforms \p direction by \p self in-place. +template +inline void matrix4_transform_direction(const Matrix4& self, BasicVector3& normal) +{ + normal = matrix4_transformed_direction(self, normal); +} + +/// \brief Returns \p vector4 transformed by \p self. +inline Vector4 matrix4_transformed_vector4(const Matrix4& self, const Vector4& vector4) +{ + return Vector4( + self[0] * vector4[0] + self[4] * vector4[1] + self[8] * vector4[2] + self[12] * vector4[3], + self[1] * vector4[0] + self[5] * vector4[1] + self[9] * vector4[2] + self[13] * vector4[3], + self[2] * vector4[0] + self[6] * vector4[1] + self[10] * vector4[2] + self[14] * vector4[3], + self[3] * vector4[0] + self[7] * vector4[1] + self[11] * vector4[2] + self[15] * vector4[3] + ); +} + +/// \brief Transforms \p vector4 by \p self in-place. +inline void matrix4_transform_vector4(const Matrix4& self, Vector4& vector4) +{ + vector4 = matrix4_transformed_vector4(self, vector4); +} + + +/// \brief Transposes \p self in-place. +inline void matrix4_transpose(Matrix4& self) +{ + std::swap(self.xy(), self.yx()); + std::swap(self.xz(), self.zx()); + std::swap(self.xw(), self.tx()); + std::swap(self.yz(), self.zy()); + std::swap(self.yw(), self.ty()); + std::swap(self.zw(), self.tz()); +} + +/// \brief Returns \p self transposed. +inline Matrix4 matrix4_transposed(const Matrix4& self) +{ + return Matrix4( + self.xx(), + self.yx(), + self.zx(), + self.tx(), + self.xy(), + self.yy(), + self.zy(), + self.ty(), + self.xz(), + self.yz(), + self.zz(), + self.tz(), + self.xw(), + self.yw(), + self.zw(), + self.tw() + ); +} + + +/// \brief Inverts an affine transform in-place. +/// Adapted from Graphics Gems 2. +inline void matrix4_affine_invert(Matrix4& self) +{ + Matrix4 other(self); + + // determinant of rotation submatrix + float det + = other[0] * ( other[5]*other[10] - other[9]*other[6] ) + - other[1] * ( other[4]*other[10] - other[8]*other[6] ) + + other[2] * ( other[4]*other[9] - other[8]*other[5] ); + + // throw exception here if (det*det < 1e-25) + + // invert rotation submatrix + det = 1.0f / det; + self[0] = ( (other[5]*other[10]- other[6]*other[9] )*det); + self[1] = (- (other[1]*other[10]- other[2]*other[9] )*det); + self[2] = ( (other[1]*other[6] - other[2]*other[5] )*det); + self[4] = (- (other[4]*other[10]- other[6]*other[8] )*det); + self[5] = ( (other[0]*other[10]- other[2]*other[8] )*det); + self[6] = (- (other[0]*other[6] - other[2]*other[4] )*det); + self[8] = ( (other[4]*other[9] - other[5]*other[8] )*det); + self[9] = (- (other[0]*other[9] - other[1]*other[8] )*det); + self[10]= ( (other[0]*other[5] - other[1]*other[4] )*det); + + // multiply translation part by rotation + self[12] = - (other[12] * self[0] + + other[13] * self[4] + + other[14] * self[8]); + self[13] = - (other[12] * self[1] + + other[13] * self[5] + + other[14] * self[9]); + self[14] = - (other[12] * self[2] + + other[13] * self[6] + + other[14] * self[10]); +} + +/// \brief A compile-time-constant integer. +template +struct ct_int +{ + enum { value = Value }; +}; + +/// \brief A compile-time-constant row/column index into a 4x4 matrix. +template +class Matrix4Index +{ +public: + typedef ct_int r; + typedef ct_int c; + typedef ct_int<(r::value * 4) + c::value> i; +}; + +/// \brief A functor which returns the cofactor of a 3x3 submatrix obtained by ignoring a given row and column of a 4x4 matrix. +/// \param Row Defines the compile-time-constant integers x, y and z with values corresponding to the indices of the three rows to use. +/// \param Col Defines the compile-time-constant integers x, y and z with values corresponding to the indices of the three columns to use. +template +class Matrix4Cofactor +{ +public: + typedef typename Matrix4Index::i xx; + typedef typename Matrix4Index::i xy; + typedef typename Matrix4Index::i xz; + typedef typename Matrix4Index::i yx; + typedef typename Matrix4Index::i yy; + typedef typename Matrix4Index::i yz; + typedef typename Matrix4Index::i zx; + typedef typename Matrix4Index::i zy; + typedef typename Matrix4Index::i zz; + static double apply(const Matrix4& self) + { + return self[xx::value] * ( self[yy::value]*self[zz::value] - self[zy::value]*self[yz::value] ) + - self[xy::value] * ( self[yx::value]*self[zz::value] - self[zx::value]*self[yz::value] ) + + self[xz::value] * ( self[yx::value]*self[zy::value] - self[zx::value]*self[yy::value] ); + } +}; + +/// \brief The cofactor element indices for a 4x4 matrix row or column. +/// \param Element The index of the element to ignore. +template +class Cofactor4 +{ +public: + typedef ct_int<(Element <= 0) ? 1 : 0> x; + typedef ct_int<(Element <= 1) ? 2 : 1> y; + typedef ct_int<(Element <= 2) ? 3 : 2> z; +}; + +/// \brief Returns the determinant of \p self. +inline double matrix4_determinant(const Matrix4& self) +{ + return self.xx() * Matrix4Cofactor< Cofactor4<0>, Cofactor4<0> >::apply(self) + - self.xy() * Matrix4Cofactor< Cofactor4<0>, Cofactor4<1> >::apply(self) + + self.xz() * Matrix4Cofactor< Cofactor4<0>, Cofactor4<2> >::apply(self) + - self.xw() * Matrix4Cofactor< Cofactor4<0>, Cofactor4<3> >::apply(self); +} + +/// \brief Returns the inverse of \p self using the Adjoint method. +/// \todo Throw an exception if the determinant is zero. +inline Matrix4 matrix4_full_inverse(const Matrix4& self) +{ + double determinant = 1.0 / matrix4_determinant(self); + + return Matrix4( + static_cast( Matrix4Cofactor< Cofactor4<0>, Cofactor4<0> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<1>, Cofactor4<0> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<2>, Cofactor4<0> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<3>, Cofactor4<0> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<0>, Cofactor4<1> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<1>, Cofactor4<1> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<2>, Cofactor4<1> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<3>, Cofactor4<1> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<0>, Cofactor4<2> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<1>, Cofactor4<2> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<2>, Cofactor4<2> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<3>, Cofactor4<2> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<0>, Cofactor4<3> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<1>, Cofactor4<3> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<2>, Cofactor4<3> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<3>, Cofactor4<3> >::apply(self) * determinant) + ); +} + +/// \brief Inverts \p self in-place using the Adjoint method. +inline void matrix4_full_invert(Matrix4& self) +{ + self = matrix4_full_inverse(self); +} + + +/// \brief Constructs a pure-translation matrix from \p translation. +inline Matrix4 matrix4_translation_for_vec3(const Vector3& translation) +{ + return Matrix4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + translation[0], translation[1], translation[2], 1 + ); +} + +/// \brief Returns the translation part of \p self. +inline Vector3 matrix4_get_translation_vec3(const Matrix4& self) +{ + return vector4_to_vector3(self.t()); +} + +/// \brief Concatenates \p self with \p translation. +/// The concatenated \p translation occurs before \p self. +inline void matrix4_translate_by_vec3(Matrix4& self, const Vector3& translation) +{ + matrix4_multiply_by_matrix4(self, matrix4_translation_for_vec3(translation)); +} + +/// \brief Returns \p self Concatenated with \p translation. +/// The concatenated translation occurs before \p self. +inline Matrix4 matrix4_translated_by_vec3(const Matrix4& self, const Vector3& translation) +{ + return matrix4_multiplied_by_matrix4(self, matrix4_translation_for_vec3(translation)); +} + + +#include "math/pi.h" + +/// \brief Returns \p angle modulated by the range [0, 360). +/// \p angle must be in the range [-360, 360). +inline float angle_modulate_degrees_range(float angle) +{ + return static_cast(float_mod_range(angle, 360.0)); +} + +/// \brief Returns \p euler angles converted from radians to degrees. +inline Vector3 euler_radians_to_degrees(const Vector3& euler) +{ + return Vector3( + static_cast(radians_to_degrees(euler.x())), + static_cast(radians_to_degrees(euler.y())), + static_cast(radians_to_degrees(euler.z())) + ); +} + +/// \brief Returns \p euler angles converted from degrees to radians. +inline Vector3 euler_degrees_to_radians(const Vector3& euler) +{ + return Vector3( + static_cast(degrees_to_radians(euler.x())), + static_cast(degrees_to_radians(euler.y())), + static_cast(degrees_to_radians(euler.z())) + ); +} + + + +/// \brief Constructs a pure-rotation matrix about the x axis from sin \p s and cosine \p c of an angle. +inline Matrix4 matrix4_rotation_for_sincos_x(float s, float c) +{ + return Matrix4( + 1, 0, 0, 0, + 0, c, s, 0, + 0,-s, c, 0, + 0, 0, 0, 1 + ); +} + +/// \brief Constructs a pure-rotation matrix about the x axis from an angle in radians. +inline Matrix4 matrix4_rotation_for_x(double x) +{ + return matrix4_rotation_for_sincos_x(static_cast(sin(x)), static_cast(cos(x))); +} + +/// \brief Constructs a pure-rotation matrix about the x axis from an angle in degrees. +inline Matrix4 matrix4_rotation_for_x_degrees(float x) +{ + return matrix4_rotation_for_x(degrees_to_radians(x)); +} + +/// \brief Constructs a pure-rotation matrix about the y axis from sin \p s and cosine \p c of an angle. +inline Matrix4 matrix4_rotation_for_sincos_y(float s, float c) +{ + return Matrix4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1 + ); +} + +/// \brief Constructs a pure-rotation matrix about the y axis from an angle in radians. +inline Matrix4 matrix4_rotation_for_y(double y) +{ + return matrix4_rotation_for_sincos_y(static_cast(sin(y)), static_cast(cos(y))); +} + +/// \brief Constructs a pure-rotation matrix about the y axis from an angle in degrees. +inline Matrix4 matrix4_rotation_for_y_degrees(float y) +{ + return matrix4_rotation_for_y(degrees_to_radians(y)); +} + +/// \brief Constructs a pure-rotation matrix about the z axis from sin \p s and cosine \p c of an angle. +inline Matrix4 matrix4_rotation_for_sincos_z(float s, float c) +{ + return Matrix4( + c, s, 0, 0, + -s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); +} + +/// \brief Constructs a pure-rotation matrix about the z axis from an angle in radians. +inline Matrix4 matrix4_rotation_for_z(double z) +{ + return matrix4_rotation_for_sincos_z(static_cast(sin(z)), static_cast(cos(z))); +} + +/// \brief Constructs a pure-rotation matrix about the z axis from an angle in degrees. +inline Matrix4 matrix4_rotation_for_z_degrees(float z) +{ + return matrix4_rotation_for_z(degrees_to_radians(z)); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (x, y, z). +/*! \verbatim +clockwise rotation around X, Y, Z, facing along axis + 1 0 0 cy 0 -sy cz sz 0 + 0 cx sx 0 1 0 -sz cz 0 + 0 -sx cx sy 0 cy 0 0 1 + +rows of Z by cols of Y + cy*cz -sy*cz+sz -sy*sz+cz +-sz*cy -sz*sy+cz + + .. or something like that.. + +final rotation is Z * Y * X + cy*cz -sx*-sy*cz+cx*sz cx*-sy*sz+sx*cz +-cy*sz sx*sy*sz+cx*cz -cx*-sy*sz+sx*cz + sy -sx*cy cx*cy + +transposed +cy.cz + 0.sz + sy.0 cy.-sz + 0 .cz + sy.0 cy.0 + 0 .0 + sy.1 | +sx.sy.cz + cx.sz + -sx.cy.0 sx.sy.-sz + cx.cz + -sx.cy.0 sx.sy.0 + cx.0 + -sx.cy.1 | +-cx.sy.cz + sx.sz + cx.cy.0 -cx.sy.-sz + sx.cz + cx.cy.0 -cx.sy.0 + 0 .0 + cx.cy.1 | +\endverbatim */ +inline Matrix4 matrix4_rotation_for_euler_xyz(const Vector3& euler) +{ +#if 1 + + double cx = cos(euler[0]); + double sx = sin(euler[0]); + double cy = cos(euler[1]); + double sy = sin(euler[1]); + double cz = cos(euler[2]); + double sz = sin(euler[2]); + + return Matrix4( + static_cast(cy*cz), + static_cast(cy*sz), + static_cast(-sy), + 0, + static_cast(sx*sy*cz + cx*-sz), + static_cast(sx*sy*sz + cx*cz), + static_cast(sx*cy), + 0, + static_cast(cx*sy*cz + sx*sz), + static_cast(cx*sy*sz + -sx*cz), + static_cast(cx*cy), + 0, + 0, + 0, + 0, + 1 + ); + +#else + + return matrix4_premultiply_by_matrix4( + matrix4_premultiply_by_matrix4( + matrix4_rotation_for_x(euler[0]), + matrix4_rotation_for_y(euler[1]) + ), + matrix4_rotation_for_z(euler[2]) + ); + +#endif +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (x, y, z). +inline Matrix4 matrix4_rotation_for_euler_xyz_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_xyz(euler_degrees_to_radians(euler)); +} + +/// \brief Concatenates \p self with the rotation transform produced by \p euler angles (degrees) in the order (x, y, z). +/// The concatenated rotation occurs before \p self. +inline void matrix4_rotate_by_euler_xyz_degrees(Matrix4& self, const Vector3& euler) +{ + matrix4_multiply_by_matrix4(self, matrix4_rotation_for_euler_xyz_degrees(euler)); +} + + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (y, z, x). +inline Matrix4 matrix4_rotation_for_euler_yzx(const Vector3& euler) +{ + return matrix4_premultiplied_by_matrix4( + matrix4_premultiplied_by_matrix4( + matrix4_rotation_for_y(euler[1]), + matrix4_rotation_for_z(euler[2]) + ), + matrix4_rotation_for_x(euler[0]) + ); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (y, z, x). +inline Matrix4 matrix4_rotation_for_euler_yzx_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_yzx(euler_degrees_to_radians(euler)); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (x, z, y). +inline Matrix4 matrix4_rotation_for_euler_xzy(const Vector3& euler) +{ + return matrix4_premultiplied_by_matrix4( + matrix4_premultiplied_by_matrix4( + matrix4_rotation_for_x(euler[0]), + matrix4_rotation_for_z(euler[2]) + ), + matrix4_rotation_for_y(euler[1]) + ); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (x, z, y). +inline Matrix4 matrix4_rotation_for_euler_xzy_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_xzy(euler_degrees_to_radians(euler)); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (y, x, z). +/*! \verbatim +| cy.cz + sx.sy.-sz + -cx.sy.0 0.cz + cx.-sz + sx.0 sy.cz + -sx.cy.-sz + cx.cy.0 | +| cy.sz + sx.sy.cz + -cx.sy.0 0.sz + cx.cz + sx.0 sy.sz + -sx.cy.cz + cx.cy.0 | +| cy.0 + sx.sy.0 + -cx.sy.1 0.0 + cx.0 + sx.1 sy.0 + -sx.cy.0 + cx.cy.1 | +\endverbatim */ +inline Matrix4 matrix4_rotation_for_euler_yxz(const Vector3& euler) +{ +#if 1 + + double cx = cos(euler[0]); + double sx = sin(euler[0]); + double cy = cos(euler[1]); + double sy = sin(euler[1]); + double cz = cos(euler[2]); + double sz = sin(euler[2]); + + return Matrix4( + static_cast(cy*cz + sx*sy*-sz), + static_cast(cy*sz + sx*sy*cz), + static_cast(-cx*sy), + 0, + static_cast(cx*-sz), + static_cast(cx*cz), + static_cast(sx), + 0, + static_cast(sy*cz + -sx*cy*-sz), + static_cast(sy*sz + -sx*cy*cz), + static_cast(cx*cy), + 0, + 0, + 0, + 0, + 1 + ); + +#else + + return matrix4_premultiply_by_matrix4( + matrix4_premultiply_by_matrix4( + matrix4_rotation_for_y(euler[1]), + matrix4_rotation_for_x(euler[0]) + ), + matrix4_rotation_for_z(euler[2]) + ); + +#endif +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (y, x, z). +inline Matrix4 matrix4_rotation_for_euler_yxz_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_yxz(euler_degrees_to_radians(euler)); +} + +/// \brief Returns \p self concatenated with the rotation transform produced by \p euler angles (degrees) in the order (y, x, z). +/// The concatenated rotation occurs before \p self. +inline Matrix4 matrix4_rotated_by_euler_yxz_degrees(const Matrix4& self, const Vector3& euler) +{ + return matrix4_multiplied_by_matrix4(self, matrix4_rotation_for_euler_yxz_degrees(euler)); +} + +/// \brief Concatenates \p self with the rotation transform produced by \p euler angles (degrees) in the order (y, x, z). +/// The concatenated rotation occurs before \p self. +inline void matrix4_rotate_by_euler_yxz_degrees(Matrix4& self, const Vector3& euler) +{ + self = matrix4_rotated_by_euler_yxz_degrees(self, euler); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (z, x, y). +inline Matrix4 matrix4_rotation_for_euler_zxy(const Vector3& euler) +{ +#if 1 + return matrix4_premultiplied_by_matrix4( + matrix4_premultiplied_by_matrix4( + matrix4_rotation_for_z(euler[2]), + matrix4_rotation_for_x(euler[0]) + ), + matrix4_rotation_for_y(euler[1]) + ); +#else + double cx = cos(euler[0]); + double sx = sin(euler[0]); + double cy = cos(euler[1]); + double sy = sin(euler[1]); + double cz = cos(euler[2]); + double sz = sin(euler[2]); + + return Matrix4( + static_cast(cz * cy + sz * sx * sy), + static_cast(sz * cx), + static_cast(cz * -sy + sz * sx * cy), + 0, + static_cast(-sz * cy + cz * sx * sy), + static_cast(cz * cx), + static_cast(-sz * -sy + cz * cx * cy), + 0, + static_cast(cx* sy), + static_cast(-sx), + static_cast(cx* cy), + 0, + 0, + 0, + 0, + 1 + ); +#endif +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degres=es) in the order (z, x, y). +inline Matrix4 matrix4_rotation_for_euler_zxy_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_zxy(euler_degrees_to_radians(euler)); +} + +/// \brief Returns \p self concatenated with the rotation transform produced by \p euler angles (degrees) in the order (z, x, y). +/// The concatenated rotation occurs before \p self. +inline Matrix4 matrix4_rotated_by_euler_zxy_degrees(const Matrix4& self, const Vector3& euler) +{ + return matrix4_multiplied_by_matrix4(self, matrix4_rotation_for_euler_zxy_degrees(euler)); +} + +/// \brief Concatenates \p self with the rotation transform produced by \p euler angles (degrees) in the order (z, x, y). +/// The concatenated rotation occurs before \p self. +inline void matrix4_rotate_by_euler_zxy_degrees(Matrix4& self, const Vector3& euler) +{ + self = matrix4_rotated_by_euler_zxy_degrees(self, euler); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (z, y, x). +inline Matrix4 matrix4_rotation_for_euler_zyx(const Vector3& euler) +{ +#if 1 + + double cx = cos(euler[0]); + double sx = sin(euler[0]); + double cy = cos(euler[1]); + double sy = sin(euler[1]); + double cz = cos(euler[2]); + double sz = sin(euler[2]); + + return Matrix4( + static_cast(cy*cz), + static_cast(sx*sy*cz + cx*sz), + static_cast(cx*-sy*cz + sx*sz), + 0, + static_cast(cy*-sz), + static_cast(sx*sy*-sz + cx*cz), + static_cast(cx*-sy*-sz + sx*cz), + 0, + static_cast(sy), + static_cast(-sx*cy), + static_cast(cx*cy), + 0, + 0, + 0, + 0, + 1 + ); + +#else + + return matrix4_premultiply_by_matrix4( + matrix4_premultiply_by_matrix4( + matrix4_rotation_for_z(euler[2]), + matrix4_rotation_for_y(euler[1]) + ), + matrix4_rotation_for_x(euler[0]) + ); + +#endif +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (z, y, x). +inline Matrix4 matrix4_rotation_for_euler_zyx_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_zyx(euler_degrees_to_radians(euler)); +} + + +/// \brief Calculates and returns a set of euler angles that produce the rotation component of \p self when applied in the order (x, y, z). +/// \p self must be affine and orthonormal (unscaled) to produce a meaningful result. +inline Vector3 matrix4_get_rotation_euler_xyz(const Matrix4& self) +{ + double a = asin(-self[2]); + double ca = cos(a); + + if (fabs(ca) > 0.005) // Gimbal lock? + { + return Vector3( + static_cast(atan2(self[6] / ca, self[10] / ca)), + static_cast(a), + static_cast(atan2(self[1] / ca, self[0]/ ca)) + ); + } + else // Gimbal lock has occurred + { + return Vector3( + static_cast(atan2(-self[9], self[5])), + static_cast(a), + 0 + ); + } +} + +/// \brief \copydoc matrix4_get_rotation_euler_xyz(const Matrix4&) +inline Vector3 matrix4_get_rotation_euler_xyz_degrees(const Matrix4& self) +{ + return euler_radians_to_degrees(matrix4_get_rotation_euler_xyz(self)); +} + +/// \brief Calculates and returns a set of euler angles that produce the rotation component of \p self when applied in the order (y, x, z). +/// \p self must be affine and orthonormal (unscaled) to produce a meaningful result. +inline Vector3 matrix4_get_rotation_euler_yxz(const Matrix4& self) +{ + double a = asin(self[6]); + double ca = cos(a); + + if (fabs(ca) > 0.005) // Gimbal lock? + { + return Vector3( + static_cast(a), + static_cast(atan2(-self[2] / ca, self[10]/ ca)), + static_cast(atan2(-self[4] / ca, self[5] / ca)) + ); + } + else // Gimbal lock has occurred + { + return Vector3( + static_cast(a), + static_cast(atan2(self[8], self[0])), + 0 + ); + } +} + +/// \brief \copydoc matrix4_get_rotation_euler_yxz(const Matrix4&) +inline Vector3 matrix4_get_rotation_euler_yxz_degrees(const Matrix4& self) +{ + return euler_radians_to_degrees(matrix4_get_rotation_euler_yxz(self)); +} + +/// \brief Calculates and returns a set of euler angles that produce the rotation component of \p self when applied in the order (z, x, y). +/// \p self must be affine and orthonormal (unscaled) to produce a meaningful result. +inline Vector3 matrix4_get_rotation_euler_zxy(const Matrix4& self) +{ + double a = asin(-self[9]); + double ca = cos(a); + + if (fabs(ca) > 0.005) // Gimbal lock? + { + return Vector3( + static_cast(a), + static_cast(atan2(self[8] / ca, self[10] / ca)), + static_cast(atan2(self[1] / ca, self[5]/ ca)) + ); + } + else // Gimbal lock has occurred + { + return Vector3( + static_cast(a), + 0, + static_cast(atan2(-self[4], self[0])) + ); + } +} + +/// \brief \copydoc matrix4_get_rotation_euler_zxy(const Matrix4&) +inline Vector3 matrix4_get_rotation_euler_zxy_degrees(const Matrix4& self) +{ + return euler_radians_to_degrees(matrix4_get_rotation_euler_zxy(self)); +} + +/// \brief Calculates and returns a set of euler angles that produce the rotation component of \p self when applied in the order (z, y, x). +/// \p self must be affine and orthonormal (unscaled) to produce a meaningful result. +inline Vector3 matrix4_get_rotation_euler_zyx(const Matrix4& self) +{ + double a = asin(self[8]); + double ca = cos(a); + + if (fabs(ca) > 0.005) // Gimbal lock? + { + return Vector3( + static_cast(atan2(-self[9] / ca, self[10]/ ca)), + static_cast(a), + static_cast(atan2(-self[4] / ca, self[0] / ca)) + ); + } + else // Gimbal lock has occurred + { + return Vector3( + 0, + static_cast(a), + static_cast(atan2(self[1], self[5])) + ); + } +} + +/// \brief \copydoc matrix4_get_rotation_euler_zyx(const Matrix4&) +inline Vector3 matrix4_get_rotation_euler_zyx_degrees(const Matrix4& self) +{ + return euler_radians_to_degrees(matrix4_get_rotation_euler_zyx(self)); +} + + +/// \brief Rotate \p self by \p euler angles (degrees) applied in the order (x, y, z), using \p pivotpoint. +inline void matrix4_pivoted_rotate_by_euler_xyz_degrees(Matrix4& self, const Vector3& euler, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint); + matrix4_rotate_by_euler_xyz_degrees(self, euler); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + + +/// \brief Constructs a pure-scale matrix from \p scale. +inline Matrix4 matrix4_scale_for_vec3(const Vector3& scale) +{ + return Matrix4( + scale[0], 0, 0, 0, + 0, scale[1], 0, 0, + 0, 0, scale[2], 0, + 0, 0, 0, 1 + ); +} + +/// \brief Calculates and returns the (x, y, z) scale values that produce the scale component of \p self. +/// \p self must be affine and orthogonal to produce a meaningful result. +inline Vector3 matrix4_get_scale_vec3(const Matrix4& self) +{ + return Vector3( + static_cast(vector3_length(vector4_to_vector3(self.x()))), + static_cast(vector3_length(vector4_to_vector3(self.y()))), + static_cast(vector3_length(vector4_to_vector3(self.z()))) + ); +} + +/// \brief Scales \p self by \p scale. +inline void matrix4_scale_by_vec3(Matrix4& self, const Vector3& scale) +{ + matrix4_multiply_by_matrix4(self, matrix4_scale_for_vec3(scale)); +} + +/// \brief Scales \p self by \p scale, using \p pivotpoint. +inline void matrix4_pivoted_scale_by_vec3(Matrix4& self, const Vector3& scale, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint); + matrix4_scale_by_vec3(self, scale); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + + +/// \brief Transforms \p self by \p translation, \p euler and \p scale. +/// The transforms are combined in the order: scale, rotate-z, rotate-y, rotate-x, translate. +inline void matrix4_transform_by_euler_xyz_degrees(Matrix4& self, const Vector3& translation, const Vector3& euler, const Vector3& scale) +{ + matrix4_translate_by_vec3(self, translation); + matrix4_rotate_by_euler_xyz_degrees(self, euler); + matrix4_scale_by_vec3(self, scale); +} + +/// \brief Transforms \p self by \p translation, \p euler and \p scale, using \p pivotpoint. +inline void matrix4_pivoted_transform_by_euler_xyz_degrees(Matrix4& self, const Vector3& translation, const Vector3& euler, const Vector3& scale, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint + translation); + matrix4_rotate_by_euler_xyz_degrees(self, euler); + matrix4_scale_by_vec3(self, scale); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + + +#endif diff --git a/tools/urt/libs/math/pi.cpp b/tools/urt/libs/math/pi.cpp new file mode 100644 index 00000000..cdf481db --- /dev/null +++ b/tools/urt/libs/math/pi.cpp @@ -0,0 +1,3 @@ + +#include "pi.h" + diff --git a/tools/urt/libs/math/pi.h b/tools/urt/libs/math/pi.h new file mode 100644 index 00000000..5b9089c5 --- /dev/null +++ b/tools/urt/libs/math/pi.h @@ -0,0 +1,25 @@ + +#if !defined(INCLUDED_MATH_PI_H) +#define INCLUDED_MATH_PI_H + +/// \file +/// \brief Pi constants and degrees/radians conversion. + +const double c_pi = 3.1415926535897932384626433832795; +const double c_half_pi = c_pi / 2; +const double c_2pi = 2 * c_pi; +const double c_inv_2pi = 1 / c_2pi; + +const double c_DEG2RADMULT = c_pi / 180.0; +const double c_RAD2DEGMULT = 180.0 / c_pi; + +inline double radians_to_degrees(double radians) +{ + return radians * c_RAD2DEGMULT; +} +inline double degrees_to_radians(double degrees) +{ + return degrees * c_DEG2RADMULT; +} + +#endif diff --git a/tools/urt/libs/math/plane.cpp b/tools/urt/libs/math/plane.cpp new file mode 100644 index 00000000..38a44241 --- /dev/null +++ b/tools/urt/libs/math/plane.cpp @@ -0,0 +1,3 @@ + +#include "plane.h" + diff --git a/tools/urt/libs/math/plane.h b/tools/urt/libs/math/plane.h new file mode 100644 index 00000000..cba70355 --- /dev/null +++ b/tools/urt/libs/math/plane.h @@ -0,0 +1,133 @@ + +#if !defined(INCLUDED_MATH_PLANE_H) +#define INCLUDED_MATH_PLANE_H + +/// \file +/// \brief Plane data types and related operations. + +#include "math/matrix.h" + +/// \brief A plane equation stored in double-precision floating-point. +class Plane3 +{ +public: + double a, b, c, d; + + Plane3() + { + } + Plane3(double _a, double _b, double _c, double _d) + : a(_a), b(_b), c(_c), d(_d) + { + } + template + Plane3(const BasicVector3& normal, double dist) + : a(normal.x()), b(normal.y()), c(normal.z()), d(dist) + { + } + + BasicVector3& normal() + { + return reinterpret_cast&>(*this); + } + const BasicVector3& normal() const + { + return reinterpret_cast&>(*this); + } + double& dist() + { + return d; + } + const double& dist() const + { + return d; + } +}; + +inline Plane3 plane3_normalised(const Plane3& plane) +{ + double rmagnitude = 1.0 / sqrt(plane.a * plane.a + plane.b * plane.b + plane.c * plane.c); + return Plane3( + plane.a * rmagnitude, + plane.b * rmagnitude, + plane.c * rmagnitude, + plane.d * rmagnitude + ); +} + +inline Plane3 plane3_translated(const Plane3& plane, const Vector3& translation) +{ + Plane3 transformed; + transformed.a = plane.a; + transformed.b = plane.b; + transformed.c = plane.c; + transformed.d = -((-plane.d * transformed.a + translation.x()) * transformed.a + + (-plane.d * transformed.b + translation.y()) * transformed.b + + (-plane.d * transformed.c + translation.z()) * transformed.c); + return transformed; +} + +inline Plane3 plane3_transformed(const Plane3& plane, const Matrix4& transform) +{ + Plane3 transformed; + transformed.a = transform[0] * plane.a + transform[4] * plane.b + transform[8] * plane.c; + transformed.b = transform[1] * plane.a + transform[5] * plane.b + transform[9] * plane.c; + transformed.c = transform[2] * plane.a + transform[6] * plane.b + transform[10] * plane.c; + transformed.d = -((-plane.d * transformed.a + transform[12]) * transformed.a + + (-plane.d * transformed.b + transform[13]) * transformed.b + + (-plane.d * transformed.c + transform[14]) * transformed.c); + return transformed; +} + +inline Plane3 plane3_inverse_transformed(const Plane3& plane, const Matrix4& transform) +{ + return Plane3 + ( + transform[ 0] * plane.a + transform[ 1] * plane.b + transform[ 2] * plane.c + transform[ 3] * plane.d, + transform[ 4] * plane.a + transform[ 5] * plane.b + transform[ 6] * plane.c + transform[ 7] * plane.d, + transform[ 8] * plane.a + transform[ 9] * plane.b + transform[10] * plane.c + transform[11] * plane.d, + transform[12] * plane.a + transform[13] * plane.b + transform[14] * plane.c + transform[15] * plane.d + ); +} + +inline Plane3 plane3_flipped(const Plane3& plane) +{ + return Plane3(vector3_negated(plane.normal()), -plane.dist()); +} + +const double c_PLANE_NORMAL_EPSILON = 0.0001f; +const double c_PLANE_DIST_EPSILON = 0.02; + +inline bool plane3_equal(const Plane3& self, const Plane3& other) +{ + return vector3_equal_epsilon(self.normal(), other.normal(), c_PLANE_NORMAL_EPSILON) + && float_equal_epsilon(self.dist(), other.dist(), c_PLANE_DIST_EPSILON); +} + +inline bool plane3_opposing(const Plane3& self, const Plane3& other) +{ + return plane3_equal(self, plane3_flipped(other)); +} + +inline bool plane3_valid(const Plane3& self) +{ + return float_equal_epsilon(vector3_dot(self.normal(), self.normal()), 1.0, 0.01); +} + +template +inline Plane3 plane3_for_points(const BasicVector3& p0, const BasicVector3& p1, const BasicVector3& p2) +{ + Plane3 self; + self.normal() = vector3_normalised(vector3_cross(vector3_subtracted(p1, p0), vector3_subtracted(p2, p0))); + self.dist() = vector3_dot(p0, self.normal()); + return self; +} + +template +inline Plane3 plane3_for_points(const BasicVector3 planepts[3]) +{ + return plane3_for_points(planepts[2], planepts[1], planepts[0]); +} + + +#endif diff --git a/tools/urt/libs/math/quaternion.cpp b/tools/urt/libs/math/quaternion.cpp new file mode 100644 index 00000000..eae3ba00 --- /dev/null +++ b/tools/urt/libs/math/quaternion.cpp @@ -0,0 +1,3 @@ + +#include "quaternion.h" + diff --git a/tools/urt/libs/math/quaternion.h b/tools/urt/libs/math/quaternion.h new file mode 100644 index 00000000..dd4ff1bc --- /dev/null +++ b/tools/urt/libs/math/quaternion.h @@ -0,0 +1,289 @@ + +#if !defined(INCLUDED_MATH_QUATERNION_H) +#define INCLUDED_MATH_QUATERNION_H + +/// \file +/// \brief Quaternion data types and related operations. + +#include "math/matrix.h" + +/// \brief A quaternion stored in single-precision floating-point. +typedef Vector4 Quaternion; + +const Quaternion c_quaternion_identity(0, 0, 0, 1); + +inline Quaternion quaternion_multiplied_by_quaternion(const Quaternion& quaternion, const Quaternion& other) +{ + return Quaternion( + quaternion[3]*other[0] + quaternion[0]*other[3] + quaternion[1]*other[2] - quaternion[2]*other[1], + quaternion[3]*other[1] + quaternion[1]*other[3] + quaternion[2]*other[0] - quaternion[0]*other[2], + quaternion[3]*other[2] + quaternion[2]*other[3] + quaternion[0]*other[1] - quaternion[1]*other[0], + quaternion[3]*other[3] - quaternion[0]*other[0] - quaternion[1]*other[1] - quaternion[2]*other[2] + ); +} + +inline void quaternion_multiply_by_quaternion(Quaternion& quaternion, const Quaternion& other) +{ + quaternion = quaternion_multiplied_by_quaternion(quaternion, other); +} + +/// \brief Constructs a quaternion which rotates between two points on the unit-sphere, \p from and \p to. +inline Quaternion quaternion_for_unit_vectors(const Vector3& from, const Vector3& to) +{ + return Quaternion(vector3_cross(from, to), static_cast(vector3_dot(from, to))); +} + +inline Quaternion quaternion_for_axisangle(const Vector3& axis, double angle) +{ + angle *= 0.5; + float sa = static_cast(sin(angle)); + return Quaternion(axis[0] * sa, axis[1] * sa, axis[2] * sa, static_cast(cos(angle))); +} + +inline Quaternion quaternion_inverse(const Quaternion& quaternion) +{ + return Quaternion(vector3_negated(vector4_to_vector3(quaternion)), quaternion[3]); +} + +inline void quaternion_conjugate(Quaternion& quaternion) +{ + quaternion = quaternion_inverse(quaternion); +} + +inline Quaternion quaternion_normalised(const Quaternion& quaternion) +{ + const double n = (1.0 / (quaternion[0] * quaternion[0] + quaternion[1] * quaternion[1] + quaternion[2] * quaternion[2] + quaternion[3] * quaternion[3])); + return Quaternion( + static_cast(quaternion[0] * n), + static_cast(quaternion[1] * n), + static_cast(quaternion[2] * n), + static_cast(quaternion[3] * n) + ); +} + +inline void quaternion_normalise(Quaternion& quaternion) +{ + quaternion = quaternion_normalised(quaternion); +} + +/// \brief Constructs a pure-rotation matrix from \p quaternion. +inline Matrix4 matrix4_rotation_for_quaternion(const Quaternion& quaternion) +{ +#if 0 + const double xx = quaternion[0] * quaternion[0]; + const double xy = quaternion[0] * quaternion[1]; + const double xz = quaternion[0] * quaternion[2]; + const double xw = quaternion[0] * quaternion[3]; + + const double yy = quaternion[1] * quaternion[1]; + const double yz = quaternion[1] * quaternion[2]; + const double yw = quaternion[1] * quaternion[3]; + + const double zz = quaternion[2] * quaternion[2]; + const double zw = quaternion[2] * quaternion[3]; + + return Matrix4( + static_cast( 1 - 2 * ( yy + zz ) ), + static_cast( 2 * ( xy + zw ) ), + static_cast( 2 * ( xz - yw ) ), + 0, + static_cast( 2 * ( xy - zw ) ), + static_cast( 1 - 2 * ( xx + zz ) ), + static_cast( 2 * ( yz + xw ) ), + 0, + static_cast( 2 * ( xz + yw ) ), + static_cast( 2 * ( yz - xw ) ), + static_cast( 1 - 2 * ( xx + yy ) ), + 0, + 0, + 0, + 0, + 1 + ); + +#else + const double x2 = quaternion[0] + quaternion[0]; + const double y2 = quaternion[1] + quaternion[1]; + const double z2 = quaternion[2] + quaternion[2]; + const double xx = quaternion[0] * x2; + const double xy = quaternion[0] * y2; + const double xz = quaternion[0] * z2; + const double yy = quaternion[1] * y2; + const double yz = quaternion[1] * z2; + const double zz = quaternion[2] * z2; + const double wx = quaternion[3] * x2; + const double wy = quaternion[3] * y2; + const double wz = quaternion[3] * z2; + + return Matrix4( + static_cast( 1.0 - (yy + zz) ), + static_cast(xy + wz), + static_cast(xz - wy), + 0, + static_cast(xy - wz), + static_cast( 1.0 - (xx + zz) ), + static_cast(yz + wx), + 0, + static_cast(xz + wy), + static_cast(yz - wx), + static_cast( 1.0 - (xx + yy) ), + 0, + 0, + 0, + 0, + 1 + ); + +#endif +} + +const double c_half_sqrt2 = 0.70710678118654752440084436210485; +const float c_half_sqrt2f = static_cast(c_half_sqrt2); + +inline bool quaternion_component_is_90(float component) +{ + return (fabs(component) - c_half_sqrt2) < 0.001; +} + +inline Matrix4 matrix4_rotation_for_quaternion_quantised(const Quaternion& quaternion) +{ + if(quaternion.y() == 0 + && quaternion.z() == 0 + && quaternion_component_is_90(quaternion.x()) + && quaternion_component_is_90(quaternion.w())) + { + return matrix4_rotation_for_sincos_x((quaternion.x() > 0) ? 1.f : -1.f, 0); + } + + if(quaternion.x() == 0 + && quaternion.z() == 0 + && quaternion_component_is_90(quaternion.y()) + && quaternion_component_is_90(quaternion.w())) + { + return matrix4_rotation_for_sincos_y((quaternion.y() > 0) ? 1.f : -1.f, 0); + } + + if(quaternion.x() == 0 + && quaternion.y() == 0 + && quaternion_component_is_90(quaternion.z()) + && quaternion_component_is_90(quaternion.w())) + { + return matrix4_rotation_for_sincos_z((quaternion.z() > 0) ? 1.f : -1.f, 0); + } + + return matrix4_rotation_for_quaternion(quaternion); +} + +inline Quaternion quaternion_for_matrix4_rotation(const Matrix4& matrix4) +{ + Matrix4 transposed = matrix4_transposed(matrix4); + + double trace = transposed[0] + transposed[5] + transposed[10] + 1.0; + + if(trace > 0.0001) + { + double S = 0.5 / sqrt(trace); + return Quaternion( + static_cast((transposed[9] - transposed[6]) * S), + static_cast((transposed[2] - transposed[8]) * S), + static_cast((transposed[4] - transposed[1]) * S), + static_cast(0.25 / S) + ); + } + + if(transposed[0] >= transposed[5] && transposed[0] >= transposed[10]) + { + double S = 2.0 * sqrt(1.0 + transposed[0] - transposed[5] - transposed[10]); + return Quaternion( + static_cast(0.25 / S), + static_cast((transposed[1] + transposed[4]) / S), + static_cast((transposed[2] + transposed[8]) / S), + static_cast((transposed[6] + transposed[9]) / S) + ); + } + + if(transposed[5] >= transposed[0] && transposed[5] >= transposed[10]) + { + double S = 2.0 * sqrt(1.0 + transposed[5] - transposed[0] - transposed[10]); + return Quaternion( + static_cast((transposed[1] + transposed[4]) / S), + static_cast(0.25 / S), + static_cast((transposed[6] + transposed[9]) / S), + static_cast((transposed[2] + transposed[8]) / S) + ); + } + + double S = 2.0 * sqrt(1.0 + transposed[10] - transposed[0] - transposed[5]); + return Quaternion( + static_cast((transposed[2] + transposed[8]) / S), + static_cast((transposed[6] + transposed[9]) / S), + static_cast(0.25 / S), + static_cast((transposed[1] + transposed[4]) / S) + ); +} + +/// \brief Returns \p self concatenated with the rotation transform produced by \p rotation. +/// The concatenated rotation occurs before \p self. +inline Matrix4 matrix4_rotated_by_quaternion(const Matrix4& self, const Quaternion& rotation) +{ + return matrix4_multiplied_by_matrix4(self, matrix4_rotation_for_quaternion(rotation)); +} + +/// \brief Concatenates \p self with the rotation transform produced by \p rotation. +/// The concatenated rotation occurs before \p self. +inline void matrix4_rotate_by_quaternion(Matrix4& self, const Quaternion& rotation) +{ + self = matrix4_rotated_by_quaternion(self, rotation); +} + +/// \brief Rotates \p self by \p rotation, using \p pivotpoint. +inline void matrix4_pivoted_rotate_by_quaternion(Matrix4& self, const Quaternion& rotation, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint); + matrix4_rotate_by_quaternion(self, rotation); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + +inline Vector3 quaternion_transformed_point(const Quaternion& quaternion, const Vector3& point) +{ + double xx = quaternion.x() * quaternion.x(); + double yy = quaternion.y() * quaternion.y(); + double zz = quaternion.z() * quaternion.z(); + double ww = quaternion.w() * quaternion.w(); + + double xy2 = quaternion.x() * quaternion.y() * 2; + double xz2 = quaternion.x() * quaternion.z() * 2; + double xw2 = quaternion.x() * quaternion.w() * 2; + double yz2 = quaternion.y() * quaternion.z() * 2; + double yw2 = quaternion.y() * quaternion.w() * 2; + double zw2 = quaternion.z() * quaternion.w() * 2; + + return Vector3( + static_cast(ww * point.x() + yw2 * point.z() - zw2 * point.y() + xx * point.x() + xy2 * point.y() + xz2 * point.z() - zz * point.x() - yy * point.x()), + static_cast(xy2 * point.x() + yy * point.y() + yz2 * point.z() + zw2 * point.x() - zz * point.y() + ww * point.y() - xw2 * point.z() - xx * point.y()), + static_cast(xz2 * point.x() + yz2 * point.y() + zz * point.z() - yw2 * point.x() - yy * point.z() + xw2 * point.y() - xx * point.z() + ww * point.z()) + ); +} + +/// \brief Constructs a pure-rotation transform from \p axis and \p angle (radians). +inline Matrix4 matrix4_rotation_for_axisangle(const Vector3& axis, double angle) +{ + return matrix4_rotation_for_quaternion(quaternion_for_axisangle(axis, angle)); +} + +/// \brief Rotates \p self about \p axis by \p angle. +inline void matrix4_rotate_by_axisangle(Matrix4& self, const Vector3& axis, double angle) +{ + matrix4_multiply_by_matrix4(self, matrix4_rotation_for_axisangle(axis, angle)); +} + +/// \brief Rotates \p self about \p axis by \p angle using \p pivotpoint. +inline void matrix4_pivoted_rotate_by_axisangle(Matrix4& self, const Vector3& axis, double angle, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint); + matrix4_rotate_by_axisangle(self, axis, angle); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + + +#endif diff --git a/tools/urt/libs/math/vector.cpp b/tools/urt/libs/math/vector.cpp new file mode 100644 index 00000000..8092f23a --- /dev/null +++ b/tools/urt/libs/math/vector.cpp @@ -0,0 +1,3 @@ + +#include "vector.h" + diff --git a/tools/urt/libs/math/vector.h b/tools/urt/libs/math/vector.h new file mode 100644 index 00000000..1710128a --- /dev/null +++ b/tools/urt/libs/math/vector.h @@ -0,0 +1,933 @@ + +#if !defined(INCLUDED_MATH_VECTOR_H) +#define INCLUDED_MATH_VECTOR_H + +/// \file +/// \brief Vector data types and related operations. + +#if 0 + +#define lrint(dbl) ((int)((dbl) + 0.5)) +#define lrintf(flt) ((int)((flt) + 0.5)) + +#endif + +#if defined (_MSC_VER) + +inline int lrint (double flt) +{ + int i; + + _asm + { + fld flt + fistp i + }; + + return i; +} + +#else // lrint is part of ISO C99 + +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 + +#define __USE_ISOC9X 1 +#define __USE_ISOC99 1 + +#endif + +#include +#include +#include +#include + +//#include "debugging/debugging.h" + +/// \brief Returns true if \p self is equal to other \p other within \p epsilon. +template +inline bool float_equal_epsilon(const Element& self, const OtherElement& other, const Element& epsilon) +{ + return fabs(other - self) < epsilon; +} + +/// \brief Returns the value midway between \p self and \p other. +template +inline Element float_mid(const Element& self, const Element& other) +{ + return Element((self + other) * 0.5); +} + +/// \brief Returns \p f rounded to the nearest integer. Note that this is not the same behaviour as casting from float to int. +template +inline int float_to_integer(const Element& f) +{ + return lrint(f); +} + +/// \brief Returns \p f rounded to the nearest multiple of \snap. +template +inline Element float_snapped(const Element& f, const OtherElement& snap) +{ + return Element(float_to_integer(f / snap) * snap); +} + +/// \brief Returns true if \p f has no decimal fraction part. +template +inline bool float_is_integer(const Element& f) +{ + return f == Element(float_to_integer(f)); +} + +/// \brief Returns \p self modulated by the range [0, \p modulus) +/// \p self must be in the range [\p -modulus, \p modulus) +template +inline Element float_mod_range(const Element& self, const ModulusElement& modulus) +{ + return Element((self < 0.0) ? self + modulus : self); +} + +/// \brief Returns \p self modulated by the range [0, \p modulus) +template +inline Element float_mod(const Element& self, const ModulusElement& modulus) +{ + return float_mod_range(Element(fmod(static_cast(self), static_cast(modulus))), modulus); +} + + +template +class BasicVector2 +{ + Element m_elements[2]; +public: + BasicVector2() + { + } + BasicVector2(const Element& x_, const Element& y_) + { + x() = x_; + y() = y_; + } + + Element& x() + { + return m_elements[0]; + } + const Element& x() const + { + return m_elements[0]; + } + Element& y() + { + return m_elements[1]; + } + const Element& y() const + { + return m_elements[1]; + } + + const Element& operator[](std::size_t i) const + { + return m_elements[i]; + } + Element& operator[](std::size_t i) + { + return m_elements[i]; + } +}; + + +template +inline BasicVector2 vector2_added(const BasicVector2& self, const BasicVector2& other) +{ + return BasicVector2( + Element(self.x() + other.x()), + Element(self.y() + other.y()) + ); +} +template +inline BasicVector2 operator+(const BasicVector2& self, const BasicVector2& other) +{ + return vector2_added(self, other); +} +template +inline void vector2_add(BasicVector2& self, const BasicVector2& other) +{ + self.x() += Element(other.x()); + self.y() += Element(other.y()); +} +template +inline void operator+=(BasicVector2& self, const BasicVector2& other) +{ + vector2_add(self, other); +} + + +template +inline BasicVector2 vector2_subtracted(const BasicVector2& self, const BasicVector2& other) +{ + return BasicVector2( + Element(self.x() - other.x()), + Element(self.y() - other.y()) + ); +} +template +inline BasicVector2 operator-(const BasicVector2& self, const BasicVector2& other) +{ + return vector2_subtracted(self, other); +} +template +inline void vector2_subtract(BasicVector2& self, const BasicVector2& other) +{ + self.x() -= Element(other.x()); + self.y() -= lement(other.y()); +} +template +inline void operator-=(BasicVector2& self, const BasicVector2& other) +{ + vector2_subtract(self, other); +} + + +template +inline BasicVector2 vector2_scaled(const BasicVector2& self, OtherElement other) +{ + return BasicVector2( + Element(self.x() * other), + Element(self.y() * other) + ); +} +template +inline BasicVector2 operator*(const BasicVector2& self, OtherElement other) +{ + return vector2_scaled(self, other); +} +template +inline void vector2_scale(BasicVector2& self, OtherElement other) +{ + self.x() *= Element(other); + self.y() *= Element(other); +} +template +inline void operator*=(BasicVector2& self, OtherElement other) +{ + vector2_scale(self, other); +} + + +template +inline BasicVector2 vector2_scaled(const BasicVector2& self, const BasicVector2& other) +{ + return BasicVector2( + Element(self.x() * other.x()), + Element(self.y() * other.y()) + ); +} +template +inline BasicVector2 operator*(const BasicVector2& self, const BasicVector2& other) +{ + return vector2_scaled(self, other); +} +template +inline void vector2_scale(BasicVector2& self, const BasicVector2& other) +{ + self.x() *= Element(other.x()); + self.y() *= Element(other.y()); +} +template +inline void operator*=(BasicVector2& self, const BasicVector2& other) +{ + vector2_scale(self, other); +} + +template +inline BasicVector2 vector2_divided(const BasicVector2& self, const BasicVector2& other) +{ + return BasicVector2( + Element(self.x() / other.x()), + Element(self.y() / other.y()) + ); +} +template +inline BasicVector2 operator/(const BasicVector2& self, const BasicVector2& other) +{ + return vector2_divided(self, other); +} +template +inline void vector2_divide(BasicVector2& self, const BasicVector2& other) +{ + self.x() /= Element(other.x()); + self.y() /= Element(other.y()); +} +template +inline void operator/=(BasicVector2& self, const BasicVector2& other) +{ + vector2_divide(self, other); +} + + +template +inline BasicVector2 vector2_divided(const BasicVector2& self, OtherElement other) +{ + return BasicVector2( + Element(self.x() / other), + Element(self.y() / other) + ); +} +template +inline BasicVector2 operator/(const BasicVector2& self, OtherElement other) +{ + return vector2_divided(self, other); +} +template +inline void vector2_divide(BasicVector2& self, OtherElement other) +{ + self.x() /= Element(other); + self.y() /= Element(other); +} +template +inline void operator/=(BasicVector2& self, OtherElement other) +{ + vector2_divide(self, other); +} + +template +inline double vector2_dot(const BasicVector2& self, const BasicVector2& other) +{ + return self.x() * other.x() + self.y() * other.y(); +} + +template +inline double vector2_length_squared(const BasicVector2& self) +{ + return vector2_dot(self, self); +} + + +typedef BasicVector2 Vector2; + +/// \brief A 3-element vector. +template +class BasicVector3 +{ + Element m_elements[3]; +public: + + BasicVector3() + { + } + template + BasicVector3(const BasicVector3& other) + { + x() = static_cast(other.x()); + y() = static_cast(other.y()); + z() = static_cast(other.z()); + } + BasicVector3(const Element& x_, const Element& y_, const Element& z_) + { + x() = x_; + y() = y_; + z() = z_; + } + + Element& x() + { + return m_elements[0]; + } + const Element& x() const + { + return m_elements[0]; + } + Element& y() + { + return m_elements[1]; + } + const Element& y() const + { + return m_elements[1]; + } + Element& z() + { + return m_elements[2]; + } + const Element& z() const + { + return m_elements[2]; + } + + const Element& operator[](std::size_t i) const + { + return m_elements[i]; + } + Element& operator[](std::size_t i) + { + return m_elements[i]; + } + + operator BasicVector2&() + { + return reinterpret_cast&>(*this); + } + operator const BasicVector2&() const + { + return reinterpret_cast&>(*this); + } +}; + +/// \brief A 3-element vector stored in single-precision floating-point. +typedef BasicVector3 Vector3; + +const Vector3 g_vector3_identity(0, 0, 0); +const Vector3 g_vector3_max = Vector3(FLT_MAX, FLT_MAX, FLT_MAX); +const Vector3 g_vector3_axis_x(1, 0, 0); +const Vector3 g_vector3_axis_y(0, 1, 0); +const Vector3 g_vector3_axis_z(0, 0, 1); + +const Vector3 g_vector3_axes[3] = { g_vector3_axis_x, g_vector3_axis_y, g_vector3_axis_z }; + +template +inline Element* vector3_to_array(BasicVector3& self) +{ + return reinterpret_cast(&self); +} +template +inline const Element* vector3_to_array(const BasicVector3& self) +{ + return reinterpret_cast(&self); +} + +template +inline void vector3_swap(BasicVector3& self, BasicVector3& other) +{ + std::swap(self.x(), other.x()); + std::swap(self.y(), other.y()); + std::swap(self.z(), other.z()); +} + +template +inline bool vector3_equal(const BasicVector3& self, const BasicVector3& other) +{ + return self.x() == other.x() && self.y() == other.y() && self.z() == other.z(); +} +template +inline bool operator==(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_equal(self, other); +} +template +inline bool operator!=(const BasicVector3& self, const BasicVector3& other) +{ + return !vector3_equal(self, other); +} + + +template +inline bool vector3_equal_epsilon(const BasicVector3& self, const BasicVector3& other, Epsilon epsilon) +{ + return float_equal_epsilon(self.x(), other.x(), epsilon) + && float_equal_epsilon(self.y(), other.y(), epsilon) + && float_equal_epsilon(self.z(), other.z(), epsilon); +} + + + +template +inline BasicVector3 vector3_added(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.x() + other.x()), + Element(self.y() + other.y()), + Element(self.z() + other.z()) + ); +} +template +inline BasicVector3 operator+(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_added(self, other); +} +template +inline void vector3_add(BasicVector3& self, const BasicVector3& other) +{ + self.x() += static_cast(other.x()); + self.y() += static_cast(other.y()); + self.z() += static_cast(other.z()); +} +template +inline void operator+=(BasicVector3& self, const BasicVector3& other) +{ + vector3_add(self, other); +} + +template +inline BasicVector3 vector3_subtracted(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.x() - other.x()), + Element(self.y() - other.y()), + Element(self.z() - other.z()) + ); +} +template +inline BasicVector3 operator-(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_subtracted(self, other); +} +template +inline void vector3_subtract(BasicVector3& self, const BasicVector3& other) +{ + self.x() -= static_cast(other.x()); + self.y() -= static_cast(other.y()); + self.z() -= static_cast(other.z()); +} +template +inline void operator-=(BasicVector3& self, const BasicVector3& other) +{ + vector3_subtract(self, other); +} + +template +inline BasicVector3 vector3_scaled(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.x() * other.x()), + Element(self.y() * other.y()), + Element(self.z() * other.z()) + ); +} +template +inline BasicVector3 operator*(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_scaled(self, other); +} +template +inline void vector3_scale(BasicVector3& self, const BasicVector3& other) +{ + self.x() *= static_cast(other.x()); + self.y() *= static_cast(other.y()); + self.z() *= static_cast(other.z()); +} +template +inline void operator*=(BasicVector3& self, const BasicVector3& other) +{ + vector3_scale(self, other); +} + +template +inline BasicVector3 vector3_scaled(const BasicVector3& self, const OtherElement& scale) +{ + return BasicVector3( + Element(self.x() * scale), + Element(self.y() * scale), + Element(self.z() * scale) + ); +} +template +inline BasicVector3 operator*(const BasicVector3& self, const OtherElement& scale) +{ + return vector3_scaled(self, scale); +} +template +inline void vector3_scale(BasicVector3& self, const OtherElement& scale) +{ + self.x() *= static_cast(scale); + self.y() *= static_cast(scale); + self.z() *= static_cast(scale); +} +template +inline void operator*=(BasicVector3& self, const OtherElement& scale) +{ + vector3_scale(self, scale); +} + +template +inline BasicVector3 vector3_divided(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.x() / other.x()), + Element(self.y() / other.y()), + Element(self.z() / other.z()) + ); +} +template +inline BasicVector3 operator/(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_divided(self, other); +} +template +inline void vector3_divide(BasicVector3& self, const BasicVector3& other) +{ + self.x() /= static_cast(other.x()); + self.y() /= static_cast(other.y()); + self.z() /= static_cast(other.z()); +} +template +inline void operator/=(BasicVector3& self, const BasicVector3& other) +{ + vector3_divide(self, other); +} + +template +inline BasicVector3 vector3_divided(const BasicVector3& self, const OtherElement& divisor) +{ + return BasicVector3( + Element(self.x() / divisor), + Element(self.y() / divisor), + Element(self.z() / divisor) + ); +} +template +inline BasicVector3 operator/(const BasicVector3& self, const OtherElement& divisor) +{ + return vector3_divided(self, divisor); +} +template +inline void vector3_divide(BasicVector3& self, const OtherElement& divisor) +{ + self.x() /= static_cast(divisor); + self.y() /= static_cast(divisor); + self.z() /= static_cast(divisor); +} +template +inline void operator/=(BasicVector3& self, const OtherElement& divisor) +{ + vector3_divide(self, divisor); +} + +template +inline double vector3_dot(const BasicVector3& self, const BasicVector3& other) +{ + return self.x() * other.x() + self.y() * other.y() + self.z() * other.z(); +} + +template +inline BasicVector3 vector3_mid(const BasicVector3& begin, const BasicVector3& end) +{ + return vector3_scaled(vector3_added(begin, end), 0.5); +} + +template +inline BasicVector3 vector3_cross(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.y() * other.z() - self.z() * other.y()), + Element(self.z() * other.x() - self.x() * other.z()), + Element(self.x() * other.y() - self.y() * other.x()) + ); +} + +template +inline BasicVector3 vector3_negated(const BasicVector3& self) +{ + return BasicVector3(-self.x(), -self.y(), -self.z()); +} + +template +inline void vector3_negate(BasicVector3& self) +{ + self = vector3_negated(self); +} + +template +inline double vector3_length_squared(const BasicVector3& self) +{ + return vector3_dot(self, self); +} + +template +inline double vector3_length(const BasicVector3& self) +{ + return sqrt(vector3_length_squared(self)); +} + +template +inline Element float_divided(Element f, Element other) +{ + //ASSERT_MESSAGE(other != 0, "float_divided: invalid divisor"); + return f / other; +} + +template +inline BasicVector3 vector3_normalised(const BasicVector3& self) +{ + return vector3_scaled(self, float_divided(1.0, vector3_length(self))); +} + +template +inline void vector3_normalise(BasicVector3& self) +{ + self = vector3_normalised(self); +} + + +template +inline BasicVector3 vector3_snapped(const BasicVector3& self) +{ + return BasicVector3( + Element(float_to_integer(self.x())), + Element(float_to_integer(self.y())), + Element(float_to_integer(self.z())) + ); +} +template +inline void vector3_snap(BasicVector3& self) +{ + self = vector3_snapped(self); +} +template +inline BasicVector3 vector3_snapped(const BasicVector3& self, const OtherElement& snap) +{ + return BasicVector3( + Element(float_snapped(self.x(), snap)), + Element(float_snapped(self.y(), snap)), + Element(float_snapped(self.z(), snap)) + ); +} +template +inline void vector3_snap(BasicVector3& self, const OtherElement& snap) +{ + self = vector3_snapped(self, snap); +} + +inline Vector3 vector3_for_spherical(double theta, double phi) +{ + return Vector3( + static_cast(cos(theta) * cos(phi)), + static_cast(sin(theta) * cos(phi)), + static_cast(sin(phi)) + ); +} + + +/// \brief A 4-element vector stored in single-precision floating-point. +class Vector4 +{ + float m_elements[4]; +public: + + Vector4() + { + } + Vector4(float x_, float y_, float z_, float w_) + { + x() = x_; + y() = y_; + z() = z_; + w() = w_; + } + Vector4(const Vector3& self, float w_) + { + x() = self.x(); + y() = self.y(); + z() = self.z(); + w() = w_; + } + + float& x() + { + return m_elements[0]; + } + float x() const + { + return m_elements[0]; + } + float& y() + { + return m_elements[1]; + } + float y() const + { + return m_elements[1]; + } + float& z() + { + return m_elements[2]; + } + float z() const + { + return m_elements[2]; + } + float& w() + { + return m_elements[3]; + } + float w() const + { + return m_elements[3]; + } + + float index(std::size_t i) const + { + return m_elements[i]; + } + float& index(std::size_t i) + { + return m_elements[i]; + } + float operator[](std::size_t i) const + { + return m_elements[i]; + } + float& operator[](std::size_t i) + { + return m_elements[i]; + } + + operator Vector3&() + { + return reinterpret_cast(*this); + } + operator const Vector3&() const + { + return reinterpret_cast(*this); + } + + bool operator==(const Vector4& other) const + { + return x() == other.x() && y() == other.y() && z() == other.z() && w() == other.w(); + } +}; + +inline float* vector4_to_array(Vector4& self) +{ + return reinterpret_cast(&self); +} +inline const float* vector4_to_array(const Vector4& self) +{ + return reinterpret_cast(&self); +} + +inline Vector3& vector4_to_vector3(Vector4& self) +{ + return reinterpret_cast(self); +} +inline const Vector3& vector4_to_vector3(const Vector4& self) +{ + return reinterpret_cast(self); +} + +inline Vector4 vector4_added(const Vector4& self, const Vector4& other) +{ + return Vector4( + float(self.x() + other.x()), + float(self.y() + other.y()), + float(self.z() + other.z()), + float(self.w() + other.w()) + ); +} +inline Vector4 operator+(const Vector4& self, const Vector4& other) +{ + return vector4_added(self, other); +} +inline void vector4_add(Vector4& self, const Vector4& other) +{ + self.x() += static_cast(other.x()); + self.y() += static_cast(other.y()); + self.z() += static_cast(other.z()); + self.w() += static_cast(other.w()); +} +inline void operator+=(Vector4& self, const Vector4& other) +{ + vector4_add(self, other); +} + +inline Vector4 vector4_subtracted(const Vector4& self, const Vector4& other) +{ + return Vector4( + float(self.x() - other.x()), + float(self.y() - other.y()), + float(self.z() - other.z()), + float(self.w() - other.w()) + ); +} +inline Vector4 operator-(const Vector4& self, const Vector4& other) +{ + return vector4_subtracted(self, other); +} +inline void vector4_subtract(Vector4& self, const Vector4& other) +{ + self.x() -= static_cast(other.x()); + self.y() -= static_cast(other.y()); + self.z() -= static_cast(other.z()); + self.w() -= static_cast(other.w()); +} +inline void operator-=(Vector4& self, const Vector4& other) +{ + vector4_subtract(self, other); +} + +inline Vector4 vector4_scaled(const Vector4& self, const Vector4& other) +{ + return Vector4( + float(self.x() * other.x()), + float(self.y() * other.y()), + float(self.z() * other.z()), + float(self.w() * other.w()) + ); +} +inline Vector4 operator*(const Vector4& self, const Vector4& other) +{ + return vector4_scaled(self, other); +} +inline void vector4_scale(Vector4& self, const Vector4& other) +{ + self.x() *= static_cast(other.x()); + self.y() *= static_cast(other.y()); + self.z() *= static_cast(other.z()); + self.w() *= static_cast(other.w()); +} +inline void operator*=(Vector4& self, const Vector4& other) +{ + vector4_scale(self, other); +} + +inline Vector4 vector4_scaled(const Vector4& self, const float& scale) +{ + return Vector4( + float(self.x() * scale), + float(self.y() * scale), + float(self.z() * scale), + float(self.w() * scale) + ); +} +inline Vector4 operator*(const Vector4& self, const float& scale) +{ + return vector4_scaled(self, scale); +} +inline void vector4_scale(Vector4& self, const float& scale) +{ + self.x() *= static_cast(scale); + self.y() *= static_cast(scale); + self.z() *= static_cast(scale); + self.w() *= static_cast(scale); +} +inline void operator*=(Vector4& self, const float& scale) +{ + vector4_scale(self, scale); +} + +inline Vector4 vector4_divided(const Vector4& self, const float& divisor) +{ + return Vector4( + float(self.x() / divisor), + float(self.y() / divisor), + float(self.z() / divisor), + float(self.w() / divisor) + ); +} +inline Vector4 operator/(const Vector4& self, const float& divisor) +{ + return vector4_divided(self, divisor); +} +inline void vector4_divide(Vector4& self, const float& divisor) +{ + self.x() /= static_cast(divisor); + self.y() /= static_cast(divisor); + self.z() /= static_cast(divisor); + self.w() /= static_cast(divisor); +} +inline void operator/=(Vector4& self, const float& divisor) +{ + vector4_divide(self, divisor); +} + +#endif diff --git a/tools/urt/libs/mathlib.h b/tools/urt/libs/mathlib.h new file mode 100644 index 00000000..682088f2 --- /dev/null +++ b/tools/urt/libs/mathlib.h @@ -0,0 +1,425 @@ +/* +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h +#include + +#ifdef __cplusplus + +// start declarations of functions defined in C library. +extern "C" +{ + +#endif + +#include "bytebool.h" + +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec5_t[5]; +typedef vec_t vec4_t[4]; + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +// plane types are used to speed some tests +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 +#define PLANE_NON_AXIAL 3 + +#define Q_PI 3.14159265358979323846f + +extern const vec3_t vec3_origin; + +extern const vec3_t g_vec3_axis_x; +extern const vec3_t g_vec3_axis_y; +extern const vec3_t g_vec3_axis_z; + +#define EQUAL_EPSILON 0.001 + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorIncrement(a,b) ((b)[0]+=(a)[0],(b)[1]+=(a)[1],(b)[2]+=(a)[2]) +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define VectorSet(v, a, b, c) ((v)[0]=(a),(v)[1]=(b),(v)[2]=(c)) +#define VectorScale(a,b,c) ((c)[0]=(b)*(a)[0],(c)[1]=(b)*(a)[1],(c)[2]=(b)*(a)[2]) +#define VectorMid(a,b,c) ((c)[0]=((a)[0]+(b)[0])*0.5f,(c)[1]=((a)[1]+(b)[1])*0.5f,(c)[2]=((a)[2]+(b)[2])*0.5f) +#define VectorNegate(a,b) ((b)[0]=-(a)[0],(b)[1]=-(a)[1],(b)[2]=-(a)[2]) +#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) +#define VectorClear(x) ((x)[0]=(x)[1]=(x)[2]=0) + +#define FLOAT_SNAP(f,snap) ( (float)( floor( (f) / (snap) + 0.5 ) * (snap) ) ) +#define FLOAT_TO_INTEGER(f) ( (float)( floor( (f) + 0.5 ) ) ) + +#define Q_rint(in) ((vec_t)floor(in+0.5)) + +qboolean VectorCompare (const vec3_t v1, const vec3_t v2); + +vec_t VectorLength(const vec3_t v); + +void VectorMA( const vec3_t va, vec_t scale, const vec3_t vb, vec3_t vc ); + +void _CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (const vec3_t in, vec3_t out); +vec_t ColorNormalize( const vec3_t in, vec3_t out ); +void VectorInverse (vec3_t v); +void VectorPolar(vec3_t v, float radius, float theta, float phi); + +// default snapping, to 1 +void VectorSnap(vec3_t v); + +// integer snapping +void VectorISnap(vec3_t point, int snap); + +// Gef: added snap to float for sub-integer grid sizes +// TTimo: we still use the int version of VectorSnap when possible +// to avoid potential rounding issues +// TTimo: renaming to VectorFSnap for C implementation +void VectorFSnap(vec3_t point, float snap); + +// NOTE: added these from Ritual's Q3Radiant +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); + + +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +void VectorToAngles( vec3_t vec, vec3_t angles ); + +#define ZERO_EPSILON 1.0E-6 +#define RAD2DEGMULT 57.29577951308232f +#define DEG2RADMULT 0.01745329251994329f +#define RAD2DEG( a ) ( (a) * RAD2DEGMULT ) +#define DEG2RAD( a ) ( (a) * DEG2RADMULT ) + +void VectorRotate (vec3_t vIn, vec3_t vRotation, vec3_t out); +void VectorRotateOrigin (vec3_t vIn, vec3_t vRotation, vec3_t vOrigin, vec3_t out); + +// some function merged from tools mathlib code + +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ); +void NormalToLatLong( const vec3_t normal, byte bytes[2] ); +int PlaneTypeForNormal (vec3_t normal); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); + + +/*! +\todo +FIXME test calls such as intersect tests should be named test_ +*/ + +typedef vec_t m3x3_t[9]; +/*!NOTE +m4x4 looks like this.. + + x y z +x axis ( 0 1 2) +y axis ( 4 5 6) +z axis ( 8 9 10) +translation (12 13 14) +scale ( 0 5 10) +*/ +typedef vec_t m4x4_t[16]; + +#define M4X4_INDEX(m,row,col) (m[(col<<2)+row]) + +typedef enum { eXYZ, eYZX, eZXY, eXZY, eYXZ, eZYX } eulerOrder_t; + +#define CLIP_PASS 0x00 // 000000 +#define CLIP_LT_X 0x01 // 000001 +#define CLIP_GT_X 0x02 // 000010 +#define CLIP_LT_Y 0x04 // 000100 +#define CLIP_GT_Y 0x08 // 001000 +#define CLIP_LT_Z 0x10 // 010000 +#define CLIP_GT_Z 0x20 // 100000 +#define CLIP_FAIL 0x3F // 111111 +typedef unsigned char clipmask_t; + +extern const m4x4_t g_m4x4_identity; + +#define M4X4_COPY(dst,src) (\ +(dst)[0]=(src)[0],\ +(dst)[1]=(src)[1],\ +(dst)[2]=(src)[2],\ +(dst)[3]=(src)[3],\ +(dst)[4]=(src)[4],\ +(dst)[5]=(src)[5],\ +(dst)[6]=(src)[6],\ +(dst)[7]=(src)[7],\ +(dst)[8]=(src)[8],\ +(dst)[9]=(src)[9],\ +(dst)[10]=(src)[10],\ +(dst)[11]=(src)[11],\ +(dst)[12]=(src)[12],\ +(dst)[13]=(src)[13],\ +(dst)[14]=(src)[14],\ +(dst)[15]=(src)[15]) + +typedef enum +{ + eRightHanded = 0, + eLeftHanded = 1, +} +m4x4Handedness_t; + +m4x4Handedness_t m4x4_handedness(const m4x4_t matrix); + +/*! assign other m4x4 to this m4x4 */ +void m4x4_assign(m4x4_t matrix, const m4x4_t other); + +// constructors +/*! create m4x4 as identity matrix */ +void m4x4_identity(m4x4_t matrix); +/*! create m4x4 as a translation matrix, for a translation vec3 */ +void m4x4_translation_for_vec3(m4x4_t matrix, const vec3_t translation); +/*! create m4x4 as a rotation matrix, for an euler angles (degrees) vec3 */ +void m4x4_rotation_for_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order); +/*! create m4x4 as a scaling matrix, for a scale vec3 */ +void m4x4_scale_for_vec3(m4x4_t matrix, const vec3_t scale); +/*! create m4x4 as a rotation matrix, for a quaternion vec4 */ +void m4x4_rotation_for_quat(m4x4_t matrix, const vec4_t rotation); +/*! create m4x4 as a rotation matrix, for an axis vec3 and an angle (radians) */ +void m4x4_rotation_for_axisangle(m4x4_t matrix, const vec3_t axis, double angle); +/*! generate a perspective matrix by specifying the view frustum */ +void m4x4_frustum(m4x4_t matrix, vec_t left, vec_t right, vec_t bottom, vec_t top, vec_t nearval, vec_t farval); + +// a valid m4x4 to access is always first argument +/*! extract translation vec3 from matrix */ +void m4x4_get_translation_vec3(const m4x4_t matrix, vec3_t translation); +/*! extract euler rotation angles from a rotation-only matrix */ +void m4x4_get_rotation_vec3(const m4x4_t matrix, vec3_t euler, eulerOrder_t order); +/*! extract scale vec3 from matrix */ +void m4x4_get_scale_vec3(const m4x4_t matrix, vec3_t scale); +/*! extract translation/euler/scale from an orthogonal matrix. NOTE: requires right-handed axis-base */ +void m4x4_get_transform_vec3(const m4x4_t matrix, vec3_t translation, vec3_t euler, eulerOrder_t order, vec3_t scale); + +// a valid m4x4 to be modified is always first argument +/*! translate m4x4 by a translation vec3 */ +void m4x4_translate_by_vec3(m4x4_t matrix, const vec3_t translation); +/*! rotate m4x4 by a euler (degrees) vec3 */ +void m4x4_rotate_by_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order); +/*! scale m4x4 by a scaling vec3 */ +void m4x4_scale_by_vec3(m4x4_t matrix, const vec3_t scale); +/*! rotate m4x4 by a quaternion vec4 */ +void m4x4_rotate_by_quat(m4x4_t matrix, const vec4_t rotation); +/*! rotate m4x4 by an axis vec3 and an angle (radians) */ +void m4x4_rotate_by_axisangle(m4x4_t matrix, const vec3_t axis, double angle); +/*! transform m4x4 by translation/eulerZYX/scaling vec3 (transform = scale * eulerZ * eulerY * eulerX * translation) */ +void m4x4_transform_by_vec3(m4x4_t matrix, const vec3_t translation, const vec3_t euler, eulerOrder_t order, const vec3_t scale); +/*! rotate m4x4 around a pivot point by eulerZYX vec3 */ +void m4x4_pivoted_rotate_by_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order, const vec3_t pivotpoint); +/*! scale m4x4 around a pivot point by scaling vec3 */ +void m4x4_pivoted_scale_by_vec3(m4x4_t matrix, const vec3_t scale, const vec3_t pivotpoint); +/*! transform m4x4 around a pivot point by translation/eulerZYX/scaling vec3 */ +void m4x4_pivoted_transform_by_vec3(m4x4_t matrix, const vec3_t translation, const vec3_t euler, eulerOrder_t order, const vec3_t scale, const vec3_t pivotpoint); +/*! transform m4x4 around a pivot point by translation/rotation/scaling vec3 */ +void m4x4_pivoted_transform_by_rotation(m4x4_t matrix, const vec3_t translation, const m4x4_t rotation, const vec3_t scale, const vec3_t pivotpoint); +/*! rotate m4x4 around a pivot point by quaternion vec4 */ +void m4x4_pivoted_rotate_by_quat(m4x4_t matrix, const vec4_t quat, const vec3_t pivotpoint); +/*! rotate m4x4 around a pivot point by axis vec3 and angle (radians) */ +void m4x4_pivoted_rotate_by_axisangle(m4x4_t matrix, const vec3_t axis, double angle, const vec3_t pivotpoint); + +/*! postmultiply m4x4 by another m4x4 */ +void m4x4_multiply_by_m4x4(m4x4_t matrix, const m4x4_t matrix_src); +/*! premultiply m4x4 by another m4x4 */ +void m4x4_premultiply_by_m4x4(m4x4_t matrix, const m4x4_t matrix_src); +/*! postmultiply orthogonal m4x4 by another orthogonal m4x4 */ +void m4x4_orthogonal_multiply_by_m4x4(m4x4_t matrix, const m4x4_t matrix_src); +/*! premultiply orthogonal m4x4 by another orthogonal m4x4 */ +void m4x4_orthogonal_premultiply_by_m4x4(m4x4_t matrix, const m4x4_t matrix_src); + +/*! multiply a point (x,y,z,1) by matrix */ +void m4x4_transform_point(const m4x4_t matrix, vec3_t point); +/*! multiply a normal (x,y,z,0) by matrix */ +void m4x4_transform_normal(const m4x4_t matrix, vec3_t normal); +/*! multiply a vec4 (x,y,z,w) by matrix */ +void m4x4_transform_vec4(const m4x4_t matrix, vec4_t vector); + +/*! multiply a point (x,y,z,1) by matrix */ +void m4x4_transform_point(const m4x4_t matrix, vec3_t point); +/*! multiply a normal (x,y,z,0) by matrix */ +void m4x4_transform_normal(const m4x4_t matrix, vec3_t normal); + +/*! transpose a m4x4 */ +void m4x4_transpose(m4x4_t matrix); +/*! invert an orthogonal 4x3 subset of a 4x4 matrix */ +int m4x4_orthogonal_invert(m4x4_t matrix); +/*! invert any m4x4 using Kramer's rule.. return 1 if matrix is singular, else return 0 */ +int m4x4_invert(m4x4_t matrix); + +/*! clip a point (x,y,z,1) by canonical matrix */ +clipmask_t m4x4_clip_point(const m4x4_t matrix, const vec3_t point, vec4_t clipped); +/*! device-space polygon for clipped triangle */ +unsigned int m4x4_clip_triangle(const m4x4_t matrix, const vec3_t p0, const vec3_t p1, const vec3_t p2, vec4_t clipped[9]); +/*! device-space line for clipped line */ +unsigned int m4x4_clip_line(const m4x4_t matrix, const vec3_t p0, const vec3_t p1, vec4_t clipped[2]); + + +//! quaternion identity +void quat_identity(vec4_t quat); +//! quaternion from two unit vectors +void quat_for_unit_vectors(vec4_t quat, const vec3_t from, const vec3_t to); +//! quaternion from axis and angle (radians) +void quat_for_axisangle(vec4_t quat, const vec3_t axis, double angle); +//! concatenates two rotations.. equivalent to m4x4_multiply_by_m4x4 .. postmultiply.. the right-hand side is the first rotation performed +void quat_multiply_by_quat(vec4_t quat, const vec4_t other); +//! negate a quaternion +void quat_conjugate(vec4_t quat); +//! normalise a quaternion +void quat_normalise(vec4_t quat); + + + +/*! +\todo object/ray intersection functions should maybe return a point rather than a distance? +*/ + +/*! +aabb_t - "axis-aligned" bounding box... + origin: centre of bounding box... + extents: +/- extents of box from origin... +*/ +typedef struct aabb_s +{ + vec3_t origin; + vec3_t extents; +} aabb_t; + +extern const aabb_t g_aabb_null; + +/*! +bbox_t - oriented bounding box... + aabb: axis-aligned bounding box... + axes: orientation axes... +*/ +typedef struct bbox_s +{ + aabb_t aabb; + vec3_t axes[3]; + vec_t radius; +} bbox_t; + +/*! +ray_t - origin point and direction unit-vector +*/ +typedef struct ray_s +{ + vec3_t origin; + vec3_t direction; +} ray_t; + +/*! +line_t - centre point and displacement of end point from centre +*/ +typedef struct line_s +{ + vec3_t origin; + vec3_t extents; +} line_t; + + +/*! Generate line from start/end points. */ +void line_construct_for_vec3(line_t* line, const vec3_t start, const vec3_t end); +/*! Return 2 if line is behind plane, else return 1 if line intersects plane, else return 0. */ +int line_test_plane(const line_t* line, const vec4_t plane); + +/*! Generate AABB from min/max. */ +void aabb_construct_for_vec3(aabb_t* aabb, const vec3_t min, const vec3_t max); +/*! Initialise AABB to negative size. */ +void aabb_clear(aabb_t* aabb); + +/*! Extend AABB to include point. */ +void aabb_extend_by_point(aabb_t* aabb, const vec3_t point); +/*! Extend AABB to include aabb_src. */ +void aabb_extend_by_aabb(aabb_t* aabb, const aabb_t* aabb_src); +/*! Extend AABB by +/- extension vector. */ +void aabb_extend_by_vec3(aabb_t* aabb, vec3_t extension); + +/*! Return 2 if point is inside, else 1 if point is on surface, else 0. */ +int aabb_test_point(const aabb_t* aabb, const vec3_t point); +/*! Return 2 if aabb_src intersects, else 1 if aabb_src touches exactly, else 0. */ +int aabb_test_aabb(const aabb_t* aabb, const aabb_t* aabb_src); +/*! Return 2 if aabb is behind plane, else 1 if aabb intersects plane, else 0. */ +int aabb_test_plane(const aabb_t* aabb, const float* plane); +/*! Return 1 if aabb intersects ray, else 0... dist = closest intersection. */ +int aabb_intersect_ray(const aabb_t* aabb, const ray_t* ray, vec3_t intersection); +/*! Return 1 if aabb intersects ray, else 0. Faster, but does not provide point of intersection */ +int aabb_test_ray(const aabb_t* aabb, const ray_t* ray); + +/*! Return 2 if oriented aabb is behind plane, else 1 if aabb intersects plane, else 0. */ +int aabb_oriented_intersect_plane(const aabb_t* aabb, const m4x4_t transform, const vec_t* plane); + +/*! Calculate the corners of the aabb. */ +void aabb_corners(const aabb_t* aabb, vec3_t corners[8]); + +/*! (deprecated) Generate AABB from oriented bounding box. */ +void aabb_for_bbox(aabb_t* aabb, const bbox_t* bbox); +/*! (deprecated) Generate AABB from 2-dimensions of min/max, specified by axis. */ +void aabb_for_area(aabb_t* aabb, vec3_t area_tl, vec3_t area_br, int axis); +/*! Generate AABB to contain src* transform. NOTE: transform must be orthogonal */ +void aabb_for_transformed_aabb(aabb_t* dst, const aabb_t* src, const m4x4_t transform); + +/*! Update bounding-sphere radius. */ +void bbox_update_radius(bbox_t* bbox); +/*! Generate oriented bounding box from AABB and transformation matrix. */ +/*!\todo Remove need to specify eulerZYX/scale. */ +void bbox_for_oriented_aabb(bbox_t* bbox, const aabb_t* aabb, + const m4x4_t matrix, const vec3_t eulerZYX, const vec3_t scale); +/*! Return 2 if bbox is behind plane, else return 1 if bbox intersects plane, else return 0. */ +int bbox_intersect_plane(const bbox_t* bbox, const vec_t* plane); + + +/*! Generate a ray from an origin point and a direction unit-vector */ +void ray_construct_for_vec3(ray_t* ray, const vec3_t origin, const vec3_t direction); + +/*! Transform a ray */ +void ray_transform(ray_t* ray, const m4x4_t matrix); + +/*! distance from ray origin in ray direction to point. FLT_MAX if no intersection. */ +vec_t ray_intersect_point(const ray_t* ray, const vec3_t point, vec_t epsilon, vec_t divergence); +/*! distance from ray origin in ray direction to triangle. FLT_MAX if no intersection. */ +vec_t ray_intersect_triangle(const ray_t* ray, qboolean bCullBack, const vec3_t vert0, const vec3_t vert1, const vec3_t vert2); +/*! distance from ray origin in ray direction to plane. */ +vec_t ray_intersect_plane(const ray_t* ray, const vec3_t normal, vec_t dist); + + +int plane_intersect_planes(const vec4_t plane1, const vec4_t plane2, const vec4_t plane3, vec3_t intersection); + + +#ifdef __cplusplus +} +#endif + +#endif /* __MATHLIB__ */ diff --git a/tools/urt/libs/mathlib/bbox.c b/tools/urt/libs/mathlib/bbox.c new file mode 100644 index 00000000..4b5a9fbc --- /dev/null +++ b/tools/urt/libs/mathlib/bbox.c @@ -0,0 +1,465 @@ +/* +This code provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "mathlib.h" + +const aabb_t g_aabb_null = { + { 0, 0, 0, }, + { -FLT_MAX, -FLT_MAX, -FLT_MAX, }, +}; + +void aabb_construct_for_vec3(aabb_t *aabb, const vec3_t min, const vec3_t max) +{ + VectorMid(min, max, aabb->origin); + VectorSubtract(max, aabb->origin, aabb->extents); +} + +void aabb_clear(aabb_t *aabb) +{ + VectorCopy(g_aabb_null.origin, aabb->origin); + VectorCopy(g_aabb_null.extents, aabb->extents); +} + +void aabb_extend_by_point(aabb_t *aabb, const vec3_t point) +{ +#if 1 + int i; + vec_t min, max, displacement; + for(i=0; i<3; i++) + { + displacement = point[i] - aabb->origin[i]; + if(fabs(displacement) > aabb->extents[i]) + { + if(aabb->extents[i] < 0) // degenerate + { + min = max = point[i]; + } + else if(displacement > 0) + { + min = aabb->origin[i] - aabb->extents[i]; + max = aabb->origin[i] + displacement; + } + else + { + max = aabb->origin[i] + aabb->extents[i]; + min = aabb->origin[i] + displacement; + } + aabb->origin[i] = (min + max) * 0.5f; + aabb->extents[i] = max - aabb->origin[i]; + } + } +#else + unsigned int i; + for(i=0; i<3; ++i) + { + if(aabb->extents[i] < 0) // degenerate + { + aabb->origin[i] = point[i]; + aabb->extents[i] = 0; + } + else + { + vec_t displacement = point[i] - aabb->origin[i]; + vec_t increment = (vec_t)fabs(displacement) - aabb->extents[i]; + if(increment > 0) + { + increment *= (vec_t)((displacement > 0) ? 0.5 : -0.5); + aabb->extents[i] += increment; + aabb->origin[i] += increment; + } + } + } +#endif +} + +void aabb_extend_by_aabb(aabb_t *aabb, const aabb_t *aabb_src) +{ + int i; + vec_t min, max, displacement, difference; + for(i=0; i<3; i++) + { + displacement = aabb_src->origin[i] - aabb->origin[i]; + difference = aabb_src->extents[i] - aabb->extents[i]; + if(aabb->extents[i] < 0 + || difference >= fabs(displacement)) + { + // 2nd contains 1st + aabb->extents[i] = aabb_src->extents[i]; + aabb->origin[i] = aabb_src->origin[i]; + } + else if(aabb_src->extents[i] < 0 + || -difference >= fabs(displacement)) + { + // 1st contains 2nd + continue; + } + else + { + // not contained + if(displacement > 0) + { + min = aabb->origin[i] - aabb->extents[i]; + max = aabb_src->origin[i] + aabb_src->extents[i]; + } + else + { + min = aabb_src->origin[i] - aabb_src->extents[i]; + max = aabb->origin[i] + aabb->extents[i]; + } + aabb->origin[i] = (min + max) * 0.5f; + aabb->extents[i] = max - aabb->origin[i]; + } + } +} + +void aabb_extend_by_vec3(aabb_t *aabb, vec3_t extension) +{ + VectorAdd(aabb->extents, extension, aabb->extents); +} + +int aabb_test_point(const aabb_t *aabb, const vec3_t point) +{ + int i; + for(i=0; i<3; i++) + if(fabs(point[i] - aabb->origin[i]) >= aabb->extents[i]) + return 0; + return 1; +} + +int aabb_test_aabb(const aabb_t *aabb, const aabb_t *aabb_src) +{ + int i; + for (i=0; i<3; i++) + if ( fabs(aabb_src->origin[i] - aabb->origin[i]) > (fabs(aabb->extents[i]) + fabs(aabb_src->extents[i])) ) + return 0; + return 1; +} + +int aabb_test_plane(const aabb_t *aabb, const float *plane) +{ + float fDist, fIntersect; + + // calc distance of origin from plane + fDist = DotProduct(plane, aabb->origin) + plane[3]; + + // calc extents distance relative to plane normal + fIntersect = (vec_t)(fabs(plane[0] * aabb->extents[0]) + fabs(plane[1] * aabb->extents[1]) + fabs(plane[2] * aabb->extents[2])); + // accept if origin is less than or equal to this distance + if (fabs(fDist) < fIntersect) return 1; // partially inside + else if (fDist < 0) return 2; // totally inside + return 0; // totally outside +} + +/* +Fast Ray-Box Intersection +by Andrew Woo +from "Graphics Gems", Academic Press, 1990 +*/ + +#define NUMDIM 3 +#define RIGHT 0 +#define LEFT 1 +#define MIDDLE 2 + +int aabb_intersect_ray(const aabb_t *aabb, const ray_t *ray, vec3_t intersection) +{ + int inside = 1; + char quadrant[NUMDIM]; + register int i; + int whichPlane; + double maxT[NUMDIM]; + double candidatePlane[NUMDIM]; + + const float *origin = ray->origin; + const float *direction = ray->direction; + + /* Find candidate planes; this loop can be avoided if + rays cast all from the eye(assume perpsective view) */ + for (i=0; iorigin[i] - aabb->extents[i])) + { + quadrant[i] = LEFT; + candidatePlane[i] = (aabb->origin[i] - aabb->extents[i]); + inside = 0; + } + else if (origin[i] > (aabb->origin[i] + aabb->extents[i])) + { + quadrant[i] = RIGHT; + candidatePlane[i] = (aabb->origin[i] + aabb->extents[i]); + inside = 0; + } + else + { + quadrant[i] = MIDDLE; + } + } + + /* Ray origin inside bounding box */ + if(inside == 1) + { + VectorCopy(ray->origin, intersection); + return 1; + } + + + /* Calculate T distances to candidate planes */ + for (i = 0; i < NUMDIM; i++) + { + if (quadrant[i] != MIDDLE && direction[i] !=0.) + maxT[i] = (candidatePlane[i] - origin[i]) / direction[i]; + else + maxT[i] = -1.; + } + + /* Get largest of the maxT's for final choice of intersection */ + whichPlane = 0; + for (i = 1; i < NUMDIM; i++) + if (maxT[whichPlane] < maxT[i]) + whichPlane = i; + + /* Check final candidate actually inside box */ + if (maxT[whichPlane] < 0.) + return 0; + for (i = 0; i < NUMDIM; i++) + { + if (whichPlane != i) + { + intersection[i] = (vec_t)(origin[i] + maxT[whichPlane] * direction[i]); + if (fabs(intersection[i] - aabb->origin[i]) > aabb->extents[i]) + return 0; + } + else + { + intersection[i] = (vec_t)candidatePlane[i]; + } + } + + return 1; /* ray hits box */ +} + +int aabb_test_ray(const aabb_t* aabb, const ray_t* ray) +{ + vec3_t displacement, ray_absolute; + vec_t f; + + displacement[0] = ray->origin[0] - aabb->origin[0]; + if(fabs(displacement[0]) > aabb->extents[0] && displacement[0] * ray->direction[0] >= 0.0f) + return 0; + + displacement[1] = ray->origin[1] - aabb->origin[1]; + if(fabs(displacement[1]) > aabb->extents[1] && displacement[1] * ray->direction[1] >= 0.0f) + return 0; + + displacement[2] = ray->origin[2] - aabb->origin[2]; + if(fabs(displacement[2]) > aabb->extents[2] && displacement[2] * ray->direction[2] >= 0.0f) + return 0; + + ray_absolute[0] = (float)fabs(ray->direction[0]); + ray_absolute[1] = (float)fabs(ray->direction[1]); + ray_absolute[2] = (float)fabs(ray->direction[2]); + + f = ray->direction[1] * displacement[2] - ray->direction[2] * displacement[1]; + if((float)fabs(f) > aabb->extents[1] * ray_absolute[2] + aabb->extents[2] * ray_absolute[1]) + return 0; + + f = ray->direction[2] * displacement[0] - ray->direction[0] * displacement[2]; + if((float)fabs(f) > aabb->extents[0] * ray_absolute[2] + aabb->extents[2] * ray_absolute[0]) + return 0; + + f = ray->direction[0] * displacement[1] - ray->direction[1] * displacement[0]; + if((float)fabs(f) > aabb->extents[0] * ray_absolute[1] + aabb->extents[1] * ray_absolute[0]) + return 0; + + return 1; +} + +void aabb_orthogonal_transform(aabb_t* dst, const aabb_t* src, const m4x4_t transform) +{ + VectorCopy(src->origin, dst->origin); + m4x4_transform_point(transform, dst->origin); + + dst->extents[0] = (vec_t)(fabs(transform[0] * src->extents[0]) + + fabs(transform[4] * src->extents[1]) + + fabs(transform[8] * src->extents[2])); + dst->extents[1] = (vec_t)(fabs(transform[1] * src->extents[0]) + + fabs(transform[5] * src->extents[1]) + + fabs(transform[9] * src->extents[2])); + dst->extents[2] = (vec_t)(fabs(transform[2] * src->extents[0]) + + fabs(transform[6] * src->extents[1]) + + fabs(transform[10] * src->extents[2])); +} + +void aabb_for_bbox(aabb_t *aabb, const bbox_t *bbox) +{ + int i; + vec3_t temp[3]; + + VectorCopy(bbox->aabb.origin, aabb->origin); + + // calculate the AABB extents in local coord space from the OBB extents and axes + VectorScale(bbox->axes[0], bbox->aabb.extents[0], temp[0]); + VectorScale(bbox->axes[1], bbox->aabb.extents[1], temp[1]); + VectorScale(bbox->axes[2], bbox->aabb.extents[2], temp[2]); + for(i=0;i<3;i++) aabb->extents[i] = (vec_t)(fabs(temp[0][i]) + fabs(temp[1][i]) + fabs(temp[2][i])); +} + +void aabb_for_area(aabb_t *aabb, vec3_t area_tl, vec3_t area_br, int axis) +{ + aabb_clear(aabb); + aabb->extents[axis] = FLT_MAX; + aabb_extend_by_point(aabb, area_tl); + aabb_extend_by_point(aabb, area_br); +} + +int aabb_oriented_intersect_plane(const aabb_t *aabb, const m4x4_t transform, const vec_t* plane) +{ + vec_t fDist, fIntersect; + + // calc distance of origin from plane + fDist = DotProduct(plane, aabb->origin) + plane[3]; + + // calc extents distance relative to plane normal + fIntersect = (vec_t)(fabs(aabb->extents[0] * DotProduct(plane, transform)) + + fabs(aabb->extents[1] * DotProduct(plane, transform+4)) + + fabs(aabb->extents[2] * DotProduct(plane, transform+8))); + // accept if origin is less than this distance + if (fabs(fDist) < fIntersect) return 1; // partially inside + else if (fDist < 0) return 2; // totally inside + return 0; // totally outside +} + +void aabb_corners(const aabb_t* aabb, vec3_t corners[8]) +{ + vec3_t min, max; + VectorSubtract(aabb->origin, aabb->extents, min); + VectorAdd(aabb->origin, aabb->extents, max); + VectorSet(corners[0], min[0], max[1], max[2]); + VectorSet(corners[1], max[0], max[1], max[2]); + VectorSet(corners[2], max[0], min[1], max[2]); + VectorSet(corners[3], min[0], min[1], max[2]); + VectorSet(corners[4], min[0], max[1], min[2]); + VectorSet(corners[5], max[0], max[1], min[2]); + VectorSet(corners[6], max[0], min[1], min[2]); + VectorSet(corners[7], min[0], min[1], min[2]); +} + + +void bbox_update_radius(bbox_t *bbox) +{ + bbox->radius = VectorLength(bbox->aabb.extents); +} + +void aabb_for_transformed_aabb(aabb_t* dst, const aabb_t* src, const m4x4_t transform) +{ + if(src->extents[0] < 0 + || src->extents[1] < 0 + || src->extents[2] < 0) + { + aabb_clear(dst); + return; + } + + VectorCopy(src->origin, dst->origin); + m4x4_transform_point(transform, dst->origin); + + dst->extents[0] = (vec_t)(fabs(transform[0] * src->extents[0]) + + fabs(transform[4] * src->extents[1]) + + fabs(transform[8] * src->extents[2])); + dst->extents[1] = (vec_t)(fabs(transform[1] * src->extents[0]) + + fabs(transform[5] * src->extents[1]) + + fabs(transform[9] * src->extents[2])); + dst->extents[2] = (vec_t)(fabs(transform[2] * src->extents[0]) + + fabs(transform[6] * src->extents[1]) + + fabs(transform[10] * src->extents[2])); +} + +void bbox_for_oriented_aabb(bbox_t *bbox, const aabb_t *aabb, const m4x4_t matrix, const vec3_t euler, const vec3_t scale) +{ + double rad[3]; + double pi_180 = Q_PI / 180; + double A, B, C, D, E, F, AD, BD; + + VectorCopy(aabb->origin, bbox->aabb.origin); + + m4x4_transform_point(matrix, bbox->aabb.origin); + + bbox->aabb.extents[0] = aabb->extents[0] * scale[0]; + bbox->aabb.extents[1] = aabb->extents[1] * scale[1]; + bbox->aabb.extents[2] = aabb->extents[2] * scale[2]; + + rad[0] = euler[0] * pi_180; + rad[1] = euler[1] * pi_180; + rad[2] = euler[2] * pi_180; + + A = cos(rad[0]); + B = sin(rad[0]); + C = cos(rad[1]); + D = sin(rad[1]); + E = cos(rad[2]); + F = sin(rad[2]); + + AD = A * -D; + BD = B * -D; + + bbox->axes[0][0] = (vec_t)(C*E); + bbox->axes[0][1] = (vec_t)(-BD*E + A*F); + bbox->axes[0][2] = (vec_t)(AD*E + B*F); + bbox->axes[1][0] = (vec_t)(-C*F); + bbox->axes[1][1] = (vec_t)(BD*F + A*E); + bbox->axes[1][2] = (vec_t)(-AD*F + B*E); + bbox->axes[2][0] = (vec_t)D; + bbox->axes[2][1] = (vec_t)(-B*C); + bbox->axes[2][2] = (vec_t)(A*C); + + bbox_update_radius(bbox); +} + +int bbox_intersect_plane(const bbox_t *bbox, const vec_t* plane) +{ + vec_t fDist, fIntersect; + + // calc distance of origin from plane + fDist = DotProduct(plane, bbox->aabb.origin) + plane[3]; + + // trivial accept/reject using bounding sphere + if (fabs(fDist) > bbox->radius) + { + if (fDist < 0) + return 2; // totally inside + else + return 0; // totally outside + } + + // calc extents distance relative to plane normal + fIntersect = (vec_t)(fabs(bbox->aabb.extents[0] * DotProduct(plane, bbox->axes[0])) + + fabs(bbox->aabb.extents[1] * DotProduct(plane, bbox->axes[1])) + + fabs(bbox->aabb.extents[2] * DotProduct(plane, bbox->axes[2]))); + // accept if origin is less than this distance + if (fabs(fDist) < fIntersect) return 1; // partially inside + else if (fDist < 0) return 2; // totally inside + return 0; // totally outside +} diff --git a/tools/urt/libs/mathlib/line.c b/tools/urt/libs/mathlib/line.c new file mode 100644 index 00000000..8838b6da --- /dev/null +++ b/tools/urt/libs/mathlib/line.c @@ -0,0 +1,44 @@ +/* +This code provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "mathlib.h" + +void line_construct_for_vec3(line_t *line, const vec3_t start, const vec3_t end) +{ + VectorMid(start, end, line->origin); + VectorSubtract(end, line->origin, line->extents); +} + +int line_test_plane(const line_t* line, const vec4_t plane) +{ + float fDist; + + // calc distance of origin from plane + fDist = DotProduct(plane, line->origin) + plane[3]; + + // accept if origin is less than or equal to this distance + if (fabs(fDist) < fabs(DotProduct(plane, line->extents))) return 1; // partially inside + else if (fDist < 0) return 2; // totally inside + return 0; // totally outside +} diff --git a/tools/urt/libs/mathlib/linear.c b/tools/urt/libs/mathlib/linear.c new file mode 100644 index 00000000..aa36b49c --- /dev/null +++ b/tools/urt/libs/mathlib/linear.c @@ -0,0 +1,207 @@ + +#include +#include +#include + +#include "mathlib.h" + +#define TINY FLT_MIN + +void lubksb(float **a, int n, int *indx, float b[]) +// Solves the set of n linear equations A.X=B. Here a[n][n] is input, not as the matrix +// A but rather as its LU decomposition determined by the routine ludcmp. indx[n] is input +// as the permutation vector returned by ludcmp. b[n] is input as the right-hand side vector +// B, and returns with the solution vector X. a, n and indx are not modified by this routine +// and can be left in place for successive calls with different right-hand sides b. This routine takes +// into account the possibility that b will begin with many zero elements, so it is efficient for use +// in matrix inversion +{ + int i,ii=-1,ip,j; + float sum; + + for (i=0;i=0) + for (j=ii;j=0;i--) { + sum=b[i]; + for (j=i+1;j big) big=temp; + if (big == 0.0) return 1; + vv[i]=1.0f/big; + } + for (j=0;j= big) { + big=dum; + imax=i; + } + } + if (j != imax) { + for (k=0;k big) big=temp; + if (big == 0.0) nrerror("Singular matrix in routine ludcmp"); + //No nonzero largest element. + vv[i]=1.0/big; //Save the scaling. + } + for (j=1;j<=n;j++) { //This is the loop over columns of Crout's method. + for (i=1;i= big) { + //Is the figure of merit for the pivot better than the best so far? + big=dum; + imax=i; + } + } + if (j != imax) { //Do we need to interchange rows? + for (k=1;k<=n;k++) { Yes, do so... + dum=a[imax][k]; + a[imax][k]=a[j][k]; + a[j][k]=dum; + } + *d = -(*d); //...and change the parity of d. + vv[imax]=vv[j]; //Also interchange the scale factor. + } + indx[j]=imax; + if (a[j][j] == 0.0) a[j][j]=TINY; + //If the pivot element is zero the matrix is singular (at least to the precision of the + // algorithm). For some applications on singular matrices, it is desirable to substitute + // TINY for zero. + if (j != n) { //Now, finally, divide by the pivot element. + dum=1.0/(a[j][j]); + for (i=j+1;i<=n;i++) a[i][j] *= dum; + } + } //Go back for the next column in the reduction. + free_vector(vv,1,n); +} + +void lubksb(float **a, int n, int *indx, float b[]) +//Solves the set of n linear equations A.X = B. Here a[1..n][1..n] is input, not as the matrix +//A but rather as its LU decomposition, determined by the routine ludcmp. indx[1..n] is input +//as the permutation vector returned by ludcmp. b[1..n] is input as the right-hand side vector +//B, and returns with the solution vector X. a, n, and indx are not modi ed by this routine +//and can be left in place for successive calls with di erent right-hand sides b. This routine takes +//into account the possibility that b will begin with many zero elements, so it is e.cient for use +//in matrix inversion. +{ + int i,ii=0,ip,j; + float sum; + for (i=1;i<=n;i++) { //When ii is set to a positive value, it will become the + //index of the first nonvanishing element of b. Wenow + //do the forward substitution, equation (2.3.6). The + //only new wrinkle is to unscramble the permutation + //as we go. + ip=indx[i]; + sum=b[ip]; + b[ip]=b[i]; + if (ii) + for (j=ii;j<=i-1;j++) sum -= a[i][j]*b[j]; + else if (sum) ii=i; //A nonzero element was encountered, so from now on we + //will have to do the sums in the loop above. b[i]=sum; + } + for (i=n;i>=1;i--) { //Now we do the backsubstitution, equation (2.3.7). + sum=b[i]; + for (j=i+1;j<=n;j++) sum -= a[i][j]*b[j]; + b[i]=sum/a[i][i]; //Store a component of the solution vector X. + } //All done! +} + +void bleh() +{ + #define N ... + float **a,**y,d,*col; + int i,j,*indx; + ... + ludcmp(a,N,indx,&d); //Decompose the matrix just once. + for(j=1;j<=N;j++) { //Find inverse by columns. + for(i=1;i<=N;i++) col[i]=0.0; + col[j]=1.0; + lubksb(a,N,indx,col); + for(i=1;i<=N;i++) y[i][j]=col[i]; + } +} +*/ diff --git a/tools/urt/libs/mathlib/m4x4.c b/tools/urt/libs/mathlib/m4x4.c new file mode 100644 index 00000000..5a67a917 --- /dev/null +++ b/tools/urt/libs/mathlib/m4x4.c @@ -0,0 +1,1880 @@ +/* +This code provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "mathlib.h" + +const m4x4_t g_m4x4_identity = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, +}; + +void m4x4_identity(m4x4_t matrix) +{ + matrix[1] = matrix[2] = matrix[3] = + matrix[4] = matrix[6] = matrix[7] = + matrix[8] = matrix[9] = matrix[11] = + matrix[12] = matrix[13] = matrix[14] = 0; + + matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1; +} + +m4x4Handedness_t m4x4_handedness(const m4x4_t matrix) +{ + vec3_t cross; + CrossProduct(matrix+0, matrix+4, cross); + return (DotProduct(matrix+8, cross) < 0) ? eLeftHanded : eRightHanded; +} + +void m4x4_assign(m4x4_t matrix, const m4x4_t other) +{ + M4X4_COPY(matrix, other); +} + +void m4x4_translation_for_vec3(m4x4_t matrix, const vec3_t translation) +{ + matrix[1] = matrix[2] = matrix[3] = + matrix[4] = matrix[6] = matrix[7] = + matrix[8] = matrix[9] = matrix[11] = 0; + + matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1; + + matrix[12] = translation[0]; + matrix[13] = translation[1]; + matrix[14] = translation[2]; +} + +/* +clockwise rotation around X, Y, Z, facing along axis + 1 0 0 cy 0 sy cz sz 0 + 0 cx sx 0 1 0 -sz cz 0 + 0 -sx cx -sy 0 cy 0 0 1 + +rows of Z by cols of Y + cy*cz -sy*cz+sz -sy*sz+cz +-sz*cy -sz*sy+cz + + .. or something like that.. + +final rotation is Z * Y * X + cy*cz -sx*-sy*cz+cx*sz cx*-sy*sz+sx*cz +-cy*sz sx*sy*sz+cx*cz -cx*-sy*sz+sx*cz + sy -sx*cy cx*cy +*/ + +/* transposed +| cy.cz + 0.sz + sy.0 cy.-sz + 0 .cz + sy.0 cy.0 + 0 .0 + sy.1 | +| sx.sy.cz + cx.sz + -sx.cy.0 sx.sy.-sz + cx.cz + -sx.cy.0 sx.sy.0 + cx.0 + -sx.cy.1 | +| -cx.sy.cz + sx.sz + cx.cy.0 -cx.sy.-sz + sx.cz + cx.cy.0 -cx.sy.0 + 0 .0 + cx.cy.1 | +*/ +void m4x4_rotation_for_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order) +{ + double cx, sx, cy, sy, cz, sz; + + cx = cos(DEG2RAD(euler[0])); + sx = sin(DEG2RAD(euler[0])); + cy = cos(DEG2RAD(euler[1])); + sy = sin(DEG2RAD(euler[1])); + cz = cos(DEG2RAD(euler[2])); + sz = sin(DEG2RAD(euler[2])); + + switch(order) + { + case eXYZ: + +#if 1 + + { + matrix[0] = (vec_t)(cy*cz); + matrix[1] = (vec_t)(cy*sz); + matrix[2] = (vec_t)-sy; + matrix[4] = (vec_t)(sx*sy*cz + cx*-sz); + matrix[5] = (vec_t)(sx*sy*sz + cx*cz); + matrix[6] = (vec_t)(sx*cy); + matrix[8] = (vec_t)(cx*sy*cz + sx*sz); + matrix[9] = (vec_t)(cx*sy*sz + -sx*cz); + matrix[10] = (vec_t)(cx*cy); + } + + matrix[12] = matrix[13] = matrix[14] = matrix[3] = matrix[7] = matrix[11] = 0; + matrix[15] = 1; + +#else + + m4x4_identity(matrix); + matrix[5] =(vec_t) cx; matrix[6] =(vec_t) sx; + matrix[9] =(vec_t)-sx; matrix[10]=(vec_t) cx; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[0] =(vec_t) cy; temp[2] =(vec_t)-sy; + temp[8] =(vec_t) sy; temp[10]=(vec_t) cy; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cz; temp[1] =(vec_t) sz; + temp[4] =(vec_t)-sz; temp[5] =(vec_t) cz; + m4x4_premultiply_by_m4x4(matrix, temp); + } +#endif + + break; + + case eYZX: + m4x4_identity(matrix); + matrix[0] =(vec_t) cy; matrix[2] =(vec_t)-sy; + matrix[8] =(vec_t) sy; matrix[10]=(vec_t) cy; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[5] =(vec_t) cx; temp[6] =(vec_t) sx; + temp[9] =(vec_t)-sx; temp[10]=(vec_t) cx; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cz; temp[1] =(vec_t) sz; + temp[4] =(vec_t)-sz; temp[5] =(vec_t) cz; + m4x4_premultiply_by_m4x4(matrix, temp); + } + break; + + case eZXY: + m4x4_identity(matrix); + matrix[0] =(vec_t) cz; matrix[1] =(vec_t) sz; + matrix[4] =(vec_t)-sz; matrix[5] =(vec_t) cz; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[5] =(vec_t) cx; temp[6] =(vec_t) sx; + temp[9] =(vec_t)-sx; temp[10]=(vec_t) cx; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cy; temp[2] =(vec_t)-sy; + temp[8] =(vec_t) sy; temp[10]=(vec_t) cy; + m4x4_premultiply_by_m4x4(matrix, temp); + } + break; + + case eXZY: + m4x4_identity(matrix); + matrix[5] =(vec_t) cx; matrix[6] =(vec_t) sx; + matrix[9] =(vec_t)-sx; matrix[10]=(vec_t) cx; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[0] =(vec_t) cz; temp[1] =(vec_t) sz; + temp[4] =(vec_t)-sz; temp[5] =(vec_t) cz; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cy; temp[2] =(vec_t)-sy; + temp[8] =(vec_t) sy; temp[10]=(vec_t) cy; + m4x4_premultiply_by_m4x4(matrix, temp); + } + break; + + case eYXZ: + +/* transposed +| cy.cz + sx.sy.-sz + -cx.sy.0 0.cz + cx.-sz + sx.0 sy.cz + -sx.cy.-sz + cx.cy.0 | +| cy.sz + sx.sy.cz + -cx.sy.0 0.sz + cx.cz + sx.0 sy.sz + -sx.cy.cz + cx.cy.0 | +| cy.0 + sx.sy.0 + -cx.sy.1 0.0 + cx.0 + sx.1 sy.0 + -sx.cy.0 + cx.cy.1 | +*/ + +#if 1 + + { + matrix[0] = (vec_t)(cy*cz + sx*sy*-sz); + matrix[1] = (vec_t)(cy*sz + sx*sy*cz); + matrix[2] = (vec_t)(-cx*sy); + matrix[4] = (vec_t)(cx*-sz); + matrix[5] = (vec_t)(cx*cz); + matrix[6] = (vec_t)(sx); + matrix[8] = (vec_t)(sy*cz + -sx*cy*-sz); + matrix[9] = (vec_t)(sy*sz + -sx*cy*cz); + matrix[10] = (vec_t)(cx*cy); + } + + matrix[12] = matrix[13] = matrix[14] = matrix[3] = matrix[7] = matrix[11] = 0; + matrix[15] = 1; + +#else + + m4x4_identity(matrix); + matrix[0] =(vec_t) cy; matrix[2] =(vec_t)-sy; + matrix[8] =(vec_t) sy; matrix[10]=(vec_t) cy; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[5] =(vec_t) cx; temp[6] =(vec_t) sx; + temp[9] =(vec_t)-sx; temp[10]=(vec_t) cx; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cz; temp[1] =(vec_t) sz; + temp[4] =(vec_t)-sz; temp[5] =(vec_t) cz; + m4x4_premultiply_by_m4x4(matrix, temp); + } +#endif + break; + + case eZYX: +#if 1 + + { + matrix[0] = (vec_t)(cy*cz); + matrix[4] = (vec_t)(cy*-sz); + matrix[8] = (vec_t)sy; + matrix[1] = (vec_t)(sx*sy*cz + cx*sz); + matrix[5] = (vec_t)(sx*sy*-sz + cx*cz); + matrix[9] = (vec_t)(-sx*cy); + matrix[2] = (vec_t)(cx*-sy*cz + sx*sz); + matrix[6] = (vec_t)(cx*-sy*-sz + sx*cz); + matrix[10] = (vec_t)(cx*cy); + } + + matrix[12] = matrix[13] = matrix[14] = matrix[3] = matrix[7] = matrix[11] = 0; + matrix[15] = 1; + +#else + + m4x4_identity(matrix); + matrix[0] =(vec_t) cz; matrix[1] =(vec_t) sz; + matrix[4] =(vec_t)-sz; matrix[5] =(vec_t) cz; + { + m4x4_t temp; + m4x4_identity(temp); + temp[0] =(vec_t) cy; temp[2] =(vec_t)-sy; + temp[8] =(vec_t) sy; temp[10]=(vec_t) cy; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[5] =(vec_t) cx; temp[6] =(vec_t) sx; + temp[9] =(vec_t)-sx; temp[10]=(vec_t) cx; + m4x4_premultiply_by_m4x4(matrix, temp); + } + +#endif + break; + + } +} + +void m4x4_scale_for_vec3(m4x4_t matrix, const vec3_t scale) +{ + matrix[1] = matrix[2] = matrix[3] = + matrix[4] = matrix[6] = matrix[7] = + matrix[8] = matrix[9] = matrix[11] = + matrix[12] = matrix[13] = matrix[14] = 0; + + matrix[15] = 1; + + matrix[0] = scale[0]; + matrix[5] = scale[1]; + matrix[10] = scale[2]; +} + +void m4x4_rotation_for_quat(m4x4_t matrix, const vec4_t quat) +{ +#if 0 + const double xx = quat[0] * quat[0]; + const double xy = quat[0] * quat[1]; + const double xz = quat[0] * quat[2]; + const double xw = quat[0] * quat[3]; + + const double yy = quat[1] * quat[1]; + const double yz = quat[1] * quat[2]; + const double yw = quat[1] * quat[3]; + + const double zz = quat[2] * quat[2]; + const double zw = quat[2] * quat[3]; + + matrix[0] = 1 - 2 * ( yy + zz ); + matrix[4] = 2 * ( xy - zw ); + matrix[8] = 2 * ( xz + yw ); + + matrix[1] = 2 * ( xy + zw ); + matrix[5] = 1 - 2 * ( xx + zz ); + matrix[9] = 2 * ( yz - xw ); + + matrix[2] = 2 * ( xz - yw ); + matrix[6] = 2 * ( yz + xw ); + matrix[10] = 1 - 2 * ( xx + yy ); +#else + const double x2 = quat[0] + quat[0]; + const double y2 = quat[1] + quat[1]; + const double z2 = quat[2] + quat[2]; + const double xx = quat[0] * x2; + const double xy = quat[0] * y2; + const double xz = quat[0] * z2; + const double yy = quat[1] * y2; + const double yz = quat[1] * z2; + const double zz = quat[2] * z2; + const double wx = quat[3] * x2; + const double wy = quat[3] * y2; + const double wz = quat[3] * z2; + + matrix[0] = (vec_t)( 1.0 - (yy + zz) ); + matrix[4] = (vec_t)(xy - wz); + matrix[8] = (vec_t)(xz + wy); + + matrix[1] = (vec_t)(xy + wz); + matrix[5] = (vec_t)( 1.0 - (xx + zz) ); + matrix[9] = (vec_t)(yz - wx); + + matrix[2] = (vec_t)(xz - wy); + matrix[6] = (vec_t)(yz + wx); + matrix[10] = (vec_t)( 1.0 - (xx + yy) ); +#endif + + matrix[3] = matrix[7] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0; + matrix[15] = 1; +} + +void m4x4_rotation_for_axisangle(m4x4_t matrix, const vec3_t axis, double angle) +{ + vec4_t quat; + quat_for_axisangle(quat, axis, angle); + m4x4_rotation_for_quat(matrix, quat); +} + +void m4x4_frustum(m4x4_t matrix, + vec_t left, vec_t right, + vec_t bottom, vec_t top, + vec_t nearval, vec_t farval) +{ + matrix[0] = (vec_t)( (2*nearval) / (right-left) ); + matrix[1] = 0; + matrix[2] = 0; + matrix[3] = 0; + + matrix[4] = 0; + matrix[5] = (vec_t)( (2*nearval) / (top-bottom) ); + matrix[6] = 0; + matrix[7] = 0; + + matrix[8] = (vec_t)( (right+left) / (right-left) ); + matrix[9] = (vec_t)( (top+bottom) / (top-bottom) ); + matrix[10] = (vec_t)( -(farval+nearval) / (farval-nearval) ); + matrix[11] =-1; + + matrix[12] = 0; + matrix[13] = 0; + matrix[14] = (vec_t)( -(2*farval*nearval) / (farval-nearval) ); + matrix[15] = 0; +} + + +void m4x4_get_translation_vec3(const m4x4_t matrix, vec3_t translation) +{ + translation[0] = matrix[12]; + translation[1] = matrix[13]; + translation[2] = matrix[14]; +} + +void m4x4_get_rotation_vec3(const m4x4_t matrix, vec3_t euler, eulerOrder_t order) +{ + double a, ca; + + switch(order) + { + case eXYZ: + a = asin(-matrix[2]); + ca = cos(a); + euler[1] = (vec_t)RAD2DEG(a); /* Calculate Y-axis angle */ + + if (fabs(ca) > 0.005) /* Gimbal lock? */ + { + /* No, so get Z-axis angle */ + euler[2] = (vec_t)RAD2DEG(atan2(matrix[1] / ca, matrix[0]/ ca)); + + /* Get X-axis angle */ + euler[0] = (vec_t)RAD2DEG(atan2(matrix[6] / ca, matrix[10] / ca)); + } + else /* Gimbal lock has occurred */ + { + /* Set Z-axis angle to zero */ + euler[2] = 0; + + /* And calculate X-axis angle */ + euler[0] = (vec_t)RAD2DEG(atan2(-matrix[9], matrix[5])); + } + break; + case eYZX: + /* NOT IMPLEMENTED */ + break; + case eZXY: + /* NOT IMPLEMENTED */ + break; + case eXZY: + /* NOT IMPLEMENTED */ + break; + case eYXZ: + a = asin(matrix[6]); + ca = cos(a); + euler[0] = (vec_t)RAD2DEG(a); /* Calculate X-axis angle */ + + if (fabs(ca) > 0.005) /* Gimbal lock? */ + { + /* No, so get Y-axis angle */ + euler[1] = (vec_t)RAD2DEG(atan2(-matrix[2] / ca, matrix[10]/ ca)); + + /* Get Z-axis angle */ + euler[2] = (vec_t)RAD2DEG(atan2(-matrix[4] / ca, matrix[5] / ca)); + } + else /* Gimbal lock has occurred */ + { + /* Set Z-axis angle to zero */ + euler[2] = 0; + + /* And calculate Y-axis angle */ + euler[1] = (vec_t)RAD2DEG(atan2(matrix[8], matrix[0])); + } + break; + case eZYX: + a = asin(matrix[8]); + ca = cos(a); + euler[1] = (vec_t)RAD2DEG(a); /* Calculate Y-axis angle */ + + if (fabs(ca) > 0.005) /* Gimbal lock? */ + { + /* No, so get X-axis angle */ + euler[0] = (vec_t)RAD2DEG(atan2(-matrix[9] / ca, matrix[10]/ ca)); + + /* Get Z-axis angle */ + euler[2] = (vec_t)RAD2DEG(atan2(-matrix[4] / ca, matrix[0] / ca)); + } + else /* Gimbal lock has occurred */ + { + /* Set X-axis angle to zero */ + euler[0] = 0; + + /* And calculate Z-axis angle */ + euler[2] = (vec_t)RAD2DEG(atan2(matrix[1], matrix[5])); + } + break; + } + + /* return only positive angles in [0,360] */ + if (euler[0] < 0) euler[0] += 360; + if (euler[1] < 0) euler[1] += 360; + if (euler[2] < 0) euler[2] += 360; +} + +void m4x4_get_scale_vec3(const m4x4_t matrix, vec3_t scale) +{ + scale[0] = VectorLength(matrix+0); + scale[1] = VectorLength(matrix+4); + scale[2] = VectorLength(matrix+8); +} + +void m4x4_get_transform_vec3(const m4x4_t matrix, vec3_t translation, vec3_t euler, eulerOrder_t order, vec3_t scale) +{ + m4x4_t normalised; + m4x4_assign(normalised, matrix); + scale[0] = VectorNormalize(normalised+0, normalised+0); + scale[1] = VectorNormalize(normalised+4, normalised+4); + scale[2] = VectorNormalize(normalised+8, normalised+8); + if(m4x4_handedness(normalised) == eLeftHanded) + { + VectorNegate(normalised+0, normalised+0); + VectorNegate(normalised+4, normalised+4); + VectorNegate(normalised+8, normalised+8); + scale[0] = -scale[0]; + scale[1] = -scale[1]; + scale[2] = -scale[2]; + } + m4x4_get_rotation_vec3(normalised, euler, order); + m4x4_get_translation_vec3(matrix, translation); +} + +void m4x4_translate_by_vec3(m4x4_t matrix, const vec3_t translation) +{ + m4x4_t temp; + m4x4_translation_for_vec3(temp, translation); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_rotate_by_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order) +{ + m4x4_t temp; + m4x4_rotation_for_vec3(temp, euler, order); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_scale_by_vec3(m4x4_t matrix, const vec3_t scale) +{ + m4x4_t temp; + m4x4_scale_for_vec3(temp, scale); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_rotate_by_quat(m4x4_t matrix, const vec4_t rotation) +{ + m4x4_t temp; + m4x4_rotation_for_quat(temp, rotation); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_rotate_by_axisangle(m4x4_t matrix, const vec3_t axis, double angle) +{ + m4x4_t temp; + m4x4_rotation_for_axisangle(temp, axis, angle); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_transform_by_vec3(m4x4_t matrix, const vec3_t translation, const vec3_t euler, eulerOrder_t order, const vec3_t scale) +{ + m4x4_translate_by_vec3(matrix, translation); + m4x4_rotate_by_vec3(matrix, euler, order); + m4x4_scale_by_vec3(matrix, scale); +} + +void m4x4_pivoted_rotate_by_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + VectorNegate(pivotpoint, vec3_temp); + + m4x4_translate_by_vec3(matrix, pivotpoint); + m4x4_rotate_by_vec3(matrix, euler, order); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_scale_by_vec3(m4x4_t matrix, const vec3_t scale, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + VectorNegate(pivotpoint, vec3_temp); + + m4x4_translate_by_vec3(matrix, pivotpoint); + m4x4_scale_by_vec3(matrix, scale); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_transform_by_vec3(m4x4_t matrix, const vec3_t translation, const vec3_t euler, eulerOrder_t order, const vec3_t scale, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + + VectorAdd(pivotpoint, translation, vec3_temp); + m4x4_translate_by_vec3(matrix, vec3_temp); + m4x4_rotate_by_vec3(matrix, euler, order); + m4x4_scale_by_vec3(matrix, scale); + VectorNegate(pivotpoint, vec3_temp); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_transform_by_rotation(m4x4_t matrix, const vec3_t translation, const m4x4_t rotation, const vec3_t scale, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + + VectorAdd(pivotpoint, translation, vec3_temp); + m4x4_translate_by_vec3(matrix, vec3_temp); + m4x4_multiply_by_m4x4(matrix, rotation); + m4x4_scale_by_vec3(matrix, scale); + VectorNegate(pivotpoint, vec3_temp); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_rotate_by_quat(m4x4_t matrix, const vec4_t rotation, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + VectorNegate(pivotpoint, vec3_temp); + + m4x4_translate_by_vec3(matrix, pivotpoint); + m4x4_rotate_by_quat(matrix, rotation); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_rotate_by_axisangle(m4x4_t matrix, const vec3_t axis, double angle, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + VectorNegate(pivotpoint, vec3_temp); + + m4x4_translate_by_vec3(matrix, pivotpoint); + m4x4_rotate_by_axisangle(matrix, axis, angle); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +/* +A = A.B + +A0 = B0 * A0 + B1 * A4 + B2 * A8 + B3 * A12 +A4 = B4 * A0 + B5 * A4 + B6 * A8 + B7 * A12 +A8 = B8 * A0 + B9 * A4 + B10* A8 + B11* A12 +A12= B12* A0 + B13* A4 + B14* A8 + B15* A12 + +A1 = B0 * A1 + B1 * A5 + B2 * A9 + B3 * A13 +A5 = B4 * A1 + B5 * A5 + B6 * A9 + B7 * A13 +A9 = B8 * A1 + B9 * A5 + B10* A9 + B11* A13 +A13= B12* A1 + B13* A5 + B14* A9 + B15* A13 + +A2 = B0 * A2 + B1 * A6 + B2 * A10+ B3 * A14 +A6 = B4 * A2 + B5 * A6 + B6 * A10+ B7 * A14 +A10= B8 * A2 + B9 * A6 + B10* A10+ B11* A14 +A14= B12* A2 + B13* A6 + B14* A10+ B15* A14 + +A3 = B0 * A3 + B1 * A7 + B2 * A11+ B3 * A15 +A7 = B4 * A3 + B5 * A7 + B6 * A11+ B7 * A15 +A11= B8 * A3 + B9 * A7 + B10* A11+ B11* A15 +A15= B12* A3 + B13* A7 + B14* A11+ B15* A15 +*/ + +void m4x4_multiply_by_m4x4(m4x4_t dst, const m4x4_t src) +{ + vec_t dst0, dst1, dst2, dst3; + +#if 1 + + dst0 = src[0] * dst[0] + src[1] * dst[4] + src[2] * dst[8] + src[3] * dst[12]; + dst1 = src[4] * dst[0] + src[5] * dst[4] + src[6] * dst[8] + src[7] * dst[12]; + dst2 = src[8] * dst[0] + src[9] * dst[4] + src[10]* dst[8] + src[11]* dst[12]; + dst3 = src[12]* dst[0] + src[13]* dst[4] + src[14]* dst[8] + src[15]* dst[12]; + dst[0] = dst0; dst[4] = dst1; dst[8] = dst2; dst[12]= dst3; + + dst0 = src[0] * dst[1] + src[1] * dst[5] + src[2] * dst[9] + src[3] * dst[13]; + dst1 = src[4] * dst[1] + src[5] * dst[5] + src[6] * dst[9] + src[7] * dst[13]; + dst2 = src[8] * dst[1] + src[9] * dst[5] + src[10]* dst[9] + src[11]* dst[13]; + dst3 = src[12]* dst[1] + src[13]* dst[5] + src[14]* dst[9] + src[15]* dst[13]; + dst[1] = dst0; dst[5] = dst1; dst[9] = dst2; dst[13]= dst3; + + dst0 = src[0] * dst[2] + src[1] * dst[6] + src[2] * dst[10]+ src[3] * dst[14]; + dst1 = src[4] * dst[2] + src[5] * dst[6] + src[6] * dst[10]+ src[7] * dst[14]; + dst2 = src[8] * dst[2] + src[9] * dst[6] + src[10]* dst[10]+ src[11]* dst[14]; + dst3 = src[12]* dst[2] + src[13]* dst[6] + src[14]* dst[10]+ src[15]* dst[14]; + dst[2] = dst0; dst[6] = dst1; dst[10]= dst2; dst[14]= dst3; + + dst0 = src[0] * dst[3] + src[1] * dst[7] + src[2] * dst[11]+ src[3] * dst[15]; + dst1 = src[4] * dst[3] + src[5] * dst[7] + src[6] * dst[11]+ src[7] * dst[15]; + dst2 = src[8] * dst[3] + src[9] * dst[7] + src[10]* dst[11]+ src[11]* dst[15]; + dst3 = src[12]* dst[3] + src[13]* dst[7] + src[14]* dst[11]+ src[15]* dst[15]; + dst[3] = dst0; dst[7] = dst1; dst[11]= dst2; dst[15]= dst3; + +#else + + vec_t * p = dst; + for(int i=0;i<4;i++) + { + dst1 = src[0] * p[0]; + dst1 += src[1] * p[4]; + dst1 += src[2] * p[8]; + dst1 += src[3] * p[12]; + dst2 = src[4] * p[0]; + dst2 += src[5] * p[4]; + dst2 += src[6] * p[8]; + dst2 += src[7] * p[12]; + dst3 = src[8] * p[0]; + dst3 += src[9] * p[4]; + dst3 += src[10] * p[8]; + dst3 += src[11] * p[12]; + dst4 = src[12] * p[0]; + dst4 += src[13] * p[4]; + dst4 += src[14] * p[8]; + dst4 += src[15] * p[12]; + + p[0] = dst1; + p[4] = dst2; + p[8] = dst3; + p[12] = dst4; + p++; + } + +#endif +} + +/* +A = B.A + +A0 = A0 * B0 + A1 * B4 + A2 * B8 + A3 * B12 +A1 = A0 * B1 + A1 * B5 + A2 * B9 + A3 * B13 +A2 = A0 * B2 + A1 * B6 + A2 * B10+ A3 * B14 +A3 = A0 * B3 + A1 * B7 + A2 * B11+ A3 * B15 + +A4 = A4 * B0 + A5 * B4 + A6 * B8 + A7 * B12 +A5 = A4 * B1 + A5 * B5 + A6 * B9 + A7 * B13 +A6 = A4 * B2 + A5 * B6 + A6 * B10+ A7 * B14 +A7 = A4 * B3 + A5 * B7 + A6 * B11+ A7 * B15 + +A8 = A8 * B0 + A9 * B4 + A10* B8 + A11* B12 +A9 = A8 * B1 + A9 * B5 + A10* B9 + A11* B13 +A10= A8 * B2 + A9 * B6 + A10* B10+ A11* B14 +A11= A8 * B3 + A9 * B7 + A10* B11+ A11* B15 + +A12= A12* B0 + A13* B4 + A14* B8 + A15* B12 +A13= A12* B1 + A13* B5 + A14* B9 + A15* B13 +A14= A12* B2 + A13* B6 + A14* B10+ A15* B14 +A15= A12* B3 + A13* B7 + A14* B11+ A15* B15 +*/ + +void m4x4_premultiply_by_m4x4(m4x4_t dst, const m4x4_t src) +{ + vec_t dst0, dst1, dst2, dst3; + +#if 1 + + dst0 = dst[0] * src[0] + dst[1] * src[4] + dst[2] * src[8] + dst[3] * src[12]; + dst1 = dst[0] * src[1] + dst[1] * src[5] + dst[2] * src[9] + dst[3] * src[13]; + dst2 = dst[0] * src[2] + dst[1] * src[6] + dst[2] * src[10]+ dst[3] * src[14]; + dst3 = dst[0] * src[3] + dst[1] * src[7] + dst[2] * src[11]+ dst[3] * src[15]; + dst[0] = dst0; dst[1] = dst1; dst[2] = dst2; dst[3]= dst3; + + dst0 = dst[4] * src[0] + dst[5] * src[4] + dst[6] * src[8] + dst[7] * src[12]; + dst1 = dst[4] * src[1] + dst[5] * src[5] + dst[6] * src[9] + dst[7] * src[13]; + dst2 = dst[4] * src[2] + dst[5] * src[6] + dst[6] * src[10]+ dst[7] * src[14]; + dst3 = dst[4] * src[3] + dst[5] * src[7] + dst[6] * src[11]+ dst[7] * src[15]; + dst[4] = dst0; dst[5] = dst1; dst[6] = dst2; dst[7]= dst3; + + dst0 = dst[8] * src[0] + dst[9] * src[4] + dst[10]* src[8] + dst[11]* src[12]; + dst1 = dst[8] * src[1] + dst[9] * src[5] + dst[10]* src[9] + dst[11]* src[13]; + dst2 = dst[8] * src[2] + dst[9] * src[6] + dst[10]* src[10]+ dst[11]* src[14]; + dst3 = dst[8] * src[3] + dst[9] * src[7] + dst[10]* src[11]+ dst[11]* src[15]; + dst[8] = dst0; dst[9] = dst1; dst[10] = dst2; dst[11]= dst3; + + dst0 = dst[12]* src[0] + dst[13]* src[4] + dst[14]* src[8] + dst[15]* src[12]; + dst1 = dst[12]* src[1] + dst[13]* src[5] + dst[14]* src[9] + dst[15]* src[13]; + dst2 = dst[12]* src[2] + dst[13]* src[6] + dst[14]* src[10]+ dst[15]* src[14]; + dst3 = dst[12]* src[3] + dst[13]* src[7] + dst[14]* src[11]+ dst[15]* src[15]; + dst[12] = dst0; dst[13] = dst1; dst[14] = dst2; dst[15]= dst3; + +#else + + vec_t* p = dst; + for(int i=0;i<4;i++) + { + dst1 = src[0] * p[0]; + dst2 = src[1] * p[0]; + dst3 = src[2] * p[0]; + dst4 = src[3] * p[0]; + dst1 += src[4] * p[1]; + dst2 += src[5] * p[1]; + dst3 += src[6] * p[1]; + dst4 += src[7] * p[1]; + dst1 += src[8] * p[2]; + dst2 += src[9] * p[2]; + dst4 += src[11] * p[2]; + dst3 += src[10] * p[2]; + dst1 += src[12] * p[3]; + dst2 += src[13] * p[3]; + dst3 += src[14] * p[3]; + dst4 += src[15] * p[3]; + + *p++ = dst1; + *p++ = dst2; + *p++ = dst3; + *p++ = dst4; + } + +#endif +} + +void m4x4_orthogonal_multiply_by_m4x4(m4x4_t dst, const m4x4_t src) +{ + vec_t dst0, dst1, dst2, dst3; + + dst0 = src[0] * dst[0] + src[1] * dst[4] + src[2] * dst[8]; + dst1 = src[4] * dst[0] + src[5] * dst[4] + src[6] * dst[8]; + dst2 = src[8] * dst[0] + src[9] * dst[4] + src[10]* dst[8]; + dst3 = src[12]* dst[0] + src[13]* dst[4] + src[14]* dst[8] + dst[12]; + dst[0] = dst0; dst[4] = dst1; dst[8] = dst2; dst[12]= dst3; + + dst0 = src[0] * dst[1] + src[1] * dst[5] + src[2] * dst[9]; + dst1 = src[4] * dst[1] + src[5] * dst[5] + src[6] * dst[9]; + dst2 = src[8] * dst[1] + src[9] * dst[5] + src[10]* dst[9]; + dst3 = src[12]* dst[1] + src[13]* dst[5] + src[14]* dst[9] + dst[13]; + dst[1] = dst0; dst[5] = dst1; dst[9] = dst2; dst[13]= dst3; + + dst0 = src[0] * dst[2] + src[1] * dst[6] + src[2] * dst[10]; + dst1 = src[4] * dst[2] + src[5] * dst[6] + src[6] * dst[10]; + dst2 = src[8] * dst[2] + src[9] * dst[6] + src[10]* dst[10]; + dst3 = src[12]* dst[2] + src[13]* dst[6] + src[14]* dst[10]+ dst[14]; + dst[2] = dst0; dst[6] = dst1; dst[10]= dst2; dst[14]= dst3; +} + +void m4x4_orthogonal_premultiply_by_m4x4(m4x4_t dst, const m4x4_t src) +{ + vec_t dst0, dst1, dst2; + + dst0 = dst[0] * src[0] + dst[1] * src[4] + dst[2] * src[8]; + dst1 = dst[0] * src[1] + dst[1] * src[5] + dst[2] * src[9]; + dst2 = dst[0] * src[2] + dst[1] * src[6] + dst[2] * src[10]; + dst[0] = dst0; dst[1] = dst1; dst[2] = dst2; + + dst0 = dst[4] * src[0] + dst[5] * src[4] + dst[6] * src[8]; + dst1 = dst[4] * src[1] + dst[5] * src[5] + dst[6] * src[9]; + dst2 = dst[4] * src[2] + dst[5] * src[6] + dst[6] * src[10]; + dst[4] = dst0; dst[5] = dst1; dst[6] = dst2; + + dst0 = dst[8] * src[0] + dst[9] * src[4] + dst[10]* src[8]; + dst1 = dst[8] * src[1] + dst[9] * src[5] + dst[10]* src[9]; + dst2 = dst[8] * src[2] + dst[9] * src[6] + dst[10]* src[10]; + dst[8] = dst0; dst[9] = dst1; dst[10] = dst2; + + dst0 = dst[12]* src[0] + dst[13]* src[4] + dst[14]* src[8] + dst[15]* src[12]; + dst1 = dst[12]* src[1] + dst[13]* src[5] + dst[14]* src[9] + dst[15]* src[13]; + dst2 = dst[12]* src[2] + dst[13]* src[6] + dst[14]* src[10]+ dst[15]* src[14]; + dst[12] = dst0; dst[13] = dst1; dst[14] = dst2; +} + +void m4x4_transform_point(const m4x4_t matrix, vec3_t point) +{ + float out1, out2, out3; + + out1 = matrix[0] * point[0] + matrix[4] * point[1] + matrix[8] * point[2] + matrix[12]; + out2 = matrix[1] * point[0] + matrix[5] * point[1] + matrix[9] * point[2] + matrix[13]; + out3 = matrix[2] * point[0] + matrix[6] * point[1] + matrix[10] * point[2] + matrix[14]; + + point[0] = out1; + point[1] = out2; + point[2] = out3; +} + +void m4x4_transform_normal(const m4x4_t matrix, vec3_t normal) +{ + float out1, out2, out3; + + out1 = matrix[0] * normal[0] + matrix[4] * normal[1] + matrix[8] * normal[2]; + out2 = matrix[1] * normal[0] + matrix[5] * normal[1] + matrix[9] * normal[2]; + out3 = matrix[2] * normal[0] + matrix[6] * normal[1] + matrix[10] * normal[2]; + + normal[0] = out1; + normal[1] = out2; + normal[2] = out3; +} + +void m4x4_transform_vec4(const m4x4_t matrix, vec4_t vector) +{ + float out1, out2, out3, out4; + + out1 = matrix[0] * vector[0] + matrix[4] * vector[1] + matrix[8] * vector[2] + matrix[12] * vector[3]; + out2 = matrix[1] * vector[0] + matrix[5] * vector[1] + matrix[9] * vector[2] + matrix[13] * vector[3]; + out3 = matrix[2] * vector[0] + matrix[6] * vector[1] + matrix[10] * vector[2] + matrix[14] * vector[3]; + out4 = matrix[3] * vector[0] + matrix[7] * vector[1] + matrix[11] * vector[2] + matrix[15] * vector[3]; + + vector[0] = out1; + vector[1] = out2; + vector[2] = out3; + vector[3] = out4; +} + +#define CLIP_X_LT_W(p) ((p)[0] < (p)[3]) +#define CLIP_X_GT_W(p) ((p)[0] > -(p)[3]) +#define CLIP_Y_LT_W(p) ((p)[1] < (p)[3]) +#define CLIP_Y_GT_W(p) ((p)[1] > -(p)[3]) +#define CLIP_Z_LT_W(p) ((p)[2] < (p)[3]) +#define CLIP_Z_GT_W(p) ((p)[2] > -(p)[3]) + +clipmask_t homogenous_clip_point(const vec4_t clipped) +{ + clipmask_t result = CLIP_FAIL; + if(CLIP_X_LT_W(clipped)) result &= ~CLIP_LT_X; // X < W + if(CLIP_X_GT_W(clipped)) result &= ~CLIP_GT_X; // X > -W + if(CLIP_Y_LT_W(clipped)) result &= ~CLIP_LT_Y; // Y < W + if(CLIP_Y_GT_W(clipped)) result &= ~CLIP_GT_Y; // Y > -W + if(CLIP_Z_LT_W(clipped)) result &= ~CLIP_LT_Z; // Z < W + if(CLIP_Z_GT_W(clipped)) result &= ~CLIP_GT_Z; // Z > -W + return result; +} + +clipmask_t m4x4_clip_point(const m4x4_t matrix, const vec3_t point, vec4_t clipped) +{ + clipped[0] = point[0]; + clipped[1] = point[1]; + clipped[2] = point[2]; + clipped[3] = 1; + m4x4_transform_vec4(matrix, clipped); + return homogenous_clip_point(clipped); +} + + +unsigned int homogenous_clip_triangle(vec4_t clipped[9]) +{ + vec4_t buffer[9]; + unsigned int rcount = 3; + unsigned int wcount = 0; + vec_t const* rptr = clipped[0]; + vec_t* wptr = buffer[0]; + const vec_t* p0; + const vec_t* p1; + unsigned char b0, b1; + + unsigned int i; + double scale; + + p0 = rptr; + b0 = CLIP_X_LT_W(p0); + for(i=0; i= 0.0) pos += det; else neg += det; + + det = src[1] * src[6] * src[8]; + if (det >= 0.0) pos += det; else neg += det; + + det = src[2] * src[4] * src[9]; + if (det >= 0.0) pos += det; else neg += det; + + det = -src[2] * src[5] * src[8]; + if (det >= 0.0) pos += det; else neg += det; + + det = -src[1] * src[4] * src[10]; + if (det >= 0.0) pos += det; else neg += det; + + det = -src[0] * src[6] * src[9]; + if (det >= 0.0) pos += det; else neg += det; + + det = pos + neg; +#elif 0 + float det + = (src[0] * src[5] * src[10]) + + (src[1] * src[6] * src[8]) + + (src[2] * src[4] * src[9]) + - (src[2] * src[5] * src[8]) + - (src[1] * src[4] * src[10]) + - (src[0] * src[6] * src[9]); +#else + float det + = src[0] * ( src[5]*src[10] - src[9]*src[6] ) + - src[1] * ( src[4]*src[10] - src[8]*src[6] ) + + src[2] * ( src[4]*src[9] - src[8]*src[5] ); + +#endif + + if (det*det < 1e-25) + return 1; + + det = 1.0f / det; + matrix[0] = ( (src[5]*src[10]- src[6]*src[9] )*det); + matrix[1] = (- (src[1]*src[10]- src[2]*src[9] )*det); + matrix[2] = ( (src[1]*src[6] - src[2]*src[5] )*det); + matrix[4] = (- (src[4]*src[10]- src[6]*src[8] )*det); + matrix[5] = ( (src[0]*src[10]- src[2]*src[8] )*det); + matrix[6] = (- (src[0]*src[6] - src[2]*src[4] )*det); + matrix[8] = ( (src[4]*src[9] - src[5]*src[8] )*det); + matrix[9] = (- (src[0]*src[9] - src[1]*src[8] )*det); + matrix[10]= ( (src[0]*src[5] - src[1]*src[4] )*det); + } + + /* Do the translation part */ + matrix[12] = - (src[12] * matrix[0] + + src[13] * matrix[4] + + src[14] * matrix[8]); + matrix[13] = - (src[12] * matrix[1] + + src[13] * matrix[5] + + src[14] * matrix[9]); + matrix[14] = - (src[12] * matrix[2] + + src[13] * matrix[6] + + src[14] * matrix[10]); + + return 0; +} + +void quat_identity(vec4_t quat) +{ + quat[0] = quat[1] = quat[2] = 0; + quat[3] = 1; +} + +void quat_multiply_by_quat(vec4_t quat, const vec4_t other) +{ + const vec_t x = quat[3]*other[0] + quat[0]*other[3] + quat[1]*other[2] - quat[2]*other[1]; + const vec_t y = quat[3]*other[1] + quat[1]*other[3] + quat[2]*other[0] - quat[0]*other[2]; + const vec_t z = quat[3]*other[2] + quat[2]*other[3] + quat[0]*other[1] - quat[1]*other[0]; + const vec_t w = quat[3]*other[3] - quat[0]*other[0] - quat[1]*other[1] - quat[2]*other[2]; + quat[0] = x; + quat[1] = y; + quat[2] = z; + quat[3] = w; +} + +void quat_conjugate(vec4_t quat) +{ + VectorNegate(quat, quat); +} + +//! quaternion from two unit vectors +void quat_for_unit_vectors(vec4_t quat, const vec3_t from, const vec3_t to) +{ + CrossProduct(from, to, quat); + quat[3] = DotProduct(from, to); +} + +void quat_normalise(vec4_t quat) +{ + const vec_t n = 1 / ( quat[0] * quat[0] + quat[1] * quat[1] + quat[2] * quat[2] + quat[3] * quat[3] ); + quat[0] *= n; + quat[1] *= n; + quat[2] *= n; + quat[3] *= n; +} + +void quat_for_axisangle(vec4_t quat, const vec3_t axis, double angle) +{ + angle *= 0.5; + + quat[3] = (float)sin(angle); + + quat[0] = axis[0] * quat[3]; + quat[1] = axis[1] * quat[3]; + quat[2] = axis[2] * quat[3]; + quat[3] = (float)cos(angle); +} + +void m3x3_multiply_by_m3x3(m3x3_t matrix, const m3x3_t matrix_src) +{ + float *pDest = matrix; + float out1, out2, out3; + int i; + + for(i=0;i<3;i++) + { + out1 = matrix_src[0] * pDest[0]; + out1 += matrix_src[1] * pDest[3]; + out1 += matrix_src[2] * pDest[6]; + out2 = matrix_src[3] * pDest[0]; + out2 += matrix_src[4] * pDest[3]; + out2 += matrix_src[5] * pDest[6]; + out3 = matrix_src[6] * pDest[0]; + out3 += matrix_src[7] * pDest[3]; + out3 += matrix_src[8] * pDest[6]; + + pDest[0] = out1; + pDest[3] = out2; + pDest[6] = out3; + + pDest++; + } +} + +void m3x3_transform_vec3(const m3x3_t matrix, vec3_t vector) +{ + float out1, out2, out3; + + out1 = matrix[0] * vector[0]; + out1 += matrix[3] * vector[1]; + out1 += matrix[6] * vector[2]; + out2 = matrix[1] * vector[0]; + out2 += matrix[4] * vector[1]; + out2 += matrix[7] * vector[2]; + out3 = matrix[2] * vector[0]; + out3 += matrix[5] * vector[1]; + out3 += matrix[8] * vector[2]; + + vector[0] = out1; + vector[1] = out2; + vector[2] = out3; +} + +float m3_det( m3x3_t mat ) +{ + float det; + + det = mat[0] * ( mat[4]*mat[8] - mat[7]*mat[5] ) + - mat[1] * ( mat[3]*mat[8] - mat[6]*mat[5] ) + + mat[2] * ( mat[3]*mat[7] - mat[6]*mat[4] ); + + return( det ); +} + +int m3_inverse( m3x3_t mr, m3x3_t ma ) +{ + float det = m3_det( ma ); + + if (det == 0 ) + { + return 1; + } + + + mr[0] = ma[4]*ma[8] - ma[5]*ma[7] / det; + mr[1] = -( ma[1]*ma[8] - ma[7]*ma[2] ) / det; + mr[2] = ma[1]*ma[5] - ma[4]*ma[2] / det; + + mr[3] = -( ma[3]*ma[8] - ma[5]*ma[6] ) / det; + mr[4] = ma[0]*ma[8] - ma[6]*ma[2] / det; + mr[5] = -( ma[0]*ma[5] - ma[3]*ma[2] ) / det; + + mr[6] = ma[3]*ma[7] - ma[6]*ma[4] / det; + mr[7] = -( ma[0]*ma[7] - ma[6]*ma[1] ) / det; + mr[8] = ma[0]*ma[4] - ma[1]*ma[3] / det; + + return 0; +} + +void m4_submat( m4x4_t mr, m3x3_t mb, int i, int j ) +{ + int ti, tj, idst, jdst; + + for ( ti = 0; ti < 4; ti++ ) + { + if ( ti < i ) + idst = ti; + else + if ( ti > i ) + idst = ti-1; + + for ( tj = 0; tj < 4; tj++ ) + { + if ( tj < j ) + jdst = tj; + else + if ( tj > j ) + jdst = tj-1; + + if ( ti != i && tj != j ) + mb[idst*3 + jdst] = mr[ti*4 + tj ]; + } + } +} + +float m4_det( m4x4_t mr ) +{ + float det, result = 0, i = 1; + m3x3_t msub3; + int n; + + for ( n = 0; n < 4; n++, i *= -1 ) + { + m4_submat( mr, msub3, 0, n ); + + det = m3_det( msub3 ); + result += mr[n] * det * i; + } + + return result; +} + +int m4x4_invert(m4x4_t matrix) +{ + float mdet = m4_det( matrix ); + m3x3_t mtemp; + int i, j, sign; + m4x4_t m4x4_temp; + +#if 0 + if ( fabs( mdet ) < 0.0000000001 ) + return 1; +#endif + + m4x4_assign(m4x4_temp, matrix); + + for ( i = 0; i < 4; i++ ) + for ( j = 0; j < 4; j++ ) + { + sign = 1 - ( (i +j) % 2 ) * 2; + + m4_submat( m4x4_temp, mtemp, i, j ); + + matrix[i+j*4] = ( m3_det( mtemp ) * sign ) / mdet; /* FIXME: try using * inverse det and see if speed/accuracy are good enough */ + } + + return 0; +} +#if 0 +void m4x4_solve_ge(m4x4_t matrix, vec4_t x) +{ + int indx[4]; + int c,r; + int i; + int best; + float scale[4]; + float f, pivot; + float aug[4]; + float recip, ratio; + float* p; + + for(r=0; r<4; r++) + { + aug[r] = 0; + indx[r] = r; + } + + for (r=0; r<4; r++) + { + scale[r] = 0; + for (c=0; c<4; c++, p++) + { + if (fabs(*p) > scale[r]) + { + scale[r] = (float)fabs(*p); + } + } + } + + for (c=0; c<3; c++) + { + pivot = 0; + for (r=c; r<4; r++) + { + f = (float)fabs(matrix[(indx[r]<<2)+c]) / scale[indx[r]]; + if (f > pivot) + { + pivot = f; + best = r; + } + } + + i = indx[c]; + indx[c] = indx[best]; + indx[best] = i; + + recip = 1 / matrix[(indx[c]<<2)+c]; + + for (r=c+1; r<4; r++) + { + p = matrix + (indx[r]<<2); + ratio = p[c] * recip; + + for (i=c+1; i<4; i++) + p[i] -= ratio * matrix[(indx[c]<<2)+i]; + aug[indx[r]] -= ratio * aug[indx[c]]; + } + } + + x[indx[3]] = aug[indx[3]] / matrix[(indx[3]<<2)+3]; + for(r=2; r>=0; r--) + { + f = aug[indx[r]]; + p = matrix + (indx[r]<<2); + recip = 1 / p[r]; + for(c=(r+1); c<4; c++) + { + f -= (p[c] * x[indx[c]]); + } + x[indx[r]] = f * recip; + } +} +#endif + +#define N 3 + +int matrix_solve_ge(vec_t* matrix, vec_t* aug, vec3_t x) +{ + int indx[N]; + int c,r; + int i; + int best; + float scale[N]; + float f, pivot; + float ratio; + float* p; + + for(r=0; r scale[r]) + { + scale[r] = (float)fabs(*p); + } + } + } + + for (c=0; c pivot) + { + pivot = f; + best = r; + } + } + + if(best == -1) return 1; + + i = indx[c]; + indx[c] = indx[best]; + indx[best] = i; + + for (r=c+1; r=0; r--) + { + f = aug[indx[r]]; + p = matrix + (indx[r]*N); + for(c=(r+1); c=0;i--) + { + temp = b[i]; + for(j=(i+1);j + +const vec3_t vec3_origin = {0.0f,0.0f,0.0f}; + +const vec3_t g_vec3_axis_x = { 1, 0, 0, }; +const vec3_t g_vec3_axis_y = { 0, 1, 0, }; +const vec3_t g_vec3_axis_z = { 0, 0, 1, }; + +/* +================ +MakeNormalVectors + +Given a normalized forward vector, create two +other perpendicular vectors +================ +*/ +void MakeNormalVectors (vec3_t forward, vec3_t right, vec3_t up) +{ + float d; + + // this rotate and negate guarantees a vector + // not colinear with the original + right[1] = -forward[0]; + right[2] = forward[1]; + right[0] = forward[2]; + + d = DotProduct (right, forward); + VectorMA (right, -d, forward, right); + VectorNormalize (right, right); + CrossProduct (right, forward, up); +} + +vec_t VectorLength(const vec3_t v) +{ + int i; + float length; + + length = 0.0f; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = (float)sqrt (length); + + return length; +} + +qboolean VectorCompare (const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (fabs(v1[i]-v2[i]) > EQUAL_EPSILON) + return qfalse; + + return qtrue; +} + +void VectorMA( const vec3_t va, vec_t scale, const vec3_t vb, vec3_t vc ) +{ + vc[0] = va[0] + scale*vb[0]; + vc[1] = va[1] + scale*vb[1]; + vc[2] = va[2] + scale*vb[2]; +} + +void _CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]-vb[0]; + out[1] = va[1]-vb[1]; + out[2] = va[2]-vb[2]; +} + +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]+vb[0]; + out[1] = va[1]+vb[1]; + out[2] = va[2]+vb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +vec_t VectorNormalize( const vec3_t in, vec3_t out ) { + vec_t length, ilength; + + length = (vec_t)sqrt (in[0]*in[0] + in[1]*in[1] + in[2]*in[2]); + if (length == 0) + { + VectorClear (out); + return 0; + } + + ilength = 1.0f/length; + out[0] = in[0]*ilength; + out[1] = in[1]*ilength; + out[2] = in[2]*ilength; + + return length; +} + +vec_t ColorNormalize( const vec3_t in, vec3_t out ) { + float max, scale; + + max = in[0]; + if (in[1] > max) + max = in[1]; + if (in[2] > max) + max = in[2]; + + if (max == 0) { + out[0] = out[1] = out[2] = 1.0; + return 0; + } + + scale = 1.0f / max; + + VectorScale (in, scale, out); + + return max; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +/* +void VectorScale (vec3_t v, vec_t scale, vec3_t out) +{ + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; +} +*/ + +void VectorRotate (vec3_t vIn, vec3_t vRotation, vec3_t out) +{ + vec3_t vWork, va; + int nIndex[3][2]; + int i; + + VectorCopy(vIn, va); + VectorCopy(va, vWork); + nIndex[0][0] = 1; nIndex[0][1] = 2; + nIndex[1][0] = 2; nIndex[1][1] = 0; + nIndex[2][0] = 0; nIndex[2][1] = 1; + + for (i = 0; i < 3; i++) + { + if (vRotation[i] != 0) + { + float dAngle = vRotation[i] * Q_PI / 180.0f; + float c = (vec_t)cos(dAngle); + float s = (vec_t)sin(dAngle); + vWork[nIndex[i][0]] = va[nIndex[i][0]] * c - va[nIndex[i][1]] * s; + vWork[nIndex[i][1]] = va[nIndex[i][0]] * s + va[nIndex[i][1]] * c; + } + VectorCopy(vWork, va); + } + VectorCopy(vWork, out); +} + +void VectorRotateOrigin (vec3_t vIn, vec3_t vRotation, vec3_t vOrigin, vec3_t out) +{ + vec3_t vTemp, vTemp2; + + VectorSubtract(vIn, vOrigin, vTemp); + VectorRotate(vTemp, vRotation, vTemp2); + VectorAdd(vTemp2, vOrigin, out); +} + +void VectorPolar(vec3_t v, float radius, float theta, float phi) +{ + v[0]=(float)(radius * cos(theta) * cos(phi)); + v[1]=(float)(radius * sin(theta) * cos(phi)); + v[2]=(float)(radius * sin(phi)); +} + +void VectorSnap(vec3_t v) +{ + int i; + for (i = 0; i < 3; i++) + { + v[i] = (vec_t)FLOAT_TO_INTEGER(v[i]); + } +} + +void VectorISnap(vec3_t point, int snap) +{ + int i; + for (i = 0 ;i < 3 ; i++) + { + point[i] = (vec_t)FLOAT_SNAP(point[i], snap); + } +} + +void VectorFSnap(vec3_t point, float snap) +{ + int i; + for (i = 0 ;i < 3 ; i++) + { + point[i] = (vec_t)FLOAT_SNAP(point[i], snap); + } +} + +void _Vector5Add (vec5_t va, vec5_t vb, vec5_t out) +{ + out[0] = va[0]+vb[0]; + out[1] = va[1]+vb[1]; + out[2] = va[2]+vb[2]; + out[3] = va[3]+vb[3]; + out[4] = va[4]+vb[4]; +} + +void _Vector5Scale (vec5_t v, vec_t scale, vec5_t out) +{ + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; + out[3] = v[3] * scale; + out[4] = v[4] * scale; +} + +void _Vector53Copy (vec5_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +// NOTE: added these from Ritual's Q3Radiant +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * (Q_PI*2.0f / 360.0f); + sy = (vec_t)sin(angle); + cy = (vec_t)cos(angle); + angle = angles[PITCH] * (Q_PI*2.0f / 360.0f); + sp = (vec_t)sin(angle); + cp = (vec_t)cos(angle); + angle = angles[ROLL] * (Q_PI*2.0f / 360.0f); + sr = (vec_t)sin(angle); + cr = (vec_t)cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = -sr*sp*cy+cr*sy; + right[1] = -sr*sp*sy-cr*cy; + right[2] = -sr*cp; + } + if (up) + { + up[0] = cr*sp*cy+sr*sy; + up[1] = cr*sp*sy-sr*cy; + up[2] = cr*cp; + } +} + +void VectorToAngles( vec3_t vec, vec3_t angles ) +{ + float forward; + float yaw, pitch; + + if ( ( vec[ 0 ] == 0 ) && ( vec[ 1 ] == 0 ) ) + { + yaw = 0; + if ( vec[ 2 ] > 0 ) + { + pitch = 90; + } + else + { + pitch = 270; + } + } + else + { + yaw = (vec_t)atan2( vec[ 1 ], vec[ 0 ] ) * 180 / Q_PI; + if ( yaw < 0 ) + { + yaw += 360; + } + + forward = ( float )sqrt( vec[ 0 ] * vec[ 0 ] + vec[ 1 ] * vec[ 1 ] ); + pitch = (vec_t)atan2( vec[ 2 ], forward ) * 180 / Q_PI; + if ( pitch < 0 ) + { + pitch += 360; + } + } + + angles[ 0 ] = pitch; + angles[ 1 ] = yaw; + angles[ 2 ] = 0; +} + +/* +===================== +PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) { + vec3_t d1, d2; + + VectorSubtract( b, a, d1 ); + VectorSubtract( c, a, d2 ); + CrossProduct( d2, d1, plane ); + if ( VectorNormalize( plane, plane ) == 0 ) { + return qfalse; + } + + plane[3] = DotProduct( a, plane ); + return qtrue; +} + +/* +** NormalToLatLong +** +** We use two byte encoded normals in some space critical applications. +** Lat = 0 at (1,0,0) to 360 (-1,0,0), encoded in 8-bit sine table format +** Lng = 0 at (0,0,1) to 180 (0,0,-1), encoded in 8-bit sine table format +** +*/ +void NormalToLatLong( const vec3_t normal, byte bytes[2] ) { + // check for singularities + if ( normal[0] == 0 && normal[1] == 0 ) { + if ( normal[2] > 0 ) { + bytes[0] = 0; + bytes[1] = 0; // lat = 0, long = 0 + } else { + bytes[0] = 128; + bytes[1] = 0; // lat = 0, long = 128 + } + } else { + int a, b; + + a = (int)( RAD2DEG( atan2( normal[1], normal[0] ) ) * (255.0f / 360.0f ) ); + a &= 0xff; + + b = (int)( RAD2DEG( acos( normal[2] ) ) * ( 255.0f / 360.0f ) ); + b &= 0xff; + + bytes[0] = b; // longitude + bytes[1] = a; // lattitude + } +} + +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal (vec3_t normal) { + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + return PLANE_NON_AXIAL; +} + +/* +================ +MatrixMultiply +================ +*/ +void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]) { + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + vec_t minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = (vec_t)fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst, dst ); +} + +/* +=============== +RotatePointAroundVector + +This is not implemented very well... +=============== +*/ +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, + float degrees ) { + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + float rad; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + rad = (float)DEG2RAD( degrees ); + zrot[0][0] = (vec_t)cos( rad ); + zrot[0][1] = (vec_t)sin( rad ); + zrot[1][0] = (vec_t)-sin( rad ); + zrot[1][1] = (vec_t)cos( rad ); + + MatrixMultiply( m, zrot, tmpmat ); + MatrixMultiply( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} diff --git a/tools/urt/libs/mathlib/mathlib.dsp b/tools/urt/libs/mathlib/mathlib.dsp new file mode 100644 index 00000000..358da218 --- /dev/null +++ b/tools/urt/libs/mathlib/mathlib.dsp @@ -0,0 +1,126 @@ +# Microsoft Developer Studio Project File - Name="mathlib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=mathlib - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mathlib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mathlib.mak" CFG="mathlib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mathlib - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "mathlib - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "mathlib" +# PROP Scc_LocalPath ".." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mathlib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W3 /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x40c /d "NDEBUG" +# ADD RSC /l 0x40c /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "mathlib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x40c /d "_DEBUG" +# ADD RSC /l 0x40c /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "mathlib - Win32 Release" +# Name "mathlib - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\bbox.c +# End Source File +# Begin Source File + +SOURCE=.\line.c +# End Source File +# Begin Source File + +SOURCE=.\linear.c +# End Source File +# Begin Source File + +SOURCE=.\m4x4.c +# End Source File +# Begin Source File + +SOURCE=.\mathlib.c +# End Source File +# Begin Source File + +SOURCE=.\ray.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\mathlib.h +# End Source File +# End Group +# End Target +# End Project diff --git a/tools/urt/libs/mathlib/mathlib.vcproj b/tools/urt/libs/mathlib/mathlib.vcproj new file mode 100644 index 00000000..fa2fb7d2 --- /dev/null +++ b/tools/urt/libs/mathlib/mathlib.vcproj @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/urt/libs/mathlib/ray.c b/tools/urt/libs/mathlib/ray.c new file mode 100644 index 00000000..10256800 --- /dev/null +++ b/tools/urt/libs/mathlib/ray.c @@ -0,0 +1,143 @@ +/* +This code provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "mathlib.h" +#include + +vec3_t identity = { 0,0,0 }; + +void ray_construct_for_vec3(ray_t *ray, const vec3_t origin, const vec3_t direction) +{ + VectorCopy(origin, ray->origin); + VectorCopy(direction, ray->direction); +} + +void ray_transform(ray_t *ray, const m4x4_t matrix) +{ + m4x4_transform_point(matrix, ray->origin); + m4x4_transform_normal(matrix, ray->direction); +} + +vec_t ray_intersect_point(const ray_t *ray, const vec3_t point, vec_t epsilon, vec_t divergence) +{ + vec3_t displacement; + vec_t depth; + + // calc displacement of test point from ray origin + VectorSubtract(point, ray->origin, displacement); + // calc length of displacement vector along ray direction + depth = DotProduct(displacement, ray->direction); + if(depth < 0.0f) return (vec_t)FLT_MAX; + // calc position of closest point on ray to test point + VectorMA (ray->origin, depth, ray->direction, displacement); + // calc displacement of test point from closest point + VectorSubtract(point, displacement, displacement); + // calc length of displacement, subtract depth-dependant epsilon + if (VectorLength(displacement) - (epsilon + (depth * divergence)) > 0.0f) return (vec_t)FLT_MAX; + return depth; +} + +// Tomas Moller and Ben Trumbore. Fast, minimum storage ray-triangle intersection. Journal of graphics tools, 2(1):21-28, 1997 + +#define EPSILON 0.000001 + +vec_t ray_intersect_triangle(const ray_t *ray, qboolean bCullBack, const vec3_t vert0, const vec3_t vert1, const vec3_t vert2) +{ + float edge1[3], edge2[3], tvec[3], pvec[3], qvec[3]; + float det,inv_det; + float u, v; + vec_t depth = (vec_t)FLT_MAX; + + /* find vectors for two edges sharing vert0 */ + VectorSubtract(vert1, vert0, edge1); + VectorSubtract(vert2, vert0, edge2); + + /* begin calculating determinant - also used to calculate U parameter */ + CrossProduct(ray->direction, edge2, pvec); + + /* if determinant is near zero, ray lies in plane of triangle */ + det = DotProduct(edge1, pvec); + + if (bCullBack == qtrue) + { + if (det < EPSILON) + return depth; + + // calculate distance from vert0 to ray origin + VectorSubtract(ray->origin, vert0, tvec); + + // calculate U parameter and test bounds + u = DotProduct(tvec, pvec); + if (u < 0.0 || u > det) + return depth; + + // prepare to test V parameter + CrossProduct(tvec, edge1, qvec); + + // calculate V parameter and test bounds + v = DotProduct(ray->direction, qvec); + if (v < 0.0 || u + v > det) + return depth; + + // calculate t, scale parameters, ray intersects triangle + depth = DotProduct(edge2, qvec); + inv_det = 1.0f / det; + depth *= inv_det; + //u *= inv_det; + //v *= inv_det; + } + else + { + /* the non-culling branch */ + if (det > -EPSILON && det < EPSILON) + return depth; + inv_det = 1.0f / det; + + /* calculate distance from vert0 to ray origin */ + VectorSubtract(ray->origin, vert0, tvec); + + /* calculate U parameter and test bounds */ + u = DotProduct(tvec, pvec) * inv_det; + if (u < 0.0 || u > 1.0) + return depth; + + /* prepare to test V parameter */ + CrossProduct(tvec, edge1, qvec); + + /* calculate V parameter and test bounds */ + v = DotProduct(ray->direction, qvec) * inv_det; + if (v < 0.0 || u + v > 1.0) + return depth; + + /* calculate t, ray intersects triangle */ + depth = DotProduct(edge2, qvec) * inv_det; + } + return depth; +} + +vec_t ray_intersect_plane(const ray_t* ray, const vec3_t normal, vec_t dist) +{ + return -(DotProduct(normal, ray->origin) - dist) / DotProduct(ray->direction, normal); +} + diff --git a/tools/urt/libs/md5lib.h b/tools/urt/libs/md5lib.h new file mode 100644 index 00000000..7f63c61b --- /dev/null +++ b/tools/urt/libs/md5lib.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5lib.h,v 1.1.2.1 2003/07/19 23:25:50 spog Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/tools/urt/libs/md5lib/md5lib.c b/tools/urt/libs/md5lib/md5lib.c new file mode 100644 index 00000000..6fb45f13 --- /dev/null +++ b/tools/urt/libs/md5lib/md5lib.c @@ -0,0 +1,395 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5lib.c,v 1.1 2003/07/18 04:24:39 ydnar Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2003-07-17 ydnar added to gtkradiant project from + http://sourceforge.net/projects/libmd5-rfc/ + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5lib.h" /* ydnar */ +#include + +/* ydnar: gtkradiant endian picking */ +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ +#define ARCH_IS_BIG_ENDIAN 1 +#else +#define ARCH_IS_BIG_ENDIAN 0 +#endif +/* ydnar: end */ + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} diff --git a/tools/urt/libs/md5lib/md5lib.dsp b/tools/urt/libs/md5lib/md5lib.dsp new file mode 100644 index 00000000..81addbbc --- /dev/null +++ b/tools/urt/libs/md5lib/md5lib.dsp @@ -0,0 +1,106 @@ +# Microsoft Developer Studio Project File - Name="md5lib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=md5lib - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "md5lib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "md5lib.mak" CFG="md5lib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "md5lib - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "md5lib - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "Perforce Project" +# PROP Scc_LocalPath ".." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "md5lib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "md5lib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "md5lib - Win32 Release" +# Name "md5lib - Win32 Debug" +# Begin Group "src" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\md5lib.c +# End Source File +# End Group +# Begin Group "include" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\md5lib.h +# End Source File +# End Group +# End Target +# End Project diff --git a/tools/urt/libs/md5lib/md5lib.vcproj b/tools/urt/libs/md5lib/md5lib.vcproj new file mode 100644 index 00000000..08eb232e --- /dev/null +++ b/tools/urt/libs/md5lib/md5lib.vcproj @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/urt/libs/memory/allocator.cpp b/tools/urt/libs/memory/allocator.cpp new file mode 100644 index 00000000..2f213c7e --- /dev/null +++ b/tools/urt/libs/memory/allocator.cpp @@ -0,0 +1,60 @@ + +#include "memory/allocator.h" + +#include + +template +struct Vector +{ + typedef std::vector > Type; +}; + +namespace +{ + class Bleh + { + int* m_blah; + public: + Bleh(int* blah) : m_blah(blah) + { + } + ~Bleh() + { + *m_blah = 15; + } + }; + + void TestAllocator() + { + Vector::Type test; + + int i = 0; + test.push_back(Bleh(&i)); + } + + void TestNewDelete() + { + { + NamedAllocator allocator("test"); + int* p = NamedNew::type(allocator).scalar(); + //new int(); + NamedDelete::type(allocator).scalar(p); + } + + { + int* p = New().scalar(3); + Delete().scalar(p); + } + + { + int* p = New().scalar(int(15.9)); + Delete().scalar(p); + } + + { + int* p = New().vector(15); + // new int[15] + Delete().vector(p, 15); + } + } +} \ No newline at end of file diff --git a/tools/urt/libs/memory/allocator.h b/tools/urt/libs/memory/allocator.h new file mode 100644 index 00000000..b7689b81 --- /dev/null +++ b/tools/urt/libs/memory/allocator.h @@ -0,0 +1,316 @@ + +#if !defined(INCLUDED_MEMORY_ALLOCATOR_H) +#define INCLUDED_MEMORY_ALLOCATOR_H + +#include + +#if 0 + +#define DefaultAllocator std::allocator + +#else + +/// \brief An allocator that uses c++ new/delete. +/// Compliant with the std::allocator interface. +template +class DefaultAllocator +{ +public: + + typedef Type value_type; + typedef value_type* pointer; + typedef const Type* const_pointer; + typedef Type& reference; + typedef const Type& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + template + struct rebind + { + typedef DefaultAllocator other; + }; + + DefaultAllocator() + { + } + DefaultAllocator(const DefaultAllocator&) + { + } + template DefaultAllocator(const DefaultAllocator&) + { + } + ~DefaultAllocator() + { + } + + pointer address(reference instance) const + { + return &instance; + } + const_pointer address(const_reference instance) const + { + return &instance; + } + Type* allocate(size_type size, const void* = 0) + { + return static_cast(::operator new(size * sizeof(Type))); + } + void deallocate(pointer p, size_type) + { + ::operator delete(p); + } + size_type max_size() const + { + return std::size_t(-1) / sizeof (Type); + } + void construct(pointer p, const Type& value) + { + new(p) Type(value); + } + void destroy(pointer p) + { + p->~Type(); + } +}; + +template +inline bool operator==(const DefaultAllocator&, const DefaultAllocator&) +{ + return true; +} +template +inline bool operator==(const DefaultAllocator&, const OtherAllocator&) +{ + return false; +} + +#endif + + +template +class NamedAllocator : public DefaultAllocator +{ + typedef DefaultAllocator allocator_type; + + const char* m_name; +public: + + typedef Type value_type; + typedef value_type* pointer; + typedef const Type* const_pointer; + typedef Type& reference; + typedef const Type& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + template + struct rebind + { + typedef NamedAllocator other; + }; + + explicit NamedAllocator(const char* name) : m_name(name) + { + } + NamedAllocator(const NamedAllocator& other) : m_name(other.m_name) + { + } + template NamedAllocator(const NamedAllocator& other) : m_name(other.m_name) + { + } + ~NamedAllocator() + { + } + + pointer address(reference instance) const + { + return allocator_type::address(instance); + } + const_pointer address(const_reference instance) const + { + return allocator_type::address(instance); + } + Type* allocate(size_type size, const void* = 0) + { + return allocator_type::allocate(size); + } + void deallocate(pointer p, size_type size) + { + allocator_type::deallocate(p, size); + } + size_type max_size() const + { + return allocator_type::max_size(); + } + void construct(pointer p, const Type& value) + { + allocator_type::construct(p, value); + } + void destroy(pointer p) + { + allocator_type::destroy(p); + } + + template + bool operator==(const NamedAllocator& other) + { + return true; + } + + // returns true if the allocators are not interchangeable + template + bool operator!=(const NamedAllocator& other) + { + return false; + } +}; + + + +#include +#include "generic/object.h" + + + +template +class DefaultConstruct +{ +public: + void operator()(Type& t) + { + constructor(t); + } +}; + +template +class Construct +{ + const T1& other; +public: + Construct(const T1& other_) : other(other_) + { + } + void operator()(Type& t) + { + constructor(t, other); + } +}; + +template +class Destroy +{ +public: + void operator()(Type& t) + { + destructor(t); + } +}; + +template > +class New : public Allocator +{ +public: + New() + { + } + explicit New(const Allocator& allocator) : Allocator(allocator) + { + } + + Type* scalar() + { + return new(Allocator::allocate(1)) Type(); + } + template + Type* scalar(const T1& t1) + { + return new(Allocator::allocate(1)) Type(t1); + } + template + Type* scalar(const T1& t1, const T2& t2) + { + return new(Allocator::allocate(1)) Type(t1, t2); + } + template + Type* scalar(const T1& t1, const T2& t2, const T3& t3) + { + return new(Allocator::allocate(1)) Type(t1, t2, t3); + } + template + Type* scalar(const T1& t1, const T2& t2, const T3& t3, const T4& t4) + { + return new(Allocator::allocate(1)) Type(t1, t2, t3, t4); + } + template + Type* scalar(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5) + { + return new(Allocator::allocate(1)) Type(t1, t2, t3, t4, t5); + } + Type* vector(std::size_t size) + { +#if 1 + Type* p = Allocator::allocate(size); + std::for_each(p, p + size, DefaultConstruct()); + return p; +#else + // this does not work with msvc71 runtime + return new(Allocator::allocate(size)) Type[size]; +#endif + } + template + Type* vector(std::size_t size, const T1& t1) + { + Type* p = Allocator::allocate(size); + std::for_each(p, p + size, Construct(t1)); + return p; + } +}; + +template > +class Delete : public Allocator +{ +public: + Delete() + { + } + explicit Delete(const Allocator& allocator) : Allocator(allocator) + { + } + + void scalar(Type* p) + { + if(p != 0) + { + p->~Type(); + Allocator::deallocate(p, 1); + } + } + void vector(Type* p, std::size_t size) + { + // '::operator delete' handles null + // 'std::allocator::deallocate' requires non-null + if(p != 0) + { + std::for_each(p, p + size, Destroy()); + Allocator::deallocate(p, size); + } + } +}; + + +template +class NamedNew +{ +public: + typedef New > type; +}; + +template +class NamedDelete +{ +public: + typedef Delete > type; +}; + +#endif diff --git a/tools/urt/libs/moduleobservers.cpp b/tools/urt/libs/moduleobservers.cpp new file mode 100644 index 00000000..adb54303 --- /dev/null +++ b/tools/urt/libs/moduleobservers.cpp @@ -0,0 +1,3 @@ + +#include "moduleobservers.h" + diff --git a/tools/urt/libs/moduleobservers.h b/tools/urt/libs/moduleobservers.h new file mode 100644 index 00000000..71c53dfd --- /dev/null +++ b/tools/urt/libs/moduleobservers.h @@ -0,0 +1,44 @@ + +#if !defined(INCLUDED_MODULEOBSERVERS_H) +#define INCLUDED_MODULEOBSERVERS_H + +#include "debugging/debugging.h" +#include +#include "moduleobserver.h" + +class ModuleObservers +{ + typedef std::set Observers; + Observers m_observers; +public: + ~ModuleObservers() + { + ASSERT_MESSAGE(m_observers.empty(), "ModuleObservers::~ModuleObservers: observers still attached"); + } + void attach(ModuleObserver& observer) + { + ASSERT_MESSAGE(m_observers.find(&observer) == m_observers.end(), "ModuleObservers::attach: cannot attach observer"); + m_observers.insert(&observer); + } + void detach(ModuleObserver& observer) + { + ASSERT_MESSAGE(m_observers.find(&observer) != m_observers.end(), "ModuleObservers::detach: cannot detach observer"); + m_observers.erase(&observer); + } + void realise() + { + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->realise(); + } + } + void unrealise() + { + for(Observers::reverse_iterator i = m_observers.rbegin(); i != m_observers.rend(); ++i) + { + (*i)->unrealise(); + } + } +}; + +#endif diff --git a/tools/urt/libs/modulesystem/moduleregistry.cpp b/tools/urt/libs/modulesystem/moduleregistry.cpp new file mode 100644 index 00000000..6aa54ce8 --- /dev/null +++ b/tools/urt/libs/modulesystem/moduleregistry.cpp @@ -0,0 +1,3 @@ + +#include "moduleregistry.h" + diff --git a/tools/urt/libs/modulesystem/moduleregistry.h b/tools/urt/libs/modulesystem/moduleregistry.h new file mode 100644 index 00000000..bbd5544e --- /dev/null +++ b/tools/urt/libs/modulesystem/moduleregistry.h @@ -0,0 +1,45 @@ + +#if !defined(INCLUDED_MODULESYSTEM_MODULEREGISTRY_H) +#define INCLUDED_MODULESYSTEM_MODULEREGISTRY_H + +#include "generic/static.h" +#include + +class ModuleRegisterable +{ +public: + virtual void selfRegister() = 0; +}; + +class ModuleRegistryList +{ + typedef std::list RegisterableModules; + RegisterableModules m_modules; +public: + void addModule(ModuleRegisterable& module) + { + m_modules.push_back(&module); + } + void registerModules() const + { + for(RegisterableModules::const_iterator i = m_modules.begin(); i != m_modules.end(); ++i) + { + (*i)->selfRegister(); + } + } +}; + +typedef SmartStatic StaticModuleRegistryList; + + +class StaticRegisterModule : public StaticModuleRegistryList +{ +public: + StaticRegisterModule(ModuleRegisterable& module) + { + StaticModuleRegistryList::instance().addModule(module); + } +}; + + +#endif diff --git a/tools/urt/libs/modulesystem/modulesmap.cpp b/tools/urt/libs/modulesystem/modulesmap.cpp new file mode 100644 index 00000000..659eec05 --- /dev/null +++ b/tools/urt/libs/modulesystem/modulesmap.cpp @@ -0,0 +1,2 @@ + +#include "modulesmap.h" diff --git a/tools/urt/libs/modulesystem/modulesmap.h b/tools/urt/libs/modulesystem/modulesmap.h new file mode 100644 index 00000000..2b69c090 --- /dev/null +++ b/tools/urt/libs/modulesystem/modulesmap.h @@ -0,0 +1,132 @@ + +#if !defined(INCLUDED_MODULESYSTEM_MODULESMAP_H) +#define INCLUDED_MODULESYSTEM_MODULESMAP_H + +#include "modulesystem.h" +#include "string/string.h" +#include +#include + +template +class ModulesMap : public Modules +{ + typedef std::map modules_t; + modules_t m_modules; +public: + ~ModulesMap() + { + for(modules_t::iterator i = m_modules.begin(); i != m_modules.end(); ++i) + { + (*i).second->release(); + } + } + + typedef modules_t::const_iterator iterator; + + iterator begin() const + { + return m_modules.begin(); + } + iterator end() const + { + return m_modules.end(); + } + + void insert(const char* name, Module& module) + { + module.capture(); + if(globalModuleServer().getError()) + { + module.release(); + globalModuleServer().setError(false); + } + else + { + m_modules.insert(modules_t::value_type(name, &module)); + } + } + + Type* find(const char* name) + { + modules_t::iterator i = m_modules.find(name); + if(i != m_modules.end()) + { + return static_cast(Module_getTable(*(*i).second)); + } + return 0; + } + + Type* findModule(const char* name) + { + return find(name); + } + void foreachModule(typename Modules::Visitor& visitor) + { + for(modules_t::iterator i = m_modules.begin(); i != m_modules.end(); ++i) + { + visitor.visit((*i).first.c_str(), *static_cast(Module_getTable(*(*i).second))); + } + } +}; + +template +class InsertModules : public ModuleServer::Visitor +{ + ModulesMap& m_modules; +public: + InsertModules(ModulesMap& modules) + : m_modules(modules) + { + } + void visit(const char* name, Module& module) + { + m_modules.insert(name, module); + } +}; + +template +class ModulesRef +{ + ModulesMap m_modules; +public: + ModulesRef(const char* names) + { + if(!globalModuleServer().getError()) + { + if(string_equal(names, "*")) + { + InsertModules visitor(m_modules); + globalModuleServer().foreachModule(Type::name(), Type::VERSION, visitor); + } + else + { + StringTokeniser tokeniser(names); + for(;;) + { + const char* name = tokeniser.getToken(); + if(string_empty(name)) + { + break; + } + Module* module = globalModuleServer().findModule(Type::name(), Type::VERSION, name); + if(module == 0) + { + globalModuleServer().setError(true); + globalErrorStream() << "ModulesRef::initialise: type=" << makeQuoted(Type::name()) << " version= " << makeQuoted(int(Type::VERSION)) << " name=" << makeQuoted(name) << " - not found\n"; + break; + } + else + { + m_modules.insert(name, *module); + } + } + } + } + } + ModulesMap& get() + { + return m_modules; + } +}; + +#endif diff --git a/tools/urt/libs/modulesystem/singletonmodule.cpp b/tools/urt/libs/modulesystem/singletonmodule.cpp new file mode 100644 index 00000000..9d6a6823 --- /dev/null +++ b/tools/urt/libs/modulesystem/singletonmodule.cpp @@ -0,0 +1,33 @@ + +#include "singletonmodule.h" + +class NullType +{ +public: + enum { VERSION = 1 }; + static const char* name() + { + return ""; + } +}; + +class NullModule +{ +public: + typedef NullType Type; + static const char* getName() + { + return ""; + } + void* getTable() + { + return NULL; + } +}; + +void TEST_SINGLETONMODULE() +{ + SingletonModule null; + null.capture(); + null.release(); +} diff --git a/tools/urt/libs/modulesystem/singletonmodule.h b/tools/urt/libs/modulesystem/singletonmodule.h new file mode 100644 index 00000000..d60a1679 --- /dev/null +++ b/tools/urt/libs/modulesystem/singletonmodule.h @@ -0,0 +1,130 @@ + +#if !defined(INCLUDED_MODULESYSTEM_SINGLETONMODULE_H) +#define INCLUDED_MODULESYSTEM_SINGLETONMODULE_H + +#include "modulesystem.h" +#include +#include "debugging/debugging.h" +#include "modulesystem/moduleregistry.h" +#include "generic/reference.h" + +template +class DefaultAPIConstructor +{ +public: + const char* getName() + { + return API::getName(); + } + + API* constructAPI(Dependencies& dependencies) + { + return new API; + } + void destroyAPI(API* api) + { + delete api; + } +}; + +template +class DependenciesAPIConstructor +{ +public: + const char* getName() + { + return API::getName(); + } + + API* constructAPI(Dependencies& dependencies) + { + return new API(dependencies); + } + void destroyAPI(API* api) + { + delete api; + } +}; + +class NullDependencies +{ +}; + + +template > +class SingletonModule : public APIConstructor, public Module, public ModuleRegisterable +{ + Dependencies* m_dependencies; + API* m_api; + std::size_t m_refcount; + bool m_dependencyCheck; + bool m_cycleCheck; +public: + typedef typename API::Type Type; + + SingletonModule() + : m_dependencies(0), m_api(0), m_refcount(0), m_dependencyCheck(false), m_cycleCheck(false) + { + } + explicit SingletonModule(const APIConstructor& constructor) + : APIConstructor(constructor), m_dependencies(0), m_api(0), m_refcount(0), m_dependencyCheck(false), m_cycleCheck(false) + { + } + ~SingletonModule() + { + ASSERT_MESSAGE(m_refcount == 0, "module still referenced at shutdown"); + } + + void selfRegister() + { + globalModuleServer().registerModule(Type::name(), Type::VERSION, APIConstructor::getName(), *this); + } + + Dependencies& getDependencies() + { + return *m_dependencies; + } + void* getTable() + { + if(m_api != 0) + { + return m_api->getTable(); + } + return 0; + } + void capture() + { + if(++m_refcount == 1) + { + globalOutputStream() << "Module Initialising: '" << Type::name() << "' '" << APIConstructor::getName() << "'\n"; + m_dependencies = new Dependencies(); + m_dependencyCheck = !globalModuleServer().getError(); + if(m_dependencyCheck) + { + m_api = APIConstructor::constructAPI(*m_dependencies); + globalOutputStream() << "Module Ready: '" << Type::name() << "' '" << APIConstructor::getName() << "'\n"; + } + else + { + globalOutputStream() << "Module Dependencies Failed: '" << Type::name() << "' '" << APIConstructor::getName() << "'\n"; + } + m_cycleCheck = true; + } + + ASSERT_MESSAGE(m_cycleCheck, "cyclic dependency detected"); + } + void release() + { + if(--m_refcount == 0) + { + if(m_dependencyCheck) + { + APIConstructor::destroyAPI(m_api); + } + delete m_dependencies; + } + } +}; + + +#endif diff --git a/tools/urt/libs/os/dir.cpp b/tools/urt/libs/os/dir.cpp new file mode 100644 index 00000000..f107d9bb --- /dev/null +++ b/tools/urt/libs/os/dir.cpp @@ -0,0 +1,3 @@ + +#include "dir.h" + diff --git a/tools/urt/libs/os/dir.h b/tools/urt/libs/os/dir.h new file mode 100644 index 00000000..ffdf01d2 --- /dev/null +++ b/tools/urt/libs/os/dir.h @@ -0,0 +1,55 @@ + +#if !defined(INCLUDED_OS_DIR_H) +#define INCLUDED_OS_DIR_H + +/// \file +/// \brief OS directory-listing object. + +#include + +typedef GDir Directory; + +inline bool directory_good(Directory* directory) +{ + return directory != 0; +} + +inline Directory* directory_open(const char* name) +{ + return g_dir_open(name, 0, 0); +} + +inline void directory_close(Directory* directory) +{ + g_dir_close(directory); +} + +inline const char* directory_read_and_increment(Directory* directory) +{ + return g_dir_read_name(directory); +} + +template +void Directory_forEach(const char* path, const Functor& functor) +{ + Directory* dir = directory_open(path); + + if(directory_good(dir)) + { + for(;;) + { + const char* name = directory_read_and_increment(dir); + if(name == 0) + { + break; + } + + functor(name); + } + + directory_close(dir); + } +} + + +#endif diff --git a/tools/urt/libs/os/file.cpp b/tools/urt/libs/os/file.cpp new file mode 100644 index 00000000..2220905c --- /dev/null +++ b/tools/urt/libs/os/file.cpp @@ -0,0 +1,3 @@ + +#include "file.h" + diff --git a/tools/urt/libs/os/file.h b/tools/urt/libs/os/file.h new file mode 100644 index 00000000..94f9975e --- /dev/null +++ b/tools/urt/libs/os/file.h @@ -0,0 +1,123 @@ + +#if !defined(INCLUDED_OS_FILE_H) +#define INCLUDED_OS_FILE_H + +/// \file +/// \brief OS file-system querying and manipulation. + +#if defined( WIN32 ) +#define S_ISDIR(mode) (mode & _S_IFDIR) +#include // access() +#define F_OK 0x00 +#define W_OK 0x02 +#define R_OK 0x04 +#else +#include // access() +#endif + +#include // rename(), remove() +#include // stat() +#include // this is included by stat.h on win32 +#include +#include + +/// \brief Attempts to move the file identified by \p from to \p to and returns true if the operation was successful. +/// +/// The operation will fail unless: +/// - The path \p from identifies an existing file which is accessible for writing. +/// - The directory component of \p from identifies an existing directory which is accessible for writing. +/// - The path \p to does not identify an existing file or directory. +/// - The directory component of \p to identifies an existing directory which is accessible for writing. +inline bool file_move(const char* from, const char* to) +{ + return rename(from, to) == 0; +} + +/// \brief Attempts to remove the file identified by \p path and returns true if the operation was successful. +/// +/// The operation will fail unless: +/// - The \p path identifies an existing file. +/// - The parent-directory component of \p path identifies an existing directory which is accessible for writing. +inline bool file_remove(const char* path) +{ + return remove(path) == 0; +} + +namespace FileAccess +{ + enum Mode + { + Read = R_OK, + Write = W_OK, + ReadWrite = Read | Write, + Exists = F_OK + }; +} + +/// \brief Returns true if the file or directory identified by \p path exists and/or may be accessed for reading, writing or both, depending on the value of \p mode. +inline bool file_accessible(const char* path, FileAccess::Mode mode) +{ + return access(path, static_cast(mode)) == 0; +} + +/// \brief Returns true if the file or directory identified by \p path exists and may be opened for reading. +inline bool file_readable(const char* path) +{ + return file_accessible(path, FileAccess::Read); +} + +/// \brief Returns true if the file or directory identified by \p path exists and may be opened for writing. +inline bool file_writeable(const char* path) +{ + return file_accessible(path, FileAccess::Write); +} + +/// \brief Returns true if the file or directory identified by \p path exists. +inline bool file_exists(const char* path) +{ + return file_accessible(path, FileAccess::Exists); +} + +/// \brief Returns true if the file or directory identified by \p path exists and is a directory. +inline bool file_is_directory(const char* path) +{ + struct stat st; + if(stat(path, &st) == -1) + { + return false; + } + return S_ISDIR (st.st_mode) != 0; +} + +typedef std::size_t FileSize; + +/// \brief Returns the size in bytes of the file identified by \p path, or 0 if the file was not found. +inline FileSize file_size(const char* path) +{ + struct stat st; + if(stat(path, &st) == -1) + { + return 0; + } + return st.st_size; +} + +/// Seconds elapsed since Jan 1, 1970 +typedef std::time_t FileTime; +/// No file can have been modified earlier than this time. +const FileTime c_invalidFileTime = -1; + +/// \brief Returns the time that the file identified by \p path was last modified, or c_invalidFileTime if the file was not found. +inline FileTime file_modified(const char* path) +{ + struct stat st; + if(stat(path, &st) == -1) + { + return c_invalidFileTime; + } + return st.st_mtime; +} + + + +#endif diff --git a/tools/urt/libs/os/path.cpp b/tools/urt/libs/os/path.cpp new file mode 100644 index 00000000..5abd021a --- /dev/null +++ b/tools/urt/libs/os/path.cpp @@ -0,0 +1,2 @@ + +#include "path.h" diff --git a/tools/urt/libs/os/path.h b/tools/urt/libs/os/path.h new file mode 100644 index 00000000..0ccbf096 --- /dev/null +++ b/tools/urt/libs/os/path.h @@ -0,0 +1,263 @@ + +#if !defined (INCLUDED_OS_PATH_H) +#define INCLUDED_OS_PATH_H + +/// \file +/// \brief OS file-system path comparison, decomposition and manipulation. +/// +/// - Paths are c-style null-terminated-character-arrays. +/// - Path separators must be forward slashes (unix style). +/// - Directory paths must end in a separator. +/// - Paths must not contain the ascii characters \\ : * ? " < > or |. +/// - Paths may be encoded in UTF-8 or any extended-ascii character set. + +#include "string/string.h" + +#if defined(WIN32) +#define OS_CASE_INSENSITIVE +#endif + +/// \brief Returns true if \p path is lexicographically sorted before \p other. +/// If both \p path and \p other refer to the same file, neither will be sorted before the other. +/// O(n) +inline bool path_less(const char* path, const char* other) +{ +#if defined(OS_CASE_INSENSITIVE) + return string_less_nocase(path, other); +#else + return string_less(path, other); +#endif +} + +/// \brief Returns <0 if \p path is lexicographically less than \p other. +/// Returns >0 if \p path is lexicographically greater than \p other. +/// Returns 0 if both \p path and \p other refer to the same file. +/// O(n) +inline int path_compare(const char* path, const char* other) +{ +#if defined(OS_CASE_INSENSITIVE) + return string_compare_nocase(path, other); +#else + return string_compare(path, other); +#endif +} + +/// \brief Returns true if \p path and \p other refer to the same file or directory. +/// O(n) +inline bool path_equal(const char* path, const char* other) +{ +#if defined(OS_CASE_INSENSITIVE) + return string_equal_nocase(path, other); +#else + return string_equal(path, other); +#endif +} + +/// \brief Returns true if the first \p n bytes of \p path and \p other form paths that refer to the same file or directory. +/// If the paths are UTF-8 encoded, [\p path, \p path + \p n) must be a complete path. +/// O(n) +inline bool path_equal_n(const char* path, const char* other, std::size_t n) +{ +#if defined(OS_CASE_INSENSITIVE) + return string_equal_nocase_n(path, other, n); +#else + return string_equal_n(path, other, n); +#endif +} + + +/// \brief Returns true if \p path is a fully qualified file-system path. +/// O(1) +inline bool path_is_absolute(const char* path) +{ +#if defined(WIN32) + return path[0] == '/' + || (path[0] != '\0' && path[1] == ':'); // local drive +#elif defined(__linux__) || defined(__APPLE__) + return path[0] == '/'; +#endif +} + +/// \brief Returns true if \p path is a directory. +/// O(n) +inline bool path_is_directory(const char* path) +{ + std::size_t length = strlen(path); + if(length > 0) + { + return path[length-1] == '/'; + } + return false; +} + +/// \brief Returns a pointer to the first character of the component of \p path following the first directory component. +/// O(n) +inline const char* path_remove_directory(const char* path) +{ + const char* first_separator = strchr(path, '/'); + if(first_separator != 0) + { + return ++first_separator; + } + return ""; +} + +/// \brief Returns a pointer to the first character of the filename component of \p path. +/// O(n) +inline const char* path_get_filename_start(const char* path) +{ + { + const char* last_forward_slash = strrchr(path, '/'); + if(last_forward_slash != 0) + { + return last_forward_slash + 1; + } + } + + // not strictly necessary,since paths should not contain '\' + { + const char* last_backward_slash = strrchr(path, '\\'); + if(last_backward_slash != 0) + { + return last_backward_slash + 1; + } + } + + return path; +} + +/// \brief Returns a pointer to the character after the end of the filename component of \p path - either the extension separator or the terminating null character. +/// O(n) +inline const char* path_get_filename_base_end(const char* path) +{ + const char* last_period = strrchr(path_get_filename_start(path), '.'); + return (last_period != 0) ? last_period : path + string_length(path); +} + +/// \brief Returns the length of the filename component (not including extension) of \p path. +/// O(n) +inline std::size_t path_get_filename_base_length(const char* path) +{ + return path_get_filename_base_end(path) - path; +} + +/// \brief If \p path is a child of \p base, returns the subpath relative to \p base, else returns \p path. +/// O(n) +inline const char* path_make_relative(const char* path, const char* base) +{ + const std::size_t length = string_length(base); + if(path_equal_n(path, base, length)) + { + return path + length; + } + return path; +} + +/// \brief Returns a pointer to the first character of the file extension of \p path, or "" if not found. +/// O(n) +inline const char* path_get_extension(const char* path) +{ + const char* last_period = strrchr(path_get_filename_start(path), '.'); + if(last_period != 0) + { + return ++last_period; + } + return ""; +} + +/// \brief Returns true if \p extension is of the same type as \p other. +/// O(n) +inline bool extension_equal(const char* extension, const char* other) +{ + return path_equal(extension, other); +} + +template +class MatchFileExtension +{ + const char* m_extension; + const Functor& m_functor; +public: + MatchFileExtension(const char* extension, const Functor& functor) : m_extension(extension), m_functor(functor) + { + } + void operator()(const char* name) const + { + const char* extension = path_get_extension(name); + if(extension_equal(extension, m_extension)) + { + m_functor(name); + } + } +}; + +/// \brief A functor which invokes its contained \p functor if the \p name argument matches its \p extension. +template +inline MatchFileExtension matchFileExtension(const char* extension, const Functor& functor) +{ + return MatchFileExtension(extension, functor); +} + +class PathCleaned +{ +public: + const char* m_path; + PathCleaned(const char* path) : m_path(path) + { + } +}; + +/// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators. +template +TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const PathCleaned& path) +{ + const char* i = path.m_path; + for(; *i != '\0'; ++i) + { + if(*i == '\\') + { + ostream << '/'; + } + else + { + ostream << *i; + } + } + return ostream; +} + +class DirectoryCleaned +{ +public: + const char* m_path; + DirectoryCleaned(const char* path) : m_path(path) + { + } +}; + +/// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators, and appends a separator if necessary. +template +TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const DirectoryCleaned& path) +{ + const char* i = path.m_path; + for(; *i != '\0'; ++i) + { + if(*i == '\\') + { + ostream << '/'; + } + else + { + ostream << *i; + } + } + char c = *(i - 1); + if(c != '/' && c != '\\') + { + ostream << '/'; + } + return ostream; +} + + +#endif diff --git a/tools/urt/libs/picomodel.h b/tools/urt/libs/picomodel.h new file mode 100644 index 00000000..dbb38a93 --- /dev/null +++ b/tools/urt/libs/picomodel.h @@ -0,0 +1,353 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef PICOMODEL_H +#define PICOMODEL_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + + + +/* version */ +#define PICOMODEL_VERSION "0.8.20" + + +/* constants */ +#define PICO_GROW_SHADERS 16 +#define PICO_GROW_SURFACES 16 +#define PICO_GROW_VERTEXES 1024 +#define PICO_GROW_INDEXES 1024 +#define PICO_GROW_ARRAYS 8 +#define PICO_GROW_FACES 256 +#define PICO_MAX_SPECIAL 8 +#define PICO_MAX_DEFAULT_EXTS 4 /* max default extensions per module */ + + +/* types */ +typedef unsigned char picoByte_t; +typedef float picoVec_t; +typedef float picoVec2_t[ 2 ]; +typedef float picoVec3_t[ 3 ]; +typedef float picoVec4_t[ 4 ]; +typedef picoByte_t picoColor_t[ 4 ]; +typedef int picoIndex_t; + +typedef enum +{ + PICO_BAD, + PICO_TRIANGLES, + PICO_PATCH +} +picoSurfaceType_t; + +typedef enum +{ + PICO_NORMAL, + PICO_VERBOSE, + PICO_WARNING, + PICO_ERROR, + PICO_FATAL +} +picoPrintLevel_t; + +typedef struct picoSurface_s picoSurface_t; +typedef struct picoShader_s picoShader_t; +typedef struct picoModel_s picoModel_t; +typedef struct picoModule_s picoModule_t; + +struct picoSurface_s +{ + void *data; + + picoModel_t *model; /* owner model */ + + picoSurfaceType_t type; + char *name; /* sea: surface name */ + picoShader_t *shader; /* ydnar: changed to ptr */ + + int numVertexes, maxVertexes; + picoVec3_t *xyz; + picoVec3_t *normal; + picoIndex_t *smoothingGroup; + + int numSTArrays, maxSTArrays; + picoVec2_t **st; + + int numColorArrays, maxColorArrays; + picoColor_t **color; + + int numIndexes, maxIndexes; + picoIndex_t *index; + + int numFaceNormals, maxFaceNormals; + picoVec3_t *faceNormal; + + int special[ PICO_MAX_SPECIAL ]; + + int submodel; //sub models are tracked +}; + + +/* seaw0lf */ +struct picoShader_s +{ + picoModel_t *model; /* owner model */ + + char *name; /* shader name */ + char *mapName; /* shader file name (name of diffuse texturemap) */ + picoColor_t ambientColor; /* ambient color of mesh (rgba) */ + picoColor_t diffuseColor; /* diffuse color of mesh (rgba) */ + picoColor_t specularColor; /* specular color of mesh (rgba) */ + float transparency; /* transparency (0..1; 1 = 100% transparent) */ + float shininess; /* shininess (0..128; 128 = 100% shiny) */ +}; + +struct picoModel_s +{ + void *data; + char *name; /* model name */ + char *fileName; /* sea: model file name */ + int frameNum; /* sea: renamed to frameNum */ + int numFrames; /* sea: number of frames */ + picoVec3_t mins; + picoVec3_t maxs; + + int numShaders, maxShaders; + picoShader_t **shader; + + int numSurfaces, maxSurfaces; + picoSurface_t **surface; + + const picoModule_t *module; /* sea */ +}; + + +/* seaw0lf */ +/* return codes used by the validation callbacks; pmv is short */ +/* for 'pico module validation'. everything >PICO_PMV_OK means */ +/* that there was an error. */ +enum +{ + PICO_PMV_OK, /* file valid */ + PICO_PMV_ERROR, /* file not valid */ + PICO_PMV_ERROR_IDENT, /* unknown file magic (aka ident) */ + PICO_PMV_ERROR_VERSION, /* unsupported file version */ + PICO_PMV_ERROR_SIZE, /* file size error */ + PICO_PMV_ERROR_MEMORY, /* out of memory error */ +}; + +/* convenience (makes it easy to add new params to the callbacks) */ +#define PM_PARAMS_CANLOAD \ + char *fileName, const void *buffer, int bufSize + +#define PM_PARAMS_LOAD \ + char *fileName, int frameNum, const void *buffer, int bufSize + +#define PM_PARAMS_CANSAVE \ + void + +#define PM_PARAMS_SAVE \ + char *fileName, picoModel_t *model + +/* pico file format module structure */ +struct picoModule_s +{ + char *version; /* internal module version (e.g. '1.5-b2') */ + + char *displayName; /* string used to display in guis, etc. */ + char *authorName; /* author name (eg. 'My Real Name') */ + char *copyright; /* copyright year and holder (eg. '2002 My Company') */ + + char *defaultExts[ PICO_MAX_DEFAULT_EXTS ]; /* default file extensions used by this file type */ + int (*canload)( PM_PARAMS_CANLOAD ); /* checks whether module can load given file (returns PMVR_*) */ + picoModel_t *(*load)( PM_PARAMS_LOAD ); /* parses model file data */ + int (*cansave)( PM_PARAMS_CANSAVE ); /* checks whether module can save (returns 1 or 0 and might spit out a message) */ + int (*save)( PM_PARAMS_SAVE ); /* saves a pico model in module's native model format */ +}; + + + +/* general functions */ +int PicoInit( void ); +void PicoShutdown( void ); +int PicoError( void ); + +void PicoSetMallocFunc( void *(*func)( size_t ) ); +void PicoSetFreeFunc( void (*func)( void* ) ); +void PicoSetLoadFileFunc( void (*func)( char*, unsigned char**, int* ) ); +void PicoSetFreeFileFunc( void (*func)( void* ) ); +void PicoSetPrintFunc( void (*func)( int, const char* ) ); + +const picoModule_t **PicoModuleList( int *numModules ); + +picoModel_t *PicoLoadModel( char *name, int frameNum ); + +typedef size_t (*PicoInputStreamReadFunc)(void* inputStream, unsigned char* buffer, size_t length); +picoModel_t* PicoModuleLoadModelStream( const picoModule_t* module, void* inputStream, PicoInputStreamReadFunc inputStreamRead, size_t streamLength, int frameNum ); + +/* model functions */ +picoModel_t *PicoNewModel( void ); +void PicoFreeModel( picoModel_t *model ); +int PicoAdjustModel( picoModel_t *model, int numShaders, int numSurfaces ); + + +/* shader functions */ +picoShader_t *PicoNewShader( picoModel_t *model ); +void PicoFreeShader( picoShader_t *shader ); +picoShader_t *PicoFindShader( picoModel_t *model, char *name, int caseSensitive ); + + +/* surface functions */ +picoSurface_t *PicoNewSurface( picoModel_t *model ); +void PicoFreeSurface( picoSurface_t *surface ); +picoSurface_t *PicoFindSurface( picoModel_t *model, char *name, int caseSensitive ); +int PicoAdjustSurface( picoSurface_t *surface, int numVertexes, int numSTArrays, int numColorArrays, int numIndexes, int numFaceNormals ); + + +/* setter functions */ +void PicoSetModelName( picoModel_t *model, char *name ); +void PicoSetModelFileName( picoModel_t *model, char *fileName ); +void PicoSetModelFrameNum( picoModel_t *model, int frameNum ); +void PicoSetModelNumFrames( picoModel_t *model, int numFrames ); +void PicoSetModelData( picoModel_t *model, void *data ); + +void PicoSetShaderName( picoShader_t *shader, char *name ); +void PicoSetShaderMapName( picoShader_t *shader, char *mapName ); +void PicoSetShaderAmbientColor( picoShader_t *shader, picoColor_t color ); +void PicoSetShaderDiffuseColor( picoShader_t *shader, picoColor_t color ); +void PicoSetShaderSpecularColor( picoShader_t *shader, picoColor_t color ); +void PicoSetShaderTransparency( picoShader_t *shader, float value ); +void PicoSetShaderShininess( picoShader_t *shader, float value ); + +void PicoSetSurfaceData( picoSurface_t *surface, void *data ); +void PicoSetSurfaceType( picoSurface_t *surface, picoSurfaceType_t type ); +void PicoSetSurfaceName( picoSurface_t *surface, char *name ); +void PicoSetSurfaceShader( picoSurface_t *surface, picoShader_t *shader ); +void PicoSetSurfaceXYZ( picoSurface_t *surface, int num, picoVec3_t xyz ); +void PicoSetSurfaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ); +void PicoSetSurfaceST( picoSurface_t *surface, int array, int num, picoVec2_t st ); +void PicoSetSurfaceColor( picoSurface_t *surface, int array, int num, picoColor_t color ); +void PicoSetSurfaceIndex( picoSurface_t *surface, int num, picoIndex_t index ); +void PicoSetSurfaceIndexes( picoSurface_t *surface, int num, picoIndex_t *index, int count ); +void PicoSetFaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ); +void PicoSetSurfaceSpecial( picoSurface_t *surface, int num, int special ); +void PicoSetSurfaceSmoothingGroup( picoSurface_t *surface, int num, picoIndex_t smoothingGroup ); + + +/* getter functions */ +char *PicoGetModelName( picoModel_t *model ); +char *PicoGetModelFileName( picoModel_t *model ); +int PicoGetModelFrameNum( picoModel_t *model ); +int PicoGetModelNumFrames( picoModel_t *model ); +void *PicoGetModelData( picoModel_t *model ); +int PicoGetModelNumShaders( picoModel_t *model ); +picoShader_t *PicoGetModelShader( picoModel_t *model, int num ); /* sea */ +int PicoGetModelNumSurfaces( picoModel_t *model ); +picoSurface_t *PicoGetModelSurface( picoModel_t *model, int num ); +int PicoGetModelTotalVertexes( picoModel_t *model ); +int PicoGetModelTotalIndexes( picoModel_t *model ); + +char *PicoGetShaderName( picoShader_t *shader ); +char *PicoGetShaderMapName( picoShader_t *shader ); +picoByte_t *PicoGetShaderAmbientColor( picoShader_t *shader ); +picoByte_t *PicoGetShaderDiffuseColor( picoShader_t *shader ); +picoByte_t *PicoGetShaderSpecularColor( picoShader_t *shader ); +float PicoGetShaderTransparency( picoShader_t *shader ); +float PicoGetShaderShininess( picoShader_t *shader ); + +void *PicoGetSurfaceData( picoSurface_t *surface ); +char *PicoGetSurfaceName( picoSurface_t *surface ); /* sea */ +picoSurfaceType_t PicoGetSurfaceType( picoSurface_t *surface ); +char *PicoGetSurfaceName( picoSurface_t *surface ); +picoShader_t *PicoGetSurfaceShader( picoSurface_t *surface ); /* sea */ + +int PicoGetSurfaceNumVertexes( picoSurface_t *surface ); +picoVec_t *PicoGetSurfaceXYZ( picoSurface_t *surface, int num ); +picoVec_t *PicoGetSurfaceNormal( picoSurface_t *surface, int num ); +picoVec_t *PicoGetSurfaceST( picoSurface_t *surface, int array, int num ); +picoByte_t *PicoGetSurfaceColor( picoSurface_t *surface, int array, int num ); +int PicoGetSurfaceNumIndexes( picoSurface_t *surface ); +picoIndex_t PicoGetSurfaceIndex( picoSurface_t *surface, int num ); +picoIndex_t *PicoGetSurfaceIndexes( picoSurface_t *surface, int num ); +picoVec_t *PicoGetFaceNormal( picoSurface_t *surface, int num ); +int PicoGetSurfaceSpecial( picoSurface_t *surface, int num ); + + +/* hashtable related functions */ +typedef struct picoVertexCombinationData_s +{ + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; +} picoVertexCombinationData_t; + +typedef struct picoVertexCombinationHash_s +{ + picoVertexCombinationData_t vcd; + picoIndex_t index; + + void *data; + + struct picoVertexCombinationHash_s *next; +} picoVertexCombinationHash_t; + +int PicoGetHashTableSize( void ); +unsigned int PicoVertexCoordGenerateHash( picoVec3_t xyz ); +picoVertexCombinationHash_t **PicoNewVertexCombinationHashTable( void ); +void PicoFreeVertexCombinationHashTable( picoVertexCombinationHash_t **hashTable ); +picoVertexCombinationHash_t *PicoFindVertexCombinationInHashTable( picoVertexCombinationHash_t **hashTable, picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color ); +picoVertexCombinationHash_t *PicoAddVertexCombinationToHashTable( picoVertexCombinationHash_t **hashTable, picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color, picoIndex_t index ); + +/* specialized functions */ +int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color, picoIndex_t smoothingGroup ); +void PicoFixSurfaceNormals( picoSurface_t *surface ); +int PicoRemapModel( picoModel_t *model, char *remapFile ); + + +void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** normals, int numSTs, picoVec2_t **st, int numColors, picoColor_t **colors, picoShader_t* shader, picoIndex_t* smoothingGroupm, int submodel); + +/* end marker */ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/urt/libs/picomodel/lwo/clip.c b/tools/urt/libs/picomodel/lwo/clip.c new file mode 100644 index 00000000..0d8e1360 --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/clip.c @@ -0,0 +1,278 @@ +/* +====================================================================== +clip.c + +Functions for LWO2 image references. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwFreeClip() + +Free memory used by an lwClip. +====================================================================== */ + +void lwFreeClip( lwClip *clip ) +{ + if ( clip ) { + lwListFree( clip->ifilter, (void *) lwFreePlugin ); + lwListFree( clip->pfilter, (void *) lwFreePlugin ); + + switch ( clip->type ) { + case ID_STIL: + _pico_free( clip->source.still.name); + break; + + case ID_ISEQ: + _pico_free( clip->source.seq.prefix ); + _pico_free( clip->source.seq.suffix ); + break; + + case ID_ANIM: + _pico_free( clip->source.anim.name ); + _pico_free( clip->source.anim.server ); + _pico_free( clip->source.anim.data ); + break; + + case ID_XREF: + _pico_free( clip->source.xref.string ); + break; + + case ID_STCC: + _pico_free( clip->source.cycle.name ); + break; + + default: + break; + } + + _pico_free( clip ); + } +} + + +/* +====================================================================== +lwGetClip() + +Read image references from a CLIP chunk in an LWO2 file. +====================================================================== */ + +lwClip *lwGetClip( picoMemStream_t *fp, int cksize ) +{ + lwClip *clip; + lwPlugin *filt; + unsigned int id; + unsigned short sz; + int pos, rlen; + + + /* allocate the Clip structure */ + + clip = _pico_calloc( 1, sizeof( lwClip )); + if ( !clip ) goto Fail; + + clip->contrast.val = 1.0f; + clip->brightness.val = 1.0f; + clip->saturation.val = 1.0f; + clip->gamma.val = 1.0f; + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* index */ + + clip->index = getI4( fp ); + + /* first subchunk header */ + + clip->type = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + sz += sz & 1; + set_flen( 0 ); + + switch ( clip->type ) { + case ID_STIL: + clip->source.still.name = getS0( fp ); + break; + + case ID_ISEQ: + clip->source.seq.digits = getU1( fp ); + clip->source.seq.flags = getU1( fp ); + clip->source.seq.offset = getI2( fp ); + getU2( fp ); /* not sure what this is yet */ + clip->source.seq.start = getI2( fp ); + clip->source.seq.end = getI2( fp ); + clip->source.seq.prefix = getS0( fp ); + clip->source.seq.suffix = getS0( fp ); + break; + + case ID_ANIM: + clip->source.anim.name = getS0( fp ); + clip->source.anim.server = getS0( fp ); + rlen = get_flen(); + clip->source.anim.data = getbytes( fp, sz - rlen ); + break; + + case ID_XREF: + clip->source.xref.index = getI4( fp ); + clip->source.xref.string = getS0( fp ); + break; + + case ID_STCC: + clip->source.cycle.lo = getI2( fp ); + clip->source.cycle.hi = getI2( fp ); + clip->source.cycle.name = getS0( fp ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the CLIP chunk? */ + + rlen = _pico_memstream_tell( fp ) - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) + return clip; + + /* process subchunks as they're encountered */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TIME: + clip->start_time = getF4( fp ); + clip->duration = getF4( fp ); + clip->frame_rate = getF4( fp ); + break; + + case ID_CONT: + clip->contrast.val = getF4( fp ); + clip->contrast.eindex = getVX( fp ); + break; + + case ID_BRIT: + clip->brightness.val = getF4( fp ); + clip->brightness.eindex = getVX( fp ); + break; + + case ID_SATR: + clip->saturation.val = getF4( fp ); + clip->saturation.eindex = getVX( fp ); + break; + + case ID_HUE: + clip->hue.val = getF4( fp ); + clip->hue.eindex = getVX( fp ); + break; + + case ID_GAMM: + clip->gamma.val = getF4( fp ); + clip->gamma.eindex = getVX( fp ); + break; + + case ID_NEGA: + clip->negative = getU2( fp ); + break; + + case ID_IFLT: + case ID_PFLT: + filt = _pico_calloc( 1, sizeof( lwPlugin )); + if ( !filt ) goto Fail; + + filt->name = getS0( fp ); + filt->flags = getU2( fp ); + rlen = get_flen(); + filt->data = getbytes( fp, sz - rlen ); + + if ( id == ID_IFLT ) { + lwListAdd( (void *) &clip->ifilter, filt ); + clip->nifilters++; + } + else { + lwListAdd( (void *) &clip->pfilter, filt ); + clip->npfilters++; + } + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the CLIP chunk? */ + + rlen = _pico_memstream_tell( fp ) - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return clip; + +Fail: + lwFreeClip( clip ); + return NULL; +} + + +/* +====================================================================== +lwFindClip() + +Returns an lwClip pointer, given a clip index. +====================================================================== */ + +lwClip *lwFindClip( lwClip *list, int index ) +{ + lwClip *clip; + + clip = list; + while ( clip ) { + if ( clip->index == index ) break; + clip = clip->next; + } + return clip; +} diff --git a/tools/urt/libs/picomodel/lwo/envelope.c b/tools/urt/libs/picomodel/lwo/envelope.c new file mode 100644 index 00000000..94e9c906 --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/envelope.c @@ -0,0 +1,600 @@ +/* +====================================================================== +envelope.c + +Envelope functions for an LWO2 reader. + +Ernie Wright 16 Nov 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + +/* +====================================================================== +lwFreeEnvelope() + +Free the memory used by an lwEnvelope. +====================================================================== */ + +void lwFreeEnvelope( lwEnvelope *env ) +{ + if ( env ) { + if ( env->name ) _pico_free( env->name ); + lwListFree( env->key, _pico_free ); + lwListFree( env->cfilter, (void *) lwFreePlugin ); + _pico_free( env ); + } +} + + +static int compare_keys( lwKey *k1, lwKey *k2 ) +{ + return k1->time > k2->time ? 1 : k1->time < k2->time ? -1 : 0; +} + + +/* +====================================================================== +lwGetEnvelope() + +Read an ENVL chunk from an LWO2 file. +====================================================================== */ + +lwEnvelope *lwGetEnvelope( picoMemStream_t *fp, int cksize ) +{ + lwEnvelope *env; + lwKey *key; + lwPlugin *plug; + unsigned int id; + unsigned short sz; + float f[ 4 ]; + int i, nparams, pos, rlen; + + + /* allocate the Envelope structure */ + + env = _pico_calloc( 1, sizeof( lwEnvelope )); + if ( !env ) goto Fail; + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* index */ + + env->index = getVX( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TYPE: + env->type = getU2( fp ); + break; + + case ID_NAME: + env->name = getS0( fp ); + break; + + case ID_PRE: + env->behavior[ 0 ] = getU2( fp ); + break; + + case ID_POST: + env->behavior[ 1 ] = getU2( fp ); + break; + + case ID_KEY: + key = _pico_calloc( 1, sizeof( lwKey )); + if ( !key ) goto Fail; + key->time = getF4( fp ); + key->value = getF4( fp ); + lwListInsert( (void **) &env->key, key, (void *) compare_keys ); + env->nkeys++; + break; + + case ID_SPAN: + if ( !key ) goto Fail; + key->shape = getU4( fp ); + + nparams = ( sz - 4 ) / 4; + if ( nparams > 4 ) nparams = 4; + for ( i = 0; i < nparams; i++ ) + f[ i ] = getF4( fp ); + + switch ( key->shape ) { + case ID_TCB: + key->tension = f[ 0 ]; + key->continuity = f[ 1 ]; + key->bias = f[ 2 ]; + break; + + case ID_BEZI: + case ID_HERM: + case ID_BEZ2: + for ( i = 0; i < nparams; i++ ) + key->param[ i ] = f[ i ]; + break; + } + break; + + case ID_CHAN: + plug = _pico_calloc( 1, sizeof( lwPlugin )); + if ( !plug ) goto Fail; + + plug->name = getS0( fp ); + plug->flags = getU2( fp ); + plug->data = getbytes( fp, sz - get_flen() ); + + lwListAdd( (void *) &env->cfilter, plug ); + env->ncfilters++; + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the ENVL chunk? */ + + rlen = _pico_memstream_tell( fp ) - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return env; + +Fail: + lwFreeEnvelope( env ); + return NULL; +} + + +/* +====================================================================== +lwFindEnvelope() + +Returns an lwEnvelope pointer, given an envelope index. +====================================================================== */ + +lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index ) +{ + lwEnvelope *env; + + env = list; + while ( env ) { + if ( env->index == index ) break; + env = env->next; + } + return env; +} + + +/* +====================================================================== +range() + +Given the value v of a periodic function, returns the equivalent value +v2 in the principal interval [lo, hi]. If i isn't NULL, it receives +the number of wavelengths between v and v2. + + v2 = v - i * (hi - lo) + +For example, range( 3 pi, 0, 2 pi, i ) returns pi, with i = 1. +====================================================================== */ + +static float range( float v, float lo, float hi, int *i ) +{ + float v2, r = hi - lo; + + if ( r == 0.0 ) { + if ( i ) *i = 0; + return lo; + } + + v2 = lo + v - r * ( float ) floor(( double ) v / r ); + if ( i ) *i = -( int )(( v2 - v ) / r + ( v2 > v ? 0.5 : -0.5 )); + + return v2; +} + + +/* +====================================================================== +hermite() + +Calculate the Hermite coefficients. +====================================================================== */ + +static void hermite( float t, float *h1, float *h2, float *h3, float *h4 ) +{ + float t2, t3; + + t2 = t * t; + t3 = t * t2; + + *h2 = 3.0f * t2 - t3 - t3; + *h1 = 1.0f - *h2; + *h4 = t3 - t2; + *h3 = *h4 - t2 + t; +} + + +/* +====================================================================== +bezier() + +Interpolate the value of a 1D Bezier curve. +====================================================================== */ + +static float bezier( float x0, float x1, float x2, float x3, float t ) +{ + float a, b, c, t2, t3; + + t2 = t * t; + t3 = t2 * t; + + c = 3.0f * ( x1 - x0 ); + b = 3.0f * ( x2 - x1 ) - c; + a = x3 - x0 - c - b; + + return a * t3 + b * t2 + c * t + x0; +} + + +/* +====================================================================== +bez2_time() + +Find the t for which bezier() returns the input time. The handle +endpoints of a BEZ2 curve represent the control points, and these have +(time, value) coordinates, so time is used as both a coordinate and a +parameter for this curve type. +====================================================================== */ + +static float bez2_time( float x0, float x1, float x2, float x3, float time, + float *t0, float *t1 ) +{ + float v, t; + + t = *t0 + ( *t1 - *t0 ) * 0.5f; + v = bezier( x0, x1, x2, x3, t ); + if ( fabs( time - v ) > .0001f ) { + if ( v > time ) + *t1 = t; + else + *t0 = t; + return bez2_time( x0, x1, x2, x3, time, t0, t1 ); + } + else + return t; +} + + +/* +====================================================================== +bez2() + +Interpolate the value of a BEZ2 curve. +====================================================================== */ + +static float bez2( lwKey *key0, lwKey *key1, float time ) +{ + float x, y, t, t0 = 0.0f, t1 = 1.0f; + + if ( key0->shape == ID_BEZ2 ) + x = key0->time + key0->param[ 2 ]; + else + x = key0->time + ( key1->time - key0->time ) / 3.0f; + + t = bez2_time( key0->time, x, key1->time + key1->param[ 0 ], key1->time, + time, &t0, &t1 ); + + if ( key0->shape == ID_BEZ2 ) + y = key0->value + key0->param[ 3 ]; + else + y = key0->value + key0->param[ 1 ] / 3.0f; + + return bezier( key0->value, y, key1->param[ 1 ] + key1->value, key1->value, t ); +} + + +/* +====================================================================== +outgoing() + +Return the outgoing tangent to the curve at key0. The value returned +for the BEZ2 case is used when extrapolating a linear pre behavior and +when interpolating a non-BEZ2 span. +====================================================================== */ + +static float outgoing( lwKey *key0, lwKey *key1 ) +{ + float a, b, d, t, out; + + switch ( key0->shape ) + { + case ID_TCB: + a = ( 1.0f - key0->tension ) + * ( 1.0f + key0->continuity ) + * ( 1.0f + key0->bias ); + b = ( 1.0f - key0->tension ) + * ( 1.0f - key0->continuity ) + * ( 1.0f - key0->bias ); + d = key1->value - key0->value; + + if ( key0->prev ) { + t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + out = t * ( a * ( key0->value - key0->prev->value ) + b * d ); + } + else + out = b * d; + break; + + case ID_LINE: + d = key1->value - key0->value; + if ( key0->prev ) { + t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + out = t * ( key0->value - key0->prev->value + d ); + } + else + out = d; + break; + + case ID_BEZI: + case ID_HERM: + out = key0->param[ 1 ]; + if ( key0->prev ) + out *= ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + break; + + case ID_BEZ2: + out = key0->param[ 3 ] * ( key1->time - key0->time ); + if ( fabs( key0->param[ 2 ] ) > 1e-5f ) + out /= key0->param[ 2 ]; + else + out *= 1e5f; + break; + + case ID_STEP: + default: + out = 0.0f; + break; + } + + return out; +} + + +/* +====================================================================== +incoming() + +Return the incoming tangent to the curve at key1. The value returned +for the BEZ2 case is used when extrapolating a linear post behavior. +====================================================================== */ + +static float incoming( lwKey *key0, lwKey *key1 ) +{ + float a, b, d, t, in; + + switch ( key1->shape ) + { + case ID_LINE: + d = key1->value - key0->value; + if ( key1->next ) { + t = ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + in = t * ( key1->next->value - key1->value + d ); + } + else + in = d; + break; + + case ID_TCB: + a = ( 1.0f - key1->tension ) + * ( 1.0f - key1->continuity ) + * ( 1.0f + key1->bias ); + b = ( 1.0f - key1->tension ) + * ( 1.0f + key1->continuity ) + * ( 1.0f - key1->bias ); + d = key1->value - key0->value; + + if ( key1->next ) { + t = ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + in = t * ( b * ( key1->next->value - key1->value ) + a * d ); + } + else + in = a * d; + break; + + case ID_BEZI: + case ID_HERM: + in = key1->param[ 0 ]; + if ( key1->next ) + in *= ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + break; + return in; + + case ID_BEZ2: + in = key1->param[ 1 ] * ( key1->time - key0->time ); + if ( fabs( key1->param[ 0 ] ) > 1e-5f ) + in /= key1->param[ 0 ]; + else + in *= 1e5f; + break; + + case ID_STEP: + default: + in = 0.0f; + break; + } + + return in; +} + + +/* +====================================================================== +evalEnvelope() + +Given a list of keys and a time, returns the interpolated value of the +envelope at that time. +====================================================================== */ + +float evalEnvelope( lwEnvelope *env, float time ) +{ + lwKey *key0, *key1, *skey, *ekey; + float t, h1, h2, h3, h4, in, out, offset = 0.0f; + int noff; + + + /* if there's no key, the value is 0 */ + + if ( env->nkeys == 0 ) return 0.0f; + + /* if there's only one key, the value is constant */ + + if ( env->nkeys == 1 ) + return env->key->value; + + /* find the first and last keys */ + + skey = ekey = env->key; + while ( ekey->next ) ekey = ekey->next; + + /* use pre-behavior if time is before first key time */ + + if ( time < skey->time ) { + switch ( env->behavior[ 0 ] ) + { + case BEH_RESET: + return 0.0f; + + case BEH_CONSTANT: + return skey->value; + + case BEH_REPEAT: + time = range( time, skey->time, ekey->time, NULL ); + break; + + case BEH_OSCILLATE: + time = range( time, skey->time, ekey->time, &noff ); + if ( noff % 2 ) + time = ekey->time - skey->time - time; + break; + + case BEH_OFFSET: + time = range( time, skey->time, ekey->time, &noff ); + offset = noff * ( ekey->value - skey->value ); + break; + + case BEH_LINEAR: + out = outgoing( skey, skey->next ) + / ( skey->next->time - skey->time ); + return out * ( time - skey->time ) + skey->value; + } + } + + /* use post-behavior if time is after last key time */ + + else if ( time > ekey->time ) { + switch ( env->behavior[ 1 ] ) + { + case BEH_RESET: + return 0.0f; + + case BEH_CONSTANT: + return ekey->value; + + case BEH_REPEAT: + time = range( time, skey->time, ekey->time, NULL ); + break; + + case BEH_OSCILLATE: + time = range( time, skey->time, ekey->time, &noff ); + if ( noff % 2 ) + time = ekey->time - skey->time - time; + break; + + case BEH_OFFSET: + time = range( time, skey->time, ekey->time, &noff ); + offset = noff * ( ekey->value - skey->value ); + break; + + case BEH_LINEAR: + in = incoming( ekey->prev, ekey ) + / ( ekey->time - ekey->prev->time ); + return in * ( time - ekey->time ) + ekey->value; + } + } + + /* get the endpoints of the interval being evaluated */ + + key0 = env->key; + while ( time > key0->next->time ) + key0 = key0->next; + key1 = key0->next; + + /* check for singularities first */ + + if ( time == key0->time ) + return key0->value + offset; + else if ( time == key1->time ) + return key1->value + offset; + + /* get interval length, time in [0, 1] */ + + t = ( time - key0->time ) / ( key1->time - key0->time ); + + /* interpolate */ + + switch ( key1->shape ) + { + case ID_TCB: + case ID_BEZI: + case ID_HERM: + out = outgoing( key0, key1 ); + in = incoming( key0, key1 ); + hermite( t, &h1, &h2, &h3, &h4 ); + return h1 * key0->value + h2 * key1->value + h3 * out + h4 * in + offset; + + case ID_BEZ2: + return bez2( key0, key1, time ) + offset; + + case ID_LINE: + return key0->value + t * ( key1->value - key0->value ) + offset; + + case ID_STEP: + return key0->value + offset; + + default: + return offset; + } +} diff --git a/tools/urt/libs/picomodel/lwo/libs_rar.rar b/tools/urt/libs/picomodel/lwo/libs_rar.rar new file mode 100644 index 0000000000000000000000000000000000000000..6421f5cc943f51abcc6b9f5c6e572a214cca6eff GIT binary patch literal 537714 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

j&1i|(H|uJ+ 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

-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%0A^Z z)}P7s6g}Y#Acx=gYFJTy1}$W=6@IV+@GpWTg%aw>f@D&1|IVz14bEw$u>4J{JeF(o z3mtuf!;wh?I9j{1{F!1?oOhHSb!}R4AD-uj2}{o)8xDiDZ`Lk$JVX!kjWXpVIi}3# zFw$3ASBdg^_u!AIxuQ#|dTyJl7dC+rB?s5f|EdMuK!eGkdvN~vb{~2rl&OE~ULFbF z>g2I*?ZzYZgl%`xB^kn{+NXC3u#JaecQyP#dps&U)1mO8_bKd764ZKnpks{7!r@*< zMr~wae5ph=7C{H%4f7pt=2r(EY4RYI`l{b_pVaEaJSO@e$x(t_oXqXp+9oD_0L9U~*PWgq9J-oU&RzebM7 zV^WM2=xd+Q!77eM;)Uv^G{yqF{v14AA66hfj&2ctiCgi7Bl%WJ0JBB!-oM^QZt zE!P`zJq;puG_oA=T9S;*w`)?8+`1otBAo}7UY=F9w7}!={=M1^vHpxCaoYlE^h#mVs}-|F)91; zLi1BSb6;0eabKt&i%X3juOJabsGhhzFd5cb`A9IvFe^1&(C9hVNd!7`5qVqwQ!8W+ zW7!!EVH4LT`x!ARDs_1@NHvz4JwYQCoeu0y0yD1Ih>169_)Wbs9E^F8IH+ll%Wzok zY>4QnFKTl%*_KTYR-K|~`Af8_c3(qYKxU|5jt!F;g3KX0;mHHVtUwT>LhljLr9#_R zqug!zfTALx;7#-5hWi)u0=2;$Z+sOUa1^xma2HGO%Fh9>UY%|QPnbnx7Zyu}Q8$n$ z<%eJc`4_RF3!w5#pQrO5`aBz<(L}jDa?h$znPgz9(08agv_e%V1{Y;rEjXxcPcF4w zex9w2Gm{;^AOMu~CEMB^shDBrvhQ9Y;cvs!K0)qHJYOFmi*1dfBQ1j68hl$e*$>z3 zD`35uoH#cna-Q2vKbrypd^qaoFu!$QoKCX`a4v5e!k5e;eB$i3ubLoq`iWNU$L4Ul zKj>M{KCRsB`xDOIdTRPe z+Oj+LN``jlmi>BKo=fx?bz82^9$|M5e7JYS!ME$u9RHc^9n9?__x9$-JoUD_mA?fAzf- z7ao!Uw!`RQXr~LHCJ>`4b=^p6IehTTf-4gZzLyzAS?0_^EGW?JM&qk_pI0b!#v6(m zBQCfq?LR2Ly0P0j05n}u$5v{UEs}Q7ich~)8U=8(D!n8Dfn{HedqzCpV5k)Km`udg zQpuhnU#_P&j$L+cWWO5EK5?MGQjD5~&CCo6r ztPe3J9JM%r2I?_sjY3_08Gz0wLqN50-Oj+`z2iLk2tcV>-%h<}qw0%luY&}gIV9k; zm#4_0-rQdwJp@EHl}^}`phcjP&6}@fZULXNtLUGrtFV{`=j}%|#toJiNxr@leigNg z0W$$#$|wQu$@EL-H0(Q!hOk20Hcy?nMbNSLlNbehUA>^2yfq`+WVuTbt5HD7&u&Yv zsa6?QJKHh!F(MG&9cAY5j}IvU+R?l-s~pIT1s0@VTYo39uXq3|AU^pE?uf?(#jJ7L zbeZ9ybtgR4zdM(96239QLRSE&alslbkL*q!5k5}G{du$b)uDQ`I%xy(U?Z;24-^o| zKUJ?eZE;s-^IV$935~yO0;O1DOwub;3#>tpdNsGy@)}}5P65Av!c9e19K)E5`zIa% zi%bdF83r=%M1u--Z3{iy?R)iv=jzt=#mB{l*;KaXpieAexN8QQI3VK=lA`rAu+Bv| z%^nnjj}n45865LHne5P2e{s&3_9wx|3^9>Q#>UVxI2KB>upG9m*MA5Fsqh$jkk&wm z=OC^Hsb}24%7JQo6$34l6!s_eX-?;$h8S z))%US&)An@U2wa6jZl(e2L$E?n2VB(i{lp9xd72D78dk~i7$F)KQe9I0`#by-CkNR zl`1qtgH__aa-3H(3I+0Ix>suttj)*F5Uu;I3cd2ZckkRcH1LwM*Yn_$8mLK*C|y(^ z`P;_^WxXUl0J`-a`|kT*$IcRtvdtlVcv4g&TlO>=-ZT-5h>MO3{d_rl7l_POuxYZ9 z0t&SQ=55UKODmxKjh^Bi_{8;5NnhO#&Mp)($|x170?Yq0qV-o6VJ4R#Em#>gML^3> zj;2YSZMwz?9$;o&^iK}*NX-ETY)Ul&s{|^xIsW8>S9&>s{1Xt(2;lfLD@OlTlDR>1 zeDr2A3p80=M@8t+m_Tx9KFmS$C-*2LGZ>1A5bjTCzGz9xT2p+4tT5RGc_2f7ZmwEA zg|L7g4LN7H{2o+q0L953j%JHIHfRzVClpAp2(tl-gFl;Wj4SC2rx%D`X5q0el{wo%_*|5)D z=r`!+G6;cfl9gSs1Fy^uo-sK*q&<3MEDG`|iK4Q_q7Vyz$+9+iuCUcaY?J~(n%7Bt ztH>~;=MZ6eK1^PD-n=;&N(!maF{C?x*`?9A^ay^8{+HWwggBa=Q~KD9CP<8-B4n)+ zpTg{DfzJmYoKI)K%hazG>PR(e`#;YcHPRBGRHB4p9or6X-|<67DtdN~@dYa+X!ZjU zRckW*l_8SnlHTa7)bC3a>Qe5jF%*ifK)N9T+L6w3be zs5Ei&JX<?i}eEq@i+5XG?(}$?k?eRD5!0f1Lr_tw7@L5)qJSt(Q@#%`kwXun^P_xP!U%KE& zZES^ZI%%Zw;fjxCVJg+sbe;cpbrma(d*`0@u|HPN5`$#(v>!G1qAdc?smCZd2o%xa1hSBLaWR6qYDh(S}eAvHV4wI7=M7|hEYJw~v zhh}l3Ot=5OlAKD+co7Xq7&o(LLk-ru#a!nQl|?ap@Y-1XoZNKL&G6W3CA1pWCPy9j zEf}WiF&v5Q$iHn(#R?JL8h+R82_)ZztMK5Jt|$uR!VBoUEYifi$h&51zv?+uD)-bS zK)LU|-iO_-r8sFRd(C!cSiwk4M7n>D95TRDHcl?mY}&C_@=&xe?k)GRPn<(c8pyO4 zkoF|laYk>Rp?MjpEMozR#`9MI7Lt~g$q|J3L)Rq%!rFwommt;~oItIm98jEA1~w(_ z-e(u~Fqv?aV$l>{(ef^g3I0}6hrl)PXuFOgoY9RJH#Ecs@Mtgf(9*!Y%Hj@d3h_^J zg+m^Pb z1WOYI%J$-^5*lY7Ytw#M8TD~|kST8@WGd`=}Gfd13rD4<*AZLHq+LTa=C1VTRK#aS<>|-n(H+vKI zrbgpEPv2!uZQXEo>N>MM1q>ZO_RPwav1jBp{^ETX$;k}mZmDFbYv!?t9Ix-a)`wrQ zg8Y2!^Jl%#|8@q|hR)qbGVy#Q(4?e?hRxz~Z^Q*TmG1dl=B4yNBL5iySkI#Kn+i+~ zOeX9|HtqzAP7?aCxWDrx85g;-_R^)+jh0eM`GEX@93`Ko)=TZ?7Gf)4FX4Q~9f;lN zxqVa|#tn)!;9c*796F)<@1Hz`QZhI*B@I-FAVIn|NnrA&gml?YcDT<(^e2?-h+sgq1<0fm*hn0GbR!a|AYi3l!)e|p%oe$|n(+iT3O zo_gqX<6r4&pTvrh>?YI#t@|v1eQa*cA0ZJner&+V4=M5mzBCiT0r87UN+lCX#E~(vkf_aL4&zZ}29Kl{ZmYT;i6abPhP>}Dv zSWGxHE2E7fY`$)8X2vWrfcnrs%zUS$>6K{5H&^uYs}%zdiZxt6>(*>Jg|6!bU)Pg% zM0O#3;ySscOB%J~bYs#uQzf9Inv?A1OL09!ECS}(K2>o2)D=%XBAs8Res#SaBN#p4 zs%%Wo?tsAunKUaCry;<8%U8NftOhyLO6+ef)Rk?CPJ5iQMc;#-py`;mX@p@MC_BjBjP%Yc!TRy^sin{zfHF21UoC5jfPdNE z{x5ih5PJGNG=ZLz|DiR!v%kbjr2CjVv7Ux(Ep8gpgr7ko-j<^migSY2bZ$LN_Urhk3D&w1&kr2cCBgZNSmequZw`_sN6(vJ+t_}mb`Tw- z)Tbz%cb<{?szMSDA4r=#Hf2Oz;QS=`nuY;^LlWdUg<|gfHfE&Ttzq@jH?pp*IKBC; zYD*ioDMao*X}DaAUtfE~OSzq-IX~w9)J9FIl3P`xWNjKGV zg)eV6)WA47G-UV^eY`OgK4+?|v-42jSQ5hxD!(Nwkxv(o)gK#p66qZQLeKJ*s+xGJ zQNnPY(yNLLgS?Kf7y;Z&;$@E%SF7Z#y3s+Rc%@&4svj&gH4Fi2%V$})%$hW06SDk9 z#lG^aAz;k128C>nNwGm+xWZ0TVO(Mgsp?gUqKH9#{2>B=fO1zi-F&DBJ;8L4PV-b= zU~d$z6x?Y~(Q=HoQ*fdR841vFoULx(Bn;IMtD^qY8-0bCmmwx&Ds3~J&K;lx*XT*( z)OlTUhWZ|~!_G0(m8ryJg-f}$b@7*i=fEixONASf%(q*YN+S4T>HOg>>fmZDy$2~q znX&ZMe#`|A_iwBXJwife)6C;ku|# z&Ao)PFOy3oK{#NzEtzTKP0~>rBjUUk^GnbA&4MYQ?Q%jVc+VK=p%y6 zUHX}R17h?M<1E`eQ>t@ILb%47)XiezBr*^aS`v#8ygtzmp2J@D8cSwI9jRe4HVGr> zwlzDvE(VQhPTx(8aY*Q)iY z*mDC`vfBeoE^+J_*M}B&J_W4a2Si$M?a!$a%BE32!QFRMU9_aS{T{J9SmPKjPN6d_6 z08F)*eVc~{zYU2>5^Po(o;UsEMc;+0UE-j=RWnBr$_nkWL)mtJh;7wOK?JXmbeFfWbdhD0wj|+{TRfn@u00hWmSbNv&E>A%tI~O!~*aA zt9b9J&>evna3Nk_vD1!i^aT+3ArG`GU+7@c@e*YR8{&zA&$ecOIS$e%T<**V3Qmb@ zETtr#-7!b>H5fJ4))$Bv68Z<%5Prn9mQ%^Y++ObE$%E4Vb6x+abMrNqIxaVWQwXNI z=uD&0aGt6ckUhQ@NmHkjjUfzIcdud94crB@^>W4lM3W6zwYv1@FxEesE=%o&=*(Sd ze9Q6A-X6@`@Q+S!p3CHDw%c)AyLw68`Dtq1g=xOhzte+*ZRs%15@8(X2g^3OLr3AQ z%}F8Wf>IslI7koUh8K!5$5i0Yo5JDKX0bWN(_RmxQwb8r_sBa0l0k`5->%nSGo6QN z;q;*IWOJyk#6nAB{O4VIk3|{u9$+;}JXq2(!>f;~tm@{AlHY%0KR&E$urpk^qsZkG zhzkNF0axX=6-6Ve%F%GM;Y^{AV)g?s4|zXK&?iURs!O8oSyT4xY1loIWB*(=quCUGz1b}) zd0u|wEMBEGd^`O3XjCal+p`A%(c!qaTo}sH6YecA`}UBsE(tZrqfgnf&#)1IC)?g8 zfY+SnWQEZz?(`XfWt9O;Q)f2p$~me(6!NR$i(^=rcFo(5_~s8!S5hI+3z!O~&ZlLA zyA4UulVz7ma~ymS7xX9}OQUx*>VCrh`Lg)?4ds^^r!9&?Og77mW6U0sC)AlH$J3>! z|9KCB-0>Z+Ji2}SyR7vy4eoGXGma4aAAzP2J`{q~HIAMb|YlGB*AXooSt*jO_pBSN_kyzr@}zIo}$!fLbBG`JKN% zR|Of5M0WmqntUm z!8-QB*7eDOKsP0aLnIm%9Xlv~U4@Wq;aD}ENTDfNRnnuufuo7f@ojn3-7?&MoZNMb zJ|U2Vi$3LedgkEv{o?L$N`OoO+fBuYXuJQk=QC@4kN5=k) zc4f7-eD?(O`2Y!9`erclSq7R*a|3XI{Sd(he{K-o&wtRFWeyzKw?#1@IyZEX4#sJ7 z4Hz>H>Sxyhtd8+)wln(s z*!v9EG;mFWZ15H*jdn|F`@lLjVVGe$K9^%_R+-|oTHQdcQIv&p&w|}{&m#N*B&C3X z$B?7ND-~ znVwo8M$MkqjG^bM6o4P8>(5mB%ofT{iCaQ6s|Fhv?TAX!P1q8Js9=9f(JjPTdYBG#xxg=cArJ&f&boPC?e7;eoF#u2# zu)W+(y0|o4MKE0mJzA~WRI4mRturK4$&~e2Q$N^fIZUCOf0^GeNn=x0_xP)q)49dH z1#J#`QJmk9K-pCKWRT!STK*YXC|hN7Y#l~mgY>C?9C`msV6e@^HF{=M(attU)1q6C zEJ9(#)hmSc6Xod_k5lDK23@W#d!M>_RE)DVZw#VE;^jFgz3&0}E*%ZbP{iufnP9>yf_~R>rj0#U_luKqMeyuPjsS*NFH2R+T0Hbn2Q= z8OfhtJ3|03+{sCCQYVSL5IZGOhD^CT*qmUwVxp>iJNf~|=5$9Ep31F#;=U$g*UP@v z$|A!AEAp3@V>}&fy>H~!gkrJM@8C0xy6xL>-4Mzl3L_2{Oh3+)f0o zg6XwYc2O?&&R%6)c^=$_?<;QDAT~1nG#1D~u>H%;E z9+_2B)f|7|6lFvMqDG)~(Z^ZnfGtzfq|`Au^OHj?&w=9oxu-@}{q*j}tY=i$WCMyn zJ0XKH-q4W;3HOS}Swdv~d`QBRKeW z;~!6NJAygSlGb$QBk}he;PiRvVZmUep+Q0BI=@S7VgD}4o(4f_Wkg4(u*7H5+1y&3MB)xeK@cMdSyU&MW>~ zV`LB_F+??#D_Dy16Iq^)COL7=I$e^jQqb-*PV_e56+9x8OsBkjpNGo5Ts!`k&jY7% zSAYp#61b(GT%xHeE}pfH77@URTV(QLc?5(OY!%88A{bc09*oxAF;rZbHe?OT2_l$C zu#U)-Yer&^*2~dKhHX=UQ*v#}kkLNWQHG4p=%kJwYz+wj0CjImCBN|QO%&3gi#(Y3 zPTP6L9sada#G3p*8JP39Gie{Ddl(Hl@UBc6YI3qed@&8Uq0WVQVf8PBQEeRrbU7l+tDznkBX$DaPv-JL2O6Q5OeLru@9PCWRhAW5n)>&r7sJXg?|)z_U6Itod1D|){DBX;vsB)2KxGnvEjZ#a?z=thXpNIy?^r}anD{ha9r!5 zFm60CffaUJ|wa%07Xn3_sC9)uan9hb%s~7>q zS0TeH_*z(8A0GL)h+uT!g@?WMDe764$&_ZjU+}y-oUj5UtTx3Lv7(KR{egMYQ9qGo z-77@mX=GG|^9J4=ulGrmh&zwDvzXS)-l|q=4XRW*?A+N&6|ORmXxi!`LA8c3p$(c+ zOcv&J>JiF`027ozXbBol?XqXDradGN33~n(S2ercI6MERf(f1%6t%hkkhDThjYa1k zWv>pe5mcx&0l&rm*OW#K`Sz513s;x7g6xxk9|x1w$BncpJ67 zUj;Tp7CoBl`OWe)SS9LP3p*DQ;${)pPMdoNXebEQhAN_y;$|L^HX+1eAx|1!<9rg( zW1UJ85NNtXUKfkzQ|;==EVc!quKb3+JJ zo|8@1FJd)cJC8laGHAlFpd(eFGcQdiX$P?0I<4~MZO!^KzS3A4`n7<%fl`&{8eUAP zlw9nm=jWYdiuORfYpN@o5+LR3O>d(>L^u(vyJNh87-}P~NBIO)32g*2KLZVj`Ci|z zK;iB8FpsDFbu2(r(kw`;LGtQXokz)|XiPNTQW<}QK|yxcsjIcx+mRq|?hf7J)cAI& zeMNhhjWB3>T+?P04V;U~aD_;Hghhac%4MQxf8ttho`$Hd22aaMpKeN9OeW@bNJW75 zvO>8s3xAZ!ONUCp_NhDLKe3TVk5n2W6+b|NcF!MOX$Z?D~gnrIACj8>L zeLutB-0s8XymGT1lLZH|^96)ag5GOq zH))1rE#+p8>JT$%ok)!xo6t}gj4TxM7f z0R|vnW#T0DqnBL{9&2PBV0a*OjKE~4$U+&Lpe@P_PGG2gGEwG7;+7wzN|uF`r%7lI z)+&jn{Mt?~)K2b^Z&}5Aed@|Kf}C{vBFjA`(XETARrOH%F^tV>XijuImC_VgE);Rx zGaNxyg17Xq3QGANtopX(qL!N-&eGy_1%s~AQRdq8Xm04Kw(fdhb?R%zfk9q5_CM=*WAGY#kYVS_RR<5SPmLU zoZv_+tD?7_pkFQG=QjCm8&*8HiJ{M{TI96HN1XGT$aWf3jJPs^JPxoaZAR+XzrEMA0 zeDUspFh?X}^uLLNlCU~T7-vEi4FAbbQIiw{%?aj?1W7%qoIJnue!-)ND~Hou+c;v4 z+aY+7LaZg3Lqwgvlp0|Vy)PHj!}wY3X`Xv#X`SFxf=S}yC^Ci3<&EHV`6WnP-HdbsKwxQ;qf}C{G z>!zv@qOpvfq@htfWC6N%=QyMtGtYe|O8;!jZjgTz$A+mCFjK%8N7x(6q%{oMQ#{%b zHAn7<g9T;y{`2W2Noo966?duGW%}s8OxhP}11)E{^d1xBpUeFK|a`r#2uJpyL{CtzVSRJhRvW8JtOkf9nv;(6d+>~ zHd;w?b3lxcTsEupxJ%amJP|pdu{ZT>rme*G*Z6ONnfC&Kp5lPSSJ^4Vt;Pgiu%HSn zzc|DYccBQB#Y6&EgsOKG;hV@qkUv_)#|9EO`tzKqYTm1Xjz2Ygv0qn?g>HP{NXTfj zrAn<>!lS1`huN(v{ZyD6-}7iK>{@!2Rz7p0){BjWJnJI(w2+)@{>%{j3pRi0C5Xv% zsAvab-V00dP~Jh=$xTHIS&%p>yn1>!UA&WwRI4z&5!uaph9nAzb6g4S{g)P=XHBSw zd2@5UR#VOITS_pUFXpODmra+5H)%Ei5UH&q9uq}M9 zlJSMOtE)$WrA_@k(*?F|!_Mrb+do&MH|cjmjlmh<^5E#`@X>qy;~bKfH)E{+?mh(b znlGiN-s8t}2WRjNXsnJ0!lcKICB?w!ul8kE>37EJQ&$kUJwWd) zcq3Uo+kMjIp-~dU@R6g_*e~)nN+IT;F*b^5E-R*R2?NmQzmaU+>6%kDgVDvX69>p2 zOB8+gWHgq5z)uE4Pl%~UBrbGKrb#I<;nQ~U=FXTCY%CO=!aKTONB3-{Whx1H;Xetg zhyy5Mi;j8>YUD6OF?>=}K#(WHd>Q%{LTAGLeoLc6IIKg7w&9YP4ht#g30N3j>ivrO zI&vgWo&0LQ_5k)rknoRf*)wHCPm*o$ji`4k&c~Q{r1ffzi`7Wt{ekOBdS2ZE zfC@$kM{UK<6b6z)X6E)=pjbV3PutbHeiS|(N>a*~gL%}PqwNS*h89f`CMOc{T(F0I zsIVh23Bj#);Fmzmv4@3#ed4=j5H};_j))&Fo5WT#In#P28X^yGx#9i@h7r$nxowg& zjZT-p4GK3r(@i$3+BSz-Fa0_l+SE4@^rdzaPK$(B@`NhkSC~WIZWF`u3jA_P-Fq*# z@06c^;IJG^))rgx2j4Y4kU2~NxsnZeWRv*G6e0P{JALXTU;mn1b#(P8RY!X2>?8gG z_qB*RtY#6SAr)L7Vl{kWNXXvhDZ!9&*sHid-=I6W`O8e-`hg^Vv4f353t_YDc`c@; zN=dHw{O#HxoNEMq#X1_BobNhWjeu5%P?RrbnN5vmE(2FSoT~hRygCa|Q0dy)qI8v6 zBbt!)b?x?^j(?(Om3h)(vS$4*;?I5af_UU`?OWL``-i}P(aRN)YSTI6wbkmCU0;j} zSF+jVv;j{_MeT1fS}YE04SS^{$QuSWs=>t^gDNf$1@in2jIoNR%aHY(8$R!nKIuZ! z-FyMIs*}FtFfuC>Zy1+f@ok7YNpEzm=w$>?=dOl7e^%v#Ke-cT>z)t~Q`|e8My$I@ zo*`bFu<4CYRn7?TGq$9DE5F7r0EJhI_$Ox_RRfUS%WTopcCO)xoYYaEvN|byT{DxG z(u-U(8} z>1)n(dlS}7_1_CM2t86LgABWug4l*Cq@gJ_L)iGHxjrc_oDPzzb1t_m2<6MvW7Cb9 z?D&bDi(pJX`g2IgkQo2UaMoLOTNAkig+}`(sp!aYTJ3SuJN^5!Y1K(uL{v2`YB{T zxR2xsL}CjiZ~huJ!<1pjk%$)a**6w#U09=KgO#c$%sj=EYKK)a>*6A=jK1mX5#`APqIfZP>P_Vz==UC#Im!>87kus?iH z5X^=7#;Z5UgRr)j!$0MlsW5kvkoElIGU3WA%SV!3#ckG;?DxA+)ILLc+C;AFQjTA1H4ho5a_{(txRUh|&>(SFzv8IqjHHbK|rC*7zdV`1NoZ4?X(G*N9 zkHDY-f;&j^i;n*zZ2XS^4GSQI>*`2j2bcijflg)eiq$cBBx z!m)WfL)sex&sXjX*n%5_+c%?uTjdBTkwdOcbn~(!o=txGONmKL*_t&cAWnzvZV@+; zz2R0Wx^l~xR?!zRR3tYZCCd?v}n$ZHraB-gePnWf`d2=l6+uZ;>uHB>~;g5FpA9^g&&ZPWFE zu)m{1|1=P)3I3ITBSZg1{dHY)XEzsDH@kng`rjt~e;?2MpRs?VLtlH}dUb&w;edcd zq`&oo0{SWb-Anv85vl)oZlJouV<{xy7`VwPo#E`2uk@t+N1e3m)Op9m;v+h%*0ieu z?mMy;(^wsyEH~q4w6$i|RN~|qfJOJkq=|G4sS3^wg_IbYn)6)}*ZFT^30Fi`OHlC` zNVOB8XL9>vY*x@6eGo10$Vq+z$nl2;&uSjKvzm32sH@kmt=ii2*E7Ej+@E$1424T4 zdk=m~JtE9y$*2#3Wb0+h2*V>wOS# zHmPs^)CLQu_~MU0{Wx&IhWY!+cLOdtunb!>qR4DN-D^vX?wq7&|#EVh}k{Jj8YId6EV3y>L5FE2d}ik^yw~`IBAR^3Y^p zxo#8Efr(5?sI|P9W(cgx^|vm5u%EIYg@p~q9d8?ZCCpmKQ_OxE_`Sz&?;4B|dI>+H zX|&9LI8;szA5|ai+`me7!}!UuspAnX6@IZEeA;twr_mT5_GJ;3^5EnW>{Gu0XId3x zwZFW9b--{LiZ5i|?BQW9P5(hEzP)zx3Z(WDQh4Vf^(Zn_S-(tA-K?EwJnFRCs=k38 zpXpH;25>qU-eSt|hApYRt24{+(mgwxk!_AHVY#$U@OAzj)FOZQL=F5 zQ*7_b`yJ8c@>jijNyt}>)7zoWunCYoVaqCayrl1gcGSs0nF*L>7OV5c7z%7ra!lqD zf)5)SrG`ag27pHR2kDMi6+k{^630Pt)Wh00K35sdEQ?=M*=*b*TTWtlL36&Ghmk0A z7A2`Ol~&gnJIG#W+dkV&6~Ug@Bm2-U&4Sy)#~wfocmNHz3&WyY2UyZ8mce53FVE@| zIc+|;J8)0Nj-Jj6gEa{0oBp}w`J<__IpmmzlL=R1@h7SFK)%Ya^ozwF^o7YUk%`0t ze%>^t5d+~&XMa29m<2Bz2m$GL2sdsd4*x;CHJS=^)l>w^HYu8{N7@s-J2;VprauUE^*JD8DaQD?aky?h zBQ!~sn*sx>7~tbVJ4@$xCVw&6HP9|{%v<4wEjyO4Vg#DpG~=2}YTM*o|E8vYQ0HW; z*@i$Dg*vK*jG}DI6LqA&<=A0E;XH!!n+&m{k!9W*fjOzEErs}2TZS9g8SiD(Eu;)8 zk)beM8d`&$NvDN*{R3)DCQ>WptWAP)k#D@q)huU|Jcr{jc-Krx7!=)`d>*V^DAg?s zBZOZPRx!&HdjjK?i>mI`E^OXWAW}alE>RbusZGVHQvA5Y)i_yjNufbRpT}~xkCg?x z=x1oZE?a$++p1m0kJh==()>viB_kikgVW3CEKKKlcG&pyJg-AlM$|RQebN#y6{0AM1LlbErlaiA0%+Ygdv&oYL|@j znP#k28}HJx3;>}rASn||{P?!1S#Wi72pb2Z4LgNV?@r+h-=mUQbd~q)d?>g`sVv5& z3h-Jz*&_a_@Hz6G)5}k`lIOarz9EBo+fo-T@*;G`)!o%CMJ}5bqox1LCgXXw2$NT% z-7!3g4H%h;0|Q5JxsNrk(rv2^vxA!btPCL;d%i7iBCjFVYmkPfTXZXFSggc)D1b2d z9sz6Yx1gQ-<VZ1|_J^#7wy}nS1&qCDoFI}RZb;yOU zFU~%H2J#XJ7{XxN4M+!qUPRocJxdNooNoyyx!J^$eOl0i>FxSvLY_U?1-Sn08c;nr zA0q*`k>Q%`@HT}L9Od!KUILM))WCM-Rj9``Rr&=Jz5R4AcSISSA1hq|5qEao9*H(~ zAfTg1^XKz;JmSoXpn6=LLx*|TN1)yz=wX9uL>Xwv|BJ!-@sB+v#wwX9T%i<7OJ3qic!v{>iCZdRz4J0CK2O)+#spc;f=JAQpGY`A4}mapq&`#3P<7A)+vQ`(`BjO*rXX$eXziz#2e#5)&QM za<=Lh0-0$z_w=jQL?xZEWpy>deiO5}FP%)vYi?C<=|itQjXC6Wnfqns6;?syo9`1Y zr^}<4cA(u_J?>B&%%qVP3(O`$Sw?#C>{(Bzq#=y8UD`qCus*4Vry%qQ{BMoz0DDXX z`$PQQyu|(r8(G)S$;=r-p_ak0Y2=qq9Q-OiIy2ODP$7^ALnWGt4O{p-cR#!o&}vyY zO*9#h7{)IPpzR$PfS?0~mxUQ7q0%}iI`~ldC%Jv?$it)_{f*v$UHq*_0zeU;Ph^ON zKF&Vd-q#?kGHzk`;Ui)q%fNdJ-ycze00T0O>9V5Sa{*xghWuR3w7SdF!CCV@fa2U3 zTmYvnjQsG&2hru3piFfnKG(&G>WRHFdS^NwoeN!_6aGw0D*r--0CptXCATBZiOg8sMnbe+304@J$Kl4VK&}&Ixr+|eo zW4}*{jVm^8x#@c0rqk3DcuTgU!Rsbt%oZG5~!M z7YE@=6S?p5b0_JCjvuUJGd?wW1&qZ4VefCDE_ASo!TDjH;W&8$;9gR_$!*syO5X*{ ze5|Cw3;5HiztH0jBb!sd-j)V&}1$nx);XV{UxxnZG6Wuh($1PTUwjGt0BauRy55Jc}}NljUDmv zZWc+RxA9R~aW&!7vWH;X-F(7f#_W8;;d=9KgTaasV=Bz88Y0v*-R;^L54qA3@hvf+;K);qfN#@=Z>ew7wOwV@X_1&|SO7*>xZ ze|w=~3RZ4wyk)-HG!5$6h*o$E<%3{W`my@ecuJEB`RiQHtiTcsGB_IT1X-iuPnL~*B6whFMa3xB^rbz*WEa{K|c2?T9Y)lz{~@N5N>ZfGs4T;xMtLZ3?@nc$#G zjKZH3kjTxb3GOJb2ZPcF4?a6nXgxH7UGXa`w2b>4Z66SFDPtO~8j$0Dh$1261rOXI z|5%9DupBB?TYB1Nbc!;M;kp=SU{&bu(J%gB8Sb8Fd^h+Q&D-Q*ACuP=wKHd9+0yat z4VRhm;+e7$D%m%4mEWeZxXKqb`)m#xV)v7XM3=r-q{kl295Njbaf_nA?1ZaJS00Y? z(n{4fyEN!U%XWuN9&Tg1B|b}5-%WhIf80R8#pzvwWMCv}!pykgLizg4bvA2Z!~|xY zm!Z5hVF%?omau%zk_UWyR6763u=`JzUEHda@xPwjf0JeRFTvkGNBsXBxWkZG*_+t9 znVJz2|7YM|A8zgQTcZ|G*7r%#1NQV5m}DNYF*6$IeWkU)~`09V?;~3c^%uWVw(w zH1fDxS@RZCR$Gmo?Hw#(A%>8M1Fx-Z_fTh1nStL231bvQ5eoDiyb+1YN zYTmp>W3_ZI{LU~gZmtB{{|#k%7%EblI!$VcE5;WZ2S^r@Y)2g08RVkEiqA8b)1a<7 zP$R?EriI0h@t8rVwsY>weL!OA0%vj%q12LX!XQFel@tkj3pwm`NvUoWt z0d~P3%a1=^LUks!6S^n==?90jD(|qV2E#TQMJ5QS%0XxQVmM|`}qc{JHebT#5+@Pk({Uk3>W+jIgm?85`_N;I}$LuyCev+Wh(%hx0C@9F^_j2+!zkl+a z+bvQxNiXvJa&DH`Fq4jywEZS4)a|@0|546HpSp9N)MCw&PZK%s7l>OO@?In(&sir# zCz)1V5R+;31_U#OYvh-5x90w&MD=(0{0!YQ6%Y0TrT3S$c-)yCUA5V{=2EA@>FZhZ zEiLSphclFQKfN9h)_1Vzp9Yd^V7UG- z%y9k}$HU(RMVzfX|F7|){~7uhYWCv)18(-{KtQIY-&(nQ$|L`Un*Rpe{?F9^MvDlM zCa2(0hm8PF-&IVw)~FjYJQu=iA!GI&*c(fx6V>c5+~l*V?f1uB)5ThRRHSan_6W~s zIv;52oSJt=BS=vqC`%^Bc)abqBomB>H>0l6d3ByszC!W@!3-W&JHbGN=*%o~r|#P) zN3Z*~S<*85o1eZ6YG1xqZr=ocQ`|p(3OIeu3|_sz@Sj0|OTwb=V0AUT_J|OaIQ_LV zu-A>XNFtbj+w43yyr`|a;m&l6dah$i=;8`&n82K%aCE_^@bLXKNzlv~Tq#K9kIkBx zd+^1vI``u6=8qG|P@GvGUb(EFPnakHVJzkMiJLf@yU3|IUx-aJA7#h&*y3npGP%%a z3(lXo>?n}Fa{m&b3JJ1gNs^|!Z_TQCOW%6BK8&sD(WPfQ<7*gF!>fe67Sq`6Z6~JK z-rq1XFa`={FqC|-tY(%{Bv&>gWCubr3#J8H3E)dPuLUb&{rlTf?}ll_h2ocf5V#~X zkUEfjU#+2L1adr5pr$8XlrjhswV)(O9f;tM02H7qpm=aYFi+}hl75Xqe3&k%Yqg!D z-OP7ks(T{%s>lZRCw*C&^6pw+HT}6l`lyLzm6J-131b^Nc41}+Ema*s{Y)cV;8ZpA z&0i;N$dBJY=}T32z10l~P5x3`?bgVTb|F$_XHdUqiZ!JU9X@>!!8{Z$^9tzJxgG}k zmTdKFpiFT8sKG}qPF2t-mwLmkXEnRE7EF-f$=|1Ex$3L~7R)2&;sjKh?SeawIyNv* zH$vs;J~>J~ZTR~>Wqtv^FO3lJXd@F`o! z6J(-PPRrm9mYX5Tgl&I8gJOXWsHYIpR{_TYU8fXFL{(gyc=D3z7aUp~Lfi#+?mWD1 zIzzm$h@|}3a5Bd&LR$)Pj$j?gg_dO!7a|}s-ZbbG(dcODNJ?7q2 z3!_MIrejqInKJSn$%uO-qT{~A>flSh=B;z;Q1sCJZO=ze@5JCLKXkiiVO53@jZ2T zKz&*fP<>jdA)R8FF}JgH6Y?G&vby#Un1fX_^RHdE}&i~I1 zljDOow+F@%zmz0)Q&O4lhS6Vt`QS~kw}xD!$-Nc?gHOMDEpmHC-gsYISw=cCn#v*+W~ zbDPh0X=cs*N6QxKkCy9q^ZagIV5YR&Ak65Fz*w@3E1X!1oOZP`w)AZvUObK8l;Uy>Qln6Y+lE4OWZYgKfRn zOvFU+xOo&jgCOXw?M7bbT<a6 z5cQ$*;erS9g%krV1Bt|n=b7#S?!`GbY~DjlrJm6vQG%56p+c=5!bf`=3Ct7Ld&74 z>>9N-He@gK-+>~#*)d>MUuxt1Ut^!W5e(M+YJhJDSlslcjg>h;G5a99t~G! z4`9asyr5v2=J#WNKiaRL?@Ma5AFv$XuUHm@xxtDt{(rv#R;bOABYNZ}tw;F}??21Z z|Gf_1OI7#14x-_xq$;1OH-;`5&eShBHwo&A$jwFSrkJY4ZS^Th8dS?z29vm}x2`$< zk+ucrTUCWv^`g;Y%qGttpZoiP+WZn#IWvm96D(Vc`_C+#R<tPfZs; zLQd79k0O!RtH_tD$os0yE7B~V%M)y%qt38G&9U{Fb+|9JBQLeq631AyrGp!}m{q~a z+R2*&hU!a3fu|xhyq8Y!b?C&Hkw)*dY|fy~ZLkavIpQLc-RqL^>yt)Y=dKgQmQlEu znHdJ2!bb1(O|EL0Zl^|D#@A`tdbqI1?w1~U>4bVi{fwRynN4V89wd;`7JWn^g!pQa z+e$QR4wqYFJ}N9;PYFO{mpzZ;2<0eK)QvXA@)18MJoFa#kP1Sd2Ls z>gPnSTG;0qw1VlYFA`jzhN>F%#zziYMobC1e+Lk@VX^mroR=(zVAO<^Ia+)&>5Y#o z5XaOExq*`=57d`sQ-?3A(vgq|qSRejY0)K{JFMJN6opKsezxOrn z?ly)8oP5N?u{gGm_KBt0J*}I^7W2+l1qz(-dWw~N{h=|UW^}1o3xU7Yvhc?fQrbZC znUf~$!Tc@;qNXRyR*#p7Vi-np1E z?#ltQiO)L^4F?LI9)CjZRm^F-nIn1U(x1`QMob z&No_?ZIPEZnv@zfQJDv(r*ZnYyui>hL?NTNH&3256AWn8H>T>v!KV>6IBf%o;lr#c41yq#&1xB?LgLn!&#Hfp)dx&V5jrXDP?Rm zJL2{c6_LC==b$Xj?uz=O=%7N7^3G?MA&k2}w!Gs0BJRC6?ydCHX>br1*cYwmFZuF( zeR^eSX(z(mV)e^kQ zcV$j2E^YK<^iuM{cX<5a&J7vT0FrBp^2qgwk%A`|;s%e1gpo-Khk$j_Hwb9b;AheC zX57Ai!8na_g0M@(#|Tv52IL+(`6~;%tLeg6UHERFGv2x75A3VXdAkL`(JgIbL}QYD z7HIYIK>~z~$U`v#t=KcT%IlIVD|I!Vom+6Sz{BhIZzG(!^H6z@o-f2$w0LU4)$g4n z+ZexnJP)YnV&E#yND;yiFc|Q+PzPz~fZd)Jsw5js#sMBemX@x9*yh%w&AHRY&jQyHhhWU`?2=P6gsQ8 z;9|}O!D9@vf7bQ!Xb{=S96y}7KG;r&w#5-wGsD9aiqIz0-2RM<+uy-I1qi8MXeBbS zb8I36s<(xrzH|E*)w!q90bk}T;i?NqFH-ia8wGXeq39wu(k1}J(4BYgk+`Y^9kTVo z$W>ns$iK@x5`Qp#dw^%d7SS;lmjz{eB78yf5zA;9RI6^)fE3j(S6Q6es%nJ6gX)}CzL-1hCqUz|?RDfr7qKo(V<+BH!n({2f5UJvJAYku>= zMp89tn?11Z;#Z`zl9U^!!EO!8S)mHyc3`n4D8Zh%;ZJGU0*qX7 z8rftXp3bDkR_Ux#v>eQ26iv=_UsjRH`@$_Xxo}+8e}I-1bi`3a4HQas?}w>mb`ywU z%gKRc=I`sK$Ug)l<@2nEcVXYLo$>ygNt5P z2G1GnD@p-0y)te@L8s62FO|l)IN1fWK8TBBrgCZoFZ)pWbcRTm;KZ%#N65gkLh)r9 zvv3gfe&~9OGr8#Dcs5P$#uR_*`Z*A#Pd)jKDyL;j=(3+SVZiT)88yW~;7YZ7bC`=6 z<&T9{rwRRe%^+KH85K)|-|O#3kH*uuZ3gPOyh|wtHwtp>vTXf1#Z73qLneLvmnAHP^ZMOOoan;g z4Flv`MSj*S;SWM~Jzr}r6l~=1k@IZWTw@fIq_-S}JF7R6!8fn>5omxl=z8e$eMfHD z#KI<`Jz+P})VClz{^()$F@cr75>jHUE?U;UDWBM?s-wa^ z2v!!}juAK+v8tKyWA-4luy%0F`q-sXfwn)iZspzq9qPAKOm9WW8gx?rt&kM?xL0JQ zRS1@i=L{&?pSVl$)dp@2VGDP~o;fo0HHhdTSnU7}TWl62wpG`i*)FuiA|khPs>x0# z<@G{v8JL2n`2PDs%ye)fI~$OUGQboN=VRJc;--k=8OVd(DM8wv7Y0{{Go;IV$xyhp zV6@^N9qdVa6)+UBUy{5+_efhYdMg~I?H)PwxG<5Ne^)cFjK*n}xzM9X|DxfLJFiWZ z%{KXb)>M7}=gQhzc&M>+roK~MR$WRGRK;C;Dx^!NJO%w{viYhyPa@(buFJBi;Br@& z2IUdYspmwdaZ=~B`|Nxi%Ude!+N&Iur;TuY$NKQm_eQOK*6@!KOwO5M2(1Nx1zc$7 z$3uCFe-HWh!aD6W^PR`-VU-)?W)M7xCPxl(kOnrGyc#b$D~u9}6^+GrCEQ9$de@#I z{hfuE#fkfWNPEX1+q$Jqv~1hhW!tvxUD{>awr$(CZQHhX*{^LNr6oir&5Y5&@i_soGVqYlSs}Cq%kU>l%Dq~kjPW*3vWc3_`;f@ zSj~9pP--LaU}6=x1brOLfc`~zJ3|tF zB|gra=bN?f8$ii$5Eb}vWkl)3^;FgM)Bah%y_Dj+=1<)$*xJ~)#E_;5!_*g2GRJ8t zHXsZ8_(c7%6ZO0Y?XYMgW)!%Fc|3D9+L~U;x)R(fT#EQ$w(Y_;c2z z!TZE7!e;HgElQozHylhMpYob@2?!ByfP9x1tv`{r?gAHbal@7 zqA~&FqB6mJAnJ;s0160POYY_Dgzpw!5=|t_NuhV%KxICgAe+7QtTEs$8Zh1dxfdYG zFP0#NqZ~fi2BC0hq}?m)c#mc)Z$|^%w1Lo1sDh~7lEt#{0;;xR0itZo%P5}I?G>Hm z^vehAKO2{>p;0UkEm<5pGm{z^y^a%PGJ_IP_$_pXbP`i&b9DF#0gIw)m&Zy!n3?(1!D$SVdHe2Add~7nGLvAT6LkpbD&NHa7lTgWwMn?G+a*d}cNM zg=Ij_A!V!oZC;_%4&8Naa3a%CY>ZLX4(zjooqeHZ)hISFEH~FD@rMi$lkMToyvVA1n#&Ayu1 z*D$PucbWTSr5cYfdir~+8RErt^u=S)6C3IqIJkxG5rY{M+VMW!nym1K#iwj0I4I^# z2?~B)EEBL3kI5yX>@t2e3}?bF?RnsNZgQay{Y_@{Qg zY9ThV6c<=}A)?#;_7^@Xa3X?=Y$}uHtomSyJJLK{N|{fve$<;c&JWgvA0@Bb=9QBm z?QKDs=r=g}ujrBmfm}M{ooi|R#xQ5b``%7qV^+_+ZE^fbXtA0x+%c+$b_?P)PK(c> zJzRh0#pYb4ACI;}AwzGC@*y{ zb)jErg54}#2QO%hpTT<;#ZIi5@YA%-O%|bzRo{;d>;Imaq5iba|MdU~r1$*8uA={M zW@ff#|Fq73K#%`9qx<>!pSHQy{Xx#?S^Z&HX8UKrB{l8xO@0K|X zY81@Gk{_~bT;FOGGqZ%TVPc%JX=xH#k!d+<`(T}U{R+ zgg@?pvNv3$%e9x!?hh56v^@TujnW>c+!Iq%0Y~fq<%3O~6o!i5e)ajY2misnmZ*WF z`DbAA-ymIWE%mJbhkN})rThPE`oCBH*>{wwpP0V^h(Q1V@P5SKfhc%C|7SA$-*B)0 ziTS@tIzJL>D^q}K>-=N`*{i^?;{{QyS7D`)+oPbq64baYEyJrAriBOmr152Bpvhrh zpU^ll5|*bDj9M~dMlDk>tYK50(&E-f5Y;Z(okK${MCsjqz z4K4+#IpX-2NkWstE(E|3Hl_#UG?nMM3hd9w(DRQ;S{ilpL7z%&=BmmI76Rq6_G+pY zn!+)%eXA$Eh_AH`2(an)2CR{PH69G9xKtqW_=VQ$3m9W4P^k|1pr4EnuRhcJiLb?@ z1`fU0HME=d7HSoUZ~FvzITv*hL4z8njQQFg7+$VS^$Un$o1p=N^aA?B024%>@b8=N znMcCi2838{5@Z?@buq^DCb}BI|3ZIG(#rR&|11Bi`JsFfeB}zhkR7&nL zDPb($`~noiZQ;#6f%DdbY&DP`~oiJrWvyo+9ZFXm}jVqdw;i}4z*re z%K6VP`@G#?Lb2HJGhA7>~zs*vk6*pEjF|`Cj=J@brC*-p~HZ- zLKy+bGn{Q{OH`Ax<$X4Iw!tn=|8*2Tbw(hka%YiR3QsS>VbEGw+SR^bUo?qk!NqW7 zrUhofvHU;uIF^CZf%9b6p}65ND5SgBPUjK^hMlg-D)Vn>i5x;m2E(3Y_nRwUu1S7u(g(n2YzC1HP zy=i7ldIwo-@1^uG9!=GpC>6;}mp%;;2AcbI*;Oa0okYgt zg4{oevcR?X=`!nITeqrBrDh5(FCsL(9V^J07_TO*qnR!KCIXN;wU&?XDNup^<3Qy~ zsM7mY`f2wL%x8*kGlr#eMztyiJ80 zAH(g0q-ZLAA#JgdhQmycs`){6J%F<`xsWRb=!2#$+EIFqMYbOM%p#{q@@4VY133}5 z+e$i+oA&%!R^>5L3FS(T*FO2Rnoqf3ApJDasPX)0meEL2+US!bsU!S22cv|;@LhTh z&>?+Y&8J&o7Ts;&>jPmk{HJt?+=sh&e*Z!lExw9^_FoA?tO&j?5(@pyFn;0&rW8|K z9vCLG;B(Lu@tKjgb2LM83eM3G?P2^M$N$?0}?O20?oWRE*8_ChAe!^fiTB?yi0z#8 z9F6R){||%s|EK!&AItxoeO#qK(RBe*e^`pm~rzY>UX*4t6cU8*-JYU5ln{p=#83cerHSa6(8P zX*dn(f`dEvAAiyC$zw;HNp=aJnCR|}S~Aw(R0HmJKsCXhV8x!r~6ObNlvyYT^4EY)mJLJ(Zr8>`_7`C_9lweaZR z{pEWO;R^#eGDZ^pXpj%qJ|2OU$gJC!_1|jtMy!4;U#M*w4EdAziSXdp0b-Zip+Vzw ztK-D1P?yhX5d(xc(}DsVA&c4(SU_v? zZd&bL3Xj2`)C1zayFIkyJwk_r0CcOgyG|P2sTJaczM-P1B20vBlJZWDe!$m<(#p>|I{L`a(RuS2JKz*hSM~|iR5C<_z3UkBK zo>q_Gly1|^uj{WZnj6Mzbx_foIPr29Hn6**E4meesrBR-)-z8-1y@99q>|)!7S^ z`;P~-7z%8Xpn`dMz$ggP$>giInd&dyF01fd7~~32y(sInQ(Ybu1i_l0@pra6Z+BSqLlKksZ5YR97&ms%{yFul?5QkN$@bb#UdRvZKes0e$ zzAEF^=xm8R0-%5)5bg_H-@iGXqdG13CZ{J0Xo(~rUPCvvM3ZBj1sYJ!L+9rOr{H4V z>hcw4spiP%@19H@$)O%3;U+_;WDm!L!dRPv(t4walwx3s&OLJbsDnyDf8|!M)4=kl zI1TCU*B^eS~!&x)vn186^j!(H-^x42Z zF?lgIYiO(1Fm_H*O-xPKZ+u(*sk@$6QZc=m>|aflp1k z-b{4aRFH;dl2o#!#ZZqpaVG zCPZuRkAMYlf{ksOaX4C8s2mO|GMP5kgy#ejbmTSqon3SaP4@;FARyJNI`tCvxxd!&#IP*W6&|%&0?%{jJ0$5}0Bx?alp$xzLrnh5gEjLrPO?fRlc&ms8DXkegPs z-PyS1jQ-k|dllwYa86bMz~<2SBV4oI+bMi!r|{DCO!$0pk>wTeSxll6`jo6@`*Z={ zwoy!V2qGhMNN!E&F?@H~pmcMGlVEYvREtZNY_ zUG~YNkhm2oTU1E-pa%wK0%{pJ`f}BlP6858BZ{W=V0iVcTuQm#Px=vjI8pK$V)*D0 zWGFmufQ=Q>0xR=yEUGQ;;(PZVb|Q)9u<(xUB+!_0`G72^xuGo8d3dvggQg|GZDK1~ zi#o~$Vy%6)1k(V4$)lTT1J^j7kPQ4#Vbs; zn7=Y2M|I91DUs7+6fRsJ@&>>RA7@bTAU*4*!$fgCD6y^gW_*g8l02MgfHN{eJCi^e z+?Ii>=$*C9Njxs}@fVq1mc*B&a<*r|PE@zkwnf2ocgNjxS{LWw@aOWIknKd=! z-j~15XHONez_2Wdz=Gt~lptGG6>laO&z1$>FIfwzH(=GIOu~GPp05Cn?WVE4O|g&P zC!;kuL+EB-tnG{rZr`TpggD&pPXWmj1bH zZ2n7VQWM}18UUc(Dk!KMB0SN~IF|4V9ejyr9u=ZwDxob?8Qv_e5c z>w16fbkF_bY75x<6n(CMzc~=aN}>|{G;_gjxKXa-#l7oq`__i+FQ4zxFafIv*AC8vTp7}n+QgU;+CU3)1rtr)Hks$ zcEwxGeEu5{hRzVOT(G0q&fl{7@jDG6By8XxH``y(E6@=ah)LM8y$jS?=;``r*3UjJ zTsO;9Y2szd|W3a@1j@sdV{bC~h?~{#Jc@WZZ5(jz85J z^S4%PSbyJ+CdK$af$2-m3~;qYb*6DSx6{5*WF)`B64h?LzIR*V;$mXwrMf%V%pBd3 zkl_Qhmxy>j0ru|nygh`i&^W}+aeUMBAFH=h)p8!~Yh@Hb%+;dFb!5@5O(*q@7d_KF z#Q%N?qf5bV0rYIHPubilC%5!bG?23zgYmuB?f?lg#-bD`$U>{;sO}D6JyTJKVJTFS zy^@`Cf-RdIO>7uV;qsiWG{*GLEQbzzN0Ktqx-x~X;4*Mjy;$wvrhYq$q~)+j*WzCf z5nw!MWId_na%5_p_>-ZAnZUC@fhzHU6DgG_F3lLA`E~cWiTP&VN5p_7eZdA)AP$4n zHp@z`TL{+5m3a&Q!hwwcb~NvFBql!Lz>(Zu*^9f!mTd9tGD00o1(ZBF5qrU;d^x%n zG<;kkCQsBR>pHbo-*&R}+rpK`WQ6kynINa4ZTW1ugo3?L$k{|Zd_Eyma8101aO|Wi z0X@vYbCRtNtjFyG4oRz>`_N*onW1UMt^IITxh+__g)+!0#iTYKj{W5rV?ABmU1Pb$ zc^KF%Z7Q@5n$LyFQCChz5fSYfxCu^Q6dqtnM_+PK3*uUx!_!jhnEQQFvj}r7d`@A3 zNP$W9Lgbg0^{wIgg1qkT6nGv6g9o&pbZ5BPu*DS?*GjNbsF$z8#a8tkqt8C;ptWH8 z-&oP&GYGu>O7canv|4EGC#N}|?0X_?ikXVb_7YI-ha=bX$#@n6I` z5v9wptUMzSaFXFl2#|=xF=};P3S$PWBkw4@*GSTG_3T+rl%+(2K;I&+Vu*?7*}|+3 zmM+YhfDDC$Z|5XS&QqZ!M`cOR!ZdSLyrEVDkZ1%$p7z(kussst&dJs1I5a=w%uY*f1HCQ#!DSLV(C z9t0kLNbtZswY2ROdRxz5$@>TDT}N0YDX$KW#aHxEU&llw8-w?L&UF5hE|8q{K~$MaTC641$OFlu<$-l zYsmDu7aGGN#o#-JdE7@c^*QVSYdW}J&_rjx(ePpZo&4~?GoT$L7}grqn6GGZa;=uJ z)jZ#40|AJXH(Y;V;Q_sCKB|fAJqboA5-C&k;=-nvJPLgb1)Ou;y(NuHJF93C&IbE`t78SsR* zWBp~55?ZVWyy&GII3Xi@D}ia#ZhjjYj&SN(Ik~ggx%|AB9sIvOxq%R^- zY1yGTsKvgXH>~P6#bU*NjV)h_&5F~%iGn{_%smeS`)0TT36@o997;*TWky&J zE)Cljr;SXM8}}@uCSageyglt5(oFB!6dUcRWLZF{;;bqEjPa^bMX=RU2i1KUz|?&4 zLfTxkONE8pB?GOvDHPiFYlRLD!rD$;TtrVwtr-A1(Yc$uR`FEF-uZ_|EEe47JzywA zp(l}ft*F3DCoZI{6_*I4C*7TXFGfhHK)Oj*x3QRqgORAG?pt_tbkK!KTlGLP9KM+P zyiFFCyUq3j%?@9fX^VGf7Qict-au~vkJtTn5H*oK?EcvL^H@Vyv&FC(T^t4EuqBqy zJnd2TM1pN6%GBt(X*gmg7!ICpMN^OW> z*^XJ>7o4)378^dzDquiJQEM7h*7ugJZaBi_qJ>A!C>8x0kM93jf$NZ>UTKqC^CmFB z+-VkE4t-)ED`C-V5*_(kLSCwu8=Xew` zId^I!MMdd!oY&`C_qRdY zySkjGumSc+~WmbJpGLJaq0Q>nfxAo64MW6x(KtIw0WSj*S`d0 zq7E~%``xaO(B$@^klmRXNFocd#2XLJwObMc5X_=ywc^_&+ezmaxJA3s3{W-DInRbzBJ;u|SHJCpKP&IH5z;g;cD z`P+BQ5E}nP4`jKozWnF>soPsVrGkCOgCn@S;}5q`ZX? zq^)FdfK^uZfx&NEvPTlR@+&Vy)jYu&$(4fmijv~@B8YU6Y@H_NrEI=Py=(Z+;*D+~ z?f^VedwXU(uoLR)n1}s>ySuhk1iLiwg@FM!Agu36=%tB6bR<|2M z#Rd;5_Zy3T;A)x30S3(J(Za)g8TUlhcnimeam64yz!&~-;^{JTl*u*s9XCpR%tW^z zax4=MER0}VZd#8kZa@0jaaxu}e)QkO`(u%ivy>`+aY^|iJZUm!3Zx+$5F7&K!VN|^ ziahdNaQJT;x=((`e@z250jz&iP{1U9$_LL<%YgsN>&8;o+Q?Ds-=F_7=cRN)g&~Zb zoI>PI$L%ruDIbK`tu37=Qs!c@{0jJgz=C zrB0*DASL*eAGOXqnZX0-4a`s^gkL1ZOL!@L`r)s+&6I;mVlDSkH{B+xn>!p@!Q#zro38Nd_<*hCB)jASPkSUZ+74vjl z2szGWt;+3JPSey7tVpMe(U8gZ6Uy{eju7d^wyu%!?K~A{i_u#1pN9}P>(r3vWO6(n z%CnLZ+T5Tlf)b*ZwAr2H52dyufkL*o5eGOjBH1GiTqB$-0D)^NU)|i=+w)QdZ^}uF zPNnXaFUuh|V`z^)W|cWtzlKx_bv-hZdhDd`jA=Wvo1$cUl{#1WqR}bb^mD?u|4d2* zt6n&SRR_&shK>4H7~r=sELQ*OW&>tXzwT!UVl?8JQ16yc6E)0G%yMsJe?4EU(D+3< zoi>D0G{K=`rli%+j(&_`rY?O;AzL;6RfK{KJIi8-cblZp;y={{Hi1YgOkp3+EV@sE zfH%~*+~&$R^jMNnV&FLqqi3ttmI>;U1T_;hdw*P^bP~-Ex9KCu7L~Z3xg*&~_EC{= zq!?kN(mPqQFJKPx@y(GGfcpIT_BP;h)RwjrCzhqrWRnrDwZ;wXN2(ggFkemIh#V#c0EW z&xLeSF(v<}z7%`HN&k$#d>S2C8p-wWH4y*srbHp%a&gZj$S*})!zWU2wN)Y05l$>{ zEigmNUg(QSAOomQ47qA@O&%X|CvkVQ=>3cWT-w;j+m;x*-(I?izGa!h-Z7FHCt9dH zoeR`=`Q)Hdo~kv{M6OIJcEV1z;vS$+(-&Aoa@YuvNP|D%1|}UCgHiRva{|{K%;?KZ zs6s6#wfI1fz+($w5YQ+Uqgse1cvUCNmn-EBaybfwEs{*g6zx%@C@pys4LT~lb)*I) z6$xODs&8_QFpH-XY$XPVHaD%E9E1qC^ghrRPCw-|6;js0M8V;JKERD#F!Qt_(IoQF z&F7kI5+*Y`IpCGTL_v0{_#<|+*VED}^kZXV?-71CZR9h~@Uo(ZXi&F&KM%IJ;i$(> zAlX;*5^EqWnG4enjwH6eaPsK75%I;MoTAA!Oj-q=Z9#Rpjq$kk9902%|LR5V6sl+^|y)5ubvQbji^!-cH_Ol zF%IQiW|Ce~wCb>z_AwkxO-6u2lO6}C;4DSmdStsB<387(C+PLOxF*!eAtNyoooeO} z4_7M(bb0KQNE%#Z6+4fMO$%?KCnbey<;T@pIK;V#fxq8_^lyI=T#KRhjjFdpwuLT)^bABp4!oXhDKRFx6ZRKIhJJbL zxuCl*R``XV12_+pqJM3Eh%Ec_Z(l(CLJ^g2}+Yy+@;!BH`BeCq7ll!@>my!tPh&T>hc7R z+#)uHJh@^DXJlmg5H4!u@=uq6Or=Iz@#6bj=m^1|Mi^%RS?jR(ufvH@P9;9ta)*j} zC@{UrXUSP?yv3E@liU}PI|$Du=4xrzSQess2aJihERQ`AvCifK?Ko$rezMU5SQNmg zhOqC(hNHe=Pe{Pi;?P_K7}a~v>QWMo=2>7Be^3d5iU}5dxR|d8@7fl4CfU{+8r&=_ zL5cAsGxpS~hrR{?OkppaaB5rh>V?YCV)t-&=)e@-aJ}X2Be8>WmJ>Hgha>c!+$3`$Fy76@6sQe23RPdB-Y>+JW-&6Oj;*M^=6B#(Lnu_TH5gWe?blEq4W$#geyS=N3* zI2}LC%(Nm2^o}y?0W?f=BjroiIKs8H5(h|ZH}JrpOXqkI)>x+)7PpW%9uGI z{7C`Jato>6N8C7B(F#=qTqqFPLNi-gc2OfoA={;;FNR@-nVBI=-*!2U8EeU+0Smp4I zaKsQ^knZ;e!{>kyC!CEI3vCjZuDWDD6HryZ;KFtG6mNWBI`UL2z%1IjFiya7alNCS zXK@$+Q(E!=E4}8B0oX{B_E2bMCGWx)BZIs(nP+jvX$l`W-)tHNF~5H1(!S=!r#A4? z#ipi0`*N@}s$1Qc=WlE#xW7`^!>0DcrFrdW1wL;AZeS=n1LZVu%XWsnml(Q446Syp zVbFdOT%%d24Y;F>K`3RCgJYh;!5TB1FP;mk- z`n7!Z-0RL>$NNiPH(Y_^wVxBzi(xFb;xEDCd&>UaBB$6d+|)}-ZHu}sB!61flah{F z-WsAeeh>=iE-n)jNziTS;TyJL+_UU})|<|B@;nydGd~SeURtSclvh4^U-1pGUClO<#le09Jv9c70k}x?p_mi`4B! z|FdNFZS<%ejssHYuN42%-0zp3HC|X6MBXQOHXm3_u`WO^cO7}T6o2&-Ip`1yb2%2u z?Yh=4RTO99^p5O)oh772phKifd8UCrbAbssT}yoFy#rTo5tqnbp8)Y1c0!pZ(mY?7rVOP8&!O8=NbvEE1jDtif+J^qeBkQQOtKFL90VID zJWV@Bh|_yC`# zj)l_hIRzq%3p{#PLulcFR+MZ!*V0t0wP`td-rLg5kfBn&k$rCW6*{n4r5!^k2ZBJ`bOMQ7g6-ZG zA6Q8E9-!@%WXz-8DohUgoVba#m41v5EL$IE;;fn&GFV|wQ5`ApL!Bq*o3N{!!fWch zHb!wi%$Vw@!=4a1AdvXG7|l_>ih>)3HN83T;&p3#D}sv2@DVrh;~|F%#ZLwwin%NM zZ;EV3!yCcCflEgbGXrlP4?vqYtYs(_XcW2`+? z>qYiC{8Y9Ne(uhH7c$8|po?~-pF(C#16un3xsdrmoI%vk!S+f26f&NvsGrqcJ|jIak_SkwY4F=KW*KBu7d?)oPkmB&-mM<<0|y zv{7+fnn;V{twIf_2s#IDA>@Nh8PM`D;0~l(V#92C-{BGaMxHanG8Cr5=SYycb($g3 z1Tc7^wm}RIY5nR-RJ#$IbIQa0i?Sfw zeFYLz%x7am%@8oib(2fhni^85bToD%uHqd%ng+uZ1+pgDf8LltqVA))2M6ziBZJ@Z znV_KYY~ihN?~_J;EEb-i`Qjw*#E>|rVG&_6=(r?cYbED{)rfn> zm+9kbHXe?@gVqJBL6~duQ;mVr71#Sw?it41S0#hCdmfY#}@>FjC^ z@DeVBOY(b;_YjTc`_noXtcu9gW-dYi7jj4^bw$1gM>)8MLh*|a@P&}*y~$|9ASa-A z>0^>_B=Oa0cPh}T5c50jj<)?+Owzw-cD(Vg75|OH&6}#4+nqoTg1sa2vPSae2`N(7 zNe}m>VCtq*J*w{V*q0R4*bP%e$j~t+0yM_9B z&y$8b2>6w}tCU9iZh)>Q`9`dR&!7$6WBVU=HZMO%3$h$tT*MLNp9>6KzeR$wR}FvJcR06}9U zg!`zV`QY7+{9?dJAx|&y=yfe|*n>RYeGhqwn6jn&ZtD5bM>@6Y^z3^?BIWsOaBw;@ zw)5j9$tJxvw3M?+$`}jw0j-qly)aii{8edpDHjXg@$P<~Sp$6nDDm}s!uk^PvBfs~ z>48pnYl}CgS{kE6*i=QIPK!od;`2U7%I@@399vGMDLD`2rJev`frS*DxIAo2o)u2$ zp&Pc|iCz~~hi?aC8&|+jHnyA+cBK1M?_Wa%;PtJi#x>s5*hW}po zfO7ChvwsP6b&`fDQ83)=NO^Lxc(mDZVh+rv58_A!x+oI!V*{tn7|m{o?vjW-Cxu~m z8TSLOlA^O0(&w-WVHppEtuEr}2Y`zpUNA*-Z6VeVIflBGHEA-V-jwdT6LM%dYYo+m?7fh;rc<`FRBoYhu&v zi%{48td`jnW^6CBmM?7jVgZ|NWzxMqv#=!gJL#6CtFgMVM&vDT#iEg{%vy<0v$A_g ztD#DKJCdnd1?c#<_x_EW+B2=Q*1FFYhnm^Yk5<@-&@46HTtoHzrRJf#JSQnFdR9VE zsq}BR9MuyHx$<_;Hz=(G<+q4Egz&SS+^h&i1f&w=vm97Hv9%WM2=g5nf}7i~(7i`) z98L81cJdb);J>?Gzne`l_=r+^XMa@t-Z+F#A>fG^k)rWsRwSjw3Gq`a;RGQXx>LF{ zJ}$lw?|%Ixd;QnvUFvLeM1bp$WppgEE$ceLXwem+leU=K0=D zA#}QlSh1z*(s{r2B_Fxpw_NbYwvK#uR)}|RIIBHv)9p@ox$S6=marpkMf8k8H(I&G zTvpYJO9ZQ(xP)&DmZ=1y6xrbqH*5{6+AhF-+==ay_2dXS+7B1Zw5MA##nx35W zr&Ss-Awz@+Y^E#k1{?VHSk95nymDZ_T^vw6%%yrGU-nB8nlAe7of!|z<@PD+fJ5={ z*S&mh??H_%KL_{x;m+!mm{hWivTWI9&B}xeO zE=;qtJg!G&&e+hJbG2j>MRWwEZ`XMRPvJ>YE+ts3u^aJ2aLnDy?WUHn6Q>SB?XLy; zo%`sl?16BMHe^wxah^FPzJq3Iy{3-wjo%baQVCi7*OkW7};GL`y zj`MTw-|2x^P+}BIQriB$7WQuE+@LNBu~%4*&!g3D=H0QzPzwU-6wAc&a4d{0uX>w>bh7KwBy$>ngxs6v0b znI!QvndIn1EcWm^81%*faa02w^#ejdlOQVzc*UtZ8sj997p{C&-{O#L^H|VF`_qD1*m4hq908sZn`j@Ko7-=EyAdJZ<9}P0 z@341VD*tG7;6m?OSkLL&>i3yy58BeI{c1q#3UW|L=2&xyevIolFb=wq0S@M@2vf~q zrDh#aBXrYge_p+~{Gny754a7s4zNxBO)~{Yib)HIvh1|rA&IW8N>J7P%uU(ikR2Fm zI()MSMGV;t<;NZeA8O`ge0G^U=(9-da3hZ}291;w_0Z*0og3Y@JvcZ#OSNVK@uIP2 zTL>}@B+zVxyfl75hd#jWT4^;Tcx>M6#F7fW)SGV8NVa7_VO@vw-m+x?cz*rPf_eg0 z@XNg{i`7ynI?Eao^X}_~`U~A_P!7*MK(r^1$YguXXS_DpXck*ZOXXGqrbREv0p}ZY z0=g{OkCWy0Ji9kQNWsKZ6o`;DZmvM2kn=gb{t?NY^S9EPpEpz{u7)>e}C@ z;{gqa*LMMpK|@TG_kjpTbuVW&B#)D` zbZX2(pu${YH@lxF$<}-O5`4K%x&9PQ+_Ufp1J{!kNV-|otVW9(ob^wMcL>OpEamYuq0=+!u=~ z_f<1$;C7Jvi?G=$VN{eD7{Z|9!EOmfOh7~Rsy+J8%}KS3$}uC`(N%wFI`*m#$tFNe zYDTp%ezr@akWLLPxbCAkQ0IwI%=I@6L3ESf6vYFgp7wmM&{^t2d+N2!J-iY$>GuHP zHV@M>AFQxwyqz6&kN1GI(hVQ-S=_(gDLKAm8qN2p`_W%!@Q_)XA-j%Ac+eCyuz#hT z(8!EDOY%nqJ7KBMz5Y$?H{>S));q@lU;|sqZXo|vD<6&Bj&ys34hSVzayZf9QFPmq z?febA{s??$l|QuE91p1Ep)#&!-4gF1T@y%Z;R7eb%OhA*y4_3-Z$Oeiu&a=tK#QR$#Y2M30g!R|bn?~$qy1*69U$K^j(4M3rfOGf7)KxWSHlEw9k z(hObGKvEI1W*&J;$mSk!P%87Ma?1zQZ`j#{_|Grz5oW^R(mw2Q!vqza${u)XD+VcY zf**o>KzG6m<~8|cYCr>t5j3l{KVK8JlIuz-Cp8LjQzrHvskZke~Cc|J{uQAPS56!hBVg!?+KCjXiDo3=k z9e0I8$>JF)?7I!tUn&6{YQYI8KpspCB=#BwZ#do%#*IsK^<)HxVo-_X;22J0o89xf z^2fjrB~<2UA(DuLIM}qB8miSq(jc{$rj^Eax-r`B`eOF z5ib}?F4q=-e)V$!m;swG$;YdPOTLN(?>ja?R_S}!z0}hhtnR1N3yB$bJA`d%whXCp zYq=gE(1-=W&irocmy9Rs3)v9$i}8#LZavO=5pbH7;|ZB!!HOU<6gVzC&|c-2=!qXq!%w2@>S(6|D4)(HSes% zKBFrRKgSJFa8segCSo9m_yBfpO0>CH1xPw6d%!q+N{`+t$E6{usISZ$hgI!aB0pWr za!;@cb66=798imt*rG3rToYYv8ME#nvMoypxtuD>0WytX3mE%rvDt|%rc+gP=5^#O z`c^i;B4RT#hCINDgK#Fc6KIo>iHx*hzfCXgET&1(&3-p`MV=3F_BE*h_@!HQ;t3k$ zO8skbVXW4b-)9}wh5FbGOdH$1oDjK>c3a~@Xm?dbzD_84D{+$8DHjNhZhyYryUAuZ zBJ*J($?}IAxQ3bG?GL&_IfkVkD+MYrr&am1^dp1Pk;eU@;PRJncwx=hmn;7ka<`2_ z$le~;6Wpf=4uVbwqiOYJEp1g^ultfl=e z4pJm3SBw7M97!`+uGH$GB`FtFEiSOUtu@HAG-`HcJrGB<8m+5rgy`f4@DYg{IC2d- zWM^K2O+vt7EKnGm%`=j76yJIUD-}?NBBxTNo2Zwc*GM38nfq{ma9W$i4P>j5)!-WT zE3V*EUs??^y9h_9K{KoYW2-6@QR2m|XvtxeTp5Bqtr;$2;DwRMi^Q2dq}RX>Iw71Q z4b`!kW&)thF6OAJN3S6~BF*m~kARW(8ShnDJyOwO<55U#9pg+3bky!v%1+7AVlgPBB~Y0tk~tG`3IwkF`fvbw2YwV@#qwkQKaV{_#ZIYnvA zL}Yf~<8kwGn`yDUQF4nl;9~1S;TQ|ffpM5{ECe{|xVZEOgqrnST3Rg%BXEmX1J8O!b9+BCM zZsnOHN-Jeg3|j!V6dxQL)xWIZ4g ztIq%xp>1lf$lpoZG7xN9Wf^P-OFq)b+8lM_0{#8adS3aM;_YMuc9%Dbz=0$aZhSF1 zbE6}W<-E!uV9&u(`A*E3Sn_by-hH}@vbt9{PL?(o0`m9u6D_}Otv~P&WV}5C#VLFf zpD&0nS?_1|M{bYM*CmdB6B=;Y-FLkVyNojKWprard@j<*aFulwVNFJ_)@K=0x2vCL zNWK%6TW9&C>!eV^E%kz85>2H$Hh>@XlX%{6l)B{cO8t~ha+SK`&sihM0<=2Y>~K zr2P;&)!)9#=Oa!j7cq{v#(dgF<}WiS6I1q;3K2*ZafBl25lzu4XL8&y0fnQxqp)N{ zSXkMQa9r{`no6?=aTWj+yJzRa5mF?6>(jj)=!>9(HgOJp>hcFujBYtAB|YPqL%rbf zY*u-NV$ZbEOmA5iaBe*#wgbrChP>RzHhwy5Z3aJVSamBH&~?x|r+Ozu>zR&DZ-XMN zNWrwk$6FTHi6kqUQu!(dF{~0Cc&(l(OCk(U`&Kkhqp8{a zdL^GBd_H21R3F-MX+6Wwa{kg9z9vVe!)Tr`@9qfLfVTPcezHf@%2`2{dMs@Y1O-Kj zD#9Dv_WYEv^?jrqqVzdKkDqUK3)u<5tU5o1R29x@?fAgo$tEAEt;eqQI@`_W)ghic zjTG>s0dKtzQ$<#5FWnXkS@H49uf~x@OYB0m$PxGU6ZA(5TGew@Ic`$-td&Qqkp29CU^*T7J4Nopc` zaaR=-eHGc-B*!$F8P^7Lv=3{6{!QR|S`4~BhS5ddK0Hmn_-;3&ap55TfI|^)UGTR? zS*WyWxt0{w$UE(Oh3|KB_t=G4yqBmOxPM|RX5WY;+Ljw?T2RTPQ)xk-jqSb;NVvSx zrG#&9_>21&#(%Ud0{K+M&rG>)u*BXtZ6alMg;TaU>&maB`?)TDe*1dI^s8pnTZsgjz9=#!lV^%spGYhbye`hr2-;DnfAzmz%g-Bz7}{XWdPX<(}r~ z;??cckjQlyRM^aadH<|N;tZtN=)=HlEvhw?JBY*-)Z6w$Jws^V_x9qt*SATUy6jmU z`w4dSD4EC~`E>R!&EoNVz3vYA_{Z?M-SZ&-wFN1>#mI_|uMT7J!iHdd+D@%o=KrhZ zgI_WD;7w{n$`bSgeOu>b=*lunt5RF8ws1=ek_THVnQ*^QPmem; zpk74r#P(-ptL~>noFCuK#TJ7<^rM#L`cLtVkv#Z)cN_|AwA@?<#*ND<6Y%#3WlTSYHzvE4)s_K%z*3-9f5Ej zCTbb|Kk)LoG8cTz#>(IqUM41XtsN%M`)yGN0*40h@y$%I0)$NX5eGm#UEh1}HuuBU zNzmOel7g`wd@T~TN*c$b}Kxv%1-GMLz)a^8__ zCX;5>a$;tMqsnTqFu_bv8Q%kv$J1m^q$lJUo>xb3lq}>erR;9G zlrNPfOc|(CkU@2*+;o4Q8obwEo1*EWM?4mvNuB&d3k4bS3e^N36s`!D!FCOq{9Iy; z=#YVV?|>sD{pUR8#t{B{wX!Xn^T8G7uVG2?xPZ#@bjbx>=u9*f-sL?oxBUY)n<=FV z#e-7j=vA4W4di-GB4HwbHCYnuH~&zEVaCSBCx71S2b&Y~v$;po1D)+OPLi`b(Cr+h zX+^p!$9jxc`LfA#mj?pGG+(IcrfXA_;qnF*oNl3e^mrDg-rR@eerBen6C&ntk~}VR z7|S{rfXQWps^)f!s#-D@aKnTEz2&)$1!=JcA0U5c8^XUm?;cfbd3@~bKdm({E*6d1|A@G=mqX$W| zH(O+Am0(_xGp<) zyiA}#=Vob)Rpeo?1c;|1b21Mx!?Oli%|*inM0T8pEd(mN3jKwb%WuXZ}b?0pj+bojwcaT8n3A#F`(_Y%p;zDUS4TrIzw$7hGAI+ud)$e zaob}aY-6~h)Tdgz?_%k%xjLG0^yhbnR`uUy0qNZaChOXzg4=jQ1ONWc1QP_d+jaq@ zG4;68Ld4T+1;>($^CASzy9?4mEQd+(P`5rb_<|78JmBWJo0mwYG0^(?pa|;@j7ikN_>DqE9i7LWWkn=V46NL2oYot164I!T~1u@s2C)!;C%cKkAOSejAW0z=kst zj4^miW7`!<>a8UAp)01>OB>iB-F z=LZj5)95w$ zGM!elst#yj5nl&_y5S(rhN8RB!eL&G6vcj{YucqMJ=L@9Fwkga6v%bx93!-gh*9!) zGQGd;>t2Xaqe;>(yT&@>pwCzLJYhFCB`e<3BW}^ZqZoqAYD$CdxF!7M6Cg+!_or@3 z27GuS%FK5z6T+rZc3#1xULO*9-|LVxY2ONtjEN9~!|56-QCvb2-U(zFF|8d$%milb zg*xlTeo&b-j1VQ4l!Xa13+KuJ$-mxKjZA9)dI3~zoUre8i3SK|9FB{hgX#Ah* zwHK1E+-OGB*gDmouuYWQ`f_j$WC=^qabKqBOVJSyu}n?UQLaExcC|?IVm>kstyheL z81z98U7_0K-M%H4&~cH)a-j$*Sad=Sh?hRkoL(fTp^6c_5Z9`ZXb*Kn*rrumB`u}v z_DpF9UUYCopGJnIJuxUO5rmh=9f@LFrF+`?gO`OPlEV$@zDXJDO+GOnUZ93-j&c~6 zc^e|(x|*Z4bR{?+_+zefdSfADqahL^+TypAS-8~ROQ+^+2Ld_ldtd^MS;)iwm^hs- zoaT)yjb9Y<^59|td>Ah3>bX8v2Pp&(-XkEb=H7v%-rAI{UpN$Vyt@?C+j?Ia;>~iK zasLW3j^7l?0CFC7EN2IbE^427#MjG#U!IUx4uU!|X-DG%#G_iVH!)!hD-#oPUz7&& z6z-+{@`(P zC~LNXLB*8eP^lJ-)P7^$jfp{aXi1%oVC!X@QNHxd>kf&j%by)AZ{-18Qp%_D zLKZn0+;+5EUGzX>M1*Uh20{Udfm>+nK3Rf;glt~(jq5B|>renlgh=3Vf>1AMONO-HS|fGTwdcWEdEZ8 zLG;5|oe=rHOOU32R}XCG*O4tR za#-FS?~+!pI}S`S)htk``3YDH>*miv{HFR?b`Ex^+B zqCro-x8Qf4o3V_YtsF#ah5vEs<^^o}`AZn5=Rp z>P`Y{GWRh$i1v#T76Kg*kZF#@^fM2jaqX9vge4?S9>BpLbU-slEb{og1E&H3iy0N? zSR`6F4ruNl0e=Ib%X21%Pj7V-bO^^;k6L$8dl|VE6og&ESh+LbgK3HkG4+f3^cybt z?SH1hzXov&aYI{x`=El9HfVNXi~9F{B9z-<@dDzCZpF zf*j{d>5WR^UrDy&*`=u&BkWDnn!L;46frmkg@u~;83*ManA;tKukmm!ws`R z7UMXQ&?z6tD565s@18ylZ$LI`kvsj^3E_N1v$qoX^fF;w4h z)wSGdQkdkjaP=7qo7NLuhm~`?8fGu(IPF^hrF&)^!mF=W*Pvdh&*I1sWWUI`ts2iS zguZ{-d>DI01RxR|Q$E+Zx&}p#!aS#A@oWo#x7wXURt+WAc7sD{f^|15u{fY&$E=F9 z(Zw!67b>c=O|)*+$TQ%4Fi#A?!>B(pTa^vdkQo8pU`&3`8bwcg0Rx^z$*x8tG-53) z_!```f@H9qMLO4^2-UP{kgx(CkG7qIYw_=GAUNt{*`U)P2wkY2jQSq&1vIJju-5qk zsU1I8@7qT1^$zxJ>~YJ?KCyp*pqDNC5c}r$yU$!t{_!s9T3^R6P0zHkwIMut8qk0b zpJPJcZCog^HW}a`#*lHfZ7wX3riLdM*7#@5xiX+w6Q5@71?1WCp;k%@5Mz|QGV{}K zzK$Q~vq?RPBS+kVZlX-KMGk(n1CS4$`Z>;RE%inj(9EE0zt5I!D8?KG{ov#_x2F$- z4iafR2c1(^@{Z#kRHoRV_DZ|Mo~%7gQG*)m+zw2lsaya!*Jhl1oRS+5Q>)a zS_J1i)VpQ~SPtCDK}b?(5GR_%VG8{iUAg{lLxz-+DcseFXGrssY9^;N@zOZG{Y7jY%Z`kPKI=MNfwqa z0FlKKz8q5p|2;UOaAQeaa=qe4sYYgH)ET0JY!Z8Oig4*Dl)VZUcn}a{xHnTv9~P6) zxlb49m^@WX7Gw(Y7tx;CowGmdV0N%zP2~qx0|_gfmBpDm5LsA|2}9yd#h*b~M6qL2 z>CkJ1M%X}p@}@~Fh_i1<5#*;N)ohtakqGb!YyODIpVzle7G{XYK77?Gd zm#4Y#^JVDI?aBLwE>6~LweO`FX0+-4xTi#*3^EFVB1r9aE*d5LbAI7;$fo$rIKkQ9 zYN3C)BWJk{g^P?ai~V+w|F&&G+X`1wMfy13hK7?K6;j3nfeG?Cn6{I}2bl47v_o-| zbj0qQ4Bu@{Gk4#_y{VD@hW~5I9Ay9n;uxtf&5tq8bghB!x37`P0Jn%A>bXgsW#d9$l(gZ@7}`Q6S0O=*sshM z!L3b^zsY)**ToL85Qz9^GHTNE{_&H26Bw=MUBTDxL@zzutEQ}XloDW5OrGZ9y$r!X$I-_6L`DZEGmo~!m)Ydr#~_9(&u zwTQZOqFfKB2cX#Sj=5=M@kq)Gj8V+s1Dc4F$6#6*l)||Z7)MZn{QH*|e!4e;4l2od z;)C(G>wN6gX>rptj@g7;4o^>T7dUpWGa7F>7Z&HBtD;h0=05DrKE4^pDR*Y@ZqsT) zUY~A#%^STfP_*_0QEO9*q&*6zHL=jDbWk`|-jw~xA%x*J!GX8>NOAIYQiGiprO5{* z>lSu8uid!p5U#}f`D6PIsK?~i7Fzdr;Nz@kDk9+$b~LTg5nj})o_L|t#$RqaDKD3@ zsq}X-yxI95M!?tjMa)FprHjo_u)RkcU&BZsEg4DR%*8 z{XWlYWnHjTuMmMIdS8FF6yP-0r@95PTX!Z`zV}^pqFfXe%zPjQ*iH>qBm5G)G!-`01IJN7FOR@J5?W_I|dT>Pu)SAg+jK9RC;h zI5V|?u7m+NeA5;F6OjE{>*2Thk9LPcshX}W2%VT`%cFg(&eqWdbq(I4zPflP*v`Er zxqk{Obg41|TUgJBeA;JyysP5vixFh6ORbqf#H&2;R#9J^kM0n;#}<9k8RD1SmnRq6 zfP-#VOrG$!FEWij6$XD3ML+RpUo}}J+1-OOhDp^Wpn-!t6-oc_6e>)%FcY?*uW(8I zheGS1eWPX@T*c>DVAA_DjBN1r%E|SXDw!?Db%TTy`gu;rl8>}(ME@Yr2rkYBwC<0^ z;&HLM#iy;=&BfX&*}!?c;;fflj{QWd6yc$=SRTv$WC(x5sRgW!hEFW)R8j6~U0BB- z)m${rypAq9DbW*hx&sU9eO=uII;TFee>!gmyur@tP&?~{mbF!uwiW1pP9F5T=I4q; znd@)N9QwXgVu2xrctfvWvc2SStO2dMR7wWNpN&pFI1~1_J|X3M`668=hkBOK zz|S4U`ODaO)W~FRX9`BJ)2qo;ddshSb0S`fpBuye7CXm0y7CG8Hm&;DxXZ+ejoco_ z^75=;^`eEnsPZjo@%SYMUwPVaPD|4IIZQNuH2?k;n&imWqWJB!tcTb5;(6LlL0h<^ z1=;yw3164QrUiXyT@wOD6in>--AWw#9S`xT`}NeO`)!%)YOxxTIv&k~zhHj=9a((g zcaEjTcu0z(5e_T#vCS$z2(Gxem}}f!BML3-I2!AuEx+*uSRiJzckO_|S>8^cXIu+T zudYRJPTFlUCE#gEe#iZV=yxm?_YeZc_mOM@&`;=-Ap64W>vKCBUdB(xe48V~D4?)y zDr+_R6!lJnMPzUa=*a<{O>Aa7?f-Ph-?QNQoAORQBgBbhBW)go@j8lyK6OGYJrwv9 zKmFe0YNI@jc*D%0YtemDbGso~?aE&!@1e!kQv&KDFB@NBvwg(&;HpkSHY}*EXs`fY zepL7l{Vf*n$FnQN(K+YHuUHwW_#!LdAbG~by(v^w&{fQwj3~=B@gLT_tlY75ed~9x zs+c{_lXKD8Q>-5BTN!(Gy)?k9jQ{CZ=<;9Iocn8I7Wfr~0pWQ~O#T1EntwNBx|~?x z2oVsy_N-K-z2q$L_JW%egS&}(&L@^o@#3ZG;bm85q}PHB;WYjCsMTvBs#aM2C# zt!W+IMK+dJA>O8%E)ZlOA7O(1p(Kdq7&8y24x5l|XSa$y41Xi5N(;DCxa0=uNN*mU z6o7=6ybWVwCRB(3yA$1pTZ#=0oPXmvi9smqFHrG;Y=y5knS_w0{L7-R1xnZO1F5F9 z=~$rYp`@SC@!L%Q2k=&vw{l?6r#;E~!}J+)YDe)JBpUp=r1!_j+l~Huy!N zpz#18OFUIjcXL(pF))3efz+TH6n(BUJhD8W{92U8PrmN8syBGo`BAai2vsNV)NoTm z2iAGbJ}=@Q^ZZxBi<55qI~B8i=8kjI{Q3Z>`37yHNWOR5Oo;sKaeoiZM~i86ow)97 z>C?T_Cl`Q&L=G<}o7>=@hijeI%GNCaAwB!?<;+l_ff*$ACPJc3!WBU64Bn)s@%xKZ zxa&Xk5Or(P-%L%s5PJV==L_J}O+3^#!14B_(*{)oYuvpIMREcA_go|=S*mhDTd;wc z&ZMXK!w!3kh$81M!oQJSEby|_0k)v9a@#$Q#?Fk-1{B6$i+}31V7~2v%UDQ@YpOE0 zCB5{4Fwo=@v(&kUf!#n;m4L@Y!F6mprPzwixPURTzXCDX&JzIVoBa~pQ8@twBx4V& z89OEtt}KNerqCB`U>t(C2O#1wQy?;>RB%JiDuEDv;?XQJRVvM6w%cX{Zhh z*nT#30idmn_VnMS%c7NRdwH$$%wpsWXM1ezc8`C8SbUW(-VEOpN9Ug;VYao167+N; zZ0vaA;C0V)*W}?+;FcMC{ybyih|ADir-waE;6f~qDy9?!8hnJ3RAc*SGnM47oxu0Ldjkv>_RV=XI&TXhfpqabS$>(VeM2xrmx zQ(v35zE`=%N0V8hGI?k_#GZkwpz?7<@?6Y3my_)oz@CK;`>6 z*CV3e^2kLEj9>KQPg)R*wzopdERYVW7U^Pzpuf70YzaTx@04{CEJ4A8pFhwoTIdHi7T#pZs91JKjuBOO1%u!7xa&DC#=RvzzH#*{GyZJWzA5wYGLj9f`Lcjs$N_VCII6cP;!9vA!5!$CSA0==X)syL)r|N;S0?jn} zTwt?pwD~T}Z9%%a_Yt9vu~GfjD?pIY%EwsK&ns$gCuAWhMulV~f{n|1VX;xAK91WflhB*kw;~f*|%eI zLYl%*A+H+zx~0G|ECOpGtOdtFES~sKI^3Rf_y!G+VdtK;=l6{*tOUOCzU5t>?e_T0 zWnSHcL&y&w7#1Y<7AYhD8s3)C+3vhxTyUR~!;6DUmtJw}Ud1H2xj=Hh}3kA_&qkBNZKTm>vJZrW>HOE$&dh~V;c_=r5 zckt`*e@KnJ8XWkBuil$a2h!T>Mr*UMCVXwR6I%!Uj1K{X)k02xPJd>A=QgfBw$ctj zHC|2^ep^YL{eh6@o2Vs5k@R)bH;TnCl5zUIC;9+@zjtKuKW##Xp7iSf+>`&MxcvXV zCw4Z5|0f{i_CM0}?f;6){nb0>=mWD`3tB;9Y=HmT{L#XRA2)&HbQK2${;MF%lu1Vx zv7{8MI`6$Jkf4c0QZ*1^!>JeEjm2Uo1K1Wpo}NBq#?Xs38_IklXYlpo=QQx*+KuP) zm?<)G_rQ%<%`2bj;tr28pNS_4X%sNdo3m?o)*^!m>>S{*LHf^-wq|yN<>W1o68M_j zNJj3BWW3N(IIJFpk1(r8pS#Oq$Sj3kOCl&h=1eS(k9b5he2XRIR)J?55{Y_?DaIdF zk4P$@^82$2(`7FkRfZW0$M|2TQ)tQs30bHQQXibexq87oivs394cfvnja^0MTdBop z@UIEmzXa02@Rj5PY6BkA`Mut}gs4)^wA!Ne)w|+3xi=~wTR~OTTSvVCH@qLI9hhg? zIRTolIa$>|HpY(^@5HXL$uKb{VQs6fXdma_+Z8!&tpJxAL&%vKqCSyKr9T&N?ukk_Ka6R~%fs5%Qwh?zWCW8c7zEZx#vAhu(Fvk+RSzX+ zyHz{{Yv(nQ7=9_#2LvZ)nM|F_0qm5f8BpA(5}vXMT?`{{tE2V*1?3>$Xk1rFKnx39fKeyeuZ+_1n{m3hhahbmU&HiR8{~; zz=-0HT=Vp4H8@C!+hn^KL!G;mMo|IWH(m{-b}qkKGmf*RYOx`tt0Xz2l>udcE5(*K=}^ZNqm%W5d+A^%9)v4jor)?^+HU zVs+IRYP!w6+Zq;JFgJLsHc%&^ENWbp7W@tCrG^Bf?Z|I zSzh30*O$eML1)mh=k>5h-j;>hRi4tbNp3Yj8=)R_U3FoR$ht{8mv5R#P~LVb31~0F z-%s<0&^I&^C92h6%H9Yks&S%({Fkb3H*q+a^N=(!d=i$E2L>gBsa~ zi#-P553qAQ(>yww2P3pNJQzI5e67X_O7vu#V{S{P^DAgPxnI z7mM9J>vp%)3S^wA?J%dLOHjBn*?@abRp79rv9uogY)ykSGu;Q9KPw-YT;}+jg=%0a zp#9*{>KaTdT$^ObHOwIUFWWz1#fe9su;0gZh?%+m31Q=JHXYeri%qKE?Ndrr=lR!F z3;=6BY-+EEa0{`w?&7Q7>%_;Nf3@Igt-MMKU-&2Ti2?ww4$n-&ie&^230Kgm+kq&+ zHv-^1N`umD51Om1-Tl{JTm8Nh*pH!&;vcdFt3b(+UIE}Chd(`&AE;b^ z5I7f>lYP-lOW|VVH*VNb4KCPh&Vpeh&$3Z#5q#Kdej_a5;O~-i0~B>~FG2rJk*oCe zJtX8nr8bp_v>>U9Sy3&c>*M9n(Z)N)A?MzsWBI$9^wj#ki#7}PKBVv$X%GF@SKa>x zAu;}^4+-e7$M~Q6`oA9yp-riCUE+jt4 z;0Vb5u*IrWE&5;-Wo@VNE{N0}Q5Yt)g_PIb=kz~G8Vw<2@6TJGqb&g#&T>lE==fNi z8bq1+96wm?%i4U?{i_1`%R3Iwqx>oX^>`n!n;hy+&~pT8sD10fgViTHDqC|1xDRu5 ztuiX^33l5eRoSKP#E*r|@kJJyC!isnt&-B$YR6I#MZaE!wE*!=3ZE}_awVOrIYbZX4-hwq>bue1I(F7 zTfcdjicGYTqQRol zL|A|d_bA^JqfSVhWG-<&7Ru8O{LUu+#dgJf$F}V#(R&?>H|0Q;P2f~aLSK=kyd`u$ ziDWx;O|}2Y)9_ftr3{WqikK08kS4*D@-DDmt_fzGTiMviR1v?UG5#+o;Qx(ml41Ql zp#_5vdH-`l|Cd8)|L24n|GxiiENuU)F~5C;SA+qKhnbq@$zJbX@b`f;&QnV)C&%s$ zb+P5_XGwI>*pN??&;iWL2xF4YvPe&FZE5 zn-yz>?0$R2BaR2$jwd33$96~Q(9<_q?(LBb`JeukM zt~Cj+OQlgRmdh%H00+N-+Q)W2OFwYh3H_V$9MFTie5dD?zY;(mw?hQ`N;eE_%fT&u z!R)?wm=GkwN7|*}n-bu^z!QhUh@tTSIAsLJgJ(kVu&e;JdS=BRlK4jHZ(X6Svp03# z`Dm`rSJnwQ#_qfX$RlPzj!iQwBNj+-2E%{@aPf1>1MbJbyaUWBZJh2qwHZ%P7L-q` zg?F~nCksdY6dHKQFFPs#ZP(%LEX&ycM*(2%Qe3i2_(aO@v(;P&7KgBIt;AxA&wTSx5sT52@>+e8 zKa1CPPagMtz2_}`Bm!a@ezUgL+1Wq-vT_U@c-UB|L<}yEz8>(*6xyfbG?VcL zsc$*0Pb~uF>K3}#xLB$!fW*p5jEr}03HQq2IkV8?uC4if&oLq@(d}FKYdhXSR^XM^ z*wP}+Q8*qAZGSUz%1;3+4wzE#*~(bawSAlKL+d0Prs-SBJkSyQRNhd8 zRthSpZL0(?z~q%-z3njc_Klr~x7=!li2Kvz{)P8VqgBd*Kd!z-JRF3340!)A0!waW z#8doJUYnNM*et)3sqap(-K5}fEsD7RO@p&6IS5}&gL1|74KY{Qu~^#Y8+=4X0J{vP=b$s2 zx98T)_oI;lir)aUE0TyKyl{5Bza=d@p$_|)E+Ovf7uNFu5aIJ%IZ^+5RIIkp z*3oGZUVZjXG{(i>#b_yPSw{k$U5?!}f_m@Zc%6fyyop2WbkKgi(OU{Z*s9K?4=Kjq z(?)O8*hF@N`CW&nFmvlW@u^c9p^J>d?b%)>G$JCUR6of=Ncm^JR|+^yc*6oP+#GHa zTwoB!4-X{Gku5x#rtR}?T;V?t*7}EWPlt!7&pH2kFnS@%)jt3pqf7!0t)o4k2EX=V zj#sw|3Ued@6}vd)s#k(3j(ZDknwza`qpZxwT8n^wK=bu+m1*krW@ruX%vmt{`1^2M z@z@fxNV+cxSJ*-rRXDuiXc*h(z!jARWxRr1upCl}96tf6yNe#S={yt_CIRMv4A;-p z&YHnrLj*paQFQFBJ*pf&$+j+R@m7)v-suq$EV(uT<&=&pO?<;hK6(yuF`NsZnm~|-7xb#yihl4w@c** zHTAUEXzk#Ph@7)DWP~LB;z=pq5$eE-pliCVOF;DG*}n%^ma;>>m5< zjeO50&(m)xNHU;__GYh)!xFRc@8Uf_++#04hc1Y~x1VH}k;NNj>&0pnnQCmMZdvK6 zXe+rcHw3|zC>AOwjn4y(o_USyGf4`Pm2$I|0Z5p$#HF*C zkVmT2%|y25BsE-x7j$ce$|3EGRY{S155ve!yJNT=-NIhRS7d@ZYEWLQql>C_x{xec z?5Tlt6%t;#vv=Uh2H+Auh`SvV{x?w^*gvmyP|%B zOQie|Iw2W(y`N8fM{_wDy>!yrcFPqDv~PK_*h0$%k6oO$YP+R+uW>k8$6d!ChD40e zJRP^YMZ5i4bqdzMW9cel{8YWjSnbH)9JaJ>l`fn5z<#Ss%!H_037fo?;CJSsCi%_= z2^Qu+QMLKJ+)$63bj4dn4`eI9>e-^fh+v4ZoC<2`QQr)b;~6Jly+6H9N$5AQcU$ z+qP4411d71$SD7yOp8H1u#h(slMhMC2DGHe98W}1$-+BB*oYxyXd{d}9X-+Nbs1rW zHMX%qnlu9#ZMVEI-~QBr2aJHv=8R{zza0lhjzu^QN5VLw;h@B} z{KDY9lgHPuz!Ubl8IAxI7AB|p;i?ZtysZgUR|W=nrnp>?*et0}lpcV`hjO!GxR~V3 zz$b`2RovSxHPyOp_09hfTPp?uBwkjiS6C+?DYg@&1u&1SPqr=^E0M2s=sk)b;<=An z#Q$+a%?5(UR`|o_CNeyQ9pdukB!W$HAslxH<3hrU$ZA$F>#Z%^v`+&z!r$Xe*2?KP*{7kwt z)JfoIM2XO2ZIr^al!c2s5&=QUKy#=(#kP&|H=KoBM<PT( zyj9dE&D>NJB?0$qFq>s_ErqlW{c6%oAlO919>@cQ>|MhO6#^XKk<{Y&(p;KZf587X zujCbR9z!57gfun|>IhrsMa-yZTLY>sbX4Nvyrxv!nu(8Q!L&k77J-GxOrc5R2AIPL;#v^@p3GQe#xjw&Qpu?5y(c-MNet2R9UFX+Mca%UvwL#Q9U@syD&OZ-ON+ zhGs@f@aHvIJrxwX6TCU?!o@8gal5shHsOfY?s6i{*pj zQ8}KHJ)DM4iG%fB9B~lFnGiY2no%~R)m&3Pw37B5OTpFiQOAyhA^2FYAr7I!B-yW| zeNu}W55UTrgAB@0AS5Fi-AERK6Js7)R6gn(qn{oWwPt1%Huey{_?6WgRM!%vJ@ zy55kCa+A~{Bo880frMcGfI>&UXIBsgWNn?mYAaN>tmN~$gCo2u-7-G)b(RuTPU}W% z=!$!oaVGJDP!5jxUIk9cy@x(Hn+eV9xAMl%cw2jFMexS~5u9UgX7Pf&t z6j|>HF+nmvRimVviL6g?MrT1Wpmm!v8c++LiNcND9Rwkb2SVAwEIDEX`k0O;ZJ$0; zkmATG3nK4Th$9Ok6r#ov_p)E@K^BHYo*+kauj}E7X=mOf(W^m}qn^uLGH%Qzo9jbJ zgDZcs3i+PovN2#McAzGqn>}B|E&i2{e#2b_*mU8g##|%3ZBSN_W| zwr7UvHy%t8>oJUwlz$0r1}6Bw+cS|%BtWl%wEkY<8T;A9ARj-rIfnxl1!zgb%X0(4z`m_t0JRA4W{L~7c>F_ zmK0Fn%bCBVdxw4{%Rd^rw#a0G*w*QrXRxrwy#gQ4bV(%%e74cDfOtX24Bx)lo~VC~ zi^_W$_{4etN&$3QQ>lK|x@q`Pf-z9=reAeD4BE#kxc@M}Ov)b;HwEs@`rEWrS z1ORaW(`BiZh6EG*l~AZ$Bn6{c;WEb1-=P*pYGt; zy*uR(HgVew`-)>GW_~9)yj7;1)5)D9H7>O-Pcfj$0>QQP!Crcft}{d&So?4sxV$gIo~7iNR&lI`&A{ zes^B*NfG^W&O~|dULwlDKLrX;daMZ{1;9wcH^khhHKzaK376lW6Fua=cmjoW^y>dB zo^WzD{vSLM6(WuV_`5SV&y#`Ky=d9G_eN5YfrL2D)!B_TwyOx>h%5#U@b>&ttOV*u zF&95h*y3jW(W}nn1W0oI(3e6Wus-WqS_$A)WpfL^No0hB58l6!1cBkNNqw+2gu054}9}v$E{z)KgiYJCk z`r3@?g9|;V-A}Xt>RIs?32Ti^Jre$_UB+GS6Lu7q_Qa%%q*X0hPOC%!!EQ~o@A>_f zUiP|^SY}seRG0U@GXN=c8%2R|RK|0maZ9%A_KcBk1q3^;0#hPLdS>|B76@z{ND#sK zbTorjkY-Wi0V%m+2($w$(+}_>lyQMz3ChfoD{n}4u!qAq^s<`uBDH&%2aInpO=?i- zZNd(G8Z7kyyWK#(Kj`mXqGqy&K+Wiy@=et6C1g_X8SJCTj6VZum+v-X@SbpW+ zsxl*sP-NiPA3@tNdBDqt^Qzglp>95h9iq0h4|kzR1%H>R;NtN;S4cbp#sM?XFQp^7 z$F@ntO95ZO%1Y#DtM(ucME5*JY;Y>KAG6&T{I7pY_B-p_9m`d^mA1oFFR*7zs~Z#W z7t@d85B-tT^z%?v%+T;vH1_N$o{QhlSad>67$^?;8nWpBcB$7(VL`g~$PIRcfE-;X zjrHhL+-r}=AcD&mdm`ATHMc0~2Y0~9XA9}ggdxf){RQF@o_;nzvY#vN_E2>L53Q9i$JgQG&H#)ff|UE`jW&A=Ud<1%^ul z$N-1HPs!%RU6)`MgC+$Qqui17*&M$B$%Y0X)`<(9N~5ubYQA3`gcdVbAw`G zyqJ}wWGhFI@NbRMN|mFJp6>Q7Cg*j?6yo9Nh*a8LlQ$|MjJ}=5qJ7O7IwouR%;X1& zuL2Ah4A8}3)&H0V;21zu0i68~-I**dA{1D>Lu!qZDpGLa^F=sMkf=B8^#e@|O!e#h zK0Xtz;eZi_!19H z^C{4{C1rg{0?q_uwLL2d;4Ym^t)_^*Z+rG&II`7~(|T7{NoN|`3C(|7(b}gimopvo zmBdt7hO@;YpuGG7BY6=q3hV$V9)}6WF4APtHMStIfn?-3=P26x##Xw{%;H5#nCR&d zX+&f_PUeJ@Zsfd=zSRVuiG#R=a0gGAc(aOlp~xjJe*~jSg9{mr^THG)hsjon*&snZ zY0xxwFpsjzkCfLYIbsE{;1z_>d+dA92CFK|+d z;Ukhg!h)LT#jjYvzBBlT1X-i7>a+9L){EXBcB=_H!D1*I`uIIXQj83aV)LYv21Haj zv?ndW_m@+KdOFv&zsgwgG{2<=e90*Yq@COCu1J8gKb=s^p~>NDxt{+>uF7sxApgT> zt=8x?O-IU7^g&Jp1!sWjZ%;iUe;|a3e1Mv0#89?TNqy>(fHZ_s%Ii&Q5)dlO&SaI^ zrc`=WEN^I8A``YC-hc+p?|P*3f|@iXM#PU}qA6mvl8jvKc5}=c1zJkkU<+gei}CJty3rlAIpvsDBc97fDVQW8U+HU$ zBx%=qb;m{#p6uKNzGKCdDp5SYGWkg?1O~)YPME-BsZUzjLvM;Q#2;N-5)4}-!JhX$ zt8m4H3xoC{RNLuMXZl%H9@bxf=Pi?^#pux5kIOpeBQKhy2wTh5&{V2}lXv_E7KpX3(jGCR@*^m4)2E`sNySabNcR5PhM zJh5Vp_3=8f)GFuQKyZ{5=}8u~*{NVPC#0b@%g&;>fdSVyqjP9Is~E4)SS26etvaaD zXA2M2U?CHot(9UQypmvpnXvz-nml++1{Qzjm`+Q%X0zmNmf0}h#GP-S(Ua_1fGpVMiCUW{^TP4QbL+HV1P4BEdD&jRGb8-QWdzkOknnE znP;YM$=rBP0*QUSK|(BrByc9;)983pD#wa1fkWv5(Yir%bZD}67N4>ULBxuuqC8!n zryd*W)BTc+Q9kdXDF9)O>0vHq+|^<>U^$7$!&#M1lQBoi;v20Zo`pDJc#525YXSVnH#FSG2kfZ*-7~Ju)L*p0d4|^y z1k%Qyp0M!X^wa)*8q7on$WwvHzy!-6 zTcExv41Mx*l4PwI7sw=fb?7FI7@dNXXKwj>Tn6dk5!DT5Tsg#=d*4Ng>wDjrMCoy$ zq>FC3f*$)yHKTr?9=W%3oexhxFFyPe^ynNPi6*R0NN^nP2>RrZqt6`Y-{C`JB^EHlbPgQ@S zOA=*k{IA~TvAkk(V7N?lKJi5SKaujKCW$R^E;~`lp4Hv54*3kpyhHa%Nh^&!O6~@l zY55t%<3N$0Jh^hA97@srC}5YYdqSZ^NjXu9KN5sC0KCng7k8_v2{V?y{zWyHSMpf> zEG|Y|m^IEu2tdq+ovJ2tuoj;Ot&du=+e%++&1z=CZW=^lVNj+3dksV7mL!|$@zKhC zacWzoBZlzF0j6!?!9~aLX$@#7bk`cwyQ>AV!0DRwIwr9;EU>9zVmX4jJ(JQ!rnf(@ zm$hcad1SFWWfRK9MNPw<`Ke9Al7SxI{?WN+_51d!FwsB1?Qr;*nGe1Vg)567WXWn| z(QrltkbUWJCSbeKr})k*R;Gg_G>?JjlxL2ZcsEC6Um*SiYoirm-@oi$V#`->(SPUw zEkjcYv|Yue`mtCTXkiVB$NvY6+_BdC$Rc9e8o(n?lK5&CKkXn&EF9|1Jz4PT#S=KY zfIuqf@kG>6s3F_|KFmCIkv+8!G2Hm7FMv1qThXV7V)B>3h~6$`u?18&XQ)J~GB}07 z#CUM!iP!ntcsX}FB#gJi7h<-FQuDZU{h>(H{O?CS>+b~F4c_mg9xGG&|D}%px1OW3 zfs>=re;)O}#yD)h0>ZE7n6lhc4CIXu;%`N2At3_Oi~midXswzi=5%6rlK}+?2I3^dK>h@*{_>&%lkE3 zbmHVNleV3+UgG6(@PQvSOIG99UnG64|FG}$;j`igYBUJ>6xnWQA30_pCQ9Y zGF}Zui>lioMX-NPV@;C0c%uRo7-^o;zd9Mp`ug0l@>&6j+zvKGaY^QqDDWL)t?riX z^thlq7nM15R}^I0q>$t73FH_uHcWKW@MIZg&}j?63KL>_sW1O2%H2dBpLI9hC~_uOBkvbFZTBho^KldfS@0u9 z;CeJn%dq&to8muA{3ys@);uAIt0Z=WzifuX`HZG3YngQV@8I|;Q|x^0Mo8?h4=?TO zjm5joThGZJE@)brVly1lOIvA~iP`88)XY1_h$GiT0?A}%^qXnW7j?jN@(DiqIVgQ< zx+*S|xB2{qgHV->z_tH$CAuzNbbs(_3bz%FCh7Jkh(so4%{8AnKF!@n5R~*%@;%5A zqO_k1a?6HXZ60tSAus=eU;TsCTN15Rw1e|>|AXB%`Xj_1$1?N-7UrNymEtFwo;iLx0o*cof3ALR}2s_rgm8@JQKzx}U4txD%b9~p@3?43GO>A!YnczSx|uT`sm zX6vrMkEfSesio~hD-T_9E#Pv+oPc1+ZOjdeRY#!5*Q6F!DZ6d~`GlYVlFkkUOcu(l zf~%+e!%|nA`K9XQ_&2PWY8s(^IPQJ5zvD#zuyY*+eBS^ZW-f3BmC=S0$xyT5ijkov z_kG2jxn*Tj`{d$KRx&c#+StMYKfVM~DrMwXM9tPY50RZ^h`Q^{8G5GUEy^kYQ4MJY z-C$1E;KvtM>~D(OhjrU|->|DBG~6%zg`;pwMv}D1oGIonJOqjrKN(`FSO2n0DL;fP z2-^On1Ib@8K7BTVne9ALbx6q8Q?+>tGo3Wnu}tk%M#c^cYB4V5B-Yn_0zM19d&9Jp zDNWn=UP}bmS??LdxyNUU2olyu=u!<{O)EH0rG!R-oGG-WB;Hg6Yc`p#cJD0;`OIoH z5hlYC4Q;A!5$7G=!V+L!1%u$jKD7EuW{p_&1VSc=_CNKzeS)l!CE7 zNNypQ9AQ>{TT!t`5PqYPQ0TPsj{O&A2-v9(-I7B}61u&tOnk`t($?v!JQ5VLxQ(A8 z!_a=a5^q5+J^&yH^Z}m_Tb(5(Bg%ZYrjh|!^G*kl1}gUszoTi17rF#L#no;XUrdy5 zHHjdL1BocilAoP8XQxDC-88l#pU#&hAEVp@fSDf>sc2GLgyb%5gvhyzWcN5C&C8+e zTr6G&OSc$a@2>($-$VLxpYX8QIy5otD8D?qqbpkTJQ_lFV%AxuA#Muanvp4ydyO+W zw9SDD<)^Pi@o-Bnge4(s@;|wj0BnxGIJA+k%x&rfGTCD=wM27WSV+`@`xG5}*3Tc1 zpQ4|%MdoL9St`8zqL7Z*w1Y%e)CKY7w^QOsz(($PH51_!d(S*OhRPQfb6X9J+ z>IoQU7zJI~f+zNHRqhrDGX8w7-34P7#T$T9QvU*^88L%%>nmWU3TgGWQ006~kyoJP zBlB~EOsaiWxh_9!DiatUrb1*I?X63ib02;36;p-sBlQqn z+>_Q-CJ22u_v-5+%e4AAdah51^eq`7NU=UjCaqUTp{GesqpMR&l&nOjpzIMgi%Va!sFGG;jhpmUYojHmr9i9o~c0NYi=n$ zvC{kwW(vU7lpVwUeXCG)`;*7l1otw@ZDqPtm=s-Mo-(6Q`Rc*$&YrUY@=w4#70VOy zhjwGQUK||}8WmaYLqnOLBt+8{@cFyY)eJE~Vx{m(N~D%-7xxT{t~#E!LZ83QIhn)tMXzEWMdTq;XuQC*R3**co2His0s@9P$XqB< zRx%_M9f|RX8@SDf?9qr@Go;XSR3z$+`xe<$F|B^1wT>s0h9_mVK8b*6^%j8nG^dgz~XXi~1Bh}KsQ{w==Ul>Tjdu6VT2 zxl2EL+IGwmOsj%U3P%iy-Gp8ZIB*Lt6!OXzX1ii_#4@s0 zOc%4o?S=ju*N1JhoD1@{1R})s21d^6hRcM%yVo`KB^5LtE{9KCg*qFp1fY#=2c@|l zVy^@Er#-RkMkfrmKc*m5u#ANKVmN#mWH+bGBk@8ep zjh2nF**kW<2mX^Ce!*@GF@_x&-_^BtJ63x@{>~!c%Huaz8GsWS7661TcNa{ou`x0! zTxtTv)&t44t=mmzcGR07-<*}9p7j3=`8a;}g&V&fD%+fVUk9FG@`4qOi*RsOb0T+K zG=IQS)Jz#$%;HA7lJ+rcf+*ITOQ#u>;jW?aDv1BwZLIOaB$3L)UIbqW-f1_kjN-oD zKfJYfXdcX#!0IDmc#S53ha}@89!n#xDU9!rFO1V7o4L_5Hn!dgBKL%AW*<{Y@&OzU zArrZ!xDU#|Nm9nbWknkli^_iC7j%sp+QNTsOg|?jb*`?&&@f)*IvTK~FpLCj`zs`^ z+xo7Qr@MNoYqb_`21p@ndawzxo@00qosy~fE-JDq?cZ*WG5viaT@HBX&QbX$dnl{^ zK~i#a?L`UgNiDEiKyKtpjYVwjV0NRNGvS%T+s=+ORvJZUbp2eg&d-^_L+9+t?k+Sm zNw2|WxfKqYTKZ`F9o zN>r6~5VF6ay{d6idrYiS>uGh`e!*<{MhNO-|u2F<=2?57{;AL8Yq7c8e#_7N=_jVSD+} z+iVmJ_JF%x+(2_?MD{3aSHGQa$4ISOqh~1TE&Bsf$I~@byW(C$Gjg`zH2;eCqf?P;Q9r z!H2rY_guCILgg0Te@@MK4c8L>TT2+jbcAgyFspld`+)6vrggN;I2Z)@7-|pOBiqWk zE-1T!HgrfO5UzOM$X@ff%MW<~NSOn|6FtY7acw=tO2`bX`}oksRDA#U zO|Rp#p%vOa804!L;=>}VtGLqubEJT@^cP*L$W52|n^PMK`}@r?bR1@FhtV2{TwsEj z`Z~{N)-}^k(E4c}VMCrUM1Vt_yL|n?T!cTe$kvSKJKGO8);Bj-zSuN&?_at#+ou+5 z)M!BUZ>}#c-=9in+uM6ToS+Y54S}fqk^`%ipJmeom@*>Tx~O2@QRKu=e0Z zk3`;iTwB6D3c6IqBb#K*0zN5wF^1NAy;(?=D}~f^HOPs{AvuMh99$S1E3f);6B5 zZtTx(ZZPaP5xX@x*#x5u0+U6DF63zF-vJ2agv|i}pONnnG2jg2gE4XvKry}Lj;&;< zcM!Ld0I&)EG8AFw1E5`j#}4!PbJA$EILI#f^IFWHG7yQvUhU@*YY6Ucs5rRj7Y`(9 zxw~k@MI>a9pOmmRm2Md>3vz81{7wU{TZohmCrB2#mvDh+{V4o|h4vJrHwVyKt%CaA zuoK7M>~>J$gH+axXDdI}h33%!B4}ST&E`6)6LB#$3R%#MD zf2rnyB@Ft1x)0!~=4pyRTT)4&O1mJ|0S+W9*T|tbdn52X?$H~K)O}hCwSm3K)zHgU+xlC2>uRDtH?_RD@0Np;BGaVHyk&mkvYBsIWDf>4ZxkuoPzu|H8tnw zcuZ37h1w3d7L8f>!j}*0rNcI@dkmM1?pDU|$pmfz(~PE{izw1~Y7kC0XX@1=C!3J{ zv~Gj!aa+ANU$^&21;79$I$ww}3rO&!a8EIHr>k6hh>Tl>&Y| z$0L4XIUG5*6W+T(&{wRSPq%_lcEI?$LpYSMOa3@oFz|v%XpWJ&QHG)9b)^&6)!G(S_-_YpMi!6Jls|HC1!CH0nUkE-bf{aJaqjJqELga$%x+OzCEi zZ6=zj*T2A(2|$rKVwI$HT^o{e2(=igr$|AVx(Uc^y^!pz#)&_FF4hhA^vH5I*s!+} zVZUxe6Tb;U3V-hV>dExo62iz;73UD?-2|&?ycjexToghHbQ*|m5U_Wb2jp@oep_+t zr1$H0rd&{_5I_ND+x*<%yweeqR}Dr`xgmpWA-fE=p#{c{)xe%FTlBQAF2H3l$NO1p zG+45D=wNx-S|m56R`^N`dzC#zh-hxeTq1LbDmNnV#;pfQ`^%^AEGXp|Kmm7gyJJ*b zf$1-ZUa0=It;ueQ(#GrMU=fZvOgSASX%?FskVnXSp|?dVMldu}au$xNG5X(_!Nu!%?I< z(k}WErKK9|04F0F>!6RwFc09uXwL&~)V^&ov?Rgq5|3q5Dy6$g(erw}0IfFp62H9o zz~|Jk5MPM{ZxQa?_*ASbUT9@!XXSVCB6WWkf?RXT#7eRrbP_sDu1af1mVwRVdk#`) zkOZGf2Q-}B$ieh&exbVQFgcJ&VStcX(w~qzS)6nXJ=8%o;P%K_Xa%#VYlX=MD_s>J zjyrjW4hsvK`l_xtB7_ZxDOjqvUQvZH5YCz16UtzHZ(8>N39ujJg%KwQ#Uv$^5X1<%J5L`tSx#h<@cmys=KbKD{Ly7wKsERWK2+mc3(2*lBXA=ak5D{%W z0_2`9)y!abM=8HTIgVFV<&R06>wF9?d`cFTo) zCF-`8`hkgDZH*q_Wdk|Nvycyt+S77l!%X;4rsE`9ypmwd-3o2)a0y`;?NVNo@fWdq z&T z-jm1>L>RzE;{f8vT&>RqSRj~;8v_f*JA^wtS9P)&tYf1PtfT{gmPm$lKJSy_Ax4BB zb+T}x56uWDiB5N1N0qtPQdCXzv!2yi=do=Xn~KH}gc;hMxv4a@okTfcTniDqvJXfc z8#W#W^_((?5zPL*P-NpJ`OjqBu~~P{Ow$~RoM`1%n( zz{_Y`rN*jVL9}szsCz!{-Aac@i_NjON!-4*t(M5;6@I|LyAKFi#k7!0}XJju*O zcq1u}s`^Oy(}(y@9>tRmhiACS`q!K)6DVU?6}4%ZuX0M+0FMOz~}6|)VExw$Y>9?_(+sWoyzrdD_=&SZE z?46$ET8L(Eea^d#>K#f_Lye;)lnHLAEjF7dbq5^Wz#ULQL{)B}DDWOBrT5*P-P)#AVCraoCrqm`(n8Lom^=U1e`OP%AJ{*OZ z6I2)sn_w=lZo#pFHH21fD!J3!Q`2}n-v_|Tlv^8>WPwYO60B1O{|UcLpKtNC9fj|T zwo7L~rAeq$_d@cOQaz|NDq6&MN~>M<@w@LRK={=;K<{B;Oh-~b zks`|V^5_~bvOhyPVcyz$h#J`m}U(l8h<09hW;}U{FCJe2!)@e zW#`L7k$JFry9ujADPi?Tnkpoc7NzY#HgpCKiCmgSRJj(}at3A|dcL(Ym>nA9l8MZp zp+`I?p1bVG4iGxX7u$WpV*jseD!wtKGQciqcLjiD!Y@;O)9V&=y(OOen zKOf9W6w?AAQ!-~^Ln+w{00_$hdPw+1rX0YXj|C~bNWv^Y=HHE|#&tc8@f&s!Z`BqU zCJF(Inp1TV4B6XhM1kU(WMrttlIqI9wCVzs9`Iv$%KX*T{7E`ExPE~Vq0g_#K+V;} zZ|PnTe^>oaJj)L}%U1;(t)V@~q)1hFXqL^~5ufl+i*9E+(kj`UI|*#NhMcU(OFi2T z`_vprQDXSK-J*SdLR&Tr0dRQf-KIl&bZEB$5jpRTjfuLcu@>QLEu>rx%bSdg|$a%WT$BN9WfvsYlH;4J^DRbL0W{ zk?I9QDD-gUAVv8zUQznlw4kg#0vVf68zIMN*Lx#BZzc>Uifh=42m+i&g~z}S-gr!U zn!{^tk#WGOAEv4d6(#WTlaLX_k<}3aCfYoJ_9r?LXW($&L?o)B?>5B+*i5+HCVi0F zJ;|vlZx?|A}6X2fYC+; z@a?ViQ2>ys^jRTa$JMf&w37RvO(KHhVDU*9oRE~Mf7kfOgOS)~h6MJeo_(C0?eBk_ zLmB^I9|^;4&)00GtNZBL=H|La(Tg;ZeJInbY5k;5K|2^KyR5GRFT1R&zRj8tx%v4C zMOYH05P6M#-#5nh@s^@YQnTe|Xt7v%y6k@Kn9O3v>9SoxU_4#DKP;c z*fVO;hFeLLUTIj%KiW&H5%N8mb;u_fuvGxg&F8oH0R!}mnEa}^C=!t3r0B=n*viMV zLDhoh2cTv83@3{!w-7bh+gzc`P)L8OLt@JNb2VxnEh@gfc>e}87f-~41VH6Hhb{&4 zsewJww9g)rgqWzID6(HXDFlV1(3?c+uD=scZ3^Z%)9;o$Dg)(?K%7BZy;s>!pzarO zK->%Xmjmq9h+YaYkzn4;ylA4@V@Ed^|440=n_0qScp1`oG zN57Q87&$6^D@ldvn^_uE>5wHm)hTncG*9@*Vi<(J18%97&Mu4LmJ}xE|IEY*A6uVLML25^d54<)nPJmmhSJbr32ESbNGEi=2l;XY zFO#ZIV7(WJ5jWUSA#k4m2-nbPS^8r_){l>`*Qp=OzfaK5KG^-DKBDy$GA_RXMDf<`bqPfAQb#zgV z3`gb&z$Nw8GM--o^BS}hrJq6HdA-m^{crdOt-wx$mGaAkH1z!KS+OVETaM z0rZ2f7eiZVA7GGt9g2~w9iQI|0pAy)GdzCz^Nr_+e1}u-O!FiWq41og>RGJcD8^n< zRz<_ks>CW~kkolag`OZ9$t8;r4nr!ylWZ!_j!tm14Z_}$%uhj2Xd0}DLs5=R82m)B zBA#_YJ6;)+qaAVF_}Sf^U1(hqu;j}e!(E$5`rbTEieKSR)=9qz$*oY3*}Z6Yda+N%^G`f1 z{nFA$XR3dQr_C?fbiW9JWUt+fJ^r8{fS;D1W-T=aharFei{+dIq^l^y04?jMs*rM+ z0#G{;@afYOhA9qVzkm?&P-jpz`BY@0E}bN_o*QKuPAYqVxxQ;QPJq?7BC% zEYMDYN8QW(PoIG0Nf7(TpWn>~%ijLV_vOWRCb3tp$M^|arS5M(^xw<4t(f~-`1%^I z{H9Nr=#bJY{{btyP%N8w;TsO~&C&Rhz3I)Z)lbqn+3ow8hgwsTt((JAQF*F*)RCeXjZd{Upob=u8IXTo_On)JZ~9wRPD_%Yas#WC)CZnfX5_mfy36=@%;X*Sc`QV&>GEk(;|RVK2oYd4fQ z3A&(QEnh2FO0I5gt$sVJpdjb@K5JXsdt}`}Wo#4^tf)K2$|s5`&q1?%GL5zK8AeM9 z%_G&FQJc|uk)@ER3uGSCO-p`#Rf88Sv#;EZUSJdRDFi-K@f8z>?|2bjfyZx$F~hUy z5FlEYU!<`n7(X@j+ls*Lwdk|!pS1&Q>A+6pByDT$qJZN% zs$C~881HF)LQa<`+)^w}T0U*;x$1S>ujn|nU{9BjK*`L_x#(sOo$TC{Kn{a5#b+K2 z@z&xSfWI7QAX0c>L;>WdAJme&{LZ0d8J}udNw?_HD!UMK?NWDO>-kob5bQxx{`h`R z9y}Zs&-Sj-R~WFfO!5(xs*G|K@GkRkh2sAhD6-e68gfgk(=ajIr|@P@?(yBko-=68 z9X#-lPKe81-hS|Of6!+&?oU?^F`4#d(J>S7)Ug^@ew`BRX6!a2BS%9TfIupZA_NYHFnVvvT{D@%gh(hU5Sa7 zYjWJS%gDg8H>?T+P^F_VwJz0dC7#HmPKbnfmJ||^DL>_Mxk;X+%^D&e^0rN93@k3m z#d+ymiw>|PhyZg!4HQlPmSSLdSem+r`(37|rWV-NwK}X7wAriAZLn`G-;q|9+HE{p zU~o4ExX@gwH+~FXqc1@PAQl7-+6O?Q5{8L`UeUcIiI-}{_7E?Tw-H=X)l%0`S8EO{ zQs(iY1Y>7rWm!6(r_kBmf*%{}hF@R6^H*(OnYQ~dj%1MQA6`a3G~#T3wP`&BlXtSg zWwrEH$C6q-0az$DnoBB=mCu_R+$gzd;EuC|WLF=VX4C94K#yL?3k0N8RaAeGvc0bMFgo$^ zZmO~T`E)qHYbrpjBMCp1ew~mh!maaUIK6*0Q$77-2q65ZCNtNXK4FrR1oZUKe7h!0 zwbC&BU+_u=yGSON{rZUdRqT46AqoRPGhqip%4MNs0)|C(`8YMi zOkca}eqf#=jUs1U7jIApiV%eCQ|8T(G8}Ee=G*V=$)6rpAzC6r0U)MJ_*a%ISo@_? zUkwH)`SUe!#>0Cv9t{C)1}F^v@?}8|`;9CxvG)9Pj{)!tVpA7a49wOv$Xc#^s@m7s z9kP9ZU_q|31QvOo-^T0>GO`?UHnRl=OgSE)oB%6*@(wzuXDFK4&}jhf9B9?vxO}{% zOW!sjJd*yhPEW5$QsQPqkGG}@PY{)n zP~tI2e-xt3jLIM`DCtN&7*WZNeCIH)d3{g$pKhDm@ZF6u1FcVp~y48bBMFqG2IwWC>jMHA?DqU-<)LTv~W3 zx;i1XhFz|QZc{=E(Y8i1aR6aTL9F63GaxLOiUJ~~GD`{00AF(%hlO6(=7!fQ@hMTu z(qX$S&Uc>G*LPco4gFUK@fC=A26#jS`OdJXfN0~m43%c z)1k8sKYLIScZl;Qp#2i7!FlOEO35M{#gn?;6+|rDSeXG9=M=;B9HVpEqU|4|Ii8eB z$WT8__4ezL?tR$>?qm+#;7o`9KmMjeh0Wp0y?*k+9jnbBBv&YPq@P9+MaZ-yZSniXRIva_Bq~*;YBrNeSZb ztmo(QK_W!O%|S3fazMf;X9IFfg~MqF6!Qof>~v{4)Z~2~OBj>qTs53s3IgFq_zg*& zOMS$02SB9`6J%8`RwHfN9IoKuXXTaBErs?T0(QkN|CuH}AqrQ)eX&iM66gcw-Eb(O z)4=19cc_Ol_y@HHLY#;5|FJ5hQ%}Kw9% zM_Pf3h(#zdle_nDohr&{8aNov6o7-q@vYL$lZjayqeKKw0M|h#(3f>^g2{M5sP<>o z%`Pzti=3`SaTCx!~%>&!H zV5as$VV`i>QzmW0bFr#^toc5!ec%<{Ra*YNsXZ;$B_lkg)#LDVY_Yzbs_HG@@$Q(E zeDfh4_j}{0>Qv>^EIMy?f|Z&=sSJe!c|Lws(g)nzcr%gZ8|yO<;*dJAx=KWDrRdR` z@0a|K-%?S5Z+%CEKF!Hun3g`Y0U-?y)}si2IEzD{Rja_rB`pjs`xMKG#EZfnWV5X30moetvQDv;>l+`HM&VxID3cPKO5swtH`}P4E z>K+^P@299m3@u6cg;@P4JNpkbaLj4#L}lai#OUxa+vnzg?8MjB?TpucD zplhPCUW&4#PLozW0fL+kSKJ{&b#wB^Jg*)jEX`>UfyHoz1cb2Vl0M7g#|S8K3a;=c zP6n?9WnX51nHx>3u0iYzO%Ur3EX|+}4^1;SB*lWz&Q)>z1M}b?4i*y(NlC?zpXOwO zJ#@9IJ&?zJyiOoa_W?Fm0`4Rr@hK?z*gndAJaHlLJB%4*Mbh|SapC@SXC^}o-aG-; z`S%$j6dPJj(D(^`sMiI2%3z}iYy~J9;DY{2@wEfEKriUk@`N+9o_H~nxU8OWQ^G(P%Pgi3Brre zf`ONH}6&y=?AC|bV6}Ggn6>^4u9|wS)3nEk7;c_w;>(r{6lGtE3^3# zBZfC>6l=+HTYsu0dvu_e^-Q>T@XG_2QNHRi&f#Kqf=MJCxbJM_7bkioP+z6ky#dNY zcxun0*&c|5Ys>yK1(NL4BVJU|_ni%r`y% zM>|A(%T2;?y9@7v$C=2cVAaX7t@Nb!*h}lV=FZDXt%0D0I&14PtJE+Mh5B=mTa=yM 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 TnHK)`g>XCeh5HlUH?V5)Fe|J)+Bbu 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

gWBe?^$;K0$LNBh7A2jjv7UN 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 literal 0 HcmV?d00001 diff --git a/tools/urt/libs/picomodel/lwo/list.c b/tools/urt/libs/picomodel/lwo/list.c new file mode 100644 index 00000000..d07b0337 --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/list.c @@ -0,0 +1,101 @@ +/* +====================================================================== +list.c + +Generic linked list operations. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwListFree() + +Free the items in a list. +====================================================================== */ + +void lwListFree( void *list, void ( *freeNode )( void * )) +{ + lwNode *node, *next; + + node = ( lwNode * ) list; + while ( node ) { + next = node->next; + freeNode( node ); + node = next; + } +} + + +/* +====================================================================== +lwListAdd() + +Append a node to a list. +====================================================================== */ + +void lwListAdd( void **list, void *node ) +{ + lwNode *head, *tail; + + head = *(( lwNode ** ) list ); + if ( !head ) { + *list = node; + return; + } + while ( head ) { + tail = head; + head = head->next; + } + tail->next = ( lwNode * ) node; + (( lwNode * ) node )->prev = tail; +} + + +/* +====================================================================== +lwListInsert() + +Insert a node into a list in sorted order. +====================================================================== */ + +void lwListInsert( void **vlist, void *vitem, int ( *compare )( void *, void * )) +{ + lwNode **list, *item, *node, *prev; + + if ( !*vlist ) { + *vlist = vitem; + return; + } + + list = ( lwNode ** ) vlist; + item = ( lwNode * ) vitem; + node = *list; + prev = NULL; + + while ( node ) { + if ( 0 < compare( node, item )) break; + prev = node; + node = node->next; + } + + if ( !prev ) { + *list = item; + node->prev = item; + item->next = node; + } + else if ( !node ) { + prev->next = item; + item->prev = prev; + } + else { + item->next = node; + item->prev = prev; + prev->next = item; + node->prev = item; + } +} diff --git a/tools/urt/libs/picomodel/lwo/lwio.c b/tools/urt/libs/picomodel/lwo/lwio.c new file mode 100644 index 00000000..f8408abd --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/lwio.c @@ -0,0 +1,442 @@ +/* +====================================================================== +lwio.c + +Functions for reading basic LWO2 data types. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +flen + +This accumulates a count of the number of bytes read. Callers can set +it at the beginning of a sequence of reads and then retrieve it to get +the number of bytes actually read. If one of the I/O functions fails, +flen is set to an error code, after which the I/O functions ignore +read requests until flen is reset. +====================================================================== */ + +#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ +#define FLEN_ERROR INT_MIN + +static int flen; + +void set_flen( int i ) { flen = i; } + +int get_flen( void ) { return flen; } + + +#ifndef __BIG_ENDIAN__ +/* +===================================================================== +revbytes() + +Reverses byte order in place. + +INPUTS + bp bytes to reverse + elsize size of the underlying data type + elcount number of elements to swap + +RESULTS + Reverses the byte order in each of elcount elements. + +This only needs to be defined on little-endian platforms, most +notably Windows. lwo2.h replaces this with a #define on big-endian +platforms. +===================================================================== */ + +void revbytes( void *bp, int elsize, int elcount ) +{ + register unsigned char *p, *q; + + p = ( unsigned char * ) bp; + + if ( elsize == 2 ) { + q = p + 1; + while ( elcount-- ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + p += 2; + q += 2; + } + return; + } + + while ( elcount-- ) { + q = p + elsize - 1; + while ( p < q ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + ++p; + --q; + } + p += elsize >> 1; + } +} +#endif + + +void *getbytes( picoMemStream_t *fp, int size ) +{ + void *data; + + if ( flen == FLEN_ERROR ) return NULL; + if ( size < 0 ) { + flen = FLEN_ERROR; + return NULL; + } + data = _pico_alloc( size ); + if ( !data ) { + flen = FLEN_ERROR; + return NULL; + } + if ( 1 != _pico_memstream_read( fp, data, size )) { + flen = FLEN_ERROR; + _pico_free( data ); + return NULL; + } + + flen += size; + return data; +} + + +void skipbytes( picoMemStream_t *fp, int n ) +{ + if ( flen == FLEN_ERROR ) return; + if ( _pico_memstream_seek( fp, n, PICO_SEEK_CUR )) + flen = FLEN_ERROR; + else + flen += n; +} + + +int getI1( picoMemStream_t *fp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + i = _pico_memstream_getc( fp ); + if ( i < 0 ) { + flen = FLEN_ERROR; + return 0; + } + if ( i > 127 ) i -= 256; + flen += 1; + return i; +} + + +short getI2( picoMemStream_t *fp ) +{ + short i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 1 != _pico_memstream_read( fp, &i, 2 )) { + flen = FLEN_ERROR; + return 0; + } + revbytes( &i, 2, 1 ); + flen += 2; + return i; +} + + +int getI4( picoMemStream_t *fp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 1 != _pico_memstream_read( fp, &i, 4 )) { + flen = FLEN_ERROR; + return 0; + } + revbytes( &i, 4, 1 ); + flen += 4; + return i; +} + + +unsigned char getU1( picoMemStream_t *fp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + i = _pico_memstream_getc( fp ); + if ( i < 0 ) { + flen = FLEN_ERROR; + return 0; + } + flen += 1; + return i; +} + + +unsigned short getU2( picoMemStream_t *fp ) +{ + unsigned short i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 1 != _pico_memstream_read( fp, &i, 2 )) { + flen = FLEN_ERROR; + return 0; + } + revbytes( &i, 2, 1 ); + flen += 2; + return i; +} + + +unsigned int getU4( picoMemStream_t *fp ) +{ + unsigned int i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 1 != _pico_memstream_read( fp, &i, 4 )) { + flen = FLEN_ERROR; + return 0; + } + revbytes( &i, 4, 1 ); + flen += 4; + return i; +} + + +int getVX( picoMemStream_t *fp ) +{ + int i, c; + + if ( flen == FLEN_ERROR ) return 0; + + c = _pico_memstream_getc( fp ); + if ( c != 0xFF ) { + i = c << 8; + c = _pico_memstream_getc( fp ); + i |= c; + flen += 2; + } + else { + c = _pico_memstream_getc( fp ); + i = c << 16; + c = _pico_memstream_getc( fp ); + i |= c << 8; + c = _pico_memstream_getc( fp ); + i |= c; + flen += 4; + } + + if ( _pico_memstream_error( fp )) { + flen = FLEN_ERROR; + return 0; + } + return i; +} + + +float getF4( picoMemStream_t *fp ) +{ + float f; + + if ( flen == FLEN_ERROR ) return 0.0f; + if ( 1 != _pico_memstream_read( fp, &f, 4 )) { + flen = FLEN_ERROR; + return 0.0f; + } + revbytes( &f, 4, 1 ); + flen += 4; + return f; +} + + +char *getS0( picoMemStream_t *fp ) +{ + char *s; + int i, c, len, pos; + + if ( flen == FLEN_ERROR ) return NULL; + + pos = _pico_memstream_tell( fp ); + for ( i = 1; ; i++ ) { + c = _pico_memstream_getc( fp ); + if ( c <= 0 ) break; + } + if ( c < 0 ) { + flen = FLEN_ERROR; + return NULL; + } + + if ( i == 1 ) { + if ( _pico_memstream_seek( fp, pos + 2, PICO_SEEK_SET )) + flen = FLEN_ERROR; + else + flen += 2; + return NULL; + } + + len = i + ( i & 1 ); + s = _pico_alloc( len ); + if ( !s ) { + flen = FLEN_ERROR; + return NULL; + } + + if ( _pico_memstream_seek( fp, pos, PICO_SEEK_SET )) { + flen = FLEN_ERROR; + return NULL; + } + if ( 1 != _pico_memstream_read( fp, s, len )) { + flen = FLEN_ERROR; + return NULL; + } + + flen += len; + return s; +} + + +int sgetI1( unsigned char **bp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + i = **bp; + if ( i > 127 ) i -= 256; + flen += 1; + *bp++; + return i; +} + + +short sgetI2( unsigned char **bp ) +{ + short i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 2 ); + revbytes( &i, 2, 1 ); + flen += 2; + *bp += 2; + return i; +} + + +int sgetI4( unsigned char **bp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 4 ); + revbytes( &i, 4, 1 ); + flen += 4; + *bp += 4; + return i; +} + + +unsigned char sgetU1( unsigned char **bp ) +{ + unsigned char c; + + if ( flen == FLEN_ERROR ) return 0; + c = **bp; + flen += 1; + *bp++; + return c; +} + + +unsigned short sgetU2( unsigned char **bp ) +{ + unsigned char *buf = *bp; + unsigned short i; + + if ( flen == FLEN_ERROR ) return 0; + i = ( buf[ 0 ] << 8 ) | buf[ 1 ]; + flen += 2; + *bp += 2; + return i; +} + + +unsigned int sgetU4( unsigned char **bp ) +{ + unsigned int i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 4 ); + revbytes( &i, 4, 1 ); + flen += 4; + *bp += 4; + return i; +} + + +int sgetVX( unsigned char **bp ) +{ + unsigned char *buf = *bp; + int i; + + if ( flen == FLEN_ERROR ) return 0; + + if ( buf[ 0 ] != 0xFF ) { + i = buf[ 0 ] << 8 | buf[ 1 ]; + flen += 2; + *bp += 2; + } + else { + i = ( buf[ 1 ] << 16 ) | ( buf[ 2 ] << 8 ) | buf[ 3 ]; + flen += 4; + *bp += 4; + } + return i; +} + + +float sgetF4( unsigned char **bp ) +{ + float f; + + if ( flen == FLEN_ERROR ) return 0.0f; + memcpy( &f, *bp, 4 ); + revbytes( &f, 4, 1 ); + flen += 4; + *bp += 4; + return f; +} + + +char *sgetS0( unsigned char **bp ) +{ + char *s; + unsigned char *buf = *bp; + int len; + + if ( flen == FLEN_ERROR ) return NULL; + + len = strlen( buf ) + 1; + if ( len == 1 ) { + flen += 2; + *bp += 2; + return NULL; + } + len += len & 1; + s = _pico_alloc( len ); + if ( !s ) { + flen = FLEN_ERROR; + return NULL; + } + + memcpy( s, buf, len ); + flen += len; + *bp += len; + return s; +} diff --git a/tools/urt/libs/picomodel/lwo/lwo2.c b/tools/urt/libs/picomodel/lwo/lwo2.c new file mode 100644 index 00000000..cc46f395 --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/lwo2.c @@ -0,0 +1,308 @@ +/* +====================================================================== +lwo2.c + +The entry point for loading LightWave object files. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + +/* disable warnings */ +#ifdef WIN32 +#pragma warning( disable:4018 ) /* signed/unsigned mismatch */ +#endif + + +/* +====================================================================== +lwFreeLayer() + +Free memory used by an lwLayer. +====================================================================== */ + +void lwFreeLayer( lwLayer *layer ) +{ + if ( layer ) { + if ( layer->name ) _pico_free( layer->name ); + lwFreePoints( &layer->point ); + lwFreePolygons( &layer->polygon ); + lwListFree( layer->vmap, (void *) lwFreeVMap ); + _pico_free( layer ); + } +} + + +/* +====================================================================== +lwFreeObject() + +Free memory used by an lwObject. +====================================================================== */ + +void lwFreeObject( lwObject *object ) +{ + if ( object ) { + lwListFree( object->layer, (void *) lwFreeLayer ); + lwListFree( object->env, (void *) lwFreeEnvelope ); + lwListFree( object->clip, (void *) lwFreeClip ); + lwListFree( object->surf, (void *) lwFreeSurface ); + lwFreeTags( &object->taglist ); + _pico_free( object ); + } +} + + +/* +====================================================================== +lwGetObject() + +Returns the contents of a LightWave object, given its filename, or +NULL if the file couldn't be loaded. On failure, failID and failpos +can be used to diagnose the cause. + +1. If the file isn't an LWO2 or an LWOB, failpos will contain 12 and + failID will be unchanged. + +2. If an error occurs while reading, failID will contain the most + recently read IFF chunk ID, and failpos will contain the value + returned by _pico_memstream_tell() at the time of the failure. + +3. If the file couldn't be opened, or an error occurs while reading + the first 12 bytes, both failID and failpos will be unchanged. + +If you don't need this information, failID and failpos can be NULL. +====================================================================== */ + +lwObject *lwGetObject( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ) +{ + lwObject *object; + lwLayer *layer; + lwNode *node; + unsigned int id, formsize, type, cksize; + int i, rlen; + + /* open the file */ + + if ( !fp ) return NULL; + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + return NULL; + } + + /* is this a LW object? */ + + if ( id != ID_FORM ) { + if ( failpos ) *failpos = 12; + return NULL; + } + + if ( type != ID_LWO2 ) { + if ( type == ID_LWOB ) + return lwGetObject5( filename, fp, failID, failpos ); + else { + if ( failpos ) *failpos = 12; + return NULL; + } + } + + /* allocate an object and a default layer */ + + object = _pico_calloc( 1, sizeof( lwObject )); + if ( !object ) goto Fail; + + layer = _pico_calloc( 1, sizeof( lwLayer )); + if ( !layer ) goto Fail; + object->layer = layer; + + /* get the first chunk header */ + + id = getU4( fp ); + cksize = getU4( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process chunks as they're encountered */ + + while ( 1 ) { + cksize += cksize & 1; + + switch ( id ) + { + case ID_LAYR: + if ( object->nlayers > 0 ) { + layer = _pico_calloc( 1, sizeof( lwLayer )); + if ( !layer ) goto Fail; + lwListAdd( (void **) &object->layer, layer ); + } + object->nlayers++; + + set_flen( 0 ); + layer->index = getU2( fp ); + layer->flags = getU2( fp ); + layer->pivot[ 0 ] = getF4( fp ); + layer->pivot[ 1 ] = getF4( fp ); + layer->pivot[ 2 ] = getF4( fp ); + layer->name = getS0( fp ); + + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) goto Fail; + if ( rlen <= cksize - 2 ) + layer->parent = getU2( fp ); + rlen = get_flen(); + if ( rlen < cksize ) + _pico_memstream_seek( fp, cksize - rlen, PICO_SEEK_CUR ); + break; + + case ID_PNTS: + if ( !lwGetPoints( fp, cksize, &layer->point )) + goto Fail; + break; + + case ID_POLS: + if ( !lwGetPolygons( fp, cksize, &layer->polygon, + layer->point.offset )) + goto Fail; + break; + + case ID_VMAP: + case ID_VMAD: + node = ( lwNode * ) lwGetVMap( fp, cksize, layer->point.offset, + layer->polygon.offset, id == ID_VMAD ); + if ( !node ) goto Fail; + lwListAdd( (void **) &layer->vmap, node ); + layer->nvmaps++; + break; + + case ID_PTAG: + if ( !lwGetPolygonTags( fp, cksize, &object->taglist, + &layer->polygon )) + goto Fail; + break; + + case ID_BBOX: + set_flen( 0 ); + for ( i = 0; i < 6; i++ ) + layer->bbox[ i ] = getF4( fp ); + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) goto Fail; + if ( rlen < cksize ) + _pico_memstream_seek( fp, cksize - rlen, PICO_SEEK_CUR ); + break; + + case ID_TAGS: + if ( !lwGetTags( fp, cksize, &object->taglist )) + goto Fail; + break; + + case ID_ENVL: + node = ( lwNode * ) lwGetEnvelope( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void **) &object->env, node ); + object->nenvs++; + break; + + case ID_CLIP: + node = ( lwNode * ) lwGetClip( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void **) &object->clip, node ); + object->nclips++; + break; + + case ID_SURF: + node = ( lwNode * ) lwGetSurface( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void **) &object->surf, node ); + object->nsurfs++; + break; + + case ID_DESC: + case ID_TEXT: + case ID_ICON: + default: + _pico_memstream_seek( fp, cksize, PICO_SEEK_CUR ); + break; + } + + /* end of the file? */ + + if ( formsize <= _pico_memstream_tell( fp ) - 8 ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + cksize = getU4( fp ); + if ( 8 != get_flen() ) goto Fail; + } + + if ( object->nlayers == 0 ) + object->nlayers = 1; + + layer = object->layer; + while ( layer ) { + lwGetBoundingBox( &layer->point, layer->bbox ); + lwGetPolyNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointPolygons( &layer->point, &layer->polygon )) goto Fail; + if ( !lwResolvePolySurfaces( &layer->polygon, &object->taglist, + &object->surf, &object->nsurfs )) goto Fail; + lwGetVertNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointVMaps( &layer->point, layer->vmap )) goto Fail; + if ( !lwGetPolyVMaps( &layer->polygon, layer->vmap )) goto Fail; + layer = layer->next; + } + + return object; + +Fail: + if ( failID ) *failID = id; + if ( fp ) { + if ( failpos ) *failpos = _pico_memstream_tell( fp ); + } + lwFreeObject( object ); + return NULL; +} + +int lwValidateObject( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ) +{ + unsigned int id, formsize, type; + + /* open the file */ + + if ( !fp ) return PICO_PMV_ERROR_MEMORY; + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + return PICO_PMV_ERROR_SIZE; + } + + /* is this a LW object? */ + + if ( id != ID_FORM ) { + if ( failpos ) *failpos = 12; + return PICO_PMV_ERROR_SIZE; + } + + if ( type != ID_LWO2 ) { + if ( type == ID_LWOB ) + return lwValidateObject5( filename, fp, failID, failpos ); + else { + if ( failpos ) *failpos = 12; + return PICO_PMV_ERROR_IDENT; + } + } + + return PICO_PMV_OK; +} diff --git a/tools/urt/libs/picomodel/lwo/lwo2.h b/tools/urt/libs/picomodel/lwo/lwo2.h new file mode 100644 index 00000000..e48e06ea --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/lwo2.h @@ -0,0 +1,651 @@ +/* +====================================================================== +lwo2.h + +Definitions and typedefs for LWO2 files. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#ifndef LWO2_H +#define LWO2_H + +/* chunk and subchunk IDs */ + +#define LWID_(a,b,c,d) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) + +#define ID_FORM LWID_('F','O','R','M') +#define ID_LWO2 LWID_('L','W','O','2') +#define ID_LWOB LWID_('L','W','O','B') + +/* top-level chunks */ +#define ID_LAYR LWID_('L','A','Y','R') +#define ID_TAGS LWID_('T','A','G','S') +#define ID_PNTS LWID_('P','N','T','S') +#define ID_BBOX LWID_('B','B','O','X') +#define ID_VMAP LWID_('V','M','A','P') +#define ID_VMAD LWID_('V','M','A','D') +#define ID_POLS LWID_('P','O','L','S') +#define ID_PTAG LWID_('P','T','A','G') +#define ID_ENVL LWID_('E','N','V','L') +#define ID_CLIP LWID_('C','L','I','P') +#define ID_SURF LWID_('S','U','R','F') +#define ID_DESC LWID_('D','E','S','C') +#define ID_TEXT LWID_('T','E','X','T') +#define ID_ICON LWID_('I','C','O','N') + +/* polygon types */ +#define ID_FACE LWID_('F','A','C','E') +#define ID_CURV LWID_('C','U','R','V') +#define ID_PTCH LWID_('P','T','C','H') +#define ID_MBAL LWID_('M','B','A','L') +#define ID_BONE LWID_('B','O','N','E') + +/* polygon tags */ +#define ID_SURF LWID_('S','U','R','F') +#define ID_PART LWID_('P','A','R','T') +#define ID_SMGP LWID_('S','M','G','P') + +/* envelopes */ +#define ID_PRE LWID_('P','R','E',' ') +#define ID_POST LWID_('P','O','S','T') +#define ID_KEY LWID_('K','E','Y',' ') +#define ID_SPAN LWID_('S','P','A','N') +#define ID_TCB LWID_('T','C','B',' ') +#define ID_HERM LWID_('H','E','R','M') +#define ID_BEZI LWID_('B','E','Z','I') +#define ID_BEZ2 LWID_('B','E','Z','2') +#define ID_LINE LWID_('L','I','N','E') +#define ID_STEP LWID_('S','T','E','P') + +/* clips */ +#define ID_STIL LWID_('S','T','I','L') +#define ID_ISEQ LWID_('I','S','E','Q') +#define ID_ANIM LWID_('A','N','I','M') +#define ID_XREF LWID_('X','R','E','F') +#define ID_STCC LWID_('S','T','C','C') +#define ID_TIME LWID_('T','I','M','E') +#define ID_CONT LWID_('C','O','N','T') +#define ID_BRIT LWID_('B','R','I','T') +#define ID_SATR LWID_('S','A','T','R') +#define ID_HUE LWID_('H','U','E',' ') +#define ID_GAMM LWID_('G','A','M','M') +#define ID_NEGA LWID_('N','E','G','A') +#define ID_IFLT LWID_('I','F','L','T') +#define ID_PFLT LWID_('P','F','L','T') + +/* surfaces */ +#define ID_COLR LWID_('C','O','L','R') +#define ID_LUMI LWID_('L','U','M','I') +#define ID_DIFF LWID_('D','I','F','F') +#define ID_SPEC LWID_('S','P','E','C') +#define ID_GLOS LWID_('G','L','O','S') +#define ID_REFL LWID_('R','E','F','L') +#define ID_RFOP LWID_('R','F','O','P') +#define ID_RIMG LWID_('R','I','M','G') +#define ID_RSAN LWID_('R','S','A','N') +#define ID_TRAN LWID_('T','R','A','N') +#define ID_TROP LWID_('T','R','O','P') +#define ID_TIMG LWID_('T','I','M','G') +#define ID_RIND LWID_('R','I','N','D') +#define ID_TRNL LWID_('T','R','N','L') +#define ID_BUMP LWID_('B','U','M','P') +#define ID_SMAN LWID_('S','M','A','N') +#define ID_SIDE LWID_('S','I','D','E') +#define ID_CLRH LWID_('C','L','R','H') +#define ID_CLRF LWID_('C','L','R','F') +#define ID_ADTR LWID_('A','D','T','R') +#define ID_SHRP LWID_('S','H','R','P') +#define ID_LINE LWID_('L','I','N','E') +#define ID_LSIZ LWID_('L','S','I','Z') +#define ID_ALPH LWID_('A','L','P','H') +#define ID_AVAL LWID_('A','V','A','L') +#define ID_GVAL LWID_('G','V','A','L') +#define ID_BLOK LWID_('B','L','O','K') + +/* texture layer */ +#define ID_TYPE LWID_('T','Y','P','E') +#define ID_CHAN LWID_('C','H','A','N') +#define ID_NAME LWID_('N','A','M','E') +#define ID_ENAB LWID_('E','N','A','B') +#define ID_OPAC LWID_('O','P','A','C') +#define ID_FLAG LWID_('F','L','A','G') +#define ID_PROJ LWID_('P','R','O','J') +#define ID_STCK LWID_('S','T','C','K') +#define ID_TAMP LWID_('T','A','M','P') + +/* texture coordinates */ +#define ID_TMAP LWID_('T','M','A','P') +#define ID_AXIS LWID_('A','X','I','S') +#define ID_CNTR LWID_('C','N','T','R') +#define ID_SIZE LWID_('S','I','Z','E') +#define ID_ROTA LWID_('R','O','T','A') +#define ID_OREF LWID_('O','R','E','F') +#define ID_FALL LWID_('F','A','L','L') +#define ID_CSYS LWID_('C','S','Y','S') + +/* image map */ +#define ID_IMAP LWID_('I','M','A','P') +#define ID_IMAG LWID_('I','M','A','G') +#define ID_WRAP LWID_('W','R','A','P') +#define ID_WRPW LWID_('W','R','P','W') +#define ID_WRPH LWID_('W','R','P','H') +#define ID_VMAP LWID_('V','M','A','P') +#define ID_AAST LWID_('A','A','S','T') +#define ID_PIXB LWID_('P','I','X','B') + +/* procedural */ +#define ID_PROC LWID_('P','R','O','C') +#define ID_COLR LWID_('C','O','L','R') +#define ID_VALU LWID_('V','A','L','U') +#define ID_FUNC LWID_('F','U','N','C') +#define ID_FTPS LWID_('F','T','P','S') +#define ID_ITPS LWID_('I','T','P','S') +#define ID_ETPS LWID_('E','T','P','S') + +/* gradient */ +#define ID_GRAD LWID_('G','R','A','D') +#define ID_GRST LWID_('G','R','S','T') +#define ID_GREN LWID_('G','R','E','N') +#define ID_PNAM LWID_('P','N','A','M') +#define ID_INAM LWID_('I','N','A','M') +#define ID_GRPT LWID_('G','R','P','T') +#define ID_FKEY LWID_('F','K','E','Y') +#define ID_IKEY LWID_('I','K','E','Y') + +/* shader */ +#define ID_SHDR LWID_('S','H','D','R') +#define ID_DATA LWID_('D','A','T','A') + + +/* generic linked list */ + +typedef struct st_lwNode { + struct st_lwNode *next, *prev; + void *data; +} lwNode; + + +/* plug-in reference */ + +typedef struct st_lwPlugin { + struct st_lwPlugin *next, *prev; + char *ord; + char *name; + int flags; + void *data; +} lwPlugin; + + +/* envelopes */ + +typedef struct st_lwKey { + struct st_lwKey *next, *prev; + float value; + float time; + unsigned int shape; /* ID_TCB, ID_BEZ2, etc. */ + float tension; + float continuity; + float bias; + float param[ 4 ]; +} lwKey; + +typedef struct st_lwEnvelope { + struct st_lwEnvelope *next, *prev; + int index; + int type; + char *name; + lwKey *key; /* linked list of keys */ + int nkeys; + int behavior[ 2 ]; /* pre and post (extrapolation) */ + lwPlugin *cfilter; /* linked list of channel filters */ + int ncfilters; +} lwEnvelope; + +#define BEH_RESET 0 +#define BEH_CONSTANT 1 +#define BEH_REPEAT 2 +#define BEH_OSCILLATE 3 +#define BEH_OFFSET 4 +#define BEH_LINEAR 5 + + +/* values that can be enveloped */ + +typedef struct st_lwEParam { + float val; + int eindex; +} lwEParam; + +typedef struct st_lwVParam { + float val[ 3 ]; + int eindex; +} lwVParam; + + +/* clips */ + +typedef struct st_lwClipStill { + char *name; +} lwClipStill; + +typedef struct st_lwClipSeq { + char *prefix; /* filename before sequence digits */ + char *suffix; /* after digits, e.g. extensions */ + int digits; + int flags; + int offset; + int start; + int end; +} lwClipSeq; + +typedef struct st_lwClipAnim { + char *name; + char *server; /* anim loader plug-in */ + void *data; +} lwClipAnim; + +typedef struct st_lwClipXRef { + char *string; + int index; + struct st_lwClip *clip; +} lwClipXRef; + +typedef struct st_lwClipCycle { + char *name; + int lo; + int hi; +} lwClipCycle; + +typedef struct st_lwClip { + struct st_lwClip *next, *prev; + int index; + unsigned int type; /* ID_STIL, ID_ISEQ, etc. */ + union { + lwClipStill still; + lwClipSeq seq; + lwClipAnim anim; + lwClipXRef xref; + lwClipCycle cycle; + } source; + float start_time; + float duration; + float frame_rate; + lwEParam contrast; + lwEParam brightness; + lwEParam saturation; + lwEParam hue; + lwEParam gamma; + int negative; + lwPlugin *ifilter; /* linked list of image filters */ + int nifilters; + lwPlugin *pfilter; /* linked list of pixel filters */ + int npfilters; +} lwClip; + + +/* textures */ + +typedef struct st_lwTMap { + lwVParam size; + lwVParam center; + lwVParam rotate; + lwVParam falloff; + int fall_type; + char *ref_object; + int coord_sys; +} lwTMap; + +typedef struct st_lwImageMap { + int cindex; + int projection; + char *vmap_name; + int axis; + int wrapw_type; + int wraph_type; + lwEParam wrapw; + lwEParam wraph; + float aa_strength; + int aas_flags; + int pblend; + lwEParam stck; + lwEParam amplitude; +} lwImageMap; + +#define PROJ_PLANAR 0 +#define PROJ_CYLINDRICAL 1 +#define PROJ_SPHERICAL 2 +#define PROJ_CUBIC 3 +#define PROJ_FRONT 4 + +#define WRAP_NONE 0 +#define WRAP_EDGE 1 +#define WRAP_REPEAT 2 +#define WRAP_MIRROR 3 + +typedef struct st_lwProcedural { + int axis; + float value[ 3 ]; + char *name; + void *data; +} lwProcedural; + +typedef struct st_lwGradKey { + struct st_lwGradKey *next, *prev; + float value; + float rgba[ 4 ]; +} lwGradKey; + +typedef struct st_lwGradient { + char *paramname; + char *itemname; + float start; + float end; + int repeat; + lwGradKey *key; /* array of gradient keys */ + short *ikey; /* array of interpolation codes */ +} lwGradient; + +typedef struct st_lwTexture { + struct st_lwTexture *next, *prev; + char *ord; + unsigned int type; + unsigned int chan; + lwEParam opacity; + short opac_type; + short enabled; + short negative; + short axis; + union { + lwImageMap imap; + lwProcedural proc; + lwGradient grad; + } param; + lwTMap tmap; +} lwTexture; + + +/* values that can be textured */ + +typedef struct st_lwTParam { + float val; + int eindex; + lwTexture *tex; /* linked list of texture layers */ +} lwTParam; + +typedef struct st_lwCParam { + float rgb[ 3 ]; + int eindex; + lwTexture *tex; /* linked list of texture layers */ +} lwCParam; + + +/* surfaces */ + +typedef struct st_lwGlow { + short enabled; + short type; + lwEParam intensity; + lwEParam size; +} Glow; + +typedef struct st_lwRMap { + lwTParam val; + int options; + int cindex; + float seam_angle; +} lwRMap; + +typedef struct st_lwLine { + short enabled; + unsigned short flags; + lwEParam size; +} lwLine; + +typedef struct st_lwSurface { + struct st_lwSurface *next, *prev; + char *name; + char *srcname; + lwCParam color; + lwTParam luminosity; + lwTParam diffuse; + lwTParam specularity; + lwTParam glossiness; + lwRMap reflection; + lwRMap transparency; + lwTParam eta; + lwTParam translucency; + lwTParam bump; + float smooth; + int sideflags; + float alpha; + int alpha_mode; + lwEParam color_hilite; + lwEParam color_filter; + lwEParam add_trans; + lwEParam dif_sharp; + lwEParam glow; + lwLine line; + lwPlugin *shader; /* linked list of shaders */ + int nshaders; +} lwSurface; + + +/* vertex maps */ + +typedef struct st_lwVMap { + struct st_lwVMap *next, *prev; + char *name; + unsigned int type; + int dim; + int nverts; + int perpoly; + int *vindex; /* array of point indexes */ + int *pindex; /* array of polygon indexes */ + float **val; +} lwVMap; + +typedef struct st_lwVMapPt { + lwVMap *vmap; + int index; /* vindex or pindex element */ +} lwVMapPt; + + +/* points and polygons */ + +typedef struct st_lwPoint { + float pos[ 3 ]; + int npols; /* number of polygons sharing the point */ + int *pol; /* array of polygon indexes */ + int nvmaps; + lwVMapPt *vm; /* array of vmap references */ +} lwPoint; + +typedef struct st_lwPolVert { + int index; /* index into the point array */ + float norm[ 3 ]; + int nvmaps; + lwVMapPt *vm; /* array of vmap references */ +} lwPolVert; + +typedef struct st_lwPolygon { + lwSurface *surf; + int part; /* part index */ + int smoothgrp; /* smoothing group */ + int flags; + unsigned int type; + float norm[ 3 ]; + int nverts; + lwPolVert *v; /* array of vertex records */ +} lwPolygon; + +typedef struct st_lwPointList { + int count; + int offset; /* only used during reading */ + lwPoint *pt; /* array of points */ +} lwPointList; + +typedef struct st_lwPolygonList { + int count; + int offset; /* only used during reading */ + int vcount; /* total number of vertices */ + int voffset; /* only used during reading */ + lwPolygon *pol; /* array of polygons */ +} lwPolygonList; + + +/* geometry layers */ + +typedef struct st_lwLayer { + struct st_lwLayer *next, *prev; + char *name; + int index; + int parent; + int flags; + float pivot[ 3 ]; + float bbox[ 6 ]; + lwPointList point; + lwPolygonList polygon; + int nvmaps; + lwVMap *vmap; /* linked list of vmaps */ +} lwLayer; + + +/* tag strings */ + +typedef struct st_lwTagList { + int count; + int offset; /* only used during reading */ + char **tag; /* array of strings */ +} lwTagList; + + +/* an object */ + +typedef struct st_lwObject { + lwLayer *layer; /* linked list of layers */ + lwEnvelope *env; /* linked list of envelopes */ + lwClip *clip; /* linked list of clips */ + lwSurface *surf; /* linked list of surfaces */ + lwTagList taglist; + int nlayers; + int nenvs; + int nclips; + int nsurfs; +} lwObject; + + +/* lwo2.c */ + +void lwFreeLayer( lwLayer *layer ); +void lwFreeObject( lwObject *object ); +lwObject *lwGetObject( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); +int lwValidateObject( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); + +/* pntspols.c */ + +void lwFreePoints( lwPointList *point ); +void lwFreePolygons( lwPolygonList *plist ); +int lwGetPoints( picoMemStream_t *fp, int cksize, lwPointList *point ); +void lwGetBoundingBox( lwPointList *point, float bbox[] ); +int lwAllocPolygons( lwPolygonList *plist, int npols, int nverts ); +int lwGetPolygons( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ); +void lwGetPolyNormals( lwPointList *point, lwPolygonList *polygon ); +int lwGetPointPolygons( lwPointList *point, lwPolygonList *polygon ); +int lwResolvePolySurfaces( lwPolygonList *polygon, lwTagList *tlist, + lwSurface **surf, int *nsurfs ); +void lwGetVertNormals( lwPointList *point, lwPolygonList *polygon ); +void lwFreeTags( lwTagList *tlist ); +int lwGetTags( picoMemStream_t *fp, int cksize, lwTagList *tlist ); +int lwGetPolygonTags( picoMemStream_t *fp, int cksize, lwTagList *tlist, + lwPolygonList *plist ); + +/* vmap.c */ + +void lwFreeVMap( lwVMap *vmap ); +lwVMap *lwGetVMap( picoMemStream_t *fp, int cksize, int ptoffset, int poloffset, + int perpoly ); +int lwGetPointVMaps( lwPointList *point, lwVMap *vmap ); +int lwGetPolyVMaps( lwPolygonList *polygon, lwVMap *vmap ); + +/* clip.c */ + +void lwFreeClip( lwClip *clip ); +lwClip *lwGetClip( picoMemStream_t *fp, int cksize ); +lwClip *lwFindClip( lwClip *list, int index ); + +/* envelope.c */ + +void lwFreeEnvelope( lwEnvelope *env ); +lwEnvelope *lwGetEnvelope( picoMemStream_t *fp, int cksize ); +lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index ); +float lwEvalEnvelope( lwEnvelope *env, float time ); + +/* surface.c */ + +void lwFreePlugin( lwPlugin *p ); +void lwFreeTexture( lwTexture *t ); +void lwFreeSurface( lwSurface *surf ); +int lwGetTHeader( picoMemStream_t *fp, int hsz, lwTexture *tex ); +int lwGetTMap( picoMemStream_t *fp, int tmapsz, lwTMap *tmap ); +int lwGetImageMap( picoMemStream_t *fp, int rsz, lwTexture *tex ); +int lwGetProcedural( picoMemStream_t *fp, int rsz, lwTexture *tex ); +int lwGetGradient( picoMemStream_t *fp, int rsz, lwTexture *tex ); +lwTexture *lwGetTexture( picoMemStream_t *fp, int bloksz, unsigned int type ); +lwPlugin *lwGetShader( picoMemStream_t *fp, int bloksz ); +lwSurface *lwGetSurface( picoMemStream_t *fp, int cksize ); +lwSurface *lwDefaultSurface( void ); + +/* lwob.c */ + +lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ); +int lwGetPolygons5( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ); +lwObject *lwGetObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); +int lwValidateObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); + +/* list.c */ + +void lwListFree( void *list, void ( *freeNode )( void * )); +void lwListAdd( void **list, void *node ); +void lwListInsert( void **vlist, void *vitem, + int ( *compare )( void *, void * )); + +/* vecmath.c */ + +float dot( float a[], float b[] ); +void cross( float a[], float b[], float c[] ); +void normalize( float v[] ); +#define vecangle( a, b ) ( float ) acos( dot( a, b )) + +/* lwio.c */ + +void set_flen( int i ); +int get_flen( void ); +void *getbytes( picoMemStream_t *fp, int size ); +void skipbytes( picoMemStream_t *fp, int n ); +int getI1( picoMemStream_t *fp ); +short getI2( picoMemStream_t *fp ); +int getI4( picoMemStream_t *fp ); +unsigned char getU1( picoMemStream_t *fp ); +unsigned short getU2( picoMemStream_t *fp ); +unsigned int getU4( picoMemStream_t *fp ); +int getVX( picoMemStream_t *fp ); +float getF4( picoMemStream_t *fp ); +char *getS0( picoMemStream_t *fp ); +int sgetI1( unsigned char **bp ); +short sgetI2( unsigned char **bp ); +int sgetI4( unsigned char **bp ); +unsigned char sgetU1( unsigned char **bp ); +unsigned short sgetU2( unsigned char **bp ); +unsigned int sgetU4( unsigned char **bp ); +int sgetVX( unsigned char **bp ); +float sgetF4( unsigned char **bp ); +char *sgetS0( unsigned char **bp ); + +#ifndef __BIG_ENDIAN__ + void revbytes( void *bp, int elsize, int elcount ); +#else + #define revbytes( b, s, c ) +#endif + +#endif diff --git a/tools/urt/libs/picomodel/lwo/lwob.c b/tools/urt/libs/picomodel/lwo/lwob.c new file mode 100644 index 00000000..e97e5268 --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/lwob.c @@ -0,0 +1,723 @@ +/* +====================================================================== +lwob.c + +Functions for an LWOB reader. LWOB is the LightWave object format +for versions of LW prior to 6.0. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + +/* disable warnings */ +#ifdef WIN32 +#pragma warning( disable:4018 ) /* signed/unsigned mismatch */ +#endif + + +/* IDs specific to LWOB */ + +#define ID_SRFS LWID_('S','R','F','S') +#define ID_FLAG LWID_('F','L','A','G') +#define ID_VLUM LWID_('V','L','U','M') +#define ID_VDIF LWID_('V','D','I','F') +#define ID_VSPC LWID_('V','S','P','C') +#define ID_RFLT LWID_('R','F','L','T') +#define ID_BTEX LWID_('B','T','E','X') +#define ID_CTEX LWID_('C','T','E','X') +#define ID_DTEX LWID_('D','T','E','X') +#define ID_LTEX LWID_('L','T','E','X') +#define ID_RTEX LWID_('R','T','E','X') +#define ID_STEX LWID_('S','T','E','X') +#define ID_TTEX LWID_('T','T','E','X') +#define ID_TFLG LWID_('T','F','L','G') +#define ID_TSIZ LWID_('T','S','I','Z') +#define ID_TCTR LWID_('T','C','T','R') +#define ID_TFAL LWID_('T','F','A','L') +#define ID_TVEL LWID_('T','V','E','L') +#define ID_TCLR LWID_('T','C','L','R') +#define ID_TVAL LWID_('T','V','A','L') +#define ID_TAMP LWID_('T','A','M','P') +#define ID_TIMG LWID_('T','I','M','G') +#define ID_TAAS LWID_('T','A','A','S') +#define ID_TREF LWID_('T','R','E','F') +#define ID_TOPC LWID_('T','O','P','C') +#define ID_SDAT LWID_('S','D','A','T') +#define ID_TFP0 LWID_('T','F','P','0') +#define ID_TFP1 LWID_('T','F','P','1') + + +/* +====================================================================== +add_clip() + +Add a clip to the clip list. Used to store the contents of an RIMG or +TIMG surface subchunk. +====================================================================== */ + +static int add_clip( char *s, lwClip **clist, int *nclips ) +{ + lwClip *clip; + char *p; + + clip = _pico_calloc( 1, sizeof( lwClip )); + if ( !clip ) return 0; + + clip->contrast.val = 1.0f; + clip->brightness.val = 1.0f; + clip->saturation.val = 1.0f; + clip->gamma.val = 1.0f; + + if ( p = strstr( s, "(sequence)" )) { + p[ -1 ] = 0; + clip->type = ID_ISEQ; + clip->source.seq.prefix = s; + clip->source.seq.digits = 3; + } + else { + clip->type = ID_STIL; + clip->source.still.name = s; + } + + *nclips++; + clip->index = *nclips; + + lwListAdd( (void *) clist, clip ); + + return clip->index; +} + + +/* +====================================================================== +add_tvel() + +Add a triple of envelopes to simulate the old texture velocity +parameters. +====================================================================== */ + +static int add_tvel( float pos[], float vel[], lwEnvelope **elist, int *nenvs ) +{ + lwEnvelope *env; + lwKey *key0, *key1; + int i; + + for ( i = 0; i < 3; i++ ) { + env = _pico_calloc( 1, sizeof( lwEnvelope )); + key0 = _pico_calloc( 1, sizeof( lwKey )); + key1 = _pico_calloc( 1, sizeof( lwKey )); + if ( !env || !key0 || !key1 ) return 0; + + key0->next = key1; + key0->value = pos[ i ]; + key0->time = 0.0f; + key1->prev = key0; + key1->value = pos[ i ] + vel[ i ] * 30.0f; + key1->time = 1.0f; + key0->shape = key1->shape = ID_LINE; + + env->index = *nenvs + i + 1; + env->type = 0x0301 + i; + env->name = _pico_alloc( 11 ); + if ( env->name ) { + strcpy( env->name, "Position.X" ); + env->name[ 9 ] += i; + } + env->key = key0; + env->nkeys = 2; + env->behavior[ 0 ] = BEH_LINEAR; + env->behavior[ 1 ] = BEH_LINEAR; + + lwListAdd( (void *) elist, env ); + } + + *nenvs += 3; + return env->index - 2; +} + + +/* +====================================================================== +get_texture() + +Create a new texture for BTEX, CTEX, etc. subchunks. +====================================================================== */ + +static lwTexture *get_texture( char *s ) +{ + lwTexture *tex; + + tex = _pico_calloc( 1, sizeof( lwTexture )); + if ( !tex ) return NULL; + + tex->tmap.size.val[ 0 ] = + tex->tmap.size.val[ 1 ] = + tex->tmap.size.val[ 2 ] = 1.0f; + tex->opacity.val = 1.0f; + tex->enabled = 1; + + if ( strstr( s, "Image Map" )) { + tex->type = ID_IMAP; + if ( strstr( s, "Planar" )) tex->param.imap.projection = 0; + else if ( strstr( s, "Cylindrical" )) tex->param.imap.projection = 1; + else if ( strstr( s, "Spherical" )) tex->param.imap.projection = 2; + else if ( strstr( s, "Cubic" )) tex->param.imap.projection = 3; + else if ( strstr( s, "Front" )) tex->param.imap.projection = 4; + tex->param.imap.aa_strength = 1.0f; + tex->param.imap.amplitude.val = 1.0f; + _pico_free( s ); + } + else { + tex->type = ID_PROC; + tex->param.proc.name = s; + } + + return tex; +} + + +/* +====================================================================== +lwGetSurface5() + +Read an lwSurface from an LWOB file. +====================================================================== */ + +lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ) +{ + lwSurface *surf; + lwTexture *tex; + lwPlugin *shdr; + char *s; + float v[ 3 ]; + unsigned int id, flags; + unsigned short sz; + int pos, rlen, i; + + + /* allocate the Surface structure */ + + surf = _pico_calloc( 1, sizeof( lwSurface )); + if ( !surf ) goto Fail; + + /* non-zero defaults */ + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* name */ + + surf->name = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_COLR: + surf->color.rgb[ 0 ] = getU1( fp ) / 255.0f; + surf->color.rgb[ 1 ] = getU1( fp ) / 255.0f; + surf->color.rgb[ 2 ] = getU1( fp ) / 255.0f; + break; + + case ID_FLAG: + flags = getU2( fp ); + if ( flags & 4 ) surf->smooth = 1.56207f; + if ( flags & 8 ) surf->color_hilite.val = 1.0f; + if ( flags & 16 ) surf->color_filter.val = 1.0f; + if ( flags & 128 ) surf->dif_sharp.val = 0.5f; + if ( flags & 256 ) surf->sideflags = 3; + if ( flags & 512 ) surf->add_trans.val = 1.0f; + break; + + case ID_LUMI: + surf->luminosity.val = getI2( fp ) / 256.0f; + break; + + case ID_VLUM: + surf->luminosity.val = getF4( fp ); + break; + + case ID_DIFF: + surf->diffuse.val = getI2( fp ) / 256.0f; + break; + + case ID_VDIF: + surf->diffuse.val = getF4( fp ); + break; + + case ID_SPEC: + surf->specularity.val = getI2( fp ) / 256.0f; + break; + + case ID_VSPC: + surf->specularity.val = getF4( fp ); + break; + + case ID_GLOS: + surf->glossiness.val = ( float ) log( getU2( fp )) / 20.7944f; + break; + + case ID_SMAN: + surf->smooth = getF4( fp ); + break; + + case ID_REFL: + surf->reflection.val.val = getI2( fp ) / 256.0f; + break; + + case ID_RFLT: + surf->reflection.options = getU2( fp ); + break; + + case ID_RIMG: + s = getS0( fp ); + surf->reflection.cindex = add_clip( s, &obj->clip, &obj->nclips ); + surf->reflection.options = 3; + break; + + case ID_RSAN: + surf->reflection.seam_angle = getF4( fp ); + break; + + case ID_TRAN: + surf->transparency.val.val = getI2( fp ) / 256.0f; + break; + + case ID_RIND: + surf->eta.val = getF4( fp ); + break; + + case ID_BTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->bump.tex, tex ); + break; + + case ID_CTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->color.tex, tex ); + break; + + case ID_DTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->diffuse.tex, tex ); + break; + + case ID_LTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->luminosity.tex, tex ); + break; + + case ID_RTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->reflection.val.tex, tex ); + break; + + case ID_STEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->specularity.tex, tex ); + break; + + case ID_TTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->transparency.val.tex, tex ); + break; + + case ID_TFLG: + flags = getU2( fp ); + + if ( flags & 1 ) i = 0; + if ( flags & 2 ) i = 1; + if ( flags & 4 ) i = 2; + tex->axis = i; + if ( tex->type == ID_IMAP ) + tex->param.imap.axis = i; + else + tex->param.proc.axis = i; + + if ( flags & 8 ) tex->tmap.coord_sys = 1; + if ( flags & 16 ) tex->negative = 1; + if ( flags & 32 ) tex->param.imap.pblend = 1; + if ( flags & 64 ) { + tex->param.imap.aa_strength = 1.0f; + tex->param.imap.aas_flags = 1; + } + break; + + case ID_TSIZ: + for ( i = 0; i < 3; i++ ) + tex->tmap.size.val[ i ] = getF4( fp ); + break; + + case ID_TCTR: + for ( i = 0; i < 3; i++ ) + tex->tmap.center.val[ i ] = getF4( fp ); + break; + + case ID_TFAL: + for ( i = 0; i < 3; i++ ) + tex->tmap.falloff.val[ i ] = getF4( fp ); + break; + + case ID_TVEL: + for ( i = 0; i < 3; i++ ) + v[ i ] = getF4( fp ); + tex->tmap.center.eindex = add_tvel( tex->tmap.center.val, v, + &obj->env, &obj->nenvs ); + break; + + case ID_TCLR: + if ( tex->type == ID_PROC ) + for ( i = 0; i < 3; i++ ) + tex->param.proc.value[ i ] = getU1( fp ) / 255.0f; + break; + + case ID_TVAL: + tex->param.proc.value[ 0 ] = getI2( fp ) / 256.0f; + break; + + case ID_TAMP: + if ( tex->type == ID_IMAP ) + tex->param.imap.amplitude.val = getF4( fp ); + break; + + case ID_TIMG: + s = getS0( fp ); + tex->param.imap.cindex = add_clip( s, &obj->clip, &obj->nclips ); + break; + + case ID_TAAS: + tex->param.imap.aa_strength = getF4( fp ); + tex->param.imap.aas_flags = 1; + break; + + case ID_TREF: + tex->tmap.ref_object = getbytes( fp, sz ); + break; + + case ID_TOPC: + tex->opacity.val = getF4( fp ); + break; + + case ID_TFP0: + if ( tex->type == ID_IMAP ) + tex->param.imap.wrapw.val = getF4( fp ); + break; + + case ID_TFP1: + if ( tex->type == ID_IMAP ) + tex->param.imap.wraph.val = getF4( fp ); + break; + + case ID_SHDR: + shdr = _pico_calloc( 1, sizeof( lwPlugin )); + if ( !shdr ) goto Fail; + shdr->name = getbytes( fp, sz ); + lwListAdd( (void *) &surf->shader, shdr ); + surf->nshaders++; + break; + + case ID_SDAT: + shdr->data = getbytes( fp, sz ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the SURF chunk? */ + + if ( cksize <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return surf; + +Fail: + if ( surf ) lwFreeSurface( surf ); + return NULL; +} + + +/* +====================================================================== +lwGetPolygons5() + +Read polygon records from a POLS chunk in an LWOB file. The polygons +are added to the array in the lwPolygonList. +====================================================================== */ + +int lwGetPolygons5( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ) +{ + lwPolygon *pp; + lwPolVert *pv; + unsigned char *buf, *bp; + int i, j, nv, nverts, npols; + + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + buf = getbytes( fp, cksize ); + if ( !buf ) goto Fail; + + /* count the polygons and vertices */ + + nverts = 0; + npols = 0; + bp = buf; + + while ( bp < buf + cksize ) { + nv = sgetU2( &bp ); + nverts += nv; + npols++; + bp += 2 * nv; + i = sgetI2( &bp ); + if ( i < 0 ) bp += 2; /* detail polygons */ + } + + if ( !lwAllocPolygons( plist, npols, nverts )) + goto Fail; + + /* fill in the new polygons */ + + bp = buf; + pp = plist->pol + plist->offset; + pv = plist->pol[ 0 ].v + plist->voffset; + + for ( i = 0; i < npols; i++ ) { + nv = sgetU2( &bp ); + + pp->nverts = nv; + pp->type = ID_FACE; + if ( !pp->v ) pp->v = pv; + for ( j = 0; j < nv; j++ ) + pv[ j ].index = sgetU2( &bp ) + ptoffset; + j = sgetI2( &bp ); + if ( j < 0 ) { + j = -j; + bp += 2; + } + j -= 1; + pp->surf = ( lwSurface * ) j; + + pp++; + pv += nv; + } + + _pico_free( buf ); + return 1; + +Fail: + if ( buf ) _pico_free( buf ); + lwFreePolygons( plist ); + return 0; +} + + +/* +====================================================================== +getLWObject5() + +Returns the contents of an LWOB, given its filename, or NULL if the +file couldn't be loaded. On failure, failID and failpos can be used +to diagnose the cause. + +1. If the file isn't an LWOB, failpos will contain 12 and failID will + be unchanged. + +2. If an error occurs while reading an LWOB, failID will contain the + most recently read IFF chunk ID, and failpos will contain the + value returned by _pico_memstream_tell() at the time of the failure. + +3. If the file couldn't be opened, or an error occurs while reading + the first 12 bytes, both failID and failpos will be unchanged. + +If you don't need this information, failID and failpos can be NULL. +====================================================================== */ + +lwObject *lwGetObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ) +{ + lwObject *object; + lwLayer *layer; + lwNode *node; + unsigned int id, formsize, type, cksize; + + + /* open the file */ + + if ( !fp ) return NULL; + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + return NULL; + } + + /* LWOB? */ + + if ( id != ID_FORM || type != ID_LWOB ) { + if ( failpos ) *failpos = 12; + return NULL; + } + + /* allocate an object and a default layer */ + + object = _pico_calloc( 1, sizeof( lwObject )); + if ( !object ) goto Fail; + + layer = _pico_calloc( 1, sizeof( lwLayer )); + if ( !layer ) goto Fail; + object->layer = layer; + object->nlayers = 1; + + /* get the first chunk header */ + + id = getU4( fp ); + cksize = getU4( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process chunks as they're encountered */ + + while ( 1 ) { + cksize += cksize & 1; + + switch ( id ) + { + case ID_PNTS: + if ( !lwGetPoints( fp, cksize, &layer->point )) + goto Fail; + break; + + case ID_POLS: + if ( !lwGetPolygons5( fp, cksize, &layer->polygon, + layer->point.offset )) + goto Fail; + break; + + case ID_SRFS: + if ( !lwGetTags( fp, cksize, &object->taglist )) + goto Fail; + break; + + case ID_SURF: + node = ( lwNode * ) lwGetSurface5( fp, cksize, object ); + if ( !node ) goto Fail; + lwListAdd( (void *) &object->surf, node ); + object->nsurfs++; + break; + + default: + _pico_memstream_seek( fp, cksize, PICO_SEEK_CUR ); + break; + } + + /* end of the file? */ + + if ( formsize <= _pico_memstream_tell( fp ) - 8 ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + cksize = getU4( fp ); + if ( 8 != get_flen() ) goto Fail; + } + + lwGetBoundingBox( &layer->point, layer->bbox ); + lwGetPolyNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointPolygons( &layer->point, &layer->polygon )) goto Fail; + if ( !lwResolvePolySurfaces( &layer->polygon, &object->taglist, + &object->surf, &object->nsurfs )) goto Fail; + lwGetVertNormals( &layer->point, &layer->polygon ); + + return object; + +Fail: + if ( failID ) *failID = id; + if ( fp ) { + if ( failpos ) *failpos = _pico_memstream_tell( fp ); + } + lwFreeObject( object ); + return NULL; +} + +int lwValidateObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ) +{ + unsigned int id, formsize, type; + + + /* open the file */ + + if ( !fp ) return PICO_PMV_ERROR_MEMORY; + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + return PICO_PMV_ERROR_SIZE; + } + + /* LWOB? */ + + if ( id != ID_FORM || type != ID_LWOB ) { + if ( failpos ) *failpos = 12; + return PICO_PMV_ERROR_IDENT; + } + + return PICO_PMV_OK; +} diff --git a/tools/urt/libs/picomodel/lwo/pntspols.c b/tools/urt/libs/picomodel/lwo/pntspols.c new file mode 100644 index 00000000..f38f6ea7 --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/pntspols.c @@ -0,0 +1,537 @@ +/* +====================================================================== +pntspols.c + +Point and polygon functions for an LWO2 reader. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwFreePoints() + +Free the memory used by an lwPointList. +====================================================================== */ + +void lwFreePoints( lwPointList *point ) +{ + int i; + + if ( point ) { + if ( point->pt ) { + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].pol ) _pico_free( point->pt[ i ].pol ); + if ( point->pt[ i ].vm ) _pico_free( point->pt[ i ].vm ); + } + _pico_free( point->pt ); + } + memset( point, 0, sizeof( lwPointList )); + } +} + + +/* +====================================================================== +lwFreePolygons() + +Free the memory used by an lwPolygonList. +====================================================================== */ + +void lwFreePolygons( lwPolygonList *plist ) +{ + int i, j; + + if ( plist ) { + if ( plist->pol ) { + for ( i = 0; i < plist->count; i++ ) { + if ( plist->pol[ i ].v ) { + for ( j = 0; j < plist->pol[ i ].nverts; j++ ) + if ( plist->pol[ i ].v[ j ].vm ) + _pico_free( plist->pol[ i ].v[ j ].vm ); + } + } + if ( plist->pol[ 0 ].v ) + _pico_free( plist->pol[ 0 ].v ); + _pico_free( plist->pol ); + } + memset( plist, 0, sizeof( lwPolygonList )); + } +} + + +/* +====================================================================== +lwGetPoints() + +Read point records from a PNTS chunk in an LWO2 file. The points are +added to the array in the lwPointList. +====================================================================== */ + +int lwGetPoints( picoMemStream_t *fp, int cksize, lwPointList *point ) +{ + float *f; + int np, i, j; + + if ( cksize == 1 ) return 1; + + /* extend the point array to hold the new points */ + + np = cksize / 12; + point->offset = point->count; + point->count += np; + if ( !_pico_realloc( (void *) &point->pt, (point->count - np) * sizeof( lwPoint ), point->count * sizeof( lwPoint )) ) + return 0; + memset( &point->pt[ point->offset ], 0, np * sizeof( lwPoint )); + + /* read the whole chunk */ + + f = ( float * ) getbytes( fp, cksize ); + if ( !f ) return 0; + revbytes( f, 4, np * 3 ); + + /* assign position values */ + + for ( i = 0, j = 0; i < np; i++, j += 3 ) { + point->pt[ i ].pos[ 0 ] = f[ j ]; + point->pt[ i ].pos[ 1 ] = f[ j + 1 ]; + point->pt[ i ].pos[ 2 ] = f[ j + 2 ]; + } + + _pico_free( f ); + return 1; +} + + +/* +====================================================================== +lwGetBoundingBox() + +Calculate the bounding box for a point list, but only if the bounding +box hasn't already been initialized. +====================================================================== */ + +void lwGetBoundingBox( lwPointList *point, float bbox[] ) +{ + int i, j; + + if ( point->count == 0 ) return; + + for ( i = 0; i < 6; i++ ) + if ( bbox[ i ] != 0.0f ) return; + + bbox[ 0 ] = bbox[ 1 ] = bbox[ 2 ] = 1e20f; + bbox[ 3 ] = bbox[ 4 ] = bbox[ 5 ] = -1e20f; + for ( i = 0; i < point->count; i++ ) { + for ( j = 0; j < 3; j++ ) { + if ( bbox[ j ] > point->pt[ i ].pos[ j ] ) + bbox[ j ] = point->pt[ i ].pos[ j ]; + if ( bbox[ j + 3 ] < point->pt[ i ].pos[ j ] ) + bbox[ j + 3 ] = point->pt[ i ].pos[ j ]; + } + } +} + + +/* +====================================================================== +lwAllocPolygons() + +Allocate or extend the polygon arrays to hold new records. +====================================================================== */ + +int lwAllocPolygons( lwPolygonList *plist, int npols, int nverts ) +{ + int i; + + plist->offset = plist->count; + plist->count += npols; + if ( !_pico_realloc( (void *) &plist->pol, (plist->count - npols) * sizeof( lwPolygon ), plist->count * sizeof( lwPolygon )) ) + return 0; + memset( plist->pol + plist->offset, 0, npols * sizeof( lwPolygon )); + + plist->voffset = plist->vcount; + plist->vcount += nverts; + if ( !_pico_realloc( (void *) &plist->pol[ 0 ].v, (plist->vcount - nverts) * sizeof( lwPolVert ), plist->vcount * sizeof( lwPolVert )) ) + return 0; + memset( plist->pol[ 0 ].v + plist->voffset, 0, nverts * sizeof( lwPolVert )); + + /* fix up the old vertex pointers */ + + for ( i = 1; i < plist->offset; i++ ) + plist->pol[ i ].v = plist->pol[ i - 1 ].v + plist->pol[ i - 1 ].nverts; + + return 1; +} + + +/* +====================================================================== +lwGetPolygons() + +Read polygon records from a POLS chunk in an LWO2 file. The polygons +are added to the array in the lwPolygonList. +====================================================================== */ + +int lwGetPolygons( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ) +{ + lwPolygon *pp; + lwPolVert *pv; + unsigned char *buf, *bp; + int i, j, flags, nv, nverts, npols; + unsigned int type; + + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + type = getU4( fp ); + buf = getbytes( fp, cksize - 4 ); + if ( cksize != get_flen() ) goto Fail; + + /* count the polygons and vertices */ + + nverts = 0; + npols = 0; + bp = buf; + + while ( bp < buf + cksize - 4 ) { + nv = sgetU2( &bp ); + nv &= 0x03FF; + nverts += nv; + npols++; + for ( i = 0; i < nv; i++ ) + j = sgetVX( &bp ); + } + + if ( !lwAllocPolygons( plist, npols, nverts )) + goto Fail; + + /* fill in the new polygons */ + + bp = buf; + pp = plist->pol + plist->offset; + pv = plist->pol[ 0 ].v + plist->voffset; + + for ( i = 0; i < npols; i++ ) { + nv = sgetU2( &bp ); + flags = nv & 0xFC00; + nv &= 0x03FF; + + pp->nverts = nv; + pp->flags = flags; + pp->type = type; + if ( !pp->v ) pp->v = pv; + for ( j = 0; j < nv; j++ ) + pp->v[ j ].index = sgetVX( &bp ) + ptoffset; + + pp++; + pv += nv; + } + + _pico_free( buf ); + return 1; + +Fail: + if ( buf ) _pico_free( buf ); + lwFreePolygons( plist ); + return 0; +} + + +/* +====================================================================== +lwGetPolyNormals() + +Calculate the polygon normals. By convention, LW's polygon normals +are found as the cross product of the first and last edges. It's +undefined for one- and two-point polygons. +====================================================================== */ + +void lwGetPolyNormals( lwPointList *point, lwPolygonList *polygon ) +{ + int i, j; + float p1[ 3 ], p2[ 3 ], pn[ 3 ], v1[ 3 ], v2[ 3 ]; + + for ( i = 0; i < polygon->count; i++ ) { + if ( polygon->pol[ i ].nverts < 3 ) continue; + for ( j = 0; j < 3; j++ ) { + p1[ j ] = point->pt[ polygon->pol[ i ].v[ 0 ].index ].pos[ j ]; + p2[ j ] = point->pt[ polygon->pol[ i ].v[ 1 ].index ].pos[ j ]; + pn[ j ] = point->pt[ polygon->pol[ i ].v[ + polygon->pol[ i ].nverts - 1 ].index ].pos[ j ]; + } + + for ( j = 0; j < 3; j++ ) { + v1[ j ] = p2[ j ] - p1[ j ]; + v2[ j ] = pn[ j ] - p1[ j ]; + } + + cross( v1, v2, polygon->pol[ i ].norm ); + normalize( polygon->pol[ i ].norm ); + } +} + + +/* +====================================================================== +lwGetPointPolygons() + +For each point, fill in the indexes of the polygons that share the +point. Returns 0 if any of the memory allocations fail, otherwise +returns 1. +====================================================================== */ + +int lwGetPointPolygons( lwPointList *point, lwPolygonList *polygon ) +{ + int i, j, k; + + /* count the number of polygons per point */ + + for ( i = 0; i < polygon->count; i++ ) + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) + ++point->pt[ polygon->pol[ i ].v[ j ].index ].npols; + + /* alloc per-point polygon arrays */ + + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].npols == 0 ) continue; + point->pt[ i ].pol = _pico_calloc( point->pt[ i ].npols, sizeof( int )); + if ( !point->pt[ i ].pol ) return 0; + point->pt[ i ].npols = 0; + } + + /* fill in polygon array for each point */ + + for ( i = 0; i < polygon->count; i++ ) { + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) { + k = polygon->pol[ i ].v[ j ].index; + point->pt[ k ].pol[ point->pt[ k ].npols ] = i; + ++point->pt[ k ].npols; + } + } + + return 1; +} + + +/* +====================================================================== +lwResolvePolySurfaces() + +Convert tag indexes into actual lwSurface pointers. If any polygons +point to tags for which no corresponding surface can be found, a +default surface is created. +====================================================================== */ + +int lwResolvePolySurfaces( lwPolygonList *polygon, lwTagList *tlist, + lwSurface **surf, int *nsurfs ) +{ + lwSurface **s, *st; + int i, index; + + if ( tlist->count == 0 ) return 1; + + s = _pico_calloc( tlist->count, sizeof( lwSurface * )); + if ( !s ) return 0; + + for ( i = 0; i < tlist->count; i++ ) { + st = *surf; + while ( st ) { + if ( !strcmp( st->name, tlist->tag[ i ] )) { + s[ i ] = st; + break; + } + st = st->next; + } + } + + for ( i = 0; i < polygon->count; i++ ) { + index = ( int ) polygon->pol[ i ].surf; + if ( index < 0 || index > tlist->count ) return 0; + if ( !s[ index ] ) { + s[ index ] = lwDefaultSurface(); + if ( !s[ index ] ) return 0; + s[ index ]->name = _pico_alloc( strlen( tlist->tag[ index ] ) + 1 ); + if ( !s[ index ]->name ) return 0; + strcpy( s[ index ]->name, tlist->tag[ index ] ); + lwListAdd( (void *) surf, s[ index ] ); + *nsurfs = *nsurfs + 1; + } + polygon->pol[ i ].surf = s[ index ]; + } + + _pico_free( s ); + return 1; +} + + +/* +====================================================================== +lwGetVertNormals() + +Calculate the vertex normals. For each polygon vertex, sum the +normals of the polygons that share the point. If the normals of the +current and adjacent polygons form an angle greater than the max +smoothing angle for the current polygon's surface, the normal of the +adjacent polygon is excluded from the sum. It's also excluded if the +polygons aren't in the same smoothing group. + +Assumes that lwGetPointPolygons(), lwGetPolyNormals() and +lwResolvePolySurfaces() have already been called. +====================================================================== */ + +void lwGetVertNormals( lwPointList *point, lwPolygonList *polygon ) +{ + int j, k, n, g, h, p; + float a; + + for ( j = 0; j < polygon->count; j++ ) { + for ( n = 0; n < polygon->pol[ j ].nverts; n++ ) { + for ( k = 0; k < 3; k++ ) + polygon->pol[ j ].v[ n ].norm[ k ] = polygon->pol[ j ].norm[ k ]; + + if ( polygon->pol[ j ].surf->smooth <= 0 ) continue; + + p = polygon->pol[ j ].v[ n ].index; + + for ( g = 0; g < point->pt[ p ].npols; g++ ) { + h = point->pt[ p ].pol[ g ]; + if ( h == j ) continue; + + if ( polygon->pol[ j ].smoothgrp != polygon->pol[ h ].smoothgrp ) + continue; + a = vecangle( polygon->pol[ j ].norm, polygon->pol[ h ].norm ); + if ( a > polygon->pol[ j ].surf->smooth ) continue; + + for ( k = 0; k < 3; k++ ) + polygon->pol[ j ].v[ n ].norm[ k ] += polygon->pol[ h ].norm[ k ]; + } + + normalize( polygon->pol[ j ].v[ n ].norm ); + } + } +} + + +/* +====================================================================== +lwFreeTags() + +Free memory used by an lwTagList. +====================================================================== */ + +void lwFreeTags( lwTagList *tlist ) +{ + int i; + + if ( tlist ) { + if ( tlist->tag ) { + for ( i = 0; i < tlist->count; i++ ) + if ( tlist->tag[ i ] ) _pico_free( tlist->tag[ i ] ); + _pico_free( tlist->tag ); + } + memset( tlist, 0, sizeof( lwTagList )); + } +} + + +/* +====================================================================== +lwGetTags() + +Read tag strings from a TAGS chunk in an LWO2 file. The tags are +added to the lwTagList array. +====================================================================== */ + +int lwGetTags( picoMemStream_t *fp, int cksize, lwTagList *tlist ) +{ + char *buf, *bp; + int i, len, ntags; + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + buf = getbytes( fp, cksize ); + if ( !buf ) return 0; + + /* count the strings */ + + ntags = 0; + bp = buf; + while ( bp < buf + cksize ) { + len = strlen( bp ) + 1; + len += len & 1; + bp += len; + ++ntags; + } + + /* expand the string array to hold the new tags */ + + tlist->offset = tlist->count; + tlist->count += ntags; + if ( !_pico_realloc( (void *) &tlist->tag, (tlist->count - ntags) * sizeof( char * ), tlist->count * sizeof( char * )) ) + goto Fail; + memset( &tlist->tag[ tlist->offset ], 0, ntags * sizeof( char * )); + + /* copy the new tags to the tag array */ + + bp = buf; + for ( i = 0; i < ntags; i++ ) + tlist->tag[ i + tlist->offset ] = sgetS0( (unsigned char **) &bp ); + + _pico_free( buf ); + return 1; + +Fail: + if ( buf ) _pico_free( buf ); + return 0; +} + + +/* +====================================================================== +lwGetPolygonTags() + +Read polygon tags from a PTAG chunk in an LWO2 file. +====================================================================== */ + +int lwGetPolygonTags( picoMemStream_t *fp, int cksize, lwTagList *tlist, + lwPolygonList *plist ) +{ + unsigned int type; + int rlen = 0, i, j; + + set_flen( 0 ); + type = getU4( fp ); + rlen = get_flen(); + if ( rlen < 0 ) return 0; + + if ( type != ID_SURF && type != ID_PART && type != ID_SMGP ) { + _pico_memstream_seek( fp, cksize - 4, PICO_SEEK_CUR ); + return 1; + } + + while ( rlen < cksize ) { + i = getVX( fp ) + plist->offset; + j = getVX( fp ) + tlist->offset; + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) return 0; + + switch ( type ) { + case ID_SURF: plist->pol[ i ].surf = ( lwSurface * ) j; break; + case ID_PART: plist->pol[ i ].part = j; break; + case ID_SMGP: plist->pol[ i ].smoothgrp = j; break; + } + } + + return 1; +} diff --git a/tools/urt/libs/picomodel/lwo/surface.c b/tools/urt/libs/picomodel/lwo/surface.c new file mode 100644 index 00000000..cdb05e58 --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/surface.c @@ -0,0 +1,1005 @@ +/* +====================================================================== +surface.c + +Surface functions for an LWO2 reader. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwFreePlugin() + +Free the memory used by an lwPlugin. +====================================================================== */ + +void lwFreePlugin( lwPlugin *p ) +{ + if ( p ) { + if ( p->ord ) _pico_free( p->ord ); + if ( p->name ) _pico_free( p->name ); + if ( p->data ) _pico_free( p->data ); + _pico_free( p ); + } +} + + +/* +====================================================================== +lwFreeTexture() + +Free the memory used by an lwTexture. +====================================================================== */ + +void lwFreeTexture( lwTexture *t ) +{ + if ( t ) { + if ( t->ord ) _pico_free( t->ord ); + switch ( t->type ) { + case ID_IMAP: + if ( t->param.imap.vmap_name ) _pico_free( t->param.imap.vmap_name ); + if ( t->tmap.ref_object ) _pico_free( t->tmap.ref_object ); + break; + case ID_PROC: + if ( t->param.proc.name ) _pico_free( t->param.proc.name ); + if ( t->param.proc.data ) _pico_free( t->param.proc.data ); + break; + case ID_GRAD: + if ( t->param.grad.key ) _pico_free( t->param.grad.key ); + if ( t->param.grad.ikey ) _pico_free( t->param.grad.ikey ); + break; + } + _pico_free( t ); + } +} + + +/* +====================================================================== +lwFreeSurface() + +Free the memory used by an lwSurface. +====================================================================== */ + +void lwFreeSurface( lwSurface *surf ) +{ + if ( surf ) { + if ( surf->name ) _pico_free( surf->name ); + if ( surf->srcname ) _pico_free( surf->srcname ); + + lwListFree( surf->shader, (void *) lwFreePlugin ); + + lwListFree( surf->color.tex, (void *) lwFreeTexture ); + lwListFree( surf->luminosity.tex, (void *) lwFreeTexture ); + lwListFree( surf->diffuse.tex, (void *) lwFreeTexture ); + lwListFree( surf->specularity.tex, (void *) lwFreeTexture ); + lwListFree( surf->glossiness.tex, (void *) lwFreeTexture ); + lwListFree( surf->reflection.val.tex, (void *) lwFreeTexture ); + lwListFree( surf->transparency.val.tex, (void *) lwFreeTexture ); + lwListFree( surf->eta.tex, (void *) lwFreeTexture ); + lwListFree( surf->translucency.tex, (void *) lwFreeTexture ); + lwListFree( surf->bump.tex, (void *) lwFreeTexture ); + + _pico_free( surf ); + } +} + + +/* +====================================================================== +lwGetTHeader() + +Read a texture map header from a SURF.BLOK in an LWO2 file. This is +the first subchunk in a BLOK, and its contents are common to all three +texture types. +====================================================================== */ + +int lwGetTHeader( picoMemStream_t *fp, int hsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int pos, rlen; + + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* ordinal string */ + + tex->ord = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_CHAN: + tex->chan = getU4( fp ); + break; + + case ID_OPAC: + tex->opac_type = getU2( fp ); + tex->opacity.val = getF4( fp ); + tex->opacity.eindex = getVX( fp ); + break; + + case ID_ENAB: + tex->enabled = getU2( fp ); + break; + + case ID_NEGA: + tex->negative = getU2( fp ); + break; + + case ID_AXIS: + tex->axis = getU2( fp ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the texture header subchunk? */ + + if ( hsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetTMap() + +Read a texture map from a SURF.BLOK in an LWO2 file. The TMAP +defines the mapping from texture to world or object coordinates. +====================================================================== */ + +int lwGetTMap( picoMemStream_t *fp, int tmapsz, lwTMap *tmap ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos, i; + + pos = _pico_memstream_tell( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_SIZE: + for ( i = 0; i < 3; i++ ) + tmap->size.val[ i ] = getF4( fp ); + tmap->size.eindex = getVX( fp ); + break; + + case ID_CNTR: + for ( i = 0; i < 3; i++ ) + tmap->center.val[ i ] = getF4( fp ); + tmap->center.eindex = getVX( fp ); + break; + + case ID_ROTA: + for ( i = 0; i < 3; i++ ) + tmap->rotate.val[ i ] = getF4( fp ); + tmap->rotate.eindex = getVX( fp ); + break; + + case ID_FALL: + tmap->fall_type = getU2( fp ); + for ( i = 0; i < 3; i++ ) + tmap->falloff.val[ i ] = getF4( fp ); + tmap->falloff.eindex = getVX( fp ); + break; + + case ID_OREF: + tmap->ref_object = getS0( fp ); + break; + + case ID_CSYS: + tmap->coord_sys = getU2( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the TMAP subchunk? */ + + if ( tmapsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetImageMap() + +Read an lwImageMap from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetImageMap( picoMemStream_t *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos; + + pos = _pico_memstream_tell( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_PROJ: + tex->param.imap.projection = getU2( fp ); + break; + + case ID_VMAP: + tex->param.imap.vmap_name = getS0( fp ); + break; + + case ID_AXIS: + tex->param.imap.axis = getU2( fp ); + break; + + case ID_IMAG: + tex->param.imap.cindex = getVX( fp ); + break; + + case ID_WRAP: + tex->param.imap.wrapw_type = getU2( fp ); + tex->param.imap.wraph_type = getU2( fp ); + break; + + case ID_WRPW: + tex->param.imap.wrapw.val = getF4( fp ); + tex->param.imap.wrapw.eindex = getVX( fp ); + break; + + case ID_WRPH: + tex->param.imap.wraph.val = getF4( fp ); + tex->param.imap.wraph.eindex = getVX( fp ); + break; + + case ID_AAST: + tex->param.imap.aas_flags = getU2( fp ); + tex->param.imap.aa_strength = getF4( fp ); + break; + + case ID_PIXB: + tex->param.imap.pblend = getU2( fp ); + break; + + case ID_STCK: + tex->param.imap.stck.val = getF4( fp ); + tex->param.imap.stck.eindex = getVX( fp ); + break; + + case ID_TAMP: + tex->param.imap.amplitude.val = getF4( fp ); + tex->param.imap.amplitude.eindex = getVX( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the image map? */ + + if ( rsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetProcedural() + +Read an lwProcedural from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetProcedural( picoMemStream_t *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos; + + pos = _pico_memstream_tell( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_AXIS: + tex->param.proc.axis = getU2( fp ); + break; + + case ID_VALU: + tex->param.proc.value[ 0 ] = getF4( fp ); + if ( sz >= 8 ) tex->param.proc.value[ 1 ] = getF4( fp ); + if ( sz >= 12 ) tex->param.proc.value[ 2 ] = getF4( fp ); + break; + + case ID_FUNC: + tex->param.proc.name = getS0( fp ); + rlen = get_flen(); + tex->param.proc.data = getbytes( fp, sz - rlen ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the procedural block? */ + + if ( rsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetGradient() + +Read an lwGradient from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetGradient( picoMemStream_t *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos, i, j, nkeys; + + pos = _pico_memstream_tell( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_PNAM: + tex->param.grad.paramname = getS0( fp ); + break; + + case ID_INAM: + tex->param.grad.itemname = getS0( fp ); + break; + + case ID_GRST: + tex->param.grad.start = getF4( fp ); + break; + + case ID_GREN: + tex->param.grad.end = getF4( fp ); + break; + + case ID_GRPT: + tex->param.grad.repeat = getU2( fp ); + break; + + case ID_FKEY: + nkeys = sz / sizeof( lwGradKey ); + tex->param.grad.key = _pico_calloc( nkeys, sizeof( lwGradKey )); + if ( !tex->param.grad.key ) return 0; + for ( i = 0; i < nkeys; i++ ) { + tex->param.grad.key[ i ].value = getF4( fp ); + for ( j = 0; j < 4; j++ ) + tex->param.grad.key[ i ].rgba[ j ] = getF4( fp ); + } + break; + + case ID_IKEY: + nkeys = sz / 2; + tex->param.grad.ikey = _pico_calloc( nkeys, sizeof( short )); + if ( !tex->param.grad.ikey ) return 0; + for ( i = 0; i < nkeys; i++ ) + tex->param.grad.ikey[ i ] = getU2( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the gradient? */ + + if ( rsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetTexture() + +Read an lwTexture from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +lwTexture *lwGetTexture( picoMemStream_t *fp, int bloksz, unsigned int type ) +{ + lwTexture *tex; + unsigned short sz; + int ok; + + tex = _pico_calloc( 1, sizeof( lwTexture )); + if ( !tex ) return NULL; + + tex->type = type; + tex->tmap.size.val[ 0 ] = + tex->tmap.size.val[ 1 ] = + tex->tmap.size.val[ 2 ] = 1.0f; + tex->opacity.val = 1.0f; + tex->enabled = 1; + + sz = getU2( fp ); + if ( !lwGetTHeader( fp, sz, tex )) { + _pico_free( tex ); + return NULL; + } + + sz = bloksz - sz - 6; + switch ( type ) { + case ID_IMAP: ok = lwGetImageMap( fp, sz, tex ); break; + case ID_PROC: ok = lwGetProcedural( fp, sz, tex ); break; + case ID_GRAD: ok = lwGetGradient( fp, sz, tex ); break; + default: + ok = !_pico_memstream_seek( fp, sz, PICO_SEEK_CUR ); + } + + if ( !ok ) { + lwFreeTexture( tex ); + return NULL; + } + + set_flen( bloksz ); + return tex; +} + + +/* +====================================================================== +lwGetShader() + +Read a shader record from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +lwPlugin *lwGetShader( picoMemStream_t *fp, int bloksz ) +{ + lwPlugin *shdr; + unsigned int id; + unsigned short sz; + int hsz, rlen, pos; + + shdr = _pico_calloc( 1, sizeof( lwPlugin )); + if ( !shdr ) return NULL; + + pos = _pico_memstream_tell( fp ); + set_flen( 0 ); + hsz = getU2( fp ); + shdr->ord = getS0( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( hsz > 0 ) { + sz += sz & 1; + hsz -= sz; + if ( id == ID_ENAB ) { + shdr->flags = getU2( fp ); + break; + } + else { + _pico_memstream_seek( fp, sz, PICO_SEEK_CUR ); + id = getU4( fp ); + sz = getU2( fp ); + } + } + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_FUNC: + shdr->name = getS0( fp ); + rlen = get_flen(); + shdr->data = getbytes( fp, sz - rlen ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the shader block? */ + + if ( bloksz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return shdr; + +Fail: + lwFreePlugin( shdr ); + return NULL; +} + + +/* +====================================================================== +compare_textures() +compare_shaders() + +Callbacks for the lwListInsert() function, which is called to add +textures to surface channels and shaders to surfaces. +====================================================================== */ + +static int compare_textures( lwTexture *a, lwTexture *b ) +{ + return strcmp( a->ord, b->ord ); +} + + +static int compare_shaders( lwPlugin *a, lwPlugin *b ) +{ + return strcmp( a->ord, b->ord ); +} + + +/* +====================================================================== +add_texture() + +Finds the surface channel (lwTParam or lwCParam) to which a texture is +applied, then calls lwListInsert(). +====================================================================== */ + +static int add_texture( lwSurface *surf, lwTexture *tex ) +{ + lwTexture **list; + + switch ( tex->chan ) { + case ID_COLR: list = &surf->color.tex; break; + case ID_LUMI: list = &surf->luminosity.tex; break; + case ID_DIFF: list = &surf->diffuse.tex; break; + case ID_SPEC: list = &surf->specularity.tex; break; + case ID_GLOS: list = &surf->glossiness.tex; break; + case ID_REFL: list = &surf->reflection.val.tex; break; + case ID_TRAN: list = &surf->transparency.val.tex; break; + case ID_RIND: list = &surf->eta.tex; break; + case ID_TRNL: list = &surf->translucency.tex; break; + case ID_BUMP: list = &surf->bump.tex; break; + default: return 0; + } + + lwListInsert( (void **) list, tex, ( void *) compare_textures ); + return 1; +} + + +/* +====================================================================== +lwDefaultSurface() + +Allocate and initialize a surface. +====================================================================== */ + +lwSurface *lwDefaultSurface( void ) +{ + lwSurface *surf; + + surf = _pico_calloc( 1, sizeof( lwSurface )); + if ( !surf ) return NULL; + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + return surf; +} + + +/* +====================================================================== +lwGetSurface() + +Read an lwSurface from an LWO2 file. +====================================================================== */ + +lwSurface *lwGetSurface( picoMemStream_t *fp, int cksize ) +{ + lwSurface *surf; + lwTexture *tex; + lwPlugin *shdr; + unsigned int id, type; + unsigned short sz; + int pos, rlen; + + + /* allocate the Surface structure */ + + surf = _pico_calloc( 1, sizeof( lwSurface )); + if ( !surf ) goto Fail; + + /* non-zero defaults */ + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* names */ + + surf->name = getS0( fp ); + surf->srcname = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_COLR: + surf->color.rgb[ 0 ] = getF4( fp ); + surf->color.rgb[ 1 ] = getF4( fp ); + surf->color.rgb[ 2 ] = getF4( fp ); + surf->color.eindex = getVX( fp ); + break; + + case ID_LUMI: + surf->luminosity.val = getF4( fp ); + surf->luminosity.eindex = getVX( fp ); + break; + + case ID_DIFF: + surf->diffuse.val = getF4( fp ); + surf->diffuse.eindex = getVX( fp ); + break; + + case ID_SPEC: + surf->specularity.val = getF4( fp ); + surf->specularity.eindex = getVX( fp ); + break; + + case ID_GLOS: + surf->glossiness.val = getF4( fp ); + surf->glossiness.eindex = getVX( fp ); + break; + + case ID_REFL: + surf->reflection.val.val = getF4( fp ); + surf->reflection.val.eindex = getVX( fp ); + break; + + case ID_RFOP: + surf->reflection.options = getU2( fp ); + break; + + case ID_RIMG: + surf->reflection.cindex = getVX( fp ); + break; + + case ID_RSAN: + surf->reflection.seam_angle = getF4( fp ); + break; + + case ID_TRAN: + surf->transparency.val.val = getF4( fp ); + surf->transparency.val.eindex = getVX( fp ); + break; + + case ID_TROP: + surf->transparency.options = getU2( fp ); + break; + + case ID_TIMG: + surf->transparency.cindex = getVX( fp ); + break; + + case ID_RIND: + surf->eta.val = getF4( fp ); + surf->eta.eindex = getVX( fp ); + break; + + case ID_TRNL: + surf->translucency.val = getF4( fp ); + surf->translucency.eindex = getVX( fp ); + break; + + case ID_BUMP: + surf->bump.val = getF4( fp ); + surf->bump.eindex = getVX( fp ); + break; + + case ID_SMAN: + surf->smooth = getF4( fp ); + break; + + case ID_SIDE: + surf->sideflags = getU2( fp ); + break; + + case ID_CLRH: + surf->color_hilite.val = getF4( fp ); + surf->color_hilite.eindex = getVX( fp ); + break; + + case ID_CLRF: + surf->color_filter.val = getF4( fp ); + surf->color_filter.eindex = getVX( fp ); + break; + + case ID_ADTR: + surf->add_trans.val = getF4( fp ); + surf->add_trans.eindex = getVX( fp ); + break; + + case ID_SHRP: + surf->dif_sharp.val = getF4( fp ); + surf->dif_sharp.eindex = getVX( fp ); + break; + + case ID_GVAL: + surf->glow.val = getF4( fp ); + surf->glow.eindex = getVX( fp ); + break; + + case ID_LINE: + surf->line.enabled = 1; + if ( sz >= 2 ) surf->line.flags = getU2( fp ); + if ( sz >= 6 ) surf->line.size.val = getF4( fp ); + if ( sz >= 8 ) surf->line.size.eindex = getVX( fp ); + break; + + case ID_ALPH: + surf->alpha_mode = getU2( fp ); + surf->alpha = getF4( fp ); + break; + + case ID_AVAL: + surf->alpha = getF4( fp ); + break; + + case ID_BLOK: + type = getU4( fp ); + + switch ( type ) { + case ID_IMAP: + case ID_PROC: + case ID_GRAD: + tex = lwGetTexture( fp, sz - 4, type ); + if ( !tex ) goto Fail; + if ( !add_texture( surf, tex )) + lwFreeTexture( tex ); + set_flen( 4 + get_flen() ); + break; + case ID_SHDR: + shdr = lwGetShader( fp, sz - 4 ); + if ( !shdr ) goto Fail; + lwListInsert( (void **) &surf->shader, shdr, (void *) compare_shaders ); + ++surf->nshaders; + set_flen( 4 + get_flen() ); + break; + } + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the SURF chunk? */ + + if ( cksize <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return surf; + +Fail: + if ( surf ) lwFreeSurface( surf ); + return NULL; +} diff --git a/tools/urt/libs/picomodel/lwo/vecmath.c b/tools/urt/libs/picomodel/lwo/vecmath.c new file mode 100644 index 00000000..44d317b0 --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/vecmath.c @@ -0,0 +1,37 @@ +/* +====================================================================== +vecmath.c + +Basic vector and matrix functions. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include + + +float dot( float a[], float b[] ) +{ + return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ]; +} + + +void cross( float a[], float b[], float c[] ) +{ + c[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ]; + c[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ]; + c[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ]; +} + + +void normalize( float v[] ) +{ + float r; + + r = ( float ) sqrt( dot( v, v )); + if ( r > 0 ) { + v[ 0 ] /= r; + v[ 1 ] /= r; + v[ 2 ] /= r; + } +} diff --git a/tools/urt/libs/picomodel/lwo/vmap.c b/tools/urt/libs/picomodel/lwo/vmap.c new file mode 100644 index 00000000..1a24bee0 --- /dev/null +++ b/tools/urt/libs/picomodel/lwo/vmap.c @@ -0,0 +1,243 @@ +/* +====================================================================== +vmap.c + +Vertex map functions for an LWO2 reader. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwFreeVMap() + +Free memory used by an lwVMap. +====================================================================== */ + +void lwFreeVMap( lwVMap *vmap ) +{ + if ( vmap ) { + if ( vmap->name ) _pico_free( vmap->name ); + if ( vmap->vindex ) _pico_free( vmap->vindex ); + if ( vmap->pindex ) _pico_free( vmap->pindex ); + if ( vmap->val ) { + if ( vmap->val[ 0 ] ) _pico_free( vmap->val[ 0 ] ); + _pico_free( vmap->val ); + } + _pico_free( vmap ); + } +} + + +/* +====================================================================== +lwGetVMap() + +Read an lwVMap from a VMAP or VMAD chunk in an LWO2. +====================================================================== */ + +lwVMap *lwGetVMap( picoMemStream_t *fp, int cksize, int ptoffset, int poloffset, + int perpoly ) +{ + unsigned char *buf, *bp; + lwVMap *vmap; + float *f; + int i, j, npts, rlen; + + + /* read the whole chunk */ + + set_flen( 0 ); + buf = getbytes( fp, cksize ); + if ( !buf ) return NULL; + + vmap = _pico_calloc( 1, sizeof( lwVMap )); + if ( !vmap ) { + _pico_free( buf ); + return NULL; + } + + /* initialize the vmap */ + + vmap->perpoly = perpoly; + + bp = buf; + set_flen( 0 ); + vmap->type = sgetU4( &bp ); + vmap->dim = sgetU2( &bp ); + vmap->name = sgetS0( &bp ); + rlen = get_flen(); + + /* count the vmap records */ + + npts = 0; + while ( bp < buf + cksize ) { + i = sgetVX( &bp ); + if ( perpoly ) + i = sgetVX( &bp ); + bp += vmap->dim * sizeof( float ); + ++npts; + } + + /* allocate the vmap */ + + vmap->nverts = npts; + vmap->vindex = _pico_calloc( npts, sizeof( int )); + if ( !vmap->vindex ) goto Fail; + if ( perpoly ) { + vmap->pindex = _pico_calloc( npts, sizeof( int )); + if ( !vmap->pindex ) goto Fail; + } + + if ( vmap->dim > 0 ) { + vmap->val = _pico_calloc( npts, sizeof( float * )); + if ( !vmap->val ) goto Fail; + f = _pico_alloc( npts * vmap->dim * sizeof( float )); + if ( !f ) goto Fail; + for ( i = 0; i < npts; i++ ) + vmap->val[ i ] = f + i * vmap->dim; + } + + /* fill in the vmap values */ + + bp = buf + rlen; + for ( i = 0; i < npts; i++ ) { + vmap->vindex[ i ] = sgetVX( &bp ); + if ( perpoly ) + vmap->pindex[ i ] = sgetVX( &bp ); + for ( j = 0; j < vmap->dim; j++ ) + vmap->val[ i ][ j ] = sgetF4( &bp ); + } + + _pico_free( buf ); + return vmap; + +Fail: + if ( buf ) _pico_free( buf ); + lwFreeVMap( vmap ); + return NULL; +} + + +/* +====================================================================== +lwGetPointVMaps() + +Fill in the lwVMapPt structure for each point. +====================================================================== */ + +int lwGetPointVMaps( lwPointList *point, lwVMap *vmap ) +{ + lwVMap *vm; + int i, j, n; + + /* count the number of vmap values for each point */ + + vm = vmap; + while ( vm ) { + if ( !vm->perpoly ) + for ( i = 0; i < vm->nverts; i++ ) + ++point->pt[ vm->vindex[ i ]].nvmaps; + vm = vm->next; + } + + /* allocate vmap references for each mapped point */ + + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].nvmaps ) { + point->pt[ i ].vm = _pico_calloc( point->pt[ i ].nvmaps, sizeof( lwVMapPt )); + if ( !point->pt[ i ].vm ) return 0; + point->pt[ i ].nvmaps = 0; + } + } + + /* fill in vmap references for each mapped point */ + + vm = vmap; + while ( vm ) { + if ( !vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + j = vm->vindex[ i ]; + n = point->pt[ j ].nvmaps; + point->pt[ j ].vm[ n ].vmap = vm; + point->pt[ j ].vm[ n ].index = i; + ++point->pt[ j ].nvmaps; + } + } + vm = vm->next; + } + + return 1; +} + + +/* +====================================================================== +lwGetPolyVMaps() + +Fill in the lwVMapPt structure for each polygon vertex. +====================================================================== */ + +int lwGetPolyVMaps( lwPolygonList *polygon, lwVMap *vmap ) +{ + lwVMap *vm; + lwPolVert *pv; + int i, j; + + /* count the number of vmap values for each polygon vertex */ + + vm = vmap; + while ( vm ) { + if ( vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + for ( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ ) { + pv = &polygon->pol[ vm->pindex[ i ]].v[ j ]; + if ( vm->vindex[ i ] == pv->index ) { + ++pv->nvmaps; + break; + } + } + } + } + vm = vm->next; + } + + /* allocate vmap references for each mapped vertex */ + + for ( i = 0; i < polygon->count; i++ ) { + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) { + pv = &polygon->pol[ i ].v[ j ]; + if ( pv->nvmaps ) { + pv->vm = _pico_calloc( pv->nvmaps, sizeof( lwVMapPt )); + if ( !pv->vm ) return 0; + pv->nvmaps = 0; + } + } + } + + /* fill in vmap references for each mapped point */ + + vm = vmap; + while ( vm ) { + if ( vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + for ( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ ) { + pv = &polygon->pol[ vm->pindex[ i ]].v[ j ]; + if ( vm->vindex[ i ] == pv->index ) { + pv->vm[ pv->nvmaps ].vmap = vm; + pv->vm[ pv->nvmaps ].index = i; + ++pv->nvmaps; + break; + } + } + } + } + vm = vm->next; + } + + return 1; +} diff --git a/tools/urt/libs/picomodel/picointernal.c b/tools/urt/libs/picomodel/picointernal.c new file mode 100644 index 00000000..68dbf905 --- /dev/null +++ b/tools/urt/libs/picomodel/picointernal.c @@ -0,0 +1,1356 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PICOINTERNAL_C + + + +/* todo: + * - fix p->curLine for parser routines. increased twice + */ + +/* dependencies */ +#include +#include "picointernal.h" + + + +/* function pointers */ +void *(*_pico_ptr_malloc )( size_t ) = malloc; +void (*_pico_ptr_free )( void* ) = free; +void (*_pico_ptr_load_file )( char*, unsigned char**, int* ) = NULL; +void (*_pico_ptr_free_file )( void* ) = NULL; +void (*_pico_ptr_print )( int, const char* ) = NULL; + +typedef union +{ + float f; + char c[4]; +} +floatSwapUnion; + +/* _pico_alloc: + * kludged memory allocation wrapper + */ +void *_pico_alloc( size_t size ) +{ + void *ptr; + + /* some sanity checks */ + if( size == 0 ) + return NULL; + if (_pico_ptr_malloc == NULL) + return NULL; + + /* allocate memory */ + ptr = _pico_ptr_malloc(size); + if (ptr == NULL) + return NULL; + + /* zero out allocated memory */ + memset(ptr,0,size); + + /* return pointer to allocated memory */ + return ptr; +} + +/* _pico_calloc: + * _pico_calloc wrapper + */ +void *_pico_calloc( size_t num, size_t size ) +{ + void *ptr; + + /* some sanity checks */ + if( num == 0 || size == 0 ) + return NULL; + if (_pico_ptr_malloc == NULL) + return NULL; + + /* allocate memory */ + ptr = _pico_ptr_malloc(num*size); + if (ptr == NULL) + return NULL; + + /* zero out allocated memory */ + memset(ptr,0,num*size); + + /* return pointer to allocated memory */ + return ptr; +} + +/* _pico_realloc: + * memory reallocation wrapper (note: only grows, + * but never shrinks or frees) + */ +void *_pico_realloc( void **ptr, size_t oldSize, size_t newSize ) +{ + void *ptr2; + + /* sanity checks */ + if( ptr == NULL ) + return NULL; + if( newSize < oldSize ) + return *ptr; + if (_pico_ptr_malloc == NULL) + return NULL; + + /* allocate new pointer */ + ptr2 = _pico_alloc( newSize ); + if( ptr2 == NULL ) + return NULL; + + /* copy */ + if( *ptr != NULL ) + { + memcpy( ptr2, *ptr, oldSize ); + _pico_free( *ptr ); + } + + /* fix up and return */ + *ptr = ptr2; + return *ptr; +} + +/* _pico_clone_alloc: + * handy function for quick string allocation/copy. it clones + * the given string and returns a pointer to the new allocated + * clone (which must be freed by caller of course) or returns + * NULL on memory alloc or param errors. if 'size' is -1 the + * length of the input string is used, otherwise 'size' is used + * as custom clone size (the string is cropped to fit into mem + * if needed). -sea + */ +char *_pico_clone_alloc( const char *str ) +{ + char* cloned; + + /* sanity check */ + if (str == NULL) + return NULL; + + /* allocate memory */ + cloned = _pico_alloc( strlen(str) + 1 ); + if (cloned == NULL) + return NULL; + + /* copy input string to cloned string */ + strcpy( cloned, str ); + + /* return ptr to cloned string */ + return cloned; +} + +/* _pico_free: + * wrapper around the free function pointer + */ +void _pico_free( void *ptr ) +{ + /* sanity checks */ + if( ptr == NULL ) + return; + if (_pico_ptr_free == NULL) + return; + + /* free the allocated memory */ + _pico_ptr_free( ptr ); +} + +/* _pico_load_file: + * wrapper around the loadfile function pointer + */ +void _pico_load_file( char *name, unsigned char **buffer, int *bufSize ) +{ + /* sanity checks */ + if( name == NULL ) + { + *bufSize = -1; + return; + } + if (_pico_ptr_load_file == NULL) + { + *bufSize = -1; + return; + } + /* do the actual call to read in the file; */ + /* BUFFER IS ALLOCATED BY THE EXTERNAL LOADFILE FUNC */ + _pico_ptr_load_file( name,buffer,bufSize ); +} + +/* _pico_free_file: + * wrapper around the file free function pointer + */ +void _pico_free_file( void *buffer ) +{ + /* sanity checks */ + if( buffer == NULL ) + return; + + /* use default free */ + if( _pico_ptr_free_file == NULL ) + { + free( buffer ); + return; + } + /* free the allocated file */ + _pico_ptr_free_file( buffer ); +} + +/* _pico_printf: + * wrapper around the print function pointer -sea + */ +void _pico_printf( int level, const char *format, ...) +{ + char str[4096]; + va_list argptr; + + /* sanity checks */ + if( format == NULL ) + return; + if (_pico_ptr_print == NULL) + return; + + /* format string */ + va_start( argptr,format ); + vsprintf( str,format,argptr ); + va_end( argptr ); + + /* remove linefeeds */ + if (str[ strlen(str)-1 ] == '\n') + str[ strlen(str)-1 ] = '\0'; + + /* do the actual call */ + _pico_ptr_print( level,str ); +} + +/* _pico_first_token: + * trims everything after the first whitespace-delimited token + */ + +void _pico_first_token( char *str ) +{ + if( !str || !*str ) + return; + while( *str && !isspace( *str ) ) + *str++; + *str = '\0'; +} + +/* _pico_strltrim: + * left trims the given string -sea + */ +char *_pico_strltrim( char *str ) +{ + char *str1 = str, *str2 = str; + + while (isspace(*str2)) str2++; + if( str2 != str ) + while( *str2 != '\0' ) /* fix: ydnar */ + *str1++ = *str2++; + return str; +} + +/* _pico_strrtrim: + * right trims the given string -sea + */ +char *_pico_strrtrim( char *str ) +{ + if (str && *str) + { + char *str1 = str; + int allspace = 1; + + while (*str1) + { + if (allspace && !isspace(*str1)) allspace = 0; + str1++; + } + if (allspace) *str = '\0'; + else { + str1--; + while ((isspace(*str1)) && (str1 >= str)) + *str1-- = '\0'; + } + } + return str; +} + +/* _pico_strlwr: + * pico internal string-to-lower routine. + */ +char *_pico_strlwr( char *str ) +{ + char *cp; + for (cp=str; *cp; ++cp) + { + if ('A' <= *cp && *cp <= 'Z') + { + *cp += ('a' - 'A'); + } + } + return str; +} + +/* _pico_strchcount: + * counts how often the given char appears in str. -sea + */ +int _pico_strchcount( char *str, int ch ) +{ + int count = 0; + while (*str++) if (*str == ch) count++; + return count; +} + +void _pico_zero_bounds( picoVec3_t mins, picoVec3_t maxs ) +{ + int i; + for (i=0; i<3; i++) + { + mins[i] = +999999; + maxs[i] = -999999; + } +} + +void _pico_expand_bounds( picoVec3_t p, picoVec3_t mins, picoVec3_t maxs ) +{ + int i; + for (i=0; i<3; i++) + { + float value = p[i]; + if (value < mins[i]) mins[i] = value; + if (value > maxs[i]) maxs[i] = value; + } +} + +void _pico_zero_vec( picoVec3_t vec ) +{ + vec[ 0 ] = vec[ 1 ] = vec[ 2 ] = 0; +} + +void _pico_zero_vec2( picoVec2_t vec ) +{ + vec[ 0 ] = vec[ 1 ] = 0; +} + +void _pico_zero_vec4( picoVec4_t vec ) +{ + vec[ 0 ] = vec[ 1 ] = vec[ 2 ] = vec[ 3 ] = 0; +} + +void _pico_set_vec( picoVec3_t v, float a, float b, float c ) +{ + v[ 0 ] = a; + v[ 1 ] = b; + v[ 2 ] = c; +} + +void _pico_set_vec4( picoVec4_t v, float a, float b, float c, float d ) +{ + v[ 0 ] = a; + v[ 1 ] = b; + v[ 2 ] = c; + v[ 3 ] = d; +} + +void _pico_copy_vec( picoVec3_t src, picoVec3_t dest ) +{ + dest[ 0 ] = src[ 0 ]; + dest[ 1 ] = src[ 1 ]; + dest[ 2 ] = src[ 2 ]; +} + +void _pico_copy_vec2( picoVec2_t src, picoVec2_t dest ) +{ + dest[ 0 ] = src[ 0 ]; + dest[ 1 ] = src[ 1 ]; +} + +void _pico_copy_vec4( picoVec4_t src, picoVec4_t dest ) +{ + dest[ 0 ] = src[ 0 ]; + dest[ 1 ] = src[ 1 ]; + dest[ 2 ] = src[ 2 ]; + dest[ 3 ] = src[ 3 ]; +} + +/* ydnar */ +picoVec_t _pico_normalize_vec( picoVec3_t vec ) +{ + double len, ilen; + + len = sqrt( vec[ 0 ] * vec[ 0 ] + vec[ 1 ] * vec[ 1 ] + vec[ 2 ] * vec[ 2 ] ); + if( len == 0.0 ) return 0.0; + ilen = 1.0 / len; + vec[ 0 ] *= (picoVec_t) ilen; + vec[ 1 ] *= (picoVec_t) ilen; + vec[ 2 ] *= (picoVec_t) ilen; + return (picoVec_t) len; +} + +void _pico_add_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ) +{ + dest[ 0 ] = a[ 0 ] + b[ 0 ]; + dest[ 1 ] = a[ 1 ] + b[ 1 ]; + dest[ 2 ] = a[ 2 ] + b[ 2 ]; +} + +void _pico_subtract_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ) +{ + dest[ 0 ] = a[ 0 ] - b[ 0 ]; + dest[ 1 ] = a[ 1 ] - b[ 1 ]; + dest[ 2 ] = a[ 2 ] - b[ 2 ]; +} + +void _pico_scale_vec( picoVec3_t v, float scale, picoVec3_t dest ) +{ + dest[ 0 ] = v[ 0 ] * scale; + dest[ 1 ] = v[ 1 ] * scale; + dest[ 2 ] = v[ 2 ] * scale; +} + +void _pico_scale_vec4( picoVec4_t v, float scale, picoVec4_t dest ) +{ + dest[ 0 ] = v[ 0 ] * scale; + dest[ 1 ] = v[ 1 ] * scale; + dest[ 2 ] = v[ 2 ] * scale; + dest[ 3 ] = v[ 3 ] * scale; +} + +picoVec_t _pico_dot_vec( picoVec3_t a, picoVec3_t b ) +{ + return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ]; +} + +void _pico_cross_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ) +{ + dest[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ]; + dest[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ]; + dest[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ]; +} + +picoVec_t _pico_calc_plane( picoVec4_t plane, picoVec3_t a, picoVec3_t b, picoVec3_t c ) +{ + picoVec3_t ba, ca; + + _pico_subtract_vec( b, a, ba ); + _pico_subtract_vec( c, a, ca ); + _pico_cross_vec( ca, ba, plane ); + plane[ 3 ] = _pico_dot_vec( a, plane ); + return _pico_normalize_vec( plane ); +} + +/* separate from _pico_set_vec4 */ +void _pico_set_color( picoColor_t c, int r, int g, int b, int a ) +{ + c[ 0 ] = r; + c[ 1 ] = g; + c[ 2 ] = b; + c[ 3 ] = a; +} + +void _pico_copy_color( picoColor_t src, picoColor_t dest ) +{ + dest[ 0 ] = src[ 0 ]; + dest[ 1 ] = src[ 1 ]; + dest[ 2 ] = src[ 2 ]; + dest[ 3 ] = src[ 3 ]; +} + +#ifdef __BIG_ENDIAN__ + +int _pico_big_long ( int src ) { return src; } +short _pico_big_short( short src ) { return src; } +float _pico_big_float( float src ) { return src; } + +int _pico_little_long( int src ) +{ + return ((src & 0xFF000000) >> 24) | + ((src & 0x00FF0000) >> 8) | + ((src & 0x0000FF00) << 8) | + ((src & 0x000000FF) << 24); +} + +short _pico_little_short( short src ) +{ + return ((src & 0xFF00) >> 8) | + ((src & 0x00FF) << 8); +} + +float _pico_little_float( float src ) +{ + floatSwapUnion in,out; + in.f = src; + out.c[ 0 ] = in.c[ 3 ]; + out.c[ 1 ] = in.c[ 2 ]; + out.c[ 2 ] = in.c[ 1 ]; + out.c[ 3 ] = in.c[ 0 ]; + return out.f; +} +#else /*__BIG_ENDIAN__*/ + +int _pico_little_long ( int src ) { return src; } +short _pico_little_short( short src ) { return src; } +float _pico_little_float( float src ) { return src; } + +int _pico_big_long( int src ) +{ + return ((src & 0xFF000000) >> 24) | + ((src & 0x00FF0000) >> 8) | + ((src & 0x0000FF00) << 8) | + ((src & 0x000000FF) << 24); +} + +short _pico_big_short( short src ) +{ + return ((src & 0xFF00) >> 8) | + ((src & 0x00FF) << 8); +} + +float _pico_big_float( float src ) +{ + floatSwapUnion in,out; + in.f = src; + out.c[ 0 ] = in.c[ 3 ]; + out.c[ 1 ] = in.c[ 2 ]; + out.c[ 2 ] = in.c[ 1 ]; + out.c[ 3 ] = in.c[ 0 ]; + return out.f; +} +#endif /*__BIG_ENDIAN__*/ + +/* _pico_stristr: + * case-insensitive strstr. -sea + */ +char *_pico_stristr( char *str, const char *substr ) +{ + const size_t sublen = strlen(substr); + while (*str) + { + if (!_pico_strnicmp(str,substr,sublen)) break; + str++; + } + if (!(*str)) str = NULL; + return str; +} + +/* +_pico_unixify() +changes dos \ style path separators to / +*/ + +void _pico_unixify( char *path ) +{ + if( path == NULL ) + return; + while( *path ) + { + if( *path == '\\' ) + *path = '/'; + path++; + } +} + +/* _pico_nofname: + * removes file name portion from given file path and converts + * the directory separators to un*x style. returns 1 on success + * or 0 when 'destSize' was exceeded. -sea + */ +int _pico_nofname( const char *path, char *dest, int destSize ) +{ + int left = destSize; + char *temp = dest; + + while ((*dest = *path) != '\0') + { + if (*dest == '/' || *dest == '\\') + { + temp = (dest + 1); + *dest = '/'; + } + dest++; path++; + + if (--left < 1) + { + *temp = '\0'; + return 0; + } + } + *temp = '\0'; + return 1; +} + +/* _pico_nopath: + * returns ptr to filename portion in given path or an empty + * string otherwise. given 'path' is not altered. -sea + */ +const char *_pico_nopath( const char *path ) +{ + const char *src; + src = path + (strlen(path) - 1); + + if (path == NULL) return ""; + if (!strchr(path,'/') && !strchr(path,'\\')) + return (path); + + while ((src--) != path) + { + if (*src == '/' || *src == '\\') + return (++src); + } + return ""; +} + +/* _pico_setfext: + * sets/changes the file extension for the given filename + * or filepath's filename portion. the given 'path' *is* + * altered. leave 'ext' empty to remove extension. -sea + */ +char *_pico_setfext( char *path, const char *ext ) +{ + char *src; + int remfext = 0; + + src = path + (strlen(path) - 1); + + if (ext == NULL) ext = ""; + if (strlen(ext ) < 1) remfext = 1; + if (strlen(path) < 1) + return path; + + while ((src--) != path) + { + if (*src == '/' || *src == '\\') + return path; + + if (*src == '.') + { + if (remfext) + { + *src = '\0'; + return path; + } + *(++src) = '\0'; + break; + } + } + strcat(path,ext); + return path; +} + +/* _pico_getline: + * extracts one line from the given buffer and stores it in dest. + * returns -1 on error or the length of the line on success. i've + * removed string trimming here. this can be done manually by the + * calling func. + */ +int _pico_getline( char *buf, int bufsize, char *dest, int destsize ) +{ + int pos; + + /* check output */ + if (dest == NULL || destsize < 1) return -1; + memset( dest,0,destsize ); + + /* check input */ + if (buf == NULL || bufsize < 1) + return -1; + + /* get next line */ + for (pos=0; poscursor == NULL) + return; + + /* skin white spaces */ + while( 1 ) + { + /* sanity checks */ + if (p->cursor < p->buffer || + p->cursor >= p->max) + { + return; + } + /* break for chars other than white spaces */ + if (*p->cursor > 0x20) break; + if (*p->cursor == 0x00) return; + + /* a bit of linefeed handling */ + if (*p->cursor == '\n') + { + *hasLFs = 1; + p->curLine++; + } + /* go to next character */ + p->cursor++; + } +} + +/* _pico_new_parser: + * allocates a new ascii parser object. + */ +picoParser_t *_pico_new_parser( picoByte_t *buffer, int bufSize ) +{ + picoParser_t *p; + + /* sanity check */ + if( buffer == NULL || bufSize <= 0 ) + return NULL; + + /* allocate reader */ + p = _pico_alloc( sizeof(picoParser_t) ); + if (p == NULL) return NULL; + memset( p,0,sizeof(picoParser_t) ); + + /* allocate token space */ + p->tokenSize = 0; + p->tokenMax = 1024; + p->token = _pico_alloc( p->tokenMax ); + if( p->token == NULL ) + { + _pico_free( p ); + return NULL; + } + /* setup */ + p->buffer = buffer; + p->cursor = buffer; + p->bufSize = bufSize; + p->max = p->buffer + bufSize; + p->curLine = 1; /* sea: new */ + + /* return ptr to parser */ + return p; +} + +/* _pico_free_parser: + * frees an existing pico parser object. + */ +void _pico_free_parser( picoParser_t *p ) +{ + /* sanity check */ + if (p == NULL) return; + + /* free the parser */ + if (p->token != NULL) + { + _pico_free( p->token ); + } + _pico_free( p ); +} + +/* _pico_parse_ex: + * reads the next token from given pico parser object. if param + * 'allowLFs' is 1 it will read beyond linefeeds and return 0 when + * the EOF is reached. if 'allowLFs' is 0 it will return 0 when + * the EOL is reached. if 'handleQuoted' is 1 the parser function + * will handle "quoted" strings and return the data between the + * quotes as token. returns 0 on end/error or 1 on success. -sea + */ +int _pico_parse_ex( picoParser_t *p, int allowLFs, int handleQuoted ) +{ + int hasLFs = 0; + char *old; + + /* sanity checks */ + if( p == NULL || p->buffer == NULL || + p->cursor < p->buffer || + p->cursor >= p->max ) + { + return 0; + } + /* clear parser token */ + p->tokenSize = 0; + p->token[ 0 ] = '\0'; + old = p->cursor; + + /* skip whitespaces */ + while( p->cursor < p->max && *p->cursor <= 32 ) + { + if (*p->cursor == '\n') + { + p->curLine++; + hasLFs++; + } + p->cursor++; + } + /* return if we're not allowed to go beyond lfs */ + if ((hasLFs > 0) && !allowLFs) + { + p->cursor = old; + return 0; + } + /* get next quoted string */ + if (*p->cursor == '\"' && handleQuoted) + { + p->cursor++; + while (p->cursor < p->max && *p->cursor) + { + if (*p->cursor == '\\') + { + if (*(p->cursor+1) == '"') + { + p->cursor++; + } + p->token[ p->tokenSize++ ] = *p->cursor++; + continue; + } + else if (*p->cursor == '\"') + { + p->cursor++; + break; + } + else if (*p->cursor == '\n') + { + p->curLine++; + } + p->token[ p->tokenSize++ ] = *p->cursor++; + } + /* terminate token */ + p->token[ p->tokenSize ] = '\0'; + return 1; + } + /* otherwise get next word */ + while( p->cursor < p->max && *p->cursor > 32 ) + { + if (*p->cursor == '\n') + { + p->curLine++; + } + p->token[ p->tokenSize++ ] = *p->cursor++; + } + /* terminate token */ + p->token[ p->tokenSize ] = '\0'; + return 1; +} + +/* _pico_parse_first: + * reads the first token from the next line and returns + * a pointer to it. returns NULL on EOL or EOF. -sea + */ +char *_pico_parse_first( picoParser_t *p ) +{ + /* sanity check */ + if (p == NULL) return NULL; + + /* try to read next token (with lfs & quots) */ + if (!_pico_parse_ex( p,1,1 )) + return NULL; + + /* return ptr to the token string */ + return p->token; +} + +/* _pico_parse: + * reads the next token from the parser and returns a pointer + * to it. quoted strings are handled as usual. returns NULL + * on EOL or EOF. -sea + */ +char *_pico_parse( picoParser_t *p, int allowLFs ) +{ + /* sanity check */ + if (p == NULL) return NULL; + + /* try to read next token (with quots) */ + if (!_pico_parse_ex( p,allowLFs,1 )) + return NULL; + + /* return ptr to the token string */ + return p->token; +} + +/* _pico_parse_skip_rest: + * skips the rest of the current line in parser. + */ +void _pico_parse_skip_rest( picoParser_t *p ) +{ + while( _pico_parse_ex( p,0,0 ) ) ; +} + +/* _pico_parse_skip_braced: + * parses/skips over a braced section. returns 1 on success + * or 0 on error (when there was no closing bracket and the + * end of buffer was reached or when the opening bracket was + * missing). + */ +int _pico_parse_skip_braced( picoParser_t *p ) +{ + int firstToken = 1; + int level; + + /* sanity check */ + if (p == NULL) return 0; + + /* set the initial level for parsing */ + level = 0; + + /* skip braced section */ + while( 1 ) + { + /* read next token (lfs allowed) */ + if (!_pico_parse_ex( p,1,1 )) + { + /* end of parser buffer reached */ + return 0; + } + /* first token must be an opening bracket */ + if (firstToken && p->token[0] != '{') + { + /* opening bracket missing */ + return 0; + } + /* we only check this once */ + firstToken = 0; + + /* update level */ + if (p->token[1] == '\0') + { + if (p->token[0] == '{') level++; + if (p->token[0] == '}') level--; + } + /* break if we're back at our starting level */ + if (level == 0) break; + } + /* successfully skipped braced section */ + return 1; +} + +int _pico_parse_check( picoParser_t *p, int allowLFs, char *str ) +{ + if (!_pico_parse_ex( p,allowLFs,1 )) + return 0; + if (!strcmp(p->token,str)) + return 1; + return 0; +} + +int _pico_parse_checki( picoParser_t *p, int allowLFs, char *str ) +{ + if (!_pico_parse_ex( p,allowLFs,1 )) + return 0; + if (!_pico_stricmp(p->token,str)) + return 1; + return 0; +} + +int _pico_parse_int( picoParser_t *p, int *out ) +{ + char *token; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* get token and turn it into an integer */ + *out = 0; + token = _pico_parse( p,0 ); + if (token == NULL) return 0; + *out = atoi( token ); + + /* success */ + return 1; +} + +int _pico_parse_int_def( picoParser_t *p, int *out, int def ) +{ + char *token; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* get token and turn it into an integer */ + *out = def; + token = _pico_parse( p,0 ); + if (token == NULL) return 0; + *out = atoi( token ); + + /* success */ + return 1; +} + +int _pico_parse_float( picoParser_t *p, float *out ) +{ + char *token; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* get token and turn it into a float */ + *out = 0.0f; + token = _pico_parse( p,0 ); + if (token == NULL) return 0; + *out = (float) atof( token ); + + /* success */ + return 1; +} + +int _pico_parse_float_def( picoParser_t *p, float *out, float def ) +{ + char *token; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* get token and turn it into a float */ + *out = def; + token = _pico_parse( p,0 ); + if (token == NULL) return 0; + *out = (float) atof( token ); + + /* success */ + return 1; +} + +int _pico_parse_vec( picoParser_t *p, picoVec3_t out ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* zero out outination vector */ + _pico_zero_vec( out ); + + /* parse three vector components */ + for (i=0; i<3; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_zero_vec( out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec_def( picoParser_t *p, picoVec3_t out, picoVec3_t def ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* assign default vector value */ + _pico_copy_vec( def,out ); + + /* parse three vector components */ + for (i=0; i<3; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_copy_vec( def,out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec2( picoParser_t *p, picoVec2_t out ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* zero out outination vector */ + _pico_zero_vec2( out ); + + /* parse two vector components */ + for (i=0; i<2; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_zero_vec2( out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec2_def( picoParser_t *p, picoVec2_t out, picoVec2_t def ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* assign default vector value */ + _pico_copy_vec2( def,out ); + + /* parse two vector components */ + for (i=0; i<2; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_copy_vec2( def,out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec4( picoParser_t *p, picoVec4_t out ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* zero out outination vector */ + _pico_zero_vec4( out ); + + /* parse four vector components */ + for (i=0; i<4; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_zero_vec4( out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec4_def( picoParser_t *p, picoVec4_t out, picoVec4_t def ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* assign default vector value */ + _pico_copy_vec4( def,out ); + + /* parse four vector components */ + for (i=0; i<4; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_copy_vec4( def,out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +/* _pico_new_memstream: + * allocates a new memorystream object. + */ +picoMemStream_t *_pico_new_memstream( picoByte_t *buffer, int bufSize ) +{ + picoMemStream_t *s; + + /* sanity check */ + if( buffer == NULL || bufSize <= 0 ) + return NULL; + + /* allocate stream */ + s = _pico_alloc( sizeof(picoMemStream_t) ); + if (s == NULL) return NULL; + memset( s,0,sizeof(picoMemStream_t) ); + + /* setup */ + s->buffer = buffer; + s->curPos = buffer; + s->bufSize = bufSize; + s->flag = 0; + + /* return ptr to stream */ + return s; +} + +/* _pico_free_memstream: + * frees an existing pico memorystream object. + */ +void _pico_free_memstream( picoMemStream_t *s ) +{ + /* sanity check */ + if (s == NULL) return; + + /* free the stream */ + _pico_free( s ); +} + +/* _pico_memstream_read: + * reads data from a pico memorystream into a buffer. + */ +int _pico_memstream_read( picoMemStream_t *s, void *buffer, int len ) +{ + int ret = 1; + + /* sanity checks */ + if (s == NULL || buffer == NULL) + return 0; + + if (s->curPos + len > s->buffer + s->bufSize) + { + s->flag |= PICO_IOEOF; + len = s->buffer + s->bufSize - s->curPos; + ret = 0; + } + + /* read the data */ + memcpy( buffer, s->curPos, len ); + s->curPos += len; + return ret; +} + +/* _pico_memstream_read: + * reads a character from a pico memorystream + */ +int _pico_memstream_getc( picoMemStream_t *s ) +{ + int c = 0; + + /* sanity check */ + if (s == NULL) + return -1; + + /* read the character */ + if (_pico_memstream_read( s, &c, 1) == 0) + return -1; + + return c; +} + +/* _pico_memstream_seek: + * sets the current read position to a different location + */ +int _pico_memstream_seek( picoMemStream_t *s, long offset, int origin ) +{ + int overflow; + + /* sanity check */ + if (s == NULL) + return -1; + + if (origin == PICO_SEEK_SET) + { + s->curPos = s->buffer + offset; + overflow = s->curPos - ( s->buffer + s->bufSize ); + if (overflow > 0) + { + s->curPos = s->buffer + s->bufSize; + return offset - overflow; + } + return 0; + } + else if (origin == PICO_SEEK_CUR) + { + s->curPos += offset; + overflow = s->curPos - ( s->buffer + s->bufSize ); + if (overflow > 0) + { + s->curPos = s->buffer + s->bufSize; + return offset - overflow; + } + return 0; + } + else if (origin == PICO_SEEK_END) + { + s->curPos = ( s->buffer + s->bufSize ) - offset; + overflow = s->buffer - s->curPos; + if (overflow > 0) + { + s->curPos = s->buffer; + return offset - overflow; + } + return 0; + } + + return -1; +} + +/* _pico_memstream_tell: + * returns the current read position in the pico memorystream + */ +long _pico_memstream_tell( picoMemStream_t *s ) +{ + /* sanity check */ + if (s == NULL) + return -1; + + return s->curPos - s->buffer; +} diff --git a/tools/urt/libs/picomodel/picointernal.h b/tools/urt/libs/picomodel/picointernal.h new file mode 100644 index 00000000..50b24bc6 --- /dev/null +++ b/tools/urt/libs/picomodel/picointernal.h @@ -0,0 +1,206 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef PICOINTERNAL_H +#define PICOINTERNAL_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/* dependencies */ +#include +#include +#include +#include +#include +#include + +#include "picomodel.h" + + +/* os dependent replacements */ +#if WIN32 || _WIN32 + #define _pico_stricmp stricmp + #define _pico_strnicmp strnicmp +#else + #define _pico_stricmp strcasecmp + #define _pico_strnicmp strncasecmp +#endif + + +/* constants */ +#define PICO_PI 3.14159265358979323846 + +#define PICO_SEEK_SET 0 +#define PICO_SEEK_CUR 1 +#define PICO_SEEK_END 2 + +#define PICO_IOEOF 1 +#define PICO_IOERR 2 + +/* types */ +typedef struct picoParser_s +{ + char *buffer; + int bufSize; + char *token; + int tokenSize; + int tokenMax; + char *cursor; + char *max; + int curLine; +} +picoParser_t; + +typedef struct picoMemStream_s +{ + picoByte_t *buffer; + int bufSize; + picoByte_t *curPos; + int flag; +} +picoMemStream_t; + + +/* variables */ +extern const picoModule_t *picoModules[]; + +extern void *(*_pico_ptr_malloc)( size_t ); +extern void (*_pico_ptr_free)( void* ); +extern void (*_pico_ptr_load_file)( char*, unsigned char**, int* ); +extern void (*_pico_ptr_free_file)( void* ); +extern void (*_pico_ptr_print)( int, const char* ); + + + +/* prototypes */ + +/* memory */ +void *_pico_alloc( size_t size ); +void *_pico_calloc( size_t num, size_t size ); +void *_pico_realloc( void **ptr, size_t oldSize, size_t newSize ); +char *_pico_clone_alloc( const char *str ); +void _pico_free( void *ptr ); + +/* files */ +void _pico_load_file( char *name, unsigned char **buffer, int *bufSize ); +void _pico_free_file( void *buffer ); + +/* strings */ +void _pico_first_token( char *str ); +char *_pico_strltrim( char *str ); +char *_pico_strrtrim( char *str ); +int _pico_strchcount( char *str, int ch ); +void _pico_printf( int level, const char *format, ... ); +char *_pico_stristr( char *str, const char *substr ); +void _pico_unixify( char *path ); +int _pico_nofname( const char *path, char *dest, int destSize ); +const char *_pico_nopath( const char *path ); +char *_pico_setfext( char *path, const char *ext ); +int _pico_getline( char *buf, int bufsize, char *dest, int destsize ); +char *_pico_strlwr( char *str ); + +/* vectors */ +void _pico_zero_bounds( picoVec3_t mins, picoVec3_t maxs ); +void _pico_expand_bounds( picoVec3_t p, picoVec3_t mins, picoVec3_t maxs ); +void _pico_zero_vec( picoVec3_t vec ); +void _pico_zero_vec2( picoVec2_t vec ); +void _pico_zero_vec4( picoVec4_t vec ); +void _pico_set_vec( picoVec3_t v, float a, float b, float c ); +void _pico_set_vec4( picoVec4_t v, float a, float b, float c, float d ); +void _pico_set_color( picoColor_t c, int r, int g, int b, int a ); +void _pico_copy_color( picoColor_t src, picoColor_t dest ); +void _pico_copy_vec( picoVec3_t src, picoVec3_t dest ); +void _pico_copy_vec2( picoVec2_t src, picoVec2_t dest ); +picoVec_t _pico_normalize_vec( picoVec3_t vec ); +void _pico_add_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ); +void _pico_subtract_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ); +picoVec_t _pico_dot_vec( picoVec3_t a, picoVec3_t b ); +void _pico_cross_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ); +picoVec_t _pico_calc_plane( picoVec4_t plane, picoVec3_t a, picoVec3_t b, picoVec3_t c ); +void _pico_scale_vec( picoVec3_t v, float scale, picoVec3_t dest ); +void _pico_scale_vec4( picoVec4_t v, float scale, picoVec4_t dest ); + +/* endian */ +int _pico_big_long( int src ); +short _pico_big_short( short src ); +float _pico_big_float( float src ); + +int _pico_little_long( int src ); +short _pico_little_short( short src ); +float _pico_little_float( float src ); + +/* pico ascii parser */ +picoParser_t *_pico_new_parser( picoByte_t *buffer, int bufSize ); +void _pico_free_parser( picoParser_t *p ); +int _pico_parse_ex( picoParser_t *p, int allowLFs, int handleQuoted ); +char *_pico_parse_first( picoParser_t *p ); +char *_pico_parse( picoParser_t *p, int allowLFs ); +void _pico_parse_skip_rest( picoParser_t *p ); +int _pico_parse_skip_braced( picoParser_t *p ); +int _pico_parse_check( picoParser_t *p, int allowLFs, char *str ); +int _pico_parse_checki( picoParser_t *p, int allowLFs, char *str ); +int _pico_parse_int( picoParser_t *p, int *out ); +int _pico_parse_int_def( picoParser_t *p, int *out, int def ); +int _pico_parse_float( picoParser_t *p, float *out ); +int _pico_parse_float_def( picoParser_t *p, float *out, float def ); +int _pico_parse_vec( picoParser_t *p, picoVec3_t out); +int _pico_parse_vec_def( picoParser_t *p, picoVec3_t out, picoVec3_t def); +int _pico_parse_vec2( picoParser_t *p, picoVec2_t out ); +int _pico_parse_vec2_def( picoParser_t *p, picoVec2_t out, picoVec2_t def ); +int _pico_parse_vec4( picoParser_t *p, picoVec4_t out); +int _pico_parse_vec4_def( picoParser_t *p, picoVec4_t out, picoVec4_t def); + +/* pico memory stream */ +picoMemStream_t *_pico_new_memstream( picoByte_t *buffer, int bufSize ); +void _pico_free_memstream( picoMemStream_t *s ); +int _pico_memstream_read( picoMemStream_t *s, void *buffer, int len ); +int _pico_memstream_getc( picoMemStream_t *s ); +int _pico_memstream_seek( picoMemStream_t *s, long offset, int origin ); +long _pico_memstream_tell( picoMemStream_t *s ); +#define _pico_memstream_eof( _pico_memstream ) ((_pico_memstream)->flag & PICO_IOEOF) +#define _pico_memstream_error( _pico_memstream ) ((_pico_memstream)->flag & PICO_IOERR) + +/* end marker */ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tools/urt/libs/picomodel/picomodel.c b/tools/urt/libs/picomodel/picomodel.c new file mode 100644 index 00000000..24736798 --- /dev/null +++ b/tools/urt/libs/picomodel/picomodel.c @@ -0,0 +1,2279 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PICOMODEL_C + + + +/* dependencies */ +#include "picointernal.h" + + + +/* +PicoInit() +initializes the picomodel library +*/ + +int PicoInit( void ) +{ + /* successfully initialized -sea */ + return 1; +} + + + +/* +PicoShutdown() +shuts the pico model library down +*/ + +void PicoShutdown( void ) +{ + /* do something interesting here in the future */ + return; +} + + + +/* +PicoError() +returns last picomodel error code (see PME_* defines) +*/ + +int PicoError( void ) +{ + /* todo: do something here */ + return 0; +} + + + +/* +PicoSetMallocFunc() +sets the ptr to the malloc function +*/ + +void PicoSetMallocFunc( void *(*func)( size_t ) ) +{ + if( func != NULL ) + _pico_ptr_malloc = func; +} + + + +/* +PicoSetFreeFunc() +sets the ptr to the free function +*/ + +void PicoSetFreeFunc( void (*func)( void* ) ) +{ + if( func != NULL ) + _pico_ptr_free = func; +} + + + +/* +PicoSetLoadFileFunc() +sets the ptr to the file load function +*/ + +void PicoSetLoadFileFunc( void (*func)( char*, unsigned char**, int* ) ) +{ + if( func != NULL ) + _pico_ptr_load_file = func; +} + + + +/* +PicoSetFreeFileFunc() +sets the ptr to the free function +*/ + +void PicoSetFreeFileFunc( void (*func)( void* ) ) +{ + if( func != NULL ) + _pico_ptr_free_file = func; +} + + + +/* +PicoSetPrintFunc() +sets the ptr to the print function +*/ + +void PicoSetPrintFunc( void (*func)( int, const char* ) ) +{ + if( func != NULL ) + _pico_ptr_print = func; +} + + + +picoModel_t *PicoModuleLoadModel( const picoModule_t* pm, char* fileName, picoByte_t* buffer, int bufSize, int frameNum ) +{ + char *modelFileName, *remapFileName; + + /* see whether this module can load the model file or not */ + if( pm->canload( fileName, buffer, bufSize ) == PICO_PMV_OK ) + { + /* use loader provided by module to read the model data */ + picoModel_t* model = pm->load( fileName, frameNum, buffer, bufSize ); + if( model == NULL ) + { + _pico_free_file( buffer ); + return NULL; + } + + /* assign pointer to file format module */ + model->module = pm; + + /* get model file name */ + modelFileName = PicoGetModelFileName( model ); + + /* apply model remappings from .remap */ + if( strlen( modelFileName ) ) + { + /* alloc copy of model file name */ + remapFileName = _pico_alloc( strlen( modelFileName ) + 20 ); + if( remapFileName != NULL ) + { + /* copy model file name and change extension */ + strcpy( remapFileName, modelFileName ); + _pico_setfext( remapFileName, "remap" ); + + /* try to remap model; we don't handle the result */ + PicoRemapModel( model, remapFileName ); + + /* free the remap file name string */ + _pico_free( remapFileName ); + } + } + + return model; + } + + return NULL; +} + +/* +PicoLoadModel() +the meat and potatoes function +*/ + +picoModel_t *PicoLoadModel( char *fileName, int frameNum ) +{ + const picoModule_t **modules, *pm; + picoModel_t *model; + picoByte_t *buffer; + int bufSize; + + + /* init */ + model = NULL; + + /* make sure we've got a file name */ + if( fileName == NULL ) + { + _pico_printf( PICO_ERROR, "PicoLoadModel: No filename given (fileName == NULL)" ); + return NULL; + } + + /* load file data (buffer is allocated by host app) */ + _pico_load_file( fileName, &buffer, &bufSize ); + if( bufSize < 0 ) + { + _pico_printf( PICO_ERROR, "PicoLoadModel: Failed loading model %s", fileName ); + return NULL; + } + + /* get ptr to list of supported modules */ + modules = PicoModuleList( NULL ); + + /* run it through the various loader functions and try */ + /* to find a loader that fits the given file data */ + for( ; *modules != NULL; modules++ ) + { + /* get module */ + pm = *modules; + + /* sanity check */ + if( pm == NULL) + break; + + /* module must be able to load */ + if( pm->canload == NULL || pm->load == NULL ) + continue; + + model = PicoModuleLoadModel(pm, fileName, buffer, bufSize, frameNum); + if(model != NULL) + { + /* model was loaded, so break out of loop */ + break; + } + } + + /* free memory used by file buffer */ + if( buffer) + _pico_free_file( buffer ); + + /* return */ + return model; +} + +picoModel_t *PicoModuleLoadModelStream( const picoModule_t* module, void* inputStream, PicoInputStreamReadFunc inputStreamRead, size_t streamLength, int frameNum ) +{ + picoModel_t *model; + picoByte_t *buffer; + int bufSize; + + + /* init */ + model = NULL; + + if( inputStream == NULL ) + { + _pico_printf( PICO_ERROR, "PicoLoadModel: invalid input stream (inputStream == NULL)" ); + return NULL; + } + + if( inputStreamRead == NULL ) + { + _pico_printf( PICO_ERROR, "PicoLoadModel: invalid input stream (inputStreamRead == NULL)" ); + return NULL; + } + + buffer = _pico_alloc(streamLength + 1); + + bufSize = (int)inputStreamRead(inputStream, buffer, streamLength); + buffer[bufSize] = '\0'; + + { + // dummy filename + char fileName[128]; + fileName[0] = '.'; + strncpy(fileName + 1, module->defaultExts[0], 126); + fileName[127] = '\0'; + model = PicoModuleLoadModel(module, fileName, buffer, bufSize, frameNum); + } + + _pico_free(buffer); + + /* return */ + return model; +} + + +/* ---------------------------------------------------------------------------- +models +---------------------------------------------------------------------------- */ + +/* +PicoNewModel() +creates a new pico model +*/ + +picoModel_t *PicoNewModel( void ) +{ + picoModel_t *model; + + /* allocate */ + model = _pico_alloc( sizeof(picoModel_t) ); + if( model == NULL ) + return NULL; + + /* clear */ + memset( model,0,sizeof(picoModel_t) ); + + /* model set up */ + _pico_zero_bounds( model->mins,model->maxs ); + + /* set initial frame count to 1 -sea */ + model->numFrames = 1; + + /* return ptr to new model */ + return model; +} + + + +/* +PicoFreeModel() +frees a model and all associated data +*/ + +void PicoFreeModel( picoModel_t *model ) +{ + int i; + + + /* sanity check */ + if( model == NULL ) + return; + + /* free bits */ + if( model->name ) + _pico_free( model->name ); + + if( model->fileName ) + _pico_free( model->fileName ); + + /* free shaders */ + for( i = 0; i < model->numShaders; i++ ) + PicoFreeShader( model->shader[ i ] ); + free( model->shader ); + + /* free surfaces */ + for( i = 0; i < model->numSurfaces; i++ ) + PicoFreeSurface( model->surface[ i ] ); + free( model->surface ); + + /* free the model */ + _pico_free( model ); +} + + + +/* +PicoAdjustModel() +adjusts a models's memory allocations to handle the requested sizes. +will always grow, never shrink +*/ + +int PicoAdjustModel( picoModel_t *model, int numShaders, int numSurfaces ) +{ + /* dummy check */ + if( model == NULL ) + return 0; + + /* bare minimums */ + /* sea: null surface/shader fix (1s=>0s) */ + if( numShaders < 0 ) + numShaders = 0; + if( numSurfaces < 0 ) + numSurfaces = 0; + + /* additional shaders? */ + while( numShaders > model->maxShaders ) + { + model->maxShaders += PICO_GROW_SHADERS; + if( !_pico_realloc( (void *) &model->shader, model->numShaders * sizeof( *model->shader ), model->maxShaders * sizeof( *model->shader ) ) ) + return 0; + } + + /* set shader count to higher */ + if( numShaders > model->numShaders ) + model->numShaders = numShaders; + + /* additional surfaces? */ + while( numSurfaces > model->maxSurfaces ) + { + model->maxSurfaces += PICO_GROW_SURFACES; + if( !_pico_realloc( (void *) &model->surface, model->numSurfaces * sizeof( *model->surface ), model->maxSurfaces * sizeof( *model->surface ) ) ) + return 0; + } + + /* set shader count to higher */ + if( numSurfaces > model->numSurfaces ) + model->numSurfaces = numSurfaces; + + /* return ok */ + return 1; +} + + + +/* ---------------------------------------------------------------------------- +shaders +---------------------------------------------------------------------------- */ + +/* +PicoNewShader() +creates a new pico shader and returns its index. -sea +*/ + +picoShader_t *PicoNewShader( picoModel_t *model ) +{ + picoShader_t *shader; + + + /* allocate and clear */ + shader = _pico_alloc( sizeof(picoShader_t) ); + if( shader == NULL ) + return NULL; + memset( shader, 0, sizeof(picoShader_t) ); + + /* attach it to the model */ + if( model != NULL ) + { + /* adjust model */ + if( !PicoAdjustModel( model, model->numShaders + 1, 0 ) ) + { + _pico_free( shader ); + return NULL; + } + + /* attach */ + model->shader[ model->numShaders - 1 ] = shader; + shader->model = model; + } + + /* setup default shader colors */ + _pico_set_color( shader->ambientColor,0,0,0,0 ); + _pico_set_color( shader->diffuseColor,255,255,255,1 ); + _pico_set_color( shader->specularColor,0,0,0,0 ); + + /* no need to do this, but i do it anyway */ + shader->transparency = 0; + shader->shininess = 0; + + /* return the newly created shader */ + return shader; +} + + + +/* +PicoFreeShader() +frees a shader and all associated data -sea +*/ + +void PicoFreeShader( picoShader_t *shader ) +{ + /* dummy check */ + if( shader == NULL ) + return; + + /* free bits */ + if( shader->name ) + _pico_free( shader->name ); + if( shader->mapName ) + _pico_free( shader->mapName ); + + /* free the shader */ + _pico_free( shader ); +} + + + +/* +PicoFindShader() +finds a named shader in a model +*/ + +picoShader_t *PicoFindShader( picoModel_t *model, char *name, int caseSensitive ) +{ + int i; + + + /* sanity checks */ + if( model == NULL || name == NULL ) /* sea: null name fix */ + return NULL; + + /* walk list */ + for( i = 0; i < model->numShaders; i++ ) + { + /* skip null shaders or shaders with null names */ + if( model->shader[ i ] == NULL || + model->shader[ i ]->name == NULL ) + continue; + + /* compare the shader name with name we're looking for */ + if( caseSensitive ) + { + if( !strcmp( name, model->shader[ i ]->name ) ) + return model->shader[ i ]; + } + else if( !_pico_stricmp( name, model->shader[ i ]->name ) ) + return model->shader[ i ]; + } + + /* named shader not found */ + return NULL; +} + + + +/* ---------------------------------------------------------------------------- +surfaces +---------------------------------------------------------------------------- */ + +/* +PicoNewSurface() +creates a new pico surface +*/ + +picoSurface_t *PicoNewSurface( picoModel_t *model ) +{ + picoSurface_t *surface; + char surfaceName[64]; + + /* allocate and clear */ + surface = _pico_alloc( sizeof( *surface ) ); + if( surface == NULL ) + return NULL; + memset( surface, 0, sizeof( *surface ) ); + + /* attach it to the model */ + if( model != NULL ) + { + /* adjust model */ + if( !PicoAdjustModel( model, 0, model->numSurfaces + 1 ) ) + { + _pico_free( surface ); + return NULL; + } + + /* attach */ + model->surface[ model->numSurfaces - 1 ] = surface; + surface->model = model; + + /* set default name */ + sprintf( surfaceName, "Unnamed_%d", model->numSurfaces ); + PicoSetSurfaceName( surface, surfaceName ); + } + + /* return */ + return surface; +} + + + +/* +PicoFreeSurface() +frees a surface and all associated data +*/ +void PicoFreeSurface( picoSurface_t *surface ) +{ + int i; + + + /* dummy check */ + if( surface == NULL ) + return; + + /* free bits */ + _pico_free( surface->xyz ); + _pico_free( surface->normal ); + _pico_free( surface->smoothingGroup ); + _pico_free( surface->index ); + _pico_free( surface->faceNormal ); + + if( surface->name ) + _pico_free( surface->name ); + + /* free arrays */ + for( i = 0; i < surface->numSTArrays; i++ ) + _pico_free( surface->st[ i ] ); + free( surface->st ); + for( i = 0; i < surface->numColorArrays; i++ ) + _pico_free( surface->color[ i ] ); + free( surface->color ); + + /* free the surface */ + _pico_free( surface ); +} + + + +/* +PicoAdjustSurface() +adjusts a surface's memory allocations to handle the requested sizes. +will always grow, never shrink +*/ + +int PicoAdjustSurface( picoSurface_t *surface, int numVertexes, int numSTArrays, int numColorArrays, int numIndexes, int numFaceNormals ) +{ + int i; + + + /* dummy check */ + if( surface == NULL ) + return 0; + + /* bare minimums */ + if( numVertexes < 1 ) + numVertexes = 1; + if( numSTArrays < 1 ) + numSTArrays = 1; + if( numColorArrays < 1 ) + numColorArrays = 1; + if( numIndexes < 1 ) + numIndexes = 1; + + /* additional vertexes? */ + while( numVertexes > surface->maxVertexes ) /* fix */ + { + surface->maxVertexes += PICO_GROW_VERTEXES; + if( !_pico_realloc( (void *) &surface->xyz, surface->numVertexes * sizeof( *surface->xyz ), surface->maxVertexes * sizeof( *surface->xyz ) ) ) + return 0; + if( !_pico_realloc( (void *) &surface->normal, surface->numVertexes * sizeof( *surface->normal ), surface->maxVertexes * sizeof( *surface->normal ) ) ) + return 0; + if( !_pico_realloc( (void *) &surface->smoothingGroup, surface->numVertexes * sizeof( *surface->smoothingGroup ), surface->maxVertexes * sizeof( *surface->smoothingGroup ) ) ) + return 0; + for( i = 0; i < surface->numSTArrays; i++ ) + if( !_pico_realloc( (void*) &surface->st[ i ], surface->numVertexes * sizeof( *surface->st[ i ] ), surface->maxVertexes * sizeof( *surface->st[ i ] ) ) ) + return 0; + for( i = 0; i < surface->numColorArrays; i++ ) + if( !_pico_realloc( (void*) &surface->color[ i ], surface->numVertexes * sizeof( *surface->color[ i ] ), surface->maxVertexes * sizeof( *surface->color[ i ] ) ) ) + return 0; + } + + /* set vertex count to higher */ + if( numVertexes > surface->numVertexes ) + surface->numVertexes = numVertexes; + + /* additional st arrays? */ + while( numSTArrays > surface->maxSTArrays ) /* fix */ + { + surface->maxSTArrays += PICO_GROW_ARRAYS; + if( !_pico_realloc( (void*) &surface->st, surface->numSTArrays * sizeof( *surface->st ), surface->maxSTArrays * sizeof( *surface->st ) ) ) + return 0; + while( surface->numSTArrays < numSTArrays ) + { + surface->st[ surface->numSTArrays ] = _pico_alloc( surface->maxVertexes * sizeof( *surface->st[ 0 ] ) ); + memset( surface->st[ surface->numSTArrays ], 0, surface->maxVertexes * sizeof( *surface->st[ 0 ] ) ); + surface->numSTArrays++; + } + } + + /* additional color arrays? */ + while( numColorArrays > surface->maxColorArrays ) /* fix */ + { + surface->maxColorArrays += PICO_GROW_ARRAYS; + if( !_pico_realloc( (void*) &surface->color, surface->numColorArrays * sizeof( *surface->color ), surface->maxColorArrays * sizeof( *surface->color ) ) ) + return 0; + while( surface->numColorArrays < numColorArrays ) + { + surface->color[ surface->numColorArrays ] = _pico_alloc( surface->maxVertexes * sizeof( *surface->color[ 0 ] ) ); + memset( surface->color[ surface->numColorArrays ], 0, surface->maxVertexes * sizeof( *surface->color[ 0 ] ) ); + surface->numColorArrays++; + } + } + + /* additional indexes? */ + while( numIndexes > surface->maxIndexes ) /* fix */ + { + surface->maxIndexes += PICO_GROW_INDEXES; + if( !_pico_realloc( (void*) &surface->index, surface->numIndexes * sizeof( *surface->index ), surface->maxIndexes * sizeof( *surface->index ) ) ) + return 0; + } + + /* set index count to higher */ + if( numIndexes > surface->numIndexes ) + surface->numIndexes = numIndexes; + + /* additional face normals? */ + while( numFaceNormals > surface->maxFaceNormals ) /* fix */ + { + surface->maxFaceNormals += PICO_GROW_FACES; + if( !_pico_realloc( (void *) &surface->faceNormal, surface->numFaceNormals * sizeof( *surface->faceNormal ), surface->maxFaceNormals * sizeof( *surface->faceNormal ) ) ) + return 0; + } + + /* set face normal count to higher */ + if( numFaceNormals > surface->numFaceNormals ) + surface->numFaceNormals = numFaceNormals; + + /* return ok */ + return 1; +} + + +/* PicoFindSurface: + * Finds first matching named surface in a model. + */ +picoSurface_t *PicoFindSurface( + picoModel_t *model, char *name, int caseSensitive ) +{ + int i; + + /* sanity check */ + if( model == NULL || name == NULL ) + return NULL; + + /* walk list */ + for( i = 0; i < model->numSurfaces; i++ ) + { + /* skip null surfaces or surfaces with null names */ + if( model->surface[ i ] == NULL || + model->surface[ i ]->name == NULL ) + continue; + + /* compare the surface name with name we're looking for */ + if (caseSensitive) { + if( !strcmp(name,model->surface[ i ]->name) ) + return model->surface[ i ]; + } else { + if( !_pico_stricmp(name,model->surface[ i ]->name) ) + return model->surface[ i ]; + } + } + /* named surface not found */ + return NULL; +} + + + +/*---------------------------------------------------------------------------- + PicoSet*() Setter Functions +----------------------------------------------------------------------------*/ + +void PicoSetModelName( picoModel_t *model, char *name ) +{ + if( model == NULL || name == NULL ) + return; + if( model->name != NULL ) + _pico_free( model->name ); + + model->name = _pico_clone_alloc( name ); +} + + + +void PicoSetModelFileName( picoModel_t *model, char *fileName ) +{ + if( model == NULL || fileName == NULL ) + return; + if( model->fileName != NULL ) + _pico_free( model->fileName ); + + model->fileName = _pico_clone_alloc( fileName ); +} + + + +void PicoSetModelFrameNum( picoModel_t *model, int frameNum ) +{ + if( model == NULL ) + return; + model->frameNum = frameNum; +} + + + +void PicoSetModelNumFrames( picoModel_t *model, int numFrames ) +{ + if( model == NULL ) + return; + model->numFrames = numFrames; +} + + + +void PicoSetModelData( picoModel_t *model, void *data ) +{ + if( model == NULL ) + return; + model->data = data; +} + + + +void PicoSetShaderName( picoShader_t *shader, char *name ) +{ + if( shader == NULL || name == NULL ) + return; + if( shader->name != NULL ) + _pico_free( shader->name ); + + shader->name = _pico_clone_alloc( name ); +} + + + +void PicoSetShaderMapName( picoShader_t *shader, char *mapName ) +{ + if( shader == NULL || mapName == NULL ) + return; + if( shader->mapName != NULL ) + _pico_free( shader->mapName ); + + shader->mapName = _pico_clone_alloc( mapName ); +} + + + +void PicoSetShaderAmbientColor( picoShader_t *shader, picoColor_t color ) +{ + if( shader == NULL || color == NULL ) + return; + shader->ambientColor[ 0 ] = color[ 0 ]; + shader->ambientColor[ 1 ] = color[ 1 ]; + shader->ambientColor[ 2 ] = color[ 2 ]; + shader->ambientColor[ 3 ] = color[ 3 ]; +} + + + +void PicoSetShaderDiffuseColor( picoShader_t *shader, picoColor_t color ) +{ + if( shader == NULL || color == NULL ) + return; + shader->diffuseColor[ 0 ] = color[ 0 ]; + shader->diffuseColor[ 1 ] = color[ 1 ]; + shader->diffuseColor[ 2 ] = color[ 2 ]; + shader->diffuseColor[ 3 ] = color[ 3 ]; +} + + + +void PicoSetShaderSpecularColor( picoShader_t *shader, picoColor_t color ) +{ + if( shader == NULL || color == NULL ) + return; + shader->specularColor[ 0 ] = color[ 0 ]; + shader->specularColor[ 1 ] = color[ 1 ]; + shader->specularColor[ 2 ] = color[ 2 ]; + shader->specularColor[ 3 ] = color[ 3 ]; +} + + + +void PicoSetShaderTransparency( picoShader_t *shader, float value ) +{ + if( shader == NULL ) + return; + shader->transparency = value; + + /* cap to 0..1 range */ + if (shader->transparency < 0.0) + shader->transparency = 0.0; + if (shader->transparency > 1.0) + shader->transparency = 1.0; +} + + + +void PicoSetShaderShininess( picoShader_t *shader, float value ) +{ + if( shader == NULL ) + return; + shader->shininess = value; + + /* cap to 0..127 range */ + if (shader->shininess < 0.0) + shader->shininess = 0.0; + if (shader->shininess > 127.0) + shader->shininess = 127.0; +} + + + +void PicoSetSurfaceData( picoSurface_t *surface, void *data ) +{ + if( surface == NULL ) + return; + surface->data = data; +} + + + +void PicoSetSurfaceType( picoSurface_t *surface, picoSurfaceType_t type ) +{ + if( surface == NULL ) + return; + surface->type = type; +} + + + +void PicoSetSurfaceName( picoSurface_t *surface, char *name ) +{ + if( surface == NULL || name == NULL ) + return; + if( surface->name != NULL ) + _pico_free( surface->name ); + + surface->name = _pico_clone_alloc( name ); +} + + + +void PicoSetSurfaceShader( picoSurface_t *surface, picoShader_t *shader ) +{ + if( surface == NULL ) + return; + surface->shader = shader; +} + + + +void PicoSetSurfaceXYZ( picoSurface_t *surface, int num, picoVec3_t xyz ) +{ + if( surface == NULL || num < 0 || xyz == NULL ) + return; + if( !PicoAdjustSurface( surface, num + 1, 0, 0, 0, 0 ) ) + return; + _pico_copy_vec( xyz, surface->xyz[ num ] ); + if( surface->model != NULL ) + _pico_expand_bounds( xyz, surface->model->mins, surface->model->maxs ); +} + + + +void PicoSetSurfaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ) +{ + if( surface == NULL || num < 0 || normal == NULL ) + return; + if( !PicoAdjustSurface( surface, num + 1, 0, 0, 0, 0 ) ) + return; + _pico_copy_vec( normal, surface->normal[ num ] ); +} + + + +void PicoSetSurfaceST( picoSurface_t *surface, int array, int num, picoVec2_t st ) +{ + if( surface == NULL || num < 0 || st == NULL ) + return; + if( !PicoAdjustSurface( surface, num + 1, array + 1, 0, 0, 0 ) ) + return; + surface->st[ array ][ num ][ 0 ] = st[ 0 ]; + surface->st[ array ][ num ][ 1 ] = st[ 1 ]; +} + + + +void PicoSetSurfaceColor( picoSurface_t *surface, int array, int num, picoColor_t color ) +{ + if( surface == NULL || num < 0 || color == NULL ) + return; + if( !PicoAdjustSurface( surface, num + 1, 0, array + 1, 0, 0 ) ) + return; + surface->color[ array ][ num ][ 0 ] = color[ 0 ]; + surface->color[ array ][ num ][ 1 ] = color[ 1 ]; + surface->color[ array ][ num ][ 2 ] = color[ 2 ]; + surface->color[ array ][ num ][ 3 ] = color[ 3 ]; +} + + + +void PicoSetSurfaceIndex( picoSurface_t *surface, int num, picoIndex_t index ) +{ + if( surface == NULL || num < 0 ) + return; + if( !PicoAdjustSurface( surface, 0, 0, 0, num + 1, 0 ) ) + return; + surface->index[ num ] = index; +} + + + +void PicoSetSurfaceIndexes( picoSurface_t *surface, int num, picoIndex_t *index, int count ) +{ + if( num < 0 || index == NULL || count < 1 ) + return; + if( !PicoAdjustSurface( surface, 0, 0, 0, num + count, 0 ) ) + return; + memcpy( &surface->index[ num ], index, count * sizeof( surface->index[ num ] ) ); +} + + + +void PicoSetFaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ) +{ + if( surface == NULL || num < 0 || normal == NULL ) + return; + if( !PicoAdjustSurface( surface, 0, 0, 0, 0, num + 1 ) ) + return; + _pico_copy_vec( normal, surface->faceNormal[ num ] ); +} + + +void PicoSetSurfaceSmoothingGroup( picoSurface_t *surface, int num, picoIndex_t smoothingGroup ) +{ + if( num < 0 ) + return; + if( !PicoAdjustSurface( surface, num + 1, 0, 0, 0, 0 ) ) + return; + surface->smoothingGroup[ num ] = smoothingGroup; +} + + +void PicoSetSurfaceSpecial( picoSurface_t *surface, int num, int special ) +{ + if( surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL ) + return; + surface->special[ num ] = special; +} + + + +/*---------------------------------------------------------------------------- + PicoGet*() Getter Functions +----------------------------------------------------------------------------*/ + +char *PicoGetModelName( picoModel_t *model ) +{ + if( model == NULL ) + return NULL; + if( model->name == NULL) + return (char*) ""; + return model->name; +} + + + +char *PicoGetModelFileName( picoModel_t *model ) +{ + if( model == NULL ) + return NULL; + if( model->fileName == NULL) + return (char*) ""; + return model->fileName; +} + + + +int PicoGetModelFrameNum( picoModel_t *model ) +{ + if( model == NULL ) + return 0; + return model->frameNum; +} + + + +int PicoGetModelNumFrames( picoModel_t *model ) +{ + if( model == NULL ) + return 0; + return model->numFrames; +} + + + +void *PicoGetModelData( picoModel_t *model ) +{ + if( model == NULL ) + return NULL; + return model->data; +} + + + +int PicoGetModelNumShaders( picoModel_t *model ) +{ + if( model == NULL ) + return 0; + return model->numShaders; +} + + + +picoShader_t *PicoGetModelShader( picoModel_t *model, int num ) +{ + /* a few sanity checks */ + if( model == NULL ) + return NULL; + if( model->shader == NULL) + return NULL; + if( num < 0 || num >= model->numShaders ) + return NULL; + + /* return the shader */ + return model->shader[ num ]; +} + + + +int PicoGetModelNumSurfaces( picoModel_t *model ) +{ + if( model == NULL ) + return 0; + return model->numSurfaces; +} + + + +picoSurface_t *PicoGetModelSurface( picoModel_t *model, int num ) +{ + /* a few sanity checks */ + if( model == NULL ) + return NULL; + if( model->surface == NULL) + return NULL; + if( num < 0 || num >= model->numSurfaces ) + return NULL; + + /* return the surface */ + return model->surface[ num ]; +} + + + +int PicoGetModelTotalVertexes( picoModel_t *model ) +{ + int i, count; + + + if( model == NULL ) + return 0; + if( model->surface == NULL ) + return 0; + + count = 0; + for( i = 0; i < model->numSurfaces; i++ ) + count += PicoGetSurfaceNumVertexes( model->surface[ i ] ); + + return count; +} + + + +int PicoGetModelTotalIndexes( picoModel_t *model ) +{ + int i, count; + + + if( model == NULL ) + return 0; + if( model->surface == NULL ) + return 0; + + count = 0; + for( i = 0; i < model->numSurfaces; i++ ) + count += PicoGetSurfaceNumIndexes( model->surface[ i ] ); + + return count; +} + + + +char *PicoGetShaderName( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + if( shader->name == NULL) + return (char*) ""; + return shader->name; +} + + + +char *PicoGetShaderMapName( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + if( shader->mapName == NULL) + return (char*) ""; + return shader->mapName; +} + + + +picoByte_t *PicoGetShaderAmbientColor( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + return shader->ambientColor; +} + + + +picoByte_t *PicoGetShaderDiffuseColor( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + return shader->diffuseColor; +} + + + +picoByte_t *PicoGetShaderSpecularColor( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + return shader->specularColor; +} + + + +float PicoGetShaderTransparency( picoShader_t *shader ) +{ + if( shader == NULL ) + return 0.0f; + return shader->transparency; +} + + + +float PicoGetShaderShininess( picoShader_t *shader ) +{ + if( shader == NULL ) + return 0.0f; + return shader->shininess; +} + + + +void *PicoGetSurfaceData( picoSurface_t *surface ) +{ + if( surface == NULL ) + return NULL; + return surface->data; +} + + + +picoSurfaceType_t PicoGetSurfaceType( picoSurface_t *surface ) +{ + if( surface == NULL ) + return PICO_BAD; + return surface->type; +} + + + +char *PicoGetSurfaceName( picoSurface_t *surface ) +{ + if( surface == NULL ) + return NULL; + if( surface->name == NULL ) + return (char*) ""; + return surface->name; +} + + + +picoShader_t *PicoGetSurfaceShader( picoSurface_t *surface ) +{ + if( surface == NULL ) + return NULL; + return surface->shader; +} + + + +int PicoGetSurfaceNumVertexes( picoSurface_t *surface ) +{ + if( surface == NULL ) + return 0; + return surface->numVertexes; +} + + + +picoVec_t *PicoGetSurfaceXYZ( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numVertexes ) + return NULL; + return surface->xyz[ num ]; +} + + + +picoVec_t *PicoGetSurfaceNormal( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numVertexes ) + return NULL; + return surface->normal[ num ]; +} + + + +picoVec_t *PicoGetSurfaceST( picoSurface_t *surface, int array, int num ) +{ + if( surface == NULL || array < 0 || array > surface->numSTArrays || num < 0 || num > surface->numVertexes ) + return NULL; + return surface->st[ array ][ num ]; +} + + + +picoByte_t *PicoGetSurfaceColor( picoSurface_t *surface, int array, int num ) +{ + if( surface == NULL || array < 0 || array > surface->numColorArrays || num < 0 || num > surface->numVertexes ) + return NULL; + return surface->color[ array ][ num ]; +} + + + +int PicoGetSurfaceNumIndexes( picoSurface_t *surface ) +{ + if( surface == NULL ) + return 0; + return surface->numIndexes; +} + + + +picoIndex_t PicoGetSurfaceIndex( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numIndexes ) + return 0; + return surface->index[ num ]; +} + + + +picoIndex_t *PicoGetSurfaceIndexes( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numIndexes ) + return NULL; + return &surface->index[ num ]; +} + + +picoVec_t *PicoGetFaceNormal( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numFaceNormals ) + return NULL; + return surface->faceNormal[ num ]; +} + +picoIndex_t PicoGetSurfaceSmoothingGroup( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numVertexes ) + return -1; + return surface->smoothingGroup[ num ]; +} + + +int PicoGetSurfaceSpecial( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL ) + return 0; + return surface->special[ num ]; +} + + + +/* ---------------------------------------------------------------------------- +hashtable related functions +---------------------------------------------------------------------------- */ + +/* hashtable code for faster vertex lookups */ +//#define HASHTABLE_SIZE 32768 // 2048 /* power of 2, use & */ +#define HASHTABLE_SIZE 7919 // 32749 // 2039 /* prime, use % */ + +int PicoGetHashTableSize( void ) +{ + return HASHTABLE_SIZE; +} + +#define HASH_USE_EPSILON + +#ifdef HASH_USE_EPSILON +#define HASH_XYZ_EPSILON 0.01f +#define HASH_XYZ_EPSILONSPACE_MULTIPLIER 1.f / HASH_XYZ_EPSILON +#define HASH_ST_EPSILON 0.0001f +#define HASH_NORMAL_EPSILON 0.02f +#endif + +unsigned int PicoVertexCoordGenerateHash( picoVec3_t xyz ) +{ + unsigned int hash = 0; + +#ifndef HASH_USE_EPSILON + hash += ~(*((unsigned int*) &xyz[ 0 ]) << 15); + hash ^= (*((unsigned int*) &xyz[ 0 ]) >> 10); + hash += (*((unsigned int*) &xyz[ 1 ]) << 3); + hash ^= (*((unsigned int*) &xyz[ 1 ]) >> 6); + hash += ~(*((unsigned int*) &xyz[ 2 ]) << 11); + hash ^= (*((unsigned int*) &xyz[ 2 ]) >> 16); +#else + picoVec3_t xyz_epsilonspace; + + _pico_scale_vec( xyz, HASH_XYZ_EPSILONSPACE_MULTIPLIER, xyz_epsilonspace ); + xyz_epsilonspace[ 0 ] = (float)floor(xyz_epsilonspace[ 0 ]); + xyz_epsilonspace[ 1 ] = (float)floor(xyz_epsilonspace[ 1 ]); + xyz_epsilonspace[ 2 ] = (float)floor(xyz_epsilonspace[ 2 ]); + + hash += ~(*((unsigned int*) &xyz_epsilonspace[ 0 ]) << 15); + hash ^= (*((unsigned int*) &xyz_epsilonspace[ 0 ]) >> 10); + hash += (*((unsigned int*) &xyz_epsilonspace[ 1 ]) << 3); + hash ^= (*((unsigned int*) &xyz_epsilonspace[ 1 ]) >> 6); + hash += ~(*((unsigned int*) &xyz_epsilonspace[ 2 ]) << 11); + hash ^= (*((unsigned int*) &xyz_epsilonspace[ 2 ]) >> 16); +#endif + + //hash = hash & (HASHTABLE_SIZE-1); + hash = hash % (HASHTABLE_SIZE); + return hash; +} + +picoVertexCombinationHash_t **PicoNewVertexCombinationHashTable( void ) +{ + picoVertexCombinationHash_t **hashTable = _pico_alloc( HASHTABLE_SIZE * sizeof(picoVertexCombinationHash_t*) ); + + memset( hashTable, 0, HASHTABLE_SIZE * sizeof(picoVertexCombinationHash_t*) ); + + return hashTable; +} + +void PicoFreeVertexCombinationHashTable( picoVertexCombinationHash_t **hashTable ) +{ + int i; + picoVertexCombinationHash_t *vertexCombinationHash; + picoVertexCombinationHash_t *nextVertexCombinationHash; + + /* dummy check */ + if (hashTable == NULL) + return; + + for( i = 0; i < HASHTABLE_SIZE; i++ ) + { + if (hashTable[ i ]) + { + nextVertexCombinationHash = NULL; + + for( vertexCombinationHash = hashTable[ i ]; vertexCombinationHash; vertexCombinationHash = nextVertexCombinationHash ) + { + nextVertexCombinationHash = vertexCombinationHash->next; + if (vertexCombinationHash->data != NULL) + { + _pico_free( vertexCombinationHash->data ); + } + _pico_free( vertexCombinationHash ); + } + } + } + + _pico_free( hashTable ); +} + +picoVertexCombinationHash_t *PicoFindVertexCombinationInHashTable( picoVertexCombinationHash_t **hashTable, picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color ) +{ + unsigned int hash; + picoVertexCombinationHash_t *vertexCombinationHash; + + /* dumy check */ + if (hashTable == NULL || xyz == NULL || normal == NULL || st == NULL || color == NULL ) + return NULL; + + hash = PicoVertexCoordGenerateHash( xyz ); + + for( vertexCombinationHash = hashTable[ hash ]; vertexCombinationHash; vertexCombinationHash = vertexCombinationHash->next ) + { +#ifndef HASH_USE_EPSILON + /* check xyz */ + if( (vertexCombinationHash->vcd.xyz[ 0 ] != xyz[ 0 ] || vertexCombinationHash->vcd.xyz[ 1 ] != xyz[ 1 ] || vertexCombinationHash->vcd.xyz[ 2 ] != xyz[ 2 ]) ) + continue; + + /* check normal */ + if( (vertexCombinationHash->vcd.normal[ 0 ] != normal[ 0 ] || vertexCombinationHash->vcd.normal[ 1 ] != normal[ 1 ] || vertexCombinationHash->vcd.normal[ 2 ] != normal[ 2 ]) ) + continue; + + /* check st */ + if( vertexCombinationHash->vcd.st[ 0 ] != st[ 0 ] || vertexCombinationHash->vcd.st[ 1 ] != st[ 1 ] ) + continue; +#else + /* check xyz */ + if( ( fabs(xyz[ 0 ] - vertexCombinationHash->vcd.xyz[ 0 ]) ) > HASH_XYZ_EPSILON || + ( fabs(xyz[ 1 ] - vertexCombinationHash->vcd.xyz[ 1 ]) ) > HASH_XYZ_EPSILON || + ( fabs(xyz[ 2 ] - vertexCombinationHash->vcd.xyz[ 2 ]) ) > HASH_XYZ_EPSILON ) + continue; + + /* check normal */ + if( ( fabs(normal[ 0 ] - vertexCombinationHash->vcd.normal[ 0 ]) ) > HASH_NORMAL_EPSILON || + ( fabs(normal[ 1 ] - vertexCombinationHash->vcd.normal[ 1 ]) ) > HASH_NORMAL_EPSILON || + ( fabs(normal[ 2 ] - vertexCombinationHash->vcd.normal[ 2 ]) ) > HASH_NORMAL_EPSILON ) + continue; + + /* check st */ + if( ( fabs(st[ 0 ] - vertexCombinationHash->vcd.st[ 0 ]) ) > HASH_ST_EPSILON || + ( fabs(st[ 1 ] - vertexCombinationHash->vcd.st[ 1 ]) ) > HASH_ST_EPSILON ) + continue; +#endif + + /* check color */ + if( *((int*) vertexCombinationHash->vcd.color) != *((int*) color) ) + continue; + + /* gotcha */ + return vertexCombinationHash; + } + + return NULL; +} + +picoVertexCombinationHash_t *PicoAddVertexCombinationToHashTable( picoVertexCombinationHash_t **hashTable, picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color, picoIndex_t index ) +{ + unsigned int hash; + picoVertexCombinationHash_t *vertexCombinationHash; + + /* dumy check */ + if (hashTable == NULL || xyz == NULL || normal == NULL || st == NULL || color == NULL ) + return NULL; + + vertexCombinationHash = _pico_alloc( sizeof(picoVertexCombinationHash_t) ); + + if (!vertexCombinationHash) + return NULL; + + hash = PicoVertexCoordGenerateHash( xyz ); + + _pico_copy_vec( xyz, vertexCombinationHash->vcd.xyz ); + _pico_copy_vec( normal, vertexCombinationHash->vcd.normal ); + _pico_copy_vec2( st, vertexCombinationHash->vcd.st ); + _pico_copy_color( color, vertexCombinationHash->vcd.color ); + vertexCombinationHash->index = index; + vertexCombinationHash->data = NULL; + vertexCombinationHash->next = hashTable[ hash ]; + hashTable[ hash ] = vertexCombinationHash; + + return vertexCombinationHash; +} + +/* ---------------------------------------------------------------------------- +specialized routines +---------------------------------------------------------------------------- */ + +/* +PicoFindSurfaceVertex() +finds a vertex matching the set parameters +fixme: needs non-naive algorithm +*/ + +int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color, picoIndex_t smoothingGroup) +{ + int i, j; + +// Sys_Printf(" %f %f %f\n", normal[0] , normal[1] , normal[2] ); + + /* dummy check */ + if( surface == NULL || surface->numVertexes <= 0 ) + return -1; + + /* walk vertex list */ + for( i = 0; i < surface->numVertexes; i++ ) + { + /* check xyz */ + if( xyz != NULL && (surface->xyz[ i ][ 0 ] != xyz[ 0 ] || surface->xyz[ i ][ 1 ] != xyz[ 1 ] || surface->xyz[ i ][ 2 ] != xyz[ 2 ]) ) + continue; + + /* check normal */ + if( normal != NULL && (surface->normal[ i ][ 0 ] != normal[ 0 ] || surface->normal[ i ][ 1 ] != normal[ 1 ] || surface->normal[ i ][ 2 ] != normal[ 2 ]) ) + continue; + + /* check normal */ + if( surface->smoothingGroup[ i ] != smoothingGroup ) + continue; + + /* check st */ + if( numSTs > 0 && st != NULL ) + { + for( j = 0; j < numSTs; j++ ) + { + if( surface->st[ j ][ i ][ 0 ] != st[ j ][ 0 ] || surface->st[ j ][ i ][ 1 ] != st[ j ][ 1 ] ) + break; + } + if( j != numSTs ) + continue; + } + + /* check color */ + if( numColors > 0 && color != NULL ) + { + for( j = 0; j < numSTs; j++ ) + { + if( *((int*) surface->color[ j ]) != *((int*) color[ j ]) ) + break; + } + if( j != numColors ) + continue; + } + + /* vertex matches */ + return i; + } + + /* nada */ + return -1; +} + + + + +typedef struct _IndexArray IndexArray; +struct _IndexArray +{ + picoIndex_t* data; + picoIndex_t* last; +}; + +void indexarray_push_back(IndexArray* self, picoIndex_t value) +{ + *self->last++ = value; +} + +size_t indexarray_size(IndexArray* self) +{ + return self->last - self->data; +} + +void indexarray_reserve(IndexArray* self, size_t size) +{ + self->data = self->last = _pico_calloc(size, sizeof(picoIndex_t)); +} + +void indexarray_clear(IndexArray* self) +{ + _pico_free(self->data); +} + +typedef struct _BinaryTreeNode BinaryTreeNode; +struct _BinaryTreeNode +{ + picoIndex_t left; + picoIndex_t right; +}; + +typedef struct _BinaryTree BinaryTree; +struct _BinaryTree +{ + BinaryTreeNode* data; + BinaryTreeNode* last; +}; + +void binarytree_extend(BinaryTree* self) +{ + self->last->left = 0; + self->last->right = 0; + ++self->last; +} + +size_t binarytree_size(BinaryTree* self) +{ + return self->last - self->data; +} + +void binarytree_reserve(BinaryTree* self, size_t size) +{ + self->data = self->last = _pico_calloc(size, sizeof(BinaryTreeNode)); +} + +void binarytree_clear(BinaryTree* self) +{ + _pico_free(self->data); +} + +typedef int (*LessFunc)(void*, picoIndex_t, picoIndex_t); + +typedef struct _UniqueIndices UniqueIndices; +struct _UniqueIndices +{ + BinaryTree tree; + IndexArray indices; + LessFunc lessFunc; + void* lessData; +}; + +size_t UniqueIndices_size(UniqueIndices* self) +{ + return binarytree_size(&self->tree); +} + +void UniqueIndices_reserve(UniqueIndices* self, size_t size) +{ + binarytree_reserve(&self->tree, size); + indexarray_reserve(&self->indices, size); +} + +void UniqueIndices_init(UniqueIndices* self, LessFunc lessFunc, void* lessData) +{ + self->lessFunc = lessFunc; + self->lessData = lessData; +} + +void UniqueIndices_destroy(UniqueIndices* self) +{ + binarytree_clear(&self->tree); + indexarray_clear(&self->indices); +} + + +picoIndex_t UniqueIndices_find_or_insert(UniqueIndices* self, picoIndex_t value) +{ + picoIndex_t index = 0; + + for(;;) + { + if(self->lessFunc(self->lessData, value, self->indices.data[index])) + { + BinaryTreeNode* node = self->tree.data + index; + if(node->left != 0) + { + index = node->left; + continue; + } + else + { + node->left = (picoIndex_t)binarytree_size(&self->tree); + binarytree_extend(&self->tree); + indexarray_push_back(&self->indices, value); + return node->left; + } + } + if(self->lessFunc(self->lessData, self->indices.data[index], value)) + { + BinaryTreeNode* node = self->tree.data + index; + if(node->right != 0) + { + index = node->right; + continue; + } + else + { + node->right = (picoIndex_t)binarytree_size(&self->tree); + binarytree_extend(&self->tree); + indexarray_push_back(&self->indices, value); + return node->right; + } + } + + return index; + } +} + +picoIndex_t UniqueIndices_insert(UniqueIndices* self, picoIndex_t value) +{ + if(self->tree.data == self->tree.last) + { + binarytree_extend(&self->tree); + indexarray_push_back(&self->indices, value); + return 0; + } + else + { + return UniqueIndices_find_or_insert(self, value); + } +} + +typedef struct picoSmoothVertices_s picoSmoothVertices_t; +struct picoSmoothVertices_s +{ + picoVec3_t* xyz; + picoIndex_t* smoothingGroups; +}; + +int lessSmoothVertex(void* data, picoIndex_t first, picoIndex_t second) +{ + picoSmoothVertices_t* smoothVertices = data; + + if(smoothVertices->xyz[first][0] != smoothVertices->xyz[second][0]) + { + return smoothVertices->xyz[first][0] < smoothVertices->xyz[second][0]; + } + if(smoothVertices->xyz[first][1] != smoothVertices->xyz[second][1]) + { + return smoothVertices->xyz[first][1] < smoothVertices->xyz[second][1]; + } + if(smoothVertices->xyz[first][2] != smoothVertices->xyz[second][2]) + { + return smoothVertices->xyz[first][2] < smoothVertices->xyz[second][2]; + } + if(smoothVertices->smoothingGroups[first] != smoothVertices->smoothingGroups[second]) + { + return smoothVertices->smoothingGroups[first] < smoothVertices->smoothingGroups[second]; + } + return 0; +} + +void _pico_vertices_combine_shared_normals(picoVec3_t* xyz, picoIndex_t* smoothingGroups, picoVec3_t* normals, picoIndex_t numVertices) +{ + UniqueIndices vertices; + IndexArray indices; + picoSmoothVertices_t smoothVertices = { xyz, smoothingGroups }; + UniqueIndices_init(&vertices, lessSmoothVertex, &smoothVertices); + UniqueIndices_reserve(&vertices, numVertices); + indexarray_reserve(&indices, numVertices); + + + { + picoIndex_t i = 0; + for(; i < numVertices; ++i) + { + size_t size = UniqueIndices_size(&vertices); + picoIndex_t index = UniqueIndices_insert(&vertices, i); + if((size_t)index != size) + { + float* normal = normals[vertices.indices.data[index]]; + _pico_add_vec(normal, normals[i], normal); + } + indexarray_push_back(&indices, index); + } + } + + { + picoIndex_t maxIndex = 0; + picoIndex_t* i = indices.data; + for(; i != indices.last; ++i) + { + if(*i <= maxIndex) + { + _pico_copy_vec(normals[vertices.indices.data[*i]], normals[i - indices.data]); + } + else + { + maxIndex = *i; + } + } + } + + UniqueIndices_destroy(&vertices); + indexarray_clear(&indices); +} + +typedef picoVec3_t* picoNormalIter_t; +typedef picoIndex_t* picoIndexIter_t; + +void _pico_triangles_generate_weighted_normals(picoIndexIter_t first, picoIndexIter_t end, picoVec3_t* xyz, picoVec3_t* normals) +{ + for(; first != end; first += 3) + { + picoVec3_t weightedNormal; + { + float* a = xyz[*(first + 0)]; + float* b = xyz[*(first + 1)]; + float* c = xyz[*(first + 2)]; + picoVec3_t ba, ca; + _pico_subtract_vec( b, a, ba ); + _pico_subtract_vec( c, a, ca ); + _pico_cross_vec( ca, ba, weightedNormal ); + } + { + int j = 0; + for(; j < 3; ++j) + { + float* normal = normals[*(first + j)]; + _pico_add_vec(weightedNormal, normal, normal); + } + } + } +} + +void _pico_normals_zero(picoNormalIter_t first, picoNormalIter_t last) +{ + for(; first != last; ++first) + { + _pico_zero_vec(*first); + } +} + +void _pico_normals_normalize(picoNormalIter_t first, picoNormalIter_t last) +{ + for(; first != last; ++first) + { + _pico_normalize_vec(*first); + } +} + +double _pico_length_vec( picoVec3_t vec ) +{ + return sqrt( vec[ 0 ] * vec[ 0 ] + vec[ 1 ] * vec[ 1 ] + vec[ 2 ] * vec[ 2 ] ); +} + +#define NORMAL_UNIT_LENGTH_EPSILON 0.01 +#define FLOAT_EQUAL_EPSILON(f, other, epsilon) (fabs(f - other) < epsilon) + +int _pico_normal_is_unit_length(picoVec3_t normal) +{ + return FLOAT_EQUAL_EPSILON(_pico_length_vec(normal), 1.0, NORMAL_UNIT_LENGTH_EPSILON); +} + +int _pico_normal_within_tolerance(picoVec3_t normal, picoVec3_t other) +{ + return _pico_dot_vec(normal, other) > 0.0f; +} + + +void _pico_normals_assign_generated_normals(picoNormalIter_t first, picoNormalIter_t last, picoNormalIter_t generated) +{ + for(; first != last; ++first, ++generated) + { + //27 - fix for badly generated normals thing. + // if(!_pico_normal_is_unit_length(*first) || !_pico_normal_within_tolerance(*first, *generated)) + { + _pico_copy_vec(*generated, *first); + } + } +} + +void PicoFixSurfaceNormals(picoSurface_t* surface) +{ + picoVec3_t* normals = (picoVec3_t*)_pico_calloc(surface->numVertexes, sizeof(picoVec3_t)); + + _pico_normals_zero(normals, normals + surface->numVertexes); + + //Just build standard no sg normals for now + _pico_triangles_generate_weighted_normals(surface->index, surface->index + surface->numIndexes, surface->xyz, normals); + _pico_vertices_combine_shared_normals(surface->xyz, surface->smoothingGroup, normals, surface->numVertexes); + + _pico_normals_normalize(normals, normals + surface->numVertexes); + + _pico_normals_assign_generated_normals(surface->normal, surface->normal + surface->numVertexes, normals); + + _pico_free(normals); +} + + +/* +PicoRemapModel() - sea +remaps model material/etc. information using the remappings +contained in the given 'remapFile' (full path to the ascii file to open) +returns 1 on success or 0 on error +*/ + +#define _prm_error_return \ +{ \ + _pico_free_parser( p ); \ + _pico_free_file( remapBuffer ); \ + return 0; \ +} + +int PicoRemapModel( picoModel_t *model, char *remapFile ) +{ + picoParser_t *p; + picoByte_t *remapBuffer; + int remapBufSize; + + + /* sanity checks */ + if( model == NULL || remapFile == NULL ) + return 0; + + /* load remap file contents */ + _pico_load_file( remapFile,&remapBuffer,&remapBufSize ); + + /* check result */ + if( remapBufSize == 0 ) + return 1; /* file is empty: no error */ + if( remapBufSize < 0 ) + return 0; /* load failed: error */ + + /* create a new pico parser */ + p = _pico_new_parser( remapBuffer, remapBufSize ); + if (p == NULL) + { + /* ram is really cheap nowadays... */ + _prm_error_return; + } + + /* doo teh parse */ + while( 1 ) + { + /* get next token in remap file */ + if (!_pico_parse( p,1 )) + break; + + /* skip over c++ style comment lines */ + if (!_pico_stricmp(p->token,"//")) + { + _pico_parse_skip_rest( p ); + continue; + } + + /* block for quick material shader name remapping */ + /* materials { "m" (=>|->|=) "s" } */ + if( !_pico_stricmp(p->token, "materials" ) ) + { + int level = 1; + + /* check bracket */ + if (!_pico_parse_check( p,1,"{" )) + _prm_error_return; + + /* process assignments */ + while( 1 ) + { + picoShader_t *shader; + char *materialName; + + + /* get material name */ + if (_pico_parse( p,1 ) == NULL) break; + if (!strlen(p->token)) continue; + materialName = _pico_clone_alloc( p->token ); + if (materialName == NULL) + _prm_error_return; + + /* handle levels */ + if (p->token[0] == '{') level++; + if (p->token[0] == '}') level--; + if (!level) break; + + /* get next token (assignment token or shader name) */ + if (!_pico_parse( p,0 )) + { + _pico_free( materialName ); + _prm_error_return; + } + /* skip assignment token (if present) */ + if (!strcmp(p->token,"=>") || + !strcmp(p->token,"->") || + !strcmp(p->token,"=")) + { + /* simply grab the next token */ + if (!_pico_parse( p,0 )) + { + _pico_free( materialName ); + _prm_error_return; + } + } + /* try to find material by name */ + shader = PicoFindShader( model,materialName,0 ); + + /* we've found a material matching the name */ + if (shader != NULL) + { + PicoSetShaderName( shader,p->token ); + } + /* free memory used by material name */ + _pico_free( materialName ); + + /* skip rest */ + _pico_parse_skip_rest( p ); + } + } + /* block for detailed single material remappings */ + /* materials[ "m" ] { key data... } */ + else if (!_pico_stricmp(p->token,"materials[")) + { + picoShader_t *shader; + char *tempMaterialName; + int level = 1; + + /* get material name */ + if (!_pico_parse( p,0 )) + _prm_error_return; + + /* temporary copy of material name */ + tempMaterialName = _pico_clone_alloc( p->token ); + if (tempMaterialName == NULL) + _prm_error_return; + + /* check square closing bracket */ + if (!_pico_parse_check( p,0,"]" )) + _prm_error_return; + + /* try to find material by name */ + shader = PicoFindShader( model,tempMaterialName,0 ); + + /* free memory used by temporary material name */ + _pico_free( tempMaterialName ); + + /* we haven't found a material matching the name */ + /* so we simply skip the braced section now and */ + /* continue parsing with the next main token */ + if (shader == NULL) + { + _pico_parse_skip_braced( p ); + continue; + } + /* check opening bracket */ + if (!_pico_parse_check( p,1,"{" )) + _prm_error_return; + + /* process material info keys */ + while( 1 ) + { + /* get key name */ + if (_pico_parse( p,1 ) == NULL) break; + if (!strlen(p->token)) continue; + + /* handle levels */ + if (p->token[0] == '{') level++; + if (p->token[0] == '}') level--; + if (!level) break; + + /* remap shader name */ + if (!_pico_stricmp(p->token,"shader")) + { + if (!_pico_parse( p,0 )) _prm_error_return; + PicoSetShaderName( shader,p->token ); + } + /* remap shader map name */ + else if (!_pico_stricmp(p->token,"mapname")) + { + if (!_pico_parse( p,0 )) _prm_error_return; + PicoSetShaderMapName( shader,p->token ); + } + /* remap shader's ambient color */ + else if (!_pico_stricmp(p->token,"ambient")) + { + picoColor_t color; + picoVec3_t v; + + /* get vector from parser */ + if (!_pico_parse_vec( p,v )) _prm_error_return; + + /* store as color */ + color[ 0 ] = (picoByte_t)v[ 0 ]; + color[ 1 ] = (picoByte_t)v[ 1 ]; + color[ 2 ] = (picoByte_t)v[ 2 ]; + + /* set new ambient color */ + PicoSetShaderAmbientColor( shader,color ); + } + /* remap shader's diffuse color */ + else if (!_pico_stricmp(p->token,"diffuse")) + { + picoColor_t color; + picoVec3_t v; + + /* get vector from parser */ + if (!_pico_parse_vec( p,v )) _prm_error_return; + + /* store as color */ + color[ 0 ] = (picoByte_t)v[ 0 ]; + color[ 1 ] = (picoByte_t)v[ 1 ]; + color[ 2 ] = (picoByte_t)v[ 2 ]; + + /* set new ambient color */ + PicoSetShaderDiffuseColor( shader,color ); + } + /* remap shader's specular color */ + else if (!_pico_stricmp(p->token,"specular")) + { + picoColor_t color; + picoVec3_t v; + + /* get vector from parser */ + if (!_pico_parse_vec( p,v )) _prm_error_return; + + /* store as color */ + color[ 0 ] = (picoByte_t)v[ 0 ]; + color[ 1 ] = (picoByte_t)v[ 1 ]; + color[ 2 ] = (picoByte_t)v[ 2 ]; + + /* set new ambient color */ + PicoSetShaderSpecularColor( shader,color ); + } + /* skip rest */ + _pico_parse_skip_rest( p ); + } + } + /* end 'materials[' */ + } + + /* free both parser and file buffer */ + _pico_free_parser( p ); + _pico_free_file( remapBuffer ); + + /* return with success */ + return 1; +} + + +/* +PicoAddTriangleToModel() - jhefty +A nice way to add individual triangles to the model. +Chooses an appropriate surface based on the shader, or adds a new surface if necessary +*/ + +void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** normals, + int numSTs, picoVec2_t **st, int numColors, picoColor_t **colors, + picoShader_t* shader, picoIndex_t* smoothingGroup,int submodel ) +{ + int i,j; + int vertDataIndex; + picoSurface_t* workSurface = NULL; + + /* see if a surface already has the shader */ + for ( i = 0 ; i < model->numSurfaces ; i++ ) + { + workSurface = model->surface[i]; + if (( workSurface->shader == shader )&&(workSurface->submodel==submodel)) + { + break; + } + } + + /* no surface uses this shader yet, so create a new surface */ + if ( !workSurface || i >=model->numSurfaces ) + { + /* create a new surface in the model for the unique shader */ + workSurface = PicoNewSurface(model); + if ( !workSurface ) + { + _pico_printf ( PICO_ERROR , "Could not allocate a new surface!\n" ); + return; + } + + /* do surface setup */ + PicoSetSurfaceType( workSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( workSurface, shader->name ); + PicoSetSurfaceShader( workSurface, shader ); + workSurface->submodel=submodel; + } + + /* add the triangle data to the surface */ + for ( i = 0 ; i < 3 ; i++ ) + { + /* get the next free spot in the index array */ + int newVertIndex = PicoGetSurfaceNumIndexes ( workSurface ); + + /* get the index of the vertex that we're going to store at newVertIndex */ + vertDataIndex = -1;// PicoFindSurfaceVertexNum ( workSurface , *xyz[i] , *normals[i] , numSTs , st[i] , numColors , colors[i], smoothingGroup[i]); + + /* the vertex wasn't found, so create a new vertex in the pool from the data we have */ + if ( vertDataIndex == -1 ) + { + /* find the next spot for a new vertex */ + vertDataIndex = PicoGetSurfaceNumVertexes ( workSurface ); + + /* assign the data to it */ + PicoSetSurfaceXYZ ( workSurface ,vertDataIndex , *xyz[i] ); + PicoSetSurfaceNormal ( workSurface , vertDataIndex , *normals[i] ); + + /* make sure to copy over all available ST's and colors for the vertex */ + for ( j = 0 ; j < numColors ; j++ ) + { + PicoSetSurfaceColor( workSurface , j , vertDataIndex , colors[i][j] ); + } + for ( j = 0 ; j < numSTs ; j++ ) + { + PicoSetSurfaceST ( workSurface , j , vertDataIndex , st[i][j] ); + } + + PicoSetSurfaceSmoothingGroup ( workSurface , vertDataIndex , smoothingGroup[i] ); + } + + /* add this vertex to the triangle */ + PicoSetSurfaceIndex ( workSurface , newVertIndex , vertDataIndex ); + } +} + + diff --git a/tools/urt/libs/picomodel/picomodel.dsp b/tools/urt/libs/picomodel/picomodel.dsp new file mode 100644 index 00000000..8d722777 --- /dev/null +++ b/tools/urt/libs/picomodel/picomodel.dsp @@ -0,0 +1,210 @@ +# Microsoft Developer Studio Project File - Name="picomodel" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=picomodel - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "picomodel.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "picomodel.mak" CFG="picomodel - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "picomodel - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "picomodel - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "picomodel" +# PROP Scc_LocalPath ".." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "picomodel - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W3 /Zi /O2 /I ".." /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x40c /d "NDEBUG" +# ADD RSC /l 0x40c /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "picomodel - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +MTL=midl.exe +F90=df.exe +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /ZI /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x40c /d "_DEBUG" +# ADD RSC /l 0x40c /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "picomodel - Win32 Release" +# Name "picomodel - Win32 Debug" +# Begin Group "src" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Group "lwo" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\lwo\clip.c +# End Source File +# Begin Source File + +SOURCE=.\lwo\envelope.c +# End Source File +# Begin Source File + +SOURCE=.\lwo\list.c +# End Source File +# Begin Source File + +SOURCE=.\lwo\lwio.c +# End Source File +# Begin Source File + +SOURCE=.\lwo\lwo2.c +# End Source File +# Begin Source File + +SOURCE=.\lwo\lwob.c +# End Source File +# Begin Source File + +SOURCE=.\lwo\pntspols.c +# End Source File +# Begin Source File + +SOURCE=.\lwo\surface.c +# End Source File +# Begin Source File + +SOURCE=.\lwo\vecmath.c +# End Source File +# Begin Source File + +SOURCE=.\lwo\vmap.c +# End Source File +# End Group +# Begin Source File + +SOURCE=.\picointernal.c +# End Source File +# Begin Source File + +SOURCE=.\picomodel.c +# End Source File +# Begin Source File + +SOURCE=.\picomodules.c +# End Source File +# Begin Source File + +SOURCE=.\pm_3ds.c +# End Source File +# Begin Source File + +SOURCE=.\pm_ase.c +# End Source File +# Begin Source File + +SOURCE=.\pm_fm.c +# End Source File +# Begin Source File + +SOURCE=.\pm_lwo.c +# End Source File +# Begin Source File + +SOURCE=.\pm_md2.c +# End Source File +# Begin Source File + +SOURCE=.\pm_md3.c +# End Source File +# Begin Source File + +SOURCE=.\pm_mdc.c +# End Source File +# Begin Source File + +SOURCE=.\pm_ms3d.c +# End Source File +# Begin Source File + +SOURCE=.\pm_obj.c +# End Source File +# Begin Source File + +SOURCE=.\pm_terrain.c +# End Source File +# End Group +# Begin Group "include" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\lwo\lwo2.h +# End Source File +# Begin Source File + +SOURCE=.\picointernal.h +# End Source File +# Begin Source File + +SOURCE=..\picomodel.h +# End Source File +# Begin Source File + +SOURCE=.\pm_fm.h +# End Source File +# End Group +# End Target +# End Project diff --git a/tools/urt/libs/picomodel/picomodel.plg b/tools/urt/libs/picomodel/picomodel.plg new file mode 100644 index 00000000..c8a6fffe --- /dev/null +++ b/tools/urt/libs/picomodel/picomodel.plg @@ -0,0 +1,16 @@ + + +

+

Build Log

+

+--------------------Configuration: picomodel - Win32 Debug-------------------- +

+

Command Lines

+ + + +

Results

+picomodel.lib - 0 error(s), 0 warning(s) +
+ + diff --git a/tools/urt/libs/picomodel/picomodel.vcproj b/tools/urt/libs/picomodel/picomodel.vcproj new file mode 100644 index 00000000..442e02c4 --- /dev/null +++ b/tools/urt/libs/picomodel/picomodel.vcproj @@ -0,0 +1,713 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/urt/libs/picomodel/picomodules.c b/tools/urt/libs/picomodel/picomodules.c new file mode 100644 index 00000000..f5632a44 --- /dev/null +++ b/tools/urt/libs/picomodel/picomodules.c @@ -0,0 +1,94 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PICOMODULES_C + + + +/* dependencies */ +#include "picointernal.h" + + + +/* external modules */ +extern const picoModule_t picoModuleMD3; +extern const picoModule_t picoModule3DS; +extern const picoModule_t picoModuleASE; +extern const picoModule_t picoModuleOBJ; +extern const picoModule_t picoModuleMS3D; +extern const picoModule_t picoModuleMDC; +extern const picoModule_t picoModuleMD2; +extern const picoModule_t picoModuleFM; +extern const picoModule_t picoModuleLWO; +extern const picoModule_t picoModuleTerrain; + + + +/* list of all supported file format modules */ +const picoModule_t *picoModules[] = +{ + &picoModuleMD3, /* quake3 arena md3 */ + &picoModule3DS, /* autodesk 3ds */ + &picoModuleASE, /* autodesk ase */ + &picoModuleMS3D, /* milkshape3d */ + &picoModuleMDC, /* return to castle wolfenstein mdc */ + &picoModuleMD2, /* quake2 md2 */ + &picoModuleFM, /* heretic2 fm */ + &picoModuleLWO, /* lightwave object */ + &picoModuleTerrain, /* picoterrain object */ + &picoModuleOBJ, /* wavefront object */ + NULL /* arnold */ +}; + + + +/* +PicoModuleList() +returns a pointer to the module list and optionally stores +the number of supported modules in 'numModules'. Note that +this param can be NULL when the count is not needed. +*/ + +const picoModule_t **PicoModuleList( int *numModules ) +{ + /* get module count */ + if( numModules != NULL ) + for( (*numModules) = 0; picoModules[ *numModules ] != NULL; (*numModules)++ ); + + /* return list of modules */ + return (const picoModule_t**) picoModules; +} diff --git a/tools/urt/libs/picomodel/pm_3ds.c b/tools/urt/libs/picomodel/pm_3ds.c new file mode 100644 index 00000000..2b19f10e --- /dev/null +++ b/tools/urt/libs/picomodel/pm_3ds.c @@ -0,0 +1,777 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PM_3DS_C + +/* dependencies */ +#include "picointernal.h" + +/* ydnar */ +static picoColor_t white = { 255,255,255,255 }; + +/* remarks: + * - 3ds file version is stored in pico special field 0 on load (ydnar: removed) + * todo: + * - sometimes there is one unnamed surface 0 having 0 verts as + * well as 0 faces. this error occurs since pm 0.6 (ydnar?) + */ +/* uncomment when debugging this module */ +/* #define DEBUG_PM_3DS +#define DEBUG_PM_3DS_EX */ + +/* structure holding persistent 3ds loader specific data used */ +/* to store formerly static vars to keep the module reentrant */ +/* safe. put everything that needs to be static in here. */ +typedef struct S3dsLoaderPers +{ + picoModel_t *model; /* ptr to output model */ + picoSurface_t *surface; /* ptr to current surface */ + picoShader_t *shader; /* ptr to current shader */ + picoByte_t *bufptr; /* ptr to raw data */ + char *basename; /* ptr to model base name (eg. jeep) */ + int cofs; + int maxofs; +} +T3dsLoaderPers; + +/* 3ds chunk types that we use */ +enum { + /* primary chunk */ + CHUNK_MAIN = 0x4D4D, + + /* main chunks */ + CHUNK_VERSION = 0x0002, + CHUNK_EDITOR_CONFIG = 0x3D3E, + CHUNK_EDITOR_DATA = 0x3D3D, + CHUNK_KEYFRAME_DATA = 0xB000, + + /* editor data sub chunks */ + CHUNK_MATERIAL = 0xAFFF, + CHUNK_OBJECT = 0x4000, + + /* material sub chunks */ + CHUNK_MATNAME = 0xA000, + CHUNK_MATDIFFUSE = 0xA020, + CHUNK_MATMAP = 0xA200, + CHUNK_MATMAPFILE = 0xA300, + + /* lets us know we're reading a new object */ + CHUNK_OBJECT_MESH = 0x4100, + + /* object mesh sub chunks */ + CHUNK_OBJECT_VERTICES = 0x4110, + CHUNK_OBJECT_FACES = 0x4120, + CHUNK_OBJECT_MATERIAL = 0x4130, + CHUNK_OBJECT_UV = 0x4140, +}; +#ifdef DEBUG_PM_3DS +static struct +{ + int id; + char *name; +} +debugChunkNames[] = +{ + { CHUNK_MAIN , "CHUNK_MAIN" }, + { CHUNK_VERSION , "CHUNK_VERSION" }, + { CHUNK_EDITOR_CONFIG , "CHUNK_EDITOR_CONFIG" }, + { CHUNK_EDITOR_DATA , "CHUNK_EDITOR_DATA" }, + { CHUNK_KEYFRAME_DATA , "CHUNK_KEYFRAME_DATA" }, + { CHUNK_MATERIAL , "CHUNK_MATERIAL" }, + { CHUNK_OBJECT , "CHUNK_OBJECT" }, + { CHUNK_MATNAME , "CHUNK_MATNAME" }, + { CHUNK_MATDIFFUSE , "CHUNK_MATDIFFUSE" }, + { CHUNK_MATMAP , "CHUNK_MATMAP" }, + { CHUNK_MATMAPFILE , "CHUNK_MATMAPFILE" }, + { CHUNK_OBJECT_MESH , "CHUNK_OBJECT_MESH" }, + { CHUNK_OBJECT_VERTICES , "CHUNK_OBJECT_VERTICES" }, + { CHUNK_OBJECT_FACES , "CHUNK_OBJECT_FACES" }, + { CHUNK_OBJECT_MATERIAL , "CHUNK_OBJECT_MATERIAL" }, + { CHUNK_OBJECT_UV , "CHUNK_OBJECT_UV" }, + { 0 , NULL } +}; +static char *DebugGetChunkName (int id) +{ + int i,max; /* imax? ;) */ + max = sizeof(debugChunkNames) / sizeof(debugChunkNames[0]); + + for (i=0; ilen)) + return PICO_PMV_ERROR_SIZE; + + /* check 3ds magic */ + if (_pico_little_short(chunk->id) != CHUNK_MAIN) + return PICO_PMV_ERROR_IDENT; + + /* file seems to be a valid 3ds */ + return PICO_PMV_OK; +} + +static T3dsChunk *GetChunk (T3dsLoaderPers *pers) +{ + T3dsChunk *chunk; + + /* sanity check */ + if (pers->cofs > pers->maxofs) return 0; + +#ifdef DEBUG_PM_3DS +/* printf("GetChunk: pers->cofs %x\n",pers->cofs); */ +#endif + /* fill in pointer to chunk */ + chunk = (T3dsChunk *)&pers->bufptr[ pers->cofs ]; + if (!chunk) return NULL; + + chunk->id = _pico_little_short(chunk->id ); + chunk->len = _pico_little_long (chunk->len); + + /* advance in buffer */ + pers->cofs += sizeof(T3dsChunk); + + /* this means yay */ + return chunk; +} + +static int GetASCIIZ (T3dsLoaderPers *pers, char *dest, int max) +{ + int pos = 0; + int ch; + + for (;;) + { + ch = pers->bufptr[ pers->cofs++ ]; + if (ch == '\0') break; + if (pers->cofs >= pers->maxofs) + { + dest[ pos ] = '\0'; + return 0; + } + dest[ pos++ ] = ch; + if (pos >= max) break; + } + dest[ pos ] = '\0'; + return 1; +} + +static picoByte_t GetByte (T3dsLoaderPers *pers) +{ + picoByte_t *value; + + /* sanity check */ + if (pers->cofs > pers->maxofs) return 0; + + /* get and return value */ + value = (picoByte_t *)(pers->bufptr + pers->cofs); + pers->cofs += 1; + return *value; +} + +static int GetWord (T3dsLoaderPers *pers) +{ + unsigned short *value; + + /* sanity check */ + if (pers->cofs > pers->maxofs) return 0; + + /* get and return value */ + value = (unsigned short *)(pers->bufptr + pers->cofs); + pers->cofs += 2; + return _pico_little_short(*value); +} + +static float GetFloat (T3dsLoaderPers *pers) +{ + float *value; + + /* sanity check */ + if (pers->cofs > pers->maxofs) return 0; + + /* get and return value */ + value = (float *)(pers->bufptr + pers->cofs); + pers->cofs += 4; + return _pico_little_float(*value); +} + +static int GetMeshVertices (T3dsLoaderPers *pers) +{ + int numVerts; + int i; + + /* get number of verts for this surface */ + numVerts = GetWord(pers); + +#ifdef DEBUG_PM_3DS + printf("GetMeshVertices: numverts %d\n",numVerts); +#endif + /* read in vertices for current surface */ + for (i=0; isurface,i,v ); + PicoSetSurfaceColor( pers->surface,0,i,white ); /* ydnar */ + +#ifdef DEBUG_PM_3DS_EX + printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]); +#endif + } + /* success (no errors occured) */ + return 1; +} + +static int GetMeshFaces (T3dsLoaderPers *pers) +{ + int numFaces; + int i; + + /* get number of faces for this surface */ + numFaces = GetWord(pers); + +#ifdef DEBUG_PM_3DS + printf("GetMeshFaces: numfaces %d\n",numFaces); +#endif + /* read in vertex indices for current surface */ + for (i=0; isurface, (i * 3 + 0), (picoIndex_t)face.a ); + PicoSetSurfaceIndex( pers->surface, (i * 3 + 1), (picoIndex_t)face.b ); + PicoSetSurfaceIndex( pers->surface, (i * 3 + 2), (picoIndex_t)face.c ); + +#ifdef DEBUG_PM_3DS_EX + printf("Face: a: %d b: %d c: %d (%d)\n",face.a,face.b,face.c,face.visible); +#endif + } + /* success (no errors occured) */ + return 1; +} + +static int GetMeshTexCoords (T3dsLoaderPers *pers) +{ + int numTexCoords; + int i; + + /* get number of uv coords for this surface */ + numTexCoords = GetWord(pers); + +#ifdef DEBUG_PM_3DS + printf("GetMeshTexCoords: numcoords %d\n",numTexCoords); +#endif + /* read in uv coords for current surface */ + for (i=0; isurface == NULL) + continue; + + /* add current uv */ + PicoSetSurfaceST( pers->surface,0,i,uv ); + +#ifdef DEBUG_PM_3DS_EX + printf("u: %f v: %f\n",uv[0],uv[1]); +#endif + } + /* success (no errors occured) */ + return 1; +} + +static int GetMeshShader (T3dsLoaderPers *pers) +{ + char shaderName[255] = { 0 }; + picoShader_t *shader; + int numSharedVerts; + int setShaderName = 0; + int i; + + /* the shader is either the color or the texture map of the */ + /* object. it can also hold other information like the brightness, */ + /* shine, etc. stuff we don't really care about. we just want the */ + /* color, or the texture map file name really */ + + /* get in the shader name */ + if (!GetASCIIZ(pers,shaderName,sizeof(shaderName))) + return 0; + + /* ydnar: trim to first whitespace */ + _pico_first_token( shaderName ); + + /* now that we have the shader name we need to go through all of */ + /* the shaders and check the name against each shader. when we */ + /* find a shader in our shader list that matches this name we */ + /* just read in, then we assign the shader's id of the object to */ + /* that shader */ + + /* get shader id for shader name */ + shader = PicoFindShader( pers->model, shaderName, 1 ); + + /* we've found a matching shader */ + if ((shader != NULL) && pers->surface) + { + char mapName[1024+1]; + char *mapNamePtr; + memset( mapName,0,sizeof(mapName) ); + + /* get ptr to shader's map name */ + mapNamePtr = PicoGetShaderMapName( shader ); + + /* we have a valid map name ptr */ + if (mapNamePtr != NULL) + { + char temp[128]; + const char *name; + + /* copy map name to local buffer */ + strcpy( mapName,mapNamePtr ); + + /* extract file name */ + name = _pico_nopath( mapName ); + strncpy( temp, name, sizeof(temp) ); + + /* remove file extension */ + /* name = _pico_setfext( name,"" ); */ + + /* assign default name if no name available */ + if (strlen(temp) < 1) + strcpy(temp,pers->basename); + + /* build shader name */ + _pico_strlwr( temp ); /* gaynux update -sea */ + sprintf( mapName,"models/mapobjects/%s/%s",pers->basename,temp ); + + /* set shader name */ + /* PicoSetShaderName( shader,mapName ); */ /* ydnar: this will screw up the named shader */ + + /* set surface's shader index */ + PicoSetSurfaceShader( pers->surface, shader ); + + setShaderName = 1; + } + } + /* we didn't set a shader name; throw out warning */ + if (!setShaderName) + { + _pico_printf( PICO_WARNING,"3DS mesh is missing shader name"); + } + /* we don't process the list of shared vertices here; there is a */ + /* short int that gives the number of faces of the mesh concerned */ + /* by this shader, then there is the list itself of these faces. */ + /* 0000 means the first face of the (4120) face list */ + + /* get number of shared verts */ + numSharedVerts = GetWord(pers); + +#ifdef DEBUG_PM_3DS + printf("GetMeshShader: uses shader '%s' (nsv %d)\n",shaderName,numSharedVerts); +#endif + /* skip list of shared verts */ + for (i=0; ishader ) + { + PicoSetShaderDiffuseColor( pers->shader,color ); + } +#ifdef DEBUG_PM_3DS + printf("GetDiffuseColor: %d %d %d\n",color[0],color[1],color[2]); +#endif + /* success (no errors occured) */ + return 1; +} + +static int DoNextEditorDataChunk (T3dsLoaderPers *pers, long endofs) +{ + T3dsChunk *chunk; + +#ifdef DEBUG_PM_3DS_EX + printf("DoNextEditorDataChunk: endofs %d\n",endofs); +#endif + while (pers->cofs < endofs) + { + long nextofs = pers->cofs; + if ((chunk = GetChunk(pers)) == NULL) return 0; + if (!chunk->len) return 0; + nextofs += chunk->len; + +#ifdef DEBUG_PM_3DS_EX + printf("Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName(chunk->id),chunk->len,pers->cofs); +#endif + /*** meshes ***/ + if (chunk->id == CHUNK_OBJECT) + { + picoSurface_t *surface; + char surfaceName[ 0xff ] = { 0 }; + + /* read in surface name */ + if( !GetASCIIZ(pers,surfaceName,sizeof(surfaceName)) ) + return 0; /* this is bad */ + +//PicoGetSurfaceName + /* ignore NULL name surfaces */ +// if( surfaceName + + /* allocate a pico surface */ + surface = PicoNewSurface( pers->model ); + if( surface == NULL ) + { + pers->surface = NULL; + return 0; /* this is bad too */ + } + /* assign ptr to current surface */ + pers->surface = surface; + + /* 3ds models surfaces are all triangle meshes */ + PicoSetSurfaceType( pers->surface,PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( pers->surface,surfaceName ); + + /* continue mess with object's sub chunks */ + DoNextEditorDataChunk(pers,nextofs); + continue; + } + if (chunk->id == CHUNK_OBJECT_MESH) + { + /* continue mess with mesh's sub chunks */ + if (!DoNextEditorDataChunk(pers,nextofs)) return 0; + continue; + } + if (chunk->id == CHUNK_OBJECT_VERTICES) + { + if (!GetMeshVertices(pers)) return 0; + continue; + } + if (chunk->id == CHUNK_OBJECT_FACES) + { + if (!GetMeshFaces(pers)) return 0; + continue; + } + if (chunk->id == CHUNK_OBJECT_UV) + { + if (!GetMeshTexCoords(pers)) return 0; + continue; + } + if (chunk->id == CHUNK_OBJECT_MATERIAL) + { + if (!GetMeshShader(pers)) return 0; + continue; + } + /*** materials ***/ + if (chunk->id == CHUNK_MATERIAL) + { + /* new shader specific things should be */ + /* initialized right here */ + picoShader_t *shader; + + /* allocate a pico shader */ + shader = PicoNewShader( pers->model ); /* ydnar */ + if( shader == NULL ) + { + pers->shader = NULL; + return 0; /* this is bad too */ + } + + /* assign ptr to current shader */ + pers->shader = shader; + + /* continue and process the material's sub chunks */ + DoNextEditorDataChunk(pers,nextofs); + continue; + } + if (chunk->id == CHUNK_MATNAME) + { + /* new material's names should be stored here. note that */ + /* GetMeshMaterial returns the name of the material that */ + /* is used by the mesh. new material names are set HERE. */ + /* but for now we skip the new material's name ... */ + if (pers->shader) + { + char *name = (char*) (pers->bufptr + pers->cofs); + char *cleanedName = _pico_clone_alloc( name ); + _pico_first_token( cleanedName ); + PicoSetShaderName( pers->shader, cleanedName ); +#ifdef DEBUG_PM_3DS + printf( "NewShader: '%s'\n", cleanedName ); +#endif + _pico_free( cleanedName ); + } + } + if (chunk->id == CHUNK_MATDIFFUSE) + { + /* todo: color for last inserted new material should be */ + /* stored somewhere by GetDiffuseColor */ + if (!GetDiffuseColor(pers)) return 0; + + /* rest of chunk is skipped here */ + } + if (chunk->id == CHUNK_MATMAP) + { + /* continue and process the material map sub chunks */ + DoNextEditorDataChunk(pers,nextofs); + continue; + } + if (chunk->id == CHUNK_MATMAPFILE) + { + /* map file name for last inserted new material should */ + /* be stored here. but for now we skip this too ... */ + if( pers->shader ) + { + char *name = (char *)(pers->bufptr + pers->cofs); + PicoSetShaderMapName( pers->shader,name ); +#ifdef DEBUG_PM_3DS + printf("NewShaderMapfile: '%s'\n",name); +#endif + } + } + /*** keyframes ***/ + if (chunk->id == CHUNK_KEYFRAME_DATA) + { + /* well umm, this is a bit too much since we don't really */ + /* need model animation sequences right now. we skip this */ +#ifdef DEBUG_PM_3DS + printf("KeyframeData: len %d\n",chunk->len); +#endif + } + /* skip unknown chunk */ + pers->cofs = nextofs; + if (pers->cofs >= pers->maxofs) break; + } + return 1; +} + +static int DoNextChunk (T3dsLoaderPers *pers, int endofs) +{ + T3dsChunk *chunk; + +#ifdef DEBUG_PM_3DS + printf("DoNextChunk: endofs %d\n",endofs); +#endif + while (pers->cofs < endofs) + { + long nextofs = pers->cofs; + if ((chunk = GetChunk(pers)) == NULL) return 0; + if (!chunk->len) return 0; + nextofs += chunk->len; + +#ifdef DEBUG_PM_3DS_EX + printf("Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName(chunk->id),chunk->len,pers->cofs); +#endif + /*** version ***/ + if (chunk->id == CHUNK_VERSION) + { + /* at this point i get the 3ds file version. since there */ + /* might be new additions to the 3ds file format in 4.0 */ + /* it might be a good idea to store the version somewhere */ + /* for later handling or message displaying */ + + /* get the version */ + int version; + version = GetWord(pers); + GetWord(pers); +#ifdef DEBUG_PM_3DS + printf("FileVersion: %d\n",version); +#endif + + /* throw out a warning for version 4 models */ + if (version == 4) + { + _pico_printf( PICO_WARNING, + "3DS version is 4. Model might load incorrectly."); + } + /* store the 3ds file version in pico special field 0 */ + /* PicoSetSurfaceSpecial(pers->surface,0,version); */ /* ydnar: this was causing a crash accessing uninitialized surface */ + + /* rest of chunk is skipped here */ + } + /*** editor data ***/ + if (chunk->id == CHUNK_EDITOR_DATA) + { + if (!DoNextEditorDataChunk(pers,nextofs)) return 0; + continue; + } + /* skip unknown chunk */ + pers->cofs = nextofs; + if (pers->cofs >= pers->maxofs) break; + } + return 1; +} + +/* _3ds_load: + * loads an autodesk 3ds model file. +*/ +static picoModel_t *_3ds_load( PM_PARAMS_LOAD ) +{ + T3dsLoaderPers pers; + picoModel_t *model; + char basename[128]; + + /* create a new pico model */ + model = PicoNewModel(); + if (model == NULL) + { + /* user must have some serious ram problems ;) */ + return NULL; + } + /* get model's base name (eg. jeep from c:\models\jeep.3ds) */ + memset( basename,0,sizeof(basename) ); + strncpy( basename,_pico_nopath(fileName),sizeof(basename) ); + _pico_setfext( basename,"" ); + + /* initialize persistant vars (formerly static) */ + pers.model = model; + pers.bufptr = (picoByte_t *)buffer; + pers.basename = (char *)basename; + pers.maxofs = bufSize; + pers.cofs = 0L; + + /* do model setup */ + PicoSetModelFrameNum( model,frameNum ); + PicoSetModelName( model,fileName ); + PicoSetModelFileName( model,fileName ); + + /* skip first chunk in file (magic) */ + GetChunk(&pers); + + /* process chunks */ + if (!DoNextChunk(&pers,pers.maxofs)) + { + /* well, bleh i guess */ + PicoFreeModel(model); + return NULL; + } + /* return allocated pico model */ + return model; +} + +/* pico file format module definition */ +const picoModule_t picoModule3DS = +{ + "0.86-b", /* module version string */ + "Autodesk 3Dstudio", /* module display name */ + "seaw0lf", /* author's name */ + "2002 seaw0lf", /* module copyright */ + { + "3ds",NULL,NULL,NULL /* default extensions to use */ + }, + _3ds_canload, /* validation routine */ + _3ds_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/tools/urt/libs/picomodel/pm_ase.c b/tools/urt/libs/picomodel/pm_ase.c new file mode 100644 index 00000000..76699bfd --- /dev/null +++ b/tools/urt/libs/picomodel/pm_ase.c @@ -0,0 +1,1427 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other aseMaterialList provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + +void Sys_Printf (const char *format, ...); + +/* marker */ +#define PM_ASE_C + +/* uncomment when debugging this module */ +//#define DEBUG_PM_ASE +//#define DEBUG_PM_ASE_EX + + +/* dependencies */ +#include "picointernal.h" + +#ifdef DEBUG_PM_ASE +#include "time.h" +#endif + +/* plain white */ +static picoColor_t white = { 255, 255, 255, 255 }; + +/* jhefty - multi-subobject material support */ + +/* Material/SubMaterial management */ +/* A material should have 1..n submaterials assigned to it */ + +typedef struct aseSubMaterial_s +{ + struct aseSubMaterial_s* next; + int subMtlId; + picoShader_t* shader; + +} aseSubMaterial_t; + +typedef struct aseMaterial_s +{ + struct aseMaterial_s* next; + struct aseSubMaterial_s* subMtls; + int mtlId; +} aseMaterial_t; + +/* Material/SubMaterial management functions */ +static aseMaterial_t* _ase_get_material ( aseMaterial_t* list , int mtlIdParent ) +{ + aseMaterial_t* mtl = list; + + while ( mtl ) + { + if ( mtlIdParent == mtl->mtlId ) + { + break; + } + mtl = mtl->next; + } + return mtl; +} + +static aseSubMaterial_t* _ase_get_submaterial ( aseMaterial_t* list, int mtlIdParent , int subMtlId ) +{ + aseMaterial_t* parent = _ase_get_material ( list , mtlIdParent ); + aseSubMaterial_t* subMtl = NULL; + + if ( !parent ) + { + _pico_printf ( PICO_ERROR , "No ASE material exists with id %i\n" , mtlIdParent ); + return NULL; + } + + subMtl = parent->subMtls; + while ( subMtl ) + { + if ( subMtlId == subMtl->subMtlId ) + { + break; + } + subMtl = subMtl->next; + } + return subMtl; +} + +aseSubMaterial_t* _ase_get_submaterial_or_default ( aseMaterial_t* materials, int mtlIdParent , int subMtlId ) +{ + aseSubMaterial_t* subMtl = _ase_get_submaterial( materials, mtlIdParent, subMtlId ); + if(subMtl != NULL) + { + return subMtl; + } + + /* ydnar: trying default submaterial */ + subMtl = _ase_get_submaterial( materials, mtlIdParent, 0 ); + if( subMtl != NULL ) + { + return subMtl; + } + + _pico_printf( PICO_ERROR, "Could not find material/submaterial for id %d/%d\n", mtlIdParent, subMtlId ); + return NULL; +} + + + + +static aseMaterial_t* _ase_add_material( aseMaterial_t **list, int mtlIdParent ) +{ + aseMaterial_t *mtl = _pico_calloc( 1, sizeof( aseMaterial_t ) ); + mtl->mtlId = mtlIdParent; + mtl->subMtls = NULL; + mtl->next = *list; + *list = mtl; + + return mtl; +} + +static aseSubMaterial_t* _ase_add_submaterial( aseMaterial_t **list, int mtlIdParent, int subMtlId, picoShader_t* shader ) +{ + aseMaterial_t *parent = _ase_get_material( *list, mtlIdParent ); + aseSubMaterial_t *subMtl = _pico_calloc( 1, sizeof ( aseSubMaterial_t ) ); + + if ( !parent ) + { + parent = _ase_add_material ( list , mtlIdParent ); + } + + subMtl->shader = shader; + subMtl->subMtlId = subMtlId; + subMtl->next = parent->subMtls; + parent->subMtls = subMtl; + + return subMtl; +} + +static void _ase_free_materials( aseMaterial_t **list ) +{ + aseMaterial_t* mtl = *list; + aseSubMaterial_t* subMtl = NULL; + + aseMaterial_t* mtlTemp = NULL; + aseSubMaterial_t* subMtlTemp = NULL; + + while ( mtl ) + { + subMtl = mtl->subMtls; + while ( subMtl ) + { + subMtlTemp = subMtl->next; + _pico_free ( subMtl ); + subMtl = subMtlTemp; + } + mtlTemp = mtl->next; + _pico_free ( mtl ); + mtl = mtlTemp; + } + (*list) = NULL; +} + +#ifdef DEBUG_PM_ASE +static void _ase_print_materials( aseMaterial_t *list ) +{ + aseMaterial_t* mtl = list; + aseSubMaterial_t* subMtl = NULL; + + while ( mtl ) + { + _pico_printf ( PICO_NORMAL , "ASE Material %i" , mtl->mtlId ); + subMtl = mtl->subMtls; + while ( subMtl ) + { + _pico_printf ( PICO_NORMAL , " -- ASE SubMaterial %i - %s\n" , subMtl->subMtlId , subMtl->shader->name ); + subMtl = subMtl->next; + } + mtl = mtl->next; + } +} +#endif //DEBUG_PM_ASE + +/* todo: + * - apply material specific uv offsets to uv coordinates + */ + +/* _ase_canload: + * validates a 3dsmax ase model file. + */ +static int _ase_canload( PM_PARAMS_CANLOAD ) +{ + picoParser_t *p; + + + /* quick data length validation */ + if( bufSize < 80 ) + return PICO_PMV_ERROR_SIZE; + + /* keep the friggin compiler happy */ + *fileName = *fileName; + + /* create pico parser */ + p = _pico_new_parser( (picoByte_t*) buffer, bufSize ); + if( p == NULL ) + return PICO_PMV_ERROR_MEMORY; + + /* get first token */ + if( _pico_parse_first( p ) == NULL) + { + return PICO_PMV_ERROR_IDENT; + } + + /* check first token */ + if( _pico_stricmp( p->token, "*3dsmax_asciiexport" ) ) + { + _pico_free_parser( p ); + return PICO_PMV_ERROR_IDENT; + } + + /* free the pico parser object */ + _pico_free_parser( p ); + + /* file seems to be a valid ase file */ + return PICO_PMV_OK; +} + +typedef struct aseVertex_s aseVertex_t; +struct aseVertex_s +{ + picoVec3_t xyz; + picoIndex_t id; +}; + +typedef struct aseTexCoord_s aseTexCoord_t; +struct aseTexCoord_s +{ + picoVec2_t texcoord; +}; + +typedef struct aseColor_s aseColor_t; +struct aseColor_s +{ + picoColor_t color; +}; + +typedef struct aseFace_s aseFace_t; +struct aseFace_s +{ + picoIndex_t indices[9]; + picoIndex_t smoothingGroup; + picoIndex_t materialId; + picoIndex_t subMaterialId; + picoVec3_t facenormal; + picoVec3_t vertexnormal[3]; +}; +typedef aseFace_t* aseFacesIter_t; + +picoSurface_t* PicoModelFindOrAddSurface( picoModel_t *model, picoShader_t* shader ) +{ + /* see if a surface already has the shader */ + int i = 0; + for ( ; i < model->numSurfaces ; i++ ) + { + picoSurface_t* workSurface = model->surface[i]; + if ( workSurface->shader == shader ) + { + return workSurface; + } + } + + /* no surface uses this shader yet, so create a new surface */ + + { + /* create a new surface in the model for the unique shader */ + picoSurface_t* workSurface = PicoNewSurface(model); + if ( !workSurface ) + { + _pico_printf ( PICO_ERROR , "Could not allocate a new surface!\n" ); + return 0; + } + + /* do surface setup */ + PicoSetSurfaceType( workSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( workSurface, shader->name ); + PicoSetSurfaceShader( workSurface, shader ); + + return workSurface; + } +} + +/* _ase_submit_triangles - jhefty + use the surface and the current face list to look up material/submaterial IDs + and submit them to the model for proper processing + +The following still holds from ydnar's _ase_make_surface: + indexes 0 1 2 = vert indexes + indexes 3 4 5 = st indexes + indexes 6 7 8 = color indexes (new) +*/ + +#if 0 +typedef picoIndex_t* picoIndexIter_t; + +typedef struct aseUniqueIndices_s aseUniqueIndices_t; +struct aseUniqueIndices_s +{ + picoIndex_t* data; + picoIndex_t* last; + + aseFace_t* faces; +}; + +size_t aseUniqueIndices_size(aseUniqueIndices_t* self) +{ + return self->last - self->data; +} + +void aseUniqueIndices_reserve(aseUniqueIndices_t* self, picoIndex_t size) +{ + self->data = self->last = (picoIndex_t*)_pico_calloc(size, sizeof(picoIndex_t)); +} + +void aseUniqueIndices_clear(aseUniqueIndices_t* self) +{ + _pico_free(self->data); +} + +void aseUniqueIndices_pushBack(aseUniqueIndices_t* self, picoIndex_t index) +{ + *self->last++ = index; +} + +picoIndex_t aseFaces_getVertexIndex(aseFace_t* faces, picoIndex_t index) +{ + return faces[index / 3].indices[index % 3]; +} + +picoIndex_t aseFaces_getTexCoordIndex(aseFace_t* faces, picoIndex_t index) +{ + return faces[index / 3].indices[(index % 3) + 3]; +} + +picoIndex_t aseFaces_getColorIndex(aseFace_t* faces, picoIndex_t index) +{ + return faces[index / 3].indices[(index % 3) + 6]; +} + +int aseUniqueIndex_equal(aseFace_t* faces, picoIndex_t index, picoIndex_t other) +{ + return aseFaces_getVertexIndex(faces, index) == aseFaces_getVertexIndex(faces, other) + && aseFaces_getTexCoordIndex(faces, index) == aseFaces_getTexCoordIndex(faces, other) + && aseFaces_getColorIndex(faces, index) == aseFaces_getColorIndex(faces, other); +} + +picoIndex_t aseUniqueIndices_insertUniqueVertex(aseUniqueIndices_t* self, picoIndex_t index) +{ + picoIndexIter_t i = self->data; + for(; i != self->last; ++i) + { + picoIndex_t other = (picoIndex_t)(i - self->data); + if(aseUniqueIndex_equal(self->faces, index, other)) + { + return other; + } + } + + aseUniqueIndices_pushBack(self, index); + return (picoIndex_t)(aseUniqueIndices_size(self) - 1); +} + +static void _ase_submit_triangles_unshared ( picoModel_t* model , aseMaterial_t* materials , aseVertex_t* vertices, aseTexCoord_t* texcoords, aseColor_t* colors, aseFace_t* faces, int numFaces, int meshHasNormals ) +{ + aseFacesIter_t i = faces, end = faces + numFaces; + + aseUniqueIndices_t indices; + aseUniqueIndices_t remap; + aseUniqueIndices_reserve(&indices, numFaces * 3); + aseUniqueIndices_reserve(&remap, numFaces * 3); + indices.faces = faces; + + for(; i != end; ++i) + { + /* look up the shader for the material/submaterial pair */ + aseSubMaterial_t* subMtl = _ase_get_submaterial_or_default( materials, (*i).materialId, (*i).subMaterialId ); + if( subMtl == NULL ) + { + return; + } + + { + picoSurface_t* surface = PicoModelFindOrAddSurface(model, subMtl->shader); + int j; + /* we pull the data from the vertex, color and texcoord arrays using the face index data */ + for ( j = 0 ; j < 3 ; j ++ ) + { + picoIndex_t index = (picoIndex_t)(((i - faces) * 3) + j); + picoIndex_t size = (picoIndex_t)aseUniqueIndices_size(&indices); + picoIndex_t unique = aseUniqueIndices_insertUniqueVertex(&indices, index); + + picoIndex_t numVertexes = PicoGetSurfaceNumVertexes(surface); + picoIndex_t numIndexes = PicoGetSurfaceNumIndexes(surface); + + aseUniqueIndices_pushBack(&remap, numIndexes); + + PicoSetSurfaceIndex(surface, numIndexes, remap.data[unique]); + + if(unique == size) + { + PicoSetSurfaceXYZ(surface, numVertexes, vertices[(*i).indices[j]].xyz); + PicoSetSurfaceNormal(surface, numVertexes, vertices[(*i).indices[j]].normal); + PicoSetSurfaceST(surface, 0, numVertexes, texcoords[(*i).indices[j + 3]].texcoord); + + if ( (*i).indices[j + 6] >= 0 ) + { + PicoSetSurfaceColor(surface, 0, numVertexes, colors[(*i).indices[j + 6]].color); + } + else + { + PicoSetSurfaceColor(surface, 0, numVertexes, white); + } + + PicoSetSurfaceSmoothingGroup(surface, numVertexes, (vertices[(*i).indices[j]].id * (1 << 16)) + (*i).smoothingGroup); + } + } + } + } + + aseUniqueIndices_clear(&indices); + aseUniqueIndices_clear(&remap); +} + +#endif + +static int VectorCompareExtn( picoVec3_t n1, picoVec3_t n2, float epsilon ) +{ + int i; + + + /* test */ + for( i= 0; i < 3; i++ ) + if( fabs( n1[ i ] - n2[ i ]) > epsilon ) + return -1; + return 1; +} + +#define CrossProductTemp(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) + + +#define MAX_FACEREFS 64 +//maximum number of faces that can share a vert. likely 1-4, but who knows. +typedef struct +{ + int count; + aseFace_t* faces[MAX_FACEREFS]; +} faceref_t; + +static void _ase_submit_triangles( picoModel_t* model , aseMaterial_t* materials , aseVertex_t* vertices, aseTexCoord_t* texcoords, aseColor_t* colors, aseFace_t* faces, int numFaces,int numVerts, int submodel ) +{ + + picoVec3_t accum; + int index; + int counter; + faceref_t* faceref; + int *normalsC; + int fc=0; + + aseFacesIter_t i = faces, end = faces + numFaces; + counter=0; + + + //allocate room for sg optimization + faceref=(faceref_t*)malloc(numVerts*sizeof(faceref_t)); + memset(faceref,0,numVerts*sizeof(faceref_t)); + + //rebuild face normals + for(i=faces; i != end; ++i) + { + + picoVec3_t a,b,c; + picoVec3_t v1,v2,v3; + int j; + counter++; + + for (j=0;j<3;j++) + { + a[j] = vertices[(*i).indices[0]].xyz[j]; + b[j] = vertices[(*i).indices[1]].xyz[j]; + c[j] = vertices[(*i).indices[2]].xyz[j]; + } + for (j=0;j<3;j++) + { + v1[j]=a[j]-b[j]; + v2[j]=c[j]-b[j]; + } + + CrossProductTemp(v1,v2,v3); + _pico_normalize_vec(v3); + (*i).facenormal[0]=v3[0]; + (*i).facenormal[1]=v3[1]; + (*i).facenormal[2]=v3[2]; + + + //throw this face into the index pools + for ( j = 0 ; j < 3 ; j ++ ) + { + index=(*i).indices[j]; + if (faceref[index].count>=MAX_FACEREFS-1) + { + + } + else + { + faceref[index].faces[faceref[index].count++]=i; + } + } + + } + + //if (counter>0) Sys_Printf( "Rebuilding %d Normals\n", counter * 3 ); + for(i=faces; i != end; ++i) + { + /* look up the shader for the material/submaterial pair */ + aseSubMaterial_t* subMtl = _ase_get_submaterial_or_default( materials, (*i).materialId, (*i).subMaterialId ); + + if( subMtl == NULL ) + { + continue; + } + + { + picoVec3_t* xyz[3]; + picoVec3_t *a[3]; + picoVec3_t* normal[3]; + picoVec2_t* st[3]; + picoColor_t* color[3]; + picoIndex_t smooth[3]; + + int j,z; + + + + /* we pull the data from the vertex, color and texcoord arrays using the face index data */ + for ( j = 0 ; j < 3 ; j ++ ) + { + aseFacesIter_t q = faces; + aseFacesIter_t qend = faces + numFaces; + + xyz[j] = &vertices[(*i).indices[j]].xyz; + + // Use Face normal + normal[j] = &(*i).facenormal; + + + //Oooor we can use the smoothing group + + //Slow method, but testing + //Find All faces that use this vertex, average their facenormals. + // skip where smoothgroups both equal 0, or don't have any shared bits (x & y) + index=(*i).indices[j]; + + accum[0]=(*i).facenormal[0]; + accum[1]=(*i).facenormal[1]; + accum[2]=(*i).facenormal[2]; + counter=1; + + + z=0; + + for (fc=0;fc0 || + VectorCompareExtn(*a[1],*xyz[j],0.01f)>0 || + VectorCompareExtn(*a[2],*xyz[j],0.01f)>0 + ) + { + if ( (*i).smoothingGroup==0 && (*q).smoothingGroup ==0 ) + continue; + + if ( (*i).smoothingGroup & (*q).smoothingGroup ) + { + accum[0]+=(*q).facenormal[0]; + accum[1]+=(*q).facenormal[1]; + accum[2]+=(*q).facenormal[2]; + + counter++; + + } + } + } + _pico_normalize_vec(accum); + + (*i).vertexnormal[j][0]=accum[0]; + (*i).vertexnormal[j][1]=accum[1]; + (*i).vertexnormal[j][2]=accum[2]; + normal[j]=&(*i).vertexnormal[j]; + + st[j] = &texcoords[(*i).indices[j + 3]].texcoord; + + if( colors != NULL && (*i).indices[j + 6] >= 0 ) + { + color[j] = &colors[(*i).indices[j + 6]].color; + } + else + { + color[j] = &white; + } + + smooth[j] = 0;// (vertices[(*i).indices[j]].id * (1 << 16)) + (*i).smoothingGroup; /* don't merge vertices */ + + } + + /* submit the triangle to the model */ + PicoAddTriangleToModel ( model , xyz , normal , 1 , st , 1 , color , subMtl->shader, smooth, submodel ); + } + + } + free(faceref); +} + + +/* _ase_load: + * loads a 3dsmax ase model file. +*/ +static picoModel_t *_ase_load( PM_PARAMS_LOAD ) +{ + picoModel_t *model; + picoParser_t *p; + char lastNodeName[ 1024 ]; + + aseVertex_t* vertices = NULL; + aseTexCoord_t* texcoords = NULL; + aseColor_t* colors = NULL; + aseFace_t* faces = NULL; + int numVertices = 0; + int numFaces = 0; + int numTextureVertices = 0; + int numTextureVertexFaces = 0; + int numColorVertices = 0; + int numColorVertexFaces = 0; + int vertexId = 0; + int currentVertexFace=0; + int currentVertexIndex=0; + int counter=0; + int submodel=0; + + aseMaterial_t* materials = NULL; + +#ifdef DEBUG_PM_ASE + clock_t start, finish; + double elapsed; + start = clock(); +#endif + + /* helper */ + #define _ase_error_return(m) \ + { \ + _pico_printf( PICO_ERROR,"%s in ASE, line %d.",m,p->curLine); \ + _pico_free_parser( p ); \ + PicoFreeModel( model ); \ + return NULL; \ + } + /* create a new pico parser */ + p = _pico_new_parser( (picoByte_t *)buffer,bufSize ); + if (p == NULL) return NULL; + + /* create a new pico model */ + model = PicoNewModel(); + if (model == NULL) + { + _pico_free_parser( p ); + return NULL; + } + /* do model setup */ + PicoSetModelFrameNum( model, frameNum ); + PicoSetModelName( model, fileName ); + PicoSetModelFileName( model, fileName ); + + /* initialize some stuff */ + memset( lastNodeName,0,sizeof(lastNodeName) ); + + /* parse ase model file */ + while( 1 ) + { + /* get first token on line */ + if (_pico_parse_first( p ) == NULL) + break; + + /* we just skip empty lines */ + if (p->token == NULL || !strlen( p->token )) + continue; + + /* we skip invalid ase statements */ + if (p->token[0] != '*' && p->token[0] != '{' && p->token[0] != '}') + { + _pico_parse_skip_rest( p ); + continue; + } + /* remember node name */ + if (!_pico_stricmp(p->token,"*node_name")) + { + /* read node name */ + char *ptr = _pico_parse( p,0 ); + if (ptr == NULL) + _ase_error_return("Node name parse error"); + + /* remember node name */ + strncpy( lastNodeName,ptr,sizeof(lastNodeName) ); + } + /* model mesh (originally contained within geomobject) */ + else if (!_pico_stricmp(p->token,"*mesh")) + { + /* finish existing surface */ + _ase_submit_triangles(model, materials, vertices, texcoords, colors, faces, numFaces,numVertices,submodel++); + _pico_free(faces); + _pico_free(vertices); + _pico_free(texcoords); + _pico_free(colors); + } + else if (!_pico_stricmp(p->token,"*mesh_numvertex")) + { + if (!_pico_parse_int( p, &numVertices) ) + _ase_error_return("Missing MESH_NUMVERTEX value"); + + vertices = _pico_calloc(numVertices, sizeof(aseVertex_t)); + currentVertexIndex=0; + } + else if (!_pico_stricmp(p->token,"*mesh_numfaces")) + { + if (!_pico_parse_int( p, &numFaces) ) + _ase_error_return("Missing MESH_NUMFACES value"); + + faces = _pico_calloc(numFaces, sizeof(aseFace_t)); + + } + else if (!_pico_stricmp(p->token,"*mesh_numtvertex")) + { + if (!_pico_parse_int( p, &numTextureVertices) ) + _ase_error_return("Missing MESH_NUMTVERTEX value"); + + texcoords = _pico_calloc(numTextureVertices, sizeof(aseTexCoord_t)); + } + else if (!_pico_stricmp(p->token,"*mesh_numtvfaces")) + { + if (!_pico_parse_int( p, &numTextureVertexFaces) ) + _ase_error_return("Missing MESH_NUMTVFACES value"); + } + else if (!_pico_stricmp(p->token,"*mesh_numcvertex")) + { + if (!_pico_parse_int( p, &numColorVertices) ) + _ase_error_return("Missing MESH_NUMCVERTEX value"); + + colors = _pico_calloc(numColorVertices, sizeof(aseColor_t)); + memset( colors, 255, numColorVertices * sizeof( aseColor_t ) ); /* ydnar: force colors to white initially */ + } + else if (!_pico_stricmp(p->token,"*mesh_numcvfaces")) + { + if (!_pico_parse_int( p, &numColorVertexFaces) ) + _ase_error_return("Missing MESH_NUMCVFACES value"); + } + /* mesh material reference. this usually comes at the end of */ + /* geomobjects after the mesh blocks. we must assume that the */ + /* new mesh was already created so all we can do here is assign */ + /* the material reference id (shader index) now. */ + else if (!_pico_stricmp(p->token,"*material_ref")) + { + int mtlId; + + /* get the material ref (0..n) */ + if (!_pico_parse_int( p,&mtlId) ) + _ase_error_return("Missing material reference ID"); + + { + int i = 0; + /* fix up all of the aseFaceList in the surface to point to the parent material */ + /* we've already saved off their subMtl */ + for(; i < numFaces; ++i) + { + faces[i].materialId = mtlId; + } + } + } + /* model mesh vertex */ + else if (!_pico_stricmp(p->token,"*mesh_vertex")) + { + int index; + + if( numVertices == 0 ) + _ase_error_return("Vertex parse error"); + + /* get vertex data (orig: index +y -x +z) */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Vertex parse error"); + if (!_pico_parse_vec( p,vertices[index].xyz )) + _ase_error_return("Vertex parse error"); + + vertices[index].id = vertexId++; + } + else if (!_pico_stricmp(p->token,"*mesh_facenormal")) + { + //Grab the faceindex for the next vertex normals. + if( numVertices == 0 ) + _ase_error_return("Vertex parse error (facenormals)"); + + if (!_pico_parse_int( p,¤tVertexFace )) + _ase_error_return("Vertex parse error"); + + if (!_pico_parse_vec( p,faces[currentVertexFace].facenormal )) + _ase_error_return("Vertex parse error"); + + } + /* model mesh vertex normal */ + else if (!_pico_stricmp(p->token,"*mesh_vertexnormal")) + { + int index; + + if( numVertices == 0 ) + _ase_error_return("Vertex parse error"); + + /* get vertex data (orig: index +y -x +z) */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Vertex parse error"); + + //^^ Index is 'wrong' in .ase models. they reference the same vert index with multiple normals.. + // I've tried, this is a lost cause. Use the SG's + // + /* + + if (!_pico_parse_vec( p,vertices[counter].normal )) + _ase_error_return("Vertex parse error"); + vertices[counter].faceid=index; + counter++; + */ + } + /* model mesh face */ + else if (!_pico_stricmp(p->token,"*mesh_normals")) + { + // counter=0; //part of the above vertex normals fix + } + + /* model mesh face */ + else if (!_pico_stricmp(p->token,"*mesh_face")) + { + picoIndex_t indexes[3]; + int index; + + if( numFaces == 0 ) + _ase_error_return("Face parse error"); + + /* get face index */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Face parse error"); + + /* get 1st vertex index */ + _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[0] )) + _ase_error_return("Face parse error"); + + /* get 2nd vertex index */ + _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[1] )) + _ase_error_return("Face parse error"); + + /* get 3rd vertex index */ + _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[2] )) + _ase_error_return("Face parse error"); + + /* parse to the subMaterial ID */ + while ( 1 ) + { + if (!_pico_parse (p,0)) /* EOL */ + { + break; + } + if (!_pico_stricmp (p->token,"*MESH_SMOOTHING" )) + { + int total=0; + char* point; + char* start; + _pico_parse(p,0); + + point=p->token; + start=point; + faces[index].smoothingGroup=0; + + //Super dodgy comma delimited string parse + while (*point<'A') + { + if (*point<=32 || *point==',') + { + total=atoi(start); + if (total!=0) + { + faces[index].smoothingGroup+=1<token,"*MESH_MTLID" )) + { + _pico_parse_int ( p , &faces[index].subMaterialId ); + } + } + + faces[index].materialId = 0; + faces[index].indices[0] = indexes[2]; + faces[index].indices[1] = indexes[1]; + faces[index].indices[2] = indexes[0]; + } + /* model texture vertex */ + else if (!_pico_stricmp(p->token,"*mesh_tvert")) + { + int index; + + if( numVertices == 0 ) + _ase_error_return("Vertex parse error"); + + /* get uv vertex index */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("UV vertex parse error"); + + /* get uv vertex s */ + if (!_pico_parse_float( p,&texcoords[index].texcoord[0] )) + _ase_error_return("UV vertex parse error"); + + /* get uv vertex t */ + if (!_pico_parse_float( p,&texcoords[index].texcoord[1] )) + _ase_error_return("UV vertex parse error"); + + /* ydnar: invert t */ + texcoords[index].texcoord[ 1 ] = 1.0f - texcoords[index].texcoord[ 1 ]; + } + /* ydnar: model mesh texture face */ + else if( !_pico_stricmp( p->token, "*mesh_tface" ) ) + { + picoIndex_t indexes[3]; + int index; + + if( numFaces == 0 ) + _ase_error_return("Texture face parse error"); + + /* get face index */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Texture face parse error"); + + /* get 1st vertex index */ + if (!_pico_parse_int( p,&indexes[0] )) + _ase_error_return("Texture face parse error"); + + /* get 2nd vertex index */ + if (!_pico_parse_int( p,&indexes[1] )) + _ase_error_return("Texture face parse error"); + + /* get 3rd vertex index */ + if (!_pico_parse_int( p,&indexes[2] )) + _ase_error_return("Texture face parse error"); + + faces[index].indices[3] = indexes[2]; + faces[index].indices[4] = indexes[1]; + faces[index].indices[5] = indexes[0]; + } + /* model color vertex */ + else if (!_pico_stricmp(p->token,"*mesh_vertcol")) + { + int index; + float colorInput; + + if( numVertices == 0 ) + _ase_error_return("Color Vertex parse error"); + + /* get color vertex index */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Color vertex parse error"); + + /* get R component */ + if (!_pico_parse_float( p,&colorInput )) + _ase_error_return("Color vertex parse error"); + colors[index].color[0] = (picoByte_t)(colorInput * 255); + + /* get G component */ + if (!_pico_parse_float( p,&colorInput )) + _ase_error_return("Color vertex parse error"); + colors[index].color[1] = (picoByte_t)(colorInput * 255); + + /* get B component */ + if (!_pico_parse_float( p,&colorInput )) + _ase_error_return("Color vertex parse error"); + colors[index].color[2] = (picoByte_t)(colorInput * 255); + + /* leave alpha alone since we don't get any data from the ASE format */ + colors[index].color[3] = 255; + + /* 27 hack, red as alpha */ + colors[index].color[3]=colors[index].color[0]; + colors[index].color[0]=255; + colors[index].color[1]=255; + colors[index].color[2]=255; + + } + /* model color face */ + else if (!_pico_stricmp(p->token,"*mesh_cface")) + { + picoIndex_t indexes[3]; + int index; + + if( numFaces == 0 ) + _ase_error_return("Face parse error"); + + /* get face index */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Face parse error"); + + /* get 1st cvertex index */ + // _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[0] )) + _ase_error_return("Face parse error"); + + /* get 2nd cvertex index */ + // _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[1] )) + _ase_error_return("Face parse error"); + + /* get 3rd cvertex index */ + // _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[2] )) + _ase_error_return("Face parse error"); + + faces[index].indices[6] = indexes[2]; + faces[index].indices[7] = indexes[1]; + faces[index].indices[8] = indexes[0]; + } + /* model material */ + else if( !_pico_stricmp( p->token, "*material" ) ) + { + aseSubMaterial_t* subMaterial = NULL; + picoShader_t *shader; + int level = 1, index; + char materialName[ 1024 ]; + float transValue = 0.0f, shineValue = 1.0f; + picoColor_t ambientColor, diffuseColor, specularColor; + char *mapname = NULL; + int subMtlId, subMaterialLevel = -1; + + + /* get material index */ + _pico_parse_int( p,&index ); + + /* check brace */ + if (!_pico_parse_check(p,1,"{")) + _ase_error_return("Material missing opening brace"); + + /* parse material block */ + while( 1 ) + { + /* get next token */ + if (_pico_parse(p,1) == NULL) break; + if (!strlen(p->token)) continue; + + /* handle levels */ + if (p->token[0] == '{') level++; + if (p->token[0] == '}') level--; + if (!level) break; + + if( level == subMaterialLevel ) + { + /* set material name */ + _pico_first_token( materialName ); + PicoSetShaderName( shader, materialName); + + /* set shader's transparency */ + PicoSetShaderTransparency( shader,transValue ); + + /* set shader's ambient color */ + PicoSetShaderAmbientColor( shader,ambientColor ); + + /* set diffuse alpha to transparency */ + diffuseColor[3] = (picoByte_t)( transValue * 255.0 ); + + /* set shader's diffuse color */ + PicoSetShaderDiffuseColor( shader,diffuseColor ); + + /* set shader's specular color */ + PicoSetShaderSpecularColor( shader,specularColor ); + + /* set shader's shininess */ + PicoSetShaderShininess( shader,shineValue ); + + /* set material map name */ + PicoSetShaderMapName( shader, mapname ); + + subMaterial = _ase_add_submaterial( &materials, index, subMtlId, shader ); + subMaterialLevel = -1; + } + + /* parse submaterial index */ + if (!_pico_stricmp(p->token,"*submaterial")) + { + /* allocate new pico shader */ + _pico_parse_int( p , &subMtlId ); + + shader = PicoNewShader( model ); + if (shader == NULL) + { + PicoFreeModel( model ); + return NULL; + } + subMaterialLevel = level; + } + /* parse material name */ + else if (!_pico_stricmp(p->token,"*material_name")) + { + char* name = _pico_parse(p,0); + if ( name == NULL) + _ase_error_return("Missing material name"); + + strcpy ( materialName , name ); + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse material transparency */ + else if (!_pico_stricmp(p->token,"*material_transparency")) + { + /* get transparency value from ase */ + if (!_pico_parse_float( p,&transValue )) + _ase_error_return("Material transparency parse error"); + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse material shininess */ + else if (!_pico_stricmp(p->token,"*material_shine")) + { + /* remark: + * - not sure but instead of '*material_shine' i might + * need to use '*material_shinestrength' */ + + /* get shine value from ase */ + if (!_pico_parse_float( p,&shineValue )) + _ase_error_return("Material shine parse error"); + + /* scale ase shine range 0..1 to pico range 0..127 */ + shineValue *= 128.0; + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse ambient material color */ + else if (!_pico_stricmp(p->token,"*material_ambient")) + { + picoVec3_t vec; + /* get r,g,b float values from ase */ + if (!_pico_parse_vec( p,vec )) + _ase_error_return("Material color parse error"); + + /* setup 0..255 range color values */ + ambientColor[ 0 ] = (int)( vec[ 0 ] * 255.0 ); + ambientColor[ 1 ] = (int)( vec[ 1 ] * 255.0 ); + ambientColor[ 2 ] = (int)( vec[ 2 ] * 255.0 ); + ambientColor[ 3 ] = (int)( 255 ); + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse diffuse material color */ + else if (!_pico_stricmp(p->token,"*material_diffuse")) + { + picoVec3_t vec; + + /* get r,g,b float values from ase */ + if (!_pico_parse_vec( p,vec )) + _ase_error_return("Material color parse error"); + + /* setup 0..255 range color */ + diffuseColor[ 0 ] = (int)( vec[ 0 ] * 255.0 ); + diffuseColor[ 1 ] = (int)( vec[ 1 ] * 255.0 ); + diffuseColor[ 2 ] = (int)( vec[ 2 ] * 255.0 ); + diffuseColor[ 3 ] = (int)( 255 ); + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse specular material color */ + else if (!_pico_stricmp(p->token,"*material_specular")) + { + picoVec3_t vec; + + /* get r,g,b float values from ase */ + if (!_pico_parse_vec( p,vec )) + _ase_error_return("Material color parse error"); + + /* setup 0..255 range color */ + specularColor[ 0 ] = (int)( vec[ 0 ] * 255 ); + specularColor[ 1 ] = (int)( vec[ 1 ] * 255 ); + specularColor[ 2 ] = (int)( vec[ 2 ] * 255 ); + specularColor[ 3 ] = (int)( 255 ); + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* material diffuse map */ + else if (!_pico_stricmp(p->token,"*map_diffuse") ) + { + int sublevel = 0; + + /* parse material block */ + while( 1 ) + { + /* get next token */ + if (_pico_parse(p,1) == NULL) break; + if (!strlen(p->token)) continue; + + /* handle levels */ + if (p->token[0] == '{') sublevel++; + if (p->token[0] == '}') sublevel--; + if (!sublevel) break; + + /* parse diffuse map bitmap */ + if (!_pico_stricmp(p->token,"*bitmap")) + { + char* name = _pico_parse(p,0); + if (name == NULL) + _ase_error_return("Missing material map bitmap name"); + mapname = _pico_alloc ( strlen ( name ) + 1 ); + strcpy ( mapname, name ); + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + } + } + /* end map_diffuse block */ + } + /* end material block */ + + if( subMaterial == NULL ) + { + /* allocate new pico shader */ + shader = PicoNewShader( model ); + if (shader == NULL) + { + PicoFreeModel( model ); + return NULL; + } + + /* set material name */ + PicoSetShaderName( shader,materialName ); + + /* set shader's transparency */ + PicoSetShaderTransparency( shader,transValue ); + + /* set shader's ambient color */ + PicoSetShaderAmbientColor( shader,ambientColor ); + + /* set diffuse alpha to transparency */ + diffuseColor[3] = (picoByte_t)( transValue * 255.0 ); + + /* set shader's diffuse color */ + PicoSetShaderDiffuseColor( shader,diffuseColor ); + + /* set shader's specular color */ + PicoSetShaderSpecularColor( shader,specularColor ); + + /* set shader's shininess */ + PicoSetShaderShininess( shader,shineValue ); + + /* set material map name */ + PicoSetShaderMapName( shader, mapname ); + + /* extract shadername from bitmap path */ + if(mapname != NULL) + { + char* p = mapname; + + /* convert to shader-name format */ + { + /* unix-style path separators */ + char* s = mapname; + for(; *s != '\0'; ++s) + { + if(*s == '\\') + { + *s = '/'; + } + } + } + { + /* remove extension */ + char* last_period = strrchr(p, '.'); + if(last_period != NULL) + { + *last_period = '\0'; + } + } + + /* find game root */ + for(; *p != '\0'; ++p) + { + if(_pico_strnicmp(p, "quake", 5) == 0 || _pico_strnicmp(p, "doom", 4) == 0) + { + break; + } + } + /* root-relative */ + for(; *p != '\0'; ++p) + { + if(*p == '/') + { + ++p; + break; + } + } + /* game-relative */ + for(; *p != '\0'; ++p) + { + if(*p == '/') + { + ++p; + break; + } + } + + if(*p != '\0') + { + /* set material name */ + PicoSetShaderName( shader,p ); + } + } + + /* this is just a material with 1 submaterial */ + subMaterial = _ase_add_submaterial( &materials, index, 0, shader ); + } + + /* ydnar: free mapname */ + if( mapname != NULL ) + _pico_free( mapname ); + } // !_pico_stricmp ( "*material" ) + + /* skip unparsed rest of line and continue */ + _pico_parse_skip_rest( p ); + } + + /* ydnar: finish existing surface */ + _ase_submit_triangles(model, materials, vertices, texcoords, colors, faces, numFaces,numVertices,submodel++); + _pico_free(faces); + _pico_free(vertices); + _pico_free(texcoords); + _pico_free(colors); + +#ifdef DEBUG_PM_ASE + _ase_print_materials(materials); + finish = clock(); + elapsed = (double)(finish - start) / CLOCKS_PER_SEC; + _pico_printf( PICO_NORMAL, "Loaded model in in %-.2f second(s)\n", elapsed ); +#endif //DEBUG_PM_ASE + + _ase_free_materials(&materials); + + _pico_free_parser( p ); + + /* return allocated pico model */ + return model; +} + +/* pico file format module definition */ +const picoModule_t picoModuleASE = +{ + "1.0", /* module version string */ + "Autodesk 3DSMAX ASCII", /* module display name */ + "Jared Hefty, seaw0lf", /* author's name */ + "2003 Jared Hefty, 2002 seaw0lf", /* module copyright */ + { + "ase",NULL,NULL,NULL /* default extensions to use */ + }, + _ase_canload, /* validation routine */ + _ase_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/tools/urt/libs/picomodel/pm_fm.c b/tools/urt/libs/picomodel/pm_fm.c new file mode 100644 index 00000000..7f15e81f --- /dev/null +++ b/tools/urt/libs/picomodel/pm_fm.c @@ -0,0 +1,667 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + +/* +Nurail: Used pm_md3.c (Randy Reddig) as a template. +*/ + +/* marker */ +#define PM_FM_C + +/* dependencies */ +#include "pm_fm.h" + +//#define FM_VERBOSE_DBG 0 +#undef FM_VERBOSE_DBG +#undef FM_DBG + +typedef struct index_LUT_s +{ + short Vert; + short ST; + struct index_LUT_s *next; + +} index_LUT_t; + +typedef struct index_DUP_LUT_s +{ + short ST; + short OldVert; + +} index_DUP_LUT_t; + + +// _fm_canload() +static int _fm_canload( PM_PARAMS_CANLOAD ) +{ + fm_t fm; + unsigned char *bb; + int fm_file_pos; + + bb = (unsigned char *) buffer; + + // Header + fm.fm_header_hdr = (fm_chunk_header_t *) bb; + fm_file_pos = sizeof(fm_chunk_header_t) + fm.fm_header_hdr->size; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "IDENT: %s\n", (unsigned char *) fm.fm_header_hdr->ident ); +#endif + if( (strcmp(fm.fm_header_hdr->ident, FM_HEADERCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Header Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_header_hdr->version ) != FM_HEADERCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Header Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // Skin + fm.fm_skin_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_skin_hdr->size; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "SKIN: %s\n", (unsigned char *) fm.fm_skin_hdr->ident ); +#endif + if( (strcmp(fm.fm_skin_hdr->ident, FM_SKINCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Skin Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_skin_hdr->version ) != FM_SKINCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Skin Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // st + fm.fm_st_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_st_hdr->size; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "ST: %s\n", (unsigned char *) fm.fm_st_hdr->ident ); +#endif + if( (strcmp(fm.fm_st_hdr->ident, FM_STCOORDCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM ST Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_st_hdr->version ) != FM_STCOORDCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM ST Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // tri + fm.fm_tri_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_tri_hdr->size; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "TRI: %s\n", (unsigned char *) fm.fm_tri_hdr->ident ); +#endif + if( (strcmp(fm.fm_tri_hdr->ident, FM_TRISCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Tri Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_tri_hdr->version ) != FM_TRISCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Tri Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // frame + fm.fm_frame_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t); +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "FRAME: %s\n", (unsigned char *) fm.fm_frame_hdr->ident ); +#endif + if( (strcmp(fm.fm_frame_hdr->ident, FM_FRAMESCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Frame Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_frame_hdr->version ) != FM_FRAMESCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Frame Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // file seems to be a valid fm + return PICO_PMV_OK; +} + + + +// _fm_load() loads a Heretic 2 model file. +static picoModel_t *_fm_load( PM_PARAMS_LOAD ) +{ + int i, j, dups, dup_index; + int fm_file_pos; + short tot_numVerts; + index_LUT_t *p_index_LUT, *p_index_LUT2, *p_index_LUT3; + index_DUP_LUT_t *p_index_LUT_DUPS; + + fm_vert_normal_t *vert; + + char skinname[FM_SKINPATHSIZE]; + fm_t fm; + fm_header_t *fm_head; + fm_st_t *texCoord; + fm_xyz_st_t *tri_verts; + fm_xyz_st_t *triangle; + fm_frame_t *frame; + + picoByte_t *bb; + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + bb = (picoByte_t*) buffer; + + // Header Header + fm.fm_header_hdr = (fm_chunk_header_t *) bb; + fm_file_pos = sizeof(fm_chunk_header_t) + fm.fm_header_hdr->size; + if( (strcmp(fm.fm_header_hdr->ident, FM_HEADERCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM Header Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_header_hdr->version ) != FM_HEADERCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM Header Version incorrect\n"); + return NULL; + } + + // Skin Header + fm.fm_skin_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_skin_hdr->size; + if( (strcmp(fm.fm_skin_hdr->ident, FM_SKINCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM Skin Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_skin_hdr->version ) != FM_SKINCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM Skin Version incorrect\n"); + return NULL; + } + + // ST Header + fm.fm_st_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_st_hdr->size; + if( (strcmp(fm.fm_st_hdr->ident, FM_STCOORDCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM ST Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_st_hdr->version ) != FM_STCOORDCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM ST Version incorrect\n"); + return NULL; + } + + // Tris Header + fm.fm_tri_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_tri_hdr->size; + if( (strcmp(fm.fm_tri_hdr->ident, FM_TRISCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM Tri Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_tri_hdr->version ) != FM_TRISCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM Tri Version incorrect\n"); + return NULL; + } + + // Frame Header + fm.fm_frame_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t); + if( (strcmp(fm.fm_frame_hdr->ident, FM_FRAMESCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM Frame Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_frame_hdr->version ) != FM_FRAMESCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM Frame Version incorrect\n"); + return NULL; + } + + // Header + fm_file_pos = sizeof(fm_chunk_header_t); + fm_head = fm.fm_header = (fm_header_t *) (bb + fm_file_pos); + fm_file_pos += fm.fm_header_hdr->size; + + // Skin + fm_file_pos += sizeof(fm_chunk_header_t); + fm.fm_skin = (fm_skinpath_t *) (bb + fm_file_pos); + fm_file_pos += fm.fm_skin_hdr->size; + + // ST + fm_file_pos += sizeof(fm_chunk_header_t); + texCoord = fm.fm_st = (fm_st_t *) (bb + fm_file_pos); + fm_file_pos += fm.fm_st_hdr->size; + + // Tri + fm_file_pos += sizeof(fm_chunk_header_t); + tri_verts = fm.fm_tri = (fm_xyz_st_t *) (bb + fm_file_pos); + fm_file_pos += fm.fm_tri_hdr->size; + + // Frame + fm_file_pos += sizeof(fm_chunk_header_t); + frame = fm.fm_frame = (fm_frame_t *) (bb + fm_file_pos); + + // do frame check + if( fm_head->numFrames < 1 ) + { + _pico_printf( PICO_ERROR, "%s has 0 frames!", fileName ); + return NULL; + } + + if( frameNum < 0 || frameNum >= fm_head->numFrames ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range FM frame specified" ); + return NULL; + } + + // swap fm + fm_head->skinWidth = _pico_little_long( fm_head->skinWidth ); + fm_head->skinHeight = _pico_little_long( fm_head->skinHeight ); + fm_head->frameSize = _pico_little_long( fm_head->frameSize ); + + fm_head->numSkins = _pico_little_long( fm_head->numSkins ); + fm_head->numXYZ = _pico_little_long( fm_head->numXYZ ); + fm_head->numST = _pico_little_long( fm_head->numST ); + fm_head->numTris = _pico_little_long( fm_head->numTris ); + fm_head->numGLCmds = _pico_little_long( fm_head->numGLCmds ); + fm_head->numFrames = _pico_little_long( fm_head->numFrames ); + + // swap frame scale and translation + for( i = 0; i < 3; i++ ) + { + frame->header.scale[ i ] = _pico_little_float( frame->header.scale[ i ] ); + frame->header.translate[ i ] = _pico_little_float( frame->header.translate[ i ] ); + } + + // swap triangles + triangle = tri_verts; + for( i = 0; i < fm_head->numTris; i++, triangle++ ) + { + for( j = 0; j < 3; j++ ) + { + triangle->index_xyz[ j ] = _pico_little_short( triangle->index_xyz[ j ] ); + triangle->index_st[ j ] = _pico_little_short( triangle->index_st[ j ] ); + } + } + + // swap st coords + for( i = 0; i < fm_head->numST; i++ ) + { + texCoord->s = _pico_little_short( texCoord[i].s ); + texCoord->t = _pico_little_short( texCoord[i].t ); + } + // set Skin Name + strncpy(skinname, (unsigned char *) fm.fm_skin, FM_SKINPATHSIZE ); + +#ifdef FM_VERBOSE_DBG + // Print out md2 values + _pico_printf(PICO_VERBOSE,"numSkins->%d numXYZ->%d numST->%d numTris->%d numFrames->%d\nSkin Name \"%s\"\n", fm_head->numSkins, fm_head->numXYZ, fm_head->numST, fm_head->numTris, fm_head->numFrames, &skinname ); +#endif + + // detox Skin name + _pico_setfext( skinname, "" ); + _pico_unixify( skinname ); + + /* create new pico model */ + picoModel = PicoNewModel(); + if( picoModel == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, fm_head->numFrames ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + // allocate new pico surface + picoSurface = PicoNewSurface( picoModel ); + if( picoSurface == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); + return NULL; + } + + + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( picoSurface, frame->header.name ); + picoShader = PicoNewShader( picoModel ); + if( picoShader == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + return NULL; + } + + PicoSetShaderName( picoShader, skinname ); + + // associate current surface with newly created shader + PicoSetSurfaceShader( picoSurface, picoShader ); + + // Init LUT for Verts + p_index_LUT = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t) * fm_head->numXYZ); + for(i=0; inumXYZ; i++) + { + p_index_LUT[i].Vert = -1; + p_index_LUT[i].ST = -1; + p_index_LUT[i].next = NULL; + } + + // Fill in Look Up Table, and allocate/fill Linked List from vert array as needed for dup STs per Vert. + tot_numVerts = fm_head->numXYZ; + dups = 0; + triangle = tri_verts; + + for(i=0; inumTris; i++) + { + for(j=0; j<3; j++) + { + if (p_index_LUT[triangle->index_xyz[j]].ST == -1) // No Main Entry + p_index_LUT[triangle->index_xyz[j]].ST = triangle->index_st[j]; + + else if (triangle->index_st[j] == p_index_LUT[triangle->index_xyz[j]].ST ) // Equal to Main Entry + { +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "-> Tri #%d, Vert %d:\t XYZ:%d ST:%d\n", i, j, triangle->index_xyz[j], triangle->index_st[j]); +#endif + continue; + } + else if ( (p_index_LUT[triangle->index_xyz[j]].next == NULL) ) // Not equal to Main entry, and no LL entry + { // Add first entry of LL from Main + p_index_LUT2 = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t)); + if (p_index_LUT2 == NULL) + _pico_printf( PICO_NORMAL, " Couldn't allocate memory!\n"); + p_index_LUT[triangle->index_xyz[j]].next = (index_LUT_t *)p_index_LUT2; + p_index_LUT2->Vert = dups; + p_index_LUT2->ST = triangle->index_st[j]; + p_index_LUT2->next = NULL; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, " ADDING first LL XYZ:%d DUP:%d ST:%d\n", triangle->index_xyz[j], dups, triangle->index_st[j]); +#endif + triangle->index_xyz[j] = dups + fm_head->numXYZ; // Make change in Tri hunk + dups++; + } + else // Try to find in LL from Main Entry + { + p_index_LUT3 = p_index_LUT2 = p_index_LUT[triangle->index_xyz[j]].next; + while ( (p_index_LUT2 != NULL) && (triangle->index_xyz[j] != p_index_LUT2->Vert) ) // Walk down LL + { + p_index_LUT3 = p_index_LUT2; + p_index_LUT2 = p_index_LUT2->next; + } + p_index_LUT2 = p_index_LUT3; + + if ( triangle->index_st[j] == p_index_LUT2->ST ) // Found it + { + triangle->index_xyz[j] = p_index_LUT2->Vert + fm_head->numXYZ; // Make change in Tri hunk +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "--> Tri #%d, Vert %d:\t XYZ:%d ST:%d\n", i, j, triangle->index_xyz[j], triangle->index_st[j]); +#endif + continue; + } + + if ( p_index_LUT2->next == NULL) // Didn't find it. Add entry to LL. + { + // Add the Entry + p_index_LUT3 = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t)); + if (p_index_LUT3 == NULL) + _pico_printf( PICO_NORMAL, " Couldn't allocate memory!\n"); + p_index_LUT2->next = (index_LUT_t *)p_index_LUT3; + p_index_LUT3->Vert = dups; + p_index_LUT3->ST = triangle->index_st[j]; + p_index_LUT3->next = NULL; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, " ADDING additional LL XYZ:%d DUP:%d NewXYZ:%d ST:%d\n", triangle->index_xyz[j], dups, dups + (fm_head->numXYZ), triangle->index_st[j]); +#endif + triangle->index_xyz[j] = dups + fm_head->numXYZ; // Make change in Tri hunk + dups++; + } + } +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "---> Tri #%d, Vert %d:\t XYZ:%d ST:%d\n", i, j, triangle->index_xyz[j], triangle->index_st[j]); +#endif + } + triangle++; + } + + // malloc and build array for Dup STs + p_index_LUT_DUPS = (index_DUP_LUT_t *)_pico_alloc(sizeof(index_DUP_LUT_t) * dups); + if (p_index_LUT_DUPS == NULL) + _pico_printf( PICO_NORMAL, " Couldn't allocate memory!\n"); + + dup_index = 0; + for(i=0; inumXYZ; i++) + { + p_index_LUT2 = p_index_LUT[i].next; + while (p_index_LUT2 != NULL) + { + p_index_LUT_DUPS[p_index_LUT2->Vert].OldVert = i; + p_index_LUT_DUPS[p_index_LUT2->Vert].ST = p_index_LUT2->ST; + dup_index++; + p_index_LUT2 = p_index_LUT2->next; + } + } +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, " Dups = %d\n", dups); + _pico_printf( PICO_NORMAL, " Dup Index = %d\n", dup_index); +#endif + for(i=0; inumXYZ; i++) + { +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "Vert: %4d\t%4d",i, p_index_LUT[i].ST); +#endif + if (p_index_LUT[i].next != NULL) + { + + p_index_LUT2 = p_index_LUT[i].next; + do { +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, " %4d %4d", p_index_LUT2->Vert, p_index_LUT2->ST); +#endif + p_index_LUT2 = p_index_LUT2->next; + } while ( p_index_LUT2 != NULL); + + } +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "\n"); +#endif + } + + +#ifdef FM_VERBOSE_DBG + for(i=0; inumTris; i++) + { + for(j=0; j<3; j++) + _pico_printf( PICO_NORMAL, "Tri #%d, Vert %d:\t XYZ:%d ST:%d\n", i, j, triangle->index_xyz[j], triangle->index_st[j]); + _pico_printf( PICO_NORMAL, "\n"); + triangle++; + } +#endif + // Build Picomodel + triangle = tri_verts; + for( j = 0; j < fm_head->numTris; j++, triangle++ ) + { + PicoSetSurfaceIndex( picoSurface, j*3 , triangle->index_xyz[0] ); + PicoSetSurfaceIndex( picoSurface, j*3+1 , triangle->index_xyz[1] ); + PicoSetSurfaceIndex( picoSurface, j*3+2 , triangle->index_xyz[2] ); + } + + vert = (fm_vert_normal_t*) ((picoByte_t*) (frame->verts) ); + for(i=0; i< fm_head->numXYZ; i++, vert++) + { + /* set vertex origin */ + xyz[ 0 ] = vert->v[0] * frame->header.scale[0] + frame->header.translate[0]; + xyz[ 1 ] = vert->v[1] * frame->header.scale[1] + frame->header.translate[1]; + xyz[ 2 ] = vert->v[2] * frame->header.scale[2] + frame->header.translate[2]; + PicoSetSurfaceXYZ( picoSurface, i , xyz ); + + /* set normal */ + normal[ 0 ] = fm_normals[vert->lightnormalindex][0]; + normal[ 1 ] = fm_normals[vert->lightnormalindex][1]; + normal[ 2 ] = fm_normals[vert->lightnormalindex][2]; + PicoSetSurfaceNormal( picoSurface, i , normal ); + + /* set st coords */ + st[ 0 ] = ((texCoord[p_index_LUT[i].ST].s) / ((float)fm_head->skinWidth)); + st[ 1 ] = (texCoord[p_index_LUT[i].ST].t / ((float)fm_head->skinHeight)); + PicoSetSurfaceST( picoSurface, 0, i , st ); + } + + if (dups) + { + for(i=0; iverts[j].v[0] * frame->header.scale[0] + frame->header.translate[0]; + xyz[ 1 ] = frame->verts[j].v[1] * frame->header.scale[1] + frame->header.translate[1]; + xyz[ 2 ] = frame->verts[j].v[2] * frame->header.scale[2] + frame->header.translate[2]; + PicoSetSurfaceXYZ( picoSurface, i + fm_head->numXYZ , xyz ); + + /* set normal */ + normal[ 0 ] = fm_normals[frame->verts[j].lightnormalindex][0]; + normal[ 1 ] = fm_normals[frame->verts[j].lightnormalindex][1]; + normal[ 2 ] = fm_normals[frame->verts[j].lightnormalindex][2]; + PicoSetSurfaceNormal( picoSurface, i + fm_head->numXYZ , normal ); + + /* set st coords */ + st[ 0 ] = ((texCoord[p_index_LUT_DUPS[i].ST].s) / ((float)fm_head->skinWidth)); + st[ 1 ] = (texCoord[p_index_LUT_DUPS[i].ST].t / ((float)fm_head->skinHeight)); + PicoSetSurfaceST( picoSurface, 0, i + fm_head->numXYZ , st ); + } + } + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, 0, color ); + + // Free up malloc'ed LL entries + for(i=0; inumXYZ; i++) + { + if(p_index_LUT[i].next != NULL) + { + p_index_LUT2 = p_index_LUT[i].next; + do { + p_index_LUT3 = p_index_LUT2->next; + _pico_free(p_index_LUT2); + p_index_LUT2 = p_index_LUT3; + dups--; + } while (p_index_LUT2 != NULL); + } + } + + if (dups) + _pico_printf(PICO_WARNING, " Not all LL mallocs freed\n"); + + // Free malloc'ed LUTs + _pico_free(p_index_LUT); + _pico_free(p_index_LUT_DUPS); + + /* return the new pico model */ + return picoModel; + +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleFM = +{ + "0.85", /* module version string */ + "Heretic 2 FM", /* module display name */ + "Nurail", /* author's name */ + "2003 Nurail", /* module copyright */ + { + "fm", NULL, NULL, NULL /* default extensions to use */ + }, + _fm_canload, /* validation routine */ + _fm_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/tools/urt/libs/picomodel/pm_fm.h b/tools/urt/libs/picomodel/pm_fm.h new file mode 100644 index 00000000..ce43d334 --- /dev/null +++ b/tools/urt/libs/picomodel/pm_fm.h @@ -0,0 +1,367 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + +// This header file is based from the following: + +/* + FlexModel.H - Header file for FlexModel file structure + + By Chris Burke + serotonin@earthlink.net +*/ + +#ifndef __PM_FM_H__ +#define __PM_FM_H__ + +#include "picointernal.h" + + +// +// Absolute limits (from QData / QMView source) +// +#define MAX_FM_TRIANGLES 2048 +#define MAX_FM_VERTS 2048 +#define MAX_FM_FRAMES 2048 +#define MAX_FM_SKINS 64 +#define MAX_FM_SKINNAME 64 +#define MAX_FM_MESH_NODES 16 + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +#define SKINPAGE_WIDTH 640 +#define SKINPAGE_HEIGHT 480 + +#define ENCODED_WIDTH_X 92 +#define ENCODED_WIDTH_Y 475 +#define ENCODED_HEIGHT_X 128 +#define ENCODED_HEIGHT_Y 475 + +#define SCALE_ADJUST_FACTOR 0.96 + +#define INFO_HEIGHT 5 +#define INFO_Y (SKINPAGE_HEIGHT-INFO_HEIGHT) + +#ifndef byte + #define byte unsigned char +#endif + + +// +// Generic header on every chunk +// +#define FM_MAXCHUNKIDENT 32L +typedef struct +{ + char ident[FM_MAXCHUNKIDENT]; + unsigned int version; + unsigned int size; +} fm_chunk_header_t; + +// +// The format of the "header" chunk +// +#define FM_HEADERCHUNKNAME "header" +#define FM_HEADERCHUNKVER 2 +#define FM_HEADERCHUNKSIZE 40 +typedef struct +{ + int skinWidth; // in pixels + int skinHeight; // in pixels + int frameSize; // size of each frame (in bytes) + int numSkins; // number of skins + int numXYZ; // number of unique vertices in 3D space + int numST; // number of unique vertices in texture space + int numTris; // number of unique triangles + int numGLCmds; // # 32-bit elements in strip/fan command list + int numFrames; // number of animation frames + int numMeshNodes; // number of mesh nodes +} fm_header_t; + +// +// The format of an entry in the "skin" chunk. +// The number of entries is given in the fmheader chunk +// +#define FM_SKINCHUNKNAME "skin" +#define FM_SKINCHUNKVER 1 +#define FM_MAXPATHLENGTH 64L +#define FM_SKINPATHSIZE (FM_MAXPATHLENGTH) +typedef struct +{ + char path[FM_SKINPATHSIZE]; // path, relative to 'base' +} fm_skinpath_t; + +// +// The format of the "st coord" chunk. This is a list +// of unique skin texture (u, v) coordinates to be mapped +// to verteces of the model +// +#define FM_STCOORDCHUNKNAME "st coord" +#define FM_STCOORDCHUNKVER 1 +#define FM_STCOORDUVSIZE (2L + 2L) + +typedef struct +{ + short s; + short t; +} fm_st_t; + +// +// The format of the "tris" chunk. This is a list of vertex indeces +// in 3D space, and the corresponding vertex indeces in texture space. +// +#define FM_TRISCHUNKNAME "tris" +#define FM_TRISCHUNKVER 1 +#define FM_TRISINFOSIZE (2L*3 + 2L*3) + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} fm_xyz_st_t; + + +// +// The format of the "frames" chunk. This is a list of animation +// frames, each specifying the coordinates and "light normal" index +// of every vertex of the model in 3D space. +// +#define FM_FRAMESCHUNKNAME "frames" +#define FM_FRAMESCHUNKVER 1 + +#define FM_NUMVERTEXNORMALS 162 + +// Frame info +typedef struct +{ + byte v[3]; // scaled by header info + byte lightnormalindex; // index in canned table of closest vertex normal +} fm_vert_normal_t; + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name +} fm_framehdr_t; + +typedef struct +{ + fm_framehdr_t header; // One header per frame + fm_vert_normal_t verts[1]; // variable number of these +} fm_frame_t; + +typedef struct +{ + fm_chunk_header_t *fm_header_hdr; + fm_header_t *fm_header; + fm_chunk_header_t *fm_skin_hdr; + fm_skinpath_t *fm_skin; + fm_chunk_header_t *fm_st_hdr; + fm_st_t *fm_st; + fm_chunk_header_t *fm_tri_hdr; + fm_xyz_st_t *fm_tri; + fm_chunk_header_t *fm_frame_hdr; + fm_frame_t *fm_frame; +} fm_t; + +float fm_normals[FM_NUMVERTEXNORMALS][3] = { + {-0.525731f, 0.000000f, 0.850651f}, + {-0.442863f, 0.238856f, 0.864188f}, + {-0.295242f, 0.000000f, 0.955423f}, + {-0.309017f, 0.500000f, 0.809017f}, + {-0.162460f, 0.262866f, 0.951056f}, + {0.000000f, 0.000000f, 1.000000f}, + {0.000000f, 0.850651f, 0.525731f}, + {-0.147621f, 0.716567f, 0.681718f}, + {0.147621f, 0.716567f, 0.681718f}, + {0.000000f, 0.525731f, 0.850651f}, + {0.309017f, 0.500000f, 0.809017f}, + {0.525731f, 0.000000f, 0.850651f}, + {0.295242f, 0.000000f, 0.955423f}, + {0.442863f, 0.238856f, 0.864188f}, + {0.162460f, 0.262866f, 0.951056f}, + {-0.681718f, 0.147621f, 0.716567f}, + {-0.809017f, 0.309017f, 0.500000f}, + {-0.587785f, 0.425325f, 0.688191f}, + {-0.850651f, 0.525731f, 0.000000f}, + {-0.864188f, 0.442863f, 0.238856f}, + {-0.716567f, 0.681718f, 0.147621f}, + {-0.688191f, 0.587785f, 0.425325f}, + {-0.500000f, 0.809017f, 0.309017f}, + {-0.238856f, 0.864188f, 0.442863f}, + {-0.425325f, 0.688191f, 0.587785f}, + {-0.716567f, 0.681718f, -0.147621f}, + {-0.500000f, 0.809017f, -0.309017f}, + {-0.525731f, 0.850651f, 0.000000f}, + {0.000000f, 0.850651f, -0.525731f}, + {-0.238856f, 0.864188f, -0.442863f}, + {0.000000f, 0.955423f, -0.295242f}, + {-0.262866f, 0.951056f, -0.162460f}, + {0.000000f, 1.000000f, 0.000000f}, + {0.000000f, 0.955423f, 0.295242f}, + {-0.262866f, 0.951056f, 0.162460f}, + {0.238856f, 0.864188f, 0.442863f}, + {0.262866f, 0.951056f, 0.162460f}, + {0.500000f, 0.809017f, 0.309017f}, + {0.238856f, 0.864188f, -0.442863f}, + {0.262866f, 0.951056f, -0.162460f}, + {0.500000f, 0.809017f, -0.309017f}, + {0.850651f, 0.525731f, 0.000000f}, + {0.716567f, 0.681718f, 0.147621f}, + {0.716567f, 0.681718f, -0.147621f}, + {0.525731f, 0.850651f, 0.000000f}, + {0.425325f, 0.688191f, 0.587785f}, + {0.864188f, 0.442863f, 0.238856f}, + {0.688191f, 0.587785f, 0.425325f}, + {0.809017f, 0.309017f, 0.500000f}, + {0.681718f, 0.147621f, 0.716567f}, + {0.587785f, 0.425325f, 0.688191f}, + {0.955423f, 0.295242f, 0.000000f}, + {1.000000f, 0.000000f, 0.000000f}, + {0.951056f, 0.162460f, 0.262866f}, + {0.850651f, -0.525731f, 0.000000f}, + {0.955423f, -0.295242f, 0.000000f}, + {0.864188f, -0.442863f, 0.238856f}, + {0.951056f, -0.162460f, 0.262866f}, + {0.809017f, -0.309017f, 0.500000f}, + {0.681718f, -0.147621f, 0.716567f}, + {0.850651f, 0.000000f, 0.525731f}, + {0.864188f, 0.442863f, -0.238856f}, + {0.809017f, 0.309017f, -0.500000f}, + {0.951056f, 0.162460f, -0.262866f}, + {0.525731f, 0.000000f, -0.850651f}, + {0.681718f, 0.147621f, -0.716567f}, + {0.681718f, -0.147621f, -0.716567f}, + {0.850651f, 0.000000f, -0.525731f}, + {0.809017f, -0.309017f, -0.500000f}, + {0.864188f, -0.442863f, -0.238856f}, + {0.951056f, -0.162460f, -0.262866f}, + {0.147621f, 0.716567f, -0.681718f}, + {0.309017f, 0.500000f, -0.809017f}, + {0.425325f, 0.688191f, -0.587785f}, + {0.442863f, 0.238856f, -0.864188f}, + {0.587785f, 0.425325f, -0.688191f}, + {0.688191f, 0.587785f, -0.425325f}, + {-0.147621f, 0.716567f, -0.681718f}, + {-0.309017f, 0.500000f, -0.809017f}, + {0.000000f, 0.525731f, -0.850651f}, + {-0.525731f, 0.000000f, -0.850651f}, + {-0.442863f, 0.238856f, -0.864188f}, + {-0.295242f, 0.000000f, -0.955423f}, + {-0.162460f, 0.262866f, -0.951056f}, + {0.000000f, 0.000000f, -1.000000f}, + {0.295242f, 0.000000f, -0.955423f}, + {0.162460f, 0.262866f, -0.951056f}, + {-0.442863f, -0.238856f, -0.864188f}, + {-0.309017f, -0.500000f, -0.809017f}, + {-0.162460f, -0.262866f, -0.951056f}, + {0.000000f, -0.850651f, -0.525731f}, + {-0.147621f, -0.716567f, -0.681718f}, + {0.147621f, -0.716567f, -0.681718f}, + {0.000000f, -0.525731f, -0.850651f}, + {0.309017f, -0.500000f, -0.809017f}, + {0.442863f, -0.238856f, -0.864188f}, + {0.162460f, -0.262866f, -0.951056f}, + {0.238856f, -0.864188f, -0.442863f}, + {0.500000f, -0.809017f, -0.309017f}, + {0.425325f, -0.688191f, -0.587785f}, + {0.716567f, -0.681718f, -0.147621f}, + {0.688191f, -0.587785f, -0.425325f}, + {0.587785f, -0.425325f, -0.688191f}, + {0.000000f, -0.955423f, -0.295242f}, + {0.000000f, -1.000000f, 0.000000f}, + {0.262866f, -0.951056f, -0.162460f}, + {0.000000f, -0.850651f, 0.525731f}, + {0.000000f, -0.955423f, 0.295242f}, + {0.238856f, -0.864188f, 0.442863f}, + {0.262866f, -0.951056f, 0.162460f}, + {0.500000f, -0.809017f, 0.309017f}, + {0.716567f, -0.681718f, 0.147621f}, + {0.525731f, -0.850651f, 0.000000f}, + {-0.238856f, -0.864188f, -0.442863f}, + {-0.500000f, -0.809017f, -0.309017f}, + {-0.262866f, -0.951056f, -0.162460f}, + {-0.850651f, -0.525731f, 0.000000f}, + {-0.716567f, -0.681718f, -0.147621f}, + {-0.716567f, -0.681718f, 0.147621f}, + {-0.525731f, -0.850651f, 0.000000f}, + {-0.500000f, -0.809017f, 0.309017f}, + {-0.238856f, -0.864188f, 0.442863f}, + {-0.262866f, -0.951056f, 0.162460f}, + {-0.864188f, -0.442863f, 0.238856f}, + {-0.809017f, -0.309017f, 0.500000f}, + {-0.688191f, -0.587785f, 0.425325f}, + {-0.681718f, -0.147621f, 0.716567f}, + {-0.442863f, -0.238856f, 0.864188f}, + {-0.587785f, -0.425325f, 0.688191f}, + {-0.309017f, -0.500000f, 0.809017f}, + {-0.147621f, -0.716567f, 0.681718f}, + {-0.425325f, -0.688191f, 0.587785f}, + {-0.162460f, -0.262866f, 0.951056f}, + {0.442863f, -0.238856f, 0.864188f}, + {0.162460f, -0.262866f, 0.951056f}, + {0.309017f, -0.500000f, 0.809017f}, + {0.147621f, -0.716567f, 0.681718f}, + {0.000000f, -0.525731f, 0.850651f}, + {0.425325f, -0.688191f, 0.587785f}, + {0.587785f, -0.425325f, 0.688191f}, + {0.688191f, -0.587785f, 0.425325f}, + {-0.955423f, 0.295242f, 0.000000f}, + {-0.951056f, 0.162460f, 0.262866f}, + {-1.000000f, 0.000000f, 0.000000f}, + {-0.850651f, 0.000000f, 0.525731f}, + {-0.955423f, -0.295242f, 0.000000f}, + {-0.951056f, -0.162460f, 0.262866f}, + {-0.864188f, 0.442863f, -0.238856f}, + {-0.951056f, 0.162460f, -0.262866f}, + {-0.809017f, 0.309017f, -0.500000f}, + {-0.864188f, -0.442863f, -0.238856f}, + {-0.951056f, -0.162460f, -0.262866f}, + {-0.809017f, -0.309017f, -0.500000f}, + {-0.681718f, 0.147621f, -0.716567f}, + {-0.681718f, -0.147621f, -0.716567f}, + {-0.850651f, 0.000000f, -0.525731f}, + {-0.688191f, 0.587785f, -0.425325f}, + {-0.587785f, 0.425325f, -0.688191f}, + {-0.425325f, 0.688191f, -0.587785f}, + {-0.425325f, -0.688191f, -0.587785f}, + {-0.587785f, -0.425325f, -0.688191f}, + {-0.688191f, -0.587785f, -0.425325f}, +}; + +#endif diff --git a/tools/urt/libs/picomodel/pm_lwo.c b/tools/urt/libs/picomodel/pm_lwo.c new file mode 100644 index 00000000..c5a6a7c6 --- /dev/null +++ b/tools/urt/libs/picomodel/pm_lwo.c @@ -0,0 +1,445 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + +/* marker */ +#define PM_LWO_C + +/* dependencies */ +#include "picointernal.h" +#include "lwo/lwo2.h" + +/* uncomment when debugging this module */ +/*#define DEBUG_PM_LWO*/ + +#ifdef DEBUG_PM_LWO +#include "time.h" +#endif + +/* helper functions */ +static const char *lwo_lwIDToStr( unsigned int lwID ) +{ + static char lwIDStr[5]; + + if (!lwID) + { + return "n/a"; + } + + lwIDStr[ 0 ] = (char)((lwID) >> 24); + lwIDStr[ 1 ] = (char)((lwID) >> 16); + lwIDStr[ 2 ] = (char)((lwID) >> 8); + lwIDStr[ 3 ] = (char)((lwID)); + lwIDStr[ 4 ] = '\0'; + + return lwIDStr; +} + +/* +_lwo_canload() +validates a LightWave Object model file. btw, i use the +preceding underscore cause it's a static func referenced +by one structure only. +*/ +static int _lwo_canload( PM_PARAMS_CANLOAD ) +{ + picoMemStream_t *s; + unsigned int failID = 0; + int failpos = -1; + int ret; + + /* create a new pico memorystream */ + s = _pico_new_memstream( (picoByte_t *)buffer, bufSize ); + if (s == NULL) + { + return PICO_PMV_ERROR_MEMORY; + } + + ret = lwValidateObject( fileName, s, &failID, &failpos ); + + _pico_free_memstream( s ); + + return ret; +} + +/* +_lwo_load() +loads a LightWave Object model file. +*/ +static picoModel_t *_lwo_load( PM_PARAMS_LOAD ) +{ + picoMemStream_t *s; + unsigned int failID = 0; + int failpos = -1; + lwObject *obj; + lwSurface *surface; + lwLayer *layer; + lwPoint *pt; + lwPolygon *pol; + lwPolVert *v; + lwVMapPt *vm; + char name[ 64 ]; + int i, j, k, numverts; + + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + int defaultSTAxis[ 2 ]; + picoVec2_t defaultXYZtoSTScale; + + picoVertexCombinationHash_t **hashTable; + picoVertexCombinationHash_t *vertexCombinationHash; + +#ifdef DEBUG_PM_LWO + clock_t load_start, load_finish, convert_start, convert_finish; + double load_elapsed, convert_elapsed; + + load_start = clock(); +#endif + + /* do frame check */ + if( frameNum < 0 || frameNum >= 1 ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range LWO frame specified" ); + return NULL; + } + + /* create a new pico memorystream */ + s = _pico_new_memstream( (picoByte_t *)buffer, bufSize ); + if (s == NULL) + { + return NULL; + } + + obj = lwGetObject( fileName, s, &failID, &failpos ); + + _pico_free_memstream( s ); + + if( !obj ) { + _pico_printf( PICO_ERROR, "Couldn't load LWO file, failed on ID '%s', position %d", lwo_lwIDToStr( failID ), failpos ); + return NULL; + } + +#ifdef DEBUG_PM_LWO + convert_start = load_finish = clock(); + load_elapsed = (double)(load_finish - load_start) / CLOCKS_PER_SEC; +#endif + + /* ------------------------------------------------- + pico model creation + ------------------------------------------------- */ + + /* create a new pico model */ + picoModel = PicoNewModel(); + if (picoModel == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, 1 ); + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + /* create all polygons from layer[ 0 ] that belong to this surface */ + layer = &obj->layer[0]; + + /* warn the user that other layers are discarded */ + if (obj->nlayers > 1) + { + _pico_printf( PICO_WARNING, "LWO loader discards any geometry data not in Layer 1 (%d layers found)", obj->nlayers ); + } + + /* initialize dummy normal */ + normal[ 0 ] = normal[ 1 ] = normal[ 2 ] = 0.f; + + /* setup default st map */ + st[ 0 ] = st[ 1 ] = 0.f; /* st[0] holds max, st[1] holds max par one */ + defaultSTAxis[ 0 ] = 0; + defaultSTAxis[ 1 ] = 1; + for( i = 0; i < 3; i++ ) + { + float min = layer->bbox[ i ]; + float max = layer->bbox[ i + 3 ]; + float size = max - min; + + if (size > st[ 0 ]) + { + defaultSTAxis[ 1 ] = defaultSTAxis[ 0 ]; + defaultSTAxis[ 0 ] = i; + + st[ 1 ] = st[ 0 ]; + st[ 0 ] = size; + } + else if (size > st[ 1 ]) + { + defaultSTAxis[ 1 ] = i; + st[ 1 ] = size; + } + } + defaultXYZtoSTScale[ 0 ] = 4.f / st[ 0 ]; + defaultXYZtoSTScale[ 1 ] = 4.f / st[ 1 ]; + + /* LWO surfaces become pico surfaces */ + surface = obj->surf; + while (surface) + { + /* allocate new pico surface */ + picoSurface = PicoNewSurface( picoModel ); + if (picoSurface == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); + lwFreeObject( obj ); + return NULL; + } + + /* LWO model surfaces are all triangle meshes */ + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( picoSurface, surface->name ); + + /* create new pico shader */ + picoShader = PicoNewShader( picoModel ); + if (picoShader == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + lwFreeObject( obj ); + return NULL; + } + + /* detox and set shader name */ + strncpy( name, surface->name, sizeof(name) ); + _pico_first_token( name ); + _pico_setfext( name, "" ); + _pico_unixify( name ); + PicoSetShaderName( picoShader, name ); + + /* associate current surface with newly created shader */ + PicoSetSurfaceShader( picoSurface, picoShader ); + + /* copy indices and vertex data */ + numverts = 0; + + hashTable = PicoNewVertexCombinationHashTable(); + + if (hashTable == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate hash table" ); + PicoFreeModel( picoModel ); + lwFreeObject( obj ); + return NULL; + } + + for( i = 0, pol = layer->polygon.pol; i < layer->polygon.count; i++, pol++ ) + { + /* does this polygon belong to this surface? */ + if (pol->surf != surface) + continue; + + /* we only support polygons of the FACE type */ + if (pol->type != ID_FACE) + { + _pico_printf( PICO_WARNING, "LWO loader discarded a polygon because it's type != FACE (%s)", lwo_lwIDToStr( pol->type ) ); + continue; + } + + /* NOTE: LWO has support for non-convex polygons, do we want to store them as well? */ + if (pol->nverts != 3) + { + _pico_printf( PICO_WARNING, "LWO loader discarded a polygon because it has != 3 verts (%d)", pol->nverts ); + continue; + } + + for( j = 0, v = pol->v; j < 3; j++, v++ ) + { + pt = &layer->point.pt[ v->index ]; + + /* setup data */ + xyz[ 0 ] = pt->pos[ 0 ]; + xyz[ 1 ] = pt->pos[ 2 ]; + xyz[ 2 ] = pt->pos[ 1 ]; + +/* doom3 lwo data doesn't seem to have smoothing-angle information */ +#if 0 + if(surface->smooth <= 0) + { + /* use face normals */ + normal[ 0 ] = v->norm[ 0 ]; + normal[ 1 ] = v->norm[ 2 ]; + normal[ 2 ] = v->norm[ 1 ]; + } + else +#endif + { + /* smooth normals later */ + normal[ 0 ] = 0; + normal[ 1 ] = 0; + normal[ 2 ] = 0; + } + + st[ 0 ] = xyz[ defaultSTAxis[ 0 ] ] * defaultXYZtoSTScale[ 0 ]; + st[ 1 ] = xyz[ defaultSTAxis[ 1 ] ] * defaultXYZtoSTScale[ 1 ]; + + color[ 0 ] = (picoByte_t)(surface->color.rgb[ 0 ] * surface->diffuse.val * 0xFF); + color[ 1 ] = (picoByte_t)(surface->color.rgb[ 1 ] * surface->diffuse.val * 0xFF); + color[ 2 ] = (picoByte_t)(surface->color.rgb[ 2 ] * surface->diffuse.val * 0xFF); + color[ 3 ] = 0xFF; + + /* set from points */ + for( k = 0, vm = pt->vm; k < pt->nvmaps; k++, vm++ ) + { + if (vm->vmap->type == LWID_('T','X','U','V')) + { + /* set st coords */ + st[ 0 ] = vm->vmap->val[ vm->index ][ 0 ]; + st[ 1 ] = 1.f - vm->vmap->val[ vm->index ][ 1 ]; + } + else if (vm->vmap->type == LWID_('R','G','B','A')) + { + /* set rgba */ + color[ 0 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 0 ] * surface->color.rgb[ 0 ] * surface->diffuse.val * 0xFF); + color[ 1 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 1 ] * surface->color.rgb[ 1 ] * surface->diffuse.val * 0xFF); + color[ 2 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 2 ] * surface->color.rgb[ 2 ] * surface->diffuse.val * 0xFF); + color[ 3 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 3 ] * 0xFF); + } + } + + /* override with polygon data */ + for( k = 0, vm = v->vm; k < v->nvmaps; k++, vm++ ) + { + if (vm->vmap->type == LWID_('T','X','U','V')) + { + /* set st coords */ + st[ 0 ] = vm->vmap->val[ vm->index ][ 0 ]; + st[ 1 ] = 1.f - vm->vmap->val[ vm->index ][ 1 ]; + } + else if (vm->vmap->type == LWID_('R','G','B','A')) + { + /* set rgba */ + color[ 0 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 0 ] * surface->color.rgb[ 0 ] * surface->diffuse.val * 0xFF); + color[ 1 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 1 ] * surface->color.rgb[ 1 ] * surface->diffuse.val * 0xFF); + color[ 2 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 2 ] * surface->color.rgb[ 2 ] * surface->diffuse.val * 0xFF); + color[ 3 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 3 ] * 0xFF); + } + } + + /* find vertex in this surface and if we can't find it there create it */ + vertexCombinationHash = PicoFindVertexCombinationInHashTable( hashTable, xyz, normal, st, color ); + + if (vertexCombinationHash) + { + /* found an existing one */ + PicoSetSurfaceIndex( picoSurface, (i * 3 + j ), vertexCombinationHash->index ); + } + else + { + /* it is a new one */ + vertexCombinationHash = PicoAddVertexCombinationToHashTable( hashTable, xyz, normal, st, color, (picoIndex_t) numverts ); + + if (vertexCombinationHash == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate hash bucket entry table" ); + PicoFreeVertexCombinationHashTable( hashTable ); + PicoFreeModel( picoModel ); + lwFreeObject( obj ); + return NULL; + } + + /* add the vertex to this surface */ + PicoSetSurfaceXYZ( picoSurface, numverts, xyz ); + + /* set dummy normal */ + PicoSetSurfaceNormal( picoSurface, numverts, normal ); + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, numverts, color ); + + /* set st coords */ + PicoSetSurfaceST( picoSurface, 0, numverts, st ); + + /* set index */ + PicoSetSurfaceIndex( picoSurface, (i * 3 + j ), (picoIndex_t) numverts ); + + numverts++; + } + } + } + + /* free the hashtable */ + PicoFreeVertexCombinationHashTable( hashTable ); + + /* get next surface */ + surface = surface->next; + } + +#ifdef DEBUG_PM_LWO + load_start = convert_finish = clock(); +#endif + + lwFreeObject( obj ); + +#ifdef DEBUG_PM_LWO + load_finish = clock(); + load_elapsed += (double)(load_finish - load_start) / CLOCKS_PER_SEC; + convert_elapsed = (double)(convert_finish - convert_start) / CLOCKS_PER_SEC; + _pico_printf( PICO_NORMAL, "Loaded model in in %-.2f second(s) (loading: %-.2fs converting: %-.2fs)\n", load_elapsed + convert_elapsed, load_elapsed, convert_elapsed ); +#endif + + /* return the new pico model */ + return picoModel; +} + +/* pico file format module definition */ +const picoModule_t picoModuleLWO = +{ + "1.0", /* module version string */ + "LightWave Object", /* module display name */ + "Arnout van Meer", /* author's name */ + "2003 Arnout van Meer, 2000 Ernie Wright", /* module copyright */ + { + "lwo", NULL, NULL, NULL /* default extensions to use */ + }, + _lwo_canload, /* validation routine */ + _lwo_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/tools/urt/libs/picomodel/pm_md2.c b/tools/urt/libs/picomodel/pm_md2.c new file mode 100644 index 00000000..c91a873b --- /dev/null +++ b/tools/urt/libs/picomodel/pm_md2.c @@ -0,0 +1,667 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + +/* +Nurail: Used pm_md3.c (Randy Reddig) as a template. +*/ + + +/* marker */ +#define PM_MD2_C + +/* dependencies */ +#include "picointernal.h" + + +/* md2 model format */ +#define MD2_MAGIC "IDP2" +#define MD2_VERSION 8 + +#define MD2_NUMVERTEXNORMALS 162 +#define MD2_MAX_SKINNAME 64 +#define MD2_MAX_TRIANGLES 4096 +#define MD2_MAX_VERTS 2048 +#define MD2_MAX_FRAMES 512 +#define MD2_MAX_MD2SKINS 32 +#define MD2_MAX_SKINNAME 64 + +#ifndef byte + #define byte unsigned char +#endif + +typedef struct index_LUT_s +{ + short Vert; + short ST; + struct index_LUT_s *next; + +} index_LUT_t; + +typedef struct index_DUP_LUT_s +{ + short ST; + short OldVert; + +} index_DUP_LUT_t; + +typedef struct +{ + short s; + short t; +} md2St_t; + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} md2Triangle_t; + +typedef struct +{ + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; +} md2XyzNormal_t; + +typedef struct md2Frame_s +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + md2XyzNormal_t verts[1]; // variable sized +} +md2Frame_t; + + +/* md2 model file md2 structure */ +typedef struct md2_s +{ + char magic[ 4 ]; + int version; + + int skinWidth; + int skinHeight; + int frameSize; + + int numSkins; + int numXYZ; + int numST; + int numTris; + int numGLCmds; + int numFrames; + + int ofsSkins; + int ofsST; + int ofsTris; + int ofsFrames; + int ofsGLCmds; + int ofsEnd; +} +md2_t; + +float md2_normals[ MD2_NUMVERTEXNORMALS ][ 3 ] = +{ + { -0.525731f, 0.000000f, 0.850651f }, + { -0.442863f, 0.238856f, 0.864188f }, + { -0.295242f, 0.000000f, 0.955423f }, + { -0.309017f, 0.500000f, 0.809017f }, + { -0.162460f, 0.262866f, 0.951056f }, + { 0.000000f, 0.000000f, 1.000000f }, + { 0.000000f, 0.850651f, 0.525731f }, + { -0.147621f, 0.716567f, 0.681718f }, + { 0.147621f, 0.716567f, 0.681718f }, + { 0.000000f, 0.525731f, 0.850651f }, + { 0.309017f, 0.500000f, 0.809017f }, + { 0.525731f, 0.000000f, 0.850651f }, + { 0.295242f, 0.000000f, 0.955423f }, + { 0.442863f, 0.238856f, 0.864188f }, + { 0.162460f, 0.262866f, 0.951056f }, + { -0.681718f, 0.147621f, 0.716567f }, + { -0.809017f, 0.309017f, 0.500000f }, + { -0.587785f, 0.425325f, 0.688191f }, + { -0.850651f, 0.525731f, 0.000000f }, + { -0.864188f, 0.442863f, 0.238856f }, + { -0.716567f, 0.681718f, 0.147621f }, + { -0.688191f, 0.587785f, 0.425325f }, + { -0.500000f, 0.809017f, 0.309017f }, + { -0.238856f, 0.864188f, 0.442863f }, + { -0.425325f, 0.688191f, 0.587785f }, + { -0.716567f, 0.681718f, -0.147621f }, + { -0.500000f, 0.809017f, -0.309017f }, + { -0.525731f, 0.850651f, 0.000000f }, + { 0.000000f, 0.850651f, -0.525731f }, + { -0.238856f, 0.864188f, -0.442863f }, + { 0.000000f, 0.955423f, -0.295242f }, + { -0.262866f, 0.951056f, -0.162460f }, + { 0.000000f, 1.000000f, 0.000000f }, + { 0.000000f, 0.955423f, 0.295242f }, + { -0.262866f, 0.951056f, 0.162460f }, + { 0.238856f, 0.864188f, 0.442863f }, + { 0.262866f, 0.951056f, 0.162460f }, + { 0.500000f, 0.809017f, 0.309017f }, + { 0.238856f, 0.864188f, -0.442863f }, + { 0.262866f, 0.951056f, -0.162460f }, + { 0.500000f, 0.809017f, -0.309017f }, + { 0.850651f, 0.525731f, 0.000000f }, + { 0.716567f, 0.681718f, 0.147621f }, + { 0.716567f, 0.681718f, -0.147621f }, + { 0.525731f, 0.850651f, 0.000000f }, + { 0.425325f, 0.688191f, 0.587785f }, + { 0.864188f, 0.442863f, 0.238856f }, + { 0.688191f, 0.587785f, 0.425325f }, + { 0.809017f, 0.309017f, 0.500000f }, + { 0.681718f, 0.147621f, 0.716567f }, + { 0.587785f, 0.425325f, 0.688191f }, + { 0.955423f, 0.295242f, 0.000000f }, + { 1.000000f, 0.000000f, 0.000000f }, + { 0.951056f, 0.162460f, 0.262866f }, + { 0.850651f, -0.525731f, 0.000000f }, + { 0.955423f, -0.295242f, 0.000000f }, + { 0.864188f, -0.442863f, 0.238856f }, + { 0.951056f, -0.162460f, 0.262866f }, + { 0.809017f, -0.309017f, 0.500000f }, + { 0.681718f, -0.147621f, 0.716567f }, + { 0.850651f, 0.000000f, 0.525731f }, + { 0.864188f, 0.442863f, -0.238856f }, + { 0.809017f, 0.309017f, -0.500000f }, + { 0.951056f, 0.162460f, -0.262866f }, + { 0.525731f, 0.000000f, -0.850651f }, + { 0.681718f, 0.147621f, -0.716567f }, + { 0.681718f, -0.147621f, -0.716567f }, + { 0.850651f, 0.000000f, -0.525731f }, + { 0.809017f, -0.309017f, -0.500000f }, + { 0.864188f, -0.442863f, -0.238856f }, + { 0.951056f, -0.162460f, -0.262866f }, + { 0.147621f, 0.716567f, -0.681718f }, + { 0.309017f, 0.500000f, -0.809017f }, + { 0.425325f, 0.688191f, -0.587785f }, + { 0.442863f, 0.238856f, -0.864188f }, + { 0.587785f, 0.425325f, -0.688191f }, + { 0.688191f, 0.587785f, -0.425325f }, + { -0.147621f, 0.716567f, -0.681718f }, + { -0.309017f, 0.500000f, -0.809017f }, + { 0.000000f, 0.525731f, -0.850651f }, + { -0.525731f, 0.000000f, -0.850651f }, + { -0.442863f, 0.238856f, -0.864188f }, + { -0.295242f, 0.000000f, -0.955423f }, + { -0.162460f, 0.262866f, -0.951056f }, + { 0.000000f, 0.000000f, -1.000000f }, + { 0.295242f, 0.000000f, -0.955423f }, + { 0.162460f, 0.262866f, -0.951056f }, + { -0.442863f, -0.238856f, -0.864188f }, + { -0.309017f, -0.500000f, -0.809017f }, + { -0.162460f, -0.262866f, -0.951056f }, + { 0.000000f, -0.850651f, -0.525731f }, + { -0.147621f, -0.716567f, -0.681718f }, + { 0.147621f, -0.716567f, -0.681718f }, + { 0.000000f, -0.525731f, -0.850651f }, + { 0.309017f, -0.500000f, -0.809017f }, + { 0.442863f, -0.238856f, -0.864188f }, + { 0.162460f, -0.262866f, -0.951056f }, + { 0.238856f, -0.864188f, -0.442863f }, + { 0.500000f, -0.809017f, -0.309017f }, + { 0.425325f, -0.688191f, -0.587785f }, + { 0.716567f, -0.681718f, -0.147621f }, + { 0.688191f, -0.587785f, -0.425325f }, + { 0.587785f, -0.425325f, -0.688191f }, + { 0.000000f, -0.955423f, -0.295242f }, + { 0.000000f, -1.000000f, 0.000000f }, + { 0.262866f, -0.951056f, -0.162460f }, + { 0.000000f, -0.850651f, 0.525731f }, + { 0.000000f, -0.955423f, 0.295242f }, + { 0.238856f, -0.864188f, 0.442863f }, + { 0.262866f, -0.951056f, 0.162460f }, + { 0.500000f, -0.809017f, 0.309017f }, + { 0.716567f, -0.681718f, 0.147621f }, + { 0.525731f, -0.850651f, 0.000000f }, + { -0.238856f, -0.864188f, -0.442863f }, + { -0.500000f, -0.809017f, -0.309017f }, + { -0.262866f, -0.951056f, -0.162460f }, + { -0.850651f, -0.525731f, 0.000000f }, + { -0.716567f, -0.681718f, -0.147621f }, + { -0.716567f, -0.681718f, 0.147621f }, + { -0.525731f, -0.850651f, 0.000000f }, + { -0.500000f, -0.809017f, 0.309017f }, + { -0.238856f, -0.864188f, 0.442863f }, + { -0.262866f, -0.951056f, 0.162460f }, + { -0.864188f, -0.442863f, 0.238856f }, + { -0.809017f, -0.309017f, 0.500000f }, + { -0.688191f, -0.587785f, 0.425325f }, + { -0.681718f, -0.147621f, 0.716567f }, + { -0.442863f, -0.238856f, 0.864188f }, + { -0.587785f, -0.425325f, 0.688191f }, + { -0.309017f, -0.500000f, 0.809017f }, + { -0.147621f, -0.716567f, 0.681718f }, + { -0.425325f, -0.688191f, 0.587785f }, + { -0.162460f, -0.262866f, 0.951056f }, + { 0.442863f, -0.238856f, 0.864188f }, + { 0.162460f, -0.262866f, 0.951056f }, + { 0.309017f, -0.500000f, 0.809017f }, + { 0.147621f, -0.716567f, 0.681718f }, + { 0.000000f, -0.525731f, 0.850651f }, + { 0.425325f, -0.688191f, 0.587785f }, + { 0.587785f, -0.425325f, 0.688191f }, + { 0.688191f, -0.587785f, 0.425325f }, + { -0.955423f, 0.295242f, 0.000000f }, + { -0.951056f, 0.162460f, 0.262866f }, + { -1.000000f, 0.000000f, 0.000000f }, + { -0.850651f, 0.000000f, 0.525731f }, + { -0.955423f, -0.295242f, 0.000000f }, + { -0.951056f, -0.162460f, 0.262866f }, + { -0.864188f, 0.442863f, -0.238856f }, + { -0.951056f, 0.162460f, -0.262866f }, + { -0.809017f, 0.309017f, -0.500000f }, + { -0.864188f, -0.442863f, -0.238856f }, + { -0.951056f, -0.162460f, -0.262866f }, + { -0.809017f, -0.309017f, -0.500000f }, + { -0.681718f, 0.147621f, -0.716567f }, + { -0.681718f, -0.147621f, -0.716567f }, + { -0.850651f, 0.000000f, -0.525731f }, + { -0.688191f, 0.587785f, -0.425325f }, + { -0.587785f, 0.425325f, -0.688191f }, + { -0.425325f, 0.688191f, -0.587785f }, + { -0.425325f, -0.688191f, -0.587785f }, + { -0.587785f, -0.425325f, -0.688191f }, + { -0.688191f, -0.587785f, -0.425325f }, +}; + + +// _md2_canload() + +static int _md2_canload( PM_PARAMS_CANLOAD ) +{ + md2_t *md2; + + /* to keep the compiler happy */ + *fileName = *fileName; + + /* sanity check */ + if( bufSize < ( sizeof( *md2 ) * 2) ) + return PICO_PMV_ERROR_SIZE; + + /* set as md2 */ + md2 = (md2_t*) buffer; + + /* check md2 magic */ + if( *((int*) md2->magic) != *((int*) MD2_MAGIC) ) + return PICO_PMV_ERROR_IDENT; + + /* check md2 version */ + if( _pico_little_long( md2->version ) != MD2_VERSION ) + return PICO_PMV_ERROR_VERSION; + + /* file seems to be a valid md2 */ + return PICO_PMV_OK; +} + + + +// _md2_load() loads a quake2 md2 model file. + + +static picoModel_t *_md2_load( PM_PARAMS_LOAD ) +{ + int i, j, dups, dup_index; + short tot_numVerts; + index_LUT_t *p_index_LUT, *p_index_LUT2, *p_index_LUT3; + index_DUP_LUT_t *p_index_LUT_DUPS; + md2Triangle_t *p_md2Triangle; + + char skinname[ MD2_MAX_SKINNAME ]; + md2_t *md2; + md2St_t *texCoord; + md2Frame_t *frame; + md2Triangle_t *triangle; + md2XyzNormal_t *vertex; + + picoByte_t *bb; + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + /* set as md2 */ + bb = (picoByte_t*) buffer; + md2 = (md2_t*) buffer; + + /* check ident and version */ + if( *((int*) md2->magic) != *((int*) MD2_MAGIC) || _pico_little_long( md2->version ) != MD2_VERSION ) + { + /* not an md2 file (todo: set error) */ + _pico_printf( PICO_ERROR, "%s is not an MD2 File!", fileName ); + return NULL; + } + + // swap md2 + md2->version = _pico_little_long( md2->version ); + + md2->skinWidth = _pico_little_long( md2->skinWidth ); + md2->skinHeight = _pico_little_long( md2->skinHeight ); + md2->frameSize = _pico_little_long( md2->frameSize ); + + md2->numSkins = _pico_little_long( md2->numSkins ); + md2->numXYZ = _pico_little_long( md2->numXYZ ); + md2->numST = _pico_little_long( md2->numST ); + md2->numTris = _pico_little_long( md2->numTris ); + md2->numGLCmds = _pico_little_long( md2->numGLCmds ); + md2->numFrames = _pico_little_long( md2->numFrames ); + + md2->ofsSkins = _pico_little_long( md2->ofsSkins ); + md2->ofsST = _pico_little_long( md2->ofsST ); + md2->ofsTris = _pico_little_long( md2->ofsTris ); + md2->ofsFrames = _pico_little_long( md2->ofsFrames ); + md2->ofsGLCmds = _pico_little_long( md2->ofsGLCmds ); + md2->ofsEnd = _pico_little_long( md2->ofsEnd ); + + // do frame check + if( md2->numFrames < 1 ) + { + _pico_printf( PICO_ERROR, "%s has 0 frames!", fileName ); + return NULL; + } + + if( frameNum < 0 || frameNum >= md2->numFrames ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range MD2 frame specified" ); + return NULL; + } + + // Setup Frame + frame = (md2Frame_t *) (bb + md2->ofsFrames + (sizeof(md2Frame_t) * frameNum)); + + // swap frame scale and translation + for( i = 0; i < 3; i++ ) + { + frame->scale[ i ] = _pico_little_float( frame->scale[ i ] ); + frame->translate[ i ] = _pico_little_float( frame->translate[ i ] ); + } + + // swap triangles + triangle = (md2Triangle_t *) ((picoByte_t *) (bb + md2->ofsTris) ); + for( i = 0; i < md2->numTris; i++, triangle++ ) + { + for( j = 0; j < 3; j++ ) + { + triangle->index_xyz[ j ] = _pico_little_short( triangle->index_xyz[ j ] ); + triangle->index_st[ j ] = _pico_little_short( triangle->index_st[ j ] ); + } + } + + // swap st coords + texCoord = (md2St_t*) ((picoByte_t *) (bb + md2->ofsST) ); + for( i = 0; i < md2->numST; i++, texCoord++ ) + { + texCoord->s = _pico_little_short( texCoord->s ); + texCoord->t = _pico_little_short( texCoord->t ); + } + + // set Skin Name + strncpy(skinname, (bb + md2->ofsSkins), MD2_MAX_SKINNAME ); + + // Print out md2 values + _pico_printf(PICO_VERBOSE,"Skins: %d Verts: %d STs: %d Triangles: %d Frames: %d\nSkin Name \"%s\"\n", md2->numSkins, md2->numXYZ, md2->numST, md2->numTris, md2->numFrames, &skinname ); + + // detox Skin name + _pico_setfext( skinname, "" ); + _pico_unixify( skinname ); + + /* create new pico model */ + picoModel = PicoNewModel(); + if( picoModel == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, md2->numFrames ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + // allocate new pico surface + picoSurface = PicoNewSurface( picoModel ); + if( picoSurface == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); + return NULL; + } + + + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( picoSurface, frame->name ); + picoShader = PicoNewShader( picoModel ); + if( picoShader == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + return NULL; + } + + PicoSetShaderName( picoShader, skinname ); + + // associate current surface with newly created shader + PicoSetSurfaceShader( picoSurface, picoShader ); + + // Init LUT for Verts + p_index_LUT = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t) * md2->numXYZ); + for(i=0; inumXYZ; i++) + { + p_index_LUT[i].Vert = -1; + p_index_LUT[i].ST = -1; + p_index_LUT[i].next = NULL; + } + + // Fill in Look Up Table, and allocate/fill Linked List from vert array as needed for dup STs per Vert. + tot_numVerts = md2->numXYZ; + dups = 0; + for(i=0; inumTris; i++) + { + p_md2Triangle = (md2Triangle_t *) ( bb + md2->ofsTris + (sizeof(md2Triangle_t)*i)); + for(j=0; j<3; j++) + { + if (p_index_LUT[p_md2Triangle->index_xyz[j]].ST == -1) // No Main Entry + p_index_LUT[p_md2Triangle->index_xyz[j]].ST = p_md2Triangle->index_st[j]; + + else if (p_md2Triangle->index_st[j] == p_index_LUT[p_md2Triangle->index_xyz[j]].ST ) // Equal to Main Entry + continue; + + else if ( (p_index_LUT[p_md2Triangle->index_xyz[j]].next == NULL) ) // Not equal to Main entry, and no LL entry + { // Add first entry of LL from Main + p_index_LUT2 = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t)); + if (p_index_LUT2 == NULL) + _pico_printf( PICO_ERROR," Couldn't allocate memory!\n"); + p_index_LUT[p_md2Triangle->index_xyz[j]].next = (index_LUT_t *)p_index_LUT2; + p_index_LUT2->Vert = dups; + p_index_LUT2->ST = p_md2Triangle->index_st[j]; + p_index_LUT2->next = NULL; + p_md2Triangle->index_xyz[j] = dups + md2->numXYZ; // Make change in Tri hunk + dups++; + } + else // Try to find in LL from Main Entry + { + p_index_LUT3 = p_index_LUT2 = p_index_LUT[p_md2Triangle->index_xyz[j]].next; + while ( (p_index_LUT2 != NULL) && (p_md2Triangle->index_xyz[j] != p_index_LUT2->Vert) ) // Walk down LL + { + p_index_LUT3 = p_index_LUT2; + p_index_LUT2 = p_index_LUT2->next; + } + p_index_LUT2 = p_index_LUT3; + + if ( p_md2Triangle->index_st[j] == p_index_LUT2->ST ) // Found it + { + p_md2Triangle->index_xyz[j] = p_index_LUT2->Vert + md2->numXYZ; // Make change in Tri hunk + continue; + } + + if ( p_index_LUT2->next == NULL) // Didn't find it. Add entry to LL. + { + // Add the Entry + p_index_LUT3 = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t)); + if (p_index_LUT3 == NULL) + _pico_printf( PICO_ERROR," Couldn't allocate memory!\n"); + p_index_LUT2->next = (index_LUT_t *)p_index_LUT3; + p_index_LUT3->Vert = p_md2Triangle->index_xyz[j]; + p_index_LUT3->ST = p_md2Triangle->index_st[j]; + p_index_LUT3->next = NULL; + p_md2Triangle->index_xyz[j] = dups + md2->numXYZ; // Make change in Tri hunk + dups++; + } + } + } + } + + // malloc and build array for Dup STs + p_index_LUT_DUPS = (index_DUP_LUT_t *)_pico_alloc(sizeof(index_DUP_LUT_t) * dups); + if (p_index_LUT_DUPS == NULL) + _pico_printf( PICO_ERROR," Couldn't allocate memory!\n"); + + dup_index = 0; + for(i=0; inumXYZ; i++) + { + p_index_LUT2 = p_index_LUT[i].next; + while (p_index_LUT2 != NULL) + { + p_index_LUT_DUPS[p_index_LUT2->Vert].OldVert = i; + p_index_LUT_DUPS[p_index_LUT2->Vert].ST = p_index_LUT2->ST; + dup_index++; + p_index_LUT2 = p_index_LUT2->next; + } + } + + // Build Picomodel + triangle = (md2Triangle_t *) ((picoByte_t *) (bb + md2->ofsTris) ); + texCoord = (md2St_t*) ((picoByte_t *) (bb + md2->ofsST) ); + vertex = (md2XyzNormal_t*) ((picoByte_t*) (frame->verts) ); + for( j = 0; j < md2->numTris; j++, triangle++ ) + { + PicoSetSurfaceIndex( picoSurface, j*3 , triangle->index_xyz[0] ); + PicoSetSurfaceIndex( picoSurface, j*3+1 , triangle->index_xyz[1] ); + PicoSetSurfaceIndex( picoSurface, j*3+2 , triangle->index_xyz[2] ); + } + + for(i=0; i< md2->numXYZ; i++, vertex++) + { + /* set vertex origin */ + xyz[ 0 ] = vertex->v[0] * frame->scale[0] + frame->translate[0]; + xyz[ 1 ] = vertex->v[1] * frame->scale[1] + frame->translate[1]; + xyz[ 2 ] = vertex->v[2] * frame->scale[2] + frame->translate[2]; + PicoSetSurfaceXYZ( picoSurface, i , xyz ); + + /* set normal */ + normal[ 0 ] = md2_normals[vertex->lightnormalindex][0]; + normal[ 1 ] = md2_normals[vertex->lightnormalindex][1]; + normal[ 2 ] = md2_normals[vertex->lightnormalindex][2]; + PicoSetSurfaceNormal( picoSurface, i , normal ); + + /* set st coords */ + st[ 0 ] = ((texCoord[p_index_LUT[i].ST].s) / ((float)md2->skinWidth)); + st[ 1 ] = (texCoord[p_index_LUT[i].ST].t / ((float)md2->skinHeight)); + PicoSetSurfaceST( picoSurface, 0, i , st ); + } + + if (dups) + { + for(i=0; iverts[j].v[0] * frame->scale[0] + frame->translate[0]; + xyz[ 1 ] = frame->verts[j].v[1] * frame->scale[1] + frame->translate[1]; + xyz[ 2 ] = frame->verts[j].v[2] * frame->scale[2] + frame->translate[2]; + PicoSetSurfaceXYZ( picoSurface, i + md2->numXYZ , xyz ); + + /* set normal */ + normal[ 0 ] = md2_normals[frame->verts[j].lightnormalindex][0]; + normal[ 1 ] = md2_normals[frame->verts[j].lightnormalindex][1]; + normal[ 2 ] = md2_normals[frame->verts[j].lightnormalindex][2]; + PicoSetSurfaceNormal( picoSurface, i + md2->numXYZ , normal ); + + /* set st coords */ + st[ 0 ] = ((texCoord[p_index_LUT_DUPS[i].ST].s) / ((float)md2->skinWidth)); + st[ 1 ] = (texCoord[p_index_LUT_DUPS[i].ST].t / ((float)md2->skinHeight)); + PicoSetSurfaceST( picoSurface, 0, i + md2->numXYZ , st ); + } + } + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, 0, color ); + + // Free up malloc'ed LL entries + for(i=0; inumXYZ; i++) + { + if(p_index_LUT[i].next != NULL) + { + p_index_LUT2 = p_index_LUT[i].next; + do { + p_index_LUT3 = p_index_LUT2->next; + _pico_free(p_index_LUT2); + p_index_LUT2 = p_index_LUT3; + dups--; + } while (p_index_LUT2 != NULL); + } + } + + if (dups) + _pico_printf(PICO_WARNING, " Not all LL mallocs freed\n"); + + // Free malloc'ed LUTs + _pico_free(p_index_LUT); + _pico_free(p_index_LUT_DUPS); + + /* return the new pico model */ + return picoModel; + +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleMD2 = +{ + "0.875", /* module version string */ + "Quake 2 MD2", /* module display name */ + "Nurail", /* author's name */ + "2003 Nurail", /* module copyright */ + { + "md2", NULL, NULL, NULL /* default extensions to use */ + }, + _md2_canload, /* validation routine */ + _md2_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/tools/urt/libs/picomodel/pm_md3.c b/tools/urt/libs/picomodel/pm_md3.c new file mode 100644 index 00000000..6d87469d --- /dev/null +++ b/tools/urt/libs/picomodel/pm_md3.c @@ -0,0 +1,425 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PM_MD3_C + + + +/* dependencies */ +#include "picointernal.h" + + + +/* md3 model format */ +#define MD3_MAGIC "IDP3" +#define MD3_VERSION 15 + +/* md3 vertex scale */ +#define MD3_SCALE (1.0f / 64.0f) + +/* md3 model frame information */ +typedef struct md3Frame_s +{ + float bounds[ 2 ][ 3 ]; + float localOrigin[ 3 ]; + float radius; + char creator[ 16 ]; +} +md3Frame_t; + +/* md3 model tag information */ +typedef struct md3Tag_s +{ + char name[ 64 ]; + float origin[ 3 ]; + float axis[ 3 ][ 3 ]; +} +md3Tag_t; + +/* md3 surface md3 (one object mesh) */ +typedef struct md3Surface_s +{ + char magic[ 4 ]; + char name[ 64 ]; /* polyset name */ + int flags; + int numFrames; /* all model surfaces should have the same */ + int numShaders; /* all model surfaces should have the same */ + int numVerts; + int numTriangles; + int ofsTriangles; + int ofsShaders; /* offset from start of md3Surface_t */ + int ofsSt; /* texture coords are common for all frames */ + int ofsVertexes; /* numVerts * numFrames */ + int ofsEnd; /* next surface follows */ +} +md3Surface_t; + +typedef struct md3Shader_s +{ + char name[ 64 ]; + int shaderIndex; /* for ingame use */ +} +md3Shader_t; + +typedef struct md3Triangle_s +{ + int indexes[ 3 ]; +} +md3Triangle_t; + +typedef struct md3TexCoord_s +{ + float st[ 2 ]; +} +md3TexCoord_t; + +typedef struct md3Vertex_s +{ + short xyz[ 3 ]; + short normal; +} +md3Vertex_t; + + +/* md3 model file md3 structure */ +typedef struct md3_s +{ + char magic[ 4 ]; /* MD3_MAGIC */ + int version; + char name[ 64 ]; /* model name */ + int flags; + int numFrames; + int numTags; + int numSurfaces; + int numSkins; /* number of skins for the mesh */ + int ofsFrames; /* offset for first frame */ + int ofsTags; /* numFrames * numTags */ + int ofsSurfaces; /* first surface, others follow */ + int ofsEnd; /* end of file */ +} +md3_t; + + + + +/* +_md3_canload() +validates a quake3 arena md3 model file. btw, i use the +preceding underscore cause it's a static func referenced +by one structure only. +*/ + +static int _md3_canload( PM_PARAMS_CANLOAD ) +{ + md3_t *md3; + + + /* to keep the compiler happy */ + *fileName = *fileName; + + /* sanity check */ + if( bufSize < ( sizeof( *md3 ) * 2) ) + return PICO_PMV_ERROR_SIZE; + + /* set as md3 */ + md3 = (md3_t*) buffer; + + /* check md3 magic */ + if( *((int*) md3->magic) != *((int*) MD3_MAGIC) ) + return PICO_PMV_ERROR_IDENT; + + /* check md3 version */ + if( _pico_little_long( md3->version ) != MD3_VERSION ) + return PICO_PMV_ERROR_VERSION; + + /* file seems to be a valid md3 */ + return PICO_PMV_OK; +} + + + +/* +_md3_load() +loads a quake3 arena md3 model file. +*/ + +static picoModel_t *_md3_load( PM_PARAMS_LOAD ) +{ + int i, j; + picoByte_t *bb; + md3_t *md3; + md3Surface_t *surface; + md3Shader_t *shader; + md3TexCoord_t *texCoord; + md3Frame_t *frame; + md3Triangle_t *triangle; + md3Vertex_t *vertex; + double lat, lng; + + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + /* ------------------------------------------------- + md3 loading + ------------------------------------------------- */ + + + /* set as md3 */ + bb = (picoByte_t*) buffer; + md3 = (md3_t*) buffer; + + /* check ident and version */ + if( *((int*) md3->magic) != *((int*) MD3_MAGIC) || _pico_little_long( md3->version ) != MD3_VERSION ) + { + /* not an md3 file (todo: set error) */ + return NULL; + } + + /* swap md3; sea: swaps fixed */ + md3->version = _pico_little_long( md3->version ); + md3->numFrames = _pico_little_long( md3->numFrames ); + md3->numTags = _pico_little_long( md3->numTags ); + md3->numSurfaces = _pico_little_long( md3->numSurfaces ); + md3->numSkins = _pico_little_long( md3->numSkins ); + md3->ofsFrames = _pico_little_long( md3->ofsFrames ); + md3->ofsTags = _pico_little_long( md3->ofsTags ); + md3->ofsSurfaces = _pico_little_long( md3->ofsSurfaces ); + md3->ofsEnd = _pico_little_long( md3->ofsEnd ); + + /* do frame check */ + if( md3->numFrames < 1 ) + { + _pico_printf( PICO_ERROR, "MD3 with 0 frames" ); + return NULL; + } + + if( frameNum < 0 || frameNum >= md3->numFrames ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range MD3 frame specified" ); + return NULL; + } + + /* swap frames */ + frame = (md3Frame_t*) (bb + md3->ofsFrames ); + for( i = 0; i < md3->numFrames; i++, frame++ ) + { + frame->radius = _pico_little_float( frame->radius ); + for( j = 0; j < 3; j++ ) + { + frame->bounds[ 0 ][ j ] = _pico_little_float( frame->bounds[ 0 ][ j ] ); + frame->bounds[ 1 ][ j ] = _pico_little_float( frame->bounds[ 1 ][ j ] ); + frame->localOrigin[ j ] = _pico_little_float( frame->localOrigin[ j ] ); + } + } + + /* swap surfaces */ + surface = (md3Surface_t*) (bb + md3->ofsSurfaces); + for( i = 0; i < md3->numSurfaces; i++ ) + { + /* swap surface md3; sea: swaps fixed */ + surface->flags = _pico_little_long( surface->flags ); + surface->numFrames = _pico_little_long( surface->numFrames ); + surface->numShaders = _pico_little_long( surface->numShaders ); + surface->numTriangles = _pico_little_long( surface->numTriangles ); + surface->ofsTriangles = _pico_little_long( surface->ofsTriangles ); + surface->numVerts = _pico_little_long( surface->numVerts ); + surface->ofsShaders = _pico_little_long( surface->ofsShaders ); + surface->ofsSt = _pico_little_long( surface->ofsSt ); + surface->ofsVertexes = _pico_little_long( surface->ofsVertexes ); + surface->ofsEnd = _pico_little_long( surface->ofsEnd ); + + /* swap triangles */ + triangle = (md3Triangle_t*) ((picoByte_t*) surface + surface->ofsTriangles); + for( j = 0; j < surface->numTriangles; j++, triangle++ ) + { + /* sea: swaps fixed */ + triangle->indexes[ 0 ] = _pico_little_long( triangle->indexes[ 0 ] ); + triangle->indexes[ 1 ] = _pico_little_long( triangle->indexes[ 1 ] ); + triangle->indexes[ 2 ] = _pico_little_long( triangle->indexes[ 2 ] ); + } + + /* swap st coords */ + texCoord = (md3TexCoord_t*) ((picoByte_t*) surface + surface->ofsSt); + for( j = 0; j < surface->numVerts; j++, texCoord++ ) + { + texCoord->st[ 0 ] = _pico_little_float( texCoord->st[ 0 ] ); + texCoord->st[ 1 ] = _pico_little_float( texCoord->st[ 1 ] ); + } + + /* swap xyz/normals */ + vertex = (md3Vertex_t*) ((picoByte_t*) surface + surface->ofsVertexes); + for( j = 0; j < (surface->numVerts * surface->numFrames); j++, vertex++) + { + vertex->xyz[ 0 ] = _pico_little_short( vertex->xyz[ 0 ] ); + vertex->xyz[ 1 ] = _pico_little_short( vertex->xyz[ 1 ] ); + vertex->xyz[ 2 ] = _pico_little_short( vertex->xyz[ 2 ] ); + vertex->normal = _pico_little_short( vertex->normal ); + } + + /* get next surface */ + surface = (md3Surface_t*) ((picoByte_t*) surface + surface->ofsEnd); + } + + /* ------------------------------------------------- + pico model creation + ------------------------------------------------- */ + + /* create new pico model */ + picoModel = PicoNewModel(); + if( picoModel == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, md3->numFrames ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + /* md3 surfaces become picomodel surfaces */ + surface = (md3Surface_t*) (bb + md3->ofsSurfaces); + + /* run through md3 surfaces */ + for( i = 0; i < md3->numSurfaces; i++ ) + { + /* allocate new pico surface */ + picoSurface = PicoNewSurface( picoModel ); + if( picoSurface == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); /* sea */ + return NULL; + } + + /* md3 model surfaces are all triangle meshes */ + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( picoSurface, surface->name ); + + /* create new pico shader -sea */ + picoShader = PicoNewShader( picoModel ); + if( picoShader == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + return NULL; + } + + /* detox and set shader name */ + shader = (md3Shader_t*) ((picoByte_t*) surface + surface->ofsShaders); + _pico_setfext( shader->name, "" ); + _pico_unixify( shader->name ); + PicoSetShaderName( picoShader, shader->name ); + + /* associate current surface with newly created shader */ + PicoSetSurfaceShader( picoSurface, picoShader ); + + /* copy indexes */ + triangle = (md3Triangle_t *) ((picoByte_t*) surface + surface->ofsTriangles); + + for( j = 0; j < surface->numTriangles; j++, triangle++ ) + { + PicoSetSurfaceIndex( picoSurface, (j * 3 + 0), (picoIndex_t) triangle->indexes[ 0 ] ); + PicoSetSurfaceIndex( picoSurface, (j * 3 + 1), (picoIndex_t) triangle->indexes[ 1 ] ); + PicoSetSurfaceIndex( picoSurface, (j * 3 + 2), (picoIndex_t) triangle->indexes[ 2 ] ); + } + + /* copy vertexes */ + texCoord = (md3TexCoord_t*) ((picoByte_t *) surface + surface->ofsSt); + vertex = (md3Vertex_t*) ((picoByte_t*) surface + surface->ofsVertexes + surface->numVerts * frameNum * sizeof( md3Vertex_t ) ); + _pico_set_color( color, 255, 255, 255, 255 ); + + for( j = 0; j < surface->numVerts; j++, texCoord++, vertex++ ) + { + /* set vertex origin */ + xyz[ 0 ] = MD3_SCALE * vertex->xyz[ 0 ]; + xyz[ 1 ] = MD3_SCALE * vertex->xyz[ 1 ]; + xyz[ 2 ] = MD3_SCALE * vertex->xyz[ 2 ]; + PicoSetSurfaceXYZ( picoSurface, j, xyz ); + + /* decode lat/lng normal to 3 float normal */ + lat = (float) ((vertex->normal >> 8) & 0xff); + lng = (float) (vertex->normal & 0xff); + lat *= PICO_PI / 128; + lng *= PICO_PI / 128; + normal[ 0 ] = (picoVec_t) cos( lat ) * (picoVec_t) sin( lng ); + normal[ 1 ] = (picoVec_t) sin( lat ) * (picoVec_t) sin( lng ); + normal[ 2 ] = (picoVec_t) cos( lng ); + PicoSetSurfaceNormal( picoSurface, j, normal ); + + /* set st coords */ + st[ 0 ] = texCoord->st[ 0 ]; + st[ 1 ] = texCoord->st[ 1 ]; + PicoSetSurfaceST( picoSurface, 0, j, st ); + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, j, color ); + } + + /* get next surface */ + surface = (md3Surface_t*) ((picoByte_t*) surface + surface->ofsEnd); + } + + /* return the new pico model */ + return picoModel; +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleMD3 = +{ + "1.3", /* module version string */ + "Quake 3 Arena", /* module display name */ + "Randy Reddig", /* author's name */ + "2002 Randy Reddig", /* module copyright */ + { + "md3", NULL, NULL, NULL /* default extensions to use */ + }, + _md3_canload, /* validation routine */ + _md3_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/tools/urt/libs/picomodel/pm_mdc.c b/tools/urt/libs/picomodel/pm_mdc.c new file mode 100644 index 00000000..3036d112 --- /dev/null +++ b/tools/urt/libs/picomodel/pm_mdc.c @@ -0,0 +1,750 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PM_MDC_C + + + +/* dependencies */ +#include "picointernal.h" + +/* mdc model format */ +#define MDC_MAGIC "IDPC" +#define MDC_VERSION 2 + +/* mdc vertex scale */ +#define MDC_SCALE (1.0f / 64.0f) +#define MDC_MAX_OFS 127.0f +#define MDC_DIST_SCALE 0.05f + +/* mdc decoding normal table */ +double mdcNormals[ 256 ][ 3 ] = +{ + { 1.000000, 0.000000, 0.000000 }, + { 0.980785, 0.195090, 0.000000 }, + { 0.923880, 0.382683, 0.000000 }, + { 0.831470, 0.555570, 0.000000 }, + { 0.707107, 0.707107, 0.000000 }, + { 0.555570, 0.831470, 0.000000 }, + { 0.382683, 0.923880, 0.000000 }, + { 0.195090, 0.980785, 0.000000 }, + { -0.000000, 1.000000, 0.000000 }, + { -0.195090, 0.980785, 0.000000 }, + { -0.382683, 0.923880, 0.000000 }, + { -0.555570, 0.831470, 0.000000 }, + { -0.707107, 0.707107, 0.000000 }, + { -0.831470, 0.555570, 0.000000 }, + { -0.923880, 0.382683, 0.000000 }, + { -0.980785, 0.195090, 0.000000 }, + { -1.000000, -0.000000, 0.000000 }, + { -0.980785, -0.195090, 0.000000 }, + { -0.923880, -0.382683, 0.000000 }, + { -0.831470, -0.555570, 0.000000 }, + { -0.707107, -0.707107, 0.000000 }, + { -0.555570, -0.831469, 0.000000 }, + { -0.382684, -0.923880, 0.000000 }, + { -0.195090, -0.980785, 0.000000 }, + { 0.000000, -1.000000, 0.000000 }, + { 0.195090, -0.980785, 0.000000 }, + { 0.382684, -0.923879, 0.000000 }, + { 0.555570, -0.831470, 0.000000 }, + { 0.707107, -0.707107, 0.000000 }, + { 0.831470, -0.555570, 0.000000 }, + { 0.923880, -0.382683, 0.000000 }, + { 0.980785, -0.195090, 0.000000 }, + { 0.980785, 0.000000, -0.195090 }, + { 0.956195, 0.218245, -0.195090 }, + { 0.883657, 0.425547, -0.195090 }, + { 0.766809, 0.611510, -0.195090 }, + { 0.611510, 0.766809, -0.195090 }, + { 0.425547, 0.883657, -0.195090 }, + { 0.218245, 0.956195, -0.195090 }, + { -0.000000, 0.980785, -0.195090 }, + { -0.218245, 0.956195, -0.195090 }, + { -0.425547, 0.883657, -0.195090 }, + { -0.611510, 0.766809, -0.195090 }, + { -0.766809, 0.611510, -0.195090 }, + { -0.883657, 0.425547, -0.195090 }, + { -0.956195, 0.218245, -0.195090 }, + { -0.980785, -0.000000, -0.195090 }, + { -0.956195, -0.218245, -0.195090 }, + { -0.883657, -0.425547, -0.195090 }, + { -0.766809, -0.611510, -0.195090 }, + { -0.611510, -0.766809, -0.195090 }, + { -0.425547, -0.883657, -0.195090 }, + { -0.218245, -0.956195, -0.195090 }, + { 0.000000, -0.980785, -0.195090 }, + { 0.218245, -0.956195, -0.195090 }, + { 0.425547, -0.883657, -0.195090 }, + { 0.611510, -0.766809, -0.195090 }, + { 0.766809, -0.611510, -0.195090 }, + { 0.883657, -0.425547, -0.195090 }, + { 0.956195, -0.218245, -0.195090 }, + { 0.923880, 0.000000, -0.382683 }, + { 0.892399, 0.239118, -0.382683 }, + { 0.800103, 0.461940, -0.382683 }, + { 0.653281, 0.653281, -0.382683 }, + { 0.461940, 0.800103, -0.382683 }, + { 0.239118, 0.892399, -0.382683 }, + { -0.000000, 0.923880, -0.382683 }, + { -0.239118, 0.892399, -0.382683 }, + { -0.461940, 0.800103, -0.382683 }, + { -0.653281, 0.653281, -0.382683 }, + { -0.800103, 0.461940, -0.382683 }, + { -0.892399, 0.239118, -0.382683 }, + { -0.923880, -0.000000, -0.382683 }, + { -0.892399, -0.239118, -0.382683 }, + { -0.800103, -0.461940, -0.382683 }, + { -0.653282, -0.653281, -0.382683 }, + { -0.461940, -0.800103, -0.382683 }, + { -0.239118, -0.892399, -0.382683 }, + { 0.000000, -0.923880, -0.382683 }, + { 0.239118, -0.892399, -0.382683 }, + { 0.461940, -0.800103, -0.382683 }, + { 0.653281, -0.653282, -0.382683 }, + { 0.800103, -0.461940, -0.382683 }, + { 0.892399, -0.239117, -0.382683 }, + { 0.831470, 0.000000, -0.555570 }, + { 0.790775, 0.256938, -0.555570 }, + { 0.672673, 0.488726, -0.555570 }, + { 0.488726, 0.672673, -0.555570 }, + { 0.256938, 0.790775, -0.555570 }, + { -0.000000, 0.831470, -0.555570 }, + { -0.256938, 0.790775, -0.555570 }, + { -0.488726, 0.672673, -0.555570 }, + { -0.672673, 0.488726, -0.555570 }, + { -0.790775, 0.256938, -0.555570 }, + { -0.831470, -0.000000, -0.555570 }, + { -0.790775, -0.256938, -0.555570 }, + { -0.672673, -0.488726, -0.555570 }, + { -0.488725, -0.672673, -0.555570 }, + { -0.256938, -0.790775, -0.555570 }, + { 0.000000, -0.831470, -0.555570 }, + { 0.256938, -0.790775, -0.555570 }, + { 0.488725, -0.672673, -0.555570 }, + { 0.672673, -0.488726, -0.555570 }, + { 0.790775, -0.256938, -0.555570 }, + { 0.707107, 0.000000, -0.707107 }, + { 0.653281, 0.270598, -0.707107 }, + { 0.500000, 0.500000, -0.707107 }, + { 0.270598, 0.653281, -0.707107 }, + { -0.000000, 0.707107, -0.707107 }, + { -0.270598, 0.653282, -0.707107 }, + { -0.500000, 0.500000, -0.707107 }, + { -0.653281, 0.270598, -0.707107 }, + { -0.707107, -0.000000, -0.707107 }, + { -0.653281, -0.270598, -0.707107 }, + { -0.500000, -0.500000, -0.707107 }, + { -0.270598, -0.653281, -0.707107 }, + { 0.000000, -0.707107, -0.707107 }, + { 0.270598, -0.653281, -0.707107 }, + { 0.500000, -0.500000, -0.707107 }, + { 0.653282, -0.270598, -0.707107 }, + { 0.555570, 0.000000, -0.831470 }, + { 0.481138, 0.277785, -0.831470 }, + { 0.277785, 0.481138, -0.831470 }, + { -0.000000, 0.555570, -0.831470 }, + { -0.277785, 0.481138, -0.831470 }, + { -0.481138, 0.277785, -0.831470 }, + { -0.555570, -0.000000, -0.831470 }, + { -0.481138, -0.277785, -0.831470 }, + { -0.277785, -0.481138, -0.831470 }, + { 0.000000, -0.555570, -0.831470 }, + { 0.277785, -0.481138, -0.831470 }, + { 0.481138, -0.277785, -0.831470 }, + { 0.382683, 0.000000, -0.923880 }, + { 0.270598, 0.270598, -0.923880 }, + { -0.000000, 0.382683, -0.923880 }, + { -0.270598, 0.270598, -0.923880 }, + { -0.382683, -0.000000, -0.923880 }, + { -0.270598, -0.270598, -0.923880 }, + { 0.000000, -0.382683, -0.923880 }, + { 0.270598, -0.270598, -0.923880 }, + { 0.195090, 0.000000, -0.980785 }, + { -0.000000, 0.195090, -0.980785 }, + { -0.195090, -0.000000, -0.980785 }, + { 0.000000, -0.195090, -0.980785 }, + { 0.980785, 0.000000, 0.195090 }, + { 0.956195, 0.218245, 0.195090 }, + { 0.883657, 0.425547, 0.195090 }, + { 0.766809, 0.611510, 0.195090 }, + { 0.611510, 0.766809, 0.195090 }, + { 0.425547, 0.883657, 0.195090 }, + { 0.218245, 0.956195, 0.195090 }, + { -0.000000, 0.980785, 0.195090 }, + { -0.218245, 0.956195, 0.195090 }, + { -0.425547, 0.883657, 0.195090 }, + { -0.611510, 0.766809, 0.195090 }, + { -0.766809, 0.611510, 0.195090 }, + { -0.883657, 0.425547, 0.195090 }, + { -0.956195, 0.218245, 0.195090 }, + { -0.980785, -0.000000, 0.195090 }, + { -0.956195, -0.218245, 0.195090 }, + { -0.883657, -0.425547, 0.195090 }, + { -0.766809, -0.611510, 0.195090 }, + { -0.611510, -0.766809, 0.195090 }, + { -0.425547, -0.883657, 0.195090 }, + { -0.218245, -0.956195, 0.195090 }, + { 0.000000, -0.980785, 0.195090 }, + { 0.218245, -0.956195, 0.195090 }, + { 0.425547, -0.883657, 0.195090 }, + { 0.611510, -0.766809, 0.195090 }, + { 0.766809, -0.611510, 0.195090 }, + { 0.883657, -0.425547, 0.195090 }, + { 0.956195, -0.218245, 0.195090 }, + { 0.923880, 0.000000, 0.382683 }, + { 0.892399, 0.239118, 0.382683 }, + { 0.800103, 0.461940, 0.382683 }, + { 0.653281, 0.653281, 0.382683 }, + { 0.461940, 0.800103, 0.382683 }, + { 0.239118, 0.892399, 0.382683 }, + { -0.000000, 0.923880, 0.382683 }, + { -0.239118, 0.892399, 0.382683 }, + { -0.461940, 0.800103, 0.382683 }, + { -0.653281, 0.653281, 0.382683 }, + { -0.800103, 0.461940, 0.382683 }, + { -0.892399, 0.239118, 0.382683 }, + { -0.923880, -0.000000, 0.382683 }, + { -0.892399, -0.239118, 0.382683 }, + { -0.800103, -0.461940, 0.382683 }, + { -0.653282, -0.653281, 0.382683 }, + { -0.461940, -0.800103, 0.382683 }, + { -0.239118, -0.892399, 0.382683 }, + { 0.000000, -0.923880, 0.382683 }, + { 0.239118, -0.892399, 0.382683 }, + { 0.461940, -0.800103, 0.382683 }, + { 0.653281, -0.653282, 0.382683 }, + { 0.800103, -0.461940, 0.382683 }, + { 0.892399, -0.239117, 0.382683 }, + { 0.831470, 0.000000, 0.555570 }, + { 0.790775, 0.256938, 0.555570 }, + { 0.672673, 0.488726, 0.555570 }, + { 0.488726, 0.672673, 0.555570 }, + { 0.256938, 0.790775, 0.555570 }, + { -0.000000, 0.831470, 0.555570 }, + { -0.256938, 0.790775, 0.555570 }, + { -0.488726, 0.672673, 0.555570 }, + { -0.672673, 0.488726, 0.555570 }, + { -0.790775, 0.256938, 0.555570 }, + { -0.831470, -0.000000, 0.555570 }, + { -0.790775, -0.256938, 0.555570 }, + { -0.672673, -0.488726, 0.555570 }, + { -0.488725, -0.672673, 0.555570 }, + { -0.256938, -0.790775, 0.555570 }, + { 0.000000, -0.831470, 0.555570 }, + { 0.256938, -0.790775, 0.555570 }, + { 0.488725, -0.672673, 0.555570 }, + { 0.672673, -0.488726, 0.555570 }, + { 0.790775, -0.256938, 0.555570 }, + { 0.707107, 0.000000, 0.707107 }, + { 0.653281, 0.270598, 0.707107 }, + { 0.500000, 0.500000, 0.707107 }, + { 0.270598, 0.653281, 0.707107 }, + { -0.000000, 0.707107, 0.707107 }, + { -0.270598, 0.653282, 0.707107 }, + { -0.500000, 0.500000, 0.707107 }, + { -0.653281, 0.270598, 0.707107 }, + { -0.707107, -0.000000, 0.707107 }, + { -0.653281, -0.270598, 0.707107 }, + { -0.500000, -0.500000, 0.707107 }, + { -0.270598, -0.653281, 0.707107 }, + { 0.000000, -0.707107, 0.707107 }, + { 0.270598, -0.653281, 0.707107 }, + { 0.500000, -0.500000, 0.707107 }, + { 0.653282, -0.270598, 0.707107 }, + { 0.555570, 0.000000, 0.831470 }, + { 0.481138, 0.277785, 0.831470 }, + { 0.277785, 0.481138, 0.831470 }, + { -0.000000, 0.555570, 0.831470 }, + { -0.277785, 0.481138, 0.831470 }, + { -0.481138, 0.277785, 0.831470 }, + { -0.555570, -0.000000, 0.831470 }, + { -0.481138, -0.277785, 0.831470 }, + { -0.277785, -0.481138, 0.831470 }, + { 0.000000, -0.555570, 0.831470 }, + { 0.277785, -0.481138, 0.831470 }, + { 0.481138, -0.277785, 0.831470 }, + { 0.382683, 0.000000, 0.923880 }, + { 0.270598, 0.270598, 0.923880 }, + { -0.000000, 0.382683, 0.923880 }, + { -0.270598, 0.270598, 0.923880 }, + { -0.382683, -0.000000, 0.923880 }, + { -0.270598, -0.270598, 0.923880 }, + { 0.000000, -0.382683, 0.923880 }, + { 0.270598, -0.270598, 0.923880 }, + { 0.195090, 0.000000, 0.980785 }, + { -0.000000, 0.195090, 0.980785 }, + { -0.195090, -0.000000, 0.980785 }, + { 0.000000, -0.195090, 0.980785 } +}; + +/* mdc model frame information */ +typedef struct mdcFrame_s +{ + float bounds[ 2 ][ 3 ]; + float localOrigin[ 3 ]; + float radius; + char creator[ 16 ]; +} +mdcFrame_t; + +/* mdc model tag information */ +typedef struct mdcTag_s +{ + short xyz[3]; + short angles[3]; +} +mdcTag_t; + +/* mdc surface mdc (one object mesh) */ +typedef struct mdcSurface_s +{ + char magic[ 4 ]; + char name[ 64 ]; /* polyset name */ + int flags; + int numCompFrames; /* all surfaces in a model should have the same */ + int numBaseFrames; /* ditto */ + int numShaders; /* all model surfaces should have the same */ + int numVerts; + int numTriangles; + int ofsTriangles; + int ofsShaders; /* offset from start of mdcSurface_t */ + int ofsSt; /* texture coords are common for all frames */ + int ofsXyzNormals; /* numVerts * numBaseFrames */ + int ofsXyzCompressed; /* numVerts * numCompFrames */ + + int ofsFrameBaseFrames; /* numFrames */ + int ofsFrameCompFrames; /* numFrames */ + int ofsEnd; /* next surface follows */ +} +mdcSurface_t; + +typedef struct mdcShader_s +{ + char name[ 64 ]; + int shaderIndex; /* for ingame use */ +} +mdcShader_t; + +typedef struct mdcTriangle_s +{ + int indexes[ 3 ]; +} +mdcTriangle_t; + +typedef struct mdcTexCoord_s +{ + float st[ 2 ]; +} +mdcTexCoord_t; + +typedef struct mdcVertex_s +{ + short xyz[ 3 ]; + short normal; +} +mdcVertex_t; + +typedef struct mdcXyzCompressed_s +{ + unsigned int ofsVec; /* offset direction from the last base frame */ +} +mdcXyzCompressed_t; + + +/* mdc model file mdc structure */ +typedef struct mdc_s +{ + char magic[ 4 ]; /* MDC_MAGIC */ + int version; + char name[ 64 ]; /* model name */ + int flags; + int numFrames; + int numTags; + int numSurfaces; + int numSkins; /* number of skins for the mesh */ + int ofsFrames; /* offset for first frame */ + int ofsTagNames; /* numTags */ + int ofsTags; /* numFrames * numTags */ + int ofsSurfaces; /* first surface, others follow */ + int ofsEnd; /* end of file */ +} +mdc_t; + + + + +/* +_mdc_canload() +validates a Return to Castle Wolfenstein model file. btw, i use the +preceding underscore cause it's a static func referenced +by one structure only. +*/ + +static int _mdc_canload( PM_PARAMS_CANLOAD ) +{ + mdc_t *mdc; + + + /* to keep the compiler happy */ + *fileName = *fileName; + + /* sanity check */ + if( bufSize < ( sizeof( *mdc ) * 2) ) + return PICO_PMV_ERROR_SIZE; + + /* set as mdc */ + mdc = (mdc_t*) buffer; + + /* check mdc magic */ + if( *((int*) mdc->magic) != *((int*) MDC_MAGIC) ) + return PICO_PMV_ERROR_IDENT; + + /* check mdc version */ + if( _pico_little_long( mdc->version ) != MDC_VERSION ) + return PICO_PMV_ERROR_VERSION; + + /* file seems to be a valid mdc */ + return PICO_PMV_OK; +} + + + +/* +_mdc_load() +loads a Return to Castle Wolfenstein mdc model file. +*/ + +static picoModel_t *_mdc_load( PM_PARAMS_LOAD ) +{ + int i, j; + picoByte_t *bb; + mdc_t *mdc; + mdcSurface_t *surface; + mdcShader_t *shader; + mdcTexCoord_t *texCoord; + mdcFrame_t *frame; + mdcTriangle_t *triangle; + mdcVertex_t *vertex; + mdcXyzCompressed_t *vertexComp; + short *mdcShort, *mdcCompVert; + double lat, lng; + + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + /* ------------------------------------------------- + mdc loading + ------------------------------------------------- */ + + + /* set as mdc */ + bb = (picoByte_t*) buffer; + mdc = (mdc_t*) buffer; + + /* check ident and version */ + if( *((int*) mdc->magic) != *((int*) MDC_MAGIC) || _pico_little_long( mdc->version ) != MDC_VERSION ) + { + /* not an mdc file (todo: set error) */ + return NULL; + } + + /* swap mdc */ + mdc->version = _pico_little_long( mdc->version ); + mdc->numFrames = _pico_little_long( mdc->numFrames ); + mdc->numTags = _pico_little_long( mdc->numTags ); + mdc->numSurfaces = _pico_little_long( mdc->numSurfaces ); + mdc->numSkins = _pico_little_long( mdc->numSkins ); + mdc->ofsFrames = _pico_little_long( mdc->ofsFrames ); + mdc->ofsTags = _pico_little_long( mdc->ofsTags ); + mdc->ofsTagNames = _pico_little_long( mdc->ofsTagNames ); + mdc->ofsSurfaces = _pico_little_long( mdc->ofsSurfaces ); + mdc->ofsEnd = _pico_little_long( mdc->ofsEnd ); + + /* do frame check */ + if( mdc->numFrames < 1 ) + { + _pico_printf( PICO_ERROR, "MDC with 0 frames" ); + return NULL; + } + + if( frameNum < 0 || frameNum >= mdc->numFrames ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range MDC frame specified" ); + return NULL; + } + + /* swap frames */ + frame = (mdcFrame_t*) (bb + mdc->ofsFrames ); + for( i = 0; i < mdc->numFrames; i++, frame++ ) + { + frame->radius = _pico_little_float( frame->radius ); + for( j = 0; j < 3; j++ ) + { + frame->bounds[ 0 ][ j ] = _pico_little_float( frame->bounds[ 0 ][ j ] ); + frame->bounds[ 1 ][ j ] = _pico_little_float( frame->bounds[ 1 ][ j ] ); + frame->localOrigin[ j ] = _pico_little_float( frame->localOrigin[ j ] ); + } + } + + /* swap surfaces */ + surface = (mdcSurface_t*) (bb + mdc->ofsSurfaces); + for( i = 0; i < mdc->numSurfaces; i++ ) + { + /* swap surface mdc */ + surface->flags = _pico_little_long( surface->flags ); + surface->numBaseFrames = _pico_little_long( surface->numBaseFrames ); + surface->numCompFrames = _pico_little_long( surface->numCompFrames ); + surface->numShaders = _pico_little_long( surface->numShaders ); + surface->numTriangles = _pico_little_long( surface->numTriangles ); + surface->ofsTriangles = _pico_little_long( surface->ofsTriangles ); + surface->numVerts = _pico_little_long( surface->numVerts ); + surface->ofsShaders = _pico_little_long( surface->ofsShaders ); + surface->ofsSt = _pico_little_long( surface->ofsSt ); + surface->ofsXyzNormals = _pico_little_long( surface->ofsXyzNormals ); + surface->ofsXyzCompressed = _pico_little_long( surface->ofsXyzCompressed ); + surface->ofsFrameBaseFrames = _pico_little_long( surface->ofsFrameBaseFrames ); + surface->ofsFrameCompFrames = _pico_little_long( surface->ofsFrameCompFrames ); + surface->ofsEnd = _pico_little_long( surface->ofsEnd ); + + /* swap triangles */ + triangle = (mdcTriangle_t*) ((picoByte_t*) surface + surface->ofsTriangles); + for( j = 0; j < surface->numTriangles; j++, triangle++ ) + { + /* sea: swaps fixed */ + triangle->indexes[ 0 ] = _pico_little_long( triangle->indexes[ 0 ] ); + triangle->indexes[ 1 ] = _pico_little_long( triangle->indexes[ 1 ] ); + triangle->indexes[ 2 ] = _pico_little_long( triangle->indexes[ 2 ] ); + } + + /* swap st coords */ + texCoord = (mdcTexCoord_t*) ((picoByte_t*) surface + surface->ofsSt); + for( j = 0; j < surface->numVerts; j++, texCoord++ ) + { + texCoord->st[ 0 ] = _pico_little_float( texCoord->st[ 0 ] ); + texCoord->st[ 1 ] = _pico_little_float( texCoord->st[ 1 ] ); + } + + /* swap xyz/normals */ + vertex = (mdcVertex_t*) ((picoByte_t*) surface + surface->ofsXyzNormals); + for( j = 0; j < (surface->numVerts * surface->numBaseFrames); j++, vertex++) + { + vertex->xyz[ 0 ] = _pico_little_short( vertex->xyz[ 0 ] ); + vertex->xyz[ 1 ] = _pico_little_short( vertex->xyz[ 1 ] ); + vertex->xyz[ 2 ] = _pico_little_short( vertex->xyz[ 2 ] ); + vertex->normal = _pico_little_short( vertex->normal ); + } + + /* swap xyz/compressed */ + vertexComp = (mdcXyzCompressed_t*) ((picoByte_t*) surface + surface->ofsXyzCompressed); + for( j = 0; j < (surface->numVerts * surface->numCompFrames); j++, vertexComp++) + { + vertexComp->ofsVec = _pico_little_long( vertexComp->ofsVec ); + } + + /* swap base frames */ + mdcShort = (short *) ((picoByte_t*) surface + surface->ofsFrameBaseFrames); + for( j = 0; j < mdc->numFrames; j++, mdcShort++) + { + *mdcShort = _pico_little_short( *mdcShort ); + } + + /* swap compressed frames */ + mdcShort = (short *) ((picoByte_t*) surface + surface->ofsFrameCompFrames); + for( j = 0; j < mdc->numFrames; j++, mdcShort++) + { + *mdcShort = _pico_little_short( *mdcShort ); + } + + /* get next surface */ + surface = (mdcSurface_t*) ((picoByte_t*) surface + surface->ofsEnd); + } + + /* ------------------------------------------------- + pico model creation + ------------------------------------------------- */ + + /* create new pico model */ + picoModel = PicoNewModel(); + if( picoModel == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, mdc->numFrames ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + /* mdc surfaces become picomodel surfaces */ + surface = (mdcSurface_t*) (bb + mdc->ofsSurfaces); + + /* run through mdc surfaces */ + for( i = 0; i < mdc->numSurfaces; i++ ) + { + /* allocate new pico surface */ + picoSurface = PicoNewSurface( picoModel ); + if( picoSurface == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); /* sea */ + return NULL; + } + + /* mdc model surfaces are all triangle meshes */ + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( picoSurface, surface->name ); + + /* create new pico shader -sea */ + picoShader = PicoNewShader( picoModel ); + if( picoShader == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + return NULL; + } + + /* detox and set shader name */ + shader = (mdcShader_t*) ((picoByte_t*) surface + surface->ofsShaders); + _pico_setfext( shader->name, "" ); + _pico_unixify( shader->name ); + PicoSetShaderName( picoShader, shader->name ); + + /* associate current surface with newly created shader */ + PicoSetSurfaceShader( picoSurface, picoShader ); + + /* copy indexes */ + triangle = (mdcTriangle_t *) ((picoByte_t*) surface + surface->ofsTriangles); + + for( j = 0; j < surface->numTriangles; j++, triangle++ ) + { + PicoSetSurfaceIndex( picoSurface, (j * 3 + 0), (picoIndex_t) triangle->indexes[ 0 ] ); + PicoSetSurfaceIndex( picoSurface, (j * 3 + 1), (picoIndex_t) triangle->indexes[ 1 ] ); + PicoSetSurfaceIndex( picoSurface, (j * 3 + 2), (picoIndex_t) triangle->indexes[ 2 ] ); + } + + /* copy vertexes */ + texCoord = (mdcTexCoord_t*) ((picoByte_t *) surface + surface->ofsSt); + mdcShort = (short *) ((picoByte_t *) surface + surface->ofsXyzNormals) + ((int)*((short *) ((picoByte_t *) surface + surface->ofsFrameBaseFrames) + frameNum) * surface->numVerts * 4); + if( surface->numCompFrames > 0 ) + { + mdcCompVert = (short *) ((picoByte_t *) surface + surface->ofsFrameCompFrames) + frameNum; + if( *mdcCompVert >= 0 ) + vertexComp = (mdcXyzCompressed_t *) ((picoByte_t *) surface + surface->ofsXyzCompressed) + (*mdcCompVert * surface->numVerts); + } + _pico_set_color( color, 255, 255, 255, 255 ); + + for( j = 0; j < surface->numVerts; j++, texCoord++, mdcShort+=4 ) + { + /* set vertex origin */ + xyz[ 0 ] = MDC_SCALE * mdcShort[ 0 ]; + xyz[ 1 ] = MDC_SCALE * mdcShort[ 1 ]; + xyz[ 2 ] = MDC_SCALE * mdcShort[ 2 ]; + + /* add compressed ofsVec */ + if( surface->numCompFrames > 0 && *mdcCompVert >= 0 ) + { + xyz[ 0 ] += ((float) ((vertexComp->ofsVec) & 255) - MDC_MAX_OFS) * MDC_DIST_SCALE; + xyz[ 1 ] += ((float) ((vertexComp->ofsVec >> 8) & 255) - MDC_MAX_OFS) * MDC_DIST_SCALE; + xyz[ 2 ] += ((float) ((vertexComp->ofsVec >> 16) & 255) - MDC_MAX_OFS) * MDC_DIST_SCALE; + PicoSetSurfaceXYZ( picoSurface, j, xyz ); + + normal[ 0 ] = (float) mdcNormals[ (vertexComp->ofsVec >> 24) ][ 0 ]; + normal[ 1 ] = (float) mdcNormals[ (vertexComp->ofsVec >> 24) ][ 1 ]; + normal[ 2 ] = (float) mdcNormals[ (vertexComp->ofsVec >> 24) ][ 2 ]; + PicoSetSurfaceNormal( picoSurface, j, normal ); + + vertexComp++; + } + else + { + PicoSetSurfaceXYZ( picoSurface, j, xyz ); + + /* decode lat/lng normal to 3 float normal */ + lat = (float) ((*(mdcShort + 3) >> 8) & 0xff); + lng = (float) (*(mdcShort + 3) & 0xff); + lat *= PICO_PI / 128; + lng *= PICO_PI / 128; + normal[ 0 ] = (picoVec_t) cos( lat ) * (picoVec_t) sin( lng ); + normal[ 1 ] = (picoVec_t) sin( lat ) * (picoVec_t) sin( lng ); + normal[ 2 ] = (picoVec_t) cos( lng ); + PicoSetSurfaceNormal( picoSurface, j, normal ); + } + + /* set st coords */ + st[ 0 ] = texCoord->st[ 0 ]; + st[ 1 ] = texCoord->st[ 1 ]; + PicoSetSurfaceST( picoSurface, 0, j, st ); + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, j, color ); + } + + /* get next surface */ + surface = (mdcSurface_t*) ((picoByte_t*) surface + surface->ofsEnd); + } + + /* return the new pico model */ + return picoModel; +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleMDC = +{ + "1.3", /* module version string */ + "RtCW MDC", /* module display name */ + "Arnout van Meer", /* author's name */ + "2002 Arnout van Meer", /* module copyright */ + { + "mdc", NULL, NULL, NULL /* default extensions to use */ + }, + _mdc_canload, /* validation routine */ + _mdc_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/tools/urt/libs/picomodel/pm_ms3d.c b/tools/urt/libs/picomodel/pm_ms3d.c new file mode 100644 index 00000000..4924fd9a --- /dev/null +++ b/tools/urt/libs/picomodel/pm_ms3d.c @@ -0,0 +1,494 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PM_MS3D_C + +/* dependencies */ +#include "picointernal.h" + +/* disable warnings */ +#ifdef WIN32 +#pragma warning( disable:4100 ) /* unref param */ +#endif + +/* remarks: + * - loader seems stable + * todo: + * - fix uv coordinate problem + * - check for buffer overflows ('bufptr' accesses) + */ +/* uncomment when debugging this module */ + #define DEBUG_PM_MS3D + #define DEBUG_PM_MS3D_EX + +/* plain white */ +static picoColor_t white = { 255,255,255,255 }; + +/* ms3d limits */ +#define MS3D_MAX_VERTS 8192 +#define MS3D_MAX_TRIS 16384 +#define MS3D_MAX_GROUPS 128 +#define MS3D_MAX_MATERIALS 128 +#define MS3D_MAX_JOINTS 128 +#define MS3D_MAX_KEYFRAMES 216 + +/* ms3d flags */ +#define MS3D_SELECTED 1 +#define MS3D_HIDDEN 2 +#define MS3D_SELECTED2 4 +#define MS3D_DIRTY 8 + +/* this freaky loader needs byte alignment */ +#pragma pack(push, 1) + +/* ms3d header */ +typedef struct SMsHeader +{ + char magic[10]; + int version; +} +TMsHeader; + +/* ms3d vertex */ +typedef struct SMsVertex +{ + unsigned char flags; /* sel, sel2, or hidden */ + float xyz[3]; + char boneID; /* -1 means 'no bone' */ + unsigned char refCount; +} +TMsVertex; + +/* ms3d triangle */ +typedef struct SMsTriangle +{ + unsigned short flags; /* sel, sel2, or hidden */ + unsigned short vertexIndices[3]; + float vertexNormals[3][3]; + float s[3]; + float t[3]; + unsigned char smoothingGroup; /* 1 - 32 */ + unsigned char groupIndex; +} +TMsTriangle; + +/* ms3d material */ +typedef struct SMsMaterial +{ + char name[32]; + float ambient[4]; + float diffuse[4]; + float specular[4]; + float emissive[4]; + float shininess; /* range 0..128 */ + float transparency; /* range 0..1 */ + unsigned char mode; + char texture [128]; /* texture.bmp */ + char alphamap[128]; /* alpha.bmp */ +} +TMsMaterial; + +// ms3d group (static part) +// followed by a variable size block (see below) +typedef struct SMsGroup +{ + unsigned char flags; // sel, hidden + char name[32]; + unsigned short numTriangles; +/* + unsigned short triangleIndices[ numTriangles ]; + char materialIndex; // -1 means 'no material' +*/ +} +TMsGroup; + +// ms3d joint +typedef struct SMsJoint +{ + unsigned char flags; + char name[32]; + char parentName[32]; + float rotation[3]; + float translation[3]; + unsigned short numRotationKeyframes; + unsigned short numTranslationKeyframes; +} +TMsJoint; + +// ms3d keyframe +typedef struct SMsKeyframe +{ + float time; + float parameter[3]; +} +TMsKeyframe; + +/* restore previous data alignment */ +#pragma pack(pop) + +/* _ms3d_canload: + * validates a milkshape3d model file. + */ +static int _ms3d_canload( PM_PARAMS_CANLOAD ) +{ + TMsHeader *hdr; + + + /* to keep the compiler happy */ + *fileName = *fileName; + + /* sanity check */ + if (bufSize < sizeof(TMsHeader)) + return PICO_PMV_ERROR_SIZE; + + /* get ms3d header */ + hdr = (TMsHeader *)buffer; + + /* check ms3d magic */ + if (strncmp(hdr->magic,"MS3D000000",10) != 0) + return PICO_PMV_ERROR_IDENT; + + /* check ms3d version */ + if (_pico_little_long(hdr->version) < 3 || + _pico_little_long(hdr->version) > 4) + { + _pico_printf( PICO_ERROR,"MS3D file ignored. Only MS3D 1.3 and 1.4 is supported." ); + return PICO_PMV_ERROR_VERSION; + } + /* file seems to be a valid ms3d */ + return PICO_PMV_OK; +} + +static unsigned char *GetWord( unsigned char *bufptr, int *out ) +{ + if (bufptr == NULL) return NULL; + *out = _pico_little_short( *(unsigned short *)bufptr ); + return( bufptr + 2 ); +} + +/* _ms3d_load: + * loads a milkshape3d model file. +*/ +static picoModel_t *_ms3d_load( PM_PARAMS_LOAD ) +{ + picoModel_t *model; + unsigned char *bufptr; + int shaderRefs[ MS3D_MAX_GROUPS ]; + int numGroups; + int numMaterials; +// unsigned char *ptrToGroups; + int numVerts; + unsigned char *ptrToVerts; + int numTris; + unsigned char *ptrToTris; + int i,k,m; + + /* create new pico model */ + model = PicoNewModel(); + if (model == NULL) return NULL; + + /* do model setup */ + PicoSetModelFrameNum( model, frameNum ); + PicoSetModelName( model, fileName ); + PicoSetModelFileName( model, fileName ); + + /* skip header */ + bufptr = (unsigned char *)buffer + sizeof(TMsHeader); + + /* get number of vertices */ + bufptr = GetWord( bufptr,&numVerts ); + ptrToVerts = bufptr; + +#ifdef DEBUG_PM_MS3D + printf("NumVertices: %d\n",numVerts); +#endif + /* swap verts */ + for (i=0; ixyz[ 0 ] = _pico_little_float( vertex->xyz[ 0 ] ); + vertex->xyz[ 1 ] = _pico_little_float( vertex->xyz[ 1 ] ); + vertex->xyz[ 2 ] = _pico_little_float( vertex->xyz[ 2 ] ); + +#ifdef DEBUG_PM_MS3D_EX_ + printf("Vertex: x: %f y: %f z: %f\n", + msvd[i]->vertex[0], + msvd[i]->vertex[1], + msvd[i]->vertex[2]); +#endif + } + /* get number of triangles */ + bufptr = GetWord( bufptr,&numTris ); + ptrToTris = bufptr; + +#ifdef DEBUG_PM_MS3D + printf("NumTriangles: %d\n",numTris); +#endif + /* swap tris */ + for (i=0; iflags = _pico_little_short( triangle->flags ); + + /* run through all tri verts */ + for (k=0; k<3; k++) + { + /* swap tex coords */ + triangle->s[ k ] = _pico_little_float( triangle->s[ k ] ); + triangle->t[ k ] = _pico_little_float( triangle->t[ k ] ); + + /* swap fields */ + triangle->vertexIndices[ k ] = _pico_little_short( triangle->vertexIndices[ k ] ); + triangle->vertexNormals[ 0 ][ k ] = _pico_little_float( triangle->vertexNormals[ 0 ][ k ] ); + triangle->vertexNormals[ 1 ][ k ] = _pico_little_float( triangle->vertexNormals[ 1 ][ k ] ); + triangle->vertexNormals[ 2 ][ k ] = _pico_little_float( triangle->vertexNormals[ 2 ][ k ] ); + + /* check for out of range indices */ + if (triangle->vertexIndices[ k ] >= numVerts) + { + _pico_printf( PICO_ERROR,"Vertex %d index %d out of range (%d, max %d)",i,k,triangle->vertexIndices[k],numVerts-1); + PicoFreeModel( model ); + return NULL; /* yuck */ + } + } + } + /* get number of groups */ + bufptr = GetWord( bufptr,&numGroups ); +// ptrToGroups = bufptr; + +#ifdef DEBUG_PM_MS3D + printf("NumGroups: %d\n",numGroups); +#endif + /* run through all groups in model */ + for (i=0; i