Initial commit

This commit is contained in:
Eagle517 2025-02-17 23:17:30 -06:00
commit 7cad314c94
4726 changed files with 1145203 additions and 0 deletions

46
Makefile Executable file
View File

@ -0,0 +1,46 @@
-include mk/conf.mk
ifndef CONFIG_STATUS
doConfigure:
$(error Configuration file not defined. Please run $(MAKE) -f mk/configure.mk)
#@$(MAKE) --no-print-directory -f ../mk/configure.mk
else
ifeq ($(CONFIG_STATUS),INVALID)
doConfigure:
$(error Invalid Configuration file. Please run $(MAKE) -f mk/configure.mk)
#@$(MAKE) --no-print-directory -f mk/configure.mk
endif
endif
default:
@$(MAKE) -s -C lib default
@$(MAKE) -s -C engine
.PHONY: tools engine dedicated docs clean
tools:
@$(MAKE) -s -C tools
dedicated:
@$(MAKE) -s -C lib
@$(MAKE) -s -C engine dedicated
engine:
@$(MAKE) -s -C lib
@$(MAKE) -s -C engine engine
html_docs:
# Assumes Doxygen is in your path
@doxygen doc/doxygen/html/doxygen.html.cfg
php_docs:
# Internal gg use only
# Assumes Doxygen is in your path
@doxygen doc/doxygen/php/doxygen.php.cfg
all: default tools
clean:
@$(MAKE) -s -C engine clean
@$(MAKE) -s -C lib clean
@$(MAKE) -s -C tools clean

72
ReadMe.html Executable file
View File

@ -0,0 +1,72 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Torque Engine SDK</title>
</head>
<body>
<!-------------------------------------------------------->
<h1>Torque Game Engine SDK</h1>
<p>Welcome to the <a href="http://www.garagegames.com">GarageGames</a>
Torque Game Engine SDK.</p>
<ul>
<li><a href="#started">Getting Started</a></li>
<li><a href="#homepage">SDK Home Page</a></li>
<li><a href="#forums">Forums</a></li>
<li><a href="#faq">Frequently Asked Questions (FAQ)</a></li>
<li><a href="#support">Support</a></li>
</ul>
</p>
<hr>
<h3 id="started">Getting Started</h3>
<p>
This SDK release includes the Torque Engine, related
tools and the demo application. Binaries and sources are included for all
applications. All Torque SDK documentation is found online on the
<a href="http://www.garagegames.com/docs/torque/index.php">Torque Game Engine SDK Documentation</a>
home page. You must be logged into the GarageGames site for all the documentation to be visible.
Documentation is divided into two main books:</p>
<ul>
<li><b>General Torque Documentation</b>, which includes a more complete section on getting started
as well as information on using the demo application, compiling the torque engine, using the torque editors,
scripting language basics, etc.</li>
<li><b>Torque Source Code Documentation</b>, which is engine API and source level documentation. This is
for more advanced users and is generated from documentation imbeded in the source code using a tool
called Doxygen.</li>
</ul>
<hr>
<h3 id="homepage">SDK Home Page</h3>
<p>The <a href="http://www.garagegames.com/mg/projects/torque1/">Torque SDK home page</a> contains links to
all the documentation as well as tutorials, example code and all other things Torque related. This should be
your first stop for information regarding the SDK.</p>
<hr>
<h3 id="forums">Forums</h3>
<p>The Torque SDK has a large online community located on the GarageGames
<a href="http://www.garagegames.com/mg/forums/result.area.php">forums</a>. Forums dedicated to the Torque engine
include both the <a href="http://www.garagegames.com/mg/forums/result.area.php?qa=8">public</a> and
<a href="http://www.garagegames.com/mg/forums/result.area.php?qa=10">private</a> SDK forums. To access the private
forums you must be logged in to the GarageGames site.</p>
<hr>
<h3 id="faq">FAQ</h3>
<p>Visit the online <a href="http://www.garagegames.com/index.php?sec=mg&mod=resource&page=category&qid=118">FAQ</a> resources</li>
for frequently answered questions regarding the engine SDK and tools.</p>
<hr>
<h3 id="support">Support</h3>
<p>Support is provided exclusively through the online <a href="http://www.garagegames.com/mg/forums/result.area.php?qa=10">Torque SDK Private Forums</a>
and <a href="http://www.garagegames.com/index.php?sec=mg&mod=resource&page=category&qid=118">FAQ</a> resources</li>.
Please make sure you read through the FAQs before posting questions. The Private forums are only visible if you are logged into the
GarageGames website.</p>
<p>- GarageGames Staff</p>
</body>
</html>

BIN
bin/bison/bison.exe Executable file

Binary file not shown.

334
bin/bison/bison.hairy Executable file
View File

@ -0,0 +1,334 @@
extern int timeclock;
int yyerror; /* Yyerror and yycost are set by guards. */
int yycost; /* If yyerror is set to a nonzero value by a */
/* guard, the reduction with which the guard */
/* is associated is not performed, and the */
/* error recovery mechanism is invoked. */
/* Yycost indicates the cost of performing */
/* the reduction given the attributes of the */
/* symbols. */
/* YYMAXDEPTH indicates the size of the parser's state and value */
/* stacks. */
#ifndef YYMAXDEPTH
#define YYMAXDEPTH 500
#endif
/* YYMAXRULES must be at least as large as the number of rules that */
/* could be placed in the rule queue. That number could be determined */
/* from the grammar and the size of the stack, but, as yet, it is not. */
#ifndef YYMAXRULES
#define YYMAXRULES 100
#endif
#ifndef YYMAXBACKUP
#define YYMAXBACKUP 100
#endif
short yyss[YYMAXDEPTH]; /* the state stack */
YYSTYPE yyvs[YYMAXDEPTH]; /* the semantic value stack */
YYLTYPE yyls[YYMAXDEPTH]; /* the location stack */
short yyrq[YYMAXRULES]; /* the rule queue */
int yychar; /* the lookahead symbol */
YYSTYPE yylval; /* the semantic value of the */
/* lookahead symbol */
YYSTYPE yytval; /* the semantic value for the state */
/* at the top of the state stack. */
YYSTYPE yyval; /* the variable used to return */
/* semantic values from the action */
/* routines */
YYLTYPE yylloc; /* location data for the lookahead */
/* symbol */
YYLTYPE yytloc; /* location data for the state at the */
/* top of the state stack */
int yynunlexed;
short yyunchar[YYMAXBACKUP];
YYSTYPE yyunval[YYMAXBACKUP];
YYLTYPE yyunloc[YYMAXBACKUP];
short *yygssp; /* a pointer to the top of the state */
/* stack; only set during error */
/* recovery. */
YYSTYPE *yygvsp; /* a pointer to the top of the value */
/* stack; only set during error */
/* recovery. */
YYLTYPE *yyglsp; /* a pointer to the top of the */
/* location stack; only set during */
/* error recovery. */
/* Yyget is an interface between the parser and the lexical analyzer. */
/* It is costly to provide such an interface, but it avoids requiring */
/* the lexical analyzer to be able to back up the scan. */
yyget()
{
if (yynunlexed > 0)
{
yynunlexed--;
yychar = yyunchar[yynunlexed];
yylval = yyunval[yynunlexed];
yylloc = yyunloc[yynunlexed];
}
else if (yychar <= 0)
yychar = 0;
else
{
yychar = yylex();
if (yychar < 0)
yychar = 0;
else yychar = YYTRANSLATE(yychar);
}
}
yyunlex(chr, val, loc)
int chr;
YYSTYPE val;
YYLTYPE loc;
{
yyunchar[yynunlexed] = chr;
yyunval[yynunlexed] = val;
yyunloc[yynunlexed] = loc;
yynunlexed++;
}
yyrestore(first, last)
register short *first;
register short *last;
{
register short *ssp;
register short *rp;
register int symbol;
register int state;
register int tvalsaved;
ssp = yygssp;
yyunlex(yychar, yylval, yylloc);
tvalsaved = 0;
while (first != last)
{
symbol = yystos[*ssp];
if (symbol < YYNTBASE)
{
yyunlex(symbol, yytval, yytloc);
tvalsaved = 1;
ssp--;
}
ssp--;
if (first == yyrq)
first = yyrq + YYMAXRULES;
first--;
for (rp = yyrhs + yyprhs[*first]; symbol = *rp; rp++)
{
if (symbol < YYNTBASE)
state = yytable[yypact[*ssp] + symbol];
else
{
state = yypgoto[symbol - YYNTBASE] + *ssp;
if (state >= 0 && state <= YYLAST && yycheck[state] == *ssp)
state = yytable[state];
else
state = yydefgoto[symbol - YYNTBASE];
}
*++ssp = state;
}
}
if ( ! tvalsaved && ssp > yyss)
{
yyunlex(yystos[*ssp], yytval, yytloc);
ssp--;
}
yygssp = ssp;
}
int
yyparse()
{
register int yystate;
register int yyn;
register short *yyssp;
register short *yyrq0;
register short *yyptr;
register YYSTYPE *yyvsp;
int yylen;
YYLTYPE *yylsp;
short *yyrq1;
short *yyrq2;
yystate = 0;
yyssp = yyss - 1;
yyvsp = yyvs - 1;
yylsp = yyls - 1;
yyrq0 = yyrq;
yyrq1 = yyrq0;
yyrq2 = yyrq0;
yychar = yylex();
if (yychar < 0)
yychar = 0;
else yychar = YYTRANSLATE(yychar);
yynewstate:
if (yyssp >= yyss + YYMAXDEPTH - 1)
{
yyabort("Parser Stack Overflow");
YYABORT;
}
*++yyssp = yystate;
yyresume:
yyn = yypact[yystate];
if (yyn == YYFLAG)
goto yydefault;
yyn += yychar;
if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar)
goto yydefault;
yyn = yytable[yyn];
if (yyn < 0)
{
yyn = -yyn;
goto yyreduce;
}
else if (yyn == 0)
goto yyerrlab;
yystate = yyn;
yyptr = yyrq2;
while (yyptr != yyrq1)
{
yyn = *yyptr++;
yylen = yyr2[yyn];
yyvsp -= yylen;
yylsp -= yylen;
yyguard(yyn, yyvsp, yylsp);
if (yyerror)
goto yysemerr;
yyaction(yyn, yyvsp, yylsp);
*++yyvsp = yyval;
yylsp++;
if (yylen == 0)
{
yylsp->timestamp = timeclock;
yylsp->first_line = yytloc.first_line;
yylsp->first_column = yytloc.first_column;
yylsp->last_line = (yylsp-1)->last_line;
yylsp->last_column = (yylsp-1)->last_column;
yylsp->text = 0;
}
else
{
yylsp->last_line = (yylsp+yylen-1)->last_line;
yylsp->last_column = (yylsp+yylen-1)->last_column;
}
if (yyptr == yyrq + YYMAXRULES)
yyptr = yyrq;
}
if (yystate == YYFINAL)
YYACCEPT;
yyrq2 = yyptr;
yyrq1 = yyrq0;
*++yyvsp = yytval;
*++yylsp = yytloc;
yytval = yylval;
yytloc = yylloc;
yyget();
goto yynewstate;
yydefault:
yyn = yydefact[yystate];
if (yyn == 0)
goto yyerrlab;
yyreduce:
*yyrq0++ = yyn;
if (yyrq0 == yyrq + YYMAXRULES)
yyrq0 = yyrq;
if (yyrq0 == yyrq2)
{
yyabort("Parser Rule Queue Overflow");
YYABORT;
}
yyssp -= yyr2[yyn];
yyn = yyr1[yyn];
yystate = yypgoto[yyn - YYNTBASE] + *yyssp;
if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp)
yystate = yytable[yystate];
else
yystate = yydefgoto[yyn - YYNTBASE];
goto yynewstate;
yysemerr:
*--yyptr = yyn;
yyrq2 = yyptr;
yyvsp += yyr2[yyn];
yyerrlab:
yygssp = yyssp;
yygvsp = yyvsp;
yyglsp = yylsp;
yyrestore(yyrq0, yyrq2);
yyrecover();
yystate = *yygssp;
yyssp = yygssp;
yyvsp = yygvsp;
yyrq0 = yyrq;
yyrq1 = yyrq0;
yyrq2 = yyrq0;
goto yyresume;
}
$

4501
bin/bison/bison.html Executable file

File diff suppressed because it is too large Load Diff

686
bin/bison/bison.simple Executable file
View File

@ -0,0 +1,686 @@
/* -*-C-*- Note some compilers choke on comments on `#line' lines. */
#line 3 "bison.simple"
/* Skeleton output parser for bison,
Copyright (C) 1984, 1989, 1990 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/* As a special exception, when this file is copied by Bison into a
Bison output file, you may use that output file without restriction.
This special exception was added by the Free Software Foundation
in version 1.24 of Bison. */
#ifndef alloca
#ifdef __GNUC__
#define alloca __builtin_alloca
#else /* not GNU C. */
#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi)
#include <alloca.h>
#else /* not sparc */
#if defined (MSDOS) && !defined (__TURBOC__)
#include <malloc.h>
#else /* not MSDOS, or __TURBOC__ */
#if defined(_AIX)
#include <malloc.h>
#pragma alloca
#else /* not MSDOS, __TURBOC__, or _AIX */
#ifdef __hpux
#ifdef __cplusplus
extern "C" {
void *alloca (unsigned int);
};
#else /* not __cplusplus */
void *alloca ();
#endif /* not __cplusplus */
#endif /* __hpux */
#endif /* not _AIX */
#endif /* not MSDOS, or __TURBOC__ */
#endif /* not sparc. */
#endif /* not GNU C. */
#endif /* alloca not defined. */
/* This is the parser code that is written into each bison parser
when the %semantic_parser declaration is not specified in the grammar.
It was written by Richard Stallman by simplifying the hairy parser
used when %semantic_parser is specified. */
/* Note: there must be only one dollar sign in this file.
It is replaced by the list of actions, each action
as one case of the switch. */
#define yyerrok (yyerrstatus = 0)
#define yyclearin (yychar = YYEMPTY)
#define YYEMPTY -2
#define YYEOF 0
#define YYACCEPT return(0)
#define YYABORT return(1)
#define YYERROR goto yyerrlab1
/* Like YYERROR except do call yyerror.
This remains here temporarily to ease the
transition to the new meaning of YYERROR, for GCC.
Once GCC version 2 has supplanted version 1, this can go. */
#define YYFAIL goto yyerrlab
#define YYRECOVERING() (!!yyerrstatus)
#define YYBACKUP(token, value) \
do \
if (yychar == YYEMPTY && yylen == 1) \
{ yychar = (token), yylval = (value); \
yychar1 = YYTRANSLATE (yychar); \
YYPOPSTACK; \
goto yybackup; \
} \
else \
{ yyerror ("syntax error: cannot back up"); YYERROR; } \
while (0)
#define YYTERROR 1
#define YYERRCODE 256
#ifndef YYPURE
#define YYLEX yylex()
#endif
#ifdef YYPURE
#ifdef YYLSP_NEEDED
#ifdef YYLEX_PARAM
#define YYLEX yylex(&yylval, &yylloc, YYLEX_PARAM)
#else
#define YYLEX yylex(&yylval, &yylloc)
#endif
#else /* not YYLSP_NEEDED */
#ifdef YYLEX_PARAM
#define YYLEX yylex(&yylval, YYLEX_PARAM)
#else
#define YYLEX yylex(&yylval)
#endif
#endif /* not YYLSP_NEEDED */
#endif
/* If nonreentrant, generate the variables here */
#ifndef YYPURE
int yychar; /* the lookahead symbol */
YYSTYPE yylval; /* the semantic value of the */
/* lookahead symbol */
#ifdef YYLSP_NEEDED
YYLTYPE yylloc; /* location data for the lookahead */
/* symbol */
#endif
int yynerrs; /* number of parse errors so far */
#endif /* not YYPURE */
#if YYDEBUG != 0
int yydebug; /* nonzero means print parse trace */
/* Since this is uninitialized, it does not stop multiple parsers
from coexisting. */
#endif
/* YYINITDEPTH indicates the initial size of the parser's stacks */
#ifndef YYINITDEPTH
#define YYINITDEPTH 200
#endif
/* YYMAXDEPTH is the maximum size the stacks can grow to
(effective only if the built-in stack extension method is used). */
#if YYMAXDEPTH == 0
#undef YYMAXDEPTH
#endif
#ifndef YYMAXDEPTH
#define YYMAXDEPTH 10000
#endif
/* Prevent warning if -Wstrict-prototypes. */
#ifdef __GNUC__
int yyparse (void);
#endif
#if __GNUC__ > 1 /* GNU C and GNU C++ define this. */
#define __yy_memcpy(FROM,TO,COUNT) __builtin_memcpy(TO,FROM,COUNT)
#else /* not GNU C or C++ */
#ifndef __cplusplus
/* This is the most reliable way to avoid incompatibilities
in available built-in functions on various systems. */
static void
__yy_memcpy (from, to, count)
char *from;
char *to;
int count;
{
register char *f = from;
register char *t = to;
register int i = count;
while (i-- > 0)
*t++ = *f++;
}
#else /* __cplusplus */
/* This is the most reliable way to avoid incompatibilities
in available built-in functions on various systems. */
static void
__yy_memcpy (char *from, char *to, int count)
{
register char *f = from;
register char *t = to;
register int i = count;
while (i-- > 0)
*t++ = *f++;
}
#endif
#endif
#line 192 "bison.simple"
/* The user can define YYPARSE_PARAM as the name of an argument to be passed
into yyparse. The argument should have type void *.
It should actually point to an object.
Grammar actions can access the variable by casting it
to the proper pointer type. */
#ifdef YYPARSE_PARAM
#define YYPARSE_PARAM_DECL void *YYPARSE_PARAM;
#else
#define YYPARSE_PARAM
#define YYPARSE_PARAM_DECL
#endif
int
yyparse(YYPARSE_PARAM)
YYPARSE_PARAM_DECL
{
register int yystate;
register int yyn;
register short *yyssp;
register YYSTYPE *yyvsp;
int yyerrstatus; /* number of tokens to shift before error messages enabled */
int yychar1 = 0; /* lookahead token as an internal (translated) token number */
short yyssa[YYINITDEPTH]; /* the state stack */
YYSTYPE yyvsa[YYINITDEPTH]; /* the semantic value stack */
short *yyss = yyssa; /* refer to the stacks thru separate pointers */
YYSTYPE *yyvs = yyvsa; /* to allow yyoverflow to reallocate them elsewhere */
#ifdef YYLSP_NEEDED
YYLTYPE yylsa[YYINITDEPTH]; /* the location stack */
YYLTYPE *yyls = yylsa;
YYLTYPE *yylsp;
#define YYPOPSTACK (yyvsp--, yyssp--, yylsp--)
#else
#define YYPOPSTACK (yyvsp--, yyssp--)
#endif
int yystacksize = YYINITDEPTH;
#ifdef YYPURE
int yychar;
YYSTYPE yylval;
int yynerrs;
#ifdef YYLSP_NEEDED
YYLTYPE yylloc;
#endif
#endif
YYSTYPE yyval; /* the variable used to return */
/* semantic values from the action */
/* routines */
int yylen;
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Starting parse\n");
#endif
yystate = 0;
yyerrstatus = 0;
yynerrs = 0;
yychar = YYEMPTY; /* Cause a token to be read. */
/* Initialize stack pointers.
Waste one element of value and location stack
so that they stay on the same level as the state stack.
The wasted elements are never initialized. */
yyssp = yyss - 1;
yyvsp = yyvs;
#ifdef YYLSP_NEEDED
yylsp = yyls;
#endif
/* Push a new state, which is found in yystate . */
/* In all cases, when you get here, the value and location stacks
have just been pushed. so pushing a state here evens the stacks. */
yynewstate:
*++yyssp = yystate;
if (yyssp >= yyss + yystacksize - 1)
{
/* Give user a chance to reallocate the stack */
/* Use copies of these so that the &'s don't force the real ones into memory. */
YYSTYPE *yyvs1 = yyvs;
short *yyss1 = yyss;
#ifdef YYLSP_NEEDED
YYLTYPE *yyls1 = yyls;
#endif
/* Get the current used size of the three stacks, in elements. */
int size = yyssp - yyss + 1;
#ifdef yyoverflow
/* Each stack pointer address is followed by the size of
the data in use in that stack, in bytes. */
#ifdef YYLSP_NEEDED
/* This used to be a conditional around just the two extra args,
but that might be undefined if yyoverflow is a macro. */
yyoverflow("parser stack overflow",
&yyss1, size * sizeof (*yyssp),
&yyvs1, size * sizeof (*yyvsp),
&yyls1, size * sizeof (*yylsp),
&yystacksize);
#else
yyoverflow("parser stack overflow",
&yyss1, size * sizeof (*yyssp),
&yyvs1, size * sizeof (*yyvsp),
&yystacksize);
#endif
yyss = yyss1; yyvs = yyvs1;
#ifdef YYLSP_NEEDED
yyls = yyls1;
#endif
#else /* no yyoverflow */
/* Extend the stack our own way. */
if (yystacksize >= YYMAXDEPTH)
{
yyerror("parser stack overflow");
return 2;
}
yystacksize *= 2;
if (yystacksize > YYMAXDEPTH)
yystacksize = YYMAXDEPTH;
yyss = (short *) alloca (yystacksize * sizeof (*yyssp));
__yy_memcpy ((char *)yyss1, (char *)yyss, size * sizeof (*yyssp));
yyvs = (YYSTYPE *) alloca (yystacksize * sizeof (*yyvsp));
__yy_memcpy ((char *)yyvs1, (char *)yyvs, size * sizeof (*yyvsp));
#ifdef YYLSP_NEEDED
yyls = (YYLTYPE *) alloca (yystacksize * sizeof (*yylsp));
__yy_memcpy ((char *)yyls1, (char *)yyls, size * sizeof (*yylsp));
#endif
#endif /* no yyoverflow */
yyssp = yyss + size - 1;
yyvsp = yyvs + size - 1;
#ifdef YYLSP_NEEDED
yylsp = yyls + size - 1;
#endif
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Stack size increased to %d\n", yystacksize);
#endif
if (yyssp >= yyss + yystacksize - 1)
YYABORT;
}
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Entering state %d\n", yystate);
#endif
goto yybackup;
yybackup:
/* Do appropriate processing given the current state. */
/* Read a lookahead token if we need one and don't already have one. */
/* yyresume: */
/* First try to decide what to do without reference to lookahead token. */
yyn = yypact[yystate];
if (yyn == YYFLAG)
goto yydefault;
/* Not known => get a lookahead token if don't already have one. */
/* yychar is either YYEMPTY or YYEOF
or a valid token in external form. */
if (yychar == YYEMPTY)
{
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Reading a token: ");
#endif
yychar = YYLEX;
}
/* Convert token to internal form (in yychar1) for indexing tables with */
if (yychar <= 0) /* This means end of input. */
{
yychar1 = 0;
yychar = YYEOF; /* Don't call YYLEX any more */
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Now at end of input.\n");
#endif
}
else
{
yychar1 = YYTRANSLATE(yychar);
#if YYDEBUG != 0
if (yydebug)
{
fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]);
/* Give the individual parser a way to print the precise meaning
of a token, for further debugging info. */
#ifdef YYPRINT
YYPRINT (stderr, yychar, yylval);
#endif
fprintf (stderr, ")\n");
}
#endif
}
yyn += yychar1;
if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1)
goto yydefault;
yyn = yytable[yyn];
/* yyn is what to do for this token type in this state.
Negative => reduce, -yyn is rule number.
Positive => shift, yyn is new state.
New state is final state => don't bother to shift,
just return success.
0, or most negative number => error. */
if (yyn < 0)
{
if (yyn == YYFLAG)
goto yyerrlab;
yyn = -yyn;
goto yyreduce;
}
else if (yyn == 0)
goto yyerrlab;
if (yyn == YYFINAL)
YYACCEPT;
/* Shift the lookahead token. */
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]);
#endif
/* Discard the token being shifted unless it is eof. */
if (yychar != YYEOF)
yychar = YYEMPTY;
*++yyvsp = yylval;
#ifdef YYLSP_NEEDED
*++yylsp = yylloc;
#endif
/* count tokens shifted since error; after three, turn off error status. */
if (yyerrstatus) yyerrstatus--;
yystate = yyn;
goto yynewstate;
/* Do the default action for the current state. */
yydefault:
yyn = yydefact[yystate];
if (yyn == 0)
goto yyerrlab;
/* Do a reduction. yyn is the number of a rule to reduce with. */
yyreduce:
yylen = yyr2[yyn];
if (yylen > 0)
yyval = yyvsp[1-yylen]; /* implement default value of the action */
#if YYDEBUG != 0
if (yydebug)
{
int i;
fprintf (stderr, "Reducing via rule %d (line %d), ",
yyn, yyrline[yyn]);
/* Print the symbols being reduced, and their result. */
for (i = yyprhs[yyn]; yyrhs[i] > 0; i++)
fprintf (stderr, "%s ", yytname[yyrhs[i]]);
fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]);
}
#endif
$ /* the action file gets copied in in place of this dollarsign */
#line 487 "bison.simple"
yyvsp -= yylen;
yyssp -= yylen;
#ifdef YYLSP_NEEDED
yylsp -= yylen;
#endif
#if YYDEBUG != 0
if (yydebug)
{
short *ssp1 = yyss - 1;
fprintf (stderr, "state stack now");
while (ssp1 != yyssp)
fprintf (stderr, " %d", *++ssp1);
fprintf (stderr, "\n");
}
#endif
*++yyvsp = yyval;
#ifdef YYLSP_NEEDED
yylsp++;
if (yylen == 0)
{
yylsp->first_line = yylloc.first_line;
yylsp->first_column = yylloc.first_column;
yylsp->last_line = (yylsp-1)->last_line;
yylsp->last_column = (yylsp-1)->last_column;
yylsp->text = 0;
}
else
{
yylsp->last_line = (yylsp+yylen-1)->last_line;
yylsp->last_column = (yylsp+yylen-1)->last_column;
}
#endif
/* Now "shift" the result of the reduction.
Determine what state that goes to,
based on the state we popped back to
and the rule number reduced by. */
yyn = yyr1[yyn];
yystate = yypgoto[yyn - YYNTBASE] + *yyssp;
if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp)
yystate = yytable[yystate];
else
yystate = yydefgoto[yyn - YYNTBASE];
goto yynewstate;
yyerrlab: /* here on detecting error */
if (! yyerrstatus)
/* If not already recovering from an error, report this error. */
{
++yynerrs;
#ifdef YYERROR_VERBOSE
yyn = yypact[yystate];
if (yyn > YYFLAG && yyn < YYLAST)
{
int size = 0;
char *msg;
int x, count;
count = 0;
/* Start X at -yyn if nec to avoid negative indexes in yycheck. */
for (x = (yyn < 0 ? -yyn : 0);
x < (sizeof(yytname) / sizeof(char *)); x++)
if (yycheck[x + yyn] == x)
size += strlen(yytname[x]) + 15, count++;
msg = (char *) malloc(size + 15);
if (msg != 0)
{
strcpy(msg, "parse error");
if (count < 5)
{
count = 0;
for (x = (yyn < 0 ? -yyn : 0);
x < (sizeof(yytname) / sizeof(char *)); x++)
if (yycheck[x + yyn] == x)
{
strcat(msg, count == 0 ? ", expecting `" : " or `");
strcat(msg, yytname[x]);
strcat(msg, "'");
count++;
}
}
yyerror(msg);
free(msg);
}
else
yyerror ("parse error; also virtual memory exceeded");
}
else
#endif /* YYERROR_VERBOSE */
yyerror("parse error");
}
goto yyerrlab1;
yyerrlab1: /* here on error raised explicitly by an action */
if (yyerrstatus == 3)
{
/* if just tried and failed to reuse lookahead token after an error, discard it. */
/* return failure if at end of input */
if (yychar == YYEOF)
YYABORT;
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]);
#endif
yychar = YYEMPTY;
}
/* Else will try to reuse lookahead token
after shifting the error token. */
yyerrstatus = 3; /* Each real token shifted decrements this */
goto yyerrhandle;
yyerrdefault: /* current state does not do anything special for the error token. */
#if 0
/* This is wrong; only states that explicitly want error tokens
should shift them. */
yyn = yydefact[yystate]; /* If its default is to accept any token, ok. Otherwise pop it.*/
if (yyn) goto yydefault;
#endif
yyerrpop: /* pop the current state because it cannot handle the error token */
if (yyssp == yyss) YYABORT;
yyvsp--;
yystate = *--yyssp;
#ifdef YYLSP_NEEDED
yylsp--;
#endif
#if YYDEBUG != 0
if (yydebug)
{
short *ssp1 = yyss - 1;
fprintf (stderr, "Error: state stack now");
while (ssp1 != yyssp)
fprintf (stderr, " %d", *++ssp1);
fprintf (stderr, "\n");
}
#endif
yyerrhandle:
yyn = yypact[yystate];
if (yyn == YYFLAG)
goto yyerrdefault;
yyn += YYTERROR;
if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR)
goto yyerrdefault;
yyn = yytable[yyn];
if (yyn < 0)
{
if (yyn == YYFLAG)
goto yyerrpop;
yyn = -yyn;
goto yyreduce;
}
else if (yyn == 0)
goto yyerrpop;
if (yyn == YYFINAL)
YYACCEPT;
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Shifting error token, ");
#endif
*++yyvsp = yylval;
#ifdef YYLSP_NEEDED
*++yylsp = yylloc;
#endif
yystate = yyn;
goto yynewstate;
}

BIN
bin/flex/flex.exe Executable file

Binary file not shown.

4079
bin/flex/flex_1.html Executable file

File diff suppressed because it is too large Load Diff

357
bin/nasm/Licence Executable file
View File

@ -0,0 +1,357 @@
Terms and Conditions for the use of the Netwide Assembler
=========================================================
Can I have the gist without reading the legalese?
-------------------------------------------------
Basically, NASM is free. You can't charge for it. You can copy it as
much as you like. You can incorporate it, or bits of it, into other
free programs if you want. (But we want to know about it if you do,
and we want to be mentioned in the credits.) We may well allow you
to incorporate it into commercial software too, but we'll probably
demand some money for it, and we'll certainly demand to be given
credit. And in extreme cases (although I can't immediately think of
a reason we might actually want to do this) we may refuse to let you
do it at all.
NASM LICENCE AGREEMENT
======================
By "the Software" this licence refers to the complete contents of
the NASM archive, excluding this licence document itself, and
excluding the contents of the `test' directory. The Netwide
Disassembler, NDISASM, is specifically included under this licence.
I. The Software is freely redistributable; anyone may copy the
Software, or parts of the Software, and give away as many copies as
they like to anyone, as long as this licence document is kept with
the Software. Charging a fee for the Software is prohibited,
although a fee may be charged for the act of transferring a copy,
and you can offer warranty protection and charge a fee for that.
II. The Software, or parts thereof, may be incorporated into other
freely redistributable software (by which we mean software that may
be obtained free of charge) without requiring permission from the
authors, as long as due credit is given to the authors of the
Software in the resulting work, as long as the authors are informed
of this action if possible, and as long as those parts of the
Software that are used remain under this licence.
III. Modified forms of the Software may be created and distributed
as long as the authors are informed of this action if possible, as
long as the resulting work remains under this licence, as long as
the modified form of the Software is distributed with documentation
which still gives credit to the original authors of the Software,
and as long as the modified form of the Software is distributed with
a clear statement that it is not the original form of the Software
in the form that it was distributed by the authors.
IV. The Software, or parts thereof, may be incorporated into other
software which is not freely redistributable (i.e. software for
which a fee is charged), as long as permission is granted from the
authors of the Software. The authors reserve the right to grant this
permission only for a fee, which may at our option take the form of
royalty payments. The authors also reserve the right to refuse to
grant permission if they deem it necessary. For further information
about who exactly the authors are, see clause XI below.
V. The Software may be incorporated, in its original archive form,
into software collections or archives which are not freely
redistributable, as long as it is clearly stated that the Software
itself remains freely redistributable and remains under this licence
and no other. Such collections are deemed not to fall under article
IV of this licence.
VI. Object files or programs generated by the Software as output do
not fall under this licence at all, and may be placed under any
licence the author wishes. The authors explicitly lay no claim to,
and assert no rights over, any programs written by other people and
assembled into object form by the Software.
VII. You may not copy, modify or distribute the Software except
under the terms given in this licence document. You may not
sublicense the Software or in any way place it under any other
licence than this one. Since you have not signed this licence, you
are not of course required to accept it; however, no other licence
applies to the Software, and nothing else grants you any permission
to copy, modify, sublicense or distribute the Software in any way.
These actions are therefore prohibited if you do not accept this
licence.
VIII. There is no warranty for the Software, to the extent permitted
by applicable law. The authors provide the Software "as is" without
warranty of any kind, either expressed or implied, including but not
limited to the implied warranties of merchantability and fitness for
a particular purpose. The entire risk as to the quality and
performance of the Software is with you. Should the Software prove
defective, you assume the cost of all necessary servicing, repair or
correction.
IX. In no event, unless required by applicable law or agreed to in
writing, will any of the authors be liable to you for damages,
including any general, special, incidental or consequential damages,
arising out of the use or the inability to use the Software,
including but not limited to loss of data or data being rendered
inaccurate or a failure of the Software to operate with any other
programs, even if you have been advised of the possibility of such
damages.
X. In addition to what this Licence otherwise provides, the Software
may be distributed in such a way as to be compliant with the GNU
General Public Licence, as published by the Free Software Foundation,
Cambridge, MA, USA; version 2, or, at your option, any later version;
incorporated herein by reference. You must include a copy of this
Licence with such distribution. Furthermore, patches sent to the
authors for the purpose of inclusion in the official release version
are considered cleared for release under the full terms of this
Licence.
XI. The authors of NASM are the original authors (Simon Tatham and
Julian Hall) and all those who the original authors feel have
contributed significantly to the overall project. If you wish to
contact the authors, Julian Hall (jules@earthcorp.com) should be your
first port of call.
XII. Should any part of this agreement be deemed unenforcable, it is
intended that the remainder of the agreement be held in force.
END OF LICENCE AGREEMENT

179
bin/nasm/Readme Executable file
View File

@ -0,0 +1,179 @@
This is a distribution of NASM, the Netwide Assembler. NASM is a
prototype general-purpose x86 assembler. It will currently output
flat-form binary files, a.out, COFF and ELF Unix object files,
Microsoft Win32 and 16-bit DOS object files, OS/2 object files, the
as86 object format, and a home-grown format called RDOFF.
Also included is NDISASM, a prototype x86 binary-file disassembler
which uses the same instruction table as NASM.
To install NASM on UNIX or Linux, type `./configure', then `make', and
then either `make install', or copy the file `nasm' (and maybe
`ndisasm') to a directory on your search path (maybe /usr/local/bin,
or ~/bin if you don't have root access). You may also want to copy the
man page `nasm.1' (and maybe `ndisasm.1') to somewhere sensible. Note
that source and binaries are also available in RPM format; to install
an RPM on a system which uses this packaging format (mostly Linux
distributions) simply do "rpm -Uivh filename.rpm".
To install under DOS, if you don't need to rebuild from the sources,
you can just copy either nasm.exe and ndisasm.exe (32-bit DOS-extended
versions), nasmr.exe and ndisasmr.exe (16-bit classical DOS
executables), or nasmw.exe and ndisasmw.exe (Win32 console
applications - less likely to run out of memory), to somewhere on your
PATH.
The 32-bit applications require a DPMI server. If you're running
under plain DOS and don't have a DPMI server already, you can get
CSDPMI from ftp://ftp.simtel.net/pub/simtelnet/gnu/djgpp/v2misc/.
To rebuild the DOS sources, various makefiles are provided:
- Makefile.dos, the one I build the standard 16-bit releases from,
designed for a hybrid system using Microsoft C and Borland Make
(don't ask why :-)
- Makefile.vc, for Microsoft Visual C++ compiling to a Win32
command-line application. This is the one I build the standard
Win32 release binaries from.
- Makefile.bor, for Borland C.
- Makefile.bc2, also for Borland C, contributed by Fox Cutter.
Reported to work better than Makefile.bor on some systems.
- Makefile.sc, for Symantec C++, compiling to a 32-bit extended DOS
executable.. Contributed by Mark Junker.
- Makefile.scw, also for Symantec C++, compiling to a Win32 command-
line application. Also contributed by Mark Junker.
- Makefile.wc, for Watcom C, compiling to a 32-bit extended DOS
executable. Contributed by Dominik Behr.
- Makefile.wcw, also for Watcom C, compiling to a Win32 command-
line application. Also contributed by Dominik Behr.
- Makefile.dj, for DJGPP, compiling to a 32-bit extended DOS
executable. Contributed by Dominik Behr.
- Makefile.lcc, for lcc-win32, compiling to a Win32 command line
application. (The lcc-win32 compiler and tools are available from
http://www.remcomp.com/lcc-win32/)
I can't guarantee that all of those makefiles work, because I don't
have all of those compilers. However, Makefile.dos and Makefile.vc
work on my system, and so do Makefile.bor and Makefile.bc2.
Be careful with Borland C: there have been various conflicting
reports about how reliable the Huge memory model is. If you try to
compile NASM in Large model, you may get DGROUP overflows due to the
vast quantity of data in the instruction tables. I've had reports
from some people that Huge model doesn't work at all (and also
reports from others that it works fine), so if you don't want to try
moving to Huge, you could try adding the option `-dc' to the
compiler command line instead, which causes string literals to be
moved from DGROUP to the code segments and might make Large model
start working. (Either solution works for me.)
To rebuild truly from scratch, or to make any changes to insns.dat or
standard.mac, you need a Perl interpreter installed. Perl
interpreters are available for a number of platforms, from:
http://www.cpan.org/ports/
For DOS you can also get one from:
ftp://ftp.simtel.net/pub/simtelnet/gnu/djgpp/v2gnu/
Dominik Behr has also contributed the file misc/pmw.bat, which is a
batch file to turn the output from Makefile.wc (NASM.EXE and
NDISASM.EXE) into standalone executables incorporating Tran's
PMODE/W DOS extender, rather than depending on an external extender
program.
Some of the Windows makefiles produce executables called nasmw.exe
and ndisasmw.exe, and some don't. Be prepared for either...
If you want to build a restricted version of NASM containing only
some of the object file formats, you can achieve this by adding
#defines to `outform.h' (see the file itself for documentation), or
equivalently by adding compiler command line options in the
Makefile.
There is a machine description file for the `LCC' retargetable C
compiler (version 4.0), in the directory `lcc', along with
instructions for its use. This means that NASM can now be used as
the code-generator back end for a useful C compiler.
Michael `Wuschel' Tippach has ported his DOS extender `WDOSX' to
enable it to work with the 32-bit binary files NASM can output: the
original extender and his port `WDOSX/N' are available from his web
page, http://www.geocities.com/SiliconValley/Park/4493.
Matt Mastracci has written a document explaining how to write
assembly language modules in DJGPP programs using NASM: it's on his
web site at http://www.ucalgary.ca/~mmastrac/djgppasm.doc.
The `misc' directory contains `nasm.sl', a NASM editing mode for the
JED programmers' editor (see http://space.mit.edu/~davis/jed.html
for details about JED). The comment at the start of the file gives
instructions on how to install the mode. This directory also
contains a file (`magic') containing lines to add to /etc/magic on
Unix systems to allow the `file' command to recognise RDF files, and
a zip file (`exasm.zip') containing the necessary files for syntax
highlighting in the Aurora DOS editor. (The Aurora files were
contributed by <U993847220@aol.com>; I haven't tested them as I
don't have Aurora.)
The `rdoff' directory contains sources for a linker and loader for
the RDF object file format, to run under Linux, and also
documentation on the internal structure of RDF files.
For information about how you can distribute and use NASM, see the
file Licence. We were tempted to put NASM under the GPL, but decided
that in many ways it was too restrictive for developers.
For release 0.98 and later, the NASM source distribution contains the
following components:
Unix Windows, OS/2 DOS
nasm-X.XX.tar.gz nasm-X.XX.zip nasmXXXs.zip
Main archive: Contain all sources you need to build NASM,
plus the documentation source code. If you have a Perl
interpreter and (possibly) Winhelp compiler installed, you
don't need any additional files.
nasm-X.XX-xdoc.tar.gz nasm-X.XX-xdoc.zip nasmXXXd.zip
Documentation in HTML, INFO, text, and PostScript format.
You can compile these files yourself from the main archive
if you have a Perl interpreter installed.
N/A nasm-X.XX-whlp.zip N/A
Documentation in Windows help format. You can compile this
file yourself from the main archive if you have a Perl
interpreter and a Winhelp compiler installed.
The Unix, Windows-OS/2, and DOS versions differ in the following ways:
The Unix version uses the Unix line ending convention (LF), and long file
names with upper and lower case.
The Windows-OS/2 version uses the Microsoft line ending convention
(CR+LF), and long file names with upper and lower case.
The DOS version uses the Microsoft line ending convention (CR+LF),
with all filenames in 8.3 monocase.
For information about how to use NASM, see the various forms of
documentation in the `doc' directory: documentation is provided in
HTML, PostScript, plain text, Texinfo, and Windows Help formats. For
information about how to use NDISASM, see `ndisasm.doc'. For
information about the internal structure of NASM, see
`internal.doc'. (In particular, _please_ read `internal.doc' before
writing any code for us...)
The NASM web page is at http://www.cryogen.com/Nasm/
Bug reports (and patches if you can) should be sent to
<hpa@zytor.com>.

BIN
bin/nasm/nasmw.exe Executable file

Binary file not shown.

BIN
bin/nasm/ndisasmw.exe Executable file

Binary file not shown.

33
engine/Makefile Executable file
View File

@ -0,0 +1,33 @@
#--------------------------------------
# include and verify the users mk/conf.mk
-include ../mk/conf.mk
ifndef CONFIG_STATUS
doConfigure:
$(error Configuration file not defined. Please run $(MAKE) -f mk/configure.mk)
#@$(MAKE) --no-print-directory -f ../mk/configure.mk
else
ifeq ($(CONFIG_STATUS),INVALID)
doConfigure:
$(error Invalid Configuration file. Please run $(MAKE) -f mk/configure.mk)
#@$(MAKE) --no-print-directory -f mk/configure.mk
else
include ../mk/conf.$(COMPILER).$(OS).mk
include ../mk/conf.$(COMPILER).mk
endif
endif
include targets.torque.mk
include ../mk/conf.common.mk
#default:
# echo default.
ifneq ($(MAKECMDGOALS),clean)
-include $(addprefix $(DIR.OBJ)/, $(addsuffix $(EXT.DEP), $(basename $(filter %.cc %.c,$(SOURCE.ALL)))))
endif

2487
engine/audio/audio.cc Executable file

File diff suppressed because it is too large Load Diff

16
engine/audio/audio.h Executable file
View File

@ -0,0 +1,16 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIO_H_
#define _AUDIO_H_
#ifndef _PLATFORMAUDIO_H_
#include "platform/platformAudio.h"
#endif
#ifndef _AUDIODATABLOCK_H_
#include "audio/audioDataBlock.h"
#endif
#endif // _H_AUDIO_

428
engine/audio/audioBuffer.cc Executable file
View File

@ -0,0 +1,428 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platformAL.h"
#include "audio/audioBuffer.h"
#include "core/stream.h"
#include "console/console.h"
#include "core/frameAllocator.h"
#ifndef TORQUE_NO_OGGVORBIS
#include "vorbis/codec.h"
#endif
#if defined(TORQUE_BIG_ENDIAN)
#define ENDIAN 1
#else
#define ENDIAN 0
#endif
//#define LOG_SOUND_LOADS
/// WAV File-header
struct WAVFileHdr
{
ALubyte id[4];
ALsizei size;
ALubyte type[4];
};
//// WAV Fmt-header
struct WAVFmtHdr
{
ALushort format;
ALushort channels;
ALuint samplesPerSec;
ALuint bytesPerSec;
ALushort blockAlign;
ALushort bitsPerSample;
};
/// WAV FmtEx-header
struct WAVFmtExHdr
{
ALushort size;
ALushort samplesPerBlock;
};
/// WAV Smpl-header
struct WAVSmplHdr
{
ALuint manufacturer;
ALuint product;
ALuint samplePeriod;
ALuint note;
ALuint fineTune;
ALuint SMPTEFormat;
ALuint SMPTEOffest;
ALuint loops;
ALuint samplerData;
struct
{
ALuint identifier;
ALuint type;
ALuint start;
ALuint end;
ALuint fraction;
ALuint count;
} loop[1];
};
/// WAV Chunk-header
struct WAVChunkHdr
{
ALubyte id[4];
ALuint size;
};
#define CHUNKSIZE 4096
//--------------------------------------
AudioBuffer::AudioBuffer(StringTableEntry filename)
{
AssertFatal(StringTable->lookup(filename), "AudioBuffer:: filename is not a string table entry");
mFilename = filename;
mLoading = false;
malBuffer = 0;
}
AudioBuffer::~AudioBuffer()
{
if( malBuffer != 0 ) {
alDeleteBuffers( 1, &malBuffer );
}
}
//--------------------------------------
Resource<AudioBuffer> AudioBuffer::find(const char *filename)
{
U32 mark = FrameAllocator::getWaterMark();
char * f2 = NULL;
Resource<AudioBuffer> buffer = ResourceManager->load(filename);
if (bool(buffer) == false)
{
// wav file doesn't exist, try ogg file instead
S32 len = dStrlen(filename);
if (len>3 && !dStricmp(filename+len-4,".wav"))
{
f2 = (char*)FrameAllocator::alloc(len+1);
dStrcpy(f2,filename);
f2[len-3] = 'o';
f2[len-2] = 'g';
f2[len-1] = 'g';
buffer = ResourceManager->load(filename);
}
}
// if resource still not there, try to create it if file exists
if (bool(buffer) == false)
{
// see if the file exists -- first try default, then try ogg
if (ResourceManager->getPathOf(filename))
{
AudioBuffer *temp = new AudioBuffer(StringTable->insert(filename));
ResourceManager->add(filename, temp);
buffer = ResourceManager->load(filename);
}
else if (f2 && ResourceManager->getPathOf(f2))
{
AudioBuffer *temp = new AudioBuffer(StringTable->insert(f2));
ResourceManager->add(f2, temp);
buffer = ResourceManager->load(f2);
}
}
FrameAllocator::setWaterMark(mark);
return buffer;
}
ResourceInstance* AudioBuffer::construct(Stream &)
{
return NULL;
}
//-----------------------------------------------------------------
ALuint AudioBuffer::getALBuffer()
{
if (!alcGetCurrentContext())
return 0;
// clear the error state
alGetError();
// Intangir> fix for newest openAL from creative (it returns true, yea right 0 is not a valid buffer)
// it MIGHT not work at all for all i know.
if (malBuffer && alIsBuffer(malBuffer))
return malBuffer;
alGenBuffers(1, &malBuffer);
if(alGetError() != AL_NO_ERROR)
return 0;
ResourceObject * obj = ResourceManager->find(mFilename);
if(obj)
{
bool readSuccess = false;
S32 len = dStrlen(mFilename);
if(len > 3 && !dStricmp(mFilename + len - 4, ".wav"))
{
#ifdef LOG_SOUND_LOADS
Con::printf("Reading WAV: %s\n", mFilename);
#endif
readSuccess = readWAV(obj);
}
#ifndef TORQUE_NO_OGGVORBIS
else if(len > 3 && !dStricmp(mFilename + len - 4, ".ogg"))
{
# ifdef LOG_SOUND_LOADS
Con::printf("Reading Ogg: %s\n", mFilename);
# endif
readSuccess = readOgg(obj);
}
#endif
if(readSuccess)
return(malBuffer);
}
alDeleteBuffers(1, &malBuffer);
return 0;
}
/*! The Read a WAV file from the given ResourceObject and initialize
an alBuffer with it.
*/
bool AudioBuffer::readWAV(ResourceObject *obj)
{
WAVChunkHdr chunkHdr;
WAVFmtExHdr fmtExHdr;
WAVFileHdr fileHdr;
WAVSmplHdr smplHdr;
WAVFmtHdr fmtHdr;
ALenum format = AL_FORMAT_MONO16;
char *data = NULL;
ALsizei size = 0;
ALsizei freq = 22050;
ALboolean loop = AL_FALSE;
Stream *stream = ResourceManager->openStream(obj);
if (!stream)
return false;
stream->read(4, &fileHdr.id[0]);
stream->read(&fileHdr.size);
stream->read(4, &fileHdr.type[0]);
fileHdr.size=((fileHdr.size+1)&~1)-4;
stream->read(4, &chunkHdr.id[0]);
stream->read(&chunkHdr.size);
// unread chunk data rounded up to nearest WORD
S32 chunkRemaining = chunkHdr.size + (chunkHdr.size&1);
while ((fileHdr.size!=0) && (stream->getStatus() != Stream::EOS))
{
// WAV Format header
if (!dStrncmp((const char*)chunkHdr.id,"fmt ",4))
{
stream->read(&fmtHdr.format);
stream->read(&fmtHdr.channels);
stream->read(&fmtHdr.samplesPerSec);
stream->read(&fmtHdr.bytesPerSec);
stream->read(&fmtHdr.blockAlign);
stream->read(&fmtHdr.bitsPerSample);
if (fmtHdr.format==0x0001)
{
format=(fmtHdr.channels==1?
(fmtHdr.bitsPerSample==8?AL_FORMAT_MONO8:AL_FORMAT_MONO16):
(fmtHdr.bitsPerSample==8?AL_FORMAT_STEREO8:AL_FORMAT_STEREO16));
freq=fmtHdr.samplesPerSec;
chunkRemaining -= sizeof(WAVFmtHdr);
}
else
{
stream->read(sizeof(WAVFmtExHdr), &fmtExHdr);
chunkRemaining -= sizeof(WAVFmtExHdr);
}
}
// WAV Format header
else if (!dStrncmp((const char*)chunkHdr.id,"data",4))
{
if (fmtHdr.format==0x0001)
{
size=chunkHdr.size;
data=new char[chunkHdr.size];
if (data)
{
stream->read(chunkHdr.size, data);
#if defined(TORQUE_BIG_ENDIAN)
// need to endian-flip the 16-bit data.
if (fmtHdr.bitsPerSample==16) // !!!TBD we don't handle stereo, so may be RL flipped.
{
U16 *ds = (U16*)data;
U16 *de = (U16*)(data+size);
while (ds<de)
{
*ds = convertLEndianToHost(*ds);
ds++;
}
}
#endif
chunkRemaining -= chunkHdr.size;
}
else
break;
}
else if (fmtHdr.format==0x0011)
{
//IMA ADPCM
}
else if (fmtHdr.format==0x0055)
{
//MP3 WAVE
}
}
// WAV Loop header
else if (!dStrncmp((const char*)chunkHdr.id,"smpl",4))
{
// this struct read is NOT endian safe but it is ok because
// we are only testing the loops field against ZERO
stream->read(sizeof(WAVSmplHdr), &smplHdr);
loop = (smplHdr.loops ? AL_TRUE : AL_FALSE);
chunkRemaining -= sizeof(WAVSmplHdr);
}
// either we have unread chunk data or we found an unknown chunk type
// loop and read up to 1K bytes at a time until we have
// read to the end of this chunk
char buffer[1024];
AssertFatal(chunkRemaining >= 0, "AudioBuffer::readWAV: remaining chunk data should never be less than zero.");
while (chunkRemaining > 0)
{
S32 readSize = getMin(1024, chunkRemaining);
stream->read(readSize, buffer);
chunkRemaining -= readSize;
}
fileHdr.size-=(((chunkHdr.size+1)&~1)+8);
// read next chunk header...
stream->read(4, &chunkHdr.id[0]);
stream->read(&chunkHdr.size);
// unread chunk data rounded up to nearest WORD
chunkRemaining = chunkHdr.size + (chunkHdr.size&1);
}
ResourceManager->closeStream(stream);
if (data)
{
alBufferData(malBuffer, format, data, size, freq);
delete [] data;
return (alGetError() == AL_NO_ERROR);
}
return false;
}
#ifndef TORQUE_NO_OGGVORBIS
/*! The Read an Ogg Vorbis file from the given ResourceObject and initialize
an alBuffer with it.
*/
bool AudioBuffer::readOgg(ResourceObject *obj)
{
OggVorbisFile vf;
vorbis_info *vi;
ALenum format = AL_FORMAT_MONO16;
char *data = NULL;
ALsizei size = 0;
ALsizei freq = 22050;
ALboolean loop = AL_FALSE;
int current_section = 0;
#if defined(TORQUE_BIG_ENDIAN)
int endian = 1;
#else
int endian = 0;
#endif
int eof = 0;
Stream *stream = ResourceManager->openStream(obj);
if (!stream)
return false;
if(vf.ov_open(stream, NULL, 0) < 0) {
return false;
}
//Read Vorbis File Info
vi = vf.ov_info(-1);
freq = vi->rate;
long samples = (long)vf.ov_pcm_total(-1);
if(vi->channels == 1) {
format = AL_FORMAT_MONO16;
size = 2 * samples;
} else {
format = AL_FORMAT_STEREO16;
size = 4 * samples;
}
data=new char[size];
if (data)
{
long ret = oggRead(&vf, data, size, endian, &current_section);
}
/* cleanup */
vf.ov_clear();
ResourceManager->closeStream(stream);
if (data)
{
alBufferData(malBuffer, format, data, size, freq);
delete [] data;
return (alGetError() == AL_NO_ERROR);
}
return false;
}
// ov_read() only returns a maximum of one page worth of data
// this helper function will repeat the read until buffer is full
long AudioBuffer::oggRead(OggVorbisFile* vf, char *buffer,int length,
int bigendianp,int *bitstream)
{
long bytesRead = 0;
long totalBytes = 0;
long offset = 0;
long bytesToRead = 0;
//while((offset + CHUNKSIZE) < length) {
while((offset) < length)
{
if((length - offset) < CHUNKSIZE)
bytesToRead = length - offset;
else
bytesToRead = CHUNKSIZE;
bytesRead = vf->ov_read(buffer, bytesToRead, bigendianp, bitstream);
if(bytesRead <= 0)
break;
offset += bytesRead;
buffer += bytesRead;
}
return offset;
}
#endif

56
engine/audio/audioBuffer.h Executable file
View File

@ -0,0 +1,56 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIOBUFFER_H_
#define _AUDIOBUFFER_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _PLATFORMAL_H_
#include "platform/platformAL.h"
#endif
#ifndef _RESMANAGER_H_
#include "core/resManager.h"
#endif
// MLH - don't need oggbvorbis in tools
#ifndef TORQUE_NO_OGGVORBIS
#include "audio/vorbisStream.h"
#endif
//--------------------------------------------------------------------------
class AudioBuffer: public ResourceInstance
{
friend class AudioThread;
private:
StringTableEntry mFilename;
bool mLoading;
ALuint malBuffer;
bool readRIFFchunk(Stream &s, const char *seekLabel, U32 *size);
bool readWAV(ResourceObject *obj);
#ifndef TORQUE_NO_OGGVORBIS
bool readOgg(ResourceObject *obj);
long oggRead(OggVorbisFile* vf, char *buffer,int length,
int bigendianp,int *bitstream);
#endif
public:
AudioBuffer(StringTableEntry filename);
~AudioBuffer();
ALuint getALBuffer();
bool isLoading() {return(mLoading);}
static Resource<AudioBuffer> find(const char *filename);
static ResourceInstance* construct(Stream& stream);
};
#endif // _H_AUDIOBUFFER_

526
engine/audio/audioDataBlock.cc Executable file
View File

@ -0,0 +1,526 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "audio/audioDataBlock.h"
#include "console/consoleTypes.h"
#include "platform/platformAL.h"
#include "sim/netConnection.h"
//--------------------------------------------------------------------------
namespace
{
void writeRangedF32(BitStream * bstream, F32 val, F32 min, F32 max, U32 numBits)
{
val = (mClampF(val, min, max) - min) / (max - min);
bstream->writeInt(val * ((1 << numBits) - 1), numBits);
}
F32 readRangedF32(BitStream * bstream, F32 min, F32 max, U32 numBits)
{
return(min + (F32(bstream->readInt(numBits)) / F32((1 << numBits) - 1)) * (max - min));
}
void writeRangedS32(BitStream * bstream, S32 val, S32 min, S32 max)
{
bstream->writeRangedU32((val - min), 0, (max - min));
}
S32 readRangedS32(BitStream * bstream, S32 min, S32 max)
{
return(bstream->readRangedU32(0, (max - min)) + min);
}
}
//--------------------------------------------------------------------------
// Class AudioEnvironment:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioEnvironment);
AudioEnvironment::AudioEnvironment()
{
mUseRoom = true;
mRoom = EAX_ENVIRONMENT_GENERIC;
mRoomHF = 0;
mReflections = 0;
mReverb = 0;
mRoomRolloffFactor = 0.1f;
mDecayTime = 0.1f;
mDecayHFRatio = 0.1f;
mReflectionsDelay = 0.f;
mReverbDelay = 0.f;
mRoomVolume = 0;
mEffectVolume = 0.f;
mDamping = 0.f;
mEnvironmentSize = 10.f;
mEnvironmentDiffusion = 1.f;
mAirAbsorption = 0.f;
mFlags = 0;
}
static EnumTable::Enums roomEnums[] =
{
{ EAX_ENVIRONMENT_GENERIC, "GENERIC" }, // 0
{ EAX_ENVIRONMENT_PADDEDCELL, "PADDEDCELL" },
{ EAX_ENVIRONMENT_ROOM, "ROOM" },
{ EAX_ENVIRONMENT_BATHROOM, "BATHROOM" },
{ EAX_ENVIRONMENT_LIVINGROOM, "LIVINGROOM" },
{ EAX_ENVIRONMENT_STONEROOM, "STONEROOM" }, // 5
{ EAX_ENVIRONMENT_AUDITORIUM, "AUDITORIUM" },
{ EAX_ENVIRONMENT_CONCERTHALL, "CONCERTHALL" },
{ EAX_ENVIRONMENT_CAVE, "CAVE" },
{ EAX_ENVIRONMENT_ARENA, "ARENA" },
{ EAX_ENVIRONMENT_HANGAR, "HANGAR" }, // 10
{ EAX_ENVIRONMENT_CARPETEDHALLWAY, "CARPETEDHALLWAY" },
{ EAX_ENVIRONMENT_HALLWAY, "HALLWAY" },
{ EAX_ENVIRONMENT_STONECORRIDOR, "STONECORRIDOR" },
{ EAX_ENVIRONMENT_ALLEY, "ALLEY" },
{ EAX_ENVIRONMENT_FOREST, "FOREST" }, // 15
{ EAX_ENVIRONMENT_CITY, "CITY" },
{ EAX_ENVIRONMENT_MOUNTAINS, "MOUNTAINS" },
{ EAX_ENVIRONMENT_QUARRY, "QUARRY" },
{ EAX_ENVIRONMENT_PLAIN, "PLAIN" },
{ EAX_ENVIRONMENT_PARKINGLOT, "PARKINGLOT" }, // 20
{ EAX_ENVIRONMENT_SEWERPIPE, "SEWERPIPE" },
{ EAX_ENVIRONMENT_UNDERWATER, "UNDERWATER" },
{ EAX_ENVIRONMENT_DRUGGED, "DRUGGED" },
{ EAX_ENVIRONMENT_DIZZY, "DIZZY" },
{ EAX_ENVIRONMENT_PSYCHOTIC, "PSYCHOTIC" } // 25
};
static EnumTable gAudioEnvironmentRoomTypes(sizeof(roomEnums) / sizeof(roomEnums[0]), &roomEnums[0]);
//--------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(AudioEnvironment)
IMPLEMENT_GETDATATYPE(AudioEnvironment)
IMPLEMENT_SETDATATYPE(AudioEnvironment)
void AudioEnvironment::initPersistFields()
{
Parent::initPersistFields();
addField("useRoom", TypeBool, Offset(mUseRoom, AudioEnvironment));
addField("room", TypeEnum, Offset(mRoom, AudioEnvironment), 1, &gAudioEnvironmentRoomTypes);
addField("roomHF", TypeS32, Offset(mRoomHF, AudioEnvironment));
addField("reflections", TypeS32, Offset(mReflections, AudioEnvironment));
addField("reverb", TypeS32, Offset(mReverb, AudioEnvironment));
addField("roomRolloffFactor", TypeF32, Offset(mRoomRolloffFactor, AudioEnvironment));
addField("decayTime", TypeF32, Offset(mDecayTime, AudioEnvironment));
addField("decayHFRatio", TypeF32, Offset(mDecayHFRatio, AudioEnvironment));
addField("reflectionsDelay", TypeF32, Offset(mReflectionsDelay, AudioEnvironment));
addField("reverbDelay", TypeF32, Offset(mReverbDelay, AudioEnvironment));
addField("roomVolume", TypeS32, Offset(mRoomVolume, AudioEnvironment));
addField("effectVolume", TypeF32, Offset(mEffectVolume, AudioEnvironment));
addField("damping", TypeF32, Offset(mDamping, AudioEnvironment));
addField("environmentSize", TypeF32, Offset(mEnvironmentSize, AudioEnvironment));
addField("environmentDiffusion", TypeF32, Offset(mEnvironmentDiffusion, AudioEnvironment));
addField("airAbsorption", TypeF32, Offset(mAirAbsorption, AudioEnvironment));
addField("flags", TypeS32, Offset(mFlags, AudioEnvironment));
}
void AudioEnvironment::packData(BitStream* stream)
{
Parent::packData(stream);
if(stream->writeFlag(mUseRoom))
stream->writeRangedU32(mRoom, EAX_ENVIRONMENT_GENERIC, EAX_ENVIRONMENT_COUNT);
else
{
writeRangedS32(stream, mRoomHF, -10000, 0);
writeRangedS32(stream, mReflections, -10000, 10000);
writeRangedS32(stream, mReverb, -10000, 2000);
writeRangedF32(stream, mRoomRolloffFactor, 0.1f, 10.f, 8);
writeRangedF32(stream, mDecayTime, 0.1f, 20.f, 8);
writeRangedF32(stream, mDecayHFRatio, 0.1f, 20.f, 8);
writeRangedF32(stream, mReflectionsDelay, 0.f, 0.3f, 9);
writeRangedF32(stream, mReverbDelay, 0.f, 0.1f, 7);
writeRangedS32(stream, mRoomVolume, -10000, 0);
writeRangedF32(stream, mEffectVolume, 0.f, 1.f, 8);
writeRangedF32(stream, mDamping, 0.f, 2.f, 9);
writeRangedF32(stream, mEnvironmentSize, 1.f, 100.f, 10);
writeRangedF32(stream, mEnvironmentDiffusion, 0.f, 1.f, 8);
writeRangedF32(stream, mAirAbsorption, -100.f, 0.f, 10);
stream->writeInt(mFlags, 6);
}
}
void AudioEnvironment::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mUseRoom = stream->readFlag();
if(mUseRoom)
mRoom = stream->readRangedU32(EAX_ENVIRONMENT_GENERIC, EAX_ENVIRONMENT_COUNT);
else
{
mRoomHF = readRangedS32(stream, -10000, 0);
mReflections = readRangedS32(stream, -10000, 10000);
mReverb = readRangedS32(stream, -10000, 2000);
mRoomRolloffFactor = readRangedF32(stream, 0.1f, 10.f, 8);
mDecayTime = readRangedF32(stream, 0.1f, 20.f, 8);
mDecayHFRatio = readRangedF32(stream, 0.1f, 20.f, 8);
mReflectionsDelay = readRangedF32(stream, 0.f, 0.3f, 9);
mReverbDelay = readRangedF32(stream, 0.f, 0.1f, 7);
mRoomVolume = readRangedS32(stream, -10000, 0);
mEffectVolume = readRangedF32(stream, 0.f, 1.f, 8);
mDamping = readRangedF32(stream, 0.f, 2.f, 9);
mEnvironmentSize = readRangedF32(stream, 1.f, 100.f, 10);
mEnvironmentDiffusion = readRangedF32(stream, 0.f, 1.f, 8);
mAirAbsorption = readRangedF32(stream, -100.f, 0.f, 10);
mFlags = stream->readInt(6);
}
}
//--------------------------------------------------------------------------
// Class AudioEnvironmentProfile:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioSampleEnvironment);
AudioSampleEnvironment::AudioSampleEnvironment()
{
mDirect = 0;
mDirectHF = 0;
mRoom = 0;
mRoomHF = 0;
mObstruction = 0.f;
mObstructionLFRatio = 0.f;
mOcclusion = 0.f;
mOcclusionLFRatio = 0.f;
mOcclusionRoomRatio = 0.f;
mRoomRolloff = 0.f;
mAirAbsorption = 0.f;
mOutsideVolumeHF = 0.f;
mFlags = 0;
}
//--------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(AudioSampleEnvironment)
IMPLEMENT_GETDATATYPE(AudioSampleEnvironment)
IMPLEMENT_SETDATATYPE(AudioSampleEnvironment)
void AudioSampleEnvironment::initPersistFields()
{
Parent::initPersistFields();
addField("direct", TypeS32, Offset(mDirect, AudioSampleEnvironment));
addField("directHF", TypeS32, Offset(mDirectHF, AudioSampleEnvironment));
addField("room", TypeS32, Offset(mRoom, AudioSampleEnvironment));
addField("obstruction", TypeF32, Offset(mObstruction, AudioSampleEnvironment));
addField("obstructionLFRatio", TypeF32, Offset(mObstructionLFRatio, AudioSampleEnvironment));
addField("occlusion", TypeF32, Offset(mOcclusion, AudioSampleEnvironment));
addField("occlusionLFRatio", TypeF32, Offset(mOcclusionLFRatio, AudioSampleEnvironment));
addField("occlusionRoomRatio", TypeF32, Offset(mOcclusionRoomRatio, AudioSampleEnvironment));
addField("roomRolloff", TypeF32, Offset(mRoomRolloff, AudioSampleEnvironment));
addField("airAbsorption", TypeF32, Offset(mAirAbsorption, AudioSampleEnvironment));
addField("outsideVolumeHF", TypeS32, Offset(mOutsideVolumeHF, AudioSampleEnvironment));
addField("flags", TypeS32, Offset(mFlags, AudioSampleEnvironment));
}
void AudioSampleEnvironment::packData(BitStream* stream)
{
Parent::packData(stream);
writeRangedS32(stream, mDirect, -10000, 1000);
writeRangedS32(stream, mDirectHF, -10000, 0);
writeRangedS32(stream, mRoom, -10000, 1000);
writeRangedS32(stream, mRoomHF, -10000, 0);
writeRangedF32(stream, mObstruction, 0.f, 1.f, 9);
writeRangedF32(stream, mObstructionLFRatio, 0.f, 1.f, 8);
writeRangedF32(stream, mOcclusion, 0.f, 1.f, 9);
writeRangedF32(stream, mOcclusionLFRatio, 0.f, 1.f, 8);
writeRangedF32(stream, mOcclusionRoomRatio, 0.f, 10.f, 9);
writeRangedF32(stream, mRoomRolloff, 0.f, 10.f, 9);
writeRangedF32(stream, mAirAbsorption, 0.f, 10.f, 9);
writeRangedS32(stream, mOutsideVolumeHF, -10000, 0);
stream->writeInt(mFlags, 3);
}
void AudioSampleEnvironment::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mDirect = readRangedS32(stream, -10000, 1000);
mDirectHF = readRangedS32(stream, -10000, 0);
mRoom = readRangedS32(stream, -10000, 1000);
mRoomHF = readRangedS32(stream, -10000, 0);
mObstruction = readRangedF32(stream, 0.f, 1.f, 9);
mObstructionLFRatio = readRangedF32(stream, 0.f, 1.f, 8);
mOcclusion = readRangedF32(stream, 0.f, 1.f, 9);
mOcclusionLFRatio = readRangedF32(stream, 0.f, 1.f, 8);
mOcclusionRoomRatio = readRangedF32(stream, 0.f, 10.f, 9);
mRoomRolloff = readRangedF32(stream, 0.f, 10.f, 9);
mAirAbsorption = readRangedF32(stream, 0.f, 10.f, 9);
mOutsideVolumeHF = readRangedS32(stream, -10000, 0);
mFlags = stream->readInt(3);
}
//--------------------------------------------------------------------------
// Class AudioDescription:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioDescription);
AudioDescription::AudioDescription()
{
mDescription.mVolume = 1.0f;
mDescription.mIsLooping = false;
mDescription.mIsStreaming = false;
mDescription.mIs3D = false;
mDescription.mReferenceDistance = 1.0f;
mDescription.mMaxDistance = 100.0f;
mDescription.mConeInsideAngle = 360;
mDescription.mConeOutsideAngle = 360;
mDescription.mConeOutsideVolume = 1.0f;
mDescription.mConeVector.set(0, 0, 1);
mDescription.mEnvironmentLevel = 0.f;
mDescription.mLoopCount = -1;
mDescription.mMinLoopGap = 0;
mDescription.mMaxLoopGap = 0;
mDescription.mType = 0;
}
//--------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(AudioDescription)
IMPLEMENT_GETDATATYPE(AudioDescription)
IMPLEMENT_SETDATATYPE(AudioDescription)
void AudioDescription::initPersistFields()
{
Parent::initPersistFields();
addField("volume", TypeF32, Offset(mDescription.mVolume, AudioDescription));
addField("isLooping", TypeBool, Offset(mDescription.mIsLooping, AudioDescription));
addField("isStreaming", TypeBool, Offset(mDescription.mIsStreaming, AudioDescription));
addField("is3D", TypeBool, Offset(mDescription.mIs3D, AudioDescription));
addField("referenceDistance", TypeF32, Offset(mDescription.mReferenceDistance, AudioDescription));
addField("maxDistance", TypeF32, Offset(mDescription.mMaxDistance, AudioDescription));
addField("coneInsideAngle", TypeS32, Offset(mDescription.mConeInsideAngle, AudioDescription));
addField("coneOutsideAngle", TypeS32, Offset(mDescription.mConeOutsideAngle, AudioDescription));
addField("coneOutsideVolume", TypeF32, Offset(mDescription.mConeOutsideVolume, AudioDescription));
addField("coneVector", TypePoint3F, Offset(mDescription.mConeVector, AudioDescription));
addField("environmentLevel", TypeF32, Offset(mDescription.mEnvironmentLevel, AudioDescription));
addField("loopCount", TypeS32, Offset(mDescription.mLoopCount, AudioDescription));
addField("minLoopGap", TypeS32, Offset(mDescription.mMinLoopGap, AudioDescription));
addField("maxLoopGap", TypeS32, Offset(mDescription.mMaxLoopGap, AudioDescription));
addField("type", TypeS32, Offset(mDescription.mType, AudioDescription));
}
//--------------------------------------------------------------------------
bool AudioDescription::onAdd()
{
if (!Parent::onAdd())
return false;
// validate the data
mDescription.mVolume = mClampF(mDescription.mVolume, 0.0f, 1.0f);
mDescription.mLoopCount = mClamp(mDescription.mLoopCount, -1, mDescription.mLoopCount);
mDescription.mMaxLoopGap = mClamp(mDescription.mMaxLoopGap, mDescription.mMinLoopGap, mDescription.mMaxLoopGap);
mDescription.mMinLoopGap = mClamp(mDescription.mMinLoopGap, 0, mDescription.mMaxLoopGap);
if (mDescription.mIs3D)
{
// validate the data
mDescription.mReferenceDistance = mClampF(mDescription.mReferenceDistance, 0.f, mDescription.mReferenceDistance);
mDescription.mMaxDistance = (mDescription.mMaxDistance > mDescription.mReferenceDistance) ? mDescription.mMaxDistance : (mDescription.mReferenceDistance+0.01f);
mDescription.mConeInsideAngle = mClamp(mDescription.mConeInsideAngle, 0, 360);
mDescription.mConeOutsideAngle = mClamp(mDescription.mConeOutsideAngle, mDescription.mConeInsideAngle, 360);
mDescription.mConeOutsideVolume = mClampF(mDescription.mConeOutsideVolume, 0.0f, 1.0f);
mDescription.mConeVector.normalize();
mDescription.mEnvironmentLevel = mClampF(mDescription.mEnvironmentLevel, 0.f, 1.f);
}
if(mDescription.mType >= Audio::NumAudioTypes)
mDescription.mType = 0;
return true;
}
//--------------------------------------------------------------------------
void AudioDescription::packData(BitStream* stream)
{
Parent::packData(stream);
stream->writeFloat(mDescription.mVolume, 6);
if(stream->writeFlag(mDescription.mIsLooping))
{
stream->write(mDescription.mLoopCount);
stream->write(mDescription.mMinLoopGap);
stream->write(mDescription.mMaxLoopGap);
}
stream->writeFlag(mDescription.mIsStreaming);
stream->writeFlag(mDescription.mIs3D);
if (mDescription.mIs3D)
{
stream->write(mDescription.mReferenceDistance);
stream->write(mDescription.mMaxDistance);
stream->writeInt(mDescription.mConeInsideAngle, 9);
stream->writeInt(mDescription.mConeOutsideAngle, 9);
stream->writeInt(mDescription.mConeOutsideVolume, 6);
stream->writeNormalVector(mDescription.mConeVector, 8);
stream->write(mDescription.mEnvironmentLevel);
}
stream->writeInt(mDescription.mType, 3);
}
//--------------------------------------------------------------------------
void AudioDescription::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mDescription.mVolume = stream->readFloat(6);
mDescription.mIsLooping = stream->readFlag();
if(mDescription.mIsLooping)
{
stream->read(&mDescription.mLoopCount);
stream->read(&mDescription.mMinLoopGap);
stream->read(&mDescription.mMaxLoopGap);
}
mDescription.mIsStreaming = stream->readFlag();
mDescription.mIs3D = stream->readFlag();
if ( mDescription.mIs3D )
{
stream->read(&mDescription.mReferenceDistance);
stream->read(&mDescription.mMaxDistance);
mDescription.mConeInsideAngle = stream->readInt(9);
mDescription.mConeOutsideAngle = stream->readInt(9);
mDescription.mConeOutsideVolume = stream->readFloat(6);
stream->readNormalVector(&mDescription.mConeVector, 8);
stream->read(&mDescription.mEnvironmentLevel);
}
mDescription.mType = stream->readInt(3);
}
//--------------------------------------------------------------------------
// Class AudioProfile:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioProfile);
AudioProfile::AudioProfile()
{
mFilename = NULL;
mDescriptionObject = NULL;
mSampleEnvironment = 0;
mPreload = false;
}
//--------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(AudioProfile)
IMPLEMENT_GETDATATYPE(AudioProfile)
IMPLEMENT_SETDATATYPE(AudioProfile)
void AudioProfile::initPersistFields()
{
Parent::initPersistFields();
addField("filename", TypeFilename, Offset(mFilename, AudioProfile));
addField("description", TypeAudioDescriptionPtr, Offset(mDescriptionObject, AudioProfile));
addField("environment", TypeAudioSampleEnvironmentPtr, Offset(mSampleEnvironment, AudioProfile));
addField("preload", TypeBool, Offset(mPreload, AudioProfile));
}
bool AudioProfile::preload(bool server, char errorBuffer[256])
{
if(!Parent::preload(server, errorBuffer))
return false;
if(!server && NetConnection::filesWereDownloaded() && !bool(AudioBuffer::find(mFilename)))
return false;
return true;
}
//--------------------------------------------------------------------------
bool AudioProfile::onAdd()
{
if (!Parent::onAdd())
return false;
// if this is client side, make sure that description is as well
if(mDescriptionObject)
{ // client side dataBlock id's are not in the dataBlock id range
if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast)
{
SimObjectId pid = mDescriptionObject->getId();
if (pid < DataBlockObjectIdFirst || pid > DataBlockObjectIdLast)
{
Con::errorf(ConsoleLogEntry::General,"AudioProfile: data dataBlock not networkable (use datablock to create).");
return false;
}
}
}
if(mPreload && mFilename != NULL && alcGetCurrentContext())
{
mBuffer = AudioBuffer::find(mFilename);
if(bool(mBuffer))
{
ALuint bufferId = mBuffer->getALBuffer();
}
}
return(true);
}
//--------------------------------------------------------------------------
void AudioProfile::packData(BitStream* stream)
{
Parent::packData(stream);
// audio description:
if (stream->writeFlag(mDescriptionObject))
stream->writeRangedU32(mDescriptionObject->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
// environmental info:
if (stream->writeFlag(mSampleEnvironment))
stream->writeRangedU32(mSampleEnvironment->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
//
char buffer[256];
if(!mFilename)
buffer[0] = 0;
else
dStrcpy(buffer, mFilename);
// S32 len = dStrlen(buffer);
// if(len > 3 && !dStricmp(buffer + len - 4, ".wav"))
// buffer[len-4] = 0;
stream->writeString(buffer);
}
//--------------------------------------------------------------------------
void AudioProfile::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
// audio datablock:
if (stream->readFlag()) {
SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst,
DataBlockObjectIdLast);
Sim::findObject(id, mDescriptionObject);
}
// sample environment:
if (stream->readFlag()) {
SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst,
DataBlockObjectIdLast);
Sim::findObject(id, mSampleEnvironment);
}
char buffer[256];
stream->readString(buffer);
// dStrcat(buffer, ".wav");
mFilename = StringTable->insert(buffer);
// Doh! Something missing from the unpackData...don't want to break
// network protocol, so set it to true always here. This is good for
// ThinkTanks only. In the future, simply send the preload bit.
mPreload = true;
}

139
engine/audio/audioDataBlock.h Executable file
View File

@ -0,0 +1,139 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIODATABLOCK_H_
#define _AUDIODATABLOCK_H_
#ifndef _PLATFORMAUDIO_H_
#include "platform/platformAudio.h"
#endif
#ifndef _AUDIOBUFFER_H_
#include "audio/audioBuffer.h"
#endif
#ifndef _BITSTREAM_H_
#include "core/bitStream.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
//--------------------------------------------------------------------------
class AudioEnvironment : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
bool mUseRoom;
S32 mRoom;
S32 mRoomHF;
S32 mReflections;
S32 mReverb;
F32 mRoomRolloffFactor;
F32 mDecayTime;
F32 mDecayHFRatio;
F32 mReflectionsDelay;
F32 mReverbDelay;
S32 mRoomVolume;
F32 mEffectVolume;
F32 mDamping;
F32 mEnvironmentSize;
F32 mEnvironmentDiffusion;
F32 mAirAbsorption;
S32 mFlags;
AudioEnvironment();
static void initPersistFields();
void packData(BitStream* stream);
void unpackData(BitStream* stream);
DECLARE_CONOBJECT(AudioEnvironment);
};
DECLARE_CONSOLETYPE(AudioEnvironment)
//--------------------------------------------------------------------------
class AudioSampleEnvironment : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
S32 mDirect;
S32 mDirectHF;
S32 mRoom;
S32 mRoomHF;
F32 mObstruction;
F32 mObstructionLFRatio;
F32 mOcclusion;
F32 mOcclusionLFRatio;
F32 mOcclusionRoomRatio;
F32 mRoomRolloff;
F32 mAirAbsorption;
S32 mOutsideVolumeHF;
S32 mFlags;
AudioSampleEnvironment();
static void initPersistFields();
void packData(BitStream* stream);
void unpackData(BitStream* stream);
DECLARE_CONOBJECT(AudioSampleEnvironment);
};
DECLARE_CONSOLETYPE(AudioSampleEnvironment)
//--------------------------------------------------------------------------
class AudioDescription: public SimDataBlock
{
private:
typedef SimDataBlock Parent;
public:
// field info
Audio::Description mDescription;
AudioDescription();
DECLARE_CONOBJECT(AudioDescription);
static void initPersistFields();
virtual bool onAdd();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
const Audio::Description* getDescription() const { return &mDescription; }
};
DECLARE_CONSOLETYPE(AudioDescription)
//----------------------------------------------------------------------------
class AudioProfile: public SimDataBlock
{
private:
typedef SimDataBlock Parent;
Resource<AudioBuffer> mBuffer;
public:
// field info
AudioDescription *mDescriptionObject;
AudioSampleEnvironment *mSampleEnvironment;
StringTableEntry mFilename;
bool mPreload;
AudioProfile();
DECLARE_CONOBJECT(AudioProfile);
static void initPersistFields();
virtual bool onAdd();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
virtual bool preload(bool server, char errorBuffer[256]);
const Audio::Description* getDescription() const { return mDescriptionObject ? mDescriptionObject->getDescription() : NULL; }
bool isPreload() { return mPreload; }
};
DECLARE_CONSOLETYPE(AudioProfile)
#endif // _H_AUDIODATABLOCK_

554
engine/audio/audioFunctions.cc Executable file
View File

@ -0,0 +1,554 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "platform/platformAudio.h"
#include "console/simBase.h"
#include "audio/audioDataBlock.h"
extern ALuint alxGetWaveLen(ALuint buffer);
extern F32 mAudioTypeVolume[Audio::NumAudioTypes];
//--------------------------------------------------------------------------
// Expose all al get/set methods...
//--------------------------------------------------------------------------
enum AL_GetSetBits{
Source = BIT(0),
Listener = BIT(1),
Context = BIT(2),
Environment = BIT(3),
Get = BIT(4),
Set = BIT(5),
Int = BIT(6),
Float = BIT(7),
Float3 = BIT(8),
Float6 = BIT(9)
};
static ALenum getEnum(const char * name, U32 flags)
{
AssertFatal(name, "Audio getEnum: bad param");
static struct {
char * mName;
ALenum mAlenum;
U32 mFlags;
} table[] = {
//-----------------------------------------------------------------------------------------------------------------
// "name" ENUM Flags
//-----------------------------------------------------------------------------------------------------------------
{ "AL_GAIN", AL_GAIN, (Source|Listener|Get|Set|Float) },
{ "AL_GAIN_LINEAR", AL_GAIN_LINEAR, (Source|Listener|Get|Set|Float) },
{ "AL_PITCH", AL_PITCH, (Source|Get|Set|Float) },
{ "AL_REFERENCE_DISTANCE", AL_REFERENCE_DISTANCE, (Source|Get|Set|Float) },
{ "AL_MAX_DISTANCE", AL_MAX_DISTANCE, (Source|Get|Set|Float) },
{ "AL_CONE_OUTER_GAIN", AL_CONE_OUTER_GAIN, (Source|Get|Set|Float) },
{ "AL_POSITION", AL_POSITION, (Source|Listener|Get|Set|Float3) },
{ "AL_DIRECTION", AL_DIRECTION, (Source|Get|Set|Float3) },
{ "AL_VELOCITY", AL_VELOCITY, (Source|Listener|Get|Set|Float3) },
{ "AL_ORIENTATION", AL_ORIENTATION, (Listener|Set|Float6) },
{ "AL_CONE_INNER_ANGLE", AL_CONE_INNER_ANGLE, (Source|Get|Set|Int) },
{ "AL_CONE_OUTER_ANGLE", AL_CONE_OUTER_ANGLE, (Source|Get|Set|Int) },
{ "AL_LOOPING", AL_LOOPING, (Source|Get|Set|Int) },
//{ "AL_STREAMING", AL_STREAMING, (Source|Get|Set|Int) },
//{ "AL_BUFFER", AL_BUFFER, (Source|Get|Set|Int) },
{ "AL_VENDOR", AL_VENDOR, (Context|Get) },
{ "AL_VERSION", AL_VERSION, (Context|Get) },
{ "AL_RENDERER", AL_RENDERER, (Context|Get) },
{ "AL_EXTENSIONS", AL_EXTENSIONS, (Context|Get) },
/*
// environment
{ "AL_ENV_ROOM_IASIG", AL_ENV_ROOM_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_ROOM_HIGH_FREQUENCY_IASIG", AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_REFLECTIONS_IASIG", AL_ENV_REFLECTIONS_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_REVERB_IASIG", AL_ENV_REVERB_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG", AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DECAY_TIME_IASIG", AL_ENV_DECAY_TIME_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG", AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_REFLECTIONS_DELAY_IASIG", AL_ENV_REFLECTIONS_DELAY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_REVERB_DELAY_IASIG", AL_ENV_REVERB_DELAY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DIFFUSION_IASIG", AL_ENV_DIFFUSION_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DENSITY_IASIG", AL_ENV_DENSITY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG", AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_ROOM_VOLUME_EXT", AL_ENV_ROOM_VOLUME_EXT, (Environment|Get|Set|Int) },
{ "AL_ENV_FLAGS_EXT", AL_ENV_FLAGS_EXT, (Environment|Get|Set|Int) },
{ "AL_ENV_EFFECT_VOLUME_EXT", AL_ENV_EFFECT_VOLUME_EXT, (Environment|Get|Set|Float) },
{ "AL_ENV_DAMPING_EXT", AL_ENV_DAMPING_EXT, (Environment|Get|Set|Float) },
{ "AL_ENV_ENVIRONMENT_SIZE_EXT", AL_ENV_ENVIRONMENT_SIZE_EXT, (Environment|Get|Set|Float) },
// sample environment
{ "AL_ENV_SAMPLE_DIRECT_EXT", AL_ENV_SAMPLE_DIRECT_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_DIRECT_HF_EXT", AL_ENV_SAMPLE_DIRECT_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_ROOM_EXT", AL_ENV_SAMPLE_ROOM_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_ROOM_HF_EXT", AL_ENV_SAMPLE_ROOM_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT", AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_FLAGS_EXT", AL_ENV_SAMPLE_FLAGS_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_REVERB_MIX_EXT", AL_ENV_SAMPLE_REVERB_MIX_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OBSTRUCTION_EXT", AL_ENV_SAMPLE_OBSTRUCTION_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT", AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_EXT", AL_ENV_SAMPLE_OCCLUSION_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT", AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_AIR_ABSORPTION_EXT", AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, (Source|Get|Set|Float) },
*/
};
for(U32 i = 0; i < (sizeof(table) / sizeof(table[0])); i++)
{
if((table[i].mFlags & flags) != flags)
continue;
if(dStricmp(table[i].mName, name) == 0)
return(table[i].mAlenum);
}
return(AL_INVALID);
}
//-----------------------------------------------
ConsoleFunctionGroupBegin(Audio, "Functions dealing with the OpenAL audio layer.\n\n"
"@see www.OpenAL.org for what these functions do. Variances from posted"
" behaviour is described below.");
ConsoleFunction(OpenALInitDriver, bool, 1, 1, "Initializes the OpenAL driver.\n\n"
"@note You must call this before any sounds will work!")
{
if (Audio::OpenALInit())
{
static bool registered = false;
if (!registered) {
ResourceManager->registerExtension(".wav", AudioBuffer::construct);
ResourceManager->registerExtension(".ogg", AudioBuffer::construct);
}
registered = true;
return true;
}
return false;
}
//-----------------------------------------------
ConsoleFunction(OpenALShutdownDriver, void, 1, 1, "OpenALShutdownDriver()")
{
Audio::OpenALShutdown();
}
//-----------------------------------------------
ConsoleFunction(OpenALRegisterExtensions, void, 1, 1, "OpenALRegisterExtensions()")
{
}
//-----------------------------------------------
ConsoleFunction(alGetString, const char *, 2, 2, "(string item)\n\n"
"This wraps alGetString().")
{
argc;
ALenum e = getEnum(argv[1], (Context|Get));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alGetString: invalid enum name '%s'", argv[1]);
return "";
}
return (const char*)alGetString(e);
}
//--------------------------------------------------------------------------
// Soundfile
//--------------------------------------------------------------------------
ConsoleFunction(alxGetWaveLen, S32, 2, 2, "(string filename)\n\n"
"@param filename File to determine length of.\n"
"@returns Length in milliseconds.")
{
Resource<AudioBuffer> buffer = AudioBuffer::find(argv[1]);
if (bool(buffer)) {
ALuint alBuffer = buffer->getALBuffer();
return alxGetWaveLen(alBuffer);
}
else {
return 0;
}
}
//--------------------------------------------------------------------------
// Source
//--------------------------------------------------------------------------
ConsoleFunction(alxCreateSource, S32, 2, 6,
"(profile) or "
"(profile, x,y,z) or "
"(description, filename) or "
"(description, filename, x,y,z)")
{
AudioDescription *description = NULL;
AudioProfile *profile = dynamic_cast<AudioProfile*>( Sim::findObject( argv[1] ) );
if (profile == NULL)
{
description = dynamic_cast<AudioDescription*>( Sim::findObject( argv[1] ) );
if (description == NULL)
{
Con::printf("Unable to locate audio profile/description '%s'", argv[1]);
return NULL_AUDIOHANDLE;
}
}
if (profile)
{
if (argc == 2)
return alxCreateSource(profile);
MatrixF transform;
transform.set(EulerF(0,0,0), Point3F( dAtof(argv[2]),dAtof(argv[3]),dAtof(argv[4]) ));
return alxCreateSource(profile, &transform);
}
if (description)
{
if (argc == 3)
return alxCreateSource(description, argv[2]);
MatrixF transform;
transform.set(EulerF(0,0,0), Point3F( dAtof(argv[3]),dAtof(argv[4]),dAtof(argv[5]) ));
return alxCreateSource(description, argv[2], &transform);
}
return NULL_AUDIOHANDLE;
}
//-----------------------------------------------
ConsoleFunction(alxSourcef, void, 4, 4, "(handle, ALenum, value)")
{
ALenum e = getEnum(argv[2], (Source|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcef: invalid enum name '%s'", argv[2]);
return;
}
alxSourcef(dAtoi(argv[1]), e, dAtof(argv[3]));
}
//-----------------------------------------------
ConsoleFunction(alxSource3f, void, 3, 6, "(handle, ALenum, x, y, z)\n\n"
"@note You can replace the last three parameters with a string, "
"\"x y z\"")
{
ALenum e = getEnum(argv[2], (Source|Set|Float3));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: invalid enum name '%s'", argv[2]);
return;
}
if((argc != 4 && argc != 6))
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: wrong number of args");
return;
}
Point3F pos;
if(argc == 4)
dSscanf(argv[3], "%g %g %g", &pos.x, &pos.y, &pos.z);
else
{
pos.x = dAtof(argv[3]);
pos.y = dAtof(argv[4]);
pos.z = dAtof(argv[5]);
}
alxSource3f(dAtoi(argv[1]), e, pos.x, pos.y, pos.z);
}
//-----------------------------------------------
ConsoleFunction(alxSourcei, void, 4, 4, "(handle, ALenum, value)")
{
ALenum e = getEnum(argv[2], (Source|Set|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcei: invalid enum name '%s'", argv[2]);
return;
}
alxSourcei(dAtoi(argv[1]), e, dAtoi(argv[3]));
}
//-----------------------------------------------
ConsoleFunction(alxGetSourcef, F32, 3, 3, "(handle, ALenum)")
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcef: invalid enum name '%s'", argv[2]);
return(0.f);
}
F32 value;
alxGetSourcef(dAtoi(argv[1]), e, &value);
return(value);
}
//-----------------------------------------------
ConsoleFunction(alxGetSource3f, const char *, 3, 3, "(handle, ALenum)" )
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSource3f: invalid enum name '%s'", argv[2]);
return("0 0 0");
}
F32 value1, value2, value3;
alxGetSource3f(dAtoi(argv[1]), e, &value1, &value2, &value3);
char * ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3);
return(ret);
}
//-----------------------------------------------
ConsoleFunction(alxGetSourcei, S32, 3, 3, "(handle, ALenum)")
{
ALenum e = getEnum(argv[2], (Source|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcei: invalid enum name '%s'", argv[2]);
return(0);
}
S32 value;
alxGetSourcei(dAtoi(argv[1]), e, &value);
return(value);
}
//-----------------------------------------------
ConsoleFunction(alxPlay, S32, 2, 5, "alxPlay(handle) or "
"alxPlay(profile) or "
"alxPlay(profile, x,y,z)")
{
if (argc == 2)
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if (handle != 0)
return alxPlay(handle);
}
AudioProfile *profile = dynamic_cast<AudioProfile*>( Sim::findObject( argv[1] ) );
if (profile == NULL)
{
Con::printf("Unable to locate audio profile '%s'", argv[1]);
return NULL_AUDIOHANDLE;
}
Point3F pos(0.f, 0.f, 0.f);
if(argc == 3)
dSscanf(argv[2], "%g %g %g", &pos.x, &pos.y, &pos.z);
else if(argc == 5)
pos.set(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]));
MatrixF transform;
transform.set(EulerF(0,0,0), pos);
return alxPlay(profile, &transform, NULL);
}
//-----------------------------------------------
ConsoleFunction(alxStop, void, 2, 2, "(int handle)")
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return;
alxStop(handle);
}
//-----------------------------------------------
ConsoleFunction(alxStopAll, void, 1, 1, "()")
{
alxStopAll();
}
//-----------------------------------------------
ConsoleFunction(alxIsPlaying, bool, 2, 5, "alxIsPlaying(handle)")
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return false;
return alxIsPlaying(handle);
}
//--------------------------------------------------------------------------
// Listener
//--------------------------------------------------------------------------
ConsoleFunction(alxListenerf, void, 3, 3, "alxListener(ALenum, value)")
{
ALenum e = getEnum(argv[1], (Listener|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alxListenerf: invalid enum name '%s'", argv[1]);
return;
}
alxListenerf(e, dAtof(argv[2]));
}
//-----------------------------------------------
ConsoleFunction(alListener3f, void, 3, 5, "alListener3f(ALenum, \"x y z\") or "
"alListener3f(ALenum, x, y, z)")
{
ALenum e = getEnum(argv[1], (Listener|Set|Float3));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alListener3f: invalid enum name '%s'", argv[1]);
return;
}
if(argc != 3 || argc != 5)
{
Con::errorf(ConsoleLogEntry::General, "alListener3f: wrong number of arguments");
return;
}
Point3F pos;
if(argc == 3)
dSscanf(argv[2], "%g %g %g", &pos.x, &pos.y, &pos.z);
else
{
pos.x = dAtof(argv[2]);
pos.y = dAtof(argv[3]);
pos.z = dAtof(argv[4]);
}
alListener3f(e, pos.x, pos.y, pos.z);
}
//-----------------------------------------------
ConsoleFunction(alxGetListenerf, F32, 2, 2, "alxGetListenerf(Alenum)")
{
ALenum e = getEnum(argv[1], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alxGetListenerf: invalid enum name '%s'", argv[1]);
return(0.f);
}
F32 value;
alxGetListenerf(e, &value);
return(value);
}
//-----------------------------------------------
ConsoleFunction(alGetListener3f, const char *, 2, 2, "alGetListener3f(Alenum)")
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alGetListener3f: invalid enum name '%s'", argv[1]);
return("0 0 0");
}
F32 value1, value2, value3;
alGetListener3f(e, &value1, &value2, &value3);
char * ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3);
return(ret);
}
//-----------------------------------------------
ConsoleFunction(alGetListeneri, S32, 2, 2, "alGetListeneri(Alenum)")
{
ALenum e = getEnum(argv[1], (Source|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alGetListeneri: invalid enum name '%s'", argv[1]);
return(0);
}
S32 value;
alGetListeneri(e, &value);
return(value);
}
//--------------------------------------------------------------------------
// Channel Volumes
//--------------------------------------------------------------------------
ConsoleFunction(alxGetChannelVolume, F32, 2, 2, "(int channel_id)\n\n"
"@param channel_id ID of channel to fetch volume from.\n"
"@return Volume of channel.")
{
U32 type = dAtoi(argv[1]);
if(type >= Audio::NumAudioTypes)
{
Con::errorf(ConsoleLogEntry::General, "alxGetChannelVolume: invalid channel '%d'", dAtoi(argv[1]));
return(0.f);
}
return(mAudioTypeVolume[type]);
}
//-----------------------------------------------
ConsoleFunction(alxSetChannelVolume, bool, 3, 3, "(int channel_id, float volume)\n\n"
"@param channel_id ID of channel to set volume on.\n"
"@param volume New volume of channel, from 0.0f-1.0f"
)
{
U32 type = dAtoi(argv[1]);
F32 volume = mClampF(dAtof(argv[2]), 0.f, 1.f);
if(type >= Audio::NumAudioTypes)
{
Con::errorf(ConsoleLogEntry::General, "alxSetChannelVolume: channel '%d' out of range [0, %d]", dAtoi(argv[1]), Audio::NumAudioTypes);
return false;
}
mAudioTypeVolume[type] = volume;
alxUpdateTypeGain(type);
return true;
}
//-----------------------------------------------
ConsoleFunction(alxGetStreamPosition, F32, 2, 2, "alxGetStreamPosition(handle)" )
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return -1;
return alxGetStreamPosition( handle );
}
//-----------------------------------------------
ConsoleFunction(alxGetStreamDuration, F32, 2, 2, "alxGetStreamDuration(handle)" )
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return -1;
return alxGetStreamDuration( handle );
}
ConsoleFunctionGroupEnd(Audio);

View File

@ -0,0 +1,64 @@
//--------------------------------------------
// audioStreamSource.h
// header for streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------------
#ifndef _AUDIOSTREAMSOURCE_H_
#define _AUDIOSTREAMSOURCE_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _PLATFORMAUDIO_H_
#include "platform/platformAudio.h"
#endif
#ifndef _PLATFORMAL_H_
#include "platform/platformAL.h"
#endif
#ifndef _AUDIOBUFFER_H_
#include "audio/audioBuffer.h"
#endif
#ifndef _RESMANAGER_H_
#include "core/resManager.h"
#endif
#define NUMBUFFERS 16
class AudioStreamSource
{
public:
//need this because subclasses are deleted through base pointer
virtual ~AudioStreamSource() {}
virtual bool initStream() = 0;
virtual bool updateBuffers() = 0;
virtual void freeStream() = 0;
virtual F32 getElapsedTime() = 0;
virtual F32 getTotalTime() = 0;
//void clear();
AUDIOHANDLE mHandle;
ALuint mSource;
Audio::Description mDescription;
AudioSampleEnvironment *mEnvironment;
Point3F mPosition;
Point3F mDirection;
F32 mPitch;
F32 mScore;
U32 mCullTime;
bool bFinishedPlaying;
bool bIsValid;
#ifdef TORQUE_OS_LINUX
void checkPosition();
#endif
protected:
const char* mFilename;
};
#endif // _AUDIOSTREAMSOURCE_H_

View File

@ -0,0 +1,33 @@
//--------------------------------------
// audioStreamSource.cc
// implementation of streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------
#include "audio/audioStreamSourceFactory.h"
#include "audio/wavStreamSource.h"
#ifndef TORQUE_NO_OGGVORBIS
#include "audio/vorbisStreamSource.h"
#include "audio/oggMixedStreamSource.h"
#endif
AudioStreamSource* AudioStreamSourceFactory::getNewInstance(const char *filename)
{
#ifndef TORQUE_NO_OGGVORBIS
if(!dStricmp(filename, "oggMixedStream"))
return new OggMixedStreamSource(filename);
S32 len = dStrlen(filename);
// Check filename extension and guess filetype from that
if(len > 3 && !dStricmp(filename + len - 4, ".wav"))
return new WavStreamSource(filename);
if(len > 3 && !dStricmp(filename + len - 4, ".ogg"))
return new VorbisStreamSource(filename);
#endif
return NULL;
}

View File

@ -0,0 +1,30 @@
//--------------------------------------------
// audioStreamSource.h
// header for streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------------
#ifndef _AUDIOSTREAMSOURCEFACTORY_H_
#define _AUDIOSTREAMSOURCEFACTORY_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _PLATFORMAUDIO_H_
#include "platform/platformAudio.h"
#endif
#ifndef _PLATFORMAL_H_
#include "platform/platformAL.h"
#endif
#ifndef _AUDIOSTREAMSOURCE_H_
#include "audio/audioStreamSource.h"
#endif
class AudioStreamSourceFactory
{
public:
static AudioStreamSource* getNewInstance(const char* filename);
};
#endif // _AUDIOSTREAMSOURCEFACTORY_H_

View File

@ -0,0 +1,133 @@
//--------------------------------------
//
// This class is basically a buffer that is filled from
// the ogg stream the theoraplayer has open
//
//--------------------------------------
#include "audio/oggMixedStreamSource.h"
OggMixedStreamSource::OggMixedStreamSource(const char *filename)
{
bIsValid = false;
bBuffersAllocated = false;
for(int i = 0; i < BUFFERCNT; i++)
{
mBufferList[i] = 0;
m_fBufferInUse[i] = false;
}
mHandle = NULL_AUDIOHANDLE;
mSource = NULL;
mFilename = filename;
mPosition = Point3F(0.f,0.f,0.f);
dMemset(&mDescription, 0, sizeof(Audio::Description));
mEnvironment = 0;
mPosition.set(0.f,0.f,0.f);
mDirection.set(0.f,1.f,0.f);
mPitch = 1.f;
mScore = 0.f;
mCullTime = 0;
bFinishedPlaying = false;
bIsValid = false;
bBuffersAllocated = false;
}
OggMixedStreamSource::~OggMixedStreamSource()
{
if(bIsValid)
freeStream();
}
bool OggMixedStreamSource::initStream()
{
alSourceStop(mSource);
alSourcei(mSource, AL_BUFFER, 0);
// Clear Error Code
alGetError();
alGenBuffers(BUFFERCNT, mBufferList);
if (alGetError() != AL_NO_ERROR)
return false;
bBuffersAllocated = true;
alSourcei(mSource, AL_LOOPING, AL_FALSE);
bIsValid = true;
return true;
}
bool OggMixedStreamSource::updateBuffers()
{
// buffers are updated from theora player
return true;
}
void OggMixedStreamSource::freeStream()
{
// free the al buffers
if(bBuffersAllocated)
{
alSourceStop(mSource);
alSourcei(mSource, AL_BUFFER, 0);
alDeleteBuffers(BUFFERCNT, mBufferList);
alGetError();
for(int i = 0; i < BUFFERCNT; i++)
{
mBufferList[i] = 0;
m_fBufferInUse[i] = false;
}
bBuffersAllocated = false;
}
}
ALuint OggMixedStreamSource::GetAvailableBuffer()
{
if(!bBuffersAllocated)
return 0;
// test for unused buffers
for(int i = 0; i < BUFFERCNT; i++)
{
if(!m_fBufferInUse[i])
{
m_fBufferInUse[i] = true;
return mBufferList[i];
}
}
alGetError();
// test for processed buffers
ALint processed;
alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);
if(!processed)
return 0; // no available buffers
ALuint BufferID;
alSourceUnqueueBuffers(mSource, 1, &BufferID);
if (alGetError() != AL_NO_ERROR)
return 0; // something went wrong..
return BufferID;
}
bool OggMixedStreamSource::QueueBuffer(ALuint BufferID)
{
alSourceQueueBuffers(mSource, 1, &BufferID);
if (alGetError() != AL_NO_ERROR)
return false;
return true;
}

View File

@ -0,0 +1,50 @@
//--------------------------------------------
// oggMixedStreamSource.h
// header for audio stream dummy class, basicly
// an audio buffer filled remotely
//
//--------------------------------------------
#ifndef _OGGMIXEDSTREAMSOURCE_H_
#define _OGGMIXEDSTREAMSOURCE_H_
#ifndef _AUDIOSTREAMSOURCE_H_
#include "audio/audioStreamSource.h"
#endif
#define BUFFERCNT 128
class OggMixedStreamSource: public AudioStreamSource
{
public:
OggMixedStreamSource(const char *filename);
virtual ~OggMixedStreamSource();
virtual bool initStream();
virtual bool updateBuffers();
virtual void freeStream();
ALuint GetAvailableBuffer();
bool QueueBuffer(ALuint BufferID);
void PlayStream();
virtual F32 getElapsedTime()
{
return 0.0;
}
virtual F32 getTotalTime()
{
return 1.0f;
}
private:
ALuint mBufferList[BUFFERCNT];
bool m_fBufferInUse[BUFFERCNT];
bool bBuffersAllocated;
void clear();
};
#endif // _OGGMIXEDSTREAMSOURCE_H_

1535
engine/audio/vorbisStream.cc Executable file

File diff suppressed because it is too large Load Diff

156
engine/audio/vorbisStream.h Executable file
View File

@ -0,0 +1,156 @@
/********************************************************************
* *
* THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. *
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
* GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
* *
* THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 *
* by the XIPHOPHORUS Company http://www.xiph.org/ *
* *
********************************************************************
function: stdio-based convenience library for opening/seeking/decoding
********************************************************************/
// modified vorbisfile to use Torque Streams
// Kurtis Seebaldt
#ifndef _OV_FILE_H_
#define _OV_FILE_H_
#include <stdio.h>
#include "vorbis/codec.h"
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _RESMANAGER_H_
#include "core/resManager.h"
#endif
/* The function prototypes for the callbacks are basically the same as for
* the stdio functions fread, fseek, fclose, ftell.
* The one difference is that the FILE * arguments have been replaced with
* a void * - this is to be used as a pointer to whatever internal data these
* functions might need. In the stdio case, it's just a FILE * cast to a void *
*
* If you use other functions, check the docs for these functions and return
* the right values. For seek_func(), you *MUST* return -1 if the stream is
* unseekable
*/
//typedef struct {
// size_t (*read_func) (Stream *ptr, size_t size, size_t nmemb, void *datasource);
// int (*seek_func) (Stream *datasource, ogg_int64_t offset, int whence);
// int (*close_func) (void *datasource);
// long (*tell_func) (void *datasource);
//} ov_callbacks;
#define NOTOPEN 0
#define PARTOPEN 1
#define OPENED 2
#define STREAMSET 3
#define INITSET 4
typedef struct OggVorbis_File {
Stream *datasource; /* Pointer to a FILE *, etc. */
int seekable;
ogg_int64_t offset;
ogg_int64_t end;
ogg_sync_state oy;
/* If the FILE handle isn't seekable (eg, a pipe), only the current
stream appears */
int links;
ogg_int64_t *offsets;
ogg_int64_t *dataoffsets;
long *serialnos;
ogg_int64_t *pcmlengths;
vorbis_info *vi;
vorbis_comment *vc;
/* Decoding working state local storage */
ogg_int64_t pcm_offset;
int ready_state;
long current_serialno;
int current_link;
double bittrack;
double samptrack;
ogg_stream_state os; /* take physical pages, weld into a logical
stream of packets */
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
vorbis_block vb; /* local working space for packet->PCM decode */
// ov_callbacks callbacks;
} OggVorbis_File;
class OggVorbisFile {
public:
OggVorbisFile();
~OggVorbisFile();
int ov_clear();
int ov_open(Stream *stream,char *initial,long ibytes);
int ov_open_callbacks(Stream *datasource,
char *initial, long ibytes);
int ov_test(Stream *stream,char *initial,long ibytes);
int ov_test_callbacks(Stream *datasource,
char *initial, long ibytes);
int ov_test_open();
long ov_bitrate(int i);
long ov_bitrate_instant();
long ov_streams();
long ov_seekable();
long ov_serialnumber(int i);
ogg_int64_t ov_raw_total(int i);
ogg_int64_t ov_pcm_total(int i);
double ov_time_total(int i);
int ov_raw_seek(long pos);
int ov_pcm_seek(ogg_int64_t pos);
int ov_pcm_seek_page(ogg_int64_t pos);
int ov_time_seek(double pos);
int ov_time_seek_page(double pos);
ogg_int64_t ov_raw_tell();
ogg_int64_t ov_pcm_tell();
double ov_time_tell();
vorbis_info *ov_info(int link);
vorbis_comment *ov_comment(int link);
long ov_read_float(float ***pcm_channels,
int *bitstream);
long ov_read(char *buffer,int length,
int bigendianp,int *bitstream);
private:
OggVorbis_File *vf;
long _get_data();
void _seek_helper(long offset);
long _get_next_page(ogg_page *og,int boundary);
long _get_prev_page(ogg_page *og);
int _bisect_forward_serialno(long begin,long searched,long end,long currentno,long m);
int _fetch_headers(vorbis_info *vi,vorbis_comment *vc,long *serialno,ogg_page *og_ptr);
void _prefetch_all_headers(long dataoffset);
void _make_decode_ready();
int _open_seekable2();
void _decode_clear();
int _process_packet(int readp);
int _fseek64_wrap(Stream *stream,ogg_int64_t off,int whence);
int _ov_open1(Stream *stream,char *initial,long ibytes);
int _ov_open2();
};
#endif

View File

@ -0,0 +1,442 @@
//--------------------------------------
// vorbisStreamSource.cc
// implementation of streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------
#include "audio/vorbisStreamSource.h"
#include "vorbis/codec.h"
#define BUFFERSIZE 32768
#define CHUNKSIZE 4096
#if defined(TORQUE_BIG_ENDIAN)
#define ENDIAN 1
#else
#define ENDIAN 0
#endif
extern const char * MusicPlayerStreamingHook(const AUDIOHANDLE & handle);
VorbisStreamSource::VorbisStreamSource(const char *filename)
{
stream = NULL;
bIsValid = false;
bBuffersAllocated = false;
bVorbisFileInitialized = false;
mBufferList[0] = 0;
clear();
mFilename = filename;
mPosition = Point3F(0.f,0.f,0.f);
}
VorbisStreamSource::~VorbisStreamSource()
{
if(bReady && bIsValid)
freeStream();
}
void VorbisStreamSource::clear()
{
if(stream)
freeStream();
mHandle = NULL_AUDIOHANDLE;
mSource = NULL;
if(mBufferList[0] != 0)
alDeleteBuffers(NUMBUFFERS, mBufferList);
for(int i = 0; i < NUMBUFFERS; i++)
mBufferList[i] = 0;
dMemset(&mDescription, 0, sizeof(Audio::Description));
mEnvironment = 0;
mPosition.set(0.f,0.f,0.f);
mDirection.set(0.f,1.f,0.f);
mPitch = 1.f;
mScore = 0.f;
mCullTime = 0;
bReady = false;
bFinishedPlaying = false;
bIsValid = false;
bBuffersAllocated = false;
bVorbisFileInitialized = false;
}
bool VorbisStreamSource::initStream()
{
vorbis_info *vi;
ALint error;
bFinished = false;
// JMQ: changed buffer to static and doubled size. workaround for
// https://206.163.64.242/mantis/view_bug_page.php?f_id=0000242
static char data[BUFFERSIZE*2];
alSourceStop(mSource);
alSourcei(mSource, AL_BUFFER, 0);
stream = ResourceManager->openStream(mFilename);
if(stream != NULL)
{
if(vf.ov_open(stream, NULL, 0) < 0)
{
return false;
}
bVorbisFileInitialized = true;
//Read Vorbis File Info
vi = vf.ov_info(-1);
freq = vi->rate;
long samples = (long)vf.ov_pcm_total(-1);
if(vi->channels == 1)
{
format = AL_FORMAT_MONO16;
DataSize = 2 * samples;
}
else
{
format = AL_FORMAT_STEREO16;
DataSize = 4 * samples;
}
DataLeft = DataSize;
// Clear Error Code
alGetError();
alGenBuffers(NUMBUFFERS, mBufferList);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
bBuffersAllocated = true;
int numBuffers = 0;
for(int loop = 0; loop < NUMBUFFERS; loop++)
{
ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;
if (DataToRead == DataLeft)
bFinished = AL_TRUE;
long ret = oggRead(data, BUFFERSIZE, ENDIAN, &current_section);
if(ret <= 0)
{
bFinished = AL_TRUE;
break;
}
DataLeft -= ret;
alBufferData(mBufferList[loop], format, data, ret, freq);
++numBuffers;
if ((error = alGetError()) != AL_NO_ERROR)
return false;
if(bFinished)
break;
}
// Queue the buffers on the source
alSourceQueueBuffers(mSource, NUMBUFFERS, mBufferList);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
alSourcei(mSource, AL_LOOPING, AL_FALSE);
bReady = true;
}
else
{
return false;
}
bIsValid = true;
return true;
}
bool VorbisStreamSource::updateBuffers()
{
ALint processed;
ALuint BufferID;
ALint error;
// JMQ: changed buffer to static and doubled size. workaround for
// https://206.163.64.242/mantis/view_bug_page.php?f_id=0000242
static char data[BUFFERSIZE*2];
// don't do anything if stream not loaded properly
if(!bIsValid)
return false;
if(bFinished && mDescription.mIsLooping)
resetStream();
// reset AL error code
alGetError();
#if 1 //def TORQUE_OS_LINUX
// JMQ: this doesn't really help on linux. it may make things worse.
// if it doesn't help on mac/win either, could disable it.
ALint state;
alGetSourcei(mSource, AL_SOURCE_STATE, &state);
if (state == AL_STOPPED)
{
// um, yeah. you should be playing
// restart
alSourcePlay(mSource);
//#ifdef TORQUE_DEBUG
//Con::errorf(">><<>><< THE MUSIC STOPPED >><<>><<");
//#endif
return true;
}
#endif
#ifdef TORQUE_OS_LINUX
checkPosition();
#endif
// Get status
alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);
// If some buffers have been played, unqueue them and load new audio into them, then add them to the queue
if (processed > 0)
{
while (processed)
{
alSourceUnqueueBuffers(mSource, 1, &BufferID);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
if (!bFinished)
{
ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;
if (DataToRead == DataLeft)
bFinished = AL_TRUE;
long ret = oggRead(data, BUFFERSIZE, ENDIAN, &current_section);
if(ret > 0)
{
DataLeft -= ret;
alBufferData(BufferID, format, data, ret, freq);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
// Queue buffer
alSourceQueueBuffers(mSource, 1, &BufferID);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
}
processed--;
if(bFinished && mDescription.mIsLooping)
{
resetStream();
}
}
else
{
buffersinqueue--;
processed--;
if (buffersinqueue == 0)
{
bFinishedPlaying = AL_TRUE;
return AL_FALSE;
}
}
}
}
return true;
}
void VorbisStreamSource::freeStream()
{
bReady = false;
if(stream != NULL)
ResourceManager->closeStream(stream);
stream = NULL;
if(bBuffersAllocated)
{
if(mBufferList[0] != 0)
alDeleteBuffers(NUMBUFFERS, mBufferList);
for(int i = 0; i < NUMBUFFERS; i++)
mBufferList[i] = 0;
bBuffersAllocated = false;
}
if(bVorbisFileInitialized)
{
vf.ov_clear();
bVorbisFileInitialized = false;
}
}
void VorbisStreamSource::resetStream()
{
// MusicPlayerStreamingHook allow you to create a handler
// where you can rotate through streaming files
// Comment in and provide hook if desired.
//
//const char * newFile = MusicPlayerStreamingHook(mHandle);
//if (newFile)
//{
// setNewFile(newFile);
// return;
//}
//else
{
vf.ov_pcm_seek(0);
DataLeft = DataSize;
bFinished = AL_FALSE;
}
}
void VorbisStreamSource::setNewFile(const char * file)
{
//---------------------
// close down old file...
//---------------------
if(stream != NULL)
{
ResourceManager->closeStream(stream);
stream = NULL;
}
if(bVorbisFileInitialized)
{
vf.ov_clear();
bVorbisFileInitialized = false;
}
//---------------------
// open up new file...
//---------------------
mFilename = file;
stream = ResourceManager->openStream(mFilename);
if(stream != NULL)
{
if(vf.ov_open(stream, NULL, 0) < 0)
{
bFinished = AL_TRUE;
bVorbisFileInitialized = false;
bIsValid = false;
return;
}
//Read Vorbis File Info
vorbis_info * vi = vf.ov_info(-1);
freq = vi->rate;
long samples = (long)vf.ov_pcm_total(-1);
if(vi->channels == 1)
{
format = AL_FORMAT_MONO16;
DataSize = 2 * samples;
}
else
{
format = AL_FORMAT_STEREO16;
DataSize = 4 * samples;
}
DataLeft = DataSize;
// Clear Error Code
alGetError();
bFinished = AL_FALSE;
bVorbisFileInitialized = true;
bIsValid = true;
}
}
// ov_read() only returns a maximum of one page worth of data
// this helper function will repeat the read until buffer is full
long VorbisStreamSource::oggRead(char *buffer,int length,
int bigendianp,int *bitstream)
{
long bytesRead = 0;
long totalBytes = 0;
long offset = 0;
long bytesToRead = 0;
while((offset) < length)
{
if((length - offset) < CHUNKSIZE)
bytesToRead = length - offset;
else
bytesToRead = CHUNKSIZE;
bytesRead = vf.ov_read(buffer, bytesToRead, bigendianp, bitstream);
//#ifdef TORQUE_OS_LINUX
#if 1 // Might fix mac audio issue and possibly others...based on references, this looks like correct action
// linux ver will hang on exit after a stream loop if we don't
// do this
if (bytesRead == OV_HOLE)
// retry, see:
// http://www.xiph.org/archives/vorbis-dev/200102/0163.html
// http://www.mit.edu/afs/sipb/user/xiphmont/ogg-sandbox/vorbis/doc/vorbis-errors.txt
continue;
#endif
if(bytesRead <= 0)
break;
offset += bytesRead;
buffer += bytesRead;
}
return offset;
}
#ifdef TORQUE_OS_LINUX
// JMQ: OpenAL sometimes replaces the stream source's position with its own
// nifty value, causing the music to pan around the listener. how nice.
// this function checks to see if the current source position in openal
// is near the initial position, and slams it to the correct value if it
// is wrong.
// This is a bad place to put this, but I don't feel like adding a new
// .cc file. And since this is an incredibly lame hack to
// workaround a stupid OpenAL bug, I see no point in overengineering it.
void AudioStreamSource::checkPosition()
{
ALfloat pos[3];
alGetSourcefv(mSource, AL_POSITION, pos);
// just compute the difference between the openal pos and the
// correct pos. it better be pretty friggin small.
Point3F openalPos(pos[0], pos[1], pos[2]);
F32 slopDist = 0.0001f;
F32 dist = mFabs((openalPos - mPosition).len());
if (dist > slopDist)
// slam!
alSource3f(mSource, AL_POSITION, mPosition.x, mPosition.y, mPosition.z);
}
#endif
F32 VorbisStreamSource::getElapsedTime()
{
return vf.ov_time_tell();
}
F32 VorbisStreamSource::getTotalTime()
{
return vf.ov_time_total(-1);
}

View File

@ -0,0 +1,58 @@
//--------------------------------------------
// vorbisStreamSource.h
// header for streaming audio source for Ogg Vorbis
//
// Kurtis Seebaldt
//--------------------------------------------
#ifndef _VORBISSTREAMSOURCE_H_
#define _VORBISSTREAMSOURCE_H_
#ifndef _AUDIOSTREAMSOURCE_H_
#include "audio/audioStreamSource.h"
#endif
#include "audio/vorbisStream.h"
class VorbisStreamSource: public AudioStreamSource
{
public:
VorbisStreamSource(const char *filename);
virtual ~VorbisStreamSource();
virtual bool initStream();
virtual bool updateBuffers();
virtual void freeStream();
virtual F32 getElapsedTime();
virtual F32 getTotalTime();
private:
ALuint mBufferList[NUMBUFFERS];
S32 mNumBuffers;
S32 mBufferSize;
Stream *stream;
bool bReady;
bool bFinished;
ALenum format;
ALsizei size;
ALsizei freq;
ALuint DataSize;
ALuint DataLeft;
ALuint buffersinqueue;
bool bBuffersAllocated;
bool bVorbisFileInitialized;
int current_section;
OggVorbisFile vf;
void clear();
long oggRead(char *buffer,int length, int bigendianp,int *bitstream);
void resetStream();
void setNewFile(const char * file);
};
#endif // _VORBISSTREAMSOURCE_H_

334
engine/audio/wavStreamSource.cc Executable file
View File

@ -0,0 +1,334 @@
//--------------------------------------
// audioStreamSource.cc
// implementation of streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------
#include "audio/wavStreamSource.h"
#define BUFFERSIZE 32768
typedef struct
{
ALubyte riff[4]; // 'RIFF'
ALsizei riffSize;
ALubyte wave[4]; // 'WAVE'
ALubyte fmt[4]; // 'fmt '
ALuint fmtSize;
ALushort Format;
ALushort Channels;
ALuint SamplesPerSec;
ALuint BytesPerSec;
ALushort BlockAlign;
ALushort BitsPerSample;
ALubyte data[4]; // 'data'
ALuint dataSize;
} WAVE_Struct;
/// WAV File-header
struct WAVFileHdr
{
ALubyte id[4];
ALsizei size;
ALubyte type[4];
};
//// WAV Fmt-header
struct WAVFmtHdr
{
ALushort format;
ALushort channels;
ALuint samplesPerSec;
ALuint bytesPerSec;
ALushort blockAlign;
ALushort bitsPerSample;
};
/// WAV FmtEx-header
struct WAVFmtExHdr
{
ALushort size;
ALushort samplesPerBlock;
};
/// WAV Smpl-header
struct WAVSmplHdr
{
ALuint manufacturer;
ALuint product;
ALuint samplePeriod;
ALuint note;
ALuint fineTune;
ALuint SMPTEFormat;
ALuint SMPTEOffest;
ALuint loops;
ALuint samplerData;
struct
{
ALuint identifier;
ALuint type;
ALuint start;
ALuint end;
ALuint fraction;
ALuint count;
} loop[1];
};
/// WAV Chunk-header
struct WAVChunkHdr
{
ALubyte id[4];
ALuint size;
};
WavStreamSource::WavStreamSource(const char *filename) {
stream = NULL;
bIsValid = false;
bBuffersAllocated = false;
mBufferList[0] = 0;
clear();
mFilename = filename;
mPosition = Point3F(0.f,0.f,0.f);
}
WavStreamSource::~WavStreamSource() {
if(bReady && bIsValid)
freeStream();
}
void WavStreamSource::clear()
{
if(stream)
freeStream();
mHandle = NULL_AUDIOHANDLE;
mSource = NULL;
if(mBufferList[0] != 0)
alDeleteBuffers(NUMBUFFERS, mBufferList);
for(int i = 0; i < NUMBUFFERS; i++)
mBufferList[i] = 0;
dMemset(&mDescription, 0, sizeof(Audio::Description));
mEnvironment = 0;
mPosition.set(0.f,0.f,0.f);
mDirection.set(0.f,1.f,0.f);
mPitch = 1.f;
mScore = 0.f;
mCullTime = 0;
bReady = false;
bFinishedPlaying = false;
bIsValid = false;
bBuffersAllocated = false;
}
bool WavStreamSource::initStream() {
WAVChunkHdr chunkHdr;
WAVFmtExHdr fmtExHdr;
WAVFileHdr fileHdr;
WAVSmplHdr smplHdr;
WAVFmtHdr fmtHdr;
WAVE_Struct wave;
ALint error;
bFinished = false;
char data[BUFFERSIZE];
alSourceStop(mSource);
alSourcei(mSource, AL_BUFFER, 0);
stream = ResourceManager->openStream(mFilename);
if(stream != NULL) {
stream->read(4, &fileHdr.id[0]);
stream->read(&fileHdr.size);
stream->read(4, &fileHdr.type[0]);
stream->read(4, &chunkHdr.id[0]);
stream->read(&chunkHdr.size);
// WAV Format header
stream->read(&fmtHdr.format);
stream->read(&fmtHdr.channels);
stream->read(&fmtHdr.samplesPerSec);
stream->read(&fmtHdr.bytesPerSec);
stream->read(&fmtHdr.blockAlign);
stream->read(&fmtHdr.bitsPerSample);
format=(fmtHdr.channels==1?
(fmtHdr.bitsPerSample==8?AL_FORMAT_MONO8:AL_FORMAT_MONO16):
(fmtHdr.bitsPerSample==8?AL_FORMAT_STEREO8:AL_FORMAT_STEREO16));
freq=fmtHdr.samplesPerSec;
stream->read(4, &chunkHdr.id[0]);
stream->read(&chunkHdr.size);
DataSize = chunkHdr.size;
DataLeft = DataSize;
dataStart = stream->getPosition();
// Clear Error Code
alGetError();
alGenBuffers(NUMBUFFERS, mBufferList);
if ((error = alGetError()) != AL_NO_ERROR) {
return false;
}
bBuffersAllocated = true;
int numBuffers = 0;
for(int loop = 0; loop < NUMBUFFERS; loop++)
{
ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;
if (DataToRead == DataLeft)
bFinished = AL_TRUE;
stream->read(DataToRead, data);
DataLeft -= DataToRead;
alBufferData(mBufferList[loop], format, data, DataToRead, freq);
if ((error = alGetError()) != AL_NO_ERROR) {
return false;
}
++numBuffers;
if(bFinished)
break;
}
// Queue the buffers on the source
alSourceQueueBuffers(mSource, numBuffers, mBufferList);
if ((error = alGetError()) != AL_NO_ERROR) {
return false;
}
buffersinqueue = numBuffers;
alSourcei(mSource, AL_LOOPING, AL_FALSE);
bReady = true;
}
else {
return false;
}
bIsValid = true;
return true;
}
bool WavStreamSource::updateBuffers() {
ALint processed;
ALuint BufferID;
ALint error;
char data[BUFFERSIZE];
// don't do anything if buffer isn't initialized
if(!bIsValid)
return false;
if(bFinished && mDescription.mIsLooping) {
resetStream();
}
// reset AL error code
alGetError();
// Get status
alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);
// If some buffers have been played, unqueue them and load new audio into them, then add them to the queue
if (processed > 0)
{
while (processed)
{
alSourceUnqueueBuffers(mSource, 1, &BufferID);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
if (!bFinished)
{
ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;
if (DataToRead == DataLeft) {
bFinished = AL_TRUE;
}
stream->read(DataToRead, data);
DataLeft -= DataToRead;
alBufferData(BufferID, format, data, DataToRead, freq);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
// Queue buffer
alSourceQueueBuffers(mSource, 1, &BufferID);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
processed--;
if(bFinished && mDescription.mIsLooping) {
resetStream();
}
}
else
{
buffersinqueue--;
processed--;
if (buffersinqueue == 0)
{
bFinishedPlaying = AL_TRUE;
return AL_FALSE;
}
}
}
}
return AL_TRUE;
}
void WavStreamSource::freeStream() {
bReady = false;
if(stream != NULL)
ResourceManager->closeStream(stream);
stream = NULL;
if(bBuffersAllocated) {
if(mBufferList[0] != 0)
alDeleteBuffers(NUMBUFFERS, mBufferList);
for(int i = 0; i < NUMBUFFERS; i++)
mBufferList[i] = 0;
bBuffersAllocated = false;
}
}
void WavStreamSource::resetStream() {
stream->setPosition(dataStart);
DataLeft = DataSize;
bFinished = AL_FALSE;
}
#include "console/console.h"
F32 WavStreamSource::getElapsedTime()
{
Con::warnf( "GetElapsedTime not implemented in WaveStreams yet" );
return -1.f;
}
F32 WavStreamSource::getTotalTime()
{
Con::warnf( "GetTotalTime not implemented in WaveStreams yet" );
return -1.f;
}

49
engine/audio/wavStreamSource.h Executable file
View File

@ -0,0 +1,49 @@
//--------------------------------------------
// wavStreamSource.h
// header for streaming audio source for WAV
//--------------------------------------------
#ifndef _WAVSTREAMSOURCE_H_
#define _WAVSTREAMSOURCE_H_
#ifndef _AUDIOSTREAMSOURCE_H_
#include "audio/audioStreamSource.h"
#endif
class WavStreamSource: public AudioStreamSource
{
public:
WavStreamSource(const char *filename);
virtual ~WavStreamSource();
virtual bool initStream();
virtual bool updateBuffers();
virtual void freeStream();
virtual F32 getElapsedTime();
virtual F32 getTotalTime();
private:
ALuint mBufferList[NUMBUFFERS];
S32 mNumBuffers;
S32 mBufferSize;
Stream *stream;
bool bReady;
bool bFinished;
ALenum format;
ALsizei size;
ALsizei freq;
ALuint DataSize;
ALuint DataLeft;
ALuint dataStart;
ALuint buffersinqueue;
bool bBuffersAllocated;
void clear();
void resetStream();
};
#endif // _AUDIOSTREAMSOURCE_H_

View File

@ -0,0 +1,97 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "collision/abstractPolyList.h"
//----------------------------------------------------------------------------
AbstractPolyList::~AbstractPolyList()
{
mInterestNormalRegistered = false;
}
static U32 PolyFace[6][4] = {
{ 3, 2, 1, 0 },
{ 7, 4, 5, 6 },
{ 0, 5, 4, 3 },
{ 6, 5, 0, 1 },
{ 7, 6, 1, 2 },
{ 4, 7, 2, 3 },
};
void AbstractPolyList::addBox(const Box3F &box)
{
Point3F pos = box.min;
F32 dx = box.max.x - box.min.x;
F32 dy = box.max.y - box.min.y;
F32 dz = box.max.z - box.min.z;
U32 base = addPoint(pos);
pos.y += dy; addPoint(pos);
pos.x += dx; addPoint(pos);
pos.y -= dy; addPoint(pos);
pos.z += dz; addPoint(pos);
pos.x -= dx; addPoint(pos);
pos.y += dy; addPoint(pos);
pos.x += dx; addPoint(pos);
for (S32 i = 0; i < 6; i++) {
begin(0,i);
S32 v1 = base + PolyFace[i][0];
S32 v2 = base + PolyFace[i][1];
S32 v3 = base + PolyFace[i][2];
S32 v4 = base + PolyFace[i][3];
vertex(v1);
vertex(v2);
vertex(v3);
vertex(v4);
plane(v1,v2,v3);
end();
}
}
bool AbstractPolyList::getMapping(MatrixF *, Box3F *)
{
// return list transform and bounds in list space...optional
return false;
}
bool AbstractPolyList::isInterestedInPlane(const PlaneF& plane)
{
if (mInterestNormalRegistered == false) {
return true;
}
else {
PlaneF xformed;
mPlaneTransformer.transform(plane, xformed);
if (mDot(xformed, mInterestNormal) >= 0.0f)
return false;
else
return true;
}
}
bool AbstractPolyList::isInterestedInPlane(const U32 index)
{
if (mInterestNormalRegistered == false) {
return true;
}
else {
const PlaneF& rPlane = getIndexedPlane(index);
if (mDot(rPlane, mInterestNormal) >= 0.0f)
return false;
else
return true;
}
}
void AbstractPolyList::setInterestNormal(const Point3F& normal)
{
mInterestNormalRegistered = true;
mInterestNormal = normal;
}

View File

@ -0,0 +1,241 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _ABSTRACTPOLYLIST_H_
#define _ABSTRACTPOLYLIST_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _MPLANETRANSFORMER_H_
#include "math/mPlaneTransformer.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
class SceneObject;
//----------------------------------------------------------------------------
/// A polygon filtering interface.
///
/// The various AbstractPolyList subclasses are used in Torque as an interface to
/// handle spatial queries. SceneObject::buildPolyList() takes an implementor of
/// AbstractPolyList (such as ConcretePolyList, ClippedPolyList, etc.) and a
/// bounding volume. The function runs geometry data from all the objects in the
/// bounding volume through the passed PolyList.
///
/// This interface only provides a method to get data INTO your implementation. Different
/// subclasses provide different interfaces to get data back out, depending on their
/// specific quirks.
///
/// The physics engine now uses convex hulls for collision detection.
///
/// @see Convex
class AbstractPolyList
{
protected:
// User set state
SceneObject* mCurrObject;
MatrixF mBaseMatrix; // Base transform
MatrixF mTransformMatrix; // Current object transform
MatrixF mMatrix; // Base * current transform
Point3F mScale;
PlaneTransformer mPlaneTransformer;
bool mInterestNormalRegistered;
Point3F mInterestNormal;
public:
AbstractPolyList();
virtual ~AbstractPolyList();
/// @name Common Interface
/// @{
void setBaseTransform(const MatrixF& mat);
/// Sets the transform applying to the current stream of
/// vertices.
///
/// @param mat Transformation of the object. (in)
/// @param scale Scaling of the object. (in)
void setTransform(const MatrixF* mat, const Point3F& scale);
/// Gets the transform applying to the current stream of
/// vertices.
///
/// @param mat Transformation of the object. (out)
/// @param scale Scaling of the object. (out)
void getTransform(MatrixF* mat, Point3F * scale);
/// This is called by the object which is currently feeding us
/// vertices, to tell us who it is.
void setObject(SceneObject*);
/// Add a box via the query interface (below). This wraps some calls
/// to addPoint and addPlane.
void addBox(const Box3F &box);
void doConstruct();
/// @}
/// @name Query Interface
///
/// It is through this interface that geometry data is fed to the
/// PolyList. The order of calls are:
/// - begin()
/// - One or more calls to vertex() and one call to plane(), in any order.
/// - end()
///
/// @code
/// // Example code that adds data to a PolyList.
/// // See AbstractPolyList::addBox() for the function this was taken from.
///
/// // First, we add points... (note that we use base to track the start of adding.)
/// U32 base = addPoint(pos);
/// pos.y += dy; addPoint(pos);
/// pos.x += dx; addPoint(pos);
/// pos.y -= dy; addPoint(pos);
/// pos.z += dz; addPoint(pos);
/// pos.x -= dx; addPoint(pos);
/// pos.y += dy; addPoint(pos);
/// pos.x += dx; addPoint(pos);
///
/// // Now we add our surfaces. (there are six, as we are adding a cube here)
/// for (S32 i = 0; i < 6; i++) {
/// // Begin a surface
/// begin(0,i);
///
/// // Calculate the vertex ids; we have a lookup table to tell us offset from base.
/// // In your own code, you might use a different method.
/// S32 v1 = base + PolyFace[i][0];
/// S32 v2 = base + PolyFace[i][1];
/// S32 v3 = base + PolyFace[i][2];
/// S32 v4 = base + PolyFace[i][3];
///
/// // Reference the four vertices that make up this surface.
/// vertex(v1);
/// vertex(v2);
/// vertex(v3);
/// vertex(v4);
///
/// // Indicate the plane of this surface.
/// plane(v1,v2,v3);
///
/// // End the surface.
/// end();
/// }
/// @endcode
/// @{
/// Are we empty of data?
virtual bool isEmpty() const = 0;
/// Adds a point to the poly list, and returns
/// an ID number for that point.
virtual U32 addPoint(const Point3F& p) = 0;
/// Adds a plane to the poly list, and returns
/// an ID number for that point.
virtual U32 addPlane(const PlaneF& plane) = 0;
/// Start a surface.
///
/// @param material A material ID for this surface.
/// @param surfaceKey A key value to associate with this surface.
virtual void begin(U32 material,U32 surfaceKey) = 0;
/// Indicate the plane of the surface.
virtual void plane(U32 v1,U32 v2,U32 v3) = 0;
/// Indicate the plane of the surface.
virtual void plane(const PlaneF& p) = 0;
/// Indicate the plane of the surface.
virtual void plane(const U32 index) = 0;
/// Reference a vertex which is in this surface.
virtual void vertex(U32 vi) = 0;
/// Mark the end of a surface.
virtual void end() = 0;
/// Return list transform and bounds in list space.
///
/// @returns False if no data is available.
virtual bool getMapping(MatrixF *, Box3F *);
/// @}
/// @name Interest
///
/// This is a mechanism to let you specify interest in a specific normal.
/// If you set a normal you're interested in, then any planes facing "away"
/// from that normal are culled from the results.
///
/// This is handy if you're using this to do a physics check, as you're not
/// interested in polygons facing away from you (since you don't collide with
/// the backsides/insides of things).
/// @{
void setInterestNormal(const Point3F& normal);
void clearInterestNormal() { mInterestNormalRegistered = false; }
virtual bool isInterestedInPlane(const PlaneF& plane);
virtual bool isInterestedInPlane(const U32 index);
/// @}
protected:
/// A helper function to convert a plane index to a PlaneF structure.
virtual const PlaneF& getIndexedPlane(const U32 index) = 0;
};
inline AbstractPolyList::AbstractPolyList()
{
doConstruct();
}
inline void AbstractPolyList::doConstruct()
{
mCurrObject = NULL;
mBaseMatrix.identity();
mMatrix.identity();
mScale.set(1, 1, 1);
mPlaneTransformer.setIdentity();
mInterestNormalRegistered = false;
}
inline void AbstractPolyList::setBaseTransform(const MatrixF& mat)
{
mBaseMatrix = mat;
}
inline void AbstractPolyList::setTransform(const MatrixF* mat, const Point3F& scale)
{
mMatrix = mBaseMatrix;
mTransformMatrix = *mat;
mMatrix.mul(*mat);
mScale = scale;
mPlaneTransformer.set(mMatrix, mScale);
}
inline void AbstractPolyList::getTransform(MatrixF* mat, Point3F * scale)
{
*mat = mTransformMatrix;
*scale = mScale;
}
inline void AbstractPolyList::setObject(SceneObject* obj)
{
mCurrObject = obj;
}
#endif

199
engine/collision/boxConvex.cc Executable file
View File

@ -0,0 +1,199 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "math/mMath.h"
#include "game/gameBase.h"
#include "collision/boxConvex.h"
//----------------------------------------------------------------------------
struct Corner {
S32 a,b,c;
S32 ab,ac,bc;
} sCorner[] =
{
{ 1,2,4, 4,0,1 },
{ 0,3,5, 4,0,3 },
{ 0,3,6, 4,1,2 },
{ 1,2,7, 4,3,2 },
{ 0,5,6, 0,1,5 },
{ 1,4,7, 0,3,5 },
{ 2,4,7, 1,2,5 },
{ 3,5,6, 3,2,5 },
};
struct Face {
S32 vertex[4];
S32 axis;
bool flip;
} sFace[] =
{
{ 0,4,5,1, 1,true },
{ 0,2,6,4, 0,true },
{ 3,7,6,2, 1,false },
{ 3,1,5,7, 0,false },
{ 0,1,3,2, 2,true },
{ 4,6,7,5, 2,false },
};
Point3F BoxConvex::support(const VectorF& v) const
{
Point3F p = mCenter;
p.x += (v.x >= 0)? mSize.x: -mSize.x;
p.y += (v.y >= 0)? mSize.y: -mSize.y;
p.z += (v.z >= 0)? mSize.z: -mSize.z;
return p;
}
Point3F BoxConvex::getVertex(S32 v)
{
Point3F p = mCenter;
p.x += (v & 1)? mSize.x: -mSize.x;
p.y += (v & 2)? mSize.y: -mSize.y;
p.z += (v & 4)? mSize.z: -mSize.z;
return p;
}
inline bool isOnPlane(Point3F p,PlaneF& plane)
{
F32 dist = mDot(plane,p) + plane.d;
return dist < 0.1 && dist > -0.1;
}
void BoxConvex::getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf)
{
cf->material = 0;
cf->object = mObject;
S32 v = 0;
v += (n.x >= 0)? 1: 0;
v += (n.y >= 0)? 2: 0;
v += (n.z >= 0)? 4: 0;
PlaneF plane;
plane.set(getVertex(v),n);
// Emit vertex and edge
S32 mask = 0;
Corner& corner = sCorner[v];
mask |= isOnPlane(getVertex(corner.a),plane)? 1: 0;
mask |= isOnPlane(getVertex(corner.b),plane)? 2: 0;
mask |= isOnPlane(getVertex(corner.c),plane)? 4: 0;
switch(mask) {
case 0: {
cf->mVertexList.increment();
mat.mulP(getVertex(v),&cf->mVertexList.last());
break;
}
case 1:
emitEdge(v,corner.a,mat,cf);
break;
case 2:
emitEdge(v,corner.b,mat,cf);
break;
case 4:
emitEdge(v,corner.c,mat,cf);
break;
case 1 | 2:
emitFace(corner.ab,mat,cf);
break;
case 2 | 4:
emitFace(corner.bc,mat,cf);
break;
case 1 | 4:
emitFace(corner.ac,mat,cf);
break;
}
}
void BoxConvex::getPolyList(AbstractPolyList* list)
{
list->setTransform(&getTransform(), getScale());
list->setObject(getObject());
U32 base = list->addPoint(mCenter + Point3F(-mSize.x, -mSize.y, -mSize.z));
list->addPoint(mCenter + Point3F( mSize.x, -mSize.y, -mSize.z));
list->addPoint(mCenter + Point3F(-mSize.x, mSize.y, -mSize.z));
list->addPoint(mCenter + Point3F( mSize.x, mSize.y, -mSize.z));
list->addPoint(mCenter + Point3F(-mSize.x, -mSize.y, mSize.z));
list->addPoint(mCenter + Point3F( mSize.x, -mSize.y, mSize.z));
list->addPoint(mCenter + Point3F(-mSize.x, mSize.y, mSize.z));
list->addPoint(mCenter + Point3F( mSize.x, mSize.y, mSize.z));
for (U32 i = 0; i < 6; i++) {
list->begin(0, i);
list->vertex(base + sFace[i].vertex[0]);
list->vertex(base + sFace[i].vertex[1]);
list->vertex(base + sFace[i].vertex[2]);
list->vertex(base + sFace[i].vertex[3]);
list->plane(base + sFace[i].vertex[0],
base + sFace[i].vertex[1],
base + sFace[i].vertex[2]);
list->end();
}
}
void BoxConvex::emitEdge(S32 v1,S32 v2,const MatrixF& mat,ConvexFeature* cf)
{
S32 vc = cf->mVertexList.size();
cf->mVertexList.increment(2);
Point3F *vp = cf->mVertexList.begin();
mat.mulP(getVertex(v1),&vp[vc]);
mat.mulP(getVertex(v2),&vp[vc + 1]);
cf->mEdgeList.increment();
ConvexFeature::Edge& edge = cf->mEdgeList.last();
edge.vertex[0] = vc;
edge.vertex[1] = vc + 1;
}
void BoxConvex::emitFace(S32 fi,const MatrixF& mat,ConvexFeature* cf)
{
Face& face = sFace[fi];
// Emit vertices
S32 vc = cf->mVertexList.size();
cf->mVertexList.increment(4);
Point3F *vp = cf->mVertexList.begin();
for (S32 v = 0; v < 4; v++)
mat.mulP(getVertex(face.vertex[v]),&vp[vc + v]);
// Emit edges
cf->mEdgeList.increment(4);
ConvexFeature::Edge* edge = cf->mEdgeList.end() - 4;
for (S32 e = 0; e < 4; e++) {
edge[e].vertex[0] = vc + e;
edge[e].vertex[1] = vc + ((e + 1) & 3);
}
// Emit 2 triangle faces
cf->mFaceList.increment(2);
ConvexFeature::Face* ef = cf->mFaceList.end() - 2;
mat.getColumn(face.axis,&ef->normal);
if (face.flip)
ef[0].normal.neg();
ef[1].normal = ef[0].normal;
ef[1].vertex[0] = ef[0].vertex[0] = vc;
ef[1].vertex[1] = ef[0].vertex[2] = vc + 2;
ef[0].vertex[1] = vc + 1;
ef[1].vertex[2] = vc + 3;
}
const MatrixF& OrthoBoxConvex::getTransform() const
{
Point3F translation;
Parent::getTransform().getColumn(3, &translation);
mOrthoMatrixCache.setColumn(3, translation);
return mOrthoMatrixCache;
}

46
engine/collision/boxConvex.h Executable file
View File

@ -0,0 +1,46 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _BOXCONVEX_H_
#define _BOXCONVEX_H_
#ifndef _CONVEX_H_
#include "collision/convex.h"
#endif
//----------------------------------------------------------------------------
class BoxConvex: public Convex
{
Point3F getVertex(S32 v);
void emitEdge(S32 v1,S32 v2,const MatrixF& mat,ConvexFeature* cf);
void emitFace(S32 fi,const MatrixF& mat,ConvexFeature* cf);
public:
//
Point3F mCenter;
VectorF mSize;
BoxConvex() { mType = BoxConvexType; }
void init(SceneObject* obj) { mObject = obj; }
Point3F support(const VectorF& v) const;
void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf);
void getPolyList(AbstractPolyList* list);
};
class OrthoBoxConvex: public BoxConvex
{
typedef BoxConvex Parent;
mutable MatrixF mOrthoMatrixCache;
public:
OrthoBoxConvex() { mOrthoMatrixCache.identity(); }
virtual const MatrixF& getTransform() const;
};
#endif

View File

@ -0,0 +1,268 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/clippedPolyList.h"
//----------------------------------------------------------------------------
ClippedPolyList::ClippedPolyList()
{
VECTOR_SET_ASSOCIATION(mPolyList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
VECTOR_SET_ASSOCIATION(mPlaneList);
mNormal.set(0,0,0);
mIndexList.reserve(100);
}
ClippedPolyList::~ClippedPolyList()
{
}
//----------------------------------------------------------------------------
void ClippedPolyList::clear()
{
// Only clears internal data
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPolyPlaneList.clear();
}
bool ClippedPolyList::isEmpty() const
{
return mPolyList.size() == 0;
}
//----------------------------------------------------------------------------
U32 ClippedPolyList::addPoint(const Point3F& p)
{
mVertexList.increment();
Vertex& v = mVertexList.last();
v.point.x = p.x * mScale.x;
v.point.y = p.y * mScale.y;
v.point.z = p.z * mScale.z;
mMatrix.mulP(v.point);
// Build the plane mask
register U32 mask = 1;
register S32 count = mPlaneList.size();
register PlaneF * plane = mPlaneList.address();
v.mask = 0;
while(--count >= 0) {
if (plane++->distToPlane(v.point) > 0)
v.mask |= mask;
mask <<= 1;
}
return mVertexList.size() - 1;
}
U32 ClippedPolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void ClippedPolyList::begin(U32 material,U32 surfaceKey)
{
mPolyList.increment();
Poly& poly = mPolyList.last();
poly.object = mCurrObject;
poly.material = material;
poly.vertexStart = mIndexList.size();
poly.surfaceKey = surfaceKey;
}
//----------------------------------------------------------------------------
void ClippedPolyList::plane(U32 v1,U32 v2,U32 v3)
{
mPolyList.last().plane.set(mVertexList[v1].point,
mVertexList[v2].point,mVertexList[v3].point);
}
void ClippedPolyList::plane(const PlaneF& p)
{
mPlaneTransformer.transform(p, mPolyList.last().plane);
}
void ClippedPolyList::plane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPolyList.last().plane = mPolyPlaneList[index];
}
const PlaneF& ClippedPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
//----------------------------------------------------------------------------
void ClippedPolyList::vertex(U32 vi)
{
mIndexList.push_back(vi);
}
//----------------------------------------------------------------------------
void ClippedPolyList::end()
{
Poly& poly = mPolyList.last();
// Anything facing away from the mNormal is rejected
if (mDot(poly.plane,mNormal) > 0) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
// Build initial inside/outside plane masks
U32 indexStart = poly.vertexStart;
U32 vertexCount = mIndexList.size() - indexStart;
U32 frontMask = 0,backMask = 0;
U32 i;
for (i = indexStart; i < mIndexList.size(); i++) {
U32 mask = mVertexList[mIndexList[i]].mask;
frontMask |= mask;
backMask |= ~mask;
}
// Trivial accept if all the vertices are on the backsides of
// all the planes.
if (!frontMask) {
poly.vertexCount = vertexCount;
return;
}
// Trivial reject if any plane not crossed has all it's points
// on the front.
U32 crossMask = frontMask & backMask;
if (~crossMask & frontMask) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
// Need to do some clipping
for (U32 p = 0; p < mPlaneList.size(); p++) {
U32 pmask = 1 << p;
// Only test against this plane if we have something
// on both sides
if (crossMask & pmask) {
U32 indexEnd = mIndexList.size();
U32 i1 = indexEnd - 1;
U32 mask1 = mVertexList[mIndexList[i1]].mask;
for (U32 i2 = indexStart; i2 < indexEnd; i2++) {
U32 mask2 = mVertexList[mIndexList[i2]].mask;
if ((mask1 ^ mask2) & pmask) {
//
mVertexList.increment();
VectorF& v1 = mVertexList[mIndexList[i1]].point;
VectorF& v2 = mVertexList[mIndexList[i2]].point;
VectorF vv = v2 - v1;
F32 t = -mPlaneList[p].distToPlane(v1) / mDot(mPlaneList[p],vv);
mIndexList.push_back(mVertexList.size() - 1);
Vertex& iv = mVertexList.last();
iv.point.x = v1.x + vv.x * t;
iv.point.y = v1.y + vv.y * t;
iv.point.z = v1.z + vv.z * t;
iv.mask = 0;
// Test against the remaining planes
for (U32 i = p + 1; i < mPlaneList.size(); i++)
if (mPlaneList[i].distToPlane(iv.point) > 0) {
iv.mask = 1 << i;
break;
}
}
if (!(mask2 & pmask)) {
U32 index = mIndexList[i2];
mIndexList.push_back(index);
}
mask1 = mask2;
i1 = i2;
}
// Check for degenerate
indexStart = indexEnd;
if (mIndexList.size() - indexStart < 3) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
}
}
// Emit what's left and compress the index list.
poly.vertexCount = mIndexList.size() - indexStart;
memcpy(&mIndexList[poly.vertexStart],
&mIndexList[indexStart],poly.vertexCount);
mIndexList.setSize(poly.vertexStart + poly.vertexCount);
}
//----------------------------------------------------------------------------
void ClippedPolyList::memcpy(U32* dst, U32* src,U32 size)
{
U32* end = src + size;
while (src != end)
*dst++ = *src++;
}
//----------------------------------------------------------------------------
void ClippedPolyList::render()
{
glVertexPointer(3,GL_FLOAT,sizeof(Vertex),mVertexList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1,0,0,0.25);
glEnable(GL_BLEND);
glDisable(GL_CULL_FACE);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
Poly * p;
for (p = mPolyList.begin(); p < mPolyList.end(); p++) {
glDrawElements(GL_POLYGON,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glColor3f(0.6,0.6,0.6);
glDisable(GL_BLEND);
for (p = mPolyList.begin(); p < mPolyList.end(); p++) {
glDrawElements(GL_LINE_LOOP,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glDisableClientState(GL_VERTEX_ARRAY);
}

View File

@ -0,0 +1,84 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CLIPPEDPOLYLIST_H_
#define _CLIPPEDPOLYLIST_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
//----------------------------------------------------------------------------
/// Clipped PolyList
///
/// This takes the geometry passed to it and clips against the PlaneList set
/// by the caller.
//
/// @see AbstractPolyList
class ClippedPolyList: public AbstractPolyList
{
void memcpy(U32* d, U32* s,U32 size);
public:
struct Vertex {
Point3F point;
U32 mask;
};
struct Poly {
PlaneF plane;
SceneObject* object;
U32 material;
U32 vertexStart;
U32 vertexCount;
U32 surfaceKey;
};
typedef Vector<PlaneF> PlaneList;
typedef Vector<Vertex> VertexList;
typedef Vector<Poly> PolyList;
typedef Vector<U32> IndexList;
// Internal data
PolyList mPolyList;
VertexList mVertexList;
IndexList mIndexList;
PlaneList mPolyPlaneList;
// Data set by caller
PlaneList mPlaneList;
VectorF mNormal;
//
ClippedPolyList();
~ClippedPolyList();
void clear();
void render();
// Virtual methods
bool isEmpty() const;
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif

65
engine/collision/collision.h Executable file
View File

@ -0,0 +1,65 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _COLLISION_H_
#define _COLLISION_H_
#ifndef _DATACHUNKER_H_
#include "core/dataChunker.h"
#endif
#ifndef _MPLANE_H_
#include "math/mPlane.h"
#endif
class SceneObject;
//----------------------------------------------------------------------------
struct Collision
{
SceneObject* object;
Point3F point;
VectorF normal;
U32 material;
// Face and Face dot are currently only set by the extrudedPolyList
// clipper. Values are otherwise undefined.
U32 face; // Which face was hit
F32 faceDot; // -Dot of face with poly normal
F32 distance;
};
struct CollisionList
{
enum {
MaxCollisions = 64
};
int count;
Collision collision[MaxCollisions];
F32 t;
// MaxHeight is currently only set by the extrudedPolyList
// clipper. It represents the maximum vertex z value of
// the returned collision surfaces.
F32 maxHeight;
};
//----------------------------------------------------------------------------
// BSP Collision tree
// Solid nodes are represented by structures with NULL frontNode and
// backNode pointers. The material field is only valid on a solid node.
// There is no structure for empty nodes, frontNode or backNode
// should be set to NULL to represent empty half-spaces.
struct BSPNode
{
U32 material;
PlaneF plane;
BSPNode *frontNode, *backNode;
};
typedef Chunker<BSPNode> BSPTree;
#endif

View File

@ -0,0 +1,149 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/concretePolyList.h"
//----------------------------------------------------------------------------
ConcretePolyList::ConcretePolyList()
{
VECTOR_SET_ASSOCIATION(mPolyList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
mIndexList.reserve(100);
}
ConcretePolyList::~ConcretePolyList()
{
}
//----------------------------------------------------------------------------
void ConcretePolyList::clear()
{
// Only clears internal data
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPolyPlaneList.clear();
}
//----------------------------------------------------------------------------
U32 ConcretePolyList::addPoint(const Point3F& p)
{
mVertexList.increment();
Point3F& v = mVertexList.last();
v.x = p.x * mScale.x;
v.y = p.y * mScale.y;
v.z = p.z * mScale.z;
mMatrix.mulP(v);
return mVertexList.size() - 1;
}
U32 ConcretePolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void ConcretePolyList::begin(U32 material,U32 surfaceKey)
{
mPolyList.increment();
Poly& poly = mPolyList.last();
poly.object = mCurrObject;
poly.material = material;
poly.vertexStart = mIndexList.size();
poly.surfaceKey = surfaceKey;
}
//----------------------------------------------------------------------------
void ConcretePolyList::plane(U32 v1,U32 v2,U32 v3)
{
mPolyList.last().plane.set(mVertexList[v1],
mVertexList[v2],mVertexList[v3]);
}
void ConcretePolyList::plane(const PlaneF& p)
{
mPlaneTransformer.transform(p, mPolyList.last().plane);
}
void ConcretePolyList::plane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPolyList.last().plane = mPolyPlaneList[index];
}
const PlaneF& ConcretePolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
//----------------------------------------------------------------------------
void ConcretePolyList::vertex(U32 vi)
{
mIndexList.push_back(vi);
}
//----------------------------------------------------------------------------
bool ConcretePolyList::isEmpty() const
{
return false;
}
void ConcretePolyList::end()
{
Poly& poly = mPolyList.last();
poly.vertexCount = mIndexList.size() - poly.vertexStart;
}
void ConcretePolyList::render()
{
glVertexPointer(3,GL_FLOAT,sizeof(Point3F),mVertexList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1,0,0,0.25);
glEnable(GL_BLEND);
glDisable(GL_CULL_FACE);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
Poly * p;
for (p = mPolyList.begin(); p < mPolyList.end(); p++) {
if(p->material != 0xFFFFFFFF)
glDrawElements(GL_POLYGON,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glColor3f(0.6,0.6,0.6);
glDisable(GL_BLEND);
for (p = mPolyList.begin(); p < mPolyList.end(); p++) {
glDrawElements(GL_LINE_LOOP,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glDisableClientState(GL_VERTEX_ARRAY);
}

View File

@ -0,0 +1,66 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONCRETEPOLYLIST_H_
#define _CONCRETEPOLYLIST_H_
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
/// A concrete, renderable PolyList
///
/// This class is used to store geometry from a PolyList query.
///
/// It allows you to render this data, as well.
///
/// @see AbstractPolyList
class ConcretePolyList : public AbstractPolyList
{
public:
struct Poly {
PlaneF plane;
SceneObject* object;
U32 material;
U32 vertexStart;
U32 vertexCount;
U32 surfaceKey;
};
typedef Vector<PlaneF> PlaneList;
typedef Vector<Point3F> VertexList;
typedef Vector<Poly> PolyList;
typedef Vector<U32> IndexList;
PolyList mPolyList;
VertexList mVertexList;
IndexList mIndexList;
PlaneList mPolyPlaneList;
public:
ConcretePolyList();
~ConcretePolyList();
void clear();
// Virtual methods
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
void render();
bool isEmpty() const;
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif // _H_EARLYOUTPOLYLIST_

641
engine/collision/convex.cc Executable file
View File

@ -0,0 +1,641 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "core/dataChunker.h"
#include "collision/collision.h"
#include "sceneGraph/sceneGraph.h"
#include "sim/sceneObject.h"
#include "terrain/terrData.h"
#include "collision/convex.h"
#include "collision/gjk.h"
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
static DataChunker sChunker;
CollisionStateList CollisionStateList::sFreeList;
CollisionWorkingList CollisionWorkingList::sFreeList;
F32 sqrDistanceEdges(const Point3F& start0,
const Point3F& end0,
const Point3F& start1,
const Point3F& end1,
Point3F* is,
Point3F* it);
//----------------------------------------------------------------------------
// Collision State
//----------------------------------------------------------------------------
CollisionState::CollisionState()
{
mLista = mListb = 0;
}
CollisionState::~CollisionState()
{
if (mLista)
mLista->free();
if (mListb)
mListb->free();
}
void CollisionState::swap()
{
}
void CollisionState::set(Convex* a,Convex* b,const MatrixF& a2w, const MatrixF& b2w)
{
}
F32 CollisionState::distance(const MatrixF& a2w, const MatrixF& b2w, const F32 dontCareDist,
const MatrixF* w2a, const MatrixF* _w2b)
{
return 0;
}
void CollisionState::render()
{
}
//----------------------------------------------------------------------------
// Feature Collision
//----------------------------------------------------------------------------
bool ConvexFeature::collide(ConvexFeature& cf,CollisionList* cList, F32 tol)
{
// Our vertices vs. other faces
const Point3F* vert = mVertexList.begin();
const Point3F* vend = mVertexList.end();
while (vert != vend) {
cf.testVertex(*vert,cList,false, tol);
vert++;
}
// Other vertices vs. our faces
vert = cf.mVertexList.begin();
vend = cf.mVertexList.end();
while (vert != vend) {
U32 storeCount = cList->count;
testVertex(*vert,cList,true, tol);
// Fix up last reference. material and object are copied from this rather
// than the object we're colliding against.
if (storeCount != cList->count) {
cList->collision[cList->count - 1].material = cf.material;
cList->collision[cList->count - 1].object = cf.object;
}
vert++;
}
// Edge vs. Edge
const Edge* edge = mEdgeList.begin();
const Edge* eend = mEdgeList.end();
while (edge != eend) {
cf.testEdge(this,mVertexList[edge->vertex[0]],
mVertexList[edge->vertex[1]],cList, tol);
edge++;
}
return true;
}
inline bool isInside(const Point3F& p, const Point3F& a, const Point3F& b, const VectorF& n)
{
VectorF v;
mCross(n,b - a,&v);
return mDot(v,p - a) < 0.0f;
}
void ConvexFeature::testVertex(const Point3F& v,CollisionList* cList,bool flip, F32 tol)
{
// Test vertex against all faces
const Face* face = mFaceList.begin();
const Face* end = mFaceList.end();
for (; face != end; face++) {
if (cList->count >= CollisionList::MaxCollisions)
return;
const Point3F& p0 = mVertexList[face->vertex[0]];
const Point3F& p1 = mVertexList[face->vertex[1]];
const Point3F& p2 = mVertexList[face->vertex[2]];
// Point near the plane?
F32 distance = mDot(face->normal,v - p0);
if (distance > tol || distance < -tol)
continue;
// Make sure it's within the bounding edges
if (isInside(v,p0,p1,face->normal) && isInside(v,p1,p2,face->normal) &&
isInside(v,p2,p0,face->normal)) {
// Add collision to this face
Collision& info = cList->collision[cList->count++];
info.point = v;
info.normal = face->normal;
if (flip)
info.normal.neg();
info.material = material;
info.object = object;
info.distance = distance;
}
}
}
void ConvexFeature::testEdge(ConvexFeature* cf,const Point3F& s1, const Point3F& e1, CollisionList* cList, F32 tol)
{
F32 tolSquared = tol*tol;
// Test edges against edges
const Edge* edge = mEdgeList.begin();
const Edge* end = mEdgeList.end();
for (; edge != end; edge++) {
if (cList->count >= CollisionList::MaxCollisions)
return;
const Point3F& s2 = mVertexList[edge->vertex[0]];
const Point3F& e2 = mVertexList[edge->vertex[1]];
// Get the distance and closest points
Point3F i1,i2;
F32 distance = sqrDistanceEdges(s1, e1, s2, e2, &i1, &i2);
if (distance > tolSquared)
continue;
distance = mSqrt(distance);
// Need to figure out how to orient the collision normal.
// The current test involves checking to see whether the collision
// points are contained within the convex volumes, which is slow.
if (inVolume(i1) || cf->inVolume(i2))
distance = -distance;
// Contact normal
VectorF normal = i1 - i2;
normal *= 1 / distance;
// Return a collision
Collision& info = cList->collision[cList->count++];
info.point = i1;
info.normal = normal;
info.distance = distance;
info.material = material;
info.object = object;
}
}
bool ConvexFeature::inVolume(const Point3F& v)
{
// Test the point to see if it's inside the volume
const Face* face = mFaceList.begin();
const Face* end = mFaceList.end();
for (; face != end; face++) {
const Point3F& p0 = mVertexList[face->vertex[0]];
if (mDot(face->normal,v - p0) > 0)
return false;
}
return true;
}
//----------------------------------------------------------------------------
// Collision State management
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
CollisionStateList::CollisionStateList()
{
mPrev = mNext = this;
mState = NULL;
}
void CollisionStateList::linkAfter(CollisionStateList* ptr)
{
mPrev = ptr;
mNext = ptr->mNext;
ptr->mNext = this;
mNext->mPrev = this;
}
void CollisionStateList::unlink()
{
mPrev->mNext = mNext;
mNext->mPrev = mPrev;
mPrev = mNext = this;
}
CollisionStateList* CollisionStateList::alloc()
{
if (!sFreeList.isEmpty()) {
CollisionStateList* nxt = sFreeList.mNext;
nxt->unlink();
nxt->mState = NULL;
return nxt;
}
return constructInPlace((CollisionStateList*)sChunker.alloc(sizeof(CollisionStateList)));
}
void CollisionStateList::free()
{
unlink();
linkAfter(&sFreeList);
}
//----------------------------------------------------------------------------
CollisionWorkingList::CollisionWorkingList()
{
wLink.mPrev = wLink.mNext = this;
rLink.mPrev = rLink.mNext = this;
}
void CollisionWorkingList::wLinkAfter(CollisionWorkingList* ptr)
{
wLink.mPrev = ptr;
wLink.mNext = ptr->wLink.mNext;
ptr->wLink.mNext = this;
wLink.mNext->wLink.mPrev = this;
}
void CollisionWorkingList::rLinkAfter(CollisionWorkingList* ptr)
{
rLink.mPrev = ptr;
rLink.mNext = ptr->rLink.mNext;
ptr->rLink.mNext = this;
rLink.mNext->rLink.mPrev = this;
}
void CollisionWorkingList::unlink()
{
wLink.mPrev->wLink.mNext = wLink.mNext;
wLink.mNext->wLink.mPrev = wLink.mPrev;
wLink.mPrev = wLink.mNext = this;
rLink.mPrev->rLink.mNext = rLink.mNext;
rLink.mNext->rLink.mPrev = rLink.mPrev;
rLink.mPrev = rLink.mNext = this;
}
CollisionWorkingList* CollisionWorkingList::alloc()
{
if (sFreeList.wLink.mNext != &sFreeList) {
CollisionWorkingList* nxt = sFreeList.wLink.mNext;
nxt->unlink();
return nxt;
}
return constructInPlace((CollisionWorkingList*)sChunker.alloc(sizeof(CollisionWorkingList)));
}
void CollisionWorkingList::free()
{
unlink();
wLinkAfter(&sFreeList);
}
//----------------------------------------------------------------------------
// Convex Base Class
//----------------------------------------------------------------------------
U32 Convex::sTag = (U32)-1;
//----------------------------------------------------------------------------
Convex::Convex()
{
mNext = mPrev = this;
mTag = 0;
}
Convex::~Convex()
{
// Unlink from Convex Database
unlink();
// Delete collision states
while (mList.mNext != &mList)
delete mList.mNext->mState;
// Free up working list
while (mWorking.wLink.mNext != &mWorking)
mWorking.wLink.mNext->free();
// Free up references
while (mReference.rLink.mNext != &mReference)
mReference.rLink.mNext->free();
}
//----------------------------------------------------------------------------
void Convex::collectGarbage()
{
// Delete unreferenced Convex Objects
for (Convex* itr = mNext; itr != this; itr = itr->mNext) {
if (itr->mReference.rLink.mNext == &itr->mReference) {
Convex* ptr = itr;
itr = itr->mPrev;
delete ptr;
}
}
}
void Convex::nukeList()
{
// Delete all Convex Objects
for (Convex* itr = mNext; itr != this; itr = itr->mNext) {
Convex* ptr = itr;
itr = itr->mPrev;
delete ptr;
}
}
void Convex::registerObject(Convex *convex)
{
convex->linkAfter(this);
}
//----------------------------------------------------------------------------
void Convex::linkAfter(Convex* ptr)
{
mPrev = ptr;
mNext = ptr->mNext;
ptr->mNext = this;
mNext->mPrev = this;
}
void Convex::unlink()
{
mPrev->mNext = mNext;
mNext->mPrev = mPrev;
mPrev = mNext = this;
}
//----------------------------------------------------------------------------
Point3F Convex::support(const VectorF&) const
{
return Point3F(0,0,0);
}
void Convex::getFeatures(const MatrixF&,const VectorF&,ConvexFeature* f)
{
f->object = NULL;
}
const MatrixF& Convex::getTransform() const
{
return mObject->getTransform();
}
const Point3F& Convex::getScale() const
{
return mObject->getScale();
}
Box3F Convex::getBoundingBox() const
{
return mObject->getWorldBox();
}
Box3F Convex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const
{
Box3F wBox = mObject->getObjBox();
wBox.min.convolve(scale);
wBox.max.convolve(scale);
mat.mul(wBox);
return wBox;
}
void Convex::render()
{
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext)
if (itr->mState->mLista == itr)
itr->mState->render();
}
//----------------------------------------------------------------------------
void Convex::addToWorkingList(Convex* ptr)
{
CollisionWorkingList* cl = CollisionWorkingList::alloc();
cl->wLinkAfter(&mWorking);
cl->rLinkAfter(&ptr->mReference);
cl->mConvex = ptr;
};
//----------------------------------------------------------------------------
void Convex::updateWorkingList(const Box3F& box, const U32 colMask)
{
sTag++;
// Clear objects off the working list that are no longer intersecting
for (CollisionWorkingList* itr = mWorking.wLink.mNext; itr != &mWorking; itr = itr->wLink.mNext) {
itr->mConvex->mTag = sTag;
if ((!box.isOverlapped(itr->mConvex->getBoundingBox())) || (!itr->mConvex->getObject()->isCollisionEnabled())) {
CollisionWorkingList* cl = itr;
itr = itr->wLink.mPrev;
cl->free();
}
}
// Special processing for the terrain and interiors...
AssertFatal(mObject->getContainer(), "Must be in a container!");
SimpleQueryList sql;
mObject->getContainer()->findObjects(box, colMask,SimpleQueryList::insertionCallback, &sql);
for (U32 i = 0; i < sql.mList.size(); i++)
sql.mList[i]->buildConvex(box, this);
}
// ---------------------------------------------------------------------------
void Convex::updateStateList(const MatrixF& mat, const Point3F& scale, const Point3F* displacement)
{
Box3F box1 = getBoundingBox(mat, scale);
box1.min -= Point3F(1, 1, 1);
box1.max += Point3F(1, 1, 1);
if (displacement) {
Point3F oldMin = box1.min;
Point3F oldMax = box1.max;
box1.min.setMin(oldMin + *displacement);
box1.min.setMin(oldMax + *displacement);
box1.max.setMax(oldMin + *displacement);
box1.max.setMax(oldMax + *displacement);
}
sTag++;
// Destroy states which are no longer intersecting
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
Convex* cv = (itr->mState->a == this)? itr->mState->b: itr->mState->a;
cv->mTag = sTag;
if (!box1.isOverlapped(cv->getBoundingBox())) {
CollisionState* cs = itr->mState;
itr = itr->mPrev;
delete cs;
}
}
// Add collision states for new overlapping objects
for (CollisionWorkingList* itr0 = mWorking.wLink.mNext; itr0 != &mWorking; itr0 = itr0->wLink.mNext) {
register Convex* cv = itr0->mConvex;
if (cv->mTag != sTag && box1.isOverlapped(cv->getBoundingBox())) {
CollisionState* state = new GjkCollisionState;
state->set(this,cv,mat,cv->getTransform());
state->mLista->linkAfter(&mList);
state->mListb->linkAfter(&cv->mList);
}
}
}
//----------------------------------------------------------------------------
CollisionState* Convex::findClosestState(const MatrixF& mat, const Point3F& scale, const F32 dontCareDist)
{
updateStateList(mat, scale);
F32 dist = +1E30;
CollisionState *st = 0;
// Prepare scaled version of transform
MatrixF axform = mat;
axform.scale(scale);
MatrixF axforminv(true);
MatrixF temp(mat);
axforminv.scale(Point3F(1.0f/scale.x,1.0f/scale.y,1.0f/scale.z));
temp.affineInverse();
axforminv.mul(temp);
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
CollisionState* state = itr->mState;
if (state->mLista != itr)
state->swap();
// Prepare scaled version of transform
MatrixF bxform = state->b->getTransform();
temp = bxform;
Point3F bscale = state->b->getScale();
bxform.scale(bscale);
MatrixF bxforminv(true);
bxforminv.scale(Point3F(1.0f/bscale.x,1.0f/bscale.y,1.0f/bscale.z));
temp.affineInverse();
bxforminv.mul(temp);
//
F32 dd = state->distance(axform, bxform, dontCareDist, &axforminv, &bxforminv);
if (dd < dist) {
dist = dd;
st = state;
}
}
if (dist < dontCareDist)
return st;
else
return NULL;
}
//----------------------------------------------------------------------------
bool Convex::getCollisionInfo(const MatrixF& mat, const Point3F& scale, CollisionList* cList,F32 tol)
{
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
CollisionState* state = itr->mState;
if (state->mLista != itr)
state->swap();
if (state->dist <= tol) {
ConvexFeature fa,fb;
VectorF v;
// The idea is that we need to scale the matrix, so we need to
// make a copy of it, before we can pass it in to getFeatures.
// This is used to scale us for comparison against the other
// convex, which is correctly scaled.
MatrixF omat = mat;
omat.scale(scale);
MatrixF imat = omat;
imat.inverse();
imat.mulV(-state->v,&v);
getFeatures(omat,v,&fa);
imat = state->b->getTransform();
imat.scale(state->b->getScale());
MatrixF bxform = imat;
imat.inverse();
imat.mulV(state->v,&v);
state->b->getFeatures(bxform,v,&fb);
fa.collide(fb,cList,tol);
}
}
return (cList->count != 0);
}
void Convex::getPolyList(AbstractPolyList*)
{
}
//-----------------------------------------------------------------------------
// This function based on code orignally written for the book:
// 3D Game Engine Design, by David H. Eberly
//
F32 sqrDistanceEdges(const Point3F& start0, const Point3F& end0,
const Point3F& start1, const Point3F& end1,
Point3F* is, Point3F* it)
{
Point3F direction0 = end0 - start0;
F32 fA00 = direction0.lenSquared();
Point3F direction1 = end1 - start1;
F32 fA11 = direction1.lenSquared();
F32 fA01 = -mDot(direction0, direction1);
Point3F kDiff = start0 - start1;
F32 fC = kDiff.lenSquared();
F32 fB0 = mDot(kDiff, direction0);
F32 fDet = mAbs((S32)(fA00*fA11 - fA01*fA01));
// Since the endpoints are tested as vertices, we're not interested
// in parallel lines, and intersections that don't involve end-points.
if (fDet >= 0.00001) {
// Calculate time of intersection for each line
F32 fB1 = -mDot(kDiff, direction1);
F32 fS = fA01*fB1-fA11*fB0;
F32 fT = fA01*fB0-fA00*fB1;
// Only interested in collisions that don't involve the end points
if (fS >= 0.0 && fS <= fDet && fT >= 0.0 && fT <= fDet) {
F32 fInvDet = 1.0 / fDet;
fS *= fInvDet;
fT *= fInvDet;
F32 fSqrDist = (fS*(fA00*fS + fA01*fT + 2.0*fB0) +
fT*(fA01*fS + fA11*fT + 2.0*fB1) + fC);
// Intersection points.
*is = start0 + direction0 * fS;
*it = start1 + direction1 * fT;
return mFabs(fSqrDist);
}
}
// Return a large number in the cases where endpoints are involved.
return 1e10f;
}

249
engine/collision/convex.h Executable file
View File

@ -0,0 +1,249 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONVEX_H_
#define _CONVEX_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
struct Collision;
struct CollisionList;
struct CollisionStateList;
class AbstractPolyList;
class SceneObject;
class Convex;
//----------------------------------------------------------------------------
class ConvexFeature
{
public:
struct Edge {
S32 vertex[2];
};
struct Face {
VectorF normal;
S32 vertex[3];
};
Vector<Point3F> mVertexList;
Vector<Edge> mEdgeList;
Vector<Face> mFaceList;
S32 material;
SceneObject* object;
ConvexFeature() : mVertexList(64), mEdgeList(128), mFaceList(64) {
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mEdgeList);
VECTOR_SET_ASSOCIATION(mFaceList);
}
bool collide(ConvexFeature& cf,CollisionList* cList, F32 tol = 0.1);
void testVertex(const Point3F& v,CollisionList* cList,bool,F32 tol);
void testEdge(ConvexFeature* cf,const Point3F& s1, const Point3F& e1, CollisionList* cList, F32 tol);
bool inVolume(const Point3F& v);
};
//----------------------------------------------------------------------------
enum ConvexType {
TSConvexType,
BoxConvexType,
TerrainConvexType,
InteriorConvexType,
ShapeBaseConvexType,
TSStaticConvexType,
InteriorMapConvexType
};
//----------------------------------------------------------------------------
struct CollisionState
{
CollisionStateList* mLista;
CollisionStateList* mListb;
Convex* a;
Convex* b;
F32 dist; // Current estimated distance
VectorF v; // Vector between closest points
//
CollisionState();
virtual ~CollisionState();
virtual void swap();
virtual void set(Convex* a,Convex* b,const MatrixF& a2w, const MatrixF& b2w);
virtual F32 distance(const MatrixF& a2w, const MatrixF& b2w, const F32 dontCareDist,
const MatrixF* w2a = NULL, const MatrixF* _w2b = NULL);
void render();
};
//----------------------------------------------------------------------------
struct CollisionStateList
{
static CollisionStateList sFreeList;
CollisionStateList* mNext;
CollisionStateList* mPrev;
CollisionState* mState;
CollisionStateList();
void linkAfter(CollisionStateList* next);
void unlink();
bool isEmpty() { return mNext == this; }
static CollisionStateList* alloc();
void free();
};
//----------------------------------------------------------------------------
struct CollisionWorkingList
{
static CollisionWorkingList sFreeList;
struct WLink {
CollisionWorkingList* mNext;
CollisionWorkingList* mPrev;
} wLink;
struct RLink {
CollisionWorkingList* mNext;
CollisionWorkingList* mPrev;
} rLink;
Convex* mConvex;
void wLinkAfter(CollisionWorkingList* next);
void rLinkAfter(CollisionWorkingList* next);
void unlink();
CollisionWorkingList();
static CollisionWorkingList* alloc();
void free();
};
//----------------------------------------------------------------------------
class Convex {
/// @name Linked list managent
/// @{
Convex* mNext; ///< Next item in linked list of Convex's
Convex* mPrev; ///< Previous item in linked list of Convex's
/// Insert this Convex after the provided convex
/// @param next
void linkAfter(Convex* next);
/// Remove this Convex from the linked list
void unlink();
/// @}
U32 mTag;
static U32 sTag;
protected:
CollisionStateList mList; ///< Objects we're testing against
CollisionWorkingList mWorking; ///< Objects within our bounds
CollisionWorkingList mReference; ///< Other convex testing against us
SceneObject* mObject; ///< Object this Convex is built around
ConvexType mType; ///< Type of Convex this is @see ConvexType
//
public:
/// Constructor
Convex();
/// Destructor
virtual ~Convex();
/// Registers another Convex by linking it after this one
void registerObject(Convex *convex);
/// Runs through the linked list of Convex objects and removes the ones
/// with no references
void collectGarbage();
/// Deletes all convex objects in the list
void nukeList();
/// Returns the type of this Convex
ConvexType getType() { return mType; }
/// Returns the object this Convex is built from
SceneObject* getObject() { return mObject; }
/// Traverses mList and renders all collision states
void render();
/// Adds the provided Convex to the list of objects within the bounds of this Convex
/// @param ptr Convex to add to the working list of this object
void addToWorkingList(Convex* ptr);
/// Returns the list of objects currently inside the bounds of this Convex
CollisionWorkingList& getWorkingList() { return mWorking; }
/// Finds the closest
CollisionState* findClosestState(const MatrixF& mat, const Point3F& scale, const F32 dontCareDist = 1);
/// Returns the list of objects this object is testing against
CollisionStateList* getStateList() { return mList.mNext; }
/// Updates the CollisionStateList (mList) with new collision states and removing
/// ones no longer under consideration
/// @param mat Used as the matrix to create a bounding box for updating the list
/// @param scale Used to scale the bounding box
/// @param displacement Bounding box displacement (optional)
void updateStateList(const MatrixF& mat, const Point3F& scale, const Point3F* displacement = NULL);
/// Updates the working collision list of objects which are currently colliding with
/// (inside the bounds of) this Convex.
///
/// @param box Used as the bounding box.
/// @param colMask Mask of objects to check against.
void updateWorkingList(const Box3F& box, const U32 colMask);
/// Returns the transform of the object this is built around
virtual const MatrixF& getTransform() const;
/// Returns the scale of the object this is built around
virtual const Point3F& getScale() const;
/// Returns the bounding box for the object this is built around in world space
virtual Box3F getBoundingBox() const;
/// Returns the object space bounding box for the object this is built around
/// transformed and scaled
/// @param mat Matrix to transform the object-space box by
/// @param scale Scaling factor to scale the bounding box by
virtual Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const;
/// Returns the farthest point, along a vector, still bound by the convex
/// @param v Vector
virtual Point3F support(const VectorF& v) const;
///
virtual void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf);
/// Builds a collision poly list out of this convex
/// @param list (Out) Poly list built
virtual void getPolyList(AbstractPolyList* list);
///
bool getCollisionInfo(const MatrixF& mat, const Point3F& scale, CollisionList* cList,F32 tol);
};
#endif

1290
engine/collision/convexBrush.cc Executable file

File diff suppressed because it is too large Load Diff

127
engine/collision/convexBrush.h Executable file
View File

@ -0,0 +1,127 @@
#ifndef _CONVEXBRUSH_H_
#define _CONVEXBRUSH_H_
#include "platform/platform.h"
#include "core/tVector.h"
#include "math/mPoint.h"
#include "math/mBox.h"
#include "math/mPlane.h"
#include "collision/abstractPolyList.h"
#include "collision/optimizedPolyList.h"
#include "sim/sceneObject.h"
#include "interior/interiorMapRes.h"
#define WINDINGSCALE 10000
class ConvexBrush
{
public:
// Constructor
ConvexBrush();
// Destructor
~ConvexBrush();
enum
{
Front = 0,
Back = 1,
On = 2,
Split = 3,
Unknown = 4
};
enum
{
Unprocessed = 0,
Good,
BadWinding,
ShortPlanes,
BadFaces,
Malformed,
Concave,
Deleted
};
// Supporting structures
struct TexInfo
{
StringTableEntry texture;
PlaneF texGens[2];
F32 scale[2];
F32 rot;
F32 texDiv[2];
};
// The brushes owner entity
InteriorMapResource::Entity* mOwner;
// Bounding box
Box3F mBounds;
Point3F mCentroid;
MatrixF mTransform;
Point3F mScale;
QuatF mRotation;
// lighting info...
MatrixF mLightingTransform;
// Some useful values
F32 mBrushScale;
S32 mID;
U32 mType;
U32 mStatus;
U32 mPrevStatus;
bool mSelected;
StringTableEntry mDebugInfo;
// Scratch buffer storage
MatrixF mTransformScratch;
QuatF mRotationScratch;
// Data
OptimizedPolyList mFaces;
Vector<TexInfo> mTexInfos;
// Setup functions
bool addPlane(PlaneF pln);
bool addFace(PlaneF pln, PlaneF texGen[2], F32 scale[2], char* texture);
bool addFace(PlaneF pln, PlaneF texGen[2], F32 scale[2], char* texture, U32 matIdx);
bool addFace(PlaneF pln, PlaneF texGen[2], F32 scale[2], U32 matIdx);
bool processBrush();
bool setScale(F32 scale);
// Utility functions
bool selfClip();
bool intersectPlanes(const PlaneF& plane1, const PlaneF& plane2, const PlaneF& plane3, Point3D* pOutput);
bool createWinding(const Vector<U32>& rPoints, const Point3F& normal, Vector<U32>& pWinding);
void addEdge(U16 zero, U16 one, U32 face);
bool generateEdgelist();
void validateWindings();
bool validateEdges();
void addIndex(Vector<U32>& indices, U32 index);
void calcCentroid();
void setupTransform();
void resetBrush();
U32 getPolySide(S32 side);
U32 whichSide(PlaneF pln);
U32 whichSide(PlaneF pln, U32 faceIndex);
bool isInsideBox(PlaneF left, PlaneF right, PlaneF top, PlaneF bottom);
bool splitBrush(PlaneF pln, ConvexBrush* fbrush, ConvexBrush* bbrush);
bool calcBounds();
bool castRay(const Point3F& s, const Point3F& e, RayInfo* info);
OptimizedPolyList getIntersectingPolys(OptimizedPolyList* list);
OptimizedPolyList getNonIntersectingPolys(OptimizedPolyList* list);
bool isPolyInside(OptimizedPolyList* list, U32 pdx);
bool getPolyList(AbstractPolyList* list);
// Debug render functions
bool render(bool genColors);
bool renderFace(U32 face, bool renderLighting);
bool renderEdges(ColorF color);
};
#endif

903
engine/collision/depthSortList.cc Executable file
View File

@ -0,0 +1,903 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/depthSortList.h"
#include "core/color.h"
#include "core/fileStream.h" // TODO, remove this
//----------------------------------------------------------------------------
// some defines and global parameters that affect poly split routine
F32 SPLIT_TOL = 0.0005f;
bool ALWAYS_RETURN_FRONT_AND_BACK = false; // if false, split routine will return polys only if a split occurs
// more global parameters
F32 XZ_TOL = 0.0f;
F32 DEPTH_TOL = 0.01f;
#define MIN_Y_DOT 0.05f
DepthSortList * gCurrentSort = NULL;
S32 gForceOverlap = -1; // force an overlap test to result in an overlap
S32 gNoOverlapCount;
S32 gBadSpots = 0;
// if polys are correctly sorted then writing depth values should result in no
// overlap of polys when looking down from camera...otoh, if polys are out of
// order, we should see overlap
bool DepthSortList::renderWithDepth = false;
//----------------------------------------------------------------------------
// following copied from shapeBase.cc because I didn't want to reference
// something from the test program in the core. This should really be a
// stand-alone function, but...
static ColorF cubeColors[8] = {
ColorF(0, 0, 0), ColorF(1, 0, 0), ColorF(0, 1, 0), ColorF(0, 0, 1),
ColorF(1, 1, 0), ColorF(1, 0, 1), ColorF(0, 1, 1), ColorF(1, 1, 1)
};
static Point3F cubePoints[8] = {
Point3F(-1, -1, -1), Point3F(-1, -1, 1), Point3F(-1, 1, -1), Point3F(-1, 1, 1),
Point3F( 1, -1, -1), Point3F( 1, -1, 1), Point3F( 1, 1, -1), Point3F( 1, 1, 1)
};
static U32 cubeFaces[6][4] = {
{ 0, 2, 6, 4 }, { 0, 2, 3, 1 }, { 0, 1, 5, 4 },
{ 3, 2, 6, 7 }, { 7, 6, 4, 5 }, { 3, 7, 5, 1 }
};
void DepthSortList::wireCube(const Point3F& size, const Point3F& pos)
{
glDisable(GL_CULL_FACE);
for(int i = 0; i < 6; i++) {
glBegin(GL_LINE_LOOP);
for(int vert = 0; vert < 4; vert++) {
int idx = cubeFaces[i][vert];
glColor3f(cubeColors[idx].red, cubeColors[idx].green, cubeColors[idx].blue);
glVertex3f(cubePoints[idx].x * size.x + pos.x, cubePoints[idx].y * size.y + pos.y, cubePoints[idx].z * size.z + pos.z);
}
glEnd();
}
}
//----------------------------------------------------------------------------
DepthSortList::DepthSortList()
{
VECTOR_SET_ASSOCIATION(mPolyExtentsList);
VECTOR_SET_ASSOCIATION(mPolyIndexList);
}
DepthSortList::~DepthSortList()
{
}
//----------------------------------------------------------------------------
void DepthSortList::clear()
{
Parent::clear();
mPolyExtentsList.clear();
mPolyIndexList.clear();
clearSort();
}
void DepthSortList::clearSort()
{
mBase = -1;
mMaxTouched = 0;
gNoOverlapCount = 0;
}
//----------------------------------------------------------------------------
void DepthSortList::end()
{
S32 numPoly = mPolyList.size();
if (mPolyList.last().plane.y >= -MIN_Y_DOT)
{
mIndexList.setSize(mPolyList.last().vertexStart);
mPolyList.decrement();
return;
}
Parent::end();
// we deleted this poly, be sure not to add anything more...
if (mPolyList.size()!=numPoly)
return;
AssertFatal(mPolyList.last().vertexCount>2,"DepthSortList::end: only two vertices in poly");
mPolyExtentsList.increment();
setExtents(mPolyList.last(),mPolyExtentsList.last());
mPolyIndexList.push_back(numPoly-1);
}
//----------------------------------------------------------------------------
bool DepthSortList::getMapping(MatrixF * mat, Box3F * box)
{
// return list transform and bounds in list space...optional
*mat = mMatrix;
mat->inverse();
box->min.set(-mExtent.x, 0.0f, -mExtent.z);
box->max.set( mExtent.x, 2.0f * mExtent.y, mExtent.z);
return true;
}
//----------------------------------------------------------------------------
void DepthSortList::render()
{
glPushMatrix();
glPushAttrib(GL_DEPTH_BUFFER_BIT);
MatrixF mat = mBaseMatrix;
mat.inverse();
dglMultMatrix(&mat);
glVertexPointer(3,GL_FLOAT,sizeof(Vertex),mVertexList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1,0,0,0.25);
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
if (renderWithDepth)
glDepthMask(GL_TRUE);
else
glDepthMask(GL_FALSE);
glDisable(GL_CULL_FACE);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
Poly * p;
S32 only = Con::getIntVariable("$only",-1);
for (S32 i=0; i<mPolyIndexList.size(); i++)
{
if (only>=0 && only!=i)
continue;
p = &mPolyList[mPolyIndexList[i]];
glDrawElements(GL_POLYGON,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
glDisableClientState(GL_VERTEX_ARRAY);
// draw outline around clip zone...
wireCube(mExtent,Point3F(0,mExtent.y*0.5f,0));
glPopMatrix();
glPopAttrib();
}
//----------------------------------------------------------------------------
void DepthSortList::setExtents(Poly & poly, PolyExtents & polyExtents)
{
Point3F p = mVertexList[mIndexList[poly.vertexStart]].point;
polyExtents.xMin = polyExtents.xMax = p.x;
polyExtents.yMin = polyExtents.yMax = p.y;
polyExtents.zMin = polyExtents.zMax = p.z;
for (S32 i=poly.vertexStart+1; i<poly.vertexStart+poly.vertexCount; i++)
{
Point3F p = mVertexList[mIndexList[i]].point;
// x
if (p.x < polyExtents.xMin)
polyExtents.xMin = p.x;
else if (p.x > polyExtents.xMax)
polyExtents.xMax = p.x;
// y
if (p.y < polyExtents.yMin)
polyExtents.yMin = p.y;
else if (p.y > polyExtents.yMax)
polyExtents.yMax = p.y;
// z
if (p.z < polyExtents.zMin)
polyExtents.zMin = p.z;
else if (p.z > polyExtents.zMax)
polyExtents.zMax = p.z;
}
}
//----------------------------------------------------------------------------
// function for comparing two poly indices
S32 FN_CDECL compareYExtents( const void* e1, const void* e2)
{
DepthSortList::PolyExtents & poly1 = gCurrentSort->getExtents(*(U32*)e1);
DepthSortList::PolyExtents & poly2 = gCurrentSort->getExtents(*(U32*)e2);
if (poly1.yMin < poly2.yMin)
return -1;
if (poly2.yMin < poly1.yMin)
return 1;
return 0;
}
//----------------------------------------------------------------------------
void DepthSortList::sortByYExtents()
{
gCurrentSort = this;
dQsort(mPolyIndexList.address(),mPolyIndexList.size(),sizeof(U32),compareYExtents);
}
//----------------------------------------------------------------------------
void DepthSortList::set(const MatrixF & mat, Point3F & extents)
{
setBaseTransform(mat);
mNormal.set(0,1,0); // ignore polys not facing up...
mExtent = extents;
mExtent *= 0.5f;
// set clip planes
mPlaneList.clear();
mPlaneList.increment();
mPlaneList.last().set(-1.0f, 0.0f, 0.0f);
mPlaneList.last().d = -mExtent.x;
mPlaneList.increment();
mPlaneList.last().set( 1.0f, 0.0f, 0.0f);
mPlaneList.last().d = -mExtent.x;
mPlaneList.increment();
mPlaneList.last().set( 0.0f,-1.0f, 0.0f);
mPlaneList.last().d = 0;
mPlaneList.increment();
mPlaneList.last().set( 0.0f, 1.0f, 0.0f);
mPlaneList.last().d = -2.0f * mExtent.y;
mPlaneList.increment();
mPlaneList.last().set( 0.0f, 0.0f,-1.0f);
mPlaneList.last().d = -mExtent.z;
mPlaneList.increment();
mPlaneList.last().set( 0.0f, 0.0f, 1.0f);
mPlaneList.last().d = -mExtent.z;
}
//----------------------------------------------------------------------------
void DepthSortList::setBase(S32 base)
{
mBase = base;
getOrderedPoly(mBase, &mBasePoly, &mBaseExtents);
mBaseNormal = &mBasePoly->plane;
mBaseDot = -mBasePoly->plane.d;
mBaseYMax = mBaseExtents->yMax;
}
//----------------------------------------------------------------------------
bool DepthSortList::sortNext()
{
// find the next poly in the depth order
// NOTE: a closer poly may occur before a farther away poly so long as
// they don't overlap in the x-z plane...
if (++mBase>=mPolyIndexList.size())
return false;
setBase(mBase);
gBadSpots = 0;
ALWAYS_RETURN_FRONT_AND_BACK = false; // split routine will return polys only if a split occurs
bool switched = false; // haven't switched base poly yet
S32 i = 0; // currently comparing base to base+i
Poly * testPoly;
PolyExtents * testExtents;
while (mBase+i+1<mPolyIndexList.size())
{
i++;
// get test poly...
getOrderedPoly(mBase+i,&testPoly,&testExtents);
Point3F & testNormal = testPoly->plane;
F32 testDot = -testPoly->plane.d;
// if base poly's y extents don't overlap test poly's, base poly can stay where it is...
if (mBase+i>mMaxTouched && mBaseYMax<=testExtents->yMin+DEPTH_TOL)
break;
// if base poly and test poly don't have overlapping x & z extents, then order doesn't matter...stay the same
if (mBaseExtents->xMin>=testExtents->xMax-XZ_TOL || mBaseExtents->xMax<=testExtents->xMin+XZ_TOL ||
mBaseExtents->zMin>=testExtents->zMax-XZ_TOL || mBaseExtents->zMax<=testExtents->zMin+XZ_TOL)
continue;
// is test poly completely behind base poly? if so, order is fine as it is
S32 v;
for (v=0; v<testPoly->vertexCount; v++)
if (mDot(mVertexList[mIndexList[testPoly->vertexStart+v]].point,*mBaseNormal)>mBaseDot+DEPTH_TOL)
break;
if (v==testPoly->vertexCount)
// test behind base
continue;
// is base poly completely in front of test poly? if so, order is fine as it is
for (v=0; v<mBasePoly->vertexCount; v++)
if (mDot(mVertexList[mIndexList[mBasePoly->vertexStart+v]].point,testNormal)<testDot-DEPTH_TOL)
break;
if (v==mBasePoly->vertexCount)
// base in front of test
continue;
// if the polys don't overlap in the x-z plane, then order doesn't matter, stay as we are
if (!overlap(mBasePoly,testPoly))
{
gNoOverlapCount++;
if (gNoOverlapCount!=gForceOverlap)
continue;
}
// handle switching/splitting of polys due to overlap
handleOverlap(testPoly,testNormal,testDot,i,switched);
}
return true;
}
//----------------------------------------------------------------------------
void DepthSortList::sort()
{
// depth sort mPolyIndexList -- entries are indices into mPolyList (where poly is found) & mPolyExtentsList
// sort by min y extent
sortByYExtents();
mBase = -1;
while (sortNext())
;
}
//----------------------------------------------------------------------------
void DepthSortList::handleOverlap(Poly * testPoly, Point3F & testNormal, F32 testDot, S32 & testOffset, bool & switched)
{
// first reverse the plane tests (i.e., test to see if basePoly behind testPoly or testPoly in front of basePoly...
// if either succeeds, switch poly
// if they both fail, split base poly
// But split anyway if basePoly has already been switched...
bool doSwitch = false;
if (!switched)
{
S32 v;
for (v=0; v<mBasePoly->vertexCount; v++)
if (mDot(mVertexList[mIndexList[mBasePoly->vertexStart+v]].point,testNormal)>testDot+DEPTH_TOL)
break;
if (v==mBasePoly->vertexCount)
doSwitch = true;
else
{
for (v=0; v<testPoly->vertexCount; v++)
if (mDot(mVertexList[mIndexList[testPoly->vertexStart+v]].point,*mBaseNormal)<mBaseDot-DEPTH_TOL)
break;
if (v==testPoly->vertexCount)
doSwitch = true;
}
}
// try to split base poly along plane of test poly
Poly frontPoly, backPoly;
bool splitBase = false, splitTest = false;
if (!doSwitch)
{
splitBase = splitPoly(*mBasePoly,testNormal,testDot,frontPoly,backPoly);
if (!splitBase)
// didn't take...no splitting happened...try splitting test poly by base poly
splitTest = splitPoly(*testPoly,*mBaseNormal,mBaseDot,frontPoly,backPoly);
}
U32 testIdx = mPolyIndexList[mBase+testOffset];
// should we switch order of test and base poly? Might have to even if we
// don't want to if there's no splitting to do...
// Note: possibility that infinite loop can be introduced here...if that happens,
// then we need to split along edges of polys
if (doSwitch || (!splitTest && !splitBase))
{
if (!doSwitch && gBadSpots++ > (mPolyIndexList.size()-mBase)<<1)
// got here one too many times...just leave and don't touch poly -- avoid infinite loop
return;
// move test poly to the front of the order
dMemmove(&mPolyIndexList[mBase+1],&mPolyIndexList[mBase],testOffset*sizeof(U32));
mPolyIndexList[mBase] = testIdx;
// base poly changed...
setBase(mBase);
if (mBase+testOffset>mMaxTouched)
mMaxTouched=mBase+testOffset;
testOffset=1; // don't need to compare against old base
switched=true;
return;
}
if (splitBase)
{
// need one more spot...frontPoly and backPoly replace basePoly
setExtents(frontPoly,mPolyExtentsList[mPolyIndexList[mBase]]);
mPolyExtentsList.increment();
setExtents(backPoly,mPolyExtentsList.last());
mPolyList[mPolyIndexList[mBase]] = frontPoly;
mPolyIndexList.insert(mBase+1);
mPolyIndexList[mBase+1] = mPolyList.size();
mPolyList.push_back(backPoly);
// new base poly...
setBase(mBase);
// increase tsetOffset & mMaxTouched because of insertion of back poly
testOffset++;
mMaxTouched++;
//
switched=false;
return;
}
// splitTest -- no other way to get here
AssertFatal(splitTest,"DepthSortList::handleOverlap: how did we get here like this?");
// put frontPoly in front of basePoly, leave backPoly where testPoly was
// we need one more spot (testPoly broken into front and back)
// and we need to shift everything from base up to test down one spot
mPolyIndexList.insert(mBase);
// need one more poly for front poly
mPolyIndexList[mBase] = mPolyList.size();
mPolyList.push_back(frontPoly);
mPolyExtentsList.increment();
setExtents(mPolyList.last(),mPolyExtentsList.last());
// set up back poly
mPolyList[testIdx] = backPoly;
setExtents(mPolyList[testIdx],mPolyExtentsList[testIdx]);
// new base poly...
setBase(mBase);
// we inserted an element, increase mMaxTouched...
mMaxTouched++;
testOffset=0;
switched = false;
}
//----------------------------------------------------------------------------
bool DepthSortList::overlap(Poly * poly1, Poly * poly2)
{
// check for overlap without any shortcuts
S32 sz1 = poly1->vertexCount;
S32 sz2 = poly2->vertexCount;
Point3F * a1, * b1;
Point3F * a2, * b2;
Point2F norm;
F32 dot;
b1 = &mVertexList[mIndexList[poly1->vertexStart+sz1-1]].point;
S32 i;
for (i=0; i<sz1; i++)
{
a1 = b1;
b1 = &mVertexList[mIndexList[poly1->vertexStart+i]].point;
// get the mid-point of this edge
Point3F mid1 = *a1+*b1;
mid1 *= 0.5f;
bool midOutside = false;
b2 = &mVertexList[mIndexList[poly2->vertexStart+sz2-1]].point;
for (S32 j=0; j<sz2; j++)
{
a2 = b2;
b2 = &mVertexList[mIndexList[poly2->vertexStart+j]].point;
// test for intersection of a1-b1 and a2-b2 (on x-z plane)
// they intersect if a1 & b1 are on opp. sides of line a2-b2
// and a2 & b2 are on opp. sides of line a1-b1
norm.set(a2->z - b2->z, b2->x - a2->x); // normal to line a2-b2
dot = norm.x * a2->x + norm.y * a2->z; // dot of a2 and norm
if (norm.x * mid1.x + norm.y * mid1.z - dot >= 0) // special check for midpoint of line
midOutside = true;
if ( ((norm.x * a1->x + norm.y * a1->z) - dot) * ((norm.x * b1->x + norm.y * b1->z) - dot) >= 0 )
// a1 & b1 are on the same side of the line a2-b2...edges don't overlap
continue;
norm.set(a1->z - b1->z, b1->x - a1->x); // normal to line a1-b1
dot = norm.x * a1->x + norm.y * a1->z; // dot of a1 and norm
if ( ((norm.x * a2->x + norm.y * a2->z) - dot) * ((norm.x * b2->x + norm.y * b2->z) - dot) >= 0 )
// a2 & b2 are on the same side of the line a1-b1...edges don't overlap
continue;
return true; // edges overlap, so polys overlap
}
if (!midOutside)
return true; // midpoint of a1-b1 is inside the poly
}
// edges don't overlap...but one poly might be contained inside the other
Point3F center = mVertexList[mIndexList[poly2->vertexStart]].point;
for (i=1; i<sz2; i++)
center += mVertexList[mIndexList[poly2->vertexStart+i]].point;
center *= 1.0f / (F32)poly2->vertexCount;
b1 = &mVertexList[mIndexList[poly1->vertexStart+sz1-1]].point;
for (i=0; i<sz1; i++)
{
a1 = b1;
b1 = &mVertexList[mIndexList[poly1->vertexStart+i]].point;
norm.set(a1->z - b1->z, b1->x - a1->x); // normal to line a1-b1
dot = norm.x * a1->x + norm.y * a1->z; // dot of a1 and norm
if (center.x * norm.x + center.z * norm.y > dot)
// center is outside this edge, poly2 is not inside poly1
break;
}
if (i==sz1)
return true; // first vert of poly2 inside poly1 (so all of poly2 inside poly1)
center = mVertexList[mIndexList[poly1->vertexStart]].point;
for (i=1; i<sz1; i++)
center += mVertexList[mIndexList[poly1->vertexStart+i]].point;
center *= 1.0f / (F32)poly1->vertexCount;
b2 = &mVertexList[mIndexList[poly2->vertexStart+sz2-1]].point;
for (i=0; i<sz2; i++)
{
a2 = b2;
b2 = &mVertexList[mIndexList[poly2->vertexStart+i]].point;
norm.set(a2->z - b2->z, b2->x - a2->x); // normal to line a2-b2
dot = norm.x * a2->x + norm.y * a2->z; // dot of a1 and norm
if (center.x * norm.x + center.z * norm.y > dot)
// v is outside this edge, poly1 is not inside poly2
break;
}
if (i==sz2)
return true; // first vert of poly1 inside poly2 (so all of poly1 inside poly2)
return false; // we survived, no overlap
}
//----------------------------------------------------------------------------
Vector<U32> frontVerts(__FILE__, __LINE__);
Vector<U32> backVerts(__FILE__, __LINE__);
// Split source poly into front and back. If either front or back is degenerate, don't do anything.
// If we have a front and a back, then add the verts to our vertex list and fill out the poly structures.
bool DepthSortList::splitPoly(const Poly & src, Point3F & normal, F32 k, Poly & frontPoly, Poly & backPoly)
{
frontVerts.clear();
backVerts.clear();
// already degenerate...
AssertFatal(src.vertexCount>=3,"DepthSortList::splitPoly - Don't need to split a triangle!");
S32 startSize = mVertexList.size();
// Assume back and front are degenerate polygons until proven otherwise.
bool backDegen = true, frontDegen = true;
U32 bIdx;
Point3F * a, * b;
F32 dota, dotb;
S32 signA, signB;
F32 splitTolSq = SPLIT_TOL * SPLIT_TOL * mDot(normal,normal);
bIdx = mIndexList[src.vertexStart+src.vertexCount-1];
b = &mVertexList[bIdx].point;
dotb = mDot(normal,*b)-k;
// Sign variable coded as follows: 1 for outside, 0 on the plane and -1 for inside.
if (dotb*dotb > splitTolSq)
signB = dotb > 0.0f ? 1 : -1;
else
signB = 0;
S32 i;
for (i = 0; i<src.vertexCount; i++)
{
a = b;
bIdx = mIndexList[src.vertexStart+i];
b = &mVertexList[bIdx].point;
dota = dotb;
dotb = mDot(normal,*b)-k;
signA = signB;
if (dotb*dotb > splitTolSq)
signB = dotb > 0.0f ? 1 : -1;
else
signB = 0;
switch(signA*3 + signB + 4) // +4 is to make values go from 0 up...hopefully enticing compiler to make a jump-table
{
case 0: // A-, B-
case 3: // A., B-
backVerts.push_back(bIdx);
backDegen = false;
break;
case 8: // A+, B+
case 5: // A., B+
frontVerts.push_back(bIdx);
frontDegen = false;
break;
case 1: // A-, B.
case 4: // A., B.
case 7: // A+, B.
backVerts.push_back(bIdx);
frontVerts.push_back(bIdx);
break;
case 2: // A-, B+
{
// intersect line A-B with plane
F32 dotA = mDot(*a,normal);
F32 dotB = mDot(*b,normal);
Vertex v;
v.point = *a-*b;
v.point *= (k-dotB)/(dotA-dotB);
v.point += *b;
frontVerts.push_back(mVertexList.size());
backVerts.push_back(mVertexList.size());
frontVerts.push_back(bIdx);
mVertexList.push_back(v);
b = &mVertexList[bIdx].point; // better get this pointer again since we just incremented vector
frontDegen = false;
break;
}
case 6: // A+, B-
{
// intersect line A-B with plane
F32 dotA = mDot(*a,normal);
F32 dotB = mDot(*b,normal);
Vertex v;
v.point = *a-*b;
v.point *= (k-dotB)/(dotA-dotB);
v.point += *b;
frontVerts.push_back(mVertexList.size());
backVerts.push_back(mVertexList.size());
backVerts.push_back(bIdx);
mVertexList.push_back(v);
b = &mVertexList[bIdx].point; // better get this pointer again since we just incremented vector
backDegen = false;
break;
}
}
}
// Check for degeneracy.
if (!ALWAYS_RETURN_FRONT_AND_BACK)
{
if (frontVerts.size()<3 || backVerts.size()<3 || frontDegen || backDegen)
{
// didn't need to be split...return two empty polys
// and restore vertex list to how it was
mVertexList.setSize(startSize);
frontPoly.vertexCount = backPoly.vertexCount = 0;
return false;
}
}
else
{
if (frontDegen)
frontVerts.clear();
if (backDegen)
backVerts.clear();
}
// front poly
frontPoly.plane = src.plane;
frontPoly.object = src.object;
frontPoly.material = src.material;
frontPoly.vertexStart = mIndexList.size();
frontPoly.vertexCount = frontVerts.size();
frontPoly.surfaceKey = src.surfaceKey;
// back poly
backPoly.plane = src.plane;
backPoly.object = src.object;
backPoly.material = src.material;
backPoly.vertexStart = frontPoly.vertexStart + frontPoly.vertexCount;
backPoly.vertexCount = backVerts.size();
backPoly.surfaceKey = src.surfaceKey;
// add indices
mIndexList.setSize(backPoly.vertexStart+backPoly.vertexCount);
if( frontPoly.vertexCount ) {
dMemcpy(&mIndexList[frontPoly.vertexStart],
frontVerts.address(),
sizeof(U32)*frontPoly.vertexCount);
}
if( backPoly.vertexCount ) {
dMemcpy(&mIndexList[backPoly.vertexStart],
backVerts.address(),
sizeof(U32)*backPoly.vertexCount);
}
return true;
}
//----------------------------------------------------------------------------
Vector<DepthSortList::Poly> gWorkListA(256);
Vector<DepthSortList::Poly> gWorkListB(256);
void DepthSortList::depthPartition(const Point3F * sourceVerts, U32 numVerts, Vector<Poly> & partition, Vector<Point3F> & partitionVerts)
{
// create the depth partition of the passed poly
// a depth partition is a partition of the poly on the
// x-z plane so that each sub-poly in the partition can be
// mapped onto exactly one plane in the depth list (i.e.,
// those polys found in mPolyIndexList... the ones that are
// depth sorted). The plane the sub-polys are mapped onto
// is the plane of the closest facing poly.
//
// y-coord of input polys are ignored, and are remapped
// on output to put the output polys on the
// corresponding planes.
// This routine is confusing because there are three lists of polys.
//
// The source list (passed in as a single poly, but becomes a list as
// it is split up) comprises the poly to be partitioned. Verts for sourcePoly
// are held in sourceVerts when passed to this routine, but immediately copied
// to mVertexList (and indices are added for each vert to mIndexList).
//
// The scraps list is generated from the source poly (it contains the outside
// piece of each cut that is made). Indices for polys in the scraps list are
// found in mIndexList and verts are found in mVerts list. Note that the depthPartition
// routine will add verts and indices to the member lists, but not polys.
//
// Finally, the partition list is the end result -- the depth partition. These
// polys are not indexed polys. The vertexStart field indexes directly into partitionVerts
// array.
if (mBase<0)
// begin the depth sort
sortByYExtents();
// apply cookie cutter to these polys
Vector<Poly> * sourceList = &gWorkListA;
sourceList->clear();
// add source poly for to passed verts
sourceList->increment();
sourceList->last().vertexStart = mIndexList.size();
sourceList->last().vertexCount = numVerts;
// add verts of source poly to mVertexList and mIndexList
mVertexList.setSize(mVertexList.size()+numVerts);
mIndexList.setSize(mIndexList.size()+numVerts);
for (S32 v=0; v<numVerts; v++)
{
mVertexList[mVertexList.size()-numVerts+v].point = sourceVerts[v];
mIndexList[mIndexList.size()-numVerts+v] = mVertexList.size()-numVerts+v;
}
// put scraps from cookie cutter in this list
Vector<Poly> * scraps = &gWorkListB;
scraps->clear();
S32 i=0;
while (sourceList->size())
{
if (i>=mBase && !sortNext())
// that's it, no more polys to sort
break;
AssertFatal(i<=mBase,"DepthSortList::depthPartition - exceeded mBase.");
// use the topmost poly as the cookie cutter
Poly & cutter = mPolyList[mPolyIndexList[i]];
S32 startVert = partitionVerts.size();
S32 j;
for (j=0; j<sourceList->size(); j++)
{
Poly & toCut = (*sourceList)[j];
cookieCutter(cutter,toCut,*scraps,partition,partitionVerts);
}
// project all the new verts onto the cutter's plane
AssertFatal(mFabs(cutter.plane.y)>=MIN_Y_DOT,"DepthSortList::depthPartition - below MIN_Y_DOT.");
F32 invY = -1.0f / cutter.plane.y;
for (j=startVert; j<partitionVerts.size(); j++)
partitionVerts[j].y = invY * (cutter.plane.d + cutter.plane.x * partitionVerts[j].x + cutter.plane.z * partitionVerts[j].z);
sourceList->clear();
// swap work lists -- scraps become source for next closest poly
Vector<Poly> * tmpListPtr = sourceList;
sourceList = scraps;
scraps = tmpListPtr;
i++;
}
}
//----------------------------------------------------------------------------
void DepthSortList::cookieCutter(Poly & cutter, Poly & cuttee,
Vector<Poly> & scraps, // outsides
Vector<Poly> & cookies, Vector<Point3F> & cookieVerts) // insides
{
// perhaps going too far with the cookie cutter analogy, but...
// cutter is used to cut cuttee
//
// the part that is inside of all cutter edges (on x-z plane)
// is put into the "cookie" list, parts that are outside are put
// onto the "scraps" list. "scraps" are indexed polys with indices
// and vertices in mIndexList and mVertexList. Cookies aren't indexed
// and points go into cookieVerts list.
ALWAYS_RETURN_FRONT_AND_BACK = true; // split routine will return polys even if no split occurs
// save off current state in case nothing inside all the edges of cutter (i.e., no "cookie")
S32 vsStart = cuttee.vertexStart;
S32 vcStart = cuttee.vertexCount;
S32 milStart = mIndexList.size();
S32 mvlStart = mVertexList.size();
S32 scStart = scraps.size();
Point3F a, b;
Poly scrap;
a = mVertexList[mIndexList[cutter.vertexStart+cutter.vertexCount-1]].point;
for (S32 i=0; i<cutter.vertexCount; i++)
{
b = mVertexList[mIndexList[cutter.vertexStart+i]].point;
Point3F n(a.z-b.z,0.0f,b.x-a.x);
F32 k = mDot(n,a);
splitPoly(cuttee,n,k,scrap,cuttee);
if (scrap.vertexCount)
scraps.push_back(scrap);
if (!cuttee.vertexCount)
// cut down to nothing...no need to keep cutting
break;
a = b;
}
if (cuttee.vertexCount)
{
// cuttee is non-degenerate, add it to cookies
cookies.push_back(cuttee);
cookies.last().vertexStart = cookieVerts.size();
for (S32 i=0; i<cuttee.vertexCount; i++)
cookieVerts.push_back(mVertexList[mIndexList[cuttee.vertexStart+i]].point);
}
else
{
// no cookie -- leave things as they were (except add cuttee to scraps)
cuttee.vertexStart = vsStart;
cuttee.vertexCount = vcStart;
mIndexList.setSize(milStart);
mVertexList.setSize(mvlStart);
scraps.setSize(scStart);
scraps.push_back(cuttee);
}
}
//----------------------------------------------------------------------------

108
engine/collision/depthSortList.h Executable file
View File

@ -0,0 +1,108 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _DEPTHSORTLIST_H_
#define _DEPTHSORTLIST_H_
#ifndef _CLIPPEDPOLYLIST_H_
#include "collision/clippedPolyList.h"
#endif
//----------------------------------------------------------------------------
class DepthSortList : public ClippedPolyList
{
typedef ClippedPolyList Parent;
public:
struct PolyExtents
{
// extents of poly on each coordinate axis
F32 xMin;
F32 xMax;
F32 yMin;
F32 yMax;
F32 zMin;
F32 zMax;
};
typedef Vector<PolyExtents> PolyExtentsList;
typedef Vector<U32> PolyIndexList;
// Internal data
PolyExtentsList mPolyExtentsList;
PolyIndexList mPolyIndexList;
Point3F mExtent; // dimensions of the sort area
S32 mBase; // base position in the list...everything before this is sorted correctly
Poly * mBasePoly; // poly currently in base position
Point3F * mBaseNormal; // normal of poly currently in base position
F32 mBaseDot; // dot of basePoly with baseNormal
F32 mBaseYMax; // max y extent of base poly
S32 mMaxTouched; // highest index swapped into thus far...y-extents may be improperly sorted before this index
PolyExtents * mBaseExtents; // x,y,z extents of basePoly
// set the base position -- everything before this point should be correctly sorted
void setBase(S32);
// some utilities used for sorting
bool splitPoly(const Poly & sourcePoly, Point3F & normal, F32 k, Poly & front, Poly & back);
bool overlap(Poly *, Poly *);
void handleOverlap(Poly * testPoly, Point3F & testNormal, F32 testDot, S32 & testOffset, bool & switched);
void sortByYExtents();
void setExtents(Poly &, PolyExtents &);
// one iteration of the sort routine -- finds a poly to fill current base position
bool sortNext();
// used by depthPartition
void cookieCutter(Poly & cutter, Poly & cuttee,
Vector<Poly> & scraps,
Vector<Poly> & cookies, Vector<Point3F> & cookieVerts);
void wireCube(const Point3F & size, const Point3F & pos);
public:
//
DepthSortList();
~DepthSortList();
void set(const MatrixF & mat, Point3F & extents);
void clear();
void clearSort();
// the point of this class
void sort();
void depthPartition(const Point3F * sourceVerts, U32 numVerts, Vector<Poly> & partition, Vector<Point3F> & partitionVerts);
// Virtual methods
void end();
// U32 addPoint(const Point3F& p);
// bool isEmpty() const;
// void begin(U32 material,U32 surfaceKey);
// void plane(U32 v1,U32 v2,U32 v3);
// void plane(const PlaneF& p);
// void vertex(U32 vi);
bool getMapping(MatrixF *, Box3F *);
// access to the polys in order (note: returned pointers are volatile, may change if polys added).
void getOrderedPoly(U32 ith, Poly ** poly, PolyExtents ** polyExtent);
// unordered access
PolyExtents & getExtents(U32 idx) { return mPolyExtentsList[idx]; }
Poly & getPoly(U32 idx) { return mPolyList[idx]; }
//
void render();
static bool renderWithDepth;
};
inline void DepthSortList::getOrderedPoly(U32 ith, Poly ** poly, PolyExtents ** polyExtent)
{
*poly = &mPolyList[mPolyIndexList[ith]];
*polyExtent = &mPolyExtentsList[mPolyIndexList[ith]];
}
#endif

View File

@ -0,0 +1,267 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/earlyOutPolyList.h"
//----------------------------------------------------------------------------
EarlyOutPolyList::EarlyOutPolyList()
{
VECTOR_SET_ASSOCIATION(mPolyList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
VECTOR_SET_ASSOCIATION(mPlaneList);
mNormal.set(0, 0, 0);
mIndexList.reserve(100);
mEarlyOut = false;
}
EarlyOutPolyList::~EarlyOutPolyList()
{
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::clear()
{
// Only clears internal data
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPolyPlaneList.clear();
mEarlyOut = false;
}
bool EarlyOutPolyList::isEmpty() const
{
return mEarlyOut == false;
}
//----------------------------------------------------------------------------
U32 EarlyOutPolyList::addPoint(const Point3F& p)
{
if (mEarlyOut == true)
return 0;
mVertexList.increment();
Vertex& v = mVertexList.last();
v.point.x = p.x * mScale.x;
v.point.y = p.y * mScale.y;
v.point.z = p.z * mScale.z;
mMatrix.mulP(v.point);
// Build the plane mask
v.mask = 0;
for (U32 i = 0; i < mPlaneList.size(); i++)
if (mPlaneList[i].distToPlane(v.point) > 0)
v.mask |= 1 << i;
// If the point is inside all the planes, then we're done!
if (v.mask == 0)
mEarlyOut = true;
return mVertexList.size() - 1;
}
U32 EarlyOutPolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::begin(U32 material,U32 surfaceKey)
{
if (mEarlyOut == true)
return;
mPolyList.increment();
Poly& poly = mPolyList.last();
poly.object = mCurrObject;
poly.material = material;
poly.vertexStart = mIndexList.size();
poly.surfaceKey = surfaceKey;
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::plane(U32 v1,U32 v2,U32 v3)
{
if (mEarlyOut == true)
return;
mPolyList.last().plane.set(mVertexList[v1].point,
mVertexList[v2].point,mVertexList[v3].point);
}
void EarlyOutPolyList::plane(const PlaneF& p)
{
if (mEarlyOut == true)
return;
mPlaneTransformer.transform(p, mPolyList.last().plane);
}
void EarlyOutPolyList::plane(const U32 index)
{
if (mEarlyOut == true)
return;
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPolyList.last().plane = mPolyPlaneList[index];
}
const PlaneF& EarlyOutPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::vertex(U32 vi)
{
if (mEarlyOut == true)
return;
mIndexList.push_back(vi);
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::end()
{
if (mEarlyOut == true)
return;
Poly& poly = mPolyList.last();
// Anything facing away from the mNormal is rejected
if (mDot(poly.plane,mNormal) > 0) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
// Build intial inside/outside plane masks
U32 indexStart = poly.vertexStart;
U32 vertexCount = mIndexList.size() - indexStart;
U32 frontMask = 0,backMask = 0;
U32 i;
for (i = indexStart; i < mIndexList.size(); i++) {
U32 mask = mVertexList[mIndexList[i]].mask;
frontMask |= mask;
backMask |= ~mask;
}
// Trivial accept if all the vertices are on the backsides of
// all the planes.
if (!frontMask) {
poly.vertexCount = vertexCount;
mEarlyOut = true;
return;
}
// Trivial reject if any plane not crossed has all it's points
// on the front.
U32 crossMask = frontMask & backMask;
if (~crossMask & frontMask) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
// Need to do some clipping
for (U32 p = 0; p < mPlaneList.size(); p++) {
U32 pmask = 1 << p;
// Only test against this plane if we have something
// on both sides
if (crossMask & pmask) {
U32 indexEnd = mIndexList.size();
U32 i1 = indexEnd - 1;
U32 mask1 = mVertexList[mIndexList[i1]].mask;
for (U32 i2 = indexStart; i2 < indexEnd; i2++) {
U32 mask2 = mVertexList[mIndexList[i2]].mask;
if ((mask1 ^ mask2) & pmask) {
//
mVertexList.increment();
VectorF& v1 = mVertexList[mIndexList[i1]].point;
VectorF& v2 = mVertexList[mIndexList[i2]].point;
VectorF vv = v2 - v1;
F32 t = -mPlaneList[p].distToPlane(v1) / mDot(mPlaneList[p],vv);
mIndexList.push_back(mVertexList.size() - 1);
Vertex& iv = mVertexList.last();
iv.point.x = v1.x + vv.x * t;
iv.point.y = v1.y + vv.y * t;
iv.point.z = v1.z + vv.z * t;
iv.mask = 0;
// Test against the remaining planes
for (U32 i = p + 1; i < mPlaneList.size(); i++)
if (mPlaneList[i].distToPlane(iv.point) > 0) {
iv.mask = 1 << i;
break;
}
}
if (!(mask2 & pmask)) {
U32 index = mIndexList[i2];
mIndexList.push_back(index);
}
mask1 = mask2;
i1 = i2;
}
// Check for degenerate
indexStart = indexEnd;
if (mIndexList.size() - indexStart < 3) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
}
}
// If we reach here, then there's a poly!
mEarlyOut = true;
// Emit what's left and compress the index list.
poly.vertexCount = mIndexList.size() - indexStart;
memcpy(&mIndexList[poly.vertexStart],
&mIndexList[indexStart],poly.vertexCount);
mIndexList.setSize(poly.vertexStart + poly.vertexCount);
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::memcpy(U32* dst, U32* src,U32 size)
{
U32* end = src + size;
while (src != end)
*dst++ = *src++;
}

View File

@ -0,0 +1,80 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _EARLYOUTPOLYLIST_H_
#define _EARLYOUTPOLYLIST_H_
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
/// Early out check PolyList
///
/// This class is used primarily for triggers and similar checks. It checks to see
/// if any of the geometry you feed it is inside its area, and if it is, it stops
/// checking for any more data and returns a true value. This is good if you want
/// to know if anything is in your "trigger" area, for instance.
///
/// @see AbstractPolyList
class EarlyOutPolyList : public AbstractPolyList
{
void memcpy(U32* d, U32* s,U32 size);
// Internal data
struct Vertex {
Point3F point;
U32 mask;
};
struct Poly {
PlaneF plane;
SceneObject* object;
U32 material;
U32 vertexStart;
U32 vertexCount;
U32 surfaceKey;
};
public:
typedef Vector<PlaneF> PlaneList;
private:
typedef Vector<Vertex> VertexList;
typedef Vector<Poly> PolyList;
typedef Vector<U32> IndexList;
PolyList mPolyList;
VertexList mVertexList;
IndexList mIndexList;
bool mEarlyOut;
PlaneList mPolyPlaneList;
public:
// Data set by caller
PlaneList mPlaneList;
VectorF mNormal;
public:
EarlyOutPolyList();
~EarlyOutPolyList();
void clear();
// Virtual methods
bool isEmpty() const;
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif // _H_EARLYOUTPOLYLIST_

View File

@ -0,0 +1,492 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/extrudedPolyList.h"
#include "collision/polyhedron.h"
#include "collision/collision.h"
const F32 sgFrontEpsilon = 0.01;
//----------------------------------------------------------------------------
// use table for U64 shifts of the form: 1 << N
// because compiler makes it a function call if done directly...
U32 U32leftShift[33] =
{
U32(1) << 0,
U32(1) << 1,
U32(1) << 2,
U32(1) << 3,
U32(1) << 4,
U32(1) << 5,
U32(1) << 6,
U32(1) << 7,
U32(1) << 8,
U32(1) << 9,
U32(1) << 10,
U32(1) << 11,
U32(1) << 12,
U32(1) << 13,
U32(1) << 14,
U32(1) << 15,
U32(1) << 16,
U32(1) << 17,
U32(1) << 18,
U32(1) << 19,
U32(1) << 20,
U32(1) << 21,
U32(1) << 22,
U32(1) << 23,
U32(1) << 24,
U32(1) << 25,
U32(1) << 26,
U32(1) << 27,
U32(1) << 28,
U32(1) << 29,
U32(1) << 30,
U32(1) << 31,
U32(0) // one more for good measure
};
// Minimum distance from a face
F32 ExtrudedPolyList::FaceEpsilon = 0.01;
// Value used to compare collision times
F32 ExtrudedPolyList::EqualEpsilon = 0.0001;
ExtrudedPolyList::ExtrudedPolyList()
{
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mExtrudedList);
VECTOR_SET_ASSOCIATION(mPlaneList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
mVelocity.set(0,0,0);
mIndexList.reserve(128);
mVertexList.reserve(64);
mPolyPlaneList.reserve(64);
mPlaneList.reserve(64);
mCollisionList = 0;
}
ExtrudedPolyList::~ExtrudedPolyList()
{
}
//----------------------------------------------------------------------------
bool ExtrudedPolyList::isEmpty() const
{
return mCollisionList->count == 0;
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::extrude(const Polyhedron& pt, const VectorF& vector)
{
// Clear state
mIndexList.clear();
mVertexList.clear();
mPlaneList.clear();
mPolyPlaneList.clear();
mFaceShift = 0;
// Determine which faces will be extruded.
mExtrudedList.setSize(pt.planeList.size());
for (U32 f = 0; f < pt.planeList.size(); f++) {
const PlaneF& face = pt.planeList[f];
ExtrudedFace& eface = mExtrudedList[f];
F32 dot = mDot(face,vector);
eface.active = dot > EqualEpsilon;
if (eface.active) {
eface.faceShift = FaceEpsilon / dot;
eface.maxDistance = dot;
eface.plane = face;
eface.planeMask = U32leftShift[mPlaneList.size()]; // U64(1) << mPlaneList.size();
// Add the face as a plane to clip against.
mPlaneList.increment(2);
PlaneF* plane = mPlaneList.end() - 2;
plane[0] = plane[1] = face;
plane[0].invert();
}
}
// Produce extruded planes for bounding and internal edges
for (U32 e = 0; e < pt.edgeList.size(); e++) {
Polyhedron::Edge const& edge = pt.edgeList[e];
ExtrudedFace& ef1 = mExtrudedList[edge.face[0]];
ExtrudedFace& ef2 = mExtrudedList[edge.face[1]];
if (ef1.active || ef2.active) {
// Assumes that the edge points are clockwise
// for face[0].
const Point3F& p1 = pt.pointList[edge.vertex[1]];
const Point3F &p2 = pt.pointList[edge.vertex[0]];
Point3F p3 = p2 + vector;
mPlaneList.increment(2);
PlaneF* plane = mPlaneList.end() - 2;
plane[0].set(p3,p2,p1);
plane[1] = plane[0];
plane[1].invert();
U32 pmask = U32leftShift[mPlaneList.size()-2]; // U64(1) << (mPlaneList.size()-2)
ef1.planeMask |= pmask;
ef2.planeMask |= pmask << 1;
}
}
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::setCollisionList(CollisionList* info)
{
mCollisionList = info;
mCollisionList->count = 0;
mCollisionList->t = 2;
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::adjustCollisionTime()
{
// Called after all the polys have been added.
// There was a reason for doing it here instead of subtracting
// the face Epsilon (faceShift) when the closest point is calculated,
// but I can't remember what it is...
if (mCollisionList->count) {
mCollisionList->t -= mFaceShift;
if (mCollisionList->t > 1)
mCollisionList->t = 1;
else
if (mCollisionList->t < 0)
mCollisionList->t = 0;
}
}
//----------------------------------------------------------------------------
U32 ExtrudedPolyList::addPoint(const Point3F& p)
{
mVertexList.increment();
Vertex& v = mVertexList.last();
v.point.x = p.x * mScale.x;
v.point.y = p.y * mScale.y;
v.point.z = p.z * mScale.z;
mMatrix.mulP(v.point);
// Build the plane mask, planes come in pairs
v.mask = 0;
for (U32 i = 0; i < mPlaneList.size(); i += 2)
{
F32 dist = mPlaneList[i].distToPlane(v.point);
if (dist >= sgFrontEpsilon)
v.mask |= U32leftShift[i]; // U64(1) << i;
else
v.mask |= U32leftShift[i+1]; // U64(2) << i;
}
return mVertexList.size() - 1;
}
U32 ExtrudedPolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::begin(U32 material, U32 /*surfaceKey*/)
{
mPoly.object = mCurrObject;
mPoly.material = material;
mIndexList.clear();
}
void ExtrudedPolyList::plane(U32 v1, U32 v2, U32 v3)
{
mPoly.plane.set(mVertexList[v1].point,
mVertexList[v2].point,
mVertexList[v3].point);
}
void ExtrudedPolyList::plane(const PlaneF& p)
{
mPlaneTransformer.transform(p, mPoly.plane);
}
void ExtrudedPolyList::plane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPoly.plane = mPolyPlaneList[index];
}
const PlaneF& ExtrudedPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
void ExtrudedPolyList::vertex(U32 vi)
{
mIndexList.push_back(vi);
}
void ExtrudedPolyList::end()
{
// Anything facing away from the mVelocity is rejected
if (mCollisionList->count >= CollisionList::MaxCollisions ||
mDot(mPoly.plane, mNormalVelocity) > 0)
return;
// Test the mPoly against the planes each extruded face.
U32 cFaceCount = 0;
ExtrudedFace* cFace[30];
bool cEdgeColl[30];
ExtrudedFace* face = mExtrudedList.begin();
ExtrudedFace* end = mExtrudedList.end();
for (; face != end; face++)
{
if (face->active && (face->faceDot = -mDot(face->plane,mPoly.plane)) > 0)
{
if (testPoly(*face)) {
cFace[cFaceCount] = face;
cEdgeColl[cFaceCount++] = false;
}
}
}
if (!cFaceCount)
{
face = mExtrudedList.begin();
end = mExtrudedList.end();
for (; face != end; face++)
{
if (face->active && (face->faceDot = -mDot(face->plane,mPoly.plane)) <= 0)
{
if (testPoly(*face)) {
cFace[cFaceCount] = face;
cEdgeColl[cFaceCount++] = true;
}
}
}
}
if (!cFaceCount)
return;
// Pick the best collision face
face = cFace[0];
bool edge = cEdgeColl[0];
for (U32 f = 1; f < cFaceCount; f++)
{
if (cFace[f]->faceDot > face->faceDot)
{
face = cFace[f];
edge = cEdgeColl[f];
}
}
// Add it to the collision list if it's better and/or equal
// to our current best.
if (face->time <= mCollisionList->t + EqualEpsilon) {
if (face->time < mCollisionList->t - EqualEpsilon) {
mFaceShift = face->faceShift;
mCollisionList->t = face->time;
mCollisionList->count = 0;
mCollisionList->maxHeight = face->height;
}
else {
if (face->height > mCollisionList->maxHeight)
mCollisionList->maxHeight = face->height;
if (face->faceShift > mFaceShift)
mFaceShift = face->faceShift;
}
Collision& collision = mCollisionList->collision[mCollisionList->count++];
collision.point = face->point;
collision.faceDot = face->faceDot;
collision.face = face - mExtrudedList.begin();
collision.object = mPoly.object;
collision.normal = mPoly.plane;
collision.material = mPoly.material;
}
}
//----------------------------------------------------------------------------
bool ExtrudedPolyList::testPoly(ExtrudedFace& face)
{
// Build intial inside/outside plane masks
U32 indexStart = 0;
U32 indexEnd = mIndexList.size();
U32 oVertexSize = mVertexList.size();
U32 oIndexSize = mIndexList.size();
U32 frontMask = 0,backMask = 0;
for (U32 i = indexStart; i < indexEnd; i++) {
U32 mask = mVertexList[mIndexList[i]].mask & face.planeMask;
frontMask |= mask;
backMask |= ~mask;
}
// Clip the mPoly against the planes that bound the face...
// Trivial accept if all the vertices are on the backsides of
// all the planes.
if (frontMask) {
// Trivial reject if any plane not crossed has all it's points
// on the front.
U32 crossMask = frontMask & backMask;
if (~crossMask & frontMask)
return false;
// Need to do some clipping
for (U32 p = 0; p < mPlaneList.size(); p++) {
U32 pmask = U32leftShift[p]; // U64(1) << p;
U32 newStart = mIndexList.size();
// Only test against this plane if we have something
// on both sides
if (face.planeMask & crossMask & pmask) {
U32 i1 = indexEnd - 1;
U32 mask1 = mVertexList[mIndexList[i1]].mask;
for (U32 i2 = indexStart; i2 < indexEnd; i2++) {
U32 mask2 = mVertexList[mIndexList[i2]].mask;
if ((mask1 ^ mask2) & pmask) {
//
mVertexList.increment();
VectorF& v1 = mVertexList[mIndexList[i1]].point;
VectorF& v2 = mVertexList[mIndexList[i2]].point;
VectorF vv = v2 - v1;
F32 t = -mPlaneList[p].distToPlane(v1) / mDot(mPlaneList[p],vv);
mIndexList.push_back(mVertexList.size() - 1);
Vertex& iv = mVertexList.last();
iv.point.x = v1.x + vv.x * t;
iv.point.y = v1.y + vv.y * t;
iv.point.z = v1.z + vv.z * t;
iv.mask = 0;
// Test against the remaining planes
for (U32 i = p + 1; i < mPlaneList.size(); i++) {
U32 mask = U32leftShift[i]; // U64(1) << i;
if (face.planeMask & mask &&
mPlaneList[i].distToPlane(iv.point) > 0) {
iv.mask = mask;
break;
}
}
}
if (!(mask2 & pmask)) {
U32 index = mIndexList[i2];
mIndexList.push_back(index);
}
mask1 = mask2;
i1 = i2;
}
// Check for degenerate
indexStart = newStart;
indexEnd = mIndexList.size();
if (mIndexList.size() - indexStart < 3) {
mVertexList.setSize(oVertexSize);
mIndexList.setSize(oIndexSize);
return false;
}
}
}
}
// Find closest point on the mPoly
Point3F bp;
F32 bd = 1E30;
F32 height = -1E30;
for (U32 b = indexStart; b < indexEnd; b++) {
Vertex& vertex = mVertexList[mIndexList[b]];
F32 dist = face.plane.distToPlane(vertex.point);
if (dist <= bd) {
bd = (dist < 0)? 0: dist;
bp = vertex.point;
}
// Since we don't clip against the back plane, we'll
// only include vertex heights that are within range.
if (vertex.point.z > height && dist < face.maxDistance)
height = vertex.point.z;
}
// Remove temporary data
mVertexList.setSize(oVertexSize);
mIndexList.setSize(oIndexSize);
// Add it to the collision list if it's better and/or equal
// to our current best.
if (bd < face.maxDistance + FaceEpsilon) {
face.time = bd / face.maxDistance;
face.height = height;
face.point = bp;
return true;
}
return false;
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::render()
{
if (!mCollisionList)
return;
glBegin(GL_LINES);
glColor3f(1,1,0);
for (U32 d = 0; d < mCollisionList->count; d++) {
Collision& face = mCollisionList->collision[d];
Point3F ep = face.point;
ep += face.normal;
glVertex3fv(face.point);
glVertex3fv(ep);
}
glEnd();
}
//--------------------------------------------------------------------------
void ExtrudedPolyList::setVelocity(const VectorF& velocity)
{
mVelocity = velocity;
if (velocity.isZero() == false)
{
mNormalVelocity = velocity;
mNormalVelocity.normalize();
setInterestNormal(mNormalVelocity);
}
else
{
mNormalVelocity.set(0, 0, 0);
clearInterestNormal();
}
}

View File

@ -0,0 +1,107 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _EXTRUDEDPOLYLIST_H_
#define _EXTRUDEDPOLYLIST_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
struct Polyhedron;
struct CollisionList;
//----------------------------------------------------------------------------
/// Extruded Polytope PolyList
///
/// This class is used primarily for collision detection, by objects which need
/// to check for obstructions along their path. You feed it a polytope to
/// extrude along the direction of movement, and it gives you a list of collisions.
///
/// @see AbstractPolyList
class ExtrudedPolyList: public AbstractPolyList
{
public:
struct Vertex {
Point3F point;
U32 mask;
};
struct Poly {
PlaneF plane;
SceneObject* object;
U32 material;
};
struct ExtrudedFace {
bool active;
PlaneF plane;
F32 maxDistance;
U32 planeMask;
F32 faceDot;
F32 faceShift;
F32 time;
Point3F point;
F32 height;
};
typedef Vector<ExtrudedFace> ExtrudedList;
typedef Vector<PlaneF> PlaneList;
typedef Vector<Vertex> VertexList;
typedef Vector<U32> IndexList;
static F32 EqualEpsilon;
static F32 FaceEpsilon;
// Internal data
VertexList mVertexList;
IndexList mIndexList;
ExtrudedList mExtrudedList;
PlaneList mPlaneList;
VectorF mVelocity;
VectorF mNormalVelocity;
F32 mFaceShift;
Poly mPoly;
// Returned info
CollisionList* mCollisionList;
PlaneList mPolyPlaneList;
//
private:
bool testPoly(ExtrudedFace&);
public:
ExtrudedPolyList();
~ExtrudedPolyList();
void extrude(const Polyhedron&, const VectorF& vec);
void setVelocity(const VectorF& velocity);
void setCollisionList(CollisionList*);
void adjustCollisionTime();
void render();
// Virtual methods
bool isEmpty() const;
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material, U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif

422
engine/collision/gjk.cc Executable file
View File

@ -0,0 +1,422 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//
// The core algorithms in this file are based on code written
// by G. van den Bergen for his interference detection library,
// "SOLID 2.0"
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "core/dataChunker.h"
#include "collision/collision.h"
#include "sceneGraph/sceneGraph.h"
#include "sim/sceneObject.h"
#include "terrain/terrData.h"
#include "collision/convex.h"
#include "collision/gjk.h"
//----------------------------------------------------------------------------
static F32 rel_error = 1E-5; // relative error in the computed distance
static F32 sTolerance = 1E-3; // Distance tolerance
static F32 sEpsilon2 = 1E-20; // Zero length vector
static U32 sIteration = 15; // Stuck in a loop?
S32 num_iterations = 0;
S32 num_irregularities = 0;
//----------------------------------------------------------------------------
GjkCollisionState::GjkCollisionState()
{
a = b = 0;
}
GjkCollisionState::~GjkCollisionState()
{
}
//----------------------------------------------------------------------------
void GjkCollisionState::swap()
{
Convex* t = a; a = b; b = t;
CollisionStateList* l = mLista; mLista = mListb; mListb = l;
v.neg();
}
//----------------------------------------------------------------------------
void GjkCollisionState::compute_det()
{
// Dot new point with current set
for (int i = 0, bit = 1; i < 4; ++i, bit <<=1)
if (bits & bit)
dp[i][last] = dp[last][i] = mDot(y[i], y[last]);
dp[last][last] = mDot(y[last], y[last]);
// Calulate the determinent
det[last_bit][last] = 1;
for (int j = 0, sj = 1; j < 4; ++j, sj <<= 1) {
if (bits & sj) {
int s2 = sj | last_bit;
det[s2][j] = dp[last][last] - dp[last][j];
det[s2][last] = dp[j][j] - dp[j][last];
for (int k = 0, sk = 1; k < j; ++k, sk <<= 1) {
if (bits & sk) {
int s3 = sk | s2;
det[s3][k] = det[s2][j] * (dp[j][j] - dp[j][k]) +
det[s2][last] * (dp[last][j] - dp[last][k]);
det[s3][j] = det[sk | last_bit][k] * (dp[k][k] - dp[k][j]) +
det[sk | last_bit][last] * (dp[last][k] - dp[last][j]);
det[s3][last] = det[sk | sj][k] * (dp[k][k] - dp[k][last]) +
det[sk | sj][j] * (dp[j][k] - dp[j][last]);
}
}
}
}
if (all_bits == 15) {
det[15][0] = det[14][1] * (dp[1][1] - dp[1][0]) +
det[14][2] * (dp[2][1] - dp[2][0]) +
det[14][3] * (dp[3][1] - dp[3][0]);
det[15][1] = det[13][0] * (dp[0][0] - dp[0][1]) +
det[13][2] * (dp[2][0] - dp[2][1]) +
det[13][3] * (dp[3][0] - dp[3][1]);
det[15][2] = det[11][0] * (dp[0][0] - dp[0][2]) +
det[11][1] * (dp[1][0] - dp[1][2]) +
det[11][3] * (dp[3][0] - dp[3][2]);
det[15][3] = det[7][0] * (dp[0][0] - dp[0][3]) +
det[7][1] * (dp[1][0] - dp[1][3]) +
det[7][2] * (dp[2][0] - dp[2][3]);
}
}
//----------------------------------------------------------------------------
inline void GjkCollisionState::compute_vector(int bits, VectorF& v)
{
F32 sum = 0;
v.set(0, 0, 0);
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
if (bits & bit) {
sum += det[bits][i];
v += y[i] * det[bits][i];
}
}
v *= 1 / sum;
}
//----------------------------------------------------------------------------
inline bool GjkCollisionState::valid(int s)
{
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
if (all_bits & bit) {
if (s & bit) {
if (det[s][i] <= 0)
return false;
}
else
if (det[s | bit][i] > 0)
return false;
}
}
return true;
}
//----------------------------------------------------------------------------
inline bool GjkCollisionState::closest(VectorF& v)
{
compute_det();
for (int s = bits; s; --s) {
if ((s & bits) == s) {
if (valid(s | last_bit)) {
bits = s | last_bit;
if (bits != 15)
compute_vector(bits, v);
return true;
}
}
}
if (valid(last_bit)) {
bits = last_bit;
v = y[last];
return true;
}
return false;
}
//----------------------------------------------------------------------------
inline bool GjkCollisionState::degenerate(const VectorF& w)
{
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1)
if ((all_bits & bit) && y[i] == w)
return true;
return false;
}
//----------------------------------------------------------------------------
inline void GjkCollisionState::nextBit()
{
last = 0;
last_bit = 1;
while (bits & last_bit) {
++last;
last_bit <<= 1;
}
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
void GjkCollisionState::set(Convex* aa, Convex* bb,
const MatrixF& a2w, const MatrixF& b2w)
{
a = aa;
b = bb;
bits = 0;
all_bits = 0;
reset(a2w,b2w);
// link
mLista = CollisionStateList::alloc();
mLista->mState = this;
mListb = CollisionStateList::alloc();
mListb->mState = this;
}
//----------------------------------------------------------------------------
void GjkCollisionState::reset(const MatrixF& a2w, const MatrixF& b2w)
{
VectorF zero(0,0,0),sa,sb;
a2w.mulP(a->support(zero),&sa);
b2w.mulP(b->support(zero),&sb);
v = sa - sb;
dist = v.len();
}
//----------------------------------------------------------------------------
void GjkCollisionState::getCollisionInfo(const MatrixF& mat, Collision* info)
{
AssertFatal(false, "GjkCollisionState::getCollisionInfo() - There remain scaling problems here.");
// This assumes that the shapes do not intersect
Point3F pa,pb;
if (bits) {
getClosestPoints(pa,pb);
mat.mulP(pa,&info->point);
b->getTransform().mulP(pb,&pa);
info->normal = info->point - pa;
}
else {
mat.mulP(p[last],&info->point);
info->normal = v;
}
info->normal.normalize();
info->object = b->getObject();
}
void GjkCollisionState::getClosestPoints(Point3F& p1, Point3F& p2)
{
F32 sum = 0;
p1.set(0, 0, 0);
p2.set(0, 0, 0);
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
if (bits & bit) {
sum += det[bits][i];
p1 += p[i] * det[bits][i];
p2 += q[i] * det[bits][i];
}
}
F32 s = 1 / sum;
p1 *= s;
p2 *= s;
}
//----------------------------------------------------------------------------
bool GjkCollisionState::intersect(const MatrixF& a2w, const MatrixF& b2w)
{
num_iterations = 0;
MatrixF w2a,w2b;
w2a = a2w;
w2b = b2w;
w2a.inverse();
w2b.inverse();
reset(a2w,b2w);
bits = 0;
all_bits = 0;
do {
nextBit();
VectorF va,sa;
w2a.mulV(-v,&va);
p[last] = a->support(va);
a2w.mulP(p[last],&sa);
VectorF vb,sb;
w2b.mulV(v,&vb);
q[last] = b->support(vb);
b2w.mulP(q[last],&sb);
VectorF w = sa - sb;
if (mDot(v,w) > 0)
return false;
if (degenerate(w)) {
++num_irregularities;
return false;
}
y[last] = w;
all_bits = bits | last_bit;
++num_iterations;
if (!closest(v) || num_iterations > sIteration) {
++num_irregularities;
return false;
}
}
while (bits < 15 && v.lenSquared() > sEpsilon2);
return true;
}
F32 GjkCollisionState::distance(const MatrixF& a2w, const MatrixF& b2w,
const F32 dontCareDist, const MatrixF* _w2a, const MatrixF* _w2b)
{
num_iterations = 0;
MatrixF w2a,w2b;
if (_w2a == NULL || _w2b == NULL) {
w2a = a2w;
w2b = b2w;
w2a.inverse();
w2b.inverse();
}
else {
w2a = *_w2a;
w2b = *_w2b;
}
reset(a2w,b2w);
bits = 0;
all_bits = 0;
F32 mu = 0;
do {
nextBit();
VectorF va,sa;
w2a.mulV(-v,&va);
p[last] = a->support(va);
a2w.mulP(p[last],&sa);
VectorF vb,sb;
w2b.mulV(v,&vb);
q[last] = b->support(vb);
b2w.mulP(q[last],&sb);
VectorF w = sa - sb;
F32 nm = mDot(v, w) / dist;
if (nm > mu)
mu = nm;
if (mu > dontCareDist)
return mu;
if (mFabs(dist - mu) <= dist * rel_error)
return dist;
++num_iterations;
if (degenerate(w) || num_iterations > sIteration) {
++num_irregularities;
return dist;
}
y[last] = w;
all_bits = bits | last_bit;
if (!closest(v)) {
++num_irregularities;
return dist;
}
dist = v.len();
}
while (bits < 15 && dist > sTolerance) ;
if (bits == 15 && mu <= 0)
dist = 0;
return dist;
}
//----------------------------------------------------------------------------
inline void renderCross(const Point3F x)
{
glVertex3fv(x + VectorF(0,0,+0.1));
glVertex3fv(x + VectorF(0,0,-0.1));
glVertex3fv(x + VectorF(0,+0.1,0));
glVertex3fv(x + VectorF(0,-0.1,0));
glVertex3fv(x + VectorF(-0.1,0,0));
glVertex3fv(x + VectorF(+0.1,0,0));
}
void GjkCollisionState::render()
{
glBegin(GL_LINES);
glColor3f(1,1,0);
// Cross for each witness point
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
Point3F x;
if (bits & bit) {
a->getTransform().mulP(p[i],&x);
renderCross(x);
b->getTransform().mulP(q[i],&x);
renderCross(x);
}
}
// Cross and line for closest points
Point3F sp,ep;
if (bits) {
Point3F p1,p2;
getClosestPoints(p1,p2);
a->getTransform().mulP(p1,&sp);
b->getTransform().mulP(p2,&ep);
}
else {
a->getTransform().mulP(p[0],&sp);
b->getTransform().mulP(q[0],&ep);
}
renderCross(sp);
renderCross(ep);
glVertex3fv(sp);
glVertex3fv(ep);
glEnd();
}

66
engine/collision/gjk.h Executable file
View File

@ -0,0 +1,66 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _GJK_H_
#define _GJK_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _CONVEX_H_
#include "collision/convex.h"
#endif
class Convex;
struct CollisionStateList;
struct Collision;
//----------------------------------------------------------------------------
struct GjkCollisionState: public CollisionState
{
/// @name Temporary values
/// @{
Point3F p[4]; ///< support points of object A in local coordinates
Point3F q[4]; ///< support points of object B in local coordinates
VectorF y[4]; ///< support points of A - B in world coordinates
S32 bits; ///< identifies current simplex
S32 all_bits; ///< all_bits = bits | last_bit
F32 det[16][4]; ///< cached sub-determinants
F32 dp[4][4]; ///< cached dot products
S32 last; ///< identifies last found support point
S32 last_bit; ///< last_bit = 1<<last
/// @}
///
void compute_det();
bool valid(int s);
void compute_vector(int bits, VectorF& v);
bool closest(VectorF& v);
bool degenerate(const VectorF& w);
void nextBit();
void swap();
void reset(const MatrixF& a2w, const MatrixF& b2w);
GjkCollisionState();
~GjkCollisionState();
void set(Convex* a,Convex* b,const MatrixF& a2w, const MatrixF& b2w);
void getCollisionInfo(const MatrixF& mat, Collision* info);
void getClosestPoints(Point3F& p1, Point3F& p2);
bool intersect(const MatrixF& a2w, const MatrixF& b2w);
F32 distance(const MatrixF& a2w, const MatrixF& b2w, const F32 dontCareDist,
const MatrixF* w2a = NULL, const MatrixF* _w2b = NULL);
void render();
};
#endif

View File

@ -0,0 +1,286 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/optimizedPolyList.h"
#include "core/color.h"
//----------------------------------------------------------------------------
OptimizedPolyList::OptimizedPolyList()
{
VECTOR_SET_ASSOCIATION(mPolyList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mPlaneList);
VECTOR_SET_ASSOCIATION(mEdgeList);
mIndexList.reserve(100);
}
OptimizedPolyList::~OptimizedPolyList()
{
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPlaneList.clear();
mEdgeList.clear();
}
//----------------------------------------------------------------------------
void OptimizedPolyList::clear()
{
// Only clears internal data
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPlaneList.clear();
mEdgeList.clear();
mPolyPlaneList.clear();
}
//----------------------------------------------------------------------------
U32 OptimizedPolyList::addPoint(const Point3F& p)
{
Point3F v;
v.x = p.x * mScale.x;
v.y = p.y * mScale.y;
v.z = p.z * mScale.z;
mMatrix.mulP(v);
for (U32 i = 0; i < mVertexList.size(); i++)
{
if (isEqual(v, mVertexList[i]))
return i;
}
// If we make it to here then we didn't find the vertex
mVertexList.push_back(v);
return mVertexList.size() - 1;
}
U32 OptimizedPolyList::addPlane(const PlaneF& plane)
{
PlaneF pln;
mPlaneTransformer.transform(plane, pln);
for (U32 i = 0; i < mPlaneList.size(); i++)
{
// The PlaneF == operator uses the Point3F == operator and
// doesn't check the d
if (isEqual(pln, mPlaneList[i]) &&
mFabs(pln.d - mPlaneList[i].d) < DEV)
return i;
}
// If we made it here then we didin't find the vertex
mPlaneList.push_back(pln);
return mPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void OptimizedPolyList::begin(U32 material,U32 surfaceKey)
{
mPolyList.increment();
Poly& poly = mPolyList.last();
poly.object = mCurrObject;
poly.material = material;
poly.vertexStart = mIndexList.size();
poly.surfaceKey = surfaceKey;
}
//----------------------------------------------------------------------------
void OptimizedPolyList::plane(U32 v1,U32 v2,U32 v3)
{
AssertFatal(v1 < mVertexList.size() && v2 < mVertexList.size() && v3 < mVertexList.size(),
"OptimizedPolyList::plane(): Vertex indices are larger than vertex list size");
mPolyList.last().plane = addPlane(PlaneF(mVertexList[v1], mVertexList[v2],mVertexList[v3]));
}
void OptimizedPolyList::plane(const PlaneF& p)
{
mPolyList.last().plane = addPlane(p);
}
void OptimizedPolyList::plane(const U32 index)
{
AssertFatal(index < mPlaneList.size(), "Out of bounds index!");
mPolyList.last().plane = index;
}
const PlaneF& OptimizedPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPlaneList.size(), "Out of bounds index!");
return mPlaneList[index];
}
//----------------------------------------------------------------------------
void OptimizedPolyList::vertex(U32 vi)
{
mIndexList.push_back(vi);
}
//----------------------------------------------------------------------------
bool OptimizedPolyList::isEmpty() const
{
return !mPolyList.size();
}
void OptimizedPolyList::end()
{
Poly& poly = mPolyList.last();
poly.vertexCount = mIndexList.size() - poly.vertexStart;
}
void OptimizedPolyList::render()
{
if (mPolyList.size() == 0 || mVertexList.size() == 0)
return;
//glVertexPointer(3, GL_FLOAT,sizeof(Point3F), mVertexList.address());
//glEnableClientState(GL_VERTEX_ARRAY);
//Poly * p;
//for (p = mPolyList.begin(); p < mPolyList.end(); p++)
// glDrawElements(GL_POLYGON,p->vertexCount, GL_UNSIGNED_INT, &mIndexList[p->vertexStart]);
//glDisableClientState(GL_VERTEX_ARRAY);
// If you need normals
for (U32 i = 0; i < mPolyList.size(); i++)
{
if (mPolyList[i].vertexCount < 3)
continue;
ColorF color(gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f));
glColor3f(color.red, color.green, color.blue);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glBegin(GL_TRIANGLES);
for (U32 j = 1; j < mPolyList[i].vertexCount - 1; j++)
{
U32 i0 = mIndexList[mPolyList[i].vertexStart];
U32 i1 = mIndexList[mPolyList[i].vertexStart + j];
U32 i2 = mIndexList[mPolyList[i].vertexStart + j + 1];
Point3F p0 = mVertexList[i0];
Point3F p1 = mVertexList[i1];
Point3F p2 = mVertexList[i2];
PlaneF normal = mPlaneList[mPolyList[i].plane];
glNormal3f(normal.x, normal.y, normal.z);
glVertex3f(p0.x, p0.y, p0.z);
glVertex3f(p1.x, p1.y, p1.z);
glVertex3f(p2.x, p2.y, p2.z);
}
glEnd();
}
}
void OptimizedPolyList::copyPolyToList(OptimizedPolyList* target, U32 pdx) const
{
target->mPolyList.increment();
OptimizedPolyList::Poly& tpoly = target->mPolyList.last();
tpoly.material = mPolyList[pdx].material;
tpoly.object = mPolyList[pdx].object;
tpoly.surfaceKey = mPolyList[pdx].surfaceKey;
tpoly.vertexCount = mPolyList[pdx].vertexCount;
PlaneF pln = mPlaneList[mPolyList[pdx].plane];
tpoly.plane = target->addPlane(pln);
tpoly.vertexStart = target->mIndexList.size();
for (U32 i = 0; i < mPolyList[pdx].vertexCount; i++)
{
U32 idx = mIndexList[mPolyList[pdx].vertexStart + i];
Point3F pt = mVertexList[idx];
target->mIndexList.increment();
target->mIndexList.last() = target->addPoint(pt);
}
}
void OptimizedPolyList::copyPolyToList(OptimizedPolyList* target, const FullPoly& poly) const
{
target->mPolyList.increment();
OptimizedPolyList::Poly& tpoly = target->mPolyList.last();
tpoly.material = poly.material;
tpoly.object = poly.object;
tpoly.surfaceKey = poly.surfaceKey;
tpoly.vertexCount = poly.indexes.size();
PlaneF pln = poly.plane;
tpoly.plane = target->addPlane(pln);
tpoly.vertexStart = target->mIndexList.size();
for (U32 i = 0; i < poly.indexes.size(); i++)
{
Point3F pt = poly.vertexes[poly.indexes[i]];
target->mIndexList.increment();
target->mIndexList.last() = target->addPoint(pt);
}
}
OptimizedPolyList::FullPoly OptimizedPolyList::getFullPoly(U32 pdx)
{
FullPoly ret;
OptimizedPolyList::Poly& poly = mPolyList[pdx];
ret.material = poly.material;
ret.object = poly.object;
ret.surfaceKey = poly.surfaceKey;
for (U32 i = 0; i < poly.vertexCount; i++)
{
U32 idx = mIndexList[poly.vertexStart + i];
Point3F& pt = mVertexList[idx];
S32 pdx = -1;
for (U32 j = 0; j < ret.vertexes.size(); j++)
{
if (pt == ret.vertexes[j])
{
pdx = j;
break;
}
}
if (pdx == -1)
{
pdx = ret.vertexes.size();
ret.vertexes.push_back(pt);
}
ret.indexes.push_back(pdx);
}
return ret;
}

View File

@ -0,0 +1,128 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _OPTIMIZEDPOLYLIST_H_
#define _OPTIMIZEDPOLYLIST_H_
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
#define DEV 0.01
/// A concrete, renderable PolyList
///
/// This class is used to store geometry from a PolyList query.
///
/// It allows you to render this data, as well.
///
/// @see AbstractPolyList
class OptimizedPolyList : public AbstractPolyList
{
public:
struct Poly
{
S32 plane;
SceneObject* object;
S32 material;
U32 vertexStart;
U32 vertexCount;
U32 surfaceKey;
U32 triangleLightingStartIndex;
Poly::Poly() { plane = -1; vertexCount = 0; material = -1; };
};
struct FullPoly
{
PlaneF plane;
SceneObject* object;
S32 material;
U32 surfaceKey;
Vector<U32> indexes;
Vector<Point3F> vertexes;
};
enum
{
NonShared = 0,
Concave,
Convex
};
struct Edge
{
U32 type;
S32 vertexes[2];
S32 faces[2];
};
struct TriangleLighting
{
U32 lightMapId;
PlaneF lightMapEquationX;
PlaneF lightMapEquationY;
};
Vector<TriangleLighting> mTriangleLightingList;
const TriangleLighting *getTriangleLighting(const U32 index)
{
if(index >= mTriangleLightingList.size())
return NULL;
return &mTriangleLightingList[index];
}
typedef Vector<PlaneF> PlaneList;
typedef Vector<Point3F> VertexList;
typedef Vector<Poly> PolyList;
typedef Vector<U32> IndexList;
typedef Vector<Edge> EdgeList;
PolyList mPolyList;
VertexList mVertexList;
IndexList mIndexList;
PlaneList mPlaneList;
EdgeList mEdgeList;
PlaneList mPolyPlaneList;
public:
OptimizedPolyList();
~OptimizedPolyList();
void clear();
// Virtual methods
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
inline bool isEqual(Point3F& a, Point3F& b)
{
return( ( mFabs( a.x - b.x ) < DEV ) &&
( mFabs( a.y - b.y ) < DEV ) &&
( mFabs( a.z - b.z ) < DEV ) );
}
void copyPolyToList(OptimizedPolyList* target, U32 pdx) const;
void copyPolyToList(OptimizedPolyList* target, const FullPoly& poly) const;
FullPoly getFullPoly(U32 pdx);
void render();
bool isEmpty() const;
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif // _OPTIMIZEDPOLYLIST_H_

View File

@ -0,0 +1,117 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/planeExtractor.h"
//----------------------------------------------------------------------------
// Plane matching parameters
static F32 NormalEpsilon = 0.93969; // 20 deg.
static F32 DistanceEpsilon = 100;
//----------------------------------------------------------------------------
PlaneExtractorPolyList::PlaneExtractorPolyList()
{
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
}
PlaneExtractorPolyList::~PlaneExtractorPolyList()
{
}
//----------------------------------------------------------------------------
void PlaneExtractorPolyList::clear()
{
mVertexList.clear();
mPolyPlaneList.clear();
}
U32 PlaneExtractorPolyList::addPoint(const Point3F& p)
{
mVertexList.increment();
Point3F& v = mVertexList.last();
v.x = p.x * mScale.x;
v.y = p.y * mScale.y;
v.z = p.z * mScale.z;
mMatrix.mulP(v);
return mVertexList.size() - 1;
}
U32 PlaneExtractorPolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void PlaneExtractorPolyList::plane(U32 v1,U32 v2,U32 v3)
{
mPlaneList->last().set(mVertexList[v1],
mVertexList[v2],mVertexList[v3]);
}
void PlaneExtractorPolyList::plane(const PlaneF& p)
{
mPlaneTransformer.transform(p, mPlaneList->last());
}
void PlaneExtractorPolyList::plane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPlaneList->last() = mPolyPlaneList[index];
}
const PlaneF& PlaneExtractorPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
//----------------------------------------------------------------------------
bool PlaneExtractorPolyList::isEmpty() const
{
return true;
}
void PlaneExtractorPolyList::begin(U32,U32)
{
mPlaneList->increment();
}
void PlaneExtractorPolyList::end()
{
// See if there are any duplicate planes
PlaneF &plane = mPlaneList->last();
PlaneF *ptr = mPlaneList->begin();
for (; ptr != &plane; ptr++)
if (mFabs(ptr->d - plane.d) < DistanceEpsilon &&
mDot(*ptr,plane) > NormalEpsilon) {
mPlaneList->decrement();
return;
}
}
void PlaneExtractorPolyList::vertex(U32)
{
}
void PlaneExtractorPolyList::render()
{
}

View File

@ -0,0 +1,62 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _PLANEEXTRACTOR_H_
#define _PLANEEXTRACTOR_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
//----------------------------------------------------------------------------
/// Fill a Vector<PlaneF> with the planes from the geometry passed through this
/// PolyList.
///
/// @see AbstractPolyList
class PlaneExtractorPolyList: public AbstractPolyList
{
void memcpy(U32* d, U32* s,U32 size);
public:
// Internal data
typedef Vector<Point3F> VertexList;
VertexList mVertexList;
Vector<PlaneF> mPolyPlaneList;
// Set by caller
Vector<PlaneF>* mPlaneList;
//
PlaneExtractorPolyList();
~PlaneExtractorPolyList();
void clear();
void render();
// Virtual methods
bool isEmpty() const;
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif

150
engine/collision/polyhedron.cc Executable file
View File

@ -0,0 +1,150 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "console/console.h"
#include "collision/polyhedron.h"
//----------------------------------------------------------------------------
void Polyhedron::buildBox(const MatrixF& transform,const Box3F& box)
{
// Box is assumed to be axis aligned in the source space.
// Transform into geometry space
Point3F xvec,yvec,zvec,min;
transform.getColumn(0,&xvec);
xvec *= box.len_x();
transform.getColumn(1,&yvec);
yvec *= box.len_y();
transform.getColumn(2,&zvec);
zvec *= box.len_z();
transform.mulP(box.min,&min);
// Initial vertices
pointList.setSize(8);
pointList[0] = min;
pointList[1] = min + yvec;
pointList[2] = min + xvec + yvec;
pointList[3] = min + xvec;
pointList[4] = pointList[0] + zvec;
pointList[5] = pointList[1] + zvec;
pointList[6] = pointList[2] + zvec;
pointList[7] = pointList[3] + zvec;
// Initial faces
planeList.setSize(6);
planeList[0].set(pointList[0],xvec);
planeList[0].invert();
planeList[1].set(pointList[2],yvec);
planeList[2].set(pointList[2],xvec);
planeList[3].set(pointList[0],yvec);
planeList[3].invert();
planeList[4].set(pointList[0],zvec);
planeList[4].invert();
planeList[5].set(pointList[4],zvec);
// The edges are constructed so that the vertices
// are oriented clockwise for face[0]
edgeList.setSize(12);
Edge* edge = edgeList.begin();
S32 nextEdge = 0;
for (int i = 0; i < 4; i++) {
S32 n = (i == 3)? 0: i + 1;
S32 p = (i == 0)? 3: i - 1;
edge->vertex[0] = i;
edge->vertex[1] = n;
edge->face[0] = i;
edge->face[1] = 4;
edge++;
edge->vertex[0] = 4 + i;
edge->vertex[1] = 4 + n;
edge->face[0] = 5;
edge->face[1] = i;
edge++;
edge->vertex[0] = i;
edge->vertex[1] = 4 + i;
edge->face[0] = p;
edge->face[1] = i;
edge++;
}
}
//----------------------------------------------------------------------------
void Polyhedron::render()
{
glVertexPointer(3,GL_FLOAT,sizeof(Point3F),pointList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor3f(1, 0, 1);
for (S32 e = 0; e < edgeList.size(); e++)
glDrawElements(GL_LINES,2,GL_UNSIGNED_INT,&edgeList[e].vertex);
for (U32 i = 0; i < planeList.size(); i++) {
Point3F origin(0, 0, 0);
U32 num = 0;
for (U32 j = 0; j < edgeList.size(); j++) {
if (edgeList[j].face[0] == i || edgeList[j].face[1] == i) {
origin += pointList[edgeList[j].vertex[0]];
origin += pointList[edgeList[j].vertex[1]];
num += 2;
}
}
origin /= F32(num);
glColor3f(1, 1, 1);
glBegin(GL_LINES);
glVertex3fv(origin);
glVertex3f(origin.x + planeList[i].x * 0.2,
origin.y + planeList[i].y * 0.2,
origin.z + planeList[i].z * 0.2);
glEnd();
}
glDisableClientState(GL_VERTEX_ARRAY);
}
void Polyhedron::render(const VectorF& vec,F32 time)
{
bool faceVisible[50];
for (int f = 0; f < planeList.size(); f++)
faceVisible[f] = mDot(planeList[f],vec) > 0;
VectorF tvec = vec;
tvec *= time;
for (int e = 0; e < edgeList.size(); e++) {
Polyhedron::Edge const& edge = edgeList[e];
if (faceVisible[edge.face[0]] || faceVisible[edge.face[1]]) {
Point3F pp;
glBegin(GL_LINE_LOOP);
glColor3f(0.5,0.5,0.5);
const Point3F& p1 = pointList[edge.vertex[0]];
const Point3F& p2 = pointList[edge.vertex[1]];
glVertex3fv(p1);
glVertex3fv(p2);
pp = p2 + vec;
glVertex3fv(pp);
pp = p1 + vec;
glVertex3fv(pp);
glEnd();
if (time <= 1.0) {
glBegin(GL_LINES);
glColor3f(0.5,0.5,0);
pp = pointList[edge.vertex[0]];
pp += tvec;
glVertex3fv(pp);
pp = pointList[edge.vertex[1]];
pp += tvec;
glVertex3fv(pp);
glEnd();
}
}
}
}

45
engine/collision/polyhedron.h Executable file
View File

@ -0,0 +1,45 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _POLYHEDRON_H_
#define _POLYHEDRON_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
//----------------------------------------------------------------------------
struct Polyhedron
{
struct Edge
{
// Edge vertices must be oriented clockwise
// for face[0]
U32 face[2];
U32 vertex[2];
};
Vector<Point3F> pointList;
Vector<PlaneF> planeList;
Vector<Edge> edgeList;
// Misc support methods
Polyhedron() {
VECTOR_SET_ASSOCIATION(pointList);
VECTOR_SET_ASSOCIATION(planeList);
VECTOR_SET_ASSOCIATION(edgeList);
}
void buildBox(const MatrixF& mat, const Box3F& box);
void render();
void render(const VectorF& vec,F32 time);
};
#endif

433
engine/collision/polytope.cc Executable file
View File

@ -0,0 +1,433 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "collision/collision.h"
#include "collision/polytope.h"
//----------------------------------------------------------------------------
Polytope::Polytope()
{
VECTOR_SET_ASSOCIATION(mEdgeList);
VECTOR_SET_ASSOCIATION(mFaceList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mVolumeList);
mVertexList.reserve(100);
mFaceList.reserve(200);
mEdgeList.reserve(100);
mVolumeList.reserve(20);
sideCount = 0;
}
//----------------------------------------------------------------------------
// Box should be axis aligned in the transform space provided.
void Polytope::buildBox(const MatrixF& transform,const Box3F& box)
{
// Box is assumed to be axis aligned in the source space.
// Transform into geometry space
Point3F xvec,yvec,zvec,min;
transform.getColumn(0,&xvec);
xvec *= box.len_x();
transform.getColumn(1,&yvec);
yvec *= box.len_y();
transform.getColumn(2,&zvec);
zvec *= box.len_z();
transform.mulP(box.min,&min);
// Initial vertices
mVertexList.setSize(8);
mVertexList[0].point = min;
mVertexList[1].point = min + yvec;
mVertexList[2].point = min + xvec + yvec;
mVertexList[3].point = min + xvec;
mVertexList[4].point = mVertexList[0].point + zvec;
mVertexList[5].point = mVertexList[1].point + zvec;
mVertexList[6].point = mVertexList[2].point + zvec;
mVertexList[7].point = mVertexList[3].point + zvec;
S32 i;
for (i = 0; i < 8; i++)
mVertexList[i].side = 0;
// Initial faces
mFaceList.setSize(6);
for (S32 f = 0; f < 6; f++) {
Face& face = mFaceList[f];
face.original = true;
face.vertex = 0;
}
mFaceList[0].plane.set(mVertexList[0].point,xvec);
mFaceList[0].plane.invert();
mFaceList[1].plane.set(mVertexList[2].point,yvec);
mFaceList[2].plane.set(mVertexList[2].point,xvec);
mFaceList[3].plane.set(mVertexList[0].point,yvec);
mFaceList[3].plane.invert();
mFaceList[4].plane.set(mVertexList[0].point,zvec);
mFaceList[4].plane.invert();
mFaceList[5].plane.set(mVertexList[4].point,zvec);
// Initial edges
mEdgeList.setSize(12);
Edge* edge = mEdgeList.begin();
S32 nextEdge = 0;
for (i = 0; i < 4; i++) {
S32 n = (i == 3)? 0: i + 1;
S32 p = (i == 0)? 3: i - 1;
edge->vertex[0] = i;
edge->vertex[1] = n;
edge->face[0] = i;
edge->face[1] = 4;
edge->next = ++nextEdge;
edge++;
edge->vertex[0] = 4 + i;
edge->vertex[1] = 4 + n;
edge->face[0] = i;
edge->face[1] = 5;
edge->next = ++nextEdge;
edge++;
edge->vertex[0] = i;
edge->vertex[1] = 4 + i;
edge->face[0] = i;
edge->face[1] = p;
edge->next = ++nextEdge;
edge++;
}
edge[-1].next = -1;
// Volume
mVolumeList.setSize(1);
Volume& volume = mVolumeList.last();
volume.edgeList = 0;
volume.material = -1;
volume.object = 0;
sideCount = 0;
}
//----------------------------------------------------------------------------
void Polytope::intersect(SimObject* object,const BSPNode* root)
{
AssertFatal(mVolumeList.size() > 0,"Polytope::intersect: Missing initial volume");
// Always clips the first volume against the BSP
VolumeStack stack;
stack.reserve(50);
stack.increment();
stack.last().edgeList = mVolumeList[0].edgeList;
stack.last().node = root;
while (!stack.empty()) {
StackElement volume = stack.last();
stack.pop_back();
// If it's a solid node, export the volume
const BSPNode* node = volume.node;
if (!node->backNode && !node->frontNode) {
mVolumeList.increment();
Volume& vol = mVolumeList.last();
vol.object = object;
vol.material = node->material;
vol.edgeList = volume.edgeList;
continue;
}
// New front and back faces
mFaceList.increment(2);
Face& frontFace = mFaceList.last();
Face& backFace = *(&frontFace - 1);
backFace.original = frontFace.original = false;
backFace.vertex = frontFace.vertex = 0;
backFace.plane = frontFace.plane = node->plane;
backFace.plane.invert();
// New front and back volumes
StackElement frontVolume,backVolume;
frontVolume.edgeList = backVolume.edgeList = -1;
const PlaneF& plane = node->plane;
S32 startVertex = mVertexList.size();
// Test & clip all the edges
S32 sideBase = ++sideCount << 1;
for (S32 i = volume.edgeList; i >= 0; i = mEdgeList[i].next) {
// Copy into tmp first to avoid problems with the array.
Edge edge = mEdgeList[i];
Vertex& v0 = mVertexList[edge.vertex[0]];
if (v0.side < sideBase)
v0.side = sideBase + ((plane.distToPlane(v0.point) >= 0)? 0: 1);
Vertex& v1 = mVertexList[edge.vertex[1]];
if (v1.side < sideBase)
v1.side = sideBase + ((plane.distToPlane(v1.point) >= 0)? 0: 1);
if (v0.side != v1.side) {
S32 s = v0.side - sideBase;
intersect(plane,v0.point,v1.point);
// Split the edge into each volume
mEdgeList.increment(2);
Edge& e0 = mEdgeList.last();
e0.next = frontVolume.edgeList;
frontVolume.edgeList = mEdgeList.size() - 1;
Edge& e1 = *(&e0 - 1);
e1.next = backVolume.edgeList;
backVolume.edgeList = frontVolume.edgeList - 1;
e0.vertex[0] = edge.vertex[s];
e1.vertex[0] = edge.vertex[s ^ 1];
e0.vertex[1] = e1.vertex[1] = mVertexList.size() - 1;
e0.face[0] = e1.face[0] = edge.face[0];
e0.face[1] = e1.face[1] = edge.face[1];
// Add new edges on the plane, one to each volume
for (S32 f = 0; f < 2; f++) {
Face& face = mFaceList[edge.face[f]];
if (face.vertex < startVertex)
face.vertex = mVertexList.size() - 1;
else {
mEdgeList.increment(2);
Edge& e0 = mEdgeList.last();
e0.next = frontVolume.edgeList;
frontVolume.edgeList = mEdgeList.size() - 1;
Edge& e1 = *(&e0 - 1);
e1.next = backVolume.edgeList;
backVolume.edgeList = frontVolume.edgeList - 1;
e1.vertex[0] = e0.vertex[0] = face.vertex;
e1.vertex[1] = e0.vertex[1] = mVertexList.size() - 1;
e1.face[0] = e0.face[0] = edge.face[f];
e1.face[1] = mFaceList.size() - 1;
e0.face[1] = e1.face[1] - 1;
}
}
}
else
if (v0.side == sideBase) {
mEdgeList.push_back(edge);
Edge& ne = mEdgeList.last();
ne.next = frontVolume.edgeList;
frontVolume.edgeList = mEdgeList.size() - 1;
}
else {
mEdgeList.push_back(edge);
Edge& ne = mEdgeList.last();
ne.next = backVolume.edgeList;
backVolume.edgeList = mEdgeList.size() - 1;
}
}
// Push the front and back nodes
if (node->frontNode && frontVolume.edgeList >= 0) {
frontVolume.node = node->frontNode;
stack.push_back(frontVolume);
}
if (node->backNode && backVolume.edgeList >= 0) {
backVolume.node = node->backNode;
stack.push_back(backVolume);
}
}
}
//----------------------------------------------------------------------------
bool Polytope::intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep)
{
// If den == 0 then the line and plane are parallel.
F32 den;
Point3F dt = ep - sp;
if ((den = plane.x * dt.x + plane.y * dt.y + plane.z * dt.z) == 0)
return false;
mVertexList.increment();
Vertex& v = mVertexList.last();
F32 s = -(plane.x * sp.x + plane.y * sp.y + plane.z * sp.z + plane.d) / den;
v.point.x = sp.x + dt.x * s;
v.point.y = sp.y + dt.y * s;
v.point.z = sp.z + dt.z * s;
v.side = 0;
return true;
}
//----------------------------------------------------------------------------
void Polytope::render()
{
glVertexPointer(3,GL_FLOAT,sizeof(Vertex),mVertexList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor3f(0.5, 0.5, 0.5);
for (VolumeList::iterator itr = mVolumeList.begin();
itr < mVolumeList.end(); itr++) {
for (S32 e = itr->edgeList; e >= 0; e = mEdgeList[e].next)
glDrawElements(GL_LINES,2,GL_UNSIGNED_INT,&mEdgeList[e].vertex);
glColor3f(1, 1, 1);
}
glDisableClientState(GL_VERTEX_ARRAY);
}
//----------------------------------------------------------------------------
void Polytope::extrudeFace(int faceIdx,const VectorF& vec,Polytope* out)
{
// Assumes the face belongs to the first volume.
out->mVertexList.clear();
out->mFaceList.clear();
out->mEdgeList.clear();
out->mVolumeList.clear();
sideCount++;
// Front & end faces
Face nface;
nface.original = true;
nface.vertex = 0;
nface.plane = mFaceList[faceIdx].plane;
out->mFaceList.setSize(2);
out->mFaceList[0] = out->mFaceList[1] = nface;
out->mFaceList[0].plane.invert();
for (S32 e = mVolumeList[0].edgeList; e >= 0; e = mEdgeList[e].next) {
Edge& edge = mEdgeList[e];
if (edge.face[0] == faceIdx || edge.face[1] == faceIdx) {
// Build face for this edge
// Should think about calulating the plane
S32 fi = out->mFaceList.size();
out->mFaceList.push_back(nface);
// Reserve 4 entries to make sure the ve[] pointers
// into the list don't get invalidated.
out->mEdgeList.reserve(out->mEdgeList.size() + 4);
Edge* ve[2];
// Build edges for each vertex
for (S32 v = 0; v < 2; v++) {
if (mVertexList[edge.vertex[v]].side < sideCount) {
mVertexList[edge.vertex[v]].side = sideCount + out->mEdgeList.size();
out->mVertexList.increment(2);
out->mVertexList.end()[-1] =
out->mVertexList.end()[-2] =
mVertexList[edge.vertex[v]];
out->mVertexList.last().point += vec;
out->mEdgeList.increment();
Edge& ne = out->mEdgeList.last();
ne.next = out->mEdgeList.size();
ne.vertex[1] = out->mVertexList.size() - 1;
ne.vertex[0] = ne.vertex[1] - 1;
ne.face[0] = ne.face[1] = -1;
ve[v] = &ne;
}
else {
S32 ei = mVertexList[edge.vertex[v]].side - sideCount;
ve[v] = &out->mEdgeList[ei];
}
// Edge should share this face
if (ve[v]->face[0] == -1)
ve[v]->face[0] = fi;
else
ve[v]->face[1] = fi;
}
// Build parallel edges
out->mEdgeList.increment(2);
for (S32 i = 0; i < 2; i++ ) {
Edge& ne = out->mEdgeList.end()[i - 2];
ne.next = out->mEdgeList.size() - 1 + i;
ne.vertex[0] = ve[0]->vertex[i];
ne.vertex[1] = ve[1]->vertex[i];
ne.face[0] = i;
ne.face[1] = fi;
}
}
}
out->mEdgeList.last().next = -1;
out->mVolumeList.increment();
Volume& nv = out->mVolumeList.last();
nv.edgeList = 0;
nv.object = 0;
nv.material = -1;
}
//----------------------------------------------------------------------------
bool Polytope::findCollision(const VectorF& vec,Polytope::Collision *best)
{
if (mVolumeList.size() <= 1)
return false;
if (!best->object)
best->distance = 1.0E30;
int bestVertex = -1;
Volume* bestVolume;
sideCount++;
// Find the closest point
for (Volume* vol = mVolumeList.begin() + 1;
vol < mVolumeList.end(); vol++) {
for (S32 e = vol->edgeList; e >= 0; e = mEdgeList[e].next) {
Edge& edge = mEdgeList[e];
if (mFaceList[edge.face[0]].original &&
mFaceList[edge.face[1]].original)
continue;
for (S32 v = 0; v < 2; v++) {
S32 vi = edge.vertex[v];
Vertex& vr = mVertexList[vi];
if (vr.side != sideCount) {
vr.side = sideCount;
F32 dist = mDot(vr.point,vec);
if (dist < best->distance) {
best->distance = dist;
bestVertex = vi;
bestVolume = vol;
}
}
}
}
}
if (bestVertex == -1)
return false;
// Fill in the return value
best->point = mVertexList[bestVertex].point;
best->object = bestVolume->object;
best->material = bestVolume->material;
// Pick the best face
F32 bestFaceDot = 1;
for (S32 e = bestVolume->edgeList; e >= 0; e = mEdgeList[e].next) {
Edge& edge = mEdgeList[e];
if (edge.vertex[0] == bestVertex || edge.vertex[1] == bestVertex) {
for (S32 f = 0; f < 2; f++) {
Face& tf = mFaceList[edge.face[f]];
F32 fd = mDot(tf.plane,vec);
if (fd < bestFaceDot) {
bestFaceDot = fd;
best->plane = tf.plane;
}
}
}
}
return true;
}

96
engine/collision/polytope.h Executable file
View File

@ -0,0 +1,96 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _POLYTOPE_H_
#define _POLYTOPE_H_
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
//----------------------------------------------------------------------------
class SimObject;
//----------------------------------------------------------------------------
class Polytope
{
// Convex Polyhedron
public:
struct Vertex {
Point3F point;
/// Temp BSP clip info
S32 side;
};
struct Edge {
S32 vertex[2];
S32 face[2];
S32 next;
};
struct Face {
PlaneF plane;
S32 original;
/// Temp BSP clip info
S32 vertex;
};
struct Volume
{
S32 edgeList;
S32 material;
SimObject* object;
};
struct StackElement
{
S32 edgeList;
const BSPNode *node;
};
struct Collision {
SimObject* object;
S32 material;
PlaneF plane;
Point3F point;
F32 distance;
Collision()
{
object = NULL;
distance = 0.0;
}
};
typedef Vector<Edge> EdgeList;
typedef Vector<Face> FaceList;
typedef Vector<Vertex> VertexList;
typedef Vector<Volume> VolumeList;
typedef Vector<StackElement> VolumeStack;
//
S32 sideCount;
EdgeList mEdgeList;
FaceList mFaceList;
VertexList mVertexList;
VolumeList mVolumeList;
private:
bool intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep);
public:
//
Polytope();
void buildBox(const MatrixF& transform,const Box3F& box);
void intersect(SimObject*, const BSPNode* node);
inline bool didIntersect() { return mVolumeList.size() > 1; }
void extrudeFace(int fi,const VectorF& vec,Polytope* out);
bool findCollision(const VectorF& vec,Polytope::Collision *best);
void render();
};
#endif

1951
engine/console/BASgram.cc Executable file

File diff suppressed because it is too large Load Diff

533
engine/console/BASgram.y Executable file
View File

@ -0,0 +1,533 @@
%{
// Make sure we don't get gram.h twice.
#define _BASGRAM_H_
#define _CMDGRAM_H_
#include <stdlib.h>
#include <stdio.h>
#include "console/console.h"
#include "console/compiler.h"
#include "console/consoleInternal.h"
#ifndef YYDEBUG
#define YYDEBUG 0
#endif
#define YYSSIZE 350
int outtext(char *fmt, ...);
extern int serrors;
#define nil 0
#undef YY_ARGS
#define YY_ARGS(x) x
int BASlex();
void BASerror(char *a, ...);
#define alloca dMalloc
%}
%{
/* Reserved Word Definitions */
/* IMPORTANT: SEE COMMENTS BELOW IF YOU ARE THINKING ABOUT MODIFYING TOKENS */
/*
ALL TOKENS BETWEEN HERE AND ADDITIONAL TOKENS BELOW MUST BE IDENTICAL TO THOSE
IN CS_CMD.y OTHERWISE BAD STUFF HAPPENS
*/
%}
%token <i> rwDEFINE rwENDDEF rwDECLARE
%token <i> rwBREAK rwELSE rwCONTINUE rwGLOBAL
%token <i> rwIF rwNIL rwRETURN rwWHILE
%token <i> rwENDIF rwENDWHILE rwENDFOR rwDEFAULT
%token <i> rwFOR rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR
%token <i> rwCASEOR rwPACKAGE
%token ILLEGAL_TOKEN
%{
/* Constants and Identifier Definitions */
%}
%token <c> CHRCONST
%token <i> INTCONST
%token <s> TTAG
%token <s> VAR
%token <s> IDENT
%token <str> STRATOM
%token <str> TAGATOM
%token <f> FLTCONST
%{
/* Operator Definitions */
%}
%token <i> '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%'
%token <i> '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@'
%token <i> opMINUSMINUS opPLUSPLUS
%token <i> STMT_SEP
%token <i> opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN
%token <i> opXORASN opORASN opSLASN opSRASN opCAT
%token <i> opEQ opNE opGE opLE opAND opOR opSTREQ
%token <i> opCOLONCOLON
%union {
char c;
int i;
const char *s;
char *str;
double f;
StmtNode *stmt;
ExprNode *expr;
SlotAssignNode *slist;
VarNode *var;
SlotDecl slot;
ObjectBlockDecl odcl;
ObjectDeclNode *od;
AssignDecl asn;
IfStmtNode *ifnode;
}
%type <s> parent_block
%type <ifnode> case_block
%type <stmt> switch_stmt
%type <stmt> decl
%type <stmt> decl_list
%type <stmt> package_decl
%type <stmt> fn_decl_stmt
%type <stmt> fn_decl_list
%type <stmt> statement_list
%type <stmt> stmt
%type <expr> expr_list
%type <expr> expr_list_decl
%type <expr> aidx_expr
%type <expr> funcall_expr
%type <expr> object_name
%type <expr> object_args
%type <expr> stmt_expr
%type <expr> case_expr
%type <expr> class_name_expr
%type <stmt> if_stmt
%type <stmt> while_stmt
%type <stmt> for_stmt
%type <stmt> stmt_block
%type <stmt> datablock_decl
%type <od> object_decl
%type <od> object_decl_list
%type <odcl> object_declare_block
%type <expr> expr
%type <slist> slot_assign_list
%type <slist> slot_assign
%type <slot> slot_acc
%type <stmt> expression_stmt
%type <var> var_list
%type <var> var_list_decl
%type <asn> assign_op_struct
%left '['
%right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN opMDASN opNDASN opNTASN opORASN opSLASN opSRASN '='
%left '?' ':'
%left opOR
%left opAND
%left '|'
%left '^'
%left '&'
%left opEQ opNE
%left '<' opLE '>' opGE
%left '@' opCAT opSTREQ opSTRNE
%left opSHL opSHR
%left '+' '-'
%left '*' '/' '%'
%right '!' '~' opPLUSPLUS opMINUSMINUS UNARY
%left '.'
%{
/*
Additional tokens
NOTE: These MUST be here, otherwise the definitions of the usual TorqueScript tokens changes
which will break the compiler. Double check the standard TS tokens arent being redefined before
testing a build.
*/
%}
%token <i> rwTHEN rwEND rwBEGIN rwCFOR rwTO rwSTEP
%%
start
: decl_list
{ }
;
decl_list
:
{ $$ = nil; }
| decl_list decl
{ if(!statementList) { statementList = $2; } else { statementList->append($2); } }
;
decl
: stmt
{ $$ = $1; }
| fn_decl_stmt
{ $$ = $1; }
| package_decl
{ $$ = $1; }
;
package_decl
: rwPACKAGE IDENT fn_decl_list rwEND
{ $$ = $3; for(StmtNode *walk = ($3);walk;walk = walk->getNext() ) walk->setPackage($2); }
;
fn_decl_list
: fn_decl_stmt
{ $$ = $1; }
| fn_decl_list fn_decl_stmt
{ $$ = $1; ($1)->append($2); }
;
statement_list
:
{ $$ = nil; }
| statement_list stmt
{ if(!$1) { $$ = $2; } else { ($1)->append($2); $$ = $1; } }
;
stmt
: if_stmt
| while_stmt
| for_stmt
| datablock_decl
| switch_stmt
| rwBREAK semicolon
{ $$ = BreakStmtNode::alloc(); }
| rwCONTINUE semicolon
{ $$ = ContinueStmtNode::alloc(); }
| rwRETURN semicolon
{ $$ = ReturnStmtNode::alloc(NULL); }
| rwRETURN expr semicolon
{ $$ = ReturnStmtNode::alloc($2); }
| expression_stmt semicolon
{ $$ = $1; }
| TTAG '=' expr semicolon
{ $$ = TTagSetStmtNode::alloc($1, $3, NULL); }
| TTAG '=' expr ',' expr semicolon
{ $$ = TTagSetStmtNode::alloc($1, $3, $5); }
;
fn_decl_stmt
: rwDEFINE IDENT '(' var_list_decl ')' statement_list rwEND
{ $$ = FunctionDeclStmtNode::alloc($2, NULL, $4, $6); }
| rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' statement_list rwEND
{ $$ = FunctionDeclStmtNode::alloc($4, $2, $6, $8); }
;
var_list_decl
:
{ $$ = NULL; }
| var_list
{ $$ = $1; }
;
var_list
: VAR
{ $$ = VarNode::alloc($1, NULL); }
| var_list ',' VAR
{ $$ = $1; ((StmtNode*)($1))->append((StmtNode*)VarNode::alloc($3, NULL)); }
;
datablock_decl
: rwDATABLOCK IDENT '(' IDENT parent_block ')' slot_assign_list rwEND
{ $$ = ObjectDeclNode::alloc(ConstantNode::alloc($2), ConstantNode::alloc($4), NULL, $5, $7, NULL, true); }
;
object_decl
: rwDECLARE class_name_expr '(' object_name parent_block object_args ')' object_declare_block rwEND
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, $8.slots, $8.decls, false); }
| rwDECLARE class_name_expr '(' object_name parent_block object_args ')' rwEND
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, NULL, NULL, false); }
;
parent_block
:
{ $$ = NULL; }
| ':' IDENT
{ $$ = $2; }
;
object_name
:
{ $$ = StrConstNode::alloc("", false); }
| expr
{ $$ = $1; }
;
object_args
:
{ $$ = NULL; }
| ',' expr_list
{ $$ = $2; }
;
object_declare_block
:
{ $$.slots = NULL; $$.decls = NULL; }
| slot_assign_list
{ $$.slots = $1; $$.decls = NULL; }
| object_decl_list
{ $$.slots = NULL; $$.decls = $1; }
| slot_assign_list object_decl_list
{ $$.slots = $1; $$.decls = $2; }
;
object_decl_list
: object_decl semicolon
{ $$ = $1; }
| object_decl_list object_decl semicolon
{ $1->append($2); $$ = $1; }
;
stmt_block
: rwBEGIN statement_list rwEND
{ $$ = $2; }
| stmt
{ $$ = $1; }
;
switch_stmt
: rwSWITCH expr case_block rwEND
{ $$ = $3; $3->propagateSwitchExpr($2, false); }
| rwSWITCHSTR expr case_block rwEND
{ $$ = $3; $3->propagateSwitchExpr($2, true); }
;
case_block
: rwCASE case_expr ':' statement_list
{ $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); }
| rwCASE case_expr ':' statement_list rwDEFAULT ':' statement_list
{ $$ = IfStmtNode::alloc($1, $2, $4, $7, false); }
| rwCASE case_expr ':' statement_list case_block
{ $$ = IfStmtNode::alloc($1, $2, $4, $5, true); }
;
case_expr
: expr
{ $$ = $1;}
| case_expr rwCASEOR expr
{ ($1)->append($3); $$=$1; }
;
if_stmt
: rwIF expr rwTHEN statement_list rwEND
{ $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); }
| rwIF expr rwTHEN statement_list rwELSE statement_list rwEND
{ $$ = IfStmtNode::alloc($1, $2, $4, $6, false); }
;
while_stmt
: rwWHILE expr rwBEGIN statement_list rwEND
{ $$ = LoopStmtNode::alloc($1, nil, $2, nil, $4, false); }
;
for_stmt
: rwFOR expr ',' expr ',' expr rwBEGIN statement_list rwEND
{ $$ = LoopStmtNode::alloc($1, $2, $4, $6, $8, false); }
;
expression_stmt
: stmt_expr
{ $$ = $1; }
;
expr
: stmt_expr
{ $$ = $1; }
| '(' expr ')'
{ $$ = $2; }
| expr '^' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '%' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '&' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '|' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '+' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '-' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '*' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '/' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| '-' expr %prec UNARY
{ $$ = FloatUnaryExprNode::alloc($1, $2); }
| '*' expr %prec UNARY
{ $$ = TTagDerefNode::alloc($2); }
| TTAG
{ $$ = TTagExprNode::alloc($1); }
| expr '?' expr ':' expr
{ $$ = ConditionalExprNode::alloc($1, $3, $5); }
| expr '<' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '>' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opGE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opLE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opEQ expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opNE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opOR expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSHL expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSHR expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opAND expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSTREQ expr
{ $$ = StreqExprNode::alloc($1, $3, true); }
| expr opSTRNE expr
{ $$ = StreqExprNode::alloc($1, $3, false); }
| expr '@' expr
{ $$ = StrcatExprNode::alloc($1, $3, $2); }
| '!' expr
{ $$ = IntUnaryExprNode::alloc($1, $2); }
| '~' expr
{ $$ = IntUnaryExprNode::alloc($1, $2); }
| TAGATOM
{ $$ = StrConstNode::alloc($1, true); }
| FLTCONST
{ $$ = FloatNode::alloc($1); }
| INTCONST
{ $$ = IntNode::alloc($1); }
| rwBREAK
{ $$ = ConstantNode::alloc(StringTable->insert("break")); }
| slot_acc
{ $$ = SlotAccessNode::alloc($1.object, $1.array, $1.slotName); }
| IDENT
{ $$ = ConstantNode::alloc($1); }
| STRATOM
{ $$ = StrConstNode::alloc($1, false); }
| VAR
{ $$ = (ExprNode*)VarNode::alloc($1, NULL); }
| VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
;
slot_acc
: expr '.' IDENT
{ $$.object = $1; $$.slotName = $3; $$.array = NULL; }
| expr '.' IDENT '[' aidx_expr ']'
{ $$.object = $1; $$.slotName = $3; $$.array = $5; }
;
class_name_expr
: IDENT
{ $$ = ConstantNode::alloc($1); }
| '(' expr ')'
{ $$ = $2; }
;
assign_op_struct
: opPLUSPLUS
{ $$.token = '+'; $$.expr = FloatNode::alloc(1); }
| opMINUSMINUS
{ $$.token = '-'; $$.expr = FloatNode::alloc(1); }
| opPLASN expr
{ $$.token = '+'; $$.expr = $2; }
| opMIASN expr
{ $$.token = '-'; $$.expr = $2; }
| opMLASN expr
{ $$.token = '*'; $$.expr = $2; }
| opDVASN expr
{ $$.token = '/'; $$.expr = $2; }
| opMODASN expr
{ $$.token = '%'; $$.expr = $2; }
| opANDASN expr
{ $$.token = '&'; $$.expr = $2; }
| opXORASN expr
{ $$.token = '^'; $$.expr = $2; }
| opORASN expr
{ $$.token = '|'; $$.expr = $2; }
| opSLASN expr
{ $$.token = opSHL; $$.expr = $2; }
| opSRASN expr
{ $$.token = opSHR; $$.expr = $2; }
;
stmt_expr
: funcall_expr
{ $$ = $1; }
| object_decl
{ $$ = $1; }
| VAR '=' expr
{ $$ = AssignExprNode::alloc($1, NULL, $3); }
| VAR '[' aidx_expr ']' '=' expr
{ $$ = AssignExprNode::alloc($1, $3, $6); }
| VAR assign_op_struct
{ $$ = AssignOpExprNode::alloc($1, NULL, $2.expr, $2.token); }
| VAR '[' aidx_expr ']' assign_op_struct
{ $$ = AssignOpExprNode::alloc($1, $3, $5.expr, $5.token); }
| slot_acc assign_op_struct
{ $$ = SlotAssignOpNode::alloc($1.object, $1.slotName, $1.array, $2.token, $2.expr); }
| slot_acc '=' expr
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $3); }
| slot_acc '=' '{' expr_list '}'
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $4); }
;
funcall_expr
: IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc($1, NULL, $3, false); }
| IDENT opCOLONCOLON IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc($3, $1, $5, false); }
| expr '.' IDENT '(' expr_list_decl ')'
{ $1->append($5); $$ = FuncCallExprNode::alloc($3, NULL, $1, true); }
;
expr_list_decl
:
{ $$ = NULL; }
| expr_list
{ $$ = $1; }
;
expr_list
: expr
{ $$ = $1; }
| expr_list ',' expr
{ ($1)->append($3); $$ = $1; }
;
slot_assign_list
: slot_assign
{ $$ = $1; }
| slot_assign_list slot_assign
{ $1->append($2); $$ = $1; }
;
slot_assign
: IDENT '=' expr semicolon
{ $$ = SlotAssignNode::alloc(NULL, NULL, $1, $3); }
| rwDATABLOCK '=' expr semicolon
{ $$ = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), $3); }
| IDENT '[' aidx_expr ']' '=' expr semicolon
{ $$ = SlotAssignNode::alloc(NULL, $3, $1, $6); }
;
aidx_expr
: expr
{ $$ = $1; }
| aidx_expr ',' expr
{ $$ = CommaCatExprNode::alloc($1, $3); }
;
semicolon:
| semicolon ';'
;
%%

2190
engine/console/BASscan.cc Executable file

File diff suppressed because it is too large Load Diff

289
engine/console/BASscan.l Executable file
View File

@ -0,0 +1,289 @@
%{
#define YYLMAX 4096
#include <stdio.h>
#include "platform/platform.h"
#include "core/stringTable.h"
#include "console/console.h"
#define _CMDGRAM_H_
#include "console/compiler.h"
#include "console/basgram.h"
using namespace Compiler;
#define YY_NEVER_INTERACTIVE 1
// Some basic parsing primitives...
static int Sc_ScanString(int ret);
static int Sc_ScanNum();
static int Sc_ScanVar();
static int Sc_ScanHex();
// Deal with debuggability of FLEX.
#ifdef TORQUE_DEBUG
#define FLEX_DEBUG 1
#else
#define FLEX_DEBUG 0
#endif
//#undef input
//#undef unput
#undef BASgetc
int BASgetc();
#define YY_INPUT(buf,result,max_size) \
{ \
int c = '*', n; \
for ( n = 0; n < max_size && \
(c = BASgetc()) != EOF && c != '\n'; ++n ) \
buf[n] = (char) c; \
if ( c == '\n' ) \
buf[n++] = (char) c; \
result = n; \
}
static int lineIndex;
// Prototypes
void BASSetScanBuffer(const char *sb, const char *fn);
void BASerror(char * s, ...);
// Error reporting
void CMDerror(char * s, ...);
// Reset the parser.
void CMDrestart(FILE *in);
%}
DIGIT [0-9]
INTEGER {DIGIT}+
FLOAT ({INTEGER}\.{INTEGER})|({INTEGER}(\.{INTEGER})?[eE][+-]?{INTEGER})
LETTER [A-Za-z_]
FILECHAR [A-Za-z_\.]
VARMID [:A-Za-z0-9_]
IDTAIL [A-Za-z0-9_]
VARTAIL {VARMID}*{IDTAIL}
VAR [$%]{LETTER}{VARTAIL}*
ID {LETTER}{IDTAIL}*
ILID [$%]{DIGIT}+{LETTER}{VARTAIL}*
FILENAME {FILECHAR}+
SPACE [ \t\v\f]
HEXDIGIT [a-fA-F0-9]
%%
;
{SPACE}+ { }
"//"[^\n\r]* ;
"rem"{SPACE}+[^\n\r]* ;
[\r] ;
[\n] { lineIndex++; }
\"(\\.|[^\\"\n\r])*\" { return(Sc_ScanString(STRATOM)); }
\'(\\.|[^\\'\n\r])*\' { return(Sc_ScanString(TAGATOM)); }
"==" return(BASlval.i = opEQ);
"!=" return(BASlval.i = opNE);
">=" return(BASlval.i = opGE);
"<=" return(BASlval.i = opLE);
"&&" return(BASlval.i = opAND);
"||" return(BASlval.i = opOR);
"::" return(BASlval.i = opCOLONCOLON);
"--" return(BASlval.i = opMINUSMINUS);
"++" return(BASlval.i = opPLUSPLUS);
"$=" return(BASlval.i = opSTREQ);
"!$=" return(BASlval.i = opSTRNE);
"<<" return(BASlval.i = opSHL);
">>" return(BASlval.i = opSHR);
"+=" return(BASlval.i = opPLASN);
"-=" return(BASlval.i = opMIASN);
"*=" return(BASlval.i = opMLASN);
"/=" return(BASlval.i = opDVASN);
"%=" return(BASlval.i = opMODASN);
"&=" return(BASlval.i = opANDASN);
"^=" return(BASlval.i = opXORASN);
"|=" return(BASlval.i = opORASN);
"<<=" return(BASlval.i = opSLASN);
">>=" return(BASlval.i = opSRASN);
"NL" {BASlval.i = '\n'; return '@'; }
"TAB" {BASlval.i = '\t'; return '@'; }
"SPC" {BASlval.i = ' '; return '@'; }
"@" {BASlval.i = 0; return '@'; }
"?" |
"[" |
"]" |
"(" |
")" |
"+" |
"-" |
"*" |
"/" |
"<" |
">" |
"|" |
"." |
"!" |
":" |
";" |
"{" |
"}" |
"," |
"&" |
"%" |
"^" |
"~" |
"=" { return(BASlval.i = BAStext[0]); }
"or" { BASlval.i = lineIndex; return(rwCASEOR); }
"break" { BASlval.i = lineIndex; return(rwBREAK); }
"return" { BASlval.i = lineIndex; return(rwRETURN); }
"else" { BASlval.i = lineIndex; return(rwELSE); }
"while" { BASlval.i = lineIndex; return(rwWHILE); }
"if" { BASlval.i = lineIndex; return(rwIF); }
"then" { BASlval.i = lineIndex; return(rwTHEN); }
"do" { BASlval.i = lineIndex; return(rwBEGIN); }
"begin" { BASlval.i = lineIndex; return(rwBEGIN); }
"end" { BASlval.i = lineIndex; return(rwEND); }
"for" { BASlval.i = lineIndex; return(rwFOR); }
"cfor" { BASlval.i = lineIndex; return(rwCFOR); }
"to" { BASlval.i = lineIndex; return(rwTO); }
"step" { BASlval.i = lineIndex; return(rwSTEP); }
"continue" { BASlval.i = lineIndex; return(rwCONTINUE); }
"function" { BASlval.i = lineIndex; return(rwDEFINE); }
"sub" { BASlval.i = lineIndex; return(rwDEFINE); }
"new" { BASlval.i = lineIndex; return(rwDECLARE); }
"datablock" { BASlval.i = lineIndex; return(rwDATABLOCK); }
"case" { BASlval.i = lineIndex; return(rwCASE); }
"switch$" { BASlval.i = lineIndex; return(rwSWITCHSTR); }
"switch" { BASlval.i = lineIndex; return(rwSWITCH); }
"default" { BASlval.i = lineIndex; return(rwDEFAULT); }
"package" { BASlval.i = lineIndex; return(rwPACKAGE); }
"true" { BASlval.i = 1; return INTCONST; }
"false" { BASlval.i = 0; return INTCONST; }
{VAR} return(Sc_ScanVar());
{ID} { BAStext[BASleng] = 0; BASlval.s = StringTable->insert(BAStext); return(IDENT); }
0[xX]{HEXDIGIT}+ return(Sc_ScanHex());
{INTEGER} { BAStext[BASleng] = 0; BASlval.i = atoi(BAStext); return INTCONST; }
{FLOAT} return Sc_ScanNum();
{ILID} return(ILLEGAL_TOKEN);
. return(ILLEGAL_TOKEN);
%%
/*
* Scan character constant.
*/
/*
* Scan identifier.
*/
static const char *scanBuffer;
static const char *fileName;
static int scanIndex;
const char * BASGetCurrentFile()
{
return fileName;
}
int BASGetCurrentLine()
{
return lineIndex;
}
void BASerror(char *, ...)
{
gSyntaxError = true;
if(fileName)
Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - Syntax error.",
fileName, lineIndex);
else
Con::errorf(ConsoleLogEntry::Script, "Syntax error in input.");
}
void BASSetScanBuffer(const char *sb, const char *fn)
{
scanBuffer = sb;
fileName = fn;
scanIndex = 0;
lineIndex = 1;
}
int BASgetc()
{
int ret = scanBuffer[scanIndex];
if(ret)
scanIndex++;
else
ret = -1;
return ret;
}
int BASwrap()
{
return 1;
}
static int Sc_ScanVar()
{
BAStext[BASleng] = 0;
BASlval.s = StringTable->insert(BAStext);
return(VAR);
}
/*
* Scan string constant.
*/
// Defined in CS_CMD.l
extern void expandEscape(char *dest, const char *src);
extern bool collapseEscape(char *buf);
static int charConv(int in)
{
switch(in)
{
case 'r':
return '\r';
case 'n':
return '\n';
case 't':
return '\t';
default:
return in;
}
}
static int getHexDigit(char c)
{
if(c >= '0' && c <= '9')
return c - '0';
if(c >= 'A' && c <= 'F')
return c - 'A' + 10;
if(c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
static int Sc_ScanString(int ret)
{
BAStext[BASleng - 1] = 0;
if(!collapseEscape(BAStext+1))
return -1;
BASlval.str = (char *) consoleAlloc(dStrlen(BAStext));
dStrcpy(BASlval.str, BAStext + 1);
return(ret);
}
static int Sc_ScanNum()
{
BAStext[BASleng] = 0;
BASlval.f = atof(BAStext);
return(FLTCONST);
}
static int Sc_ScanHex()
{
int val = 0;
dSscanf(BAStext, "%x", &val);
BASlval.i = val;
return INTCONST;
}

2140
engine/console/CMDgram.cc Executable file

File diff suppressed because it is too large Load Diff

526
engine/console/CMDgram.y Executable file
View File

@ -0,0 +1,526 @@
%{
// Make sure we don't get gram.h twice.
#define _CMDGRAM_H_
#include <stdlib.h>
#include <stdio.h>
#include "console/console.h"
#include "console/compiler.h"
#include "console/consoleInternal.h"
#ifndef YYDEBUG
#define YYDEBUG 0
#endif
#define YYSSIZE 350
int outtext(char *fmt, ...);
extern int serrors;
#define nil 0
#undef YY_ARGS
#define YY_ARGS(x) x
int CMDlex();
void CMDerror(char *, ...);
#define alloca dMalloc
%}
%{
/* Reserved Word Definitions */
%}
%token <i> rwDEFINE rwENDDEF rwDECLARE
%token <i> rwBREAK rwELSE rwCONTINUE rwGLOBAL
%token <i> rwIF rwNIL rwRETURN rwWHILE rwDO
%token <i> rwENDIF rwENDWHILE rwENDFOR rwDEFAULT
%token <i> rwFOR rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR
%token <i> rwCASEOR rwPACKAGE rwNAMESPACE rwCLASS
%token ILLEGAL_TOKEN
%{
/* Constants and Identifier Definitions */
%}
%token <c> CHRCONST
%token <i> INTCONST
%token <s> TTAG
%token <s> VAR
%token <s> IDENT
%token <str> STRATOM
%token <str> TAGATOM
%token <f> FLTCONST
%{
/* Operator Definitions */
%}
%token <i> '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%'
%token <i> '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@'
%token <i> opMINUSMINUS opPLUSPLUS
%token <i> STMT_SEP
%token <i> opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN
%token <i> opXORASN opORASN opSLASN opSRASN opCAT
%token <i> opEQ opNE opGE opLE opAND opOR opSTREQ
%token <i> opCOLONCOLON
%union {
char c;
int i;
const char * s;
char * str;
double f;
StmtNode * stmt;
ExprNode * expr;
SlotAssignNode * slist;
VarNode * var;
SlotDecl slot;
ObjectBlockDecl odcl;
ObjectDeclNode * od;
AssignDecl asn;
IfStmtNode * ifnode;
}
%type <s> parent_block
%type <ifnode> case_block
%type <stmt> switch_stmt
%type <stmt> decl
%type <stmt> decl_list
%type <stmt> package_decl
%type <stmt> fn_decl_stmt
%type <stmt> fn_decl_list
%type <stmt> statement_list
%type <stmt> stmt
%type <expr> expr_list
%type <expr> expr_list_decl
%type <expr> aidx_expr
%type <expr> funcall_expr
%type <expr> object_name
%type <expr> object_args
%type <expr> stmt_expr
%type <expr> case_expr
%type <expr> class_name_expr
%type <stmt> if_stmt
%type <stmt> while_stmt
%type <stmt> for_stmt
%type <stmt> stmt_block
%type <stmt> datablock_decl
%type <od> object_decl
%type <od> object_decl_list
%type <odcl> object_declare_block
%type <expr> expr
%type <slist> slot_assign_list
%type <slist> slot_assign
%type <slot> slot_acc
%type <stmt> expression_stmt
%type <var> var_list
%type <var> var_list_decl
%type <asn> assign_op_struct
%left '['
%right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN opMDASN opNDASN opNTASN opORASN opSLASN opSRASN '='
%left '?' ':'
%left opOR
%left opAND
%left '|'
%left '^'
%left '&'
%left opEQ opNE
%left '<' opLE '>' opGE
%left '@' opCAT opSTREQ opSTRNE
%left opSHL opSHR
%left '+' '-'
%left '*' '/' '%'
%right '!' '~' opPLUSPLUS opMINUSMINUS UNARY
%left '.'
%%
start
: decl_list
{ }
;
decl_list
:
{ $$ = nil; }
| decl_list decl
{ if(!statementList) { statementList = $2; } else { statementList->append($2); } }
;
decl
: stmt
{ $$ = $1; }
| fn_decl_stmt
{ $$ = $1; }
| package_decl
{ $$ = $1; }
;
package_decl
: rwPACKAGE IDENT '{' fn_decl_list '}' ';'
{ $$ = $4; for(StmtNode *walk = ($4);walk;walk = walk->getNext() ) walk->setPackage($2); }
;
fn_decl_list
: fn_decl_stmt
{ $$ = $1; }
| fn_decl_list fn_decl_stmt
{ $$ = $1; ($1)->append($2); }
;
statement_list
:
{ $$ = nil; }
| statement_list stmt
{ if(!$1) { $$ = $2; } else { ($1)->append($2); $$ = $1; } }
;
stmt
: if_stmt
| while_stmt
| for_stmt
| datablock_decl
| switch_stmt
| rwBREAK ';'
{ $$ = BreakStmtNode::alloc(); }
| rwCONTINUE ';'
{ $$ = ContinueStmtNode::alloc(); }
| rwRETURN ';'
{ $$ = ReturnStmtNode::alloc(NULL); }
| rwRETURN expr ';'
{ $$ = ReturnStmtNode::alloc($2); }
| expression_stmt ';'
{ $$ = $1; }
| TTAG '=' expr ';'
{ $$ = TTagSetStmtNode::alloc($1, $3, NULL); }
| TTAG '=' expr ',' expr ';'
{ $$ = TTagSetStmtNode::alloc($1, $3, $5); }
;
fn_decl_stmt
: rwDEFINE IDENT '(' var_list_decl ')' '{' statement_list '}'
{ $$ = FunctionDeclStmtNode::alloc($2, NULL, $4, $7); }
| rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' '{' statement_list '}'
{ $$ = FunctionDeclStmtNode::alloc($4, $2, $6, $9); }
;
var_list_decl
:
{ $$ = NULL; }
| var_list
{ $$ = $1; }
;
var_list
: VAR
{ $$ = VarNode::alloc($1, NULL); }
| var_list ',' VAR
{ $$ = $1; ((StmtNode*)($1))->append((StmtNode*)VarNode::alloc($3, NULL)); }
;
datablock_decl
: rwDATABLOCK IDENT '(' IDENT parent_block ')' '{' slot_assign_list '}' ';'
{ $$ = ObjectDeclNode::alloc(ConstantNode::alloc($2), ConstantNode::alloc($4), NULL, $5, $8, NULL, true); }
;
object_decl
: rwDECLARE class_name_expr '(' object_name parent_block object_args ')' '{' object_declare_block '}'
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, $9.slots, $9.decls, false); }
| rwDECLARE class_name_expr '(' object_name parent_block object_args ')'
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, NULL, NULL, false); }
;
parent_block
:
{ $$ = NULL; }
| ':' IDENT
{ $$ = $2; }
;
object_name
:
{ $$ = StrConstNode::alloc("", false); }
| expr
{ $$ = $1; }
;
object_args
:
{ $$ = NULL; }
| ',' expr_list
{ $$ = $2; }
;
object_declare_block
:
{ $$.slots = NULL; $$.decls = NULL; }
| slot_assign_list
{ $$.slots = $1; $$.decls = NULL; }
| object_decl_list
{ $$.slots = NULL; $$.decls = $1; }
| slot_assign_list object_decl_list
{ $$.slots = $1; $$.decls = $2; }
;
object_decl_list
: object_decl ';'
{ $$ = $1; }
| object_decl_list object_decl ';'
{ $1->append($2); $$ = $1; }
;
stmt_block
: '{' statement_list '}'
{ $$ = $2; }
| stmt
{ $$ = $1; }
;
switch_stmt
: rwSWITCH '(' expr ')' '{' case_block '}'
{ $$ = $6; $6->propagateSwitchExpr($3, false); }
| rwSWITCHSTR '(' expr ')' '{' case_block '}'
{ $$ = $6; $6->propagateSwitchExpr($3, true); }
;
case_block
: rwCASE case_expr ':' statement_list
{ $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); }
| rwCASE case_expr ':' statement_list rwDEFAULT ':' statement_list
{ $$ = IfStmtNode::alloc($1, $2, $4, $7, false); }
| rwCASE case_expr ':' statement_list case_block
{ $$ = IfStmtNode::alloc($1, $2, $4, $5, true); }
;
case_expr
: expr
{ $$ = $1;}
| case_expr rwCASEOR expr
{ ($1)->append($3); $$=$1; }
;
if_stmt
: rwIF '(' expr ')' stmt_block
{ $$ = IfStmtNode::alloc($1, $3, $5, NULL, false); }
| rwIF '(' expr ')' stmt_block rwELSE stmt_block
{ $$ = IfStmtNode::alloc($1, $3, $5, $7, false); }
;
while_stmt
: rwWHILE '(' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, nil, $3, nil, $5, false); }
| rwDO stmt_block rwWHILE '(' expr ')'
{ $$ = LoopStmtNode::alloc($3, nil, $5, nil, $2, true); }
;
for_stmt
: rwFOR '(' expr ';' expr ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, $3, $5, $7, $9, false); }
| rwFOR '(' expr ';' expr ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, $3, $5, NULL, $8, false); }
| rwFOR '(' expr ';' ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, $3, NULL, $6, $8, false); }
| rwFOR '(' expr ';' ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, $3, NULL, NULL, $7, false); }
| rwFOR '(' ';' expr ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, NULL, $4, $6, $8, false); }
| rwFOR '(' ';' expr ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, NULL, $4, NULL, $7, false); }
| rwFOR '(' ';' ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, NULL, NULL, $5, $7, false); }
| rwFOR '(' ';' ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, NULL, NULL, NULL, $6, false); }
;
expression_stmt
: stmt_expr
{ $$ = $1; }
;
expr
: stmt_expr
{ $$ = $1; }
| '(' expr ')'
{ $$ = $2; }
| expr '^' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '%' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '&' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '|' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '+' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '-' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '*' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '/' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| '-' expr %prec UNARY
{ $$ = FloatUnaryExprNode::alloc($1, $2); }
| '*' expr %prec UNARY
{ $$ = TTagDerefNode::alloc($2); }
| TTAG
{ $$ = TTagExprNode::alloc($1); }
| expr '?' expr ':' expr
{ $$ = ConditionalExprNode::alloc($1, $3, $5); }
| expr '<' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '>' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opGE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opLE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opEQ expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opNE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opOR expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSHL expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSHR expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opAND expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSTREQ expr
{ $$ = StreqExprNode::alloc($1, $3, true); }
| expr opSTRNE expr
{ $$ = StreqExprNode::alloc($1, $3, false); }
| expr '@' expr
{ $$ = StrcatExprNode::alloc($1, $3, $2); }
| '!' expr
{ $$ = IntUnaryExprNode::alloc($1, $2); }
| '~' expr
{ $$ = IntUnaryExprNode::alloc($1, $2); }
| TAGATOM
{ $$ = StrConstNode::alloc($1, true); }
| FLTCONST
{ $$ = FloatNode::alloc($1); }
| INTCONST
{ $$ = IntNode::alloc($1); }
| rwBREAK
{ $$ = ConstantNode::alloc(StringTable->insert("break")); }
| slot_acc
{ $$ = SlotAccessNode::alloc($1.object, $1.array, $1.slotName); }
| IDENT
{ $$ = ConstantNode::alloc($1); }
| STRATOM
{ $$ = StrConstNode::alloc($1, false); }
| VAR
{ $$ = (ExprNode*)VarNode::alloc($1, NULL); }
| VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
;
slot_acc
: expr '.' IDENT
{ $$.object = $1; $$.slotName = $3; $$.array = NULL; }
| expr '.' IDENT '[' aidx_expr ']'
{ $$.object = $1; $$.slotName = $3; $$.array = $5; }
;
class_name_expr
: IDENT
{ $$ = ConstantNode::alloc($1); }
| '(' expr ')'
{ $$ = $2; }
;
assign_op_struct
: opPLUSPLUS
{ $$.token = '+'; $$.expr = FloatNode::alloc(1); }
| opMINUSMINUS
{ $$.token = '-'; $$.expr = FloatNode::alloc(1); }
| opPLASN expr
{ $$.token = '+'; $$.expr = $2; }
| opMIASN expr
{ $$.token = '-'; $$.expr = $2; }
| opMLASN expr
{ $$.token = '*'; $$.expr = $2; }
| opDVASN expr
{ $$.token = '/'; $$.expr = $2; }
| opMODASN expr
{ $$.token = '%'; $$.expr = $2; }
| opANDASN expr
{ $$.token = '&'; $$.expr = $2; }
| opXORASN expr
{ $$.token = '^'; $$.expr = $2; }
| opORASN expr
{ $$.token = '|'; $$.expr = $2; }
| opSLASN expr
{ $$.token = opSHL; $$.expr = $2; }
| opSRASN expr
{ $$.token = opSHR; $$.expr = $2; }
;
stmt_expr
: funcall_expr
{ $$ = $1; }
| object_decl
{ $$ = $1; }
| VAR '=' expr
{ $$ = AssignExprNode::alloc($1, NULL, $3); }
| VAR '[' aidx_expr ']' '=' expr
{ $$ = AssignExprNode::alloc($1, $3, $6); }
| VAR assign_op_struct
{ $$ = AssignOpExprNode::alloc($1, NULL, $2.expr, $2.token); }
| VAR '[' aidx_expr ']' assign_op_struct
{ $$ = AssignOpExprNode::alloc($1, $3, $5.expr, $5.token); }
| slot_acc assign_op_struct
{ $$ = SlotAssignOpNode::alloc($1.object, $1.slotName, $1.array, $2.token, $2.expr); }
| slot_acc '=' expr
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $3); }
| slot_acc '=' '{' expr_list '}'
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $4); }
;
funcall_expr
: IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc($1, NULL, $3, false); }
| IDENT opCOLONCOLON IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc($3, $1, $5, false); }
| expr '.' IDENT '(' expr_list_decl ')'
{ $1->append($5); $$ = FuncCallExprNode::alloc($3, NULL, $1, true); }
;
expr_list_decl
:
{ $$ = NULL; }
| expr_list
{ $$ = $1; }
;
expr_list
: expr
{ $$ = $1; }
| expr_list ',' expr
{ ($1)->append($3); $$ = $1; }
;
slot_assign_list
: slot_assign
{ $$ = $1; }
| slot_assign_list slot_assign
{ $1->append($2); $$ = $1; }
;
slot_assign
: IDENT '=' expr ';'
{ $$ = SlotAssignNode::alloc(NULL, NULL, $1, $3); }
| rwDATABLOCK '=' expr ';'
{ $$ = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), $3); }
| IDENT '[' aidx_expr ']' '=' expr ';'
{ $$ = SlotAssignNode::alloc(NULL, $3, $1, $6); }
;
aidx_expr
: expr
{ $$ = $1; }
| aidx_expr ',' expr
{ $$ = CommaCatExprNode::alloc($1, $3); }
;
%%

2336
engine/console/CMDscan.cc Executable file

File diff suppressed because it is too large Load Diff

466
engine/console/CMDscan.l Executable file
View File

@ -0,0 +1,466 @@
%{
#define YYLMAX 4096
#include <stdio.h>
#include "platform/platform.h"
#include "core/stringTable.h"
#include "console/console.h"
#include "console/compiler.h"
using namespace Compiler;
#define YY_NEVER_INTERACTIVE 1
// Some basic parsing primitives...
static int Sc_ScanString(int ret);
static int Sc_ScanNum();
static int Sc_ScanVar();
static int Sc_ScanHex();
// Deal with debuggability of FLEX.
#ifdef TORQUE_DEBUG
#define FLEX_DEBUG 1
#else
#define FLEX_DEBUG 0
#endif
// Install our own input code...
#undef CMDgetc
int CMDgetc();
// Hack to make windows lex happy.
#ifndef isatty
inline int isatty(int) { return 0; }
#endif
// Wrap our getc, so that lex doesn't try to do its own buffering/file IO.
#define YY_INPUT(buf,result,max_size) \
{ \
int c = '*', n; \
for ( n = 0; n < max_size && \
(c = CMDgetc()) != EOF && c != '\n'; ++n ) \
buf[n] = (char) c; \
if ( c == '\n' ) \
buf[n++] = (char) c; \
result = n; \
}
// General helper stuff.
static int lineIndex;
// File state
void CMDSetScanBuffer(const char *sb, const char *fn);
const char * CMDgetFileLine(int &lineNumber);
// Error reporting
void CMDerror(char * s, ...);
// Reset the parser.
void CMDrestart(FILE *in);
%}
DIGIT [0-9]
INTEGER {DIGIT}+
FLOAT ({INTEGER}\.{INTEGER})|({INTEGER}(\.{INTEGER})?[eE][+-]?{INTEGER})
LETTER [A-Za-z_]
FILECHAR [A-Za-z_\.]
VARMID [:A-Za-z0-9_]
IDTAIL [A-Za-z0-9_]
VARTAIL {VARMID}*{IDTAIL}
VAR [$%]{LETTER}{VARTAIL}*
ID {LETTER}{IDTAIL}*
ILID [$%]{DIGIT}+{LETTER}{VARTAIL}*
FILENAME {FILECHAR}+
SPACE [ \t\v\f]
HEXDIGIT [a-fA-F0-9]
%%
;
{SPACE}+ { }
"//"[^\n\r]* ;
[\r] ;
[\n] {lineIndex++;}
\"(\\.|[^\\"\n\r])*\" { return(Sc_ScanString(STRATOM)); }
\'(\\.|[^\\'\n\r])*\' { return(Sc_ScanString(TAGATOM)); }
"==" return(CMDlval.i = opEQ);
"!=" return(CMDlval.i = opNE);
">=" return(CMDlval.i = opGE);
"<=" return(CMDlval.i = opLE);
"&&" return(CMDlval.i = opAND);
"||" return(CMDlval.i = opOR);
"::" return(CMDlval.i = opCOLONCOLON);
"--" return(CMDlval.i = opMINUSMINUS);
"++" return(CMDlval.i = opPLUSPLUS);
"$=" return(CMDlval.i = opSTREQ);
"!$=" return(CMDlval.i = opSTRNE);
"<<" return(CMDlval.i = opSHL);
">>" return(CMDlval.i = opSHR);
"+=" return(CMDlval.i = opPLASN);
"-=" return(CMDlval.i = opMIASN);
"*=" return(CMDlval.i = opMLASN);
"/=" return(CMDlval.i = opDVASN);
"%=" return(CMDlval.i = opMODASN);
"&=" return(CMDlval.i = opANDASN);
"^=" return(CMDlval.i = opXORASN);
"|=" return(CMDlval.i = opORASN);
"<<=" return(CMDlval.i = opSLASN);
">>=" return(CMDlval.i = opSRASN);
"NL" {CMDlval.i = '\n'; return '@'; }
"TAB" {CMDlval.i = '\t'; return '@'; }
"SPC" {CMDlval.i = ' '; return '@'; }
"@" {CMDlval.i = 0; return '@'; }
"/*" {
register int c;
for ( ; ; )
{
while ( (c = CMDgetc()) != '*' && c != EOF )
; /* eat up text of comment */
if ( c == '*' )
{
while ( (c = CMDgetc()) == '*' )
;
if ( c == '/' )
break; /* found the end */
}
if ( c == EOF )
{
CMDerror( "EOF in comment" );
break;
}
}
}
"?" |
"[" |
"]" |
"(" |
")" |
"+" |
"-" |
"*" |
"/" |
"<" |
">" |
"|" |
"." |
"!" |
":" |
";" |
"{" |
"}" |
"," |
"&" |
"%" |
"^" |
"~" |
"=" { return(CMDlval.i = CMDtext[0]); }
"or" { CMDlval.i = lineIndex; return(rwCASEOR); }
"break" { CMDlval.i = lineIndex; return(rwBREAK); }
"return" { CMDlval.i = lineIndex; return(rwRETURN); }
"else" { CMDlval.i = lineIndex; return(rwELSE); }
"while" { CMDlval.i = lineIndex; return(rwWHILE); }
"do" { CMDlval.i = lineIndex; return(rwDO); }
"if" { CMDlval.i = lineIndex; return(rwIF); }
"for" { CMDlval.i = lineIndex; return(rwFOR); }
"continue" { CMDlval.i = lineIndex; return(rwCONTINUE); }
"function" { CMDlval.i = lineIndex; return(rwDEFINE); }
"new" { CMDlval.i = lineIndex; return(rwDECLARE); }
"datablock" { CMDlval.i = lineIndex; return(rwDATABLOCK); }
"case" { CMDlval.i = lineIndex; return(rwCASE); }
"switch$" { CMDlval.i = lineIndex; return(rwSWITCHSTR); }
"switch" { CMDlval.i = lineIndex; return(rwSWITCH); }
"default" { CMDlval.i = lineIndex; return(rwDEFAULT); }
"package" { CMDlval.i = lineIndex; return(rwPACKAGE); }
"namespace" { CMDlval.i = lineIndex; return(rwNAMESPACE); }
"true" { CMDlval.i = 1; return INTCONST; }
"false" { CMDlval.i = 0; return INTCONST; }
{VAR} return(Sc_ScanVar());
{ID} { CMDtext[CMDleng] = 0; CMDlval.s = StringTable->insert(CMDtext); return(IDENT); }
0[xX]{HEXDIGIT}+ return(Sc_ScanHex());
{INTEGER} { CMDtext[CMDleng] = 0; CMDlval.i = dAtoi(CMDtext); return INTCONST; }
{FLOAT} return Sc_ScanNum();
{ILID} return(ILLEGAL_TOKEN);
. return(ILLEGAL_TOKEN);
%%
static const char *scanBuffer;
static const char *fileName;
static int scanIndex;
const char * CMDGetCurrentFile()
{
return fileName;
}
int CMDGetCurrentLine()
{
return lineIndex;
}
void CMDerror(char *, ...)
{
gSyntaxError = true;
if(fileName)
Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - Syntax error.",
fileName, lineIndex);
else
Con::errorf(ConsoleLogEntry::Script, "Syntax error in input.");
}
void CMDSetScanBuffer(const char *sb, const char *fn)
{
scanBuffer = sb;
fileName = fn;
scanIndex = 0;
lineIndex = 1;
}
int CMDgetc()
{
int ret = scanBuffer[scanIndex];
if(ret)
scanIndex++;
else
ret = -1;
return ret;
}
int CMDwrap()
{
return 1;
}
static int Sc_ScanVar()
{
// Truncate the temp buffer...
CMDtext[CMDleng] = 0;
// Make it a stringtable string!
CMDlval.s = StringTable->insert(CMDtext);
return(VAR);
}
static int charConv(int in)
{
switch(in)
{
case 'r':
return '\r';
case 'n':
return '\n';
case 't':
return '\t';
default:
return in;
}
}
static int getHexDigit(char c)
{
if(c >= '0' && c <= '9')
return c - '0';
if(c >= 'A' && c <= 'F')
return c - 'A' + 10;
if(c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
static int Sc_ScanString(int ret)
{
CMDtext[CMDleng - 1] = 0;
if(!collapseEscape(CMDtext+1))
return -1;
CMDlval.str = (char *) consoleAlloc(dStrlen(CMDtext));
dStrcpy(CMDlval.str, CMDtext + 1);
return(ret);
}
void expandEscape(char *dest, const char *src)
{
U8 c;
while((c = (U8) *src++) != 0)
{
if(c == '\"')
{
*dest++ = '\\';
*dest++ = '\"';
}
else if(c == '\\')
{
*dest++ = '\\';
*dest++ = '\\';
}
else if(c == '\r')
{
*dest++ = '\\';
*dest++ = 'r';
}
else if(c == '\n')
{
*dest++ = '\\';
*dest++ = 'n';
}
else if(c == '\t')
{
*dest++ = '\\';
*dest++ = 't';
}
else if(c == '\'')
{
*dest++ = '\\';
*dest++ = '\'';
}
else if((c >= 1 && c <= 7) ||
(c >= 11 && c <= 12) ||
(c >= 14 && c <= 15))
{
/* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */
static U8 expandRemap[15] = { 0x0,
0x0,
0x1,
0x2,
0x3,
0x4,
0x5,
0x6,
0x0,
0x0,
0x0,
0x7,
0x8,
0x0,
0x9 };
*dest++ = '\\';
*dest++ = 'c';
if(c == 15)
*dest++ = 'r';
else if(c == 16)
*dest++ = 'p';
else if(c == 17)
*dest++ = 'o';
else
*dest++ = expandRemap[c] + '0';
}
else if(c < 32)
{
*dest++ = '\\';
*dest++ = 'x';
S32 dig1 = c >> 4;
S32 dig2 = c & 0xf;
if(dig1 < 10)
dig1 += '0';
else
dig1 += 'A' - 10;
if(dig2 < 10)
dig2 += '0';
else
dig2 += 'A' - 10;
*dest++ = dig1;
*dest++ = dig2;
}
else
*dest++ = c;
}
*dest = '\0';
}
bool collapseEscape(char *buf)
{
S32 len = dStrlen(buf) + 1;
for(S32 i = 0; i < len;)
{
if(buf[i] == '\\')
{
if(buf[i+1] == 'x')
{
S32 dig1 = getHexDigit(buf[i+2]);
if(dig1 == -1)
return false;
S32 dig2 = getHexDigit(buf[i+3]);
if(dig2 == -1)
return false;
buf[i] = dig1 * 16 + dig2;
dMemmove(buf + i + 1, buf + i + 4, len - i - 3);
len -= 3;
i++;
}
else if(buf[i+1] == 'c')
{
/* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */
static U8 collapseRemap[10] = { 0x1,
0x2,
0x3,
0x4,
0x5,
0x6,
0x7,
0xb,
0xc,
0xe };
if(buf[i+2] == 'r')
buf[i] = 15;
else if(buf[i+2] == 'p')
buf[i] = 16;
else if(buf[i+2] == 'o')
buf[i] = 17;
else
{
int dig1 = buf[i+2] - '0';
if(dig1 < 0 || dig1 > 9)
return false;
buf[i] = collapseRemap[dig1];
}
// Make sure we don't put 0x1 at the beginning of the string.
if ((buf[i] == 0x1) && (i == 0))
{
buf[i] = 0x2;
buf[i+1] = 0x1;
dMemmove(buf + i + 2, buf + i + 3, len - i - 1);
len -= 1;
}
else
{
dMemmove(buf + i + 1, buf + i + 3, len - i - 2);
len -= 2;
}
i++;
}
else
{
buf[i] = charConv(buf[i+1]);
dMemmove(buf + i + 1, buf + i + 2, len - i - 1);
len--;
i++;
}
}
else
i++;
}
return true;
}
static int Sc_ScanNum()
{
CMDtext[CMDleng] = 0;
CMDlval.f = dAtof(CMDtext);
return(FLTCONST);
}
static int Sc_ScanHex()
{
S32 val = 0;
dSscanf(CMDtext, "%x", &val);
CMDlval.i = val;
return INTCONST;
}
void CMD_reset()
{
CMDrestart(NULL);
}

461
engine/console/ast.h Executable file
View File

@ -0,0 +1,461 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _AST_H_
#define _AST_H_
class ExprEvalState;
class Namespace;
class SimObject;
class SimGroup;
enum TypeReq {
TypeReqNone,
TypeReqUInt,
TypeReqFloat,
TypeReqString
};
/// Representation of a node for the scripting language parser.
///
/// When the scripting language is evaluated, it is turned from a string representation,
/// into a parse tree, thence into byte code, which is ultimately interpreted by the VM.
///
/// This is the base class for the nodes in the parse tree. There are a great many subclasses,
/// each representing a different language construct.
struct StmtNode
{
StmtNode *next; ///< Next entry in parse tree.
StmtNode();
/// @name next Accessors
/// @{
///
void append(StmtNode *next);
StmtNode *getNext() { return next; }
/// @}
/// @name Debug Info
/// @{
StringTableEntry dbgFileName; ///< Name of file this node is associated with.
S32 dbgLineNumber; ///< Line number this node is associated with.
/// @}
/// @name Breaking
/// @{
void addBreakCount();
void addBreakLine(U32 ip);
/// @}
/// @name Compilation
/// @{
virtual U32 precompileStmt(U32 loopCount) = 0;
virtual U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint) = 0;
virtual void setPackage(StringTableEntry packageName);
/// @}
};
struct BreakStmtNode : StmtNode
{
static BreakStmtNode *alloc();
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
struct ContinueStmtNode : StmtNode
{
static ContinueStmtNode *alloc();
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
/// A mathematical expression.
struct ExprNode : StmtNode
{
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
virtual U32 precompile(TypeReq type) = 0;
virtual U32 compile(U32 *codeStream, U32 ip, TypeReq type) = 0;
virtual TypeReq getPreferredType() = 0;
};
struct ReturnStmtNode : StmtNode
{
ExprNode *expr;
static ReturnStmtNode *alloc(ExprNode *expr);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
struct IfStmtNode : StmtNode
{
ExprNode *testExpr;
StmtNode *ifBlock, *elseBlock;
U32 endifOffset;
U32 elseOffset;
bool integer;
bool propagate;
static IfStmtNode *alloc(S32 lineNumber, ExprNode *testExpr, StmtNode *ifBlock, StmtNode *elseBlock, bool propagateThrough);
void propagateSwitchExpr(ExprNode *left, bool string);
ExprNode *getSwitchOR(ExprNode *left, ExprNode *list, bool string);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
struct LoopStmtNode : StmtNode
{
ExprNode *testExpr;
ExprNode *initExpr;
ExprNode *endLoopExpr;
StmtNode *loopBlock;
bool isDoLoop;
U32 breakOffset;
U32 continueOffset;
U32 loopBlockStartOffset;
bool integer;
static LoopStmtNode *alloc(S32 lineNumber, ExprNode *testExpr, ExprNode *initExpr, ExprNode *endLoopExpr, StmtNode *loopBlock, bool isDoLoop);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
/// A binary mathematical expression (ie, left op right).
struct BinaryExprNode : ExprNode
{
S32 op;
ExprNode *left;
ExprNode *right;
};
struct FloatBinaryExprNode : BinaryExprNode
{
static FloatBinaryExprNode *alloc(S32 op, ExprNode *left, ExprNode *right);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct ConditionalExprNode : ExprNode
{
ExprNode *testExpr;
ExprNode *trueExpr;
ExprNode *falseExpr;
bool integer;
static ConditionalExprNode *alloc(ExprNode *testExpr, ExprNode *trueExpr, ExprNode *falseExpr);
virtual U32 precompile(TypeReq type);
virtual U32 compile(U32 *codeStream, U32 ip, TypeReq type);
virtual TypeReq getPreferredType();
};
struct IntBinaryExprNode : BinaryExprNode
{
TypeReq subType;
U32 operand;
static IntBinaryExprNode *alloc(S32 op, ExprNode *left, ExprNode *right);
void getSubTypeOperand();
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct StreqExprNode : BinaryExprNode
{
bool eq;
static StreqExprNode *alloc(ExprNode *left, ExprNode *right, bool eq);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct StrcatExprNode : BinaryExprNode
{
int appendChar;
static StrcatExprNode *alloc(ExprNode *left, ExprNode *right, int appendChar);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct CommaCatExprNode : BinaryExprNode
{
static CommaCatExprNode *alloc(ExprNode *left, ExprNode *right);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct IntUnaryExprNode : ExprNode
{
S32 op;
ExprNode *expr;
bool integer;
static IntUnaryExprNode *alloc(S32 op, ExprNode *expr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct FloatUnaryExprNode : ExprNode
{
S32 op;
ExprNode *expr;
static FloatUnaryExprNode *alloc(S32 op, ExprNode *expr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct VarNode : ExprNode
{
StringTableEntry varName;
ExprNode *arrayIndex;
static VarNode *alloc(StringTableEntry varName, ExprNode *arrayIndex);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct IntNode : ExprNode
{
S32 value;
U32 index; // if it's converted to float/string
static IntNode *alloc(S32 value);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct FloatNode : ExprNode
{
F64 value;
U32 index;
static FloatNode *alloc(F64 value);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct StrConstNode : ExprNode
{
char *str;
F64 fVal;
U32 index;
bool tag;
static StrConstNode *alloc(char *str, bool tag);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct ConstantNode : ExprNode
{
StringTableEntry value;
F64 fVal;
U32 index;
static ConstantNode *alloc(StringTableEntry value);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct AssignExprNode : ExprNode
{
StringTableEntry varName;
ExprNode *expr;
ExprNode *arrayIndex;
TypeReq subType;
static AssignExprNode *alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct AssignDecl
{
S32 token;
ExprNode *expr;
bool integer;
};
struct AssignOpExprNode : ExprNode
{
StringTableEntry varName;
ExprNode *expr;
ExprNode *arrayIndex;
S32 op;
U32 operand;
TypeReq subType;
static AssignOpExprNode *alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr, S32 op);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct TTagSetStmtNode : StmtNode
{
StringTableEntry tag;
ExprNode *valueExpr;
ExprNode *stringExpr;
static TTagSetStmtNode *alloc(StringTableEntry tag, ExprNode *valueExpr, ExprNode *stringExpr);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
struct TTagDerefNode : ExprNode
{
ExprNode *expr;
static TTagDerefNode *alloc(ExprNode *expr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct TTagExprNode : ExprNode
{
StringTableEntry tag;
static TTagExprNode *alloc(StringTableEntry tag);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct FuncCallExprNode : ExprNode
{
StringTableEntry funcName;
StringTableEntry nameSpace;
ExprNode *args;
U32 callType;
enum {
FunctionCall,
MethodCall,
ParentCall
};
static FuncCallExprNode *alloc(StringTableEntry funcName, StringTableEntry nameSpace, ExprNode *args, bool dot);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct SlotDecl
{
ExprNode *object;
StringTableEntry slotName;
ExprNode *array;
};
struct SlotAccessNode : ExprNode
{
ExprNode *objectExpr, *arrayExpr;
StringTableEntry slotName;
static SlotAccessNode *alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct SlotAssignNode : ExprNode
{
ExprNode *objectExpr, *arrayExpr;
StringTableEntry slotName;
ExprNode *valueExpr;
static SlotAssignNode *alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct SlotAssignOpNode : ExprNode
{
ExprNode *objectExpr, *arrayExpr;
StringTableEntry slotName;
S32 op;
ExprNode *valueExpr;
U32 operand;
TypeReq subType;
static SlotAssignOpNode *alloc(ExprNode *objectExpr, StringTableEntry slotName, ExprNode *arrayExpr, S32 op, ExprNode *valueExpr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct ObjectDeclNode : ExprNode
{
ExprNode *classNameExpr;
StringTableEntry parentObject;
ExprNode *objectNameExpr;
ExprNode *argList;
SlotAssignNode *slotDecls;
ObjectDeclNode *subObjects;
bool structDecl;
U32 failOffset;
static ObjectDeclNode *alloc(ExprNode *classNameExpr, ExprNode *objectNameExpr, ExprNode *argList, StringTableEntry parentObject, SlotAssignNode *slotDecls, ObjectDeclNode *subObjects, bool structDecl);
U32 precompile(TypeReq type);
U32 precompileSubObject(bool);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
U32 compileSubObject(U32 *codeStream, U32 ip, bool);
TypeReq getPreferredType();
};
struct ObjectBlockDecl
{
SlotAssignNode *slots;
ObjectDeclNode *decls;
};
struct FunctionDeclStmtNode : StmtNode
{
StringTableEntry fnName;
VarNode *args;
StmtNode *stmts;
StringTableEntry nameSpace;
StringTableEntry package;
U32 endOffset;
U32 argc;
static FunctionDeclStmtNode *alloc(StringTableEntry fnName, StringTableEntry nameSpace, VarNode *args, StmtNode *stmts);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
void setPackage(StringTableEntry packageName);
};
extern StmtNode *statementList;
extern void createFunction(const char *fnName, VarNode *args, StmtNode *statements);
extern ExprEvalState gEvalState;
extern bool lookupFunction(const char *fnName, VarNode **args, StmtNode **statements);
typedef const char *(*cfunc)(S32 argc, char **argv);
extern bool lookupCFunction(const char *fnName, cfunc *f);
#endif

335
engine/console/astAlloc.cc Executable file
View File

@ -0,0 +1,335 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/compiler.h"
#include "console/consoleInternal.h"
using namespace Compiler;
/// @file
///
/// TorqueScript AST node allocators.
///
/// These static methods exist to allocate new AST node for the compiler. They
/// all allocate memory from the consoleAllocator for efficiency, and often take
/// arguments relating to the state of the nodes. They are called from gram.y
/// (really gram.c) as the lexer analyzes the script code.
//------------------------------------------------------------
BreakStmtNode *BreakStmtNode::alloc()
{
BreakStmtNode *ret = (BreakStmtNode *) consoleAlloc(sizeof(BreakStmtNode));
constructInPlace(ret);
return ret;
}
ContinueStmtNode *ContinueStmtNode::alloc()
{
ContinueStmtNode *ret = (ContinueStmtNode *) consoleAlloc(sizeof(ContinueStmtNode));
constructInPlace(ret);
return ret;
}
ReturnStmtNode *ReturnStmtNode::alloc(ExprNode *expr)
{
ReturnStmtNode *ret = (ReturnStmtNode *) consoleAlloc(sizeof(ReturnStmtNode));
constructInPlace(ret);
ret->expr = expr;
return ret;
}
IfStmtNode *IfStmtNode::alloc(S32 lineNumber, ExprNode *testExpr, StmtNode *ifBlock, StmtNode *elseBlock, bool propagate)
{
IfStmtNode *ret = (IfStmtNode *) consoleAlloc(sizeof(IfStmtNode));
constructInPlace(ret);
ret->dbgLineNumber = lineNumber;
ret->testExpr = testExpr;
ret->ifBlock = ifBlock;
ret->elseBlock = elseBlock;
ret->propagate = propagate;
return ret;
}
LoopStmtNode *LoopStmtNode::alloc(S32 lineNumber, ExprNode *initExpr, ExprNode *testExpr, ExprNode *endLoopExpr, StmtNode *loopBlock, bool isDoLoop)
{
LoopStmtNode *ret = (LoopStmtNode *) consoleAlloc(sizeof(LoopStmtNode));
constructInPlace(ret);
ret->dbgLineNumber = lineNumber;
ret->testExpr = testExpr;
ret->initExpr = initExpr;
ret->endLoopExpr = endLoopExpr;
ret->loopBlock = loopBlock;
ret->isDoLoop = isDoLoop;
// Deal with setting some dummy constant nodes if we weren't provided with
// info... This allows us to play nice with missing parts of for(;;) for
// instance.
if(!ret->testExpr) ret->testExpr = IntNode::alloc(1);
return ret;
}
FloatBinaryExprNode *FloatBinaryExprNode::alloc(S32 op, ExprNode *left, ExprNode *right)
{
FloatBinaryExprNode *ret = (FloatBinaryExprNode *) consoleAlloc(sizeof(FloatBinaryExprNode));
constructInPlace(ret);
ret->op = op;
ret->left = left;
ret->right = right;
return ret;
}
IntBinaryExprNode *IntBinaryExprNode::alloc(S32 op, ExprNode *left, ExprNode *right)
{
IntBinaryExprNode *ret = (IntBinaryExprNode *) consoleAlloc(sizeof(IntBinaryExprNode));
constructInPlace(ret);
ret->op = op;
ret->left = left;
ret->right = right;
return ret;
}
StreqExprNode *StreqExprNode::alloc(ExprNode *left, ExprNode *right, bool eq)
{
StreqExprNode *ret = (StreqExprNode *) consoleAlloc(sizeof(StreqExprNode));
constructInPlace(ret);
ret->left = left;
ret->right = right;
ret->eq = eq;
return ret;
}
StrcatExprNode *StrcatExprNode::alloc(ExprNode *left, ExprNode *right, int appendChar)
{
StrcatExprNode *ret = (StrcatExprNode *) consoleAlloc(sizeof(StrcatExprNode));
constructInPlace(ret);
ret->left = left;
ret->right = right;
ret->appendChar = appendChar;
return ret;
}
CommaCatExprNode *CommaCatExprNode::alloc(ExprNode *left, ExprNode *right)
{
CommaCatExprNode *ret = (CommaCatExprNode *) consoleAlloc(sizeof(CommaCatExprNode));
constructInPlace(ret);
ret->left = left;
ret->right = right;
return ret;
}
IntUnaryExprNode *IntUnaryExprNode::alloc(S32 op, ExprNode *expr)
{
IntUnaryExprNode *ret = (IntUnaryExprNode *) consoleAlloc(sizeof(IntUnaryExprNode));
constructInPlace(ret);
ret->op = op;
ret->expr = expr;
return ret;
}
FloatUnaryExprNode *FloatUnaryExprNode::alloc(S32 op, ExprNode *expr)
{
FloatUnaryExprNode *ret = (FloatUnaryExprNode *) consoleAlloc(sizeof(FloatUnaryExprNode));
constructInPlace(ret);
ret->op = op;
ret->expr = expr;
return ret;
}
VarNode *VarNode::alloc(StringTableEntry varName, ExprNode *arrayIndex)
{
VarNode *ret = (VarNode *) consoleAlloc(sizeof(VarNode));
constructInPlace(ret);
ret->varName = varName;
ret->arrayIndex = arrayIndex;
return ret;
}
IntNode *IntNode::alloc(S32 value)
{
IntNode *ret = (IntNode *) consoleAlloc(sizeof(IntNode));
constructInPlace(ret);
ret->value = value;
return ret;
}
ConditionalExprNode *ConditionalExprNode::alloc(ExprNode *testExpr, ExprNode *trueExpr, ExprNode *falseExpr)
{
ConditionalExprNode *ret = (ConditionalExprNode *) consoleAlloc(sizeof(ConditionalExprNode));
constructInPlace(ret);
ret->testExpr = testExpr;
ret->trueExpr = trueExpr;
ret->falseExpr = falseExpr;
ret->integer = false;
return ret;
}
FloatNode *FloatNode::alloc(F64 value)
{
FloatNode *ret = (FloatNode *) consoleAlloc(sizeof(FloatNode));
constructInPlace(ret);
ret->value = value;
return ret;
}
StrConstNode *StrConstNode::alloc(char *str, bool tag)
{
StrConstNode *ret = (StrConstNode *) consoleAlloc(sizeof(StrConstNode));
constructInPlace(ret);
ret->str = (char *) consoleAlloc(dStrlen(str) + 1);
ret->tag = tag;
dStrcpy(ret->str, str);
return ret;
}
ConstantNode *ConstantNode::alloc(StringTableEntry value)
{
ConstantNode *ret = (ConstantNode *) consoleAlloc(sizeof(ConstantNode));
constructInPlace(ret);
ret->value = value;
return ret;
}
AssignExprNode *AssignExprNode::alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr)
{
AssignExprNode *ret = (AssignExprNode *) consoleAlloc(sizeof(AssignExprNode));
constructInPlace(ret);
ret->varName = varName;
ret->expr = expr;
ret->arrayIndex = arrayIndex;
return ret;
}
AssignOpExprNode *AssignOpExprNode::alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr, S32 op)
{
AssignOpExprNode *ret = (AssignOpExprNode *) consoleAlloc(sizeof(AssignOpExprNode));
constructInPlace(ret);
ret->varName = varName;
ret->expr = expr;
ret->arrayIndex = arrayIndex;
ret->op = op;
return ret;
}
TTagSetStmtNode *TTagSetStmtNode::alloc(StringTableEntry tag, ExprNode *valueExpr, ExprNode *stringExpr)
{
TTagSetStmtNode *ret = (TTagSetStmtNode *) consoleAlloc(sizeof(TTagSetStmtNode));
constructInPlace(ret);
ret->tag = tag;
ret->valueExpr = valueExpr;
ret->stringExpr = stringExpr;
return ret;
}
TTagDerefNode *TTagDerefNode::alloc(ExprNode *expr)
{
TTagDerefNode *ret = (TTagDerefNode *) consoleAlloc(sizeof(TTagDerefNode));
constructInPlace(ret);
ret->expr = expr;
return ret;
}
TTagExprNode *TTagExprNode::alloc(StringTableEntry tag)
{
TTagExprNode *ret = (TTagExprNode *) consoleAlloc(sizeof(TTagExprNode));
constructInPlace(ret);
ret->tag = tag;
return ret;
}
FuncCallExprNode *FuncCallExprNode::alloc(StringTableEntry funcName, StringTableEntry nameSpace, ExprNode *args, bool dot)
{
FuncCallExprNode *ret = (FuncCallExprNode *) consoleAlloc(sizeof(FuncCallExprNode));
constructInPlace(ret);
ret->funcName = funcName;
ret->nameSpace = nameSpace;
ret->args = args;
if(dot)
ret->callType = MethodCall;
else
{
if(nameSpace && !dStricmp(nameSpace, "Parent"))
ret->callType = ParentCall;
else
ret->callType = FunctionCall;
}
return ret;
}
SlotAccessNode *SlotAccessNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName)
{
SlotAccessNode *ret = (SlotAccessNode *) consoleAlloc(sizeof(SlotAccessNode));
constructInPlace(ret);
ret->objectExpr = objectExpr;
ret->arrayExpr = arrayExpr;
ret->slotName = slotName;
return ret;
}
SlotAssignNode *SlotAssignNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr)
{
SlotAssignNode *ret = (SlotAssignNode *) consoleAlloc(sizeof(SlotAssignNode));
constructInPlace(ret);
ret->objectExpr = objectExpr;
ret->arrayExpr = arrayExpr;
ret->slotName = slotName;
ret->valueExpr = valueExpr;
return ret;
}
SlotAssignOpNode *SlotAssignOpNode::alloc(ExprNode *objectExpr, StringTableEntry slotName, ExprNode *arrayExpr, S32 op, ExprNode *valueExpr)
{
SlotAssignOpNode *ret = (SlotAssignOpNode *) consoleAlloc(sizeof(SlotAssignOpNode));
constructInPlace(ret);
ret->objectExpr = objectExpr;
ret->arrayExpr = arrayExpr;
ret->slotName = slotName;
ret->op = op;
ret->valueExpr = valueExpr;
return ret;
}
ObjectDeclNode *ObjectDeclNode::alloc(ExprNode *classNameExpr, ExprNode *objectNameExpr, ExprNode *argList, StringTableEntry parentObject, SlotAssignNode *slotDecls, ObjectDeclNode *subObjects, bool structDecl)
{
ObjectDeclNode *ret = (ObjectDeclNode *) consoleAlloc(sizeof(ObjectDeclNode));
constructInPlace(ret);
ret->classNameExpr = classNameExpr;
ret->objectNameExpr = objectNameExpr;
ret->argList = argList;
ret->slotDecls = slotDecls;
ret->subObjects = subObjects;
ret->structDecl = structDecl;
if(parentObject)
ret->parentObject = parentObject;
else
ret->parentObject = StringTable->insert("");
return ret;
}
FunctionDeclStmtNode *FunctionDeclStmtNode::alloc(StringTableEntry fnName, StringTableEntry nameSpace, VarNode *args, StmtNode *stmts)
{
FunctionDeclStmtNode *ret = (FunctionDeclStmtNode *) consoleAlloc(sizeof(FunctionDeclStmtNode));
constructInPlace(ret);
ret->fnName = fnName;
ret->args = args;
ret->stmts = stmts;
ret->nameSpace = nameSpace;
ret->package = NULL;
return ret;
}

1680
engine/console/astNodes.cc Executable file

File diff suppressed because it is too large Load Diff

85
engine/console/basgram.h Executable file
View File

@ -0,0 +1,85 @@
typedef union {
char c;
int i;
const char *s;
char *str;
double f;
StmtNode *stmt;
ExprNode *expr;
SlotAssignNode *slist;
VarNode *var;
SlotDecl slot;
ObjectBlockDecl odcl;
ObjectDeclNode *od;
AssignDecl asn;
IfStmtNode *ifnode;
} YYSTYPE;
#define rwDEFINE 258
#define rwENDDEF 259
#define rwDECLARE 260
#define rwBREAK 261
#define rwELSE 262
#define rwCONTINUE 263
#define rwGLOBAL 264
#define rwIF 265
#define rwNIL 266
#define rwRETURN 267
#define rwWHILE 268
#define rwENDIF 269
#define rwENDWHILE 270
#define rwENDFOR 271
#define rwDEFAULT 272
#define rwFOR 273
#define rwDATABLOCK 274
#define rwSWITCH 275
#define rwCASE 276
#define rwSWITCHSTR 277
#define rwCASEOR 278
#define rwPACKAGE 279
#define ILLEGAL_TOKEN 280
#define CHRCONST 281
#define INTCONST 282
#define TTAG 283
#define VAR 284
#define IDENT 285
#define STRATOM 286
#define TAGATOM 287
#define FLTCONST 288
#define opMINUSMINUS 289
#define opPLUSPLUS 290
#define STMT_SEP 291
#define opSHL 292
#define opSHR 293
#define opPLASN 294
#define opMIASN 295
#define opMLASN 296
#define opDVASN 297
#define opMODASN 298
#define opANDASN 299
#define opXORASN 300
#define opORASN 301
#define opSLASN 302
#define opSRASN 303
#define opCAT 304
#define opEQ 305
#define opNE 306
#define opGE 307
#define opLE 308
#define opAND 309
#define opOR 310
#define opSTREQ 311
#define opCOLONCOLON 312
#define opMDASN 313
#define opNDASN 314
#define opNTASN 315
#define opSTRNE 316
#define UNARY 317
#define rwTHEN 318
#define rwEND 319
#define rwBEGIN 320
#define rwCFOR 321
#define rwTO 322
#define rwSTEP 323
extern YYSTYPE BASlval;

6
engine/console/bison.bat Executable file
View File

@ -0,0 +1,6 @@
echo Changing to %4 ...
cd %4
echo Generating %2 and %3 with prefix %1.
..\..\bin\bison\bison.exe -o %2 %3 --defines -p %1
echo Renaming %2 to %5 .
move /Y %2 %5

686
engine/console/bison.simple Executable file
View File

@ -0,0 +1,686 @@
/* -*-C-*- Note some compilers choke on comments on `#line' lines. */
#line 3 "bison.simple"
/* Skeleton output parser for bison,
Copyright (C) 1984, 1989, 1990 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/* As a special exception, when this file is copied by Bison into a
Bison output file, you may use that output file without restriction.
This special exception was added by the Free Software Foundation
in version 1.24 of Bison. */
#ifndef alloca
#ifdef __GNUC__
#define alloca __builtin_alloca
#else /* not GNU C. */
#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi)
#include <alloca.h>
#else /* not sparc */
#if defined (MSDOS) && !defined (__TURBOC__)
#include <malloc.h>
#else /* not MSDOS, or __TURBOC__ */
#if defined(_AIX)
#include <malloc.h>
#pragma alloca
#else /* not MSDOS, __TURBOC__, or _AIX */
#ifdef __hpux
#ifdef __cplusplus
extern "C" {
void *alloca (unsigned int);
};
#else /* not __cplusplus */
void *alloca ();
#endif /* not __cplusplus */
#endif /* __hpux */
#endif /* not _AIX */
#endif /* not MSDOS, or __TURBOC__ */
#endif /* not sparc. */
#endif /* not GNU C. */
#endif /* alloca not defined. */
/* This is the parser code that is written into each bison parser
when the %semantic_parser declaration is not specified in the grammar.
It was written by Richard Stallman by simplifying the hairy parser
used when %semantic_parser is specified. */
/* Note: there must be only one dollar sign in this file.
It is replaced by the list of actions, each action
as one case of the switch. */
#define yyerrok (yyerrstatus = 0)
#define yyclearin (yychar = YYEMPTY)
#define YYEMPTY -2
#define YYEOF 0
#define YYACCEPT return(0)
#define YYABORT return(1)
#define YYERROR goto yyerrlab1
/* Like YYERROR except do call yyerror.
This remains here temporarily to ease the
transition to the new meaning of YYERROR, for GCC.
Once GCC version 2 has supplanted version 1, this can go. */
#define YYFAIL goto yyerrlab
#define YYRECOVERING() (!!yyerrstatus)
#define YYBACKUP(token, value) \
do \
if (yychar == YYEMPTY && yylen == 1) \
{ yychar = (token), yylval = (value); \
yychar1 = YYTRANSLATE (yychar); \
YYPOPSTACK; \
goto yybackup; \
} \
else \
{ yyerror ("syntax error: cannot back up"); YYERROR; } \
while (0)
#define YYTERROR 1
#define YYERRCODE 256
#ifndef YYPURE
#define YYLEX yylex()
#endif
#ifdef YYPURE
#ifdef YYLSP_NEEDED
#ifdef YYLEX_PARAM
#define YYLEX yylex(&yylval, &yylloc, YYLEX_PARAM)
#else
#define YYLEX yylex(&yylval, &yylloc)
#endif
#else /* not YYLSP_NEEDED */
#ifdef YYLEX_PARAM
#define YYLEX yylex(&yylval, YYLEX_PARAM)
#else
#define YYLEX yylex(&yylval)
#endif
#endif /* not YYLSP_NEEDED */
#endif
/* If nonreentrant, generate the variables here */
#ifndef YYPURE
int yychar; /* the lookahead symbol */
YYSTYPE yylval; /* the semantic value of the */
/* lookahead symbol */
#ifdef YYLSP_NEEDED
YYLTYPE yylloc; /* location data for the lookahead */
/* symbol */
#endif
int yynerrs; /* number of parse errors so far */
#endif /* not YYPURE */
#if YYDEBUG != 0
int yydebug; /* nonzero means print parse trace */
/* Since this is uninitialized, it does not stop multiple parsers
from coexisting. */
#endif
/* YYINITDEPTH indicates the initial size of the parser's stacks */
#ifndef YYINITDEPTH
#define YYINITDEPTH 200
#endif
/* YYMAXDEPTH is the maximum size the stacks can grow to
(effective only if the built-in stack extension method is used). */
#if YYMAXDEPTH == 0
#undef YYMAXDEPTH
#endif
#ifndef YYMAXDEPTH
#define YYMAXDEPTH 10000
#endif
/* Prevent warning if -Wstrict-prototypes. */
#ifdef __GNUC__
int yyparse (void);
#endif
#if __GNUC__ > 1 /* GNU C and GNU C++ define this. */
#define __yy_memcpy(FROM,TO,COUNT) __builtin_memcpy(TO,FROM,COUNT)
#else /* not GNU C or C++ */
#ifndef __cplusplus
/* This is the most reliable way to avoid incompatibilities
in available built-in functions on various systems. */
static void
__yy_memcpy (from, to, count)
char *from;
char *to;
int count;
{
register char *f = from;
register char *t = to;
register int i = count;
while (i-- > 0)
*t++ = *f++;
}
#else /* __cplusplus */
/* This is the most reliable way to avoid incompatibilities
in available built-in functions on various systems. */
static void
__yy_memcpy (char *from, char *to, int count)
{
register char *f = from;
register char *t = to;
register int i = count;
while (i-- > 0)
*t++ = *f++;
}
#endif
#endif
#line 192 "bison.simple"
/* The user can define YYPARSE_PARAM as the name of an argument to be passed
into yyparse. The argument should have type void *.
It should actually point to an object.
Grammar actions can access the variable by casting it
to the proper pointer type. */
#ifdef YYPARSE_PARAM
#define YYPARSE_PARAM_DECL void *YYPARSE_PARAM;
#else
#define YYPARSE_PARAM
#define YYPARSE_PARAM_DECL
#endif
int
yyparse(YYPARSE_PARAM)
YYPARSE_PARAM_DECL
{
register int yystate;
register int yyn;
register short *yyssp;
register YYSTYPE *yyvsp;
int yyerrstatus; /* number of tokens to shift before error messages enabled */
int yychar1 = 0; /* lookahead token as an internal (translated) token number */
short yyssa[YYINITDEPTH]; /* the state stack */
YYSTYPE yyvsa[YYINITDEPTH]; /* the semantic value stack */
short *yyss = yyssa; /* refer to the stacks thru separate pointers */
YYSTYPE *yyvs = yyvsa; /* to allow yyoverflow to reallocate them elsewhere */
#ifdef YYLSP_NEEDED
YYLTYPE yylsa[YYINITDEPTH]; /* the location stack */
YYLTYPE *yyls = yylsa;
YYLTYPE *yylsp;
#define YYPOPSTACK (yyvsp--, yyssp--, yylsp--)
#else
#define YYPOPSTACK (yyvsp--, yyssp--)
#endif
int yystacksize = YYINITDEPTH;
#ifdef YYPURE
int yychar;
YYSTYPE yylval;
int yynerrs;
#ifdef YYLSP_NEEDED
YYLTYPE yylloc;
#endif
#endif
YYSTYPE yyval; /* the variable used to return */
/* semantic values from the action */
/* routines */
int yylen;
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Starting parse\n");
#endif
yystate = 0;
yyerrstatus = 0;
yynerrs = 0;
yychar = YYEMPTY; /* Cause a token to be read. */
/* Initialize stack pointers.
Waste one element of value and location stack
so that they stay on the same level as the state stack.
The wasted elements are never initialized. */
yyssp = yyss - 1;
yyvsp = yyvs;
#ifdef YYLSP_NEEDED
yylsp = yyls;
#endif
/* Push a new state, which is found in yystate . */
/* In all cases, when you get here, the value and location stacks
have just been pushed. so pushing a state here evens the stacks. */
yynewstate:
*++yyssp = yystate;
if (yyssp >= yyss + yystacksize - 1)
{
/* Give user a chance to reallocate the stack */
/* Use copies of these so that the &'s don't force the real ones into memory. */
YYSTYPE *yyvs1 = yyvs;
short *yyss1 = yyss;
#ifdef YYLSP_NEEDED
YYLTYPE *yyls1 = yyls;
#endif
/* Get the current used size of the three stacks, in elements. */
int size = yyssp - yyss + 1;
#ifdef yyoverflow
/* Each stack pointer address is followed by the size of
the data in use in that stack, in bytes. */
#ifdef YYLSP_NEEDED
/* This used to be a conditional around just the two extra args,
but that might be undefined if yyoverflow is a macro. */
yyoverflow("parser stack overflow",
&yyss1, size * sizeof (*yyssp),
&yyvs1, size * sizeof (*yyvsp),
&yyls1, size * sizeof (*yylsp),
&yystacksize);
#else
yyoverflow("parser stack overflow",
&yyss1, size * sizeof (*yyssp),
&yyvs1, size * sizeof (*yyvsp),
&yystacksize);
#endif
yyss = yyss1; yyvs = yyvs1;
#ifdef YYLSP_NEEDED
yyls = yyls1;
#endif
#else /* no yyoverflow */
/* Extend the stack our own way. */
if (yystacksize >= YYMAXDEPTH)
{
yyerror("parser stack overflow");
return 2;
}
yystacksize *= 2;
if (yystacksize > YYMAXDEPTH)
yystacksize = YYMAXDEPTH;
yyss = (short *) alloca (yystacksize * sizeof (*yyssp));
__yy_memcpy ((char *)yyss1, (char *)yyss, size * sizeof (*yyssp));
yyvs = (YYSTYPE *) alloca (yystacksize * sizeof (*yyvsp));
__yy_memcpy ((char *)yyvs1, (char *)yyvs, size * sizeof (*yyvsp));
#ifdef YYLSP_NEEDED
yyls = (YYLTYPE *) alloca (yystacksize * sizeof (*yylsp));
__yy_memcpy ((char *)yyls1, (char *)yyls, size * sizeof (*yylsp));
#endif
#endif /* no yyoverflow */
yyssp = yyss + size - 1;
yyvsp = yyvs + size - 1;
#ifdef YYLSP_NEEDED
yylsp = yyls + size - 1;
#endif
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Stack size increased to %d\n", yystacksize);
#endif
if (yyssp >= yyss + yystacksize - 1)
YYABORT;
}
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Entering state %d\n", yystate);
#endif
goto yybackup;
yybackup:
/* Do appropriate processing given the current state. */
/* Read a lookahead token if we need one and don't already have one. */
/* yyresume: */
/* First try to decide what to do without reference to lookahead token. */
yyn = yypact[yystate];
if (yyn == YYFLAG)
goto yydefault;
/* Not known => get a lookahead token if don't already have one. */
/* yychar is either YYEMPTY or YYEOF
or a valid token in external form. */
if (yychar == YYEMPTY)
{
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Reading a token: ");
#endif
yychar = YYLEX;
}
/* Convert token to internal form (in yychar1) for indexing tables with */
if (yychar <= 0) /* This means end of input. */
{
yychar1 = 0;
yychar = YYEOF; /* Don't call YYLEX any more */
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Now at end of input.\n");
#endif
}
else
{
yychar1 = YYTRANSLATE(yychar);
#if YYDEBUG != 0
if (yydebug)
{
fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]);
/* Give the individual parser a way to print the precise meaning
of a token, for further debugging info. */
#ifdef YYPRINT
YYPRINT (stderr, yychar, yylval);
#endif
fprintf (stderr, ")\n");
}
#endif
}
yyn += yychar1;
if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1)
goto yydefault;
yyn = yytable[yyn];
/* yyn is what to do for this token type in this state.
Negative => reduce, -yyn is rule number.
Positive => shift, yyn is new state.
New state is final state => don't bother to shift,
just return success.
0, or most negative number => error. */
if (yyn < 0)
{
if (yyn == YYFLAG)
goto yyerrlab;
yyn = -yyn;
goto yyreduce;
}
else if (yyn == 0)
goto yyerrlab;
if (yyn == YYFINAL)
YYACCEPT;
/* Shift the lookahead token. */
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]);
#endif
/* Discard the token being shifted unless it is eof. */
if (yychar != YYEOF)
yychar = YYEMPTY;
*++yyvsp = yylval;
#ifdef YYLSP_NEEDED
*++yylsp = yylloc;
#endif
/* count tokens shifted since error; after three, turn off error status. */
if (yyerrstatus) yyerrstatus--;
yystate = yyn;
goto yynewstate;
/* Do the default action for the current state. */
yydefault:
yyn = yydefact[yystate];
if (yyn == 0)
goto yyerrlab;
/* Do a reduction. yyn is the number of a rule to reduce with. */
yyreduce:
yylen = yyr2[yyn];
if (yylen > 0)
yyval = yyvsp[1-yylen]; /* implement default value of the action */
#if YYDEBUG != 0
if (yydebug)
{
int i;
fprintf (stderr, "Reducing via rule %d (line %d), ",
yyn, yyrline[yyn]);
/* Print the symbols being reduced, and their result. */
for (i = yyprhs[yyn]; yyrhs[i] > 0; i++)
fprintf (stderr, "%s ", yytname[yyrhs[i]]);
fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]);
}
#endif
$ /* the action file gets copied in in place of this dollarsign */
#line 487 "bison.simple"
yyvsp -= yylen;
yyssp -= yylen;
#ifdef YYLSP_NEEDED
yylsp -= yylen;
#endif
#if YYDEBUG != 0
if (yydebug)
{
short *ssp1 = yyss - 1;
fprintf (stderr, "state stack now");
while (ssp1 != yyssp)
fprintf (stderr, " %d", *++ssp1);
fprintf (stderr, "\n");
}
#endif
*++yyvsp = yyval;
#ifdef YYLSP_NEEDED
yylsp++;
if (yylen == 0)
{
yylsp->first_line = yylloc.first_line;
yylsp->first_column = yylloc.first_column;
yylsp->last_line = (yylsp-1)->last_line;
yylsp->last_column = (yylsp-1)->last_column;
yylsp->text = 0;
}
else
{
yylsp->last_line = (yylsp+yylen-1)->last_line;
yylsp->last_column = (yylsp+yylen-1)->last_column;
}
#endif
/* Now "shift" the result of the reduction.
Determine what state that goes to,
based on the state we popped back to
and the rule number reduced by. */
yyn = yyr1[yyn];
yystate = yypgoto[yyn - YYNTBASE] + *yyssp;
if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp)
yystate = yytable[yystate];
else
yystate = yydefgoto[yyn - YYNTBASE];
goto yynewstate;
yyerrlab: /* here on detecting error */
if (! yyerrstatus)
/* If not already recovering from an error, report this error. */
{
++yynerrs;
#ifdef YYERROR_VERBOSE
yyn = yypact[yystate];
if (yyn > YYFLAG && yyn < YYLAST)
{
int size = 0;
char *msg;
int x, count;
count = 0;
/* Start X at -yyn if nec to avoid negative indexes in yycheck. */
for (x = (yyn < 0 ? -yyn : 0);
x < (sizeof(yytname) / sizeof(char *)); x++)
if (yycheck[x + yyn] == x)
size += strlen(yytname[x]) + 15, count++;
msg = (char *) malloc(size + 15);
if (msg != 0)
{
strcpy(msg, "parse error");
if (count < 5)
{
count = 0;
for (x = (yyn < 0 ? -yyn : 0);
x < (sizeof(yytname) / sizeof(char *)); x++)
if (yycheck[x + yyn] == x)
{
strcat(msg, count == 0 ? ", expecting `" : " or `");
strcat(msg, yytname[x]);
strcat(msg, "'");
count++;
}
}
yyerror(msg);
free(msg);
}
else
yyerror ("parse error; also virtual memory exceeded");
}
else
#endif /* YYERROR_VERBOSE */
yyerror("parse error");
}
goto yyerrlab1;
yyerrlab1: /* here on error raised explicitly by an action */
if (yyerrstatus == 3)
{
/* if just tried and failed to reuse lookahead token after an error, discard it. */
/* return failure if at end of input */
if (yychar == YYEOF)
YYABORT;
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]);
#endif
yychar = YYEMPTY;
}
/* Else will try to reuse lookahead token
after shifting the error token. */
yyerrstatus = 3; /* Each real token shifted decrements this */
goto yyerrhandle;
yyerrdefault: /* current state does not do anything special for the error token. */
#if 0
/* This is wrong; only states that explicitly want error tokens
should shift them. */
yyn = yydefact[yystate]; /* If its default is to accept any token, ok. Otherwise pop it.*/
if (yyn) goto yydefault;
#endif
yyerrpop: /* pop the current state because it cannot handle the error token */
if (yyssp == yyss) YYABORT;
yyvsp--;
yystate = *--yyssp;
#ifdef YYLSP_NEEDED
yylsp--;
#endif
#if YYDEBUG != 0
if (yydebug)
{
short *ssp1 = yyss - 1;
fprintf (stderr, "Error: state stack now");
while (ssp1 != yyssp)
fprintf (stderr, " %d", *++ssp1);
fprintf (stderr, "\n");
}
#endif
yyerrhandle:
yyn = yypact[yystate];
if (yyn == YYFLAG)
goto yyerrdefault;
yyn += YYTERROR;
if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR)
goto yyerrdefault;
yyn = yytable[yyn];
if (yyn < 0)
{
if (yyn == YYFLAG)
goto yyerrpop;
yyn = -yyn;
goto yyreduce;
}
else if (yyn == 0)
goto yyerrpop;
if (yyn == YYFINAL)
YYACCEPT;
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Shifting error token, ");
#endif
*++yyvsp = yylval;
#ifdef YYLSP_NEEDED
*++yylsp = yylloc;
#endif
yystate = yyn;
goto yynewstate;
}

82
engine/console/cmdgram.h Executable file
View File

@ -0,0 +1,82 @@
typedef union {
char c;
int i;
const char * s;
char * str;
double f;
StmtNode * stmt;
ExprNode * expr;
SlotAssignNode * slist;
VarNode * var;
SlotDecl slot;
ObjectBlockDecl odcl;
ObjectDeclNode * od;
AssignDecl asn;
IfStmtNode * ifnode;
} YYSTYPE;
#define rwDEFINE 258
#define rwENDDEF 259
#define rwDECLARE 260
#define rwBREAK 261
#define rwELSE 262
#define rwCONTINUE 263
#define rwGLOBAL 264
#define rwIF 265
#define rwNIL 266
#define rwRETURN 267
#define rwWHILE 268
#define rwDO 269
#define rwENDIF 270
#define rwENDWHILE 271
#define rwENDFOR 272
#define rwDEFAULT 273
#define rwFOR 274
#define rwDATABLOCK 275
#define rwSWITCH 276
#define rwCASE 277
#define rwSWITCHSTR 278
#define rwCASEOR 279
#define rwPACKAGE 280
#define rwNAMESPACE 281
#define rwCLASS 282
#define ILLEGAL_TOKEN 283
#define CHRCONST 284
#define INTCONST 285
#define TTAG 286
#define VAR 287
#define IDENT 288
#define STRATOM 289
#define TAGATOM 290
#define FLTCONST 291
#define opMINUSMINUS 292
#define opPLUSPLUS 293
#define STMT_SEP 294
#define opSHL 295
#define opSHR 296
#define opPLASN 297
#define opMIASN 298
#define opMLASN 299
#define opDVASN 300
#define opMODASN 301
#define opANDASN 302
#define opXORASN 303
#define opORASN 304
#define opSLASN 305
#define opSRASN 306
#define opCAT 307
#define opEQ 308
#define opNE 309
#define opGE 310
#define opLE 311
#define opAND 312
#define opOR 313
#define opSTREQ 314
#define opCOLONCOLON 315
#define opMDASN 316
#define opNDASN 317
#define opNTASN 318
#define opSTRNE 319
#define UNARY 320
extern YYSTYPE CMDlval;

559
engine/console/codeBlock.cc Executable file
View File

@ -0,0 +1,559 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/compiler.h"
#include "console/codeBlock.h"
#include "console/telnetDebugger.h"
#include "core/resManager.h"
using namespace Compiler;
bool CodeBlock::smInFunction = false;
U32 CodeBlock::smBreakLineCount = 0;
CodeBlock * CodeBlock::smCodeBlockList = NULL;
CodeBlock * CodeBlock::smCurrentCodeBlock = NULL;
ConsoleParser *CodeBlock::smCurrentParser = NULL;
//-------------------------------------------------------------------------
CodeBlock::CodeBlock()
{
globalStrings = NULL;
functionStrings = NULL;
globalFloats = NULL;
functionFloats = NULL;
lineBreakPairs = NULL;
breakList = NULL;
breakListSize = 0;
refCount = 0;
code = NULL;
name = NULL;
mRoot = StringTable->insert("");
}
CodeBlock::~CodeBlock()
{
// Make sure we aren't lingering in the current code block...
AssertFatal(smCurrentCodeBlock != this, "CodeBlock::~CodeBlock - Caught lingering in smCurrentCodeBlock!")
if(name)
removeFromCodeList();
delete[] const_cast<char*>(globalStrings);
delete[] const_cast<char*>(functionStrings);
delete[] globalFloats;
delete[] functionFloats;
delete[] code;
delete[] breakList;
}
//-------------------------------------------------------------------------
StringTableEntry CodeBlock::getCurrentCodeBlockName()
{
if (CodeBlock::getCurrentBlock())
return CodeBlock::getCurrentBlock()->name;
else
return NULL;
}
CodeBlock *CodeBlock::find(StringTableEntry name)
{
for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
if(walk->name == name)
return walk;
return NULL;
}
//-------------------------------------------------------------------------
void CodeBlock::addToCodeList()
{
// remove any code blocks with my name
for(CodeBlock **walk = &smCodeBlockList; *walk;walk = &((*walk)->nextFile))
{
if((*walk)->name == name)
{
*walk = (*walk)->nextFile;
break;
}
}
nextFile = smCodeBlockList;
smCodeBlockList = this;
}
void CodeBlock::clearAllBreaks()
{
if(!lineBreakPairs)
return;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
code[p[1]] = p[0] & 0xFF;
}
}
void CodeBlock::clearBreakpoint(U32 lineNumber)
{
if(!lineBreakPairs)
return;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
if((p[0] >> 8) == lineNumber)
{
code[p[1]] = p[0] & 0xFF;
return;
}
}
}
void CodeBlock::setAllBreaks()
{
if(!lineBreakPairs)
return;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
code[p[1]] = OP_BREAK;
}
}
bool CodeBlock::setBreakpoint(U32 lineNumber)
{
if(!lineBreakPairs)
return false;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
if((p[0] >> 8) == lineNumber)
{
code[p[1]] = OP_BREAK;
return true;
}
}
return false;
}
U32 CodeBlock::findFirstBreakLine(U32 lineNumber)
{
if(!lineBreakPairs)
return 0;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
U32 line = (p[0] >> 8);
if( lineNumber <= line )
return line;
}
return 0;
}
struct LinePair
{
U32 instLine;
U32 ip;
};
void CodeBlock::findBreakLine(U32 ip, U32 &line, U32 &instruction)
{
U32 min = 0;
U32 max = lineBreakPairCount - 1;
LinePair *p = (LinePair *) lineBreakPairs;
U32 found;
if(!lineBreakPairCount || p[min].ip > ip || p[max].ip < ip)
{
line = 0;
instruction = OP_INVALID;
return;
}
else if(p[min].ip == ip)
found = min;
else if(p[max].ip == ip)
found = max;
else
{
for(;;)
{
if(min == max - 1)
{
found = min;
break;
}
U32 mid = (min + max) >> 1;
if(p[mid].ip == ip)
{
found = mid;
break;
}
else if(p[mid].ip > ip)
max = mid;
else
min = mid;
}
}
instruction = p[found].instLine & 0xFF;
line = p[found].instLine >> 8;
}
const char *CodeBlock::getFileLine(U32 ip)
{
static char nameBuffer[256];
U32 line, inst;
findBreakLine(ip, line, inst);
dSprintf(nameBuffer, sizeof(nameBuffer), "%s (%d)", name ? name : "<input>", line);
return nameBuffer;
}
void CodeBlock::removeFromCodeList()
{
for(CodeBlock **walk = &smCodeBlockList; *walk; walk = &((*walk)->nextFile))
{
if(*walk == this)
{
*walk = nextFile;
// clear out all breakpoints
clearAllBreaks();
return;
}
}
}
void CodeBlock::calcBreakList()
{
U32 size = 0;
S32 line = -1;
U32 seqCount = 0;
U32 i;
for(i = 0; i < lineBreakPairCount; i++)
{
U32 lineNumber = lineBreakPairs[i * 2];
if(lineNumber == U32(line + 1))
seqCount++;
else
{
if(seqCount)
size++;
size++;
seqCount = 1;
}
line = lineNumber;
}
if(seqCount)
size++;
breakList = new U32[size];
breakListSize = size;
line = -1;
seqCount = 0;
size = 0;
for(i = 0; i < lineBreakPairCount; i++)
{
U32 lineNumber = lineBreakPairs[i * 2];
if(lineNumber == U32(line + 1))
seqCount++;
else
{
if(seqCount)
breakList[size++] = seqCount;
breakList[size++] = lineNumber - getMax(0, line) - 1;
seqCount = 1;
}
line = lineNumber;
}
if(seqCount)
breakList[size++] = seqCount;
for(i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
p[0] = (p[0] << 8) | code[p[1]];
}
// Let the telnet debugger know that this code
// block has been loaded and that it can add break
// points it has for it.
if ( TelDebugger )
TelDebugger->addAllBreakpoints( this );
}
bool CodeBlock::read(StringTableEntry fileName, Stream &st)
{
name = fileName;
//
if (name)
{
if (const char *slash = dStrchr(this->name, '/'))
{
char root[512];
dStrncpy(root, this->name, slash-this->name);
root[slash-this->name] = 0;
mRoot = StringTable->insert(root);
}
}
//
addToCodeList();
U32 globalSize,size,i;
st.read(&size);
if(size)
{
globalSize = size;
globalStrings = new char[size];
st.read(size, globalStrings);
}
st.read(&size);
if(size)
{
functionStrings = new char[size];
st.read(size, functionStrings);
}
st.read(&size);
if(size)
{
globalFloats = new F64[size];
for(U32 i = 0; i < size; i++)
st.read(&globalFloats[i]);
}
st.read(&size);
if(size)
{
functionFloats = new F64[size];
for(U32 i = 0; i < size; i++)
st.read(&functionFloats[i]);
}
U32 codeSize;
st.read(&codeSize);
st.read(&lineBreakPairCount);
U32 totSize = codeSize + lineBreakPairCount * 2;
code = new U32[totSize];
for(i = 0; i < codeSize; i++)
{
U8 b;
st.read(&b);
if(b == 0xFF)
st.read(&code[i]);
else
code[i] = b;
}
for(i = codeSize; i < totSize; i++)
st.read(&code[i]);
lineBreakPairs = code + codeSize;
// StringTable-ize our identifiers.
U32 identCount;
st.read(&identCount);
while(identCount--)
{
U32 offset;
st.read(&offset);
StringTableEntry ste;
if(offset < globalSize)
ste = StringTable->insert(globalStrings + offset);
else
ste = StringTable->insert("");
U32 count;
st.read(&count);
while(count--)
{
U32 ip;
st.read(&ip);
code[ip] = *((U32 *) &ste);
}
}
if(lineBreakPairCount)
calcBreakList();
return true;
}
bool CodeBlock::compile(const char *codeFileName, StringTableEntry fileName, const char *script)
{
gSyntaxError = false;
consoleAllocReset();
STEtoU32 = compileSTEtoU32;
statementList = NULL;
// Set up the parser.
smCurrentParser = getParserForFile(fileName);
AssertISV(smCurrentParser, avar("CodeBlock::compile - no parser available for '%s'!", fileName));
// Now do some parsing.
smCurrentParser->setScanBuffer(script, fileName);
smCurrentParser->restart(NULL);
smCurrentParser->parse();
if(gSyntaxError)
{
consoleAllocReset();
return false;
}
FileStream st;
if(!ResourceManager->openFileForWrite(st, codeFileName))
return false;
st.write(U32(Con::DSOVersion));
// Reset all our value tables...
resetTables();
smInFunction = false;
smBreakLineCount = 0;
setBreakCodeBlock(this);
if(statementList)
codeSize = precompileBlock(statementList, 0) + 1;
else
codeSize = 1;
lineBreakPairCount = smBreakLineCount;
code = new U32[codeSize + smBreakLineCount * 2];
lineBreakPairs = code + codeSize;
// Write string table data...
getGlobalStringTable().write(st);
getFunctionStringTable().write(st);
// Write float table data...
getGlobalFloatTable().write(st);
getFunctionFloatTable().write(st);
smBreakLineCount = 0;
U32 lastIp;
if(statementList)
lastIp = compileBlock(statementList, code, 0, 0, 0);
else
lastIp = 0;
if(lastIp != codeSize - 1)
Con::errorf(ConsoleLogEntry::General, "CodeBlock::compile - precompile size mismatch, a precompile/compile function pair is probably mismatched.");
code[lastIp++] = OP_RETURN;
U32 totSize = codeSize + smBreakLineCount * 2;
st.write(codeSize);
st.write(lineBreakPairCount);
// Write out our bytecode, doing a bit of compression for low numbers.
U32 i;
for(i = 0; i < codeSize; i++)
{
if(code[i] < 0xFF)
st.write(U8(code[i]));
else
{
st.write(U8(0xFF));
st.write(code[i]);
}
}
// Write the break info...
for(i = codeSize; i < totSize; i++)
st.write(code[i]);
getIdentTable().write(st);
consoleAllocReset();
st.close();
return true;
}
const char *CodeBlock::compileExec(StringTableEntry fileName, const char *string, bool noCalls, int setFrame)
{
STEtoU32 = evalSTEtoU32;
consoleAllocReset();
name = fileName;
if(name)
addToCodeList();
statementList = NULL;
// Set up the parser.
smCurrentParser = getParserForFile(fileName);
AssertISV(smCurrentParser, avar("CodeBlock::compile - no parser available for '%s'!", fileName));
// Now do some parsing.
smCurrentParser->setScanBuffer(string, fileName);
smCurrentParser->restart(NULL);
smCurrentParser->parse();
if(!statementList)
{
delete this;
return "";
}
resetTables();
smInFunction = false;
smBreakLineCount = 0;
setBreakCodeBlock(this);
codeSize = precompileBlock(statementList, 0) + 1;
lineBreakPairCount = smBreakLineCount;
globalStrings = getGlobalStringTable().build();
functionStrings = getFunctionStringTable().build();
globalFloats = getGlobalFloatTable().build();
functionFloats = getFunctionFloatTable().build();
code = new U32[codeSize + lineBreakPairCount * 2];
lineBreakPairs = code + codeSize;
smBreakLineCount = 0;
U32 lastIp = compileBlock(statementList, code, 0, 0, 0);
code[lastIp++] = OP_RETURN;
consoleAllocReset();
if(lineBreakPairCount && fileName)
calcBreakList();
if(lastIp != codeSize)
Con::warnf(ConsoleLogEntry::General, "precompile size mismatch");
return exec(0, fileName, NULL, 0, 0, noCalls, NULL, setFrame);
}
//-------------------------------------------------------------------------
void CodeBlock::incRefCount()
{
refCount++;
}
void CodeBlock::decRefCount()
{
refCount--;
if(!refCount)
delete this;
}

129
engine/console/codeBlock.h Executable file
View File

@ -0,0 +1,129 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CODEBLOCK_H_
#define _CODEBLOCK_H_
#include "console/compiler.h"
#include "console/consoleParser.h"
class Stream;
/// Core TorqueScript code management class.
///
/// This class represents a block of code, usually mapped directly to a file.
class CodeBlock
{
private:
static CodeBlock* smCodeBlockList;
static CodeBlock* smCurrentCodeBlock;
public:
static U32 smBreakLineCount;
static bool smInFunction;
static Compiler::ConsoleParser * smCurrentParser;
static CodeBlock* getCurrentBlock()
{
return smCurrentCodeBlock;
}
static CodeBlock *getCodeBlockList()
{
return smCodeBlockList;
}
static StringTableEntry getCurrentCodeBlockName();
static CodeBlock *find(StringTableEntry);
CodeBlock();
~CodeBlock();
StringTableEntry name;
char *globalStrings;
char *functionStrings;
F64 *globalFloats;
F64 *functionFloats;
U32 codeSize;
U32 *code;
U32 refCount;
U32 lineBreakPairCount;
U32 *lineBreakPairs;
U32 breakListSize;
U32 *breakList;
CodeBlock *nextFile;
StringTableEntry mRoot;
void addToCodeList();
void removeFromCodeList();
void calcBreakList();
void clearAllBreaks();
void setAllBreaks();
/// Returns the first breakable line or 0 if none was found.
/// @param lineNumber The one based line number.
U32 findFirstBreakLine(U32 lineNumber);
void clearBreakpoint(U32 lineNumber);
/// Set a OP_BREAK instruction on a line. If a break
/// is not possible on that line it returns false.
/// @param lineNumber The one based line number.
bool setBreakpoint(U32 lineNumber);
void findBreakLine(U32 ip, U32 &line, U32 &instruction);
void getFunctionArgs(char buffer[1024], U32 offset);
const char *getFileLine(U32 ip);
bool read(StringTableEntry fileName, Stream &st);
bool compile(const char *dsoName, StringTableEntry fileName, const char *script);
void incRefCount();
void decRefCount();
/// Compiles and executes a block of script storing the compiled code in this
/// CodeBlock. If there is no filename breakpoints will not be generated and
/// the CodeBlock will not be added to the linked list of loaded CodeBlocks.
/// Note that if the script contains no executable statements the CodeBlock
/// will delete itself on return an empty string. The return string is any
/// result of the code executed, if any, or an empty string.
///
/// @param fileName The file name, including path and extension, for the
/// block of code or an empty string.
/// @param script The script code to compile and execute.
/// @param noCalls Skips calling functions from the script.
/// @param setFrame A zero based index of the stack frame to execute the code
/// with, zero being the top of the stack. If the the index is
/// -1 a new frame is created. If the index is out of range the
/// top stack frame is used.
const char *compileExec(StringTableEntry fileName, const char *script,
bool noCalls, int setFrame = -1 );
/// Executes the existing code in the CodeBlock. The return string is any
/// result of the code executed, if any, or an empty string.
///
/// @param offset The instruction offset to start executing from.
/// @param fnName The name of the function to execute or null.
/// @param ns The namespace of the function to execute or null.
/// @param argc The number of parameters passed to the function or
/// zero to execute code outside of a function.
/// @param argv The function parameter list.
/// @param noCalls Skips calling functions from the script.
/// @param setFrame A zero based index of the stack frame to execute the code
/// with, zero being the top of the stack. If the the index is
/// -1 a new frame is created. If the index is out of range the
/// top stack frame is used.
/// @param packageName The code package name or null.
const char *exec(U32 offset, const char *fnName, Namespace *ns, U32 argc,
const char **argv, bool noCalls, StringTableEntry packageName,
S32 setFrame = -1);
};
#endif

1199
engine/console/compiledEval.cc Executable file

File diff suppressed because it is too large Load Diff

271
engine/console/compiler.cc Executable file
View File

@ -0,0 +1,271 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/console.h"
#include "console/telnetDebugger.h"
#include "platform/event.h"
#include "console/ast.h"
#include "core/tAlgorithm.h"
#include "core/resManager.h"
#include "core/findMatch.h"
#include "console/consoleInternal.h"
#include "core/fileStream.h"
#include "console/compiler.h"
#include "console/simBase.h"
namespace Compiler
{
F64 consoleStringToNumber(const char *str, StringTableEntry file, U32 line)
{
F64 val = dAtof(str);
if(val != 0)
return val;
else if(!dStricmp(str, "true"))
return 1;
else if(!dStricmp(str, "false"))
return 0;
else if(file)
{
Con::warnf(ConsoleLogEntry::General, "%s (%d): string always evaluates to 0.", file, line);
return 0;
}
return 0;
}
//------------------------------------------------------------
CompilerStringTable *gCurrentStringTable, gGlobalStringTable, gFunctionStringTable;
CompilerFloatTable *gCurrentFloatTable, gGlobalFloatTable, gFunctionFloatTable;
DataChunker gConsoleAllocator;
CompilerIdentTable gIdentTable;
CodeBlock *gCurBreakBlock;
//------------------------------------------------------------
CodeBlock *getBreakCodeBlock() { return gCurBreakBlock; }
void setBreakCodeBlock(CodeBlock *cb) { gCurBreakBlock = cb; }
//------------------------------------------------------------
U32 evalSTEtoU32(StringTableEntry ste, U32)
{
return *((U32 *) &ste);
}
U32 compileSTEtoU32(StringTableEntry ste, U32 ip)
{
if(ste)
getIdentTable().add(ste, ip);
return 0;
}
U32 (*STEtoU32)(StringTableEntry ste, U32 ip) = evalSTEtoU32;
//------------------------------------------------------------
bool gSyntaxError = false;
//------------------------------------------------------------
CompilerStringTable *getCurrentStringTable() { return gCurrentStringTable; }
CompilerStringTable &getGlobalStringTable() { return gGlobalStringTable; }
CompilerStringTable &getFunctionStringTable() { return gFunctionStringTable; }
void setCurrentStringTable (CompilerStringTable* cst) { gCurrentStringTable = cst; }
CompilerFloatTable *getCurrentFloatTable() { return gCurrentFloatTable; }
CompilerFloatTable &getGlobalFloatTable() { return gGlobalFloatTable; }
CompilerFloatTable &getFunctionFloatTable() { return gFunctionFloatTable; }
void setCurrentFloatTable (CompilerFloatTable* cst) { gCurrentFloatTable = cst; }
CompilerIdentTable &getIdentTable() { return gIdentTable; }
void precompileIdent(StringTableEntry ident)
{
if(ident)
gGlobalStringTable.add(ident);
}
void resetTables()
{
setCurrentStringTable(&gGlobalStringTable);
setCurrentFloatTable(&gGlobalFloatTable);
getGlobalFloatTable().reset();
getGlobalStringTable().reset();
getFunctionFloatTable().reset();
getFunctionStringTable().reset();
getIdentTable().reset();
}
void *consoleAlloc(U32 size) { return gConsoleAllocator.alloc(size); }
void consoleAllocReset() { gConsoleAllocator.freeBlocks(); }
}
//-------------------------------------------------------------------------
using namespace Compiler;
//-------------------------------------------------------------------------
U32 CompilerStringTable::add(const char *str, bool caseSens, bool tag)
{
// Is it already in?
Entry **walk;
for(walk = &list; *walk; walk = &((*walk)->next))
{
if((*walk)->tag != tag)
continue;
if(caseSens)
{
if(!dStrcmp((*walk)->string, str))
return (*walk)->start;
}
else
{
if(!dStricmp((*walk)->string, str))
return (*walk)->start;
}
}
// Write it out.
Entry *newStr = (Entry *) consoleAlloc(sizeof(Entry));
*walk = newStr;
newStr->next = NULL;
newStr->start = totalLen;
U32 len = dStrlen(str) + 1;
if(tag && len < 7) // alloc space for the numeric tag 1 for tag, 5 for # and 1 for nul
len = 7;
totalLen += len;
newStr->string = (char *) consoleAlloc(len);
newStr->len = len;
newStr->tag = tag;
dStrcpy(newStr->string, str);
return newStr->start;
}
U32 CompilerStringTable::addIntString(U32 value)
{
dSprintf(buf, sizeof(buf), "%d", value);
return add(buf);
}
U32 CompilerStringTable::addFloatString(F64 value)
{
dSprintf(buf, sizeof(buf), "%g", value);
return add(buf);
}
void CompilerStringTable::reset()
{
list = NULL;
totalLen = 0;
}
char *CompilerStringTable::build()
{
char *ret = new char[totalLen];
for(Entry *walk = list; walk; walk = walk->next)
dStrcpy(ret + walk->start, walk->string);
return ret;
}
void CompilerStringTable::write(Stream &st)
{
st.write(totalLen);
for(Entry *walk = list; walk; walk = walk->next)
st.write(walk->len, walk->string);
}
//------------------------------------------------------------
U32 CompilerFloatTable::add(F64 value)
{
Entry **walk;
U32 i = 0;
for(walk = &list; *walk; walk = &((*walk)->next), i++)
if(value == (*walk)->val)
return i;
Entry *newFloat = (Entry *) consoleAlloc(sizeof(Entry));
newFloat->val = value;
newFloat->next = NULL;
count++;
*walk = newFloat;
return count-1;
}
void CompilerFloatTable::reset()
{
list = NULL;
count = 0;
}
F64 *CompilerFloatTable::build()
{
F64 *ret = new F64[count];
U32 i = 0;
for(Entry *walk = list; walk; walk = walk->next, i++)
ret[i] = walk->val;
return ret;
}
void CompilerFloatTable::write(Stream &st)
{
st.write(count);
for(Entry *walk = list; walk; walk = walk->next)
st.write(walk->val);
}
//------------------------------------------------------------
void CompilerIdentTable::reset()
{
list = NULL;
}
void CompilerIdentTable::add(StringTableEntry ste, U32 ip)
{
U32 index = gGlobalStringTable.add(ste, false);
Entry *newEntry = (Entry *) consoleAlloc(sizeof(Entry));
newEntry->offset = index;
newEntry->ip = ip;
for(Entry *walk = list; walk; walk = walk->next)
{
if(walk->offset == index)
{
newEntry->nextIdent = walk->nextIdent;
walk->nextIdent = newEntry;
return;
}
}
newEntry->next = list;
list = newEntry;
newEntry->nextIdent = NULL;
}
void CompilerIdentTable::write(Stream &st)
{
U32 count = 0;
Entry * walk;
for(walk = list; walk; walk = walk->next)
count++;
st.write(count);
for(walk = list; walk; walk = walk->next)
{
U32 ec = 0;
Entry * el;
for(el = walk; el; el = el->nextIdent)
ec++;
st.write(walk->offset);
st.write(ec);
for(el = walk; el; el = el->nextIdent)
st.write(el->ip);
}
}

235
engine/console/compiler.h Executable file
View File

@ -0,0 +1,235 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _COMPILER_H_
#define _COMPILER_H_
class Stream;
class DataChunker;
#include "platform/platform.h"
#include "console/ast.h"
#include "console/codeBlock.h"
// Autogenerated, so we should only ever include from once place - here.
// (We can't stick include guards in it without patching bison.)
#ifndef _CMDGRAM_H_
#define _CMDGRAM_H_
#include "console/cmdgram.h"
#endif
namespace Compiler
{
/// The opcodes for the TorqueScript VM.
enum CompiledInstructions
{
OP_FUNC_DECL,
OP_CREATE_OBJECT,
OP_ADD_OBJECT,
OP_END_OBJECT,
OP_JMPIFFNOT,
OP_JMPIFNOT,
OP_JMPIFF,
OP_JMPIF,
OP_JMPIFNOT_NP,
OP_JMPIF_NP,
OP_JMP,
OP_RETURN,
OP_CMPEQ,
OP_CMPGR,
OP_CMPGE,
OP_CMPLT,
OP_CMPLE,
OP_CMPNE,
OP_XOR,
OP_MOD,
OP_BITAND,
OP_BITOR,
OP_NOT,
OP_NOTF,
OP_ONESCOMPLEMENT,
OP_SHR,
OP_SHL,
OP_AND,
OP_OR,
OP_ADD,
OP_SUB,
OP_MUL,
OP_DIV,
OP_NEG,
OP_SETCURVAR,
OP_SETCURVAR_CREATE,
OP_SETCURVAR_ARRAY,
OP_SETCURVAR_ARRAY_CREATE,
OP_LOADVAR_UINT,
OP_LOADVAR_FLT,
OP_LOADVAR_STR,
OP_SAVEVAR_UINT,
OP_SAVEVAR_FLT,
OP_SAVEVAR_STR,
OP_SETCUROBJECT,
OP_SETCUROBJECT_NEW,
OP_SETCURFIELD,
OP_SETCURFIELD_ARRAY,
OP_LOADFIELD_UINT,
OP_LOADFIELD_FLT,
OP_LOADFIELD_STR,
OP_SAVEFIELD_UINT,
OP_SAVEFIELD_FLT,
OP_SAVEFIELD_STR,
OP_STR_TO_UINT,
OP_STR_TO_FLT,
OP_STR_TO_NONE,
OP_FLT_TO_UINT,
OP_FLT_TO_STR,
OP_FLT_TO_NONE,
OP_UINT_TO_FLT,
OP_UINT_TO_STR,
OP_UINT_TO_NONE,
OP_LOADIMMED_UINT,
OP_LOADIMMED_FLT,
OP_TAG_TO_STR,
OP_LOADIMMED_STR,
OP_LOADIMMED_IDENT,
OP_CALLFUNC_RESOLVE,
OP_CALLFUNC,
OP_ADVANCE_STR,
OP_ADVANCE_STR_APPENDCHAR,
OP_ADVANCE_STR_COMMA,
OP_ADVANCE_STR_NUL,
OP_REWIND_STR,
OP_TERMINATE_REWIND_STR,
OP_COMPARE_STR,
OP_PUSH,
OP_PUSH_FRAME,
OP_BREAK,
OP_INVALID
};
//------------------------------------------------------------
F64 consoleStringToNumber(const char *str, StringTableEntry file = 0, U32 line = 0);
U32 precompileBlock(StmtNode *block, U32 loopCount);
U32 compileBlock(StmtNode *block, U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
//------------------------------------------------------------
struct CompilerIdentTable
{
struct Entry
{
U32 offset;
U32 ip;
Entry *next;
Entry *nextIdent;
};
Entry *list;
void add(StringTableEntry ste, U32 ip);
void reset();
void write(Stream &st);
};
//------------------------------------------------------------
struct CompilerStringTable
{
U32 totalLen;
struct Entry
{
char *string;
U32 start;
U32 len;
bool tag;
Entry *next;
};
Entry *list;
char buf[256];
U32 add(const char *str, bool caseSens = true, bool tag = false);
U32 addIntString(U32 value);
U32 addFloatString(F64 value);
void reset();
char *build();
void write(Stream &st);
};
//------------------------------------------------------------
struct CompilerFloatTable
{
struct Entry
{
F64 val;
Entry *next;
};
U32 count;
Entry *list;
U32 add(F64 value);
void reset();
F64 *build();
void write(Stream &st);
};
//------------------------------------------------------------
inline StringTableEntry U32toSTE(U32 u)
{
return *((StringTableEntry *) &u);
}
extern U32 (*STEtoU32)(StringTableEntry ste, U32 ip);
U32 evalSTEtoU32(StringTableEntry ste, U32);
U32 compileSTEtoU32(StringTableEntry ste, U32 ip);
CompilerStringTable *getCurrentStringTable();
CompilerStringTable &getGlobalStringTable();
CompilerStringTable &getFunctionStringTable();
void setCurrentStringTable (CompilerStringTable* cst);
CompilerFloatTable *getCurrentFloatTable();
CompilerFloatTable &getGlobalFloatTable();
CompilerFloatTable &getFunctionFloatTable();
void setCurrentFloatTable (CompilerFloatTable* cst);
CompilerIdentTable &getIdentTable();
void precompileIdent(StringTableEntry ident);
CodeBlock *getBreakCodeBlock();
void setBreakCodeBlock(CodeBlock *cb);
/// Helper function to reset the float, string, and ident tables to a base
/// starting state.
void resetTables();
void *consoleAlloc(U32 size);
void consoleAllocReset();
extern bool gSyntaxError;
};
#endif

1046
engine/console/console.cc Executable file

File diff suppressed because it is too large Load Diff

807
engine/console/console.h Executable file
View File

@ -0,0 +1,807 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONSOLE_H_
#define _CONSOLE_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#include <stdarg.h>
class SimObject;
struct EnumTable;
class Namespace;
/// Indicates that warnings about undefined script variables should be displayed.
///
/// @note This is set and controlled by script.
extern bool gWarnUndefinedScriptVariables;
enum StringTableConstants
{
StringTagPrefixByte = 0x01 ///< Magic value prefixed to tagged strings.
};
/// Represents an entry in the log.
struct ConsoleLogEntry
{
/// This field indicates the severity of the log entry.
///
/// Log entries are filtered and displayed differently based on
/// their severity. Errors are highlighted red, while normal entries
/// are displayed as normal text. Often times, the engine will be
/// configured to hide all log entries except warnings or errors,
/// or to perform a special notification when it encounters an error.
enum Level
{
Normal = 0,
Warning,
Error,
NUM_CLASS
} mLevel;
/// Used to associate a log entry with a module.
///
/// Log entries can come from different sources; for instance,
/// the scripting engine, or the network code. This allows the
/// logging system to be aware of where different log entries
/// originated from.
enum Type
{
General = 0,
Assert,
Script,
GUI,
Network,
NUM_TYPE
} mType;
/// Indicates the actual log entry.
///
/// This contains a description of the event being logged.
/// For instance, "unable to access file", or "player connected
/// successfully", or nearly anything else you might imagine.
///
/// Typically, the description should contain a concise, descriptive
/// string describing whatever is being logged. Whenever possible,
/// include useful details like the name of the file being accessed,
/// or the id of the player or GuiControl, so that if a log needs
/// to be used to locate a bug, it can be done as painlessly as
/// possible.
const char *mString;
};
/// Scripting engine representation of an enum.
///
/// This data structure is used by the scripting engine
/// to expose enumerations to the scripting language. It
/// acts to relate named constants to integer values, just
/// like an enum in C++.
struct EnumTable
{
/// Number of enumerated items in the table.
S32 size;
/// This represents a specific item in the enumeration.
struct Enums
{
S32 index; ///< Index label maps to.
const char *label;///< Label for this index.
};
Enums *table;
/// Constructor.
///
/// This sets up the EnumTable with predefined data.
///
/// @param sSize Size of the table.
/// @param sTable Pointer to table of Enums.
///
/// @see gLiquidTypeTable
/// @see gAlignTable
EnumTable(S32 sSize, Enums *sTable)
{ size = sSize; table = sTable; }
};
typedef const char *StringTableEntry;
/// @defgroup console_callbacks Scripting Engine Callbacks
///
/// The scripting engine makes heavy use of callbacks to represent
/// function exposed to the scripting language. StringCallback,
/// IntCallback, FloatCallback, VoidCallback, and BoolCallback all
/// represent exposed script functions returning different types.
///
/// ConsumerCallback is used with the function Con::addConsumer; functions
/// registered with Con::addConsumer are called whenever something is outputted
/// to the console. For instance, the TelnetConsole registers itself with the
/// console so it can echo the console over the network.
///
/// @note Callbacks to the scripting language - for instance, onExit(), which is
/// a script function called when the engine is shutting down - are handled
/// using Con::executef() and kin.
/// @{
///
typedef const char * (*StringCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef S32 (*IntCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef F32 (*FloatCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef void (*VoidCallback)(SimObject *obj, S32 argc, const char *argv[]); // We have it return a value so things don't break..
typedef bool (*BoolCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef void (*ConsumerCallback)(ConsoleLogEntry::Level level, const char *consoleLine);
/// @}
/// @defgroup console_types Scripting Engine Type Functions
///
/// @see Con::registerType
/// @{
typedef const char* (*GetDataFunction)(void *dptr, EnumTable *tbl, BitSet32 flag);
typedef void (*SetDataFunction)(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag);
/// @}
/// This namespace contains the core of the console functionality.
///
/// @section con_intro Introduction
///
/// The console is a key part of Torque's architecture. It allows direct run-time control
/// of many aspects of the engine.
///
/// @nosubgrouping
namespace Con
{
/// Various configuration constants.
enum Constants
{
/// This is the version number associated with DSO files.
///
/// If you make any changes to the way the scripting language works
/// (such as DSO format changes, adding/removing op-codes) that would
/// break compatibility, then you should increment this.
///
/// If you make a really major change, increment it to the next multiple
/// of ten.
///
/// 12/29/04 - BJG - 33->34 Removed some opcodes, part of namespace upgrade.
/// 12/30/04 - BJG - 34->35 Reordered some things, further general shuffling.
/// 11/03/05 - BJG - 35->36 Integrated new debugger code.
DSOVersion = 36,
MaxLineLength = 512, ///< Maximum length of a line of console input.
MaxDataTypes = 256 ///< Maximum number of registered data types.
};
/// @name Control Functions
///
/// The console must be initialized and shutdown appropriately during the
/// lifetime of the app. These functions are used to manage this behavior.
///
/// @note Torque deals with this aspect of console management, so you don't need
/// to call these functions in normal usage of the engine.
/// @{
/// Initializes the console.
///
/// This performs the following steps:
/// - Calls Namespace::init() to initialize the scripting namespace hierarchy.
/// - Calls ConsoleConstructor::setup() to initialize globally defined console
/// methods and functions.
/// - Registers some basic global script variables.
/// - Calls AbstractClassRep::init() to initialize Torque's class database.
/// - Registers some basic global script functions that couldn't usefully
/// be defined anywhere else.
void init();
/// Shuts down the console.
///
/// This performs the following steps:
/// - Closes the console log file.
/// - Calls Namespace::shutdown() to shut down the scripting namespace hierarchy.
void shutdown();
/// Is the console active at this time?
bool isActive();
/// @}
/// @name Console Consumers
///
/// The console distributes its output through Torque by using
/// consumers. Every time a new line is printed to the console,
/// all the ConsumerCallbacks registered using addConsumer are
/// called, in order.
///
/// @note The GuiConsole control, which provides the on-screen
/// in-game console, uses a different technique to render
/// the console. It calls getLockLog() to lock the Vector
/// of on-screen console entries, then it renders them as
/// needed. While the Vector is locked, the console will
/// not change the Vector. When the GuiConsole control is
/// done with the console entries, it calls unlockLog()
/// to tell the console that it is again safe to modify
/// the Vector.
///
/// @see TelnetConsole
/// @see TelnetDebugger
/// @see WinConsole
/// @see MacCarbConsole
/// @see StdConsole
/// @see ConsoleLogger
///
/// @{
void addConsumer(ConsumerCallback cb);
void removeConsumer(ConsumerCallback cb);
/// @}
/// @name Miscellaneous
/// @{
/// Remove color marking information from a string.
///
/// @note It does this in-place, so be careful! It may
/// potentially blast data if you're not careful.
/// When in doubt, make a copy of the string first.
void stripColorChars(char* line);
/// Convert from a relative script path to an absolute script path.
///
/// This is used in (among other places) the exec() script function, which
/// takes a parameter indicating a script file and executes it. Script paths
/// can be one of:
/// - <b>Absolute:</b> <i>fps/foo/bar.cs</i> Paths of this sort are passed
/// through.
/// - <b>Mod-relative:</b> <i>~/foo/bar.cs</i> Paths of this sort have their
/// replaced with the name of the current mod.
/// - <b>File-relative:</b> <i>./baz/blip.cs</i> Paths of this sort are
/// calculated relative to the path of the current scripting file.
///
/// @note This function determines paths relative to the currently executing
/// CodeBlock. Calling it outside of script execution will result in
/// it directly copying src to filename, since it won't know to what the
/// path is relative!
///
/// @param filename Pointer to string buffer to fill with absolute path.
/// @param size Size of buffer pointed to by filename.
/// @param src Original, possibly relative script path.
bool expandScriptFilename(char *filename, U32 size, const char *src);
/// Returns true if fn is a global scripting function.
///
/// This looks in the global namespace. It also checks to see if fn
/// is in the StringTable; if not, it returns false.
bool isFunction(const char *fn);
/// This is the basis for tab completion in the console.
///
/// @note This is an internally used function. You probably don't
/// care much about how this works.
///
/// This function does some basic parsing to try to ascertain the namespace in which
/// we are attempting to do tab completion, then bumps control off to the appropriate
/// tabComplete function, either in SimObject or Namespace.
///
/// @param inputBuffer Pointer to buffer containing starting data, or last result.
/// @param cursorPos Location of cursor in this buffer. This is used to indicate
/// what part of the string should be kept and what part should
/// be advanced to the next match if any.
/// @param maxResultLength Maximum amount of result data to put into inputBuffer. This
/// is capped by MaxCompletionBufferSize.
/// @param forwardTab Should we go forward to next match or backwards to previous
/// match? True indicates forward.
U32 tabComplete(char* inputBuffer, U32 cursorPos, U32 maxResultLength, bool forwardTab);
/// @}
/// @name Variable Management
/// @{
/// Add a console variable that references the value of a variable in C++ code.
///
/// If a value is assigned to the console variable the C++ variable is updated,
/// and vice-versa.
///
/// @param name Global console variable name to create
/// @param type The type of the C++ variable; see the ConsoleDynamicTypes enum for a complete list.
/// @param pointer Pointer to the variable.
/// @see ConsoleDynamicTypes
bool addVariable(const char *name, S32 type, void *pointer);
/// Remove a console variable.
///
/// @param name Global console variable name to remove
/// @return true if variable existed before removal.
bool removeVariable(const char *name);
/// Assign a string value to a locally scoped console variable
///
/// @note The context of the variable is determined by gEvalState; that is,
/// by the currently executing code.
///
/// @param name Local console variable name to set
/// @param value String value to assign to name
void setLocalVariable(const char *name, const char *value);
/// Retrieve the string value to a locally scoped console variable
///
/// @note The context of the variable is determined by gEvalState; that is,
/// by the currently executing code.
///
/// @param name Local console variable name to get
const char* getLocalVariable(const char* name);
/// @}
/// @name Global Variable Accessors
/// @{
/// Assign a string value to a global console variable
/// @param name Global console variable name to set
/// @param value String value to assign to this variable.
void setVariable(const char *name, const char *value);
/// Retrieve the string value of a global console variable
/// @param name Global Console variable name to query
/// @return The string value of the variable or "" if the variable does not exist.
const char* getVariable(const char* name);
/// Same as setVariable(), but for bools.
void setBoolVariable (const char* name,bool var);
/// Same as getVariable(), but for bools.
///
/// @param name Name of the variable.
/// @param def Default value to supply if no matching variable is found.
bool getBoolVariable (const char* name,bool def = false);
/// Same as setVariable(), but for ints.
void setIntVariable (const char* name,S32 var);
/// Same as getVariable(), but for ints.
///
/// @param name Name of the variable.
/// @param def Default value to supply if no matching variable is found.
S32 getIntVariable (const char* name,S32 def = 0);
/// Same as setVariable(), but for floats.
void setFloatVariable(const char* name,F32 var);
/// Same as getVariable(), but for floats.
///
/// @param name Name of the variable.
/// @param def Default value to supply if no matching variable is found.
F32 getFloatVariable(const char* name,F32 def = .0f);
/// @}
/// @name Global Function Registration
/// @{
/// Register a C++ function with the console making it a global function callable from the scripting engine.
///
/// @param name Name of the new function.
/// @param cb Pointer to the function implementing the scripting call; a console callback function returning a specific type value.
/// @param usage Documentation for this function. @ref console_autodoc
/// @param minArgs Minimum number of arguments this function accepts
/// @param maxArgs Maximum number of arguments this function accepts
void addCommand(const char *name, StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(const char *name, IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *name, FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *name, VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *name, BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
/// @}
/// @name Namespace Function Registration
/// @{
/// Register a C++ function with the console making it callable
/// as a method of the given namespace from the scripting engine.
///
/// @param nameSpace Name of the namespace to associate the new function with; this is usually the name of a class.
/// @param name Name of the new function.
/// @param cb Pointer to the function implementing the scripting call; a console callback function returning a specific type value.
/// @param usage Documentation for this function. @ref console_autodoc
/// @param minArgs Minimum number of arguments this function accepts
/// @param maxArgs Maximum number of arguments this function accepts
void addCommand(const char *nameSpace, const char *name,StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(const char *nameSpace, const char *name,IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *nameSpace, const char *name,FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *nameSpace, const char *name,VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *nameSpace, const char *name,BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
/// @}
/// @name Special Purpose Registration
///
/// These are special-purpose functions that exist to allow commands to be grouped, so
/// that when we generate console docs, they can be more meaningfully presented.
///
/// @ref console_autodoc "Click here for more information about console docs and grouping."
///
/// @{
void markCommandGroup (const char * nsName, const char *name, const char* usage=NULL);
void beginCommandGroup(const char * nsName, const char *name, const char* usage);
void endCommandGroup (const char * nsName, const char *name);
/// @deprecated
void addOverload (const char * nsName, const char *name, const char *altUsage);
/// @}
/// @name Console Output
///
/// These functions process the formatted string and pass it to all the ConsumerCallbacks that are
/// currently registered. The console log file and the console window callbacks are installed by default.
///
/// @see addConsumer()
/// @see removeConsumer()
/// @{
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
void printf(const char *_format, ...);
/// @note The console window colors warning text as LIGHT GRAY.
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
void warnf(const char *_format, ...);
/// @note The console window colors warning text as RED.
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
void errorf(const char *_format, ...);
/// @note The console window colors warning text as LIGHT GRAY.
/// @param type Allows you to associate the warning message with an internal module.
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
/// @see Con::warnf()
void warnf(ConsoleLogEntry::Type type, const char *_format, ...);
/// @note The console window colors warning text as RED.
/// @param type Allows you to associate the warning message with an internal module.
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
/// @see Con::errorf()
void errorf(ConsoleLogEntry::Type type, const char *_format, ...);
/// @}
/// Returns true when called from the main thread, false otherwise
bool isMainThread();
/// @name Console Execution
///
/// These are functions relating to the execution of script code.
///
/// @{
/// Call a script function from C/C++ code.
///
/// @param argc Number of elements in the argv parameter
/// @param argv A character string array containing the name of the function
/// to call followed by the arguments to that function.
/// @code
/// // Call a Torque script function called mAbs, having one parameter.
/// char* argv[] = {"abs", "-9"};
/// char* result = execute(2, argv);
/// @endcode
const char *execute(S32 argc, const char* argv[]);
/// @see execute(S32 argc, const char* argv[])
const char *executef(S32 argc, ...);
/// Call a Torque Script member function of a SimObject from C/C++ code.
/// @param object Object on which to execute the method call.
/// @param argc Number of elements in the argv parameter (must be >2, see argv)
/// @param argv A character string array containing the name of the member function
/// to call followed by an empty parameter (gets filled with object ID)
/// followed by arguments to that function.
/// @code
/// // Call the method setMode() on an object, passing it one parameter.
///
/// char* argv[] = {"setMode", "", "2"};
/// char* result = execute(mysimobject, 3, argv);
/// @endcode
const char *execute(SimObject *object, S32 argc, const char *argv[]);
/// @see execute(SimObject *, S32 argc, const char *argv[])
const char *executef(SimObject *, S32 argc, ...);
/// Evaluate an arbitrary chunk of code.
///
/// @param string Buffer containing code to execute.
/// @param echo Should we echo the string to the console?
/// @param fileName Indicate what file this code is coming from; used in error reporting and such.
const char *evaluate(const char* string, bool echo = false, const char *fileName = NULL);
/// Evaluate an arbitrary line of script.
///
/// This wraps dVsprintf(), so you can substitute parameters into the code being executed.
const char *evaluatef(const char* string, ...);
/// @}
/// @name Console Function Implementation Helpers
///
/// The functions Con::getIntArg, Con::getFloatArg and Con::getArgBuffer(size) are used to
/// allocate on the console stack string variables that will be passed into the next console
// function called. This allows the console to avoid copying some data.
///
/// getReturnBuffer lets you allocate stack space to return data in.
/// @{
///
char *getReturnBuffer(U32 bufferSize);
char *getArgBuffer(U32 bufferSize);
char *getFloatArg(F64 arg);
char *getIntArg (S32 arg);
/// @}
/// @name Namespaces
/// @{
Namespace *lookupNamespace(const char *nsName);
bool linkNamespaces(const char *parentName, const char *childName);
/// @note This should only be called from consoleObject.h
bool classLinkNamespaces(Namespace *parent, Namespace *child);
/// @}
/// @name Logging
/// @{
void getLockLog(ConsoleLogEntry * &log, U32 &size);
void unlockLog(void);
void setLogMode(S32 mode);
/// @}
/// @name Dynamic Type System
/// @{
///
/* void registerType( const char *typeName, S32 type, S32 size, GetDataFunction gdf, SetDataFunction sdf, bool isDatablockType = false );
void registerType( const char* typeName, S32 type, S32 size, bool isDatablockType = false );
void registerTypeGet( S32 type, GetDataFunction gdf );
void registerTypeSet( S32 type, SetDataFunction sdf );
const char *getTypeName(S32 type);
bool isDatablockType( S32 type ); */
void setData(S32 type, void *dptr, S32 index, S32 argc, const char **argv, EnumTable *tbl = NULL, BitSet32 flag = 0);
const char *getData(S32 type, void *dptr, S32 index, EnumTable *tbl = NULL, BitSet32 flag = 0);
/// @}
};
extern void expandEscape(char *dest, const char *src);
extern bool collapseEscape(char *buf);
extern S32 HashPointer(StringTableEntry ptr);
/// This is the backend for the ConsoleMethod()/ConsoleFunction() macros.
///
/// See the group ConsoleConstructor Innards for specifics on how this works.
///
/// @see @ref console_autodoc
/// @nosubgrouping
class ConsoleConstructor
{
public:
/// @name Entry Type Fields
///
/// One of these is set based on the type of entry we want
/// inserted in the console.
///
/// @ref console_autodoc
/// @{
StringCallback sc; ///< A function/method that returns a string.
IntCallback ic; ///< A function/method that returns an int.
FloatCallback fc; ///< A function/method that returns a float.
VoidCallback vc; ///< A function/method that returns nothing.
BoolCallback bc; ///< A function/method that returns a bool.
bool group; ///< Indicates that this is a group marker.
bool overload; ///< Indicates that this is an overload marker.
/// @deprecated Unused.
/// @}
/// Minimum/maximum number of arguments for the function.
S32 mina, maxa;
const char *usage; ///< Usage string.
const char *funcName; ///< Function name.
const char *className; ///< Class name.
/// @name ConsoleConstructer Innards
///
/// The ConsoleConstructor class is used as the backend for the ConsoleFunction() and
/// ConsoleMethod() macros. The way it works takes advantage of several properties of
/// C++.
///
/// The ConsoleFunction()/ConsoleMethod() macros wrap the declaration of a ConsoleConstructor.
///
/// @code
/// // The definition of a ConsoleFunction using the macro
/// ConsoleFunction(ExpandFilename, const char*, 2, 2, "(string filename)")
/// {
/// argc;
/// char* ret = Con::getReturnBuffer( 1024 );
/// Con::expandScriptFilename(ret, 1024, argv[1]);
/// return ret;
/// }
///
/// // Resulting code
/// static const char* cExpandFilename(SimObject *, S32, const char **argv);
/// static ConsoleConstructor
/// gExpandFilenameobj(NULL,"ExpandFilename", cExpandFilename,
/// "(string filename)", 2, 2);
/// static const char* cExpandFilename(SimObject *, S32 argc, const char **argv)
/// {
/// argc;
/// char* ret = Con::getReturnBuffer( 1024 );
/// Con::expandScriptFilename(ret, 1024, argv[1]);
/// return ret;
/// }
///
/// // A similar thing happens when you do a ConsoleMethod.
/// @endcode
///
/// As you can see, several global items are defined when you use the ConsoleFunction method.
/// The macro constructs the name of these items from the parameters you passed it. Your
/// implementation of the console function is is placed in a function with a name based on
/// the actual name of the console funnction. In addition, a ConsoleConstructor is declared.
///
/// Because it is defined as a global, the constructor for the ConsoleConstructor is called
/// before execution of main() is started. The constructor is called once for each global
/// ConsoleConstructor variable, in the order in which they were defined (this property only holds true
/// within file scope).
///
/// We have ConsoleConstructor create a linked list at constructor time, by storing a static
/// pointer to the head of the list, and keeping a pointer to the next item in each instance
/// of ConsoleConstructor. init() is a helper function in this process, automatically filling
/// in commonly used fields and updating first and next as needed. In this way, a list of
/// items to add to the console is assemble in memory, ready for use, before we start
/// execution of the program proper.
///
/// In Con::init(), ConsoleConstructor::setup() is called to process this prepared list. Each
/// item in the list is iterated over, and the appropriate Con namespace functions (usually
/// Con::addCommand) are invoked to register the ConsoleFunctions and ConsoleMethods in
/// the appropriate namespaces.
///
/// @see Namespace
/// @see Con
/// @{
ConsoleConstructor *next;
static ConsoleConstructor *first;
void init(const char *cName, const char *fName, const char *usg, S32 minArgs, S32 maxArgs);
static void setup();
/// @}
/// @name Basic Console Constructors
/// @{
ConsoleConstructor(const char *className, const char *funcName, StringCallback sfunc, const char* usage, S32 minArgs, S32 maxArgs);
ConsoleConstructor(const char *className, const char *funcName, IntCallback ifunc, const char* usage, S32 minArgs, S32 maxArgs);
ConsoleConstructor(const char *className, const char *funcName, FloatCallback ffunc, const char* usage, S32 minArgs, S32 maxArgs);
ConsoleConstructor(const char *className, const char *funcName, VoidCallback vfunc, const char* usage, S32 minArgs, S32 maxArgs);
ConsoleConstructor(const char *className, const char *funcName, BoolCallback bfunc, const char* usage, S32 minArgs, S32 maxArgs);
/// @}
/// @name Magic Console Constructors
///
/// These perform various pieces of "magic" related to consoleDoc functionality.
/// @ref console_autodoc
/// @{
/// Indicates a group marker. (A doxygen illusion)
///
/// @see Con::markCommandGroup
/// @ref console_autodoc
ConsoleConstructor(const char *className, const char *groupName, const char* usage);
/// @}
};
/// @name Global Console Definition Macros
///
/// @note If TORQUE_DEBUG is defined, then we gather documentation information, and
/// do some extra sanity checks.
///
/// @see ConsoleConstructor
/// @ref console_autodoc
/// @{
// O hackery of hackeries
#define conmethod_return_const return (const
#define conmethod_return_S32 return (S32
#define conmethod_return_F32 return (F32
#define conmethod_nullify(val)
#define conmethod_return_void conmethod_nullify(void
#define conmethod_return_bool return (bool
#if !defined(TORQUE_SHIPPING)
// Console function macros
# define ConsoleFunctionGroupBegin(groupName, usage) \
static ConsoleConstructor gConsoleFunctionGroup##groupName##__GroupBegin(NULL,#groupName,usage);
# define ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) \
static returnType c##name(SimObject *, S32, const char **argv); \
static ConsoleConstructor g##name##obj(NULL,#name,c##name,usage1,minArgs,maxArgs); \
static returnType c##name(SimObject *, S32 argc, const char **argv)
# define ConsoleFunctionGroupEnd(groupName) \
static ConsoleConstructor gConsoleFunctionGroup##groupName##__GroupEnd(NULL,#groupName,NULL);
// Console method macros
# define ConsoleMethodGroupBegin(className, groupName, usage) \
static ConsoleConstructor className##groupName##__GroupBegin(#className,#groupName,usage);
# define ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage1) \
static inline returnType c##className##name(className *, S32, const char **argv); \
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
AssertFatal( dynamic_cast<className*>( object ), "Object passed to " #name " is not a " #className "!" ); \
conmethod_return_##returnType ) c##className##name(static_cast<className*>(object),argc,argv); \
}; \
static ConsoleConstructor className##name##obj(#className,#name,c##className##name##caster,usage1,minArgs,maxArgs); \
static inline returnType c##className##name(className *object, S32 argc, const char **argv)
# define ConsoleStaticMethod(className,name,returnType,minArgs,maxArgs,usage1) \
static inline returnType c##className##name(S32, const char **); \
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
conmethod_return_##returnType ) c##className##name(argc,argv); \
}; \
static ConsoleConstructor \
className##name##obj(#className,#name,c##className##name##caster,usage1,minArgs,maxArgs); \
static inline returnType c##className##name(S32 argc, const char **argv)
# define ConsoleMethodGroupEnd(className, groupName) \
static ConsoleConstructor className##groupName##__GroupEnd(#className,#groupName,NULL);
#else
// These do nothing if we don't want doc information.
# define ConsoleFunctionGroupBegin(groupName, usage)
# define ConsoleFunctionGroupEnd(groupName)
# define ConsoleMethodGroupBegin(className, groupName, usage)
# define ConsoleMethodGroupEnd(className, groupName)
// These are identical to what's above, we just want to null out the usage strings.
# define ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) \
static returnType c##name(SimObject *, S32, const char **); \
static ConsoleConstructor g##name##obj(NULL,#name,c##name,"",minArgs,maxArgs);\
static returnType c##name(SimObject *, S32 argc, const char **argv)
# define ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage1) \
static inline returnType c##className##name(className *, S32, const char **argv); \
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
conmethod_return_##returnType ) c##className##name(static_cast<className*>(object),argc,argv); \
}; \
static ConsoleConstructor \
className##name##obj(#className,#name,c##className##name##caster,"",minArgs,maxArgs); \
static inline returnType c##className##name(className *object, S32 argc, const char **argv)
# define ConsoleStaticMethod(className,name,returnType,minArgs,maxArgs,usage1) \
static inline returnType c##className##name(S32, const char **); \
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
conmethod_return_##returnType ) c##className##name(argc,argv); \
}; \
static ConsoleConstructor \
className##name##obj(#className,#name,c##className##name##caster,"",minArgs,maxArgs); \
static inline returnType c##className##name(S32 argc, const char **argv)
#endif
/// @}
#endif

378
engine/console/consoleDoc.cc Executable file
View File

@ -0,0 +1,378 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/console.h"
#include "console/ast.h"
#include "core/tAlgorithm.h"
#include "core/resManager.h"
#include "core/findMatch.h"
#include "console/consoleInternal.h"
#include "console/consoleObject.h"
#include "core/fileStream.h"
#include "console/compiler.h"
//--- Information pertaining to this page... ------------------
/// @file
///
/// For specifics on using the consoleDoc functionality, see @ref console_autodoc
ConsoleFunctionGroupBegin(ConsoleDoc, "Console self-documentation functions. These output psuedo C++ suitable for feeeding through Doxygen or another auto documentation tool.");
ConsoleFunction(dumpConsoleClasses, void, 1, 1, "() dumps all declared console classes to the console.")
{
Namespace::dumpClasses();
}
ConsoleFunction(dumpConsoleFunctions, void, 1, 1, "() dumps all declared console functions to the console.")
{
Namespace::dumpFunctions();
}
ConsoleFunctionGroupEnd(ConsoleDoc);
/// Helper table to convert type ids to human readable names.
const char *typeNames[] =
{
"Script",
"string",
"int",
"float",
"void",
"bool",
"",
"",
"unknown_overload"
};
void printClassHeader(const char * className, const char * superClassName, const bool stub)
{
if(stub)
{
Con::printf("/// Stub class");
Con::printf("/// ");
Con::printf("/// @note This is a stub class to ensure a proper class hierarchy. No ");
Con::printf("/// information was available for this class.");
}
// Print out appropriate class header
if(superClassName)
Con::printf("class %s : public %s {", className, superClassName ? superClassName : "");
else if(!className)
Con::printf("namespace Global {");
else
Con::printf("class %s {", className);
if(className)
Con::printf(" public:");
}
void printClassMethod(const bool isVirtual, const char *retType, const char *methodName, const char* args, const char*usage)
{
if(usage && usage[0] != ';' && usage[0] != 0)
Con::printf(" /*! %s */", usage);
Con::printf(" %s%s %s(%s) {}", isVirtual ? "virtual " : "", retType, methodName, args);
}
void printGroupStart(const char * aName, const char * aDocs)
{
Con::printf("");
Con::printf(" /*! @name %s", aName);
if(aDocs)
{
Con::printf(" ");
Con::printf(" %s", aDocs);
}
Con::printf(" @{ */");
}
void printClassMember(const bool isDeprec, const char * aType, const char * aName, const char * aDocs)
{
Con::printf(" /*!");
if(aDocs)
{
Con::printf(" %s", aDocs);
Con::printf(" ");
}
if(isDeprec)
Con::printf(" @deprecated This member is deprecated, which means that its value is always undefined.");
Con::printf(" */");
Con::printf(" %s %s;", isDeprec ? "deprecated" : aType, aName);
}
void printGroupEnd()
{
Con::printf(" /// @}");
Con::printf("");
}
void printClassFooter()
{
Con::printf("};");
Con::printf("");
}
void Namespace::printNamespaceEntries(Namespace * g)
{
static bool inGroup = false;
// Go through all the entries.
// Iterate through the methods of the namespace...
for(Entry *ewalk = g->mEntryList; ewalk; ewalk = ewalk->mNext)
{
char buffer[1024]; //< This will bite you in the butt someday.
int eType = ewalk->mType;
const char * funcName = ewalk->mFunctionName;
// If it's a function
if(eType >= Entry::ScriptFunctionType || eType == Entry::OverloadMarker)
{
if(eType==Entry::OverloadMarker)
{
// Deal with crap from the OverloadMarker case.
// It has no type information so we have to "correct" its type.
// Find the original
eType = 8;
for(Entry *eseek = g->mEntryList; eseek; eseek = eseek->mNext)
{
if(!dStrcmp(eseek->mFunctionName, ewalk->cb.mGroupName))
{
eType = eseek->mType;
break;
}
}
// And correct the name
funcName = ewalk->cb.mGroupName;
}
// A quick note - if your usage field starts with a (, then it's auto-integrated into
// the script docs! Use this HEAVILY!
// We add some heuristics here as well. If you're of the form:
// *.methodName(*)
// then we will also extract parameters.
const char *use = ewalk->mUsage ? ewalk->mUsage : "";
const char *bgn = dStrchr(use, '(');
const char *end = dStrchr(use, ')');
const char *dot = dStrchr(use, '.');
if(use[0] == '(')
{
if(!end)
end = use + 1;
use++;
U32 len = end - use;
dStrncpy(buffer, use, len);
buffer[len] = 0;
printClassMethod(true, typeNames[eType], funcName, buffer, end+1);
continue; // Skip to next one.
}
// We check to see if they're giving a prototype.
if(dot && bgn && end) // If there's two parentheses, and a dot...
if(dot < bgn && bgn < end) // And they're in the order dot, bgn, end...
{
use++;
U32 len = end - bgn - 1;
dStrncpy(buffer, bgn+1, len);
buffer[len] = 0;
// Then let's do the heuristic-trick
printClassMethod(true, typeNames[eType], funcName, buffer, end+1);
continue; // Get to next item.
}
// Finally, see if they did it foo(*) style.
char* func_pos = dStrstr(use, funcName);
if((func_pos) && (func_pos < bgn) && (end > bgn))
{
U32 len = end - bgn - 1;
dStrncpy(buffer, bgn+1, len);
buffer[len] = 0;
printClassMethod(true, typeNames[eType], funcName, buffer, end+1);
continue;
}
// Default...
printClassMethod(true, typeNames[eType], funcName, "", ewalk->mUsage);
}
else if(ewalk->mType == Entry::GroupMarker)
{
if(!inGroup)
printGroupStart(ewalk->cb.mGroupName, ewalk->mUsage);
else
printGroupEnd();
inGroup = !inGroup;
}
else if(ewalk->mFunctionOffset) // If it's a builtin function...
{
ewalk->mCode->getFunctionArgs(buffer, ewalk->mFunctionOffset);
printClassMethod(false, typeNames[ewalk->mType], ewalk->mFunctionName, buffer, "");
}
else
{
Con::printf(" // got an unknown thing?? %d", ewalk->mType );
}
}
}
void Namespace::dumpClasses()
{
Vector<Namespace *> vec;
trashCache();
// We use mHashSequence to mark if we have traversed...
// so mark all as zero to start.
for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext)
walk->mHashSequence = 0;
for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext)
{
Vector<Namespace *> stack;
// Get all the parents of this namespace... (and mark them as we go)
Namespace *parentWalk = walk;
while(parentWalk)
{
if(parentWalk->mHashSequence != 0)
break;
if(parentWalk->mPackage == 0)
{
parentWalk->mHashSequence = 1; // Mark as traversed.
stack.push_back(parentWalk);
}
parentWalk = parentWalk->mParent;
}
// Load stack into our results vector.
while(stack.size())
{
vec.push_back(stack[stack.size() - 1]);
stack.pop_back();
}
}
// Go through previously discovered classes
U32 i;
for(i = 0; i < vec.size(); i++)
{
const char *className = vec[i]->mName;
const char *superClassName = vec[i]->mParent ? vec[i]->mParent->mName : NULL;
// Skip the global namespace, that gets dealt with in dumpFunctions
if(!className) continue;
// If we hit a class with no members and no classRep, do clever filtering.
if(vec[i]->mEntryList == NULL && vec[i]->mClassRep == NULL)
{
// Print out a short stub so we get a proper class hierarchy.
if(superClassName) { // Filter hack; we don't want non-inheriting classes...
printClassHeader(className,superClassName, true);
printClassFooter();
}
continue;
}
// Print the header for the class..
printClassHeader(className, superClassName, false);
// Deal with entries.
printNamespaceEntries(vec[i]);
// Deal with the classRep (to get members)...
AbstractClassRep *rep = vec[i]->mClassRep;
AbstractClassRep::FieldList emptyList;
AbstractClassRep::FieldList *parentList = &emptyList;
AbstractClassRep::FieldList *fieldList = &emptyList;
if(rep)
{
// Get information about the parent's fields...
AbstractClassRep *parentRep = vec[i]->mParent ? vec[i]->mParent->mClassRep : NULL;
if(parentRep)
parentList = &(parentRep->mFieldList);
// Get information about our fields
fieldList = &(rep->mFieldList);
// Go through all our fields...
for(U32 j = 0; j < fieldList->size(); j++)
{
switch((*fieldList)[j].type)
{
case AbstractClassRep::StartGroupFieldType:
printGroupStart((*fieldList)[j].pGroupname, (*fieldList)[j].pFieldDocs);
break;
case AbstractClassRep::EndGroupFieldType:
printGroupEnd();
break;
default:
case AbstractClassRep::DepricatedFieldType:
{
bool isDeprecated = ((*fieldList)[j].type == AbstractClassRep::DepricatedFieldType);
if(isDeprecated)
{
printClassMember(
true,
"<deprecated>",
(*fieldList)[j].pFieldname,
(*fieldList)[j].pFieldDocs
);
}
else
{
ConsoleBaseType *cbt = ConsoleBaseType::getType((*fieldList)[j].type);
printClassMember(
false,
cbt ? cbt->getTypeClassName() : "<unknown>",
(*fieldList)[j].pFieldname,
(*fieldList)[j].pFieldDocs
);
}
}
}
}
}
// Close the class/namespace.
printClassFooter();
}
}
void Namespace::dumpFunctions()
{
// Get the global namespace.
Namespace* g = find(NULL); //->mParent;
printClassHeader(NULL,NULL, false);
while(g)
{
printNamespaceEntries(g);
g = g->mParent;
}
printClassFooter();
}

176
engine/console/consoleDoc.h Executable file
View File

@ -0,0 +1,176 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
// This file exists solely to document consoleDoc.cc
/// @page console_autodoc Console Auto-Documentation
///
/// @see consoleDoc.cc
///
/// @section console_autodoc_using Using Console Auto-Documentation
///
/// There are on the order of three hundred functions exposed to the script language
/// through the console. It is therefore extremely important that they be documented,
/// but due to their number, it is difficult to maintain a seperate reference document.
///
/// Therefore, a simple documentation system has been built into the scripting engine. It
/// was initially added by Mark Frohnmayer, and later enhanced by Ben Garney. The
/// scripting engine supports grouping functions and methods, to help organize the
/// several hundred functions, as well as associating a "usage string" with functions and
/// groups.
///
/// @note The results of a console doc dump will vary depending on when you run it. If
/// you run it, for example, while in the game menu, it won't output any data for
/// the script-defined classes which are defined for gameplay. To get comprehensive
/// documentation, you may need to write a special script that will get all your
/// classes loaded into the scripting engine.
///
/// The console documentation system is designed to output a dump of the current state
/// of the scripting engine in a format understandable by Doxygen. It does this by
/// traversing the namespace/class hierarchy in memory at the time of the dump, and
/// outputting psuedo-C++ code equivalent to this class hierarchy.
///
/// @subsection console_autodoc_using_script For the Scripter...
///
/// Currently, there is no way to associate usage strings or other documentation with script code
/// like you can with C++ code.
///
/// You can get a list of all the methods and fields of an object from any object which inherits
/// from SimObject (ie, every object), as well as the documentation on those objects by using the
/// dump() method from the console:
///
/// @code
/// ==>$foo = new SimObject();
/// ==>$foo.dump();
/// Member Fields:
/// Tagged Fields:
/// Methods:
/// delete() - obj.delete()
/// dump() - obj.dump()
/// getClassName() - obj.getClassName()
/// getGroup() - obj.getGroup()
/// getId() - obj.getId()
/// getName() - obj.getName()
/// getType() - obj.getType()
/// save() - obj.save(fileName, <selectedOnly>)
/// schedule() - object.schedule(time, command, <arg1...argN>);
/// setName() - obj.setName(newName)
/// @endcode
///
/// In the Torque example app, there are two functions defined in common\\client\\scriptDoc.cs
/// which automate the process of dumping the documentation. They make use of the ConsoleLogger
/// object to output the documentation to a file, and look like this:
///
/// @note You may want to add this code, or code like it, to your project if you have
/// rewritten the script code in common.
///
/// @code
/// // Writes out all script functions to a file
/// function writeOutFunctions() {
/// new ConsoleLogger( logger, "scriptFunctions.txt", false );
/// dumpConsoleFunctions();
/// logger.delete();
/// }
///
/// // Writes out all script classes to a file
/// function writeOutClasses() {
/// new ConsoleLogger( logger, "scriptClasses.txt", false );
/// dumpConsoleClasses();
/// logger.delete();
/// }
/// @endcode
///
/// @subsection console_autodoc_using_coder For the C++ Coder...
///
/// @note <b>It is of the utmost important that you keep your usage strings up to date!</b>
/// Usage strings are the only way that a scripter has to know how to use the methods,
/// functions, and variables you expose. Misleading, missing, or out of date documentation
/// will make their lives much harder - and yours, too, because you'll have to keep
/// explaining things to them! So make everyone's lives easier - keep your usage strings
/// clear, concise, and up to date.
///
/// There are four types of items which can be documented using the autodocumentation system:
/// - <b>Fields</b>, which are defined using the addField() calls. They are documented
/// by passing a string to the usage parameter.
/// - <b>Field groups</b>, which are defined using the beginGroup() and endGroup() calls.
/// They are documented by passing a descriptive string to the usage parameter.
/// - <b>Method groups</b>, which are defined using beginCommandGroup(), endCommandGroup(),
/// ConsoleMethodGroupEnd(), ConsoleMethodGroupBegin(), ConsoleFunctionGroupEnd(), and
/// ConsoleFunctionGroupBegin().
/// - <b>Methods and functions</b>, which are defined using either SimObject::addCommand(),
/// the ConsoleMethod() macro, or the ConsoleFunction() macro. Methods and functions are
/// special in that the usage strings should be in a specific format, so
/// that parameter information can be extracted from them and placed into the Doxygen
/// output.
///
/// You can use standard Doxygen commands in your comments, to make the documentation clearer.
/// Of particular use are \@returns, \@param, \@note, and \@deprecated.
///
/// <b>Examples using global definitions.</b>
///
/// @code
/// // Example of using Doxygen commands.
/// ConsoleFunction(alxGetWaveLen, S32, 2, 2, "(string filename)"
/// "Get length of a wave file\n\n"
/// "@param filename File to determine length of.\n"
/// "@returns Length in milliseconds.")
///
/// // A function group...
/// ConsoleFunctionGroupBegin(Example, "This is an example group! Notice that the name for the group"
/// "must be a valid identifier, due to limitations in the C preprocessor.");
///
/// // ConsoleFunction definitions go here.
///
/// ConsoleFunctionGroupEnd(Example);
///
/// // You can do similar things with methods...
/// ConsoleMethodGroupBegin(SimSet, UsefulFuncs, "Here are some useful functions involving a SimSet.");
/// ConsoleMethod(SimSet, listObjects, void, 2, 2, "set.listObjects();")
/// ConsoleMethodGroupEnd(SimSet, UsefulFuncs, "Here are some more useful functions involving a SimSet.");
/// @endcode
///
/// <b>Examples using addField</b>
///
/// @note Using addCommand is strongly deprecated.
///
/// @code
/// // Example of a field group.
/// addGroup( "Logging", "Things relating to logging." );
/// addField( "level", TypeEnum, Offset( mLevel, ConsoleLogger ), 1, &gLogLevelTable );
/// endGroup( "Logging" );
/// @endcode
///
/// @section console_autodoc_makingdocs How to Generate Console Docs
///
/// Console docs can be generated by running the dumpConsoleFunctions() and
/// dumpConsoleClasses(), then running the output through Doxygen. There is an
/// example Doxygen configuration file to do this in HEAD,
/// at doc\\doxygen\\html\\script_doxygen.html.cfg. Doxygen will parse the psuedo-C++
/// generated by the console doc code and produce a class hierarchy and documentation
/// of the global namespace. You may need to tweak the paths in the configuration file
/// slightly to reflect your individual setup.
///
/// @section console_autodoc_internals Console Auto-Documentation Internals
///
/// The consoleDoc system works by inserting "hidden" entries in Namespace and
/// AbstractClassRep; these hidden entries are assigned special type IDs so that
/// they aren't touched by the standard name resolution code. At documentation
/// creation time, the dumpConsole functions iterate through the Namespace hierarchy
/// and the AbstractClassRep data and extract this "hidden" information, outputting
/// it in a Doxygen-compatible format.
///
/// @note You can customize the output of the console documentation system by modifying
/// these functions:
/// - printClassHeader()
/// - printClassMethod()
/// - printGroupStart()
/// - printClassMember()
/// - printGroupEnd()
/// - printClassFooter()
///
/// @note There was once support for 'overloaded' script functions; ie, script functions
/// with multiple usage strings. Certain functions in the audio library used this.
/// However, it was deemed too complex, and removed from the scripting engine. There
/// are still some latent traces of it, however.

1270
engine/console/consoleFunctions.cc Executable file

File diff suppressed because it is too large Load Diff

1064
engine/console/consoleInternal.cc Executable file

File diff suppressed because it is too large Load Diff

310
engine/console/consoleInternal.h Executable file
View File

@ -0,0 +1,310 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONSOLEINTERNAL_H_
#define _CONSOLEINTERNAL_H_
#ifndef _STRINGTABLE_H_
#include "core/stringTable.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
class ExprEvalState;
struct FunctionDecl;
class CodeBlock;
class AbstractClassRep;
class Namespace
{
enum {
MaxActivePackages = 512,
};
static U32 mNumActivePackages;
static U32 mOldNumActivePackages;
static StringTableEntry mActivePackages[MaxActivePackages];
public:
StringTableEntry mName;
StringTableEntry mPackage;
Namespace *mParent;
Namespace *mNext;
AbstractClassRep *mClassRep;
U32 mRefCountToParent;
struct Entry
{
enum {
GroupMarker = -3,
OverloadMarker = -2,
InvalidFunctionType = -1,
ScriptFunctionType,
StringCallbackType,
IntCallbackType,
FloatCallbackType,
VoidCallbackType,
BoolCallbackType
};
Namespace *mNamespace;
Entry *mNext;
StringTableEntry mFunctionName;
S32 mType;
S32 mMinArgs;
S32 mMaxArgs;
const char *mUsage;
StringTableEntry mPackage;
CodeBlock *mCode;
U32 mFunctionOffset;
union {
StringCallback mStringCallbackFunc;
IntCallback mIntCallbackFunc;
VoidCallback mVoidCallbackFunc;
FloatCallback mFloatCallbackFunc;
BoolCallback mBoolCallbackFunc;
const char* mGroupName;
} cb;
Entry();
void clear();
const char *execute(S32 argc, const char **argv, ExprEvalState *state);
};
Entry *mEntryList;
Entry **mHashTable;
U32 mHashSize;
U32 mHashSequence; ///< @note The hash sequence is used by the autodoc console facility
/// as a means of testing reference state.
Namespace();
void addFunction(StringTableEntry name, CodeBlock *cb, U32 functionOffset);
void addCommand(StringTableEntry name,StringCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(StringTableEntry name,IntCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(StringTableEntry name,FloatCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(StringTableEntry name,VoidCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(StringTableEntry name,BoolCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addOverload(const char *name, const char* altUsage);
void markGroup(const char* name, const char* usage);
char * lastUsage;
void getEntryList(Vector<Entry *> *);
Entry *lookup(StringTableEntry name);
Entry *lookupRecursive(StringTableEntry name);
Entry *createLocalEntry(StringTableEntry name);
void buildHashTable();
void clearEntries();
bool classLinkTo(Namespace *parent);
const char *tabComplete(const char *prevText, S32 baseLen, bool fForward);
static U32 mCacheSequence;
static DataChunker mCacheAllocator;
static DataChunker mAllocator;
static void trashCache();
static Namespace *mNamespaceList;
static Namespace *mGlobalNamespace;
static void init();
static void shutdown();
static Namespace *global();
static Namespace *find(StringTableEntry name, StringTableEntry package=NULL);
static void activatePackage(StringTableEntry name);
static void deactivatePackage(StringTableEntry name);
static void dumpClasses();
static void dumpFunctions();
static void printNamespaceEntries(Namespace * g);
static void unlinkPackages();
static void relinkPackages();
static bool isPackage(StringTableEntry name);
};
extern char *typeValueEmpty;
class Dictionary
{
public:
struct Entry
{
enum
{
TypeInternalInt = -3,
TypeInternalFloat = -2,
TypeInternalString = -1,
};
StringTableEntry name;
Entry *nextEntry;
S32 type;
char *sval;
U32 ival; // doubles as strlen when type = -1
F32 fval;
U32 bufferLen;
void *dataPtr;
Entry(StringTableEntry name);
~Entry();
U32 getIntValue()
{
if(type <= TypeInternalString)
return ival;
else
return dAtoi(Con::getData(type, dataPtr, 0));
}
F32 getFloatValue()
{
if(type <= TypeInternalString)
return fval;
else
return dAtof(Con::getData(type, dataPtr, 0));
}
const char *getStringValue()
{
if(type == TypeInternalString)
return sval;
if(type == TypeInternalFloat)
return Con::getData(TypeF32, &fval, 0);
else if(type == TypeInternalInt)
return Con::getData(TypeS32, &ival, 0);
else
return Con::getData(type, dataPtr, 0);
}
void setIntValue(U32 val)
{
if(type <= TypeInternalString)
{
fval = (F32)val;
ival = val;
if(sval != typeValueEmpty)
{
dFree(sval);
sval = typeValueEmpty;
}
type = TypeInternalInt;
return;
}
else
{
const char *dptr = Con::getData(TypeS32, &val, 0);
Con::setData(type, dataPtr, 0, 1, &dptr);
}
}
void setFloatValue(F32 val)
{
if(type <= TypeInternalString)
{
fval = val;
ival = static_cast<U32>(val);
if(sval != typeValueEmpty)
{
dFree(sval);
sval = typeValueEmpty;
}
type = TypeInternalFloat;
return;
}
else
{
const char *dptr = Con::getData(TypeF32, &val, 0);
Con::setData(type, dataPtr, 0, 1, &dptr);
}
}
void setStringValue(const char *value);
};
private:
struct HashTableData
{
Dictionary* owner;
S32 size;
S32 count;
Entry **data;
};
HashTableData *hashTable;
ExprEvalState *exprState;
public:
StringTableEntry scopeName;
Namespace *scopeNamespace;
CodeBlock *code;
U32 ip;
Dictionary();
Dictionary(ExprEvalState *state, Dictionary* ref=NULL);
~Dictionary();
Entry *lookup(StringTableEntry name);
Entry *add(StringTableEntry name);
void setState(ExprEvalState *state, Dictionary* ref=NULL);
void remove(Entry *);
void reset();
void exportVariables(const char *varString, const char *fileName, bool append);
void deleteVariables(const char *varString);
void setVariable(StringTableEntry name, const char *value);
const char *getVariable(StringTableEntry name, bool *valid = NULL);
void addVariable(const char *name, S32 type, void *dataPtr);
bool removeVariable(StringTableEntry name);
/// Return the best tab completion for prevText, with the length
/// of the pre-tab string in baseLen.
const char *tabComplete(const char *prevText, S32 baseLen, bool);
};
class ExprEvalState
{
public:
/// @name Expression Evaluation
/// @{
///
SimObject *thisObject;
Dictionary::Entry *currentVariable;
bool traceOn;
ExprEvalState();
~ExprEvalState();
/// @}
/// @name Stack Management
/// @{
///
Dictionary globalVars;
Vector<Dictionary *> stack;
void setCurVarName(StringTableEntry name);
void setCurVarNameCreate(StringTableEntry name);
S32 getIntVariable();
F64 getFloatVariable();
const char *getStringVariable();
void setIntVariable(S32 val);
void setFloatVariable(F64 val);
void setStringVariable(const char *str);
void pushFrame(StringTableEntry frameName, Namespace *ns);
void popFrame();
/// Puts a reference to an existing stack frame
/// on the top of the stack.
void pushFrameRef(S32 stackIndex);
/// @}
};
#endif

215
engine/console/consoleLogger.cc Executable file
View File

@ -0,0 +1,215 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/consoleLogger.h"
#include "console/consoleTypes.h"
Vector<ConsoleLogger *> ConsoleLogger::mActiveLoggers;
bool ConsoleLogger::smInitialized = false;
IMPLEMENT_CONOBJECT( ConsoleLogger );
//-----------------------------------------------------------------------------
ConsoleLogger::ConsoleLogger()
{
mFilename = NULL;
mLogging = false;
mAppend = false;
}
//-----------------------------------------------------------------------------
ConsoleLogger::ConsoleLogger( const char *fileName, bool append )
{
mLogging = false;
mLevel = ConsoleLogEntry::Normal;
mFilename = StringTable->insert( fileName );
mAppend = append;
init();
}
//-----------------------------------------------------------------------------
static EnumTable::Enums logLevelEnums[] =
{
{ ConsoleLogEntry::Normal, "normal" },
{ ConsoleLogEntry::Warning, "warning" },
{ ConsoleLogEntry::Error, "error" },
};
static EnumTable gLogLevelTable( 3, &logLevelEnums[0] );
void ConsoleLogger::initPersistFields()
{
Parent::initPersistFields();
addGroup( "Logging" );
addField( "level", TypeEnum, Offset( mLevel, ConsoleLogger ), 1, &gLogLevelTable );
endGroup( "Logging" );
}
//-----------------------------------------------------------------------------
bool ConsoleLogger::processArguments( S32 argc, const char **argv )
{
if( argc == 0 )
return false;
bool append = false;
if( argc == 2 )
append = dAtob( argv[1] );
mAppend = append;
mFilename = StringTable->insert( argv[0] );
if( init() )
{
attach();
return true;
}
return false;
}
//-----------------------------------------------------------------------------
ConsoleLogger::~ConsoleLogger()
{
detach();
}
//-----------------------------------------------------------------------------
bool ConsoleLogger::init()
{
if( smInitialized )
return true;
Con::addConsumer( ConsoleLogger::logCallback );
smInitialized = true;
return true;
}
//-----------------------------------------------------------------------------
bool ConsoleLogger::attach()
{
if( mFilename == NULL )
{
Con::errorf( "ConsoleLogger failed to attach: no filename supplied." );
return false;
}
// Check to see if this is initialized before using it
if( !smInitialized )
{
if( !init() )
{
Con::errorf( "ConsoleLogger failed to initalize." );
return false;
}
}
if( mLogging )
return false;
// Open the filestream
mStream.open( mFilename, ( mAppend ? FileStream::WriteAppend : FileStream::Write ) );
// Add this to list of active loggers
mActiveLoggers.push_back( this );
mLogging = true;
return true;
}
//-----------------------------------------------------------------------------
bool ConsoleLogger::detach()
{
// Make sure this is valid before messing with it
if( !smInitialized )
{
if( !init() )
{
return false;
}
}
if( !mLogging )
return false;
// Close filestream
mStream.close();
// Remove this object from the list of active loggers
for( int i = 0; i < mActiveLoggers.size(); i++ )
{
if( mActiveLoggers[i] == this )
{
mActiveLoggers.erase( i );
mLogging = false;
return true;
}
}
return false; // If this happens, it's bad...
}
//-----------------------------------------------------------------------------
void ConsoleLogger::logCallback( ConsoleLogEntry::Level level, const char *consoleLine )
{
ConsoleLogger *curr;
// Loop through active consumers and send them the message
for( int i = 0; i < mActiveLoggers.size(); i++ )
{
curr = mActiveLoggers[i];
// If the log level is within the log threshhold, log it
if( curr->mLevel <= level )
curr->log( consoleLine );
}
}
//-----------------------------------------------------------------------------
void ConsoleLogger::log( const char *consoleLine )
{
// Check to see if this is intalized before using it
if( !smInitialized )
{
if( !init() )
{
Con::errorf( "I don't know how this happened, but log called on this without it being initialized" );
return;
}
}
mStream.writeLine( (U8 *)consoleLine );
}
//-----------------------------------------------------------------------------
ConsoleMethod( ConsoleLogger, attach, bool, 2, 2, "() Attaches this object to the console and begins logging" )
{
ConsoleLogger *logger = static_cast<ConsoleLogger *>( object );
return logger->attach();
}
//-----------------------------------------------------------------------------
ConsoleMethod( ConsoleLogger, detach, bool, 2, 2, "() Detaches this object from the console and stops logging" )
{
ConsoleLogger *logger = static_cast<ConsoleLogger *>( object );
return logger->detach();
}

93
engine/console/consoleLogger.h Executable file
View File

@ -0,0 +1,93 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/simBase.h"
#include "console/console.h"
#include "core/fileStream.h"
#ifndef _CONSOLE_LOGGER_H_
#define _CONSOLE_LOGGER_H_
/// A class designed to be used as a console consumer and log
/// the data it receives to a file.
class ConsoleLogger : public SimObject
{
typedef SimObject Parent;
private:
bool mLogging; ///< True if it is currently consuming and logging
FileStream mStream; ///< File stream this object writes to
static bool smInitialized; ///< This is for use with the default constructor
bool mAppend; ///< If false, it will clear the file before logging to it.
StringTableEntry mFilename; ///< The file name to log to.
/// List of active ConsoleLoggers to send log messages to
static Vector<ConsoleLogger *> mActiveLoggers;
/// The log function called by the consumer callback
/// @param consoleLine Line of text to log
void log( const char *consoleLine );
/// Utility function, sets up the object (for script interface) returns true if successful
bool init();
public:
// @name Public console variables
/// @{
ConsoleLogEntry::Level mLevel; ///< The level of log messages to log
/// @}
DECLARE_CONOBJECT( ConsoleLogger );
static void initPersistFields();
/// Console constructor
///
/// @code
/// // Example script constructor usage.
/// %obj = new ConsoleLogger( objName, logFileName, [append = false] );
/// @endcode
bool processArguments( S32 argc, const char **argv );
/// Default constructor, make sure to initalize
ConsoleLogger();
/// Constructor
/// @param fileName File name to log to
/// @param append If false, it will clear the file, then start logging, else it will append
ConsoleLogger( const char *fileName, bool append = false );
/// Destructor
~ConsoleLogger();
/// Attach to the console and begin logging
///
/// Returns true if the action is successful
bool attach();
/// Detach from the console and stop logging
///
/// Returns true if the action is successful
bool detach();
/// Sets the level of console messages to log.
///
/// @param level Log level. Only items of the specified level or
/// lower are logged.
/// @see ConsoleLogEntry::Level
void setLogLevel( ConsoleLogEntry::Level level );
/// Returns the level of console messages to log
ConsoleLogEntry::Level getLogLevel() const;
/// The callback for the console consumer
///
/// @note This is a global callback, not executed per-instance.
/// @see Con::addConsumer
static void logCallback( ConsoleLogEntry::Level level, const char *consoleLine );
};
#endif

348
engine/console/consoleObject.cc Executable file
View File

@ -0,0 +1,348 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/consoleObject.h"
#include "core/stringTable.h"
#include "core/crc.h"
#include "console/console.h"
#include "console/consoleInternal.h"
#include "console/typeValidators.h"
AbstractClassRep * AbstractClassRep::classLinkList = NULL;
static AbstractClassRep::FieldList sg_tempFieldList;
U32 AbstractClassRep::NetClassCount [NetClassGroupsCount][NetClassTypesCount] = {{0, },};
U32 AbstractClassRep::NetClassBitSize[NetClassGroupsCount][NetClassTypesCount] = {{0, },};
AbstractClassRep ** AbstractClassRep::classTable[NetClassGroupsCount][NetClassTypesCount];
U32 AbstractClassRep::classCRC[NetClassGroupsCount] = {INITIAL_CRC_VALUE, };
bool AbstractClassRep::initialized = false;
//--------------------------------------
const AbstractClassRep::Field *AbstractClassRep::findField(StringTableEntry name) const
{
for(U32 i = 0; i < mFieldList.size(); i++)
if(mFieldList[i].pFieldname == name)
return &mFieldList[i];
return NULL;
}
//--------------------------------------
void AbstractClassRep::registerClassRep(AbstractClassRep* in_pRep)
{
AssertFatal(in_pRep != NULL, "AbstractClassRep::registerClassRep was passed a NULL pointer!");
#ifdef TORQUE_DEBUG // assert if this class is already registered.
for(AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass)
{
AssertFatal(dStrcmp(in_pRep->mClassName, walk->mClassName),
"Duplicate class name registered in AbstractClassRep::registerClassRep()");
}
#endif
in_pRep->nextClass = classLinkList;
classLinkList = in_pRep;
}
//--------------------------------------
ConsoleObject* AbstractClassRep::create(const char* in_pClassName)
{
AssertFatal(initialized,
"AbstractClassRep::create() - Tried to create an object before AbstractClassRep::initialize().");
for (AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass)
if (!dStrcmp(walk->getClassName(), in_pClassName))
return walk->create();
AssertWarn(0, avar("Couldn't find class rep for dynamic class: %s", in_pClassName));
return NULL;
}
//--------------------------------------
ConsoleObject* AbstractClassRep::create(const U32 groupId, const U32 typeId, const U32 in_classId)
{
AssertFatal(initialized,
"AbstractClassRep::create() - Tried to create an object before AbstractClassRep::initialize().");
AssertFatal(in_classId < NetClassCount[groupId][typeId],
"AbstractClassRep::create() - Class id out of range.");
AssertFatal(classTable[groupId][typeId][in_classId] != NULL,
"AbstractClassRep::create() - No class with requested ID type.");
// Look up the specified class and create it.
if(classTable[groupId][typeId][in_classId])
return classTable[groupId][typeId][in_classId]->create();
return NULL;
}
//--------------------------------------
static S32 QSORT_CALLBACK ACRCompare(const void *aptr, const void *bptr)
{
const AbstractClassRep *a = *((const AbstractClassRep **) aptr);
const AbstractClassRep *b = *((const AbstractClassRep **) bptr);
if(a->mClassType != b->mClassType)
return a->mClassType - b->mClassType;
return dStrcmp(a->getClassName(), b->getClassName());
}
void AbstractClassRep::initialize()
{
AssertFatal(!initialized, "Duplicate call to AbstractClassRep::initialize()!");
Vector<AbstractClassRep *> dynamicTable(__FILE__, __LINE__);
AbstractClassRep *walk;
// Initialize namespace references...
for (walk = classLinkList; walk; walk = walk->nextClass)
{
walk->mNamespace = Con::lookupNamespace(StringTable->insert(walk->getClassName()));
walk->mNamespace->mClassRep = walk;
}
// Initialize field lists... (and perform other console registration).
for (walk = classLinkList; walk; walk = walk->nextClass)
{
// sg_tempFieldList is used as a staging area for field lists
// (see addField, addGroup, etc.)
sg_tempFieldList.setSize(0);
walk->init();
// So if we have things in it, copy it over...
if (sg_tempFieldList.size() != 0)
walk->mFieldList = sg_tempFieldList;
// And of course delete it every round.
sg_tempFieldList.clear();
}
// Calculate counts and bit sizes for the various NetClasses.
for (U32 group = 0; group < NetClassGroupsCount; group++)
{
U32 groupMask = 1 << group;
// Specifically, for each NetClass of each NetGroup...
for(U32 type = 0; type < NetClassTypesCount; type++)
{
// Go through all the classes and find matches...
for (walk = classLinkList; walk; walk = walk->nextClass)
{
if(walk->mClassType == type && walk->mClassGroupMask & groupMask)
dynamicTable.push_back(walk);
}
// Set the count for this NetGroup and NetClass
NetClassCount[group][type] = dynamicTable.size();
if(!NetClassCount[group][type])
continue; // If no classes matched, skip to next.
// Sort by type and then by name.
dQsort((void *) &dynamicTable[0], dynamicTable.size(), sizeof(AbstractClassRep *), ACRCompare);
// Allocate storage in the classTable
classTable[group][type] = new AbstractClassRep*[NetClassCount[group][type]];
// Fill this in and assign class ids for this group.
for(U32 i = 0; i < NetClassCount[group][type];i++)
{
classTable[group][type][i] = dynamicTable[i];
dynamicTable[i]->mClassId[group] = i;
}
// And calculate the size of bitfields for this group and type.
NetClassBitSize[group][type] =
getBinLog2(getNextPow2(NetClassCount[group][type] + 1));
dynamicTable.clear();
}
}
// Ok, we're golden!
initialized = true;
}
//------------------------------------------------------------------------------
//-------------------------------------- ConsoleObject
char replacebuf[1024];
char* suppressSpaces(const char* in_pname)
{
U32 i = 0;
char chr;
do
{
chr = in_pname[i];
replacebuf[i++] = (chr != 32) ? chr : '_';
} while(chr);
return replacebuf;
}
void ConsoleObject::addGroup(const char* in_pGroupname, const char* in_pGroupDocs)
{
// Remove spaces.
char* pFieldNameBuf = suppressSpaces(in_pGroupname);
// Append group type to fieldname.
dStrcat(pFieldNameBuf, "_begingroup");
// Create Field.
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(pFieldNameBuf);
f.pGroupname = StringTable->insert(in_pGroupname);
if(in_pGroupDocs)
f.pFieldDocs = StringTable->insert(in_pGroupDocs);
else
f.pFieldDocs = NULL;
f.type = AbstractClassRep::StartGroupFieldType;
f.elementCount = 0;
f.groupExpand = false;
f.validator = NULL;
// Add to field list.
sg_tempFieldList.push_back(f);
}
void ConsoleObject::endGroup(const char* in_pGroupname)
{
// Remove spaces.
char* pFieldNameBuf = suppressSpaces(in_pGroupname);
// Append group type to fieldname.
dStrcat(pFieldNameBuf, "_endgroup");
// Create Field.
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(pFieldNameBuf);
f.pGroupname = StringTable->insert(in_pGroupname);
f.pFieldDocs = NULL;
f.type = AbstractClassRep::EndGroupFieldType;
f.groupExpand = false;
f.validator = NULL;
f.elementCount = 0;
// Add to field list.
sg_tempFieldList.push_back(f);
}
void ConsoleObject::addField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
const char* in_pFieldDocs)
{
addField(
in_pFieldname,
in_fieldType,
in_fieldOffset,
1,
NULL,
in_pFieldDocs);
}
void ConsoleObject::addField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
const U32 in_elementCount,
EnumTable *in_table,
const char* in_pFieldDocs)
{
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(in_pFieldname);
f.pGroupname = NULL;
if(in_pFieldDocs)
f.pFieldDocs = StringTable->insert(in_pFieldDocs);
else
f.pFieldDocs = NULL;
f.type = in_fieldType;
f.offset = in_fieldOffset;
f.elementCount = in_elementCount;
f.table = in_table;
f.validator = NULL;
sg_tempFieldList.push_back(f);
}
void ConsoleObject::addFieldV(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
TypeValidator *v,
const char* in_pFieldDocs)
{
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(in_pFieldname);
f.pGroupname = NULL;
if(in_pFieldDocs)
f.pFieldDocs = StringTable->insert(in_pFieldDocs);
else
f.pFieldDocs = NULL;
f.type = in_fieldType;
f.offset = in_fieldOffset;
f.elementCount = 1;
f.table = NULL;
f.validator = v;
v->fieldIndex = sg_tempFieldList.size();
sg_tempFieldList.push_back(f);
}
void ConsoleObject::addDepricatedField(const char *fieldName)
{
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(fieldName);
f.pGroupname = NULL;
f.pFieldDocs = NULL;
f.type = AbstractClassRep::DepricatedFieldType;
f.offset = 0;
f.elementCount = 0;
f.table = NULL;
f.validator = NULL;
sg_tempFieldList.push_back(f);
}
bool ConsoleObject::removeField(const char* in_pFieldname)
{
for (U32 i = 0; i < sg_tempFieldList.size(); i++) {
if (dStricmp(in_pFieldname, sg_tempFieldList[i].pFieldname) == 0) {
sg_tempFieldList.erase(i);
return true;
}
}
return false;
}
//--------------------------------------
void ConsoleObject::initPersistFields()
{
}
//--------------------------------------
void ConsoleObject::consoleInit()
{
}
ConsoleObject::~ConsoleObject()
{
}
//--------------------------------------
AbstractClassRep* ConsoleObject::getClassRep() const
{
return NULL;
}

680
engine/console/consoleObject.h Executable file
View File

@ -0,0 +1,680 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONSOLEOBJECT_H_
#define _CONSOLEOBJECT_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _STRINGTABLE_H_
#include "core/stringTable.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
class Namespace;
enum NetClassTypes {
NetClassTypeObject = 0,
NetClassTypeDataBlock,
NetClassTypeEvent,
NetClassTypesCount,
};
enum NetClassGroups {
NetClassGroupGame = 0,
NetClassGroupCommunity,
NetClassGroup3,
NetClassGroup4,
NetClassGroupsCount,
};
enum NetClassMasks {
NetClassGroupGameMask = BIT(NetClassGroupGame),
NetClassGroupCommunityMask = BIT(NetClassGroupCommunity),
};
enum NetDirection
{
NetEventDirAny,
NetEventDirServerToClient,
NetEventDirClientToServer,
};
class SimObject;
class TypeValidator;
/// Core functionality for class manipulation.
///
/// @section AbstractClassRep_intro Introduction (or, Why AbstractClassRep?)
///
/// Many of Torque's subsystems, especially network, console, and sim,
/// require the ability to programatically instantiate classes. For instance,
/// when objects are ghosted, the networking layer needs to be able to create
/// an instance of the object on the client. When the console scripting
/// language runtime encounters the "new" keyword, it has to be able to fill
/// that request.
///
/// Since standard C++ doesn't provide a function to create a new instance of
/// an arbitrary class at runtime, one must be created. This is what
/// AbstractClassRep and ConcreteClassRep are all about. They allow the registration
/// and instantiation of arbitrary classes at runtime.
///
/// In addition, ACR keeps track of the fields (registered via addField() and co.) of
/// a class, allowing programmatic access of class fields.
///
/// @see ConsoleObject
///
/// @note In general, you will only access the functionality implemented in this class via
/// ConsoleObject::create(). Most of the time, you will only ever need to use this part
/// part of the engine indirectly - ie, you will use the networking system or the console,
/// or ConsoleObject, and they will indirectly use this code. <b>The following discussion
/// is really only relevant for advanced engine users.</b>
///
/// @section AbstractClassRep_netstuff NetClasses and Class IDs
///
/// Torque supports a notion of group, type, and direction for objects passed over
/// the network. Class IDs are assigned sequentially per-group, per-type, so that, for instance,
/// the IDs assigned to Datablocks are seperate from the IDs assigned to NetObjects or NetEvents.
/// This can translate into significant bandwidth savings (especially since the size of the fields
/// for transmitting these bits are determined at run-time based on the number of IDs given out.
///
/// @section AbstractClassRep_details AbstractClassRep Internals
///
/// Much like ConsoleConstructor, ACR does some preparatory work at runtime before execution
/// is passed to main(). In actual fact, this preparatory work is done by the ConcreteClassRep
/// template. Let's examine this more closely.
///
/// If we examine ConsoleObject, we see that two macros must be used in the definition of a
/// properly integrated objects. From the ConsoleObject example:
///
/// @code
/// // This is from inside the class definition...
/// DECLARE_CONOBJECT(TorqueObject);
///
/// // And this is from outside the class definition...
/// IMPLEMENT_CONOBJECT(TorqueObject);
/// @endcode
///
/// What do these things actually do?
///
/// Not all that much, in fact. They expand to code something like this:
///
/// @code
/// // This is from inside the class definition...
/// static ConcreteClassRep<TorqueObject> dynClassRep;
/// static AbstractClassRep* getParentStaticClassRep();
/// static AbstractClassRep* getStaticClassRep();
/// virtual AbstractClassRep* getClassRep() const;
/// @endcode
///
/// @code
/// // And this is from outside the class definition...
/// AbstractClassRep* TorqueObject::getClassRep() const { return &TorqueObject::dynClassRep; }
/// AbstractClassRep* TorqueObject::getStaticClassRep() { return &dynClassRep; }
/// AbstractClassRep* TorqueObject::getParentStaticClassRep() { return Parent::getStaticClassRep(); }
/// ConcreteClassRep<TorqueObject> TorqueObject::dynClassRep("TorqueObject", 0, -1, 0);
/// @endcode
///
/// As you can see, getClassRep(), getStaticClassRep(), and getParentStaticClassRep() are just
/// accessors to allow access to various ConcreteClassRep instances. This is where the Parent
/// typedef comes into play as well - it lets getParentStaticClassRep() get the right
/// class rep.
///
/// In addition, dynClassRep is declared as a member of TorqueObject, and defined later
/// on. Much like ConsoleConstructor, ConcreteClassReps add themselves to a global linked
/// list in their constructor.
///
/// Then, when AbstractClassRep::initialize() is called, from Con::init(), we iterate through
/// the list and perform the following tasks:
/// - Sets up a Namespace for each class.
/// - Call the init() method on each ConcreteClassRep. This method:
/// - Links namespaces between parent and child classes, using Con::classLinkNamespaces.
/// - Calls initPersistFields() and consoleInit().
/// - As a result of calling initPersistFields, the field list for the class is populated.
/// - Assigns network IDs for classes based on their NetGroup membership. Determines
/// bit allocations for network ID fields.
///
/// @nosubgrouping
class AbstractClassRep
{
friend class ConsoleObject;
public:
/// @name 'Tructors
/// @{
AbstractClassRep()
{
VECTOR_SET_ASSOCIATION(mFieldList);
parentClass = NULL;
}
virtual ~AbstractClassRep() { }
/// @}
/// @name Representation Interface
/// @{
S32 mClassGroupMask; ///< Mask indicating in which NetGroups this object belongs.
S32 mClassType; ///< Stores the NetClass of this class.
S32 mNetEventDir; ///< Stores the NetDirection of this class.
S32 mClassId[NetClassGroupsCount]; ///< Stores the IDs assigned to this class for each group.
S32 getClassId (U32 netClassGroup) const;
static U32 getClassCRC (U32 netClassGroup);
const char* getClassName() const;
static AbstractClassRep* getClassList();
Namespace* getNameSpace();
AbstractClassRep* getNextClass();
AbstractClassRep* getParentClass();
/// Helper class to see if we are a given class, or a subclass thereof.
bool isClass(AbstractClassRep *acr)
{
AbstractClassRep *walk = this;
// Walk up parents, checking for equivalence.
while(walk)
{
if(walk == acr)
return true;
walk = walk->parentClass;
};
return false;
}
virtual ConsoleObject* create () const = 0;
protected:
virtual void init() const = 0;
const char * mClassName;
AbstractClassRep * nextClass;
AbstractClassRep * parentClass;
Namespace * mNamespace;
/// @}
/// @name Fields
/// @{
public:
enum ACRFieldTypes
{
StartGroupFieldType = 0xFFFFFFFD,
EndGroupFieldType = 0xFFFFFFFE,
DepricatedFieldType = 0xFFFFFFFF
};
struct Field {
const char* pFieldname; ///< Name of the field.
const char* pGroupname; ///< Optionally filled field containing the group name.
///
/// This is filled when type is StartField or EndField
const char* pFieldDocs; ///< Documentation about this field; see consoleDoc.cc.
bool groupExpand; ///< Flag to track expanded/not state of this group in the editor.
U32 type; ///< A type ID. @see ACRFieldTypes
U32 offset; ///< Memory offset from beginning of class for this field.
S32 elementCount; ///< Number of elements, if this is an array.
EnumTable * table; ///< If this is an enum, this points to the table defining it.
BitSet32 flag; ///< Stores various flags
TypeValidator *validator; ///< Validator, if any.
};
typedef Vector<Field> FieldList;
FieldList mFieldList;
bool mDynamicGroupExpand;
const Field *findField(StringTableEntry fieldName) const;
/// @}
/// @name Abstract Class Database
/// @{
protected:
static AbstractClassRep ** classTable[NetClassGroupsCount][NetClassTypesCount];
static AbstractClassRep * classLinkList;
static U32 classCRC[NetClassGroupsCount];
static bool initialized;
static ConsoleObject* create(const char* in_pClassName);
static ConsoleObject* create(const U32 groupId, const U32 typeId, const U32 in_classId);
public:
static U32 NetClassCount [NetClassGroupsCount][NetClassTypesCount];
static U32 NetClassBitSize[NetClassGroupsCount][NetClassTypesCount];
static void registerClassRep(AbstractClassRep*);
static void initialize(); // Called from Con::init once on startup
/// @}
};
inline AbstractClassRep *AbstractClassRep::getClassList()
{
return classLinkList;
}
inline U32 AbstractClassRep::getClassCRC(U32 group)
{
return classCRC[group];
}
inline AbstractClassRep *AbstractClassRep::getNextClass()
{
return nextClass;
}
inline AbstractClassRep *AbstractClassRep::getParentClass()
{
return parentClass;
}
inline S32 AbstractClassRep::getClassId(U32 group) const
{
return mClassId[group];
}
inline const char* AbstractClassRep::getClassName() const
{
return mClassName;
}
inline Namespace *AbstractClassRep::getNameSpace()
{
return mNamespace;
}
//------------------------------------------------------------------------------
//-------------------------------------- ConcreteClassRep
//
/// Helper class for AbstractClassRep.
///
/// @see AbtractClassRep
/// @see ConsoleObject
template <class T>
class ConcreteClassRep : public AbstractClassRep
{
public:
ConcreteClassRep(const char *name, S32 netClassGroupMask, S32 netClassType, S32 netEventDir, AbstractClassRep *parent)
{
// name is a static compiler string so no need to worry about copying or deleting
mClassName = name;
// Clean up mClassId
for(U32 i = 0; i < NetClassGroupsCount; i++)
mClassId[i] = -1;
// Set properties for this ACR
mClassType = netClassType;
mClassGroupMask = netClassGroupMask;
mNetEventDir = netEventDir;
parentClass = parent;
// Finally, register ourselves.
registerClassRep(this);
};
/// Perform class specific initialization tasks.
///
/// Link namespaces, call initPersistFields() and consoleInit().
void init() const
{
// Get handle to our parent class, if any, and ourselves (we are our parent's child).
AbstractClassRep *parent = T::getParentStaticClassRep();
AbstractClassRep *child = T::getStaticClassRep ();
// If we got reps, then link those namespaces! (To get proper inheritance.)
if(parent && child)
Con::classLinkNamespaces(parent->getNameSpace(), child->getNameSpace());
// Finally, do any class specific initialization...
T::initPersistFields();
T::consoleInit();
}
/// Wrap constructor.
ConsoleObject* create() const { return new T; }
};
//------------------------------------------------------------------------------
/// Interface class to the console.
///
/// @section ConsoleObject_basics The Basics
///
/// Any object which you want to work with the console system should derive from this,
/// and access functionality through the static interface.
///
/// This class is always used with the DECLARE_CONOBJECT and IMPLEMENT_* macros.
///
/// @code
/// // A very basic example object. It will do nothing!
/// class TorqueObject : public ConsoleObject {
/// // Must provide a Parent typedef so the console system knows what we inherit from.
/// typedef ConsoleObject Parent;
///
/// // This does a lot of menial declaration for you.
/// DECLARE_CONOBJECT(TorqueObject);
///
/// // This is for us to register our fields in.
/// static void initPersistFields();
///
/// // A sample field.
/// S8 mSample;
/// }
/// @endcode
///
/// @code
/// // And the accordant implementation...
/// IMPLEMENT_CONOBJECT(TorqueObject);
///
/// void TorqueObject::initPersistFields()
/// {
/// // If you want to inherit any fields from the parent (you do), do this:
/// Parent::initPersistFields();
///
/// // Pass the field, the type, the offset, and a usage string.
/// addField("sample", TypeS8, Offset(mSample, TorqueObject), "A test field.");
/// }
/// @endcode
///
/// That's all you need to do to get a class registered with the console system. At this point,
/// you can instantiate it via script, tie methods to it using ConsoleMethod, register fields,
/// and so forth. You can also register any global variables related to the class by creating
/// a consoleInit() method.
///
/// You will need to use different IMPLEMENT_ macros in different cases; for instance, if you
/// are making a NetObject (for ghosting), a DataBlock, or a NetEvent.
///
/// @see AbstractClassRep for gory implementation details.
/// @nosubgrouping
class ConsoleObject
{
protected:
/// @deprecated This is disallowed.
ConsoleObject() { /* disallowed */ }
/// @deprecated This is disallowed.
ConsoleObject(const ConsoleObject&);
protected:
/// Get a reference to a field by name.
const AbstractClassRep::Field *findField(StringTableEntry fieldName) const;
public:
/// Gets the ClassRep.
virtual AbstractClassRep* getClassRep() const;
/// Set the value of a field.
bool setField(const char *fieldName, const char *value);
virtual ~ConsoleObject();
public:
/// @name Object Creation
/// @{
static ConsoleObject* create(const char* in_pClassName);
static ConsoleObject* create(const U32 groupId, const U32 typeId, const U32 in_classId);
/// @}
public:
/// Get the classname from a class tag.
static const char* lookupClassName(const U32 in_classTag);
protected:
/// @name Fields
/// @{
/// Mark the beginning of a group of fields.
///
/// This is used in the consoleDoc system.
/// @see console_autodoc
static void addGroup(const char* in_pGroupname, const char* in_pGroupDocs = NULL);
/// Mark the end of a group of fields.
///
/// This is used in the consoleDoc system.
/// @see console_autodoc
static void endGroup(const char* in_pGroupname);
/// Register a complex field.
///
/// @param in_pFieldname Name of the field.
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
/// @param in_elementCount Number of elements in this field. Arrays of elements are assumed to be contiguous in memory.
/// @param in_table An EnumTable, if this is an enumerated field.
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
static void addField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
const U32 in_elementCount = 1,
EnumTable * in_table = NULL,
const char* in_pFieldDocs = NULL);
/// Register a simple field.
///
/// @param in_pFieldname Name of the field.
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
static void addField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
const char* in_pFieldDocs);
/// Register a validated field.
///
/// A validated field is just like a normal field except that you can't
/// have it be an array, and that you give it a pointer to a TypeValidator
/// subclass, which is then used to validate any value placed in it. Invalid
/// values are ignored and an error is printed to the console.
///
/// @see addField
/// @see typeValidators.h
static void addFieldV(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
TypeValidator *v,
const char * in_pFieldDocs = NULL);
/// Add a deprecated field.
///
/// A deprecated field will always be undefined, even if you assign a value to it. This
/// is useful when you need to make sure that a field is not being used anymore.
static void addDepricatedField(const char *fieldName);
/// Remove a field.
///
/// Sometimes, you just have to remove a field!
/// @returns True on success.
static bool removeField(const char* in_pFieldname);
/// @}
public:
/// Register dynamic fields in a subclass of ConsoleObject.
///
/// @see addField(), addFieldV(), addDepricatedField(), addGroup(), endGroup()
static void initPersistFields();
/// Register global constant variables and do other one-time initialization tasks in
/// a subclass of ConsoleObject.
///
/// @deprecated You should use ConsoleMethod and ConsoleFunction, not this, to
/// register methods or commands.
/// @see console
static void consoleInit();
/// @name Field List
/// @{
/// Get a list of all the fields. This information cannot be modified.
const AbstractClassRep::FieldList& getFieldList() const;
/// Get a list of all the fields, set up so we can modify them.
///
/// @note This is a bad trick to pull if you aren't very careful,
/// since you can blast field data!
AbstractClassRep::FieldList& getModifiableFieldList();
/// Get a handle to a boolean telling us if we expanded the dynamic group.
///
/// @see GuiInspector::Inspect()
bool& getDynamicGroupExpand();
/// @}
/// @name ConsoleObject Implementation
///
/// These functions are implemented in every subclass of
/// ConsoleObject by an IMPLEMENT_CONOBJECT or IMPLEMENT_CO_* macro.
/// @{
/// Get the abstract class information for this class.
static AbstractClassRep *getStaticClassRep() { return NULL; }
/// Get the abstract class information for this class's superclass.
static AbstractClassRep *getParentStaticClassRep() { return NULL; }
/// Get our network-layer class id.
///
/// @param netClassGroup The net class for which we want our ID.
/// @see
S32 getClassId(U32 netClassGroup) const;
/// Get our compiler and platform independent class name.
///
/// @note This name can be used to instantiate another instance using create()
const char *getClassName() const;
/// @}
};
#define addNamedField(fieldName,type,className) addField(#fieldName, type, Offset(fieldName,className))
#define addNamedFieldV(fieldName,type,className, validator) addFieldV(#fieldName, type, Offset(fieldName,className), validator)
//------------------------------------------------------------------------------
//-------------------------------------- Inlines
//
inline S32 ConsoleObject::getClassId(U32 netClassGroup) const
{
AssertFatal(getClassRep() != NULL,"Cannot get tag from non-declared dynamic class!");
return getClassRep()->getClassId(netClassGroup);
}
inline const char * ConsoleObject::getClassName() const
{
AssertFatal(getClassRep() != NULL,
"Cannot get tag from non-declared dynamic class");
return getClassRep()->getClassName();
}
inline const AbstractClassRep::Field * ConsoleObject::findField(StringTableEntry name) const
{
AssertFatal(getClassRep() != NULL,
avar("Cannot get field '%s' from non-declared dynamic class.", name));
return getClassRep()->findField(name);
}
inline bool ConsoleObject::setField(const char *fieldName, const char *value)
{
//sanity check
if ((! fieldName) || (! fieldName[0]) || (! value))
return false;
if (! getClassRep())
return false;
const AbstractClassRep::Field *myField = getClassRep()->findField(StringTable->insert(fieldName));
if (! myField)
return false;
Con::setData(
myField->type,
(void *) (((const char *)(this)) + myField->offset),
0,
1,
&value,
myField->table,
myField->flag);
return true;
}
inline ConsoleObject* ConsoleObject::create(const char* in_pClassName)
{
return AbstractClassRep::create(in_pClassName);
}
inline ConsoleObject* ConsoleObject::create(const U32 groupId, const U32 typeId, const U32 in_classId)
{
return AbstractClassRep::create(groupId, typeId, in_classId);
}
inline const AbstractClassRep::FieldList& ConsoleObject::getFieldList() const
{
return getClassRep()->mFieldList;
}
inline AbstractClassRep::FieldList& ConsoleObject::getModifiableFieldList()
{
return getClassRep()->mFieldList;
}
inline bool& ConsoleObject::getDynamicGroupExpand()
{
return getClassRep()->mDynamicGroupExpand;
}
/// @name ConsoleObject Macros
/// @{
#define DECLARE_CONOBJECT(className) \
static ConcreteClassRep<className> dynClassRep; \
static AbstractClassRep* getParentStaticClassRep(); \
static AbstractClassRep* getStaticClassRep(); \
virtual AbstractClassRep* getClassRep() const
#define IMPLEMENT_CONOBJECT(className) \
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
ConcreteClassRep<className> className::dynClassRep(#className, 0, -1, 0, className::getParentStaticClassRep())
#define IMPLEMENT_CO_NETOBJECT_V1(className) \
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
ConcreteClassRep<className> className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeObject, 0, className::getParentStaticClassRep())
#define IMPLEMENT_CO_DATABLOCK_V1(className) \
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
ConcreteClassRep<className> className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeDataBlock, 0, className::getParentStaticClassRep())
/// @}
#endif //_CONSOLEOBJECT_H_

76
engine/console/consoleParser.cc Executable file
View File

@ -0,0 +1,76 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/console.h"
#include "console/consoleParser.h"
namespace Compiler
{
static ConsoleParser *gParserList = NULL;
static ConsoleParser *gDefaultParser = NULL;
void freeConsoleParserList(void)
{
ConsoleParser *pParser;
while(pParser = gParserList)
{
gParserList = pParser->next;
delete pParser;
}
gDefaultParser = NULL;
}
bool addConsoleParser(char *ext, fnGetCurrentFile gcf, fnGetCurrentLine gcl, fnParse p, fnRestart r, fnSetScanBuffer ssb, bool def /* = false */)
{
AssertFatal(ext && gcf && gcl && p && r, "AddConsoleParser called with one or more NULL arguments");
ConsoleParser *pParser;
if(pParser = new ConsoleParser)
{
pParser->ext = ext;
pParser->getCurrentFile = gcf;
pParser->getCurrentLine = gcl;
pParser->parse = p;
pParser->restart = r;
pParser->setScanBuffer = ssb;
if(def)
gDefaultParser = pParser;
pParser->next = gParserList;
gParserList = pParser;
return true;
}
return false;
}
ConsoleParser * getParserForFile(const char *filename)
{
char *ptr;
if(filename == NULL)
return gDefaultParser;
if(ptr = dStrrchr((char *)filename, '.'))
{
ptr++;
ConsoleParser *p;
for(p = gParserList;p;p = p->next)
{
if(dStricmp(ptr, p->ext) == 0)
return p;
}
}
return gDefaultParser;
}
} // end namespace Con

105
engine/console/consoleParser.h Executable file
View File

@ -0,0 +1,105 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//
// TorqueBASIC
// (c) Copyright 2004 Burnt Wasp
//-----------------------------------------------------------------------------
#ifndef _CONSOLE_PARSER_H_
#define _CONSOLE_PARSER_H_
#include <stdio.h>
namespace Compiler
{
//////////////////////////////////////////////////////////////////////////
/// \brief Function for GetCurrentFile from the lexer
//////////////////////////////////////////////////////////////////////////
typedef const char *(*fnGetCurrentFile)();
//////////////////////////////////////////////////////////////////////////
/// \brief Function for GetCurrentLine from the lexer
//////////////////////////////////////////////////////////////////////////
typedef S32 (*fnGetCurrentLine)();
//////////////////////////////////////////////////////////////////////////
/// \brief Function for Parse from the lexer
//////////////////////////////////////////////////////////////////////////
typedef S32 (*fnParse)();
//////////////////////////////////////////////////////////////////////////
/// \brief Function for Restart from the lexer
//////////////////////////////////////////////////////////////////////////
typedef void (*fnRestart)(FILE *input_file);
//////////////////////////////////////////////////////////////////////////
/// \brief Function for SetScanBuffer from the lexer
//////////////////////////////////////////////////////////////////////////
typedef void (*fnSetScanBuffer)(const char *sb, const char *fn);
//////////////////////////////////////////////////////////////////////////
/// \brief List of parsers for the compiler
//////////////////////////////////////////////////////////////////////////
struct ConsoleParser
{
struct ConsoleParser *next; //!< Next object in list or NULL
char *ext; //!< Filename extension handled by this parser
fnGetCurrentFile getCurrentFile; //!< GetCurrentFile lexer function
fnGetCurrentLine getCurrentLine; //!< GetCurrentLine lexer function
fnParse parse; //!< Parse lexer function
fnRestart restart; //!< Restart lexer function
fnSetScanBuffer setScanBuffer; //!< SetScanBuffer lexer function
};
// Macros
//////////////////////////////////////////////////////////////////////////
/// \brief Declare a parser's function prototypes
//////////////////////////////////////////////////////////////////////////
#define CON_DECLARE_PARSER(prefix) \
const char * prefix##GetCurrentFile(); \
S32 prefix##GetCurrentLine(); \
void prefix##SetScanBuffer(const char *sb, const char *fn); \
S32 prefix##parse(); \
void prefix##restart(FILE *input_file)
//////////////////////////////////////////////////////////////////////////
/// \brief Helper macro to add console parsers
//////////////////////////////////////////////////////////////////////////
#define CON_ADD_PARSER(prefix, ext, def) \
Compiler::addConsoleParser(ext, prefix##GetCurrentFile, prefix##GetCurrentLine, prefix##parse, \
prefix##restart, prefix##SetScanBuffer, def)
//////////////////////////////////////////////////////////////////////////
/// \brief Free the console parser list
///
/// \sa AddConsoleParser()
//////////////////////////////////////////////////////////////////////////
void freeConsoleParserList(void);
//////////////////////////////////////////////////////////////////////////
/// \brief Add a console parser to the list
///
/// \param ext Filename extension
/// \param gcf GetCurrentFile function
/// \param gcl GetCurrentLine function
/// \param p Parse function
/// \param r Restart function
/// \param ssb SetScanBuffer function
/// \param def true if this is the default parser (<b>Note:</b> set this only on the .cs parser!)
/// \return true for success, false for failure (out of memory)
/// \sa FreeConsoleParserList(), ConsoleParser
//////////////////////////////////////////////////////////////////////////
bool addConsoleParser(char *ext, fnGetCurrentFile gcf, fnGetCurrentLine gcl, fnParse p, fnRestart r, fnSetScanBuffer ssb, bool def = false);
//////////////////////////////////////////////////////////////////////////
/// \brief Get the parser for a particular file based on its extension
///
/// \param filename Filename of file to obtain parser for
/// \sa ConsoleParser
//////////////////////////////////////////////////////////////////////////
ConsoleParser * getParserForFile(const char *filename);
} // end namespace Con
#endif // _CONSOLE_PARSER_H_

490
engine/console/consoleTypes.cc Executable file
View File

@ -0,0 +1,490 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/consoleTypes.h"
#include "core/stringTable.h"
#include "core/color.h"
#include "console/simBase.h"
//////////////////////////////////////////////////////////////////////////
// TypeString
//////////////////////////////////////////////////////////////////////////
ConsoleType( string, TypeString, sizeof(const char*) )
ConsoleGetType( TypeString )
{
return *((const char **)(dptr));
}
ConsoleSetType( TypeString )
{
if(argc == 1)
*((const char **) dptr) = StringTable->insert(argv[0]);
else
Con::printf("(TypeString) Cannot set multiple args to a single string.");
}
//////////////////////////////////////////////////////////////////////////
// TypeCaseString
//////////////////////////////////////////////////////////////////////////
ConsoleType( caseString, TypeCaseString, sizeof(const char*) )
ConsoleSetType( TypeCaseString )
{
if(argc == 1)
*((const char **) dptr) = StringTable->insert(argv[0], true);
else
Con::printf("(TypeCaseString) Cannot set multiple args to a single string.");
}
ConsoleGetType( TypeCaseString )
{
return *((const char **)(dptr));
}
//////////////////////////////////////////////////////////////////////////
// TypeFileName
//////////////////////////////////////////////////////////////////////////
ConsoleType( filename, TypeFilename, sizeof( const char* ) )
ConsoleSetType( TypeFilename )
{
if(argc == 1)
{
char buffer[1024];
if (Con::expandScriptFilename(buffer, 1024, argv[0]))
*((const char **) dptr) = StringTable->insert(buffer);
else
Con::warnf("(TypeFilename) illegal filename detected: %s", argv[0]);
}
else
Con::printf("(TypeFilename) Cannot set multiple args to a single filename.");
}
ConsoleGetType( TypeFilename )
{
return *((const char **)(dptr));
}
//////////////////////////////////////////////////////////////////////////
// TypeS8
//////////////////////////////////////////////////////////////////////////
ConsoleType( char, TypeS8, sizeof(U8) )
ConsoleGetType( TypeS8 )
{
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%d", *((U8 *) dptr) );
return returnBuffer;
}
ConsoleSetType( TypeS8 )
{
if(argc == 1)
*((U8 *) dptr) = dAtoi(argv[0]);
else
Con::printf("(TypeU8) Cannot set multiple args to a single S32.");
}
//////////////////////////////////////////////////////////////////////////
// TypeS32
//////////////////////////////////////////////////////////////////////////
ConsoleType( int, TypeS32, sizeof(S32) )
ConsoleGetType( TypeS32 )
{
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%d", *((S32 *) dptr) );
return returnBuffer;
}
ConsoleSetType( TypeS32 )
{
if(argc == 1)
*((S32 *) dptr) = dAtoi(argv[0]);
else
Con::printf("(TypeS32) Cannot set multiple args to a single S32.");
}
//////////////////////////////////////////////////////////////////////////
// TypeS32Vector
//////////////////////////////////////////////////////////////////////////
ConsoleType( intList, TypeS32Vector, sizeof(Vector<S32>) )
ConsoleGetType( TypeS32Vector )
{
Vector<S32> *vec = (Vector<S32> *)dptr;
char* returnBuffer = Con::getReturnBuffer(1024);
S32 maxReturn = 1024;
returnBuffer[0] = '\0';
S32 returnLeng = 0;
for (Vector<S32>::iterator itr = vec->begin(); itr != vec->end(); itr++)
{
// concatenate the next value onto the return string
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%d ", *itr);
// update the length of the return string (so far)
returnLeng = dStrlen(returnBuffer);
}
// trim off that last extra space
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
returnBuffer[returnLeng - 1] = '\0';
return returnBuffer;
}
ConsoleSetType( TypeS32Vector )
{
Vector<S32> *vec = (Vector<S32> *)dptr;
// we assume the vector should be cleared first (not just appending)
vec->clear();
if(argc == 1)
{
const char *values = argv[0];
const char *endValues = values + dStrlen(values);
S32 value;
// advance through the string, pulling off S32's and advancing the pointer
while (values < endValues && dSscanf(values, "%d", &value) != 0)
{
vec->push_back(value);
const char *nextValues = dStrchr(values, ' ');
if (nextValues != 0 && nextValues < endValues)
values = nextValues + 1;
else
break;
}
}
else if (argc > 1)
{
for (S32 i = 0; i < argc; i++)
vec->push_back(dAtoi(argv[i]));
}
else
Con::printf("Vector<S32> must be set as { a, b, c, ... } or \"a b c ...\"");
}
//////////////////////////////////////////////////////////////////////////
// TypeF32
//////////////////////////////////////////////////////////////////////////
ConsoleType( float, TypeF32, sizeof(F32) )
ConsoleGetType( TypeF32 )
{
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%g", *((F32 *) dptr) );
return returnBuffer;
}
ConsoleSetType( TypeF32 )
{
if(argc == 1)
*((F32 *) dptr) = dAtof(argv[0]);
else
Con::printf("(TypeF32) Cannot set multiple args to a single F32.");
}
//////////////////////////////////////////////////////////////////////////
// TypeF32Vector
//////////////////////////////////////////////////////////////////////////
ConsoleType( floatList, TypeF32Vector, sizeof(Vector<F32>) )
ConsoleGetType( TypeF32Vector )
{
Vector<F32> *vec = (Vector<F32> *)dptr;
char* returnBuffer = Con::getReturnBuffer(1024);
S32 maxReturn = 1024;
returnBuffer[0] = '\0';
S32 returnLeng = 0;
for (Vector<F32>::iterator itr = vec->begin(); itr != vec->end(); itr++)
{
// concatenate the next value onto the return string
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%g ", *itr);
// update the length of the return string (so far)
returnLeng = dStrlen(returnBuffer);
}
// trim off that last extra space
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
returnBuffer[returnLeng - 1] = '\0';
return returnBuffer;
}
ConsoleSetType( TypeF32Vector )
{
Vector<F32> *vec = (Vector<F32> *)dptr;
// we assume the vector should be cleared first (not just appending)
vec->clear();
if(argc == 1)
{
const char *values = argv[0];
const char *endValues = values + dStrlen(values);
F32 value;
// advance through the string, pulling off F32's and advancing the pointer
while (values < endValues && dSscanf(values, "%g", &value) != 0)
{
vec->push_back(value);
const char *nextValues = dStrchr(values, ' ');
if (nextValues != 0 && nextValues < endValues)
values = nextValues + 1;
else
break;
}
}
else if (argc > 1)
{
for (S32 i = 0; i < argc; i++)
vec->push_back(dAtof(argv[i]));
}
else
Con::printf("Vector<F32> must be set as { a, b, c, ... } or \"a b c ...\"");
}
//////////////////////////////////////////////////////////////////////////
// TypeBool
//////////////////////////////////////////////////////////////////////////
ConsoleType( bool, TypeBool, sizeof(bool) )
ConsoleGetType( TypeBool )
{
return *((bool *) dptr) ? "1" : "0";
}
ConsoleSetType( TypeBool )
{
if(argc == 1)
*((bool *) dptr) = dAtob(argv[0]);
else
Con::printf("(TypeBool) Cannot set multiple args to a single bool.");
}
//////////////////////////////////////////////////////////////////////////
// TypeBoolVector
//////////////////////////////////////////////////////////////////////////
ConsoleType( boolList, TypeBoolVector, sizeof(Vector<bool>) )
ConsoleGetType( TypeBoolVector )
{
Vector<bool> *vec = (Vector<bool>*)dptr;
char* returnBuffer = Con::getReturnBuffer(1024);
S32 maxReturn = 1024;
returnBuffer[0] = '\0';
S32 returnLeng = 0;
for (Vector<bool>::iterator itr = vec->begin(); itr < vec->end(); itr++)
{
// concatenate the next value onto the return string
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%d ", (*itr == true ? 1 : 0));
returnLeng = dStrlen(returnBuffer);
}
// trim off that last extra space
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
returnBuffer[returnLeng - 1] = '\0';
return(returnBuffer);
}
ConsoleSetType( TypeBoolVector )
{
Vector<bool> *vec = (Vector<bool>*)dptr;
// we assume the vector should be cleared first (not just appending)
vec->clear();
if (argc == 1)
{
const char *values = argv[0];
const char *endValues = values + dStrlen(values);
S32 value;
// advance through the string, pulling off bool's and advancing the pointer
while (values < endValues && dSscanf(values, "%d", &value) != 0)
{
vec->push_back(value == 0 ? false : true);
const char *nextValues = dStrchr(values, ' ');
if (nextValues != 0 && nextValues < endValues)
values = nextValues + 1;
else
break;
}
}
else if (argc > 1)
{
for (S32 i = 0; i < argc; i++)
vec->push_back(dAtob(argv[i]));
}
else
Con::printf("Vector<bool> must be set as { a, b, c, ... } or \"a b c ...\"");
}
//////////////////////////////////////////////////////////////////////////
// TypeEnum
//////////////////////////////////////////////////////////////////////////
ConsoleType( enumval, TypeEnum, sizeof(S32) )
ConsoleGetType( TypeEnum )
{
AssertFatal(tbl, "Null enum table passed to getDataTypeEnum()");
S32 dptrVal = *(S32*)dptr;
for (S32 i = 0; i < tbl->size; i++)
{
if (dptrVal == tbl->table[i].index)
{
return tbl->table[i].label;
}
}
//not found
return "";
}
ConsoleSetType( TypeEnum )
{
AssertFatal(tbl, "Null enum table passed to setDataTypeEnum()");
if (argc != 1) return;
S32 val = 0;
for (S32 i = 0; i < tbl->size; i++)
{
if (! dStricmp(argv[0], tbl->table[i].label))
{
val = tbl->table[i].index;
break;
}
}
*((S32 *) dptr) = val;
}
//////////////////////////////////////////////////////////////////////////
// TypeFlag
//////////////////////////////////////////////////////////////////////////
ConsoleType( flag, TypeFlag, sizeof(S32) )
ConsoleGetType( TypeFlag )
{
BitSet32 tempFlags = *(BitSet32 *)dptr;
if (tempFlags.test(flag)) return "true";
else return "false";
}
ConsoleSetType( TypeFlag )
{
bool value = true;
if (argc != 1)
{
Con::printf("flag must be true or false");
}
else
{
value = dAtob(argv[0]);
}
((BitSet32 *)dptr)->set(flag, value);
}
//////////////////////////////////////////////////////////////////////////
// TypeColorF
//////////////////////////////////////////////////////////////////////////
ConsoleType( ColorF, TypeColorF, sizeof(ColorF) )
ConsoleGetType( TypeColorF )
{
ColorF * color = (ColorF*)dptr;
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%g %g %g %g", color->red, color->green, color->blue, color->alpha);
return(returnBuffer);
}
ConsoleSetType( TypeColorF )
{
ColorF *tmpColor = (ColorF *) dptr;
if(argc == 1)
{
tmpColor->set(0, 0, 0, 1);
F32 r,g,b,a;
S32 args = dSscanf(argv[0], "%g %g %g %g", &r, &g, &b, &a);
tmpColor->red = r;
tmpColor->green = g;
tmpColor->blue = b;
if (args == 4)
tmpColor->alpha = a;
}
else if(argc == 3)
{
tmpColor->red = dAtof(argv[0]);
tmpColor->green = dAtof(argv[1]);
tmpColor->blue = dAtof(argv[2]);
tmpColor->alpha = 1.f;
}
else if(argc == 4)
{
tmpColor->red = dAtof(argv[0]);
tmpColor->green = dAtof(argv[1]);
tmpColor->blue = dAtof(argv[2]);
tmpColor->alpha = dAtof(argv[3]);
}
else
Con::printf("Color must be set as { r, g, b [,a] }");
}
//////////////////////////////////////////////////////////////////////////
// TypeColorI
//////////////////////////////////////////////////////////////////////////
ConsoleType( ColorI, TypeColorI, sizeof(ColorI) )
ConsoleGetType( TypeColorI )
{
ColorI *color = (ColorI *) dptr;
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%d %d %d %d", color->red, color->green, color->blue, color->alpha);
return returnBuffer;
}
ConsoleSetType( TypeColorI )
{
ColorI *tmpColor = (ColorI *) dptr;
if(argc == 1)
{
tmpColor->set(0, 0, 0, 255);
S32 r,g,b,a;
S32 args = dSscanf(argv[0], "%d %d %d %d", &r, &g, &b, &a);
tmpColor->red = r;
tmpColor->green = g;
tmpColor->blue = b;
if (args == 4)
tmpColor->alpha = a;
}
else if(argc == 3)
{
tmpColor->red = dAtoi(argv[0]);
tmpColor->green = dAtoi(argv[1]);
tmpColor->blue = dAtoi(argv[2]);
tmpColor->alpha = 255;
}
else if(argc == 4)
{
tmpColor->red = dAtoi(argv[0]);
tmpColor->green = dAtoi(argv[1]);
tmpColor->blue = dAtoi(argv[2]);
tmpColor->alpha = dAtoi(argv[3]);
}
else
Con::printf("Color must be set as { r, g, b [,a] }");
}
//////////////////////////////////////////////////////////////////////////
// TypeSimObjectPtr
//////////////////////////////////////////////////////////////////////////
ConsoleType( SimObjectPtr, TypeSimObjectPtr, sizeof(SimObject*) )
ConsoleSetType( TypeSimObjectPtr )
{
if(argc == 1)
{
SimObject **obj = (SimObject **)dptr;
*obj = Sim::findObject(argv[0]);
}
else
Con::printf("(TypeSimObjectPtr) Cannot set multiple args to a single S32.");
}
ConsoleGetType( TypeSimObjectPtr )
{
SimObject **obj = (SimObject**)dptr;
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%s", *obj ? (*obj)->getName() : "");
return returnBuffer;
}

38
engine/console/consoleTypes.h Executable file
View File

@ -0,0 +1,38 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONSOLETYPES_H_
#define _CONSOLETYPES_H_
#ifndef _DYNAMIC_CONSOLETYPES_H_
#include "console/dynamicTypes.h"
#endif
#ifndef Offset
#if defined(TORQUE_COMPILER_GCC) && (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
#define Offset(m,T) ((int)(&((T *)1)->m) - 1)
#else
#define Offset(x, cls) ((dsize_t)((const char *)&(((cls *)0)->x)-(const char *)0))
#endif
#endif
// Define Core Console Types
DefineConsoleType( TypeF32 )
DefineConsoleType( TypeS8 )
DefineConsoleType( TypeS32 )
DefineConsoleType( TypeS32Vector )
DefineConsoleType( TypeBool )
DefineConsoleType( TypeBoolVector )
DefineConsoleType( TypeF32Vector )
DefineConsoleType( TypeString )
DefineConsoleType( TypeCaseString )
DefineConsoleType( TypeFilename )
DefineConsoleType( TypeEnum )
DefineConsoleType( TypeFlag )
DefineConsoleType( TypeColorI )
DefineConsoleType( TypeColorF )
DefineConsoleType( TypeSimObjectPtr )
#endif

72
engine/console/dynamicTypes.cc Executable file
View File

@ -0,0 +1,72 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/dynamicTypes.h"
// Init the globals.
ConsoleBaseType *ConsoleBaseType::smListHead = NULL;
S32 ConsoleBaseType::smConsoleTypeCount = 0;
// And, we also privately store the types lookup table.
VectorPtr<ConsoleBaseType*> gConsoleTypeTable;
ConsoleBaseType *ConsoleBaseType::getListHead()
{
return smListHead;
}
void ConsoleBaseType::initialize()
{
// Prep and empty the vector.
gConsoleTypeTable.setSize(smConsoleTypeCount+1);
dMemset(gConsoleTypeTable.address(), 0, sizeof(ConsoleBaseType*) * gConsoleTypeTable.size());
// Walk the list and register each one with the console system.
ConsoleBaseType *walk = getListHead();
while(walk)
{
// Store a pointer to the type in the appropriate slot.
const S32 id = walk->getTypeID();
AssertFatal(gConsoleTypeTable[id]==NULL, "ConsoleBaseType::initialize - encountered a table slot that contained something!");
gConsoleTypeTable[id] = walk;
// Advance down the list...
walk = walk->getListNext();
}
// Alright, we're all done here; we can now achieve fast lookups by ID.
}
ConsoleBaseType *ConsoleBaseType::getType(const S32 typeID)
{
return gConsoleTypeTable[typeID];
}
//-------------------------------------------------------------------------
ConsoleBaseType::ConsoleBaseType(const S32 size, S32 *idPtr, const char *aTypeName)
{
// General initialization.
mInspectorFieldType = NULL;
// Store general info.
mTypeSize = size;
mTypeName = aTypeName;
// Get our type ID and store it.
mTypeID = smConsoleTypeCount++;
*idPtr = mTypeID;
// Link ourselves into the list.
mListNext = smListHead;
smListHead = this;
// Alright, all done for now. Console initialization time code
// takes us from having a list of general info and classes to
// a fully initialized type table.
}
ConsoleBaseType::~ConsoleBaseType()
{
// Nothing to do for now; we could unlink ourselves from the list, but why?
}

106
engine/console/dynamicTypes.h Executable file
View File

@ -0,0 +1,106 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _DYNAMIC_CONSOLETYPES_H_
#define _DYNAMIC_CONSOLETYPES_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
class ConsoleBaseType
{
protected:
/// This is used to generate unique IDs for each type.
static S32 smConsoleTypeCount;
/// We maintain a linked list of all console types; this is its head.
static ConsoleBaseType *smListHead;
/// Next item in the list of all console types.
ConsoleBaseType *mListNext;
/// Destructor is private to avoid people mucking up the list.
~ConsoleBaseType();
S32 mTypeID;
dsize_t mTypeSize;
const char *mTypeName;
const char *mInspectorFieldType;
public:
/// @name cbt_list List Interface
///
/// Interface for accessing/traversing the list of types.
/// Get the head of the list.
static ConsoleBaseType *getListHead();
/// Get the item that follows this item in the list.
ConsoleBaseType *getListNext() const
{
return mListNext;
}
/// Called once to initialize the console type system.
static void initialize();
/// Call me to get a pointer to a type's info.
static ConsoleBaseType *getType(const S32 typeID);
/// @}
/// The constructor is responsible for linking an element into the
/// master list, registering the type ID, etc.
ConsoleBaseType(const S32 size, S32 *idPtr, const char *aTypeName);
const S32 getTypeID() const { return mTypeID; }
const S32 getTypeSize() const { return mTypeSize; }
const char *getTypeName() const { return mTypeName; }
void setInspectorFieldType(const char *type) { mInspectorFieldType = type; }
const char *getInspectorFieldType() { return mInspectorFieldType; }
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag)=0;
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag )=0;
virtual const char *getTypeClassName()=0;
virtual const bool isDatablock() { return false; };
};
#define DefineConsoleType( type ) extern S32 type;
#define ConsoleType( typeName, type, size ) \
class ConsoleType##type : public ConsoleBaseType \
{ \
public: \
ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); \
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag ); \
virtual const char *getTypeClassName() { return #typeName ; } \
}; \
S32 type = -1; \
ConsoleType##type gConsoleType##type##Instance(size,&type,#type); \
#define ConsoleSetType( type ) \
void ConsoleType##type::setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag)
#define ConsoleGetType( type ) \
const char *ConsoleType##type::getData(void *dptr, EnumTable *tbl, BitSet32 flag )
#define DatablockConsoleType( typeName, type, size, className ) \
class ConsoleType##type : public ConsoleBaseType \
{ \
public: \
ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); \
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag ); \
virtual const char *getTypeClassName() { return #className; }; \
virtual const bool isDatablock() { return true; }; \
}; \
S32 type = -1; \
ConsoleType##type gConsoleType##type##Instance(size,&type,#type); \
#endif

170
engine/console/scriptObject.cc Executable file
View File

@ -0,0 +1,170 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/simBase.h"
#include "console/consoleTypes.h"
//-----------------------------------------------------------------------------
// Script object placeholder
//-----------------------------------------------------------------------------
class ScriptObject : public SimObject
{
typedef SimObject Parent;
StringTableEntry mClassName;
StringTableEntry mSuperClassName;
public:
ScriptObject();
bool onAdd();
void onRemove();
DECLARE_CONOBJECT(ScriptObject);
static void initPersistFields();
};
IMPLEMENT_CONOBJECT(ScriptObject);
void ScriptObject::initPersistFields()
{
addGroup("Classes", "Script objects have the ability to inherit and have class information.");
addField("class", TypeString, Offset(mClassName, ScriptObject), "Class of object.");
addField("superClass", TypeString, Offset(mSuperClassName, ScriptObject), "Superclass of object.");
endGroup("Classes");
}
ScriptObject::ScriptObject()
{
mClassName = "";
mSuperClassName = "";
}
bool ScriptObject::onAdd()
{
if (!Parent::onAdd())
return false;
// it's possible that all the namespace links can fail, if
// multiple objects are named the same thing with different script
// hierarchies.
// linkNamespaces will now return false and echo an error message
// rather than asserting.
// superClassName -> ScriptObject
StringTableEntry parent = StringTable->insert("ScriptObject");
if(mSuperClassName[0])
{
if(Con::linkNamespaces(parent, mSuperClassName))
parent = mSuperClassName;
}
// className -> superClassName
if (mClassName[0])
{
if(Con::linkNamespaces(parent, mClassName))
parent = mClassName;
}
// objectName -> className
StringTableEntry objectName = getName();
if (objectName && objectName[0])
{
if(Con::linkNamespaces(parent, objectName))
parent = objectName;
}
// Store our namespace
mNameSpace = Con::lookupNamespace(parent);
// Call onAdd in script!
Con::executef(this, 2, "onAdd", Con::getIntArg(getId()));
return true;
}
void ScriptObject::onRemove()
{
// Call onRemove in script!
Con::executef(this, 2, "onRemove", Con::getIntArg(getId()));
Parent::onRemove();
}
//-----------------------------------------------------------------------------
// Script group placeholder
//-----------------------------------------------------------------------------
class ScriptGroup : public SimGroup
{
typedef SimGroup Parent;
StringTableEntry mClassName;
StringTableEntry mSuperClassName;
public:
ScriptGroup();
bool onAdd();
void onRemove();
DECLARE_CONOBJECT(ScriptGroup);
static void initPersistFields();
};
IMPLEMENT_CONOBJECT(ScriptGroup);
void ScriptGroup::initPersistFields()
{
addGroup("Classes");
addField("class", TypeString, Offset(mClassName, ScriptGroup));
addField("superClass", TypeString, Offset(mSuperClassName, ScriptGroup));
endGroup("Classes");
}
ScriptGroup::ScriptGroup()
{
mClassName = "";
mSuperClassName = "";
}
bool ScriptGroup::onAdd()
{
if (!Parent::onAdd())
return false;
// superClassName -> ScriptGroup
StringTableEntry parent = StringTable->insert("ScriptGroup");
if(mSuperClassName[0])
{
if(Con::linkNamespaces(parent, mSuperClassName))
parent = mSuperClassName;
}
// className -> superClassName
if(mClassName[0])
{
if(Con::linkNamespaces(parent, mClassName))
parent = mClassName;
}
// objectName -> className
StringTableEntry objectName = getName();
if (objectName && objectName[0])
{
if(Con::linkNamespaces(parent, objectName))
parent = objectName;
}
// Store our namespace
mNameSpace = Con::lookupNamespace(parent);
// Call onAdd in script!
Con::executef(this, 2, "onAdd", Con::getIntArg(getId()));
return true;
}
void ScriptGroup::onRemove()
{
// Call onRemove in script!
Con::executef(this, 2, "onRemove", Con::getIntArg(getId()));
Parent::onRemove();
}

1739
engine/console/simBase.cc Executable file

File diff suppressed because it is too large Load Diff

1368
engine/console/simBase.h Executable file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More