Si esta parte esta pendiente, el problema a resolver consiste en lo siguiente, ¿si sabes que aun dedicando todo el tiempo de procesador no vas a terminar una tarea que haces? realmente no puedes aumentar los refrescos a pantalla porque el tiempo del procesador no te lo permite, entonces lo único que puedes hacer es cambiar el tiempo de las lecturas a entradas/salida, para que el teclado se lea efectivamente 60 veces por segundo, independientemente si la pantalla se refresca a esa velocidad o no.
Como garantizar que el numero de lecturas a teclado sean 60 por segundo si los frames por segundo pueden ir de 0-60
Sabemos que cuando un frame se toma mas tiempo del que debe debemos hacer mas lecturas a teclado, si el frame se tardara el doble o el triple o un múltiplo, es tan simple como hacer un frameskiping, es decir por cada frame que se tarde el doble (o el múltiplo) hacemos una doble (o un múltiplo) de lectura a teclado y aumentamos el numero de frames en 2 (o en el múltiplo) para que marque 60 frames por segundo con frameskiping. Pero para el caso donde no son múltiplos, esto no basta tiene que ser mucho mas dinámico.
Pensemos en el caso cuando le toma a un frame 1.5 veces terminar , si se mantuviese a esa velocidad, se refrescar la pantalla 40 veces por segundo, hace falta refrescar la lectura a teclado otras 20 veces mas.¿cuando deben hacerse dichos refrescos para que el usuario no note la diferencia? podríamos meter un frameskiping cada 2 frames es decir en el segundo frame hacer una doble lectura teclado, pero el usuario notaria el cambio en pantalla.
Implementando a nivel teórico lo anterior,
UPDATED
int fps_sincronizar()
{ char stringBuffer[255];
static unsigned int frames=0;
int tiempodesignado, tiempotrascurrido,framesideales, retraso;
frames++;
if(( update.get_ticks() > 1020 ) ||(frames==60)) {
sprintf(stringBuffer, "Map test - FPS %d", frames);//imprime el numero de frames
SDL_WM_SetCaption (stringBuffer, NULL);
frames=0;
update.start();
fps.start();
}else
{tiempodesignado=((1020- update.get_ticks())/(60-frames));
tiempotrascurrido=fps.get_ticks() ;
if(tiempotrascurrido < tiempodesignado) {
SDL_Delay( tiempodesignado- tiempotrascurrido );
fps.start();
return 1;
}
else{
framesideales= (update.get_ticks())/17;//frames que ya deveriamos a ver refrescado
retraso=framesideales-frames;
fps.start();
if((retraso)>=1)//si nos retasamos por mas de un frame
{
frames=frames+retraso;
return (1+retraso);//refresca mas la entrada y salida
}
}
}
return 1;
}
Código para maquinas lentas probado
Definí una resolución de 1280 x 1024, para alentarlo, esto hizo que el juego fuera a 10 fps sin frameskip, active el frameskip y cambio de 10 a un números alrededor de 60, la jugabilidad es casi la misma con excepción de la batalla, pues hay que hacer que el frameskip afecte a las animaciones y los tiempos, puede haber errores generados por las múltiples lecturas a teclado, por lo que me gustaría un mayor testeo.
Nuevo viernes 23 de noviembre
De cualquier manera aquí esta
Download
En primer lugar, deberíamos tener un intervalo ideal de juego para el cual todo nuestro código esté designado. Como la mayoría de casos, este suele ser 60, con lo cual:
Sin embargo, este intervalo no siempre será estable. Dependiendo de la velocidad de nuestro procesador, potencia de nuestra GPU o simplemente de la carga de procesos que exista, este valor puede fluctuar, tardando más o menos. La solución para mantener la uniformidad de nuestro programa a lo largo del tiempo es simple: debemos calcular el factor, la relación que existe entre el intervalo real y el intervalo ideal. Simplemente sería algo asi:
Δt = tintervalo/tideal
De esta forma conseguimos calcular el delta de tiempo. Obtenido este factor, simplemente consistirá en aplicarlo a cualquier valor que sea dependiente del tiempo, como contadores, aceleración o movimiento. Por ejemplo:
Δx = Δx + (4 * Δt)
El único problema en un principio que puede ofrecernos este sistema es que las cosas se muevan de forma extraña. Esto es debido a que la más minima fluctuación en la Δ de tiempo altera la uniformidad del movimiento. La solución más facil para paliar este problema consiste en, en vez de utilizar la Δt directamente, almacenarla en un array de cuantos vectores queramos y realizar la media de todas las Δt almacenadas. De esta forma se consigue un valor más uniforme.
Si quieres una implementación rapidilla de como hacerlo, sería algo así:
class CDeltaTime {
public:
// Methods
CDeltaTime(int pIdealFPS) {
// Basically, calls the set function
setIdealFPS(pIdealFPS);
}
void setIdealFPS(int pIdealFPS) {
// Change FPS settings and calculates ideal time.
idealFPS = pIdealFPS;
idealTime = 1/float(idealFPS);
clear();
}
void clear() {
// Reset time and fill the delta time array with ideal time. Prevents
// large disparities in the interval at the start of the game loop.
timePrevious = SDL_GetTicks();
timeCurrent = SDL_GetTicks();
for (int i=15; i>=0; i--) deltaTimeArray[i] = idealTime;
}
void update() {
// Calculate interval between frames
timePrevious = timeCurrent;
timeCurrent = SDL_GetTicks();
// Calculate delta interval
deltaTimeArray[deltaCurrentVector] = (float(timeCurrent-timePrevious)*0.001)/idealTime;
++deltaCurrentVector &= 0xF;
// Calculate delta time sum
for (int i=15, deltaTime = 0.0f; i>=0; i--, deltaTime+=deltaTimeArray[i]) ;
deltaTime *= 0.0625f; // It's the same as deltaTime /= 16, but faster.
}
// Attributes
long idealFPS;
float idealTime;
long timePrevious;
long timeCurrent;
float deltaTime;
float deltaTimeArray[16];
long deltaCurrentVector;
};
Me parece interesante eso de que el sistema de animación no debe de depender de la velocidad del CPU, pero a la vez, cuando estaba programando un juego(hace bastante tiempo por cierto) tuve que lidiar con el problema de que cambios en el reloj afectaban al sistema de colisiones.
Así que en estos momentos me puse a pensar un poco y se me ocurrió un sistema para evitar eso.
Se trata de tener una cola de pre(pre(bufferes)) en memoria(es la mejor palabra que encontré para lo que me quiero referir), lo que sucede es que cuando se dibuja un mundo en la pantalla normalmente lo que se hace es iterar sobre todo lo que va a estar en ella, dibujarlo en un buffer, y luego dibujar el buffer en la pantalla. A lo que yo le llamo "pre(pre(buffer))" es la información sobre lo que se va a dibujar(cordenadas de los objetos en pantalla basicamente y tambien información sobre el momento en el que se tiene pensado dibujarlo).
Así que se tendría un hilo que estuviera metiendo pre(pre(bufferes)) a la cola y otro hilo que los estuviera dibujando(y saltándose algunos si el reloj así lo permite).
A gran escala esto sería lo que harían:
Hilo 1:
mientras(noHaTerminadoElJuego())
mueveObjetos();
cola.meter(preprebuffer());
Hilo 2:
mientras(noHaTerminadoElJuego())
mientras(cola.siguiente().tiempo<tiempoActual) //Se salta todos los frames que no alcanzó a dibujar
Cola.Sacar();
si(ColaNoVacia())
dibujar(Cola.Sacar())
La ventaja de esto es que una maquina que la velocidad será la misma en una máquina que dibuje despacio(solamente la animación sería menos nítida).
La desventaja que le vería a esto sería la manera de sincronizar los controles con el hilo 1, ya que como el hilo 1 trabaja sin tomar en cuenta el tiempo, en el peor de los casos podría pasar que el jugador haga un movimiento y suceda en una de los frames que aún no se han dibujado o que suceda con retraso pero creo que se ha de poder controlar eso.
Por cierto, lobishomen... Si lo propuesto de un array por el Gigante de Yeso es una perdida de memoria... Lo tuyo ya....
Creo que son buenas ideas y que mezclandolas todas se puede sacar algo mucho mejor... Es cuestion de ir probando... Si no siempre se pueden poner todos los métodos y que el user elija cual prefiere... (esto seria un poco complicado, me parece)
¿No hay nada que te permita obtener la hora del sistema
Cuando yo hacia estas cosas pillaba la hora del sistema y le daba a cada segundo x frames... Si faltaban frames pk renderizaba rapido... Le añadia duplicados alternativamente, si sobraban, quitaba frames de por enmedio (lo que provocaba... el clasico lag del xp, ya que este usa algo asi), por lo que el movimiento se hacia uniforme...No se. Esto lo hice yo con Blitz3d... Pero en c++ no sabría hacerlo, por eso propongo la idea asi a nivel teorico...
Sin embargo, como he visto, en muchos casos es cambiar solo una clase... por lo que perfectamenbte se podrían implementar los 2 y que el usuario decida cual le va mejor... y mantener eso hasta que... se encuentre lo ideal...
Gracias por leer este toston...
PD: PC. alguien sabe algo de los google ads
(simplemente me sorprendio)...PD2: Si mezclamos lo de los buffers de lobishomen con lo de Δt... Hacer una cola de Δ, y dejar solo las justas y necesarias ya n sería tan... desperdicio como en un array...
Aunque la inestabilidad producida por fluctuaciones del Δ de tiempo entre fotogramas sea posible verla a algunos niveles, el utilizar este pequeño buffer acaba con estos problemas y da al resultado uniformidad completa (16 vectores de float, 64 bytes en total comparado con los... no se... 1GB de RAM que es el standard hoy en día, ¿dónde ves tu un desperdicio importante de memoria ahí? xD).
Sin embargo, que sea inaplicable para el codigo actual que tienes es comprensible. Es un cambio de filosofía en como manejar ciertos elementos, pero se puede aplicar perfectamente a TODO. Todos los juegos hoy en día están programados así, sin excepción. Si el caso es implementarlo, ¿porque no hacer algo así?:
extern CSystem * System;
extern CWorld * World;
extern CMath * Maths;
#define ACTOR_STATE_IDLE 0x00
#define ACTOR_STATE_MOVING 0x01
#define ACTOR_DIRECTION_UP 0x00
#define ACTOR_DIRECTION_DOWN 0x01
#define ACTOR_DIRECTION_LEFT 0x02
#define ACTOR_DIRECTION_RIGHT 0x03
#define ACTOR_FLAGS_FREEZE 0x01
#define ACTOR_SPEED_SLOW 4.0f
void CActor::MoveOnInput() {
// In case the method was called and the actor is put on freeze
// (for example, on message display or game pause)
if (flags & ACTOR_FLAGS_FREEZE) return;
// Calculate Grid X and Grid Y
GridX = long(x)>>4; GridY = long(y)>>4;
// Depending on current actor state, select between accepting input
// and motion
switch(state) {
case ACTOR_STATE_MOVING:
// Calculate how many pixels has the actor travelled and how many's left
motion.distance = Maths->Minf(motion.distance+ACTOR_SPEED_SLOW*System->TimeDelta, 16.0f);
motion.delta = Maths->Clampf(ACTOR_SPEED_SLOW*System->TimeDelta, 0, 16-motion.distance); // Clampf(value, min, max)
// Change position of character by adding the delta
switch(motion.direction) {
case ACTOR_DIRECTION_UP: y -= motion.delta; break;
case ACTOR_DIRECTION_DOWN: y += motion.delta; break;
case ACTOR_DIRECTION_LEFT: x -= motion.delta; break;
case ACTOR_DIRECTION_RIGHT: x += motion.delta; break;
}
// If the actor finished moving, set back to idle state. However, if the player still keeps pressing,
// keep going on the pressed direction (note this trick is performed with playing with the break
// statement and the switch, as when the actor finished the switch statement isn't broken,
// hence accessing again to the idle state without having to wait for another logic cycle =D)
if (motion.distance == 16.0f) state = ACTOR_STATE_IDLE;
else break;
case ACTOR_STATE_IDLE:
if (Input->Pressed(KEY_UP)==true && World->CollisionAt(GridX, GridY-1, WORLD_COLLISION_FROM_DOWN)==false) {
state = ACTOR_STATE_MOVING;
motion.direction = ACTOR_DIRECTION_UP;
motion.distance = 0.0f;
} else if (Input->Pressed(KEY_DOWN)==true && World->CollisionAt(GridX, GridY+1, WORLD_COLLISION_FROM_UP)==false) {
state = ACTOR_STATE_MOVING;
motion.direction = ACTOR_DIRECTION_DOWN;
motion.distance = 0.0f;
} else if (Input->Pressed(KEY_LEFT)==true && World->CollisionAt(GridX-1, GridY, WORLD_COLLISION_FROM_RIGHT)==false) {
state = ACTOR_STATE_MOVING;
motion.direction = ACTOR_DIRECTION_LEFT;
motion.distance = 0.0f;
} else if (Input->Pressed(KEY_RIGHT)==true && World->CollisionAt(GridX+1, GridY, WORLD_COLLISION_FROM_LEFT)==false) {
state = ACTOR_STATE_MOVING;
motion.direction = ACTOR_DIRECTION_RIGHT;
motion.distance = 0.0f;
}
break;
}
}
Aunque la implementación da por hecho que solo puede llegar a moverse un solo tile por cambio de estado, no importa pues la sensación que dará al jugador seguirá siendo la de total uniformidad en el movimiento (un RPG no necesita grandes velocidades en juego). Aunque en caso de que se quiera hacer que funcione en varias no sería mas que repetir el ciclo lógico un determinado número de veces, tal como sugiere lobishomen; mediante una cola o una simple condición para comprobar el tiempo.
Implementar el sistema en otras cosas como menús y cosas así tampoco tiene mucha dificultad, puesto que el cambio de selección suele hacerse mediante fracciones de tiempo, con lo cual solo sería comprobar la delta, o incrementar alguna clase de contador de "mantener" tecla. La verdad, yo he utilizado el sistema basado en delta de tiempo para cosas bastante complejas y siempre ha habido una u otra forma de hacer que funcionase perfectamente.
¿Cómo que no? ¿Acaso no puedes usar la delta para sincronizar las animaciones?
En fin, como el código del player veo que está desordenado (y la verdad, no es que yo haya hecho muy buena labor con el código original del mapa), supongo que acabará siendo reescrito o reorganizado de alguna u otra forma en algun punto, ¿no?. Deberías considerar el utilizar este método entonces...
¿Pero merece la pena sacrificar claridad de código a cambio de un aumento tan pequeño de eficiencia?
Pregunto porque en lo que yo estudio (informatica) se insiste mucho en la legibilidad y claridad del código, de hecho lo consideran una característica tan importante como la eficiencia casi. Por eso tampoco habría que recurrir a sacrificar los switch por montones de if's.
PD: No me manejo mucho con lo de partir temas etc... pero si esta noche ningun mod ha partido el tema intentaré hacerlo yo mismo [EDIT delaPipol: llegaste tarde, ya lo partí yo xD]
En cuanto al codigo, si que funciona, lo he implementado en Game Maker antes de postearlo para asegurarme de que no habia puesto ninguna burrada xD. Sin embargo, no entiendo eso segundo que has dicho... ¿A que te refieres con que "cuando el mapa es mas grande que la ventana"? El scroll es un mero "efecto gráfico" por así decirlo, no influye en términos de la lógica ni la estructura del programa. ¿O te refieres en el momento en que el jugador fuera a tratar de salirse la región de scroll u/o del mapa? En cuyo caso, eso debería estar gestionado en el método World->CollisionAt, devolviendo true (existe dureza en esa posición) para evitar que el actor pueda moverse en tal caso.
Por otra parte, lo que dices de las máquinas viejas es cierto, pero si nos remontamos a la historia, las FPU han estado en los PCs desde la aparición del Intel 80486 integrado en la circuitería del procesador, con lo cual incluso en los supuestos mas remotos como este caso, no creo que aunque no se utilizase, se pudiera hacer mucho... Si bien también es cierto que los procesadores ARM de los cuales estan dotados consolas portatiles como la Nintendo DS, Gameboy Advance, PDAs y móviles carecen de FPU, seguiría siendo bastante factible utilizarlo, al menos mediante punto fijo.
Como comenté antes existe una librería bastante buena de matemáticas en punto fijo que utiliza enteros de 64 bits (long long, teniendo una precisión de 32.32 para entero y decimal respectivamente) que funciona bastante bien, principalmente porque está optimizada en ensamblador para varias arquitecturas (ARM siendo una de ellas) y da bastantes buenos resultados. Hace algun tiempo lo utilice para montar un motor 3D en que todo era por software (y imaginaros que también los calculos eran mucho, mucho mas pesados que a los que podríamos enfrentarnos en un RPG, especialmente por el tema de renderizado) y mantenía bastante buen rendimiento a 133MHz. Aquí la adjunto, por si quieres echarle un vistado.
/*
* $Id: math-sll.c,v 1.15 2002/08/20 18:01:54 andrewm Exp $
*
* Change: CHUI
*
* Purpose
* A fixed point (31.32 bit) math library.
*
* Description
* Floating point packs the most accuracy in the available bits, but it
* often provides more accuracy than is required. It is time consuming to
* carry the extra precision around, particularly on platforms that don't
* have a dedicated floating point processor.
*
* This library is a compromise. All math is done using the 64 bit signed
* "long long" format (sll), and is not intended to be portable, just as
* fast as possible. Since "long long" is a elementary type, it can be
* passed around without resorting to the use of pointers. Since the
* format used is fixed point, there is never a need to do time consuming
* checks and adjustments to maintain normalized numbers, as is the case
* in floating point.
*
* Simply put, this library is limited to handling numbers with a whole
* part of up to 2^31 - 1 = 2.147483647e9 in magnitude, and fractional
* parts down to 2^-32 = 2.3283064365e-10 in magnitude. This yields a
* decent range and accuracy for many applications.
*
* IMPORTANT
* No checking for arguments out of range (error).
* No checking for divide by zero (error).
* No checking for overflow (error).
* No checking for underflow (warning).
* Chops, doesn't round.
*
* Functions
* sll dbl2sll(double x) double -> sll
* double slldbl(sll x) sll -> double
*
* sll slladd(sll x, sll y) x + y
* sll sllsub(sll x, sll y) x - y
* sll sllmul(sll x, sll y) x * y
* sll slldiv(sll x, sll y) x / y
*
* sll sllinv(sll v) 1 / x
* sll sllmul2(sll x) x * 2
* sll sllmul4(sll x) x * 4
* sll sllmul2n(sll x, int n) x * 2^n, 0 <= n <= 31
* sll slldiv2(sll x) x / 2
* sll slldiv4(sll x) x / 4
* sll slldiv2n(sll x, int n) x / 2^n, 0 <= n <= 31
*
* sll sllcos(sll x) cos x
* sll sllsin(sll x) sin x
* sll slltan(sll x) tan x
* sll sllatan(sll x) atan x
*
* sll sllexp(sll x) e^x
* sll slllog(sll x) ln x
*
* sll sllpow(sll x, sll y) x^y
* sll sllsqrt(sll x) x^(1 / 2)
*
* History
* * Aug 20 2002 Nicolas Pitre <nico@cam.org> v1.15
* - Replaced all shifting assembly with C equivalents
* - Reformated ARM asm and changed comments to begin with @
* - Updated C version of sllmul()
* - Removed the unsupported architecture #error - should be portable now
*
* * Aug 17 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.14
* - Fixed sign handling of ARM sllmul()
* - Ported sllmul() to x86 - it can be inlined now
* - Updated the sllmul() comments to reflect my changes
* - Updated the header comments
*
* * Aug 17 2002 Nicolas Pitre <nico@cam.org> v1.13
* - Corrected and expanded upon Andrew's sllmul() comments
* - Added in an non-optimal but portable C version of sllmul()
*
* * Aug 16 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.12
* - Added in corrected optimized sllmul() for ARM by Nicolas Pitre
* - Changed comments on multiplication to describe Nicolas's method
*
* * Jun 17 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.11
* - Reverted optimized sllmul() for ARM because of bug
*
* * Jun 17 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.10
* - Added in optimized sllmul() for ARM by Nicolas Pitre
* - Changed comments on multiplication to describe Nicolas's method
* - Optimized multiplications and divisions by powers of 2
*
* * Feb 5 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.9
* - Optimized multiplcations and divisions by powers of 2
*
* * Feb 5 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.8
* - Consolidated constants
* - Added macro for _slladd() _sllsub()
* - Removed __inline__ from slladd() sllsub()
* - Renamed umul() to ullmul() and made global
* - Added function prototypes
* - Corrected header comment about fractional range
* - Added warning for non-Linux operating systems
*
* * Feb 5 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.7
* - Corrected some i386 assembly comments
* - Renamed calc_*() to _sll*()
* - Moved _sllexp() closer to sllexp()
*
* * Feb 5 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.6
* - Added sllmul2() sllmul4() sllmul2n() for i386
* - Added slldiv2() slldiv4() slldiv2n() for i386
* - Removed input constraints on sllmul2() sllmul4() sllmul2n() for ARM
* - Removed input constraints on slldiv2() slldiv4() slldiv2n() for ARM
* - Modified ARM assembly for WYSIWYG output
* - Changed asm to __asm__
*
* * Feb 5 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.5
* - Fixed umul() for i386
* - Fixed dbl2sll() and sll2dbl() - I forgot ARM doubles are big-endian
* - Very lightly tested on ARM and i386 and it seems okay
*
* * Feb 4 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.4
* - Added umul() for i386
*
* * Jan 20 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.3
* - Added fast multiplication functions sllmul2(), sllmul4(), sllmul2n()
* - Added fast division functions slldiv2() slldiv(), slldiv4n()
* - Added square root function sllsqrt()
* - Added library roll-call
* - Reformatted the history to RPM format (ick)
* - Moved sllexp() closer to related functions
* - Added algorithm description to sllpow()
*
* * Jan 19 2002 Andrew E. Mileski <andrewm@isoar.ca> v1.1
* - Corrected constants, thanks to Mark A. Lisher for noticing
* - Put source under CVS control
*
* * Jan 18 2002 Andrew E. Mileski <andrewm@isoar.ca>
* - Added some more explanation to calc_cos() and calc_sin()
* - Added sllatan() and documented it fairly verbosely
*
* * July 13 2000 Andrew E. Mileski <andrewm@isoar.ca>
* - Corrected documentation for multiplication algorithm
*
* * May 21 2000 Andrew E. Mileski <andrewm@isoar.ca>
* - Rewrote slltanx() to avoid scaling argument for both sine and cosine
*
* * May 19 2000 Andrew E. Mileski <andrewm@isoar.ca>
* - Unrolled loops
* - Added sllinv(), and sllneg()
* - Changed constants to type "LL" (was "UL" - oops)
* - Changed all routines to use inverse constants instead of division
*
* * May 15, 2000 - Andrew E. Mileski <andrewm@isoar.ca>
* - Fixed slltan() - used sin/cos instead of sllsin/sllcos. Doh!
* - Added slllog(x) and sllpow(x,y)
*
* * May 11, 2000 - Andrew E. Mileski <andrewm@isoar.ca>
* - Added simple tan(x) that could stand some optimization
* - Added more constants (see <math.h>)
*
* * May 3, 2000 - Andrew E. Mileski <andrewm@isoar.ca>
* - Added sllsin(), sllcos(), and trig constants
*
* * May 2, 2000 - Andrew E. Mileski <andrewm@isoar.ca>
* - All routines and macros now have sll their identifiers
* - Changed mul() to umul() and added sllmul() to handle signed numbers
* - Added and tested sllexp(), sllint(), and sllfrac()
* - Added some constants
*
* * Apr 26, 2000 - Andrew E. Mileski <andrewm@isoar.ca>
* - Added mul(), and began testing it (unsigned only)
*
* * Apr 25, 2000 - Andrew E. Mileski <andrewm@isoar.ca>
* - Added sll2dbl() [convert a signed long long to a double]
* - Began testing. Well gee whiz it works! :)
*
* * Apr 24, 2000 - Andrew E. Mileski <andrewm@isoar.ca>
* - Added dbl2sll() [convert a double to signed long long]
* - Began documenting
*
* * Apr ??, 2000 - Andrew E. Mileski <andrewm@isoar.ca>
* - Conceived, written, and fiddled with
*
*
* Copyright (C) 2000 Andrew E. Mileski
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2 as published by the Free Software Foundation.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef MATHSLL_H
#define MATHSLL_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef USE_FIXED_POINT
#include<math.h>
typedef double sll;
typedef double ull;
#define sllvalue(X) ((double)X)
#define int2sll(X) ((sll)(X))
#define sll2int(X) ((int)(X))
#define sll_abs(X) (fabs(X))
#define sllneg(X) (-(X))
static __inline__ double sll2dbl(sll x)
{
return (double)x;
}
static __inline__ sll slladd(sll x, sll y)
{
return x + y;
}
static __inline__ sll sllsub(sll x, sll y)
{
return x - y;
}
static __inline__ sll sllmul(sll x, sll y)
{
return x * y;
}
static __inline__ sll slldiv(sll x, sll y)
{
return x / y;
}
static __inline__ sll sllinv(sll x)
{
return 1 / x;
}
static __inline__ float sll2float(sll s)
{
return (float)s;
}
static __inline__ sll float2sll(float f)
{
return (sll)f;
}
static __inline__ sll sllsin(sll x)
{
return sin(x);
}
static __inline__ sll sllcos(sll x)
{
return cos(x);
}
static __inline__ sll slltan(sll x)
{
return tan(x);
}
static __inline__ sll sllsqrt(sll x)
{
return sqrt(x);
}
static __inline__ sll dbl2sll(double dbl)
{
return (float)dbl;
}
static __inline__ sll sllpow(sll x, sll y)
{
return pow(x,y);
}
#else
/* Data types */
typedef signed long long sll;
typedef unsigned long long ull;
/* Macros */
#define int2sll(X) (((sll) (X)) << 32)
#define sllvalue(X) (X)
#define sll2int(X) ((int) ((X) >> 32))
#define sll_abs(X) ((X) & 0xefffffffffffffffLL)
#define sllint(X) ((X) & 0xffffffff00000000LL)
#define sllfrac(X) ((X) & 0x00000000ffffffffLL)
#define sllneg(X) (-(X))
#define _slladd(X,Y) ((X) + (Y))
#define _sllsub(X,Y) ((X) - (Y))
/* Constants (converted from double) */
#define CONST_0 0x0000000000000000LL
#define CONST_1 0x0000000100000000LL
#define CONST_2 0x0000000200000000LL
#define CONST_3 0x0000000300000000LL
#define CONST_4 0x0000000400000000LL
#define CONST_10 0x0000000a00000000LL
#define CONST_1_2 0x0000000080000000LL
#define CONST_1_3 0x0000000055555555LL
#define CONST_1_4 0x0000000040000000LL
#define CONST_1_5 0x0000000033333333LL
#define CONST_1_6 0x000000002aaaaaaaLL
#define CONST_1_7 0x0000000024924924LL
#define CONST_1_8 0x0000000020000000LL
#define CONST_1_9 0x000000001c71c71cLL
#define CONST_1_10 0x0000000019999999LL
#define CONST_1_11 0x000000001745d174LL
#define CONST_1_12 0x0000000015555555LL
#define CONST_1_20 0x000000000cccccccLL
#define CONST_1_30 0x0000000008888888LL
#define CONST_1_42 0x0000000006186186LL
#define CONST_1_56 0x0000000004924924LL
#define CONST_1_72 0x00000000038e38e3LL
#define CONST_1_90 0x0000000002d82d82LL
#define CONST_1_110 0x000000000253c825LL
#define CONST_1_132 0x0000000001f07c1fLL
#define CONST_1_156 0x0000000001a41a41LL
#define CONST_E 0x00000002b7e15162LL
#define CONST_1_E 0x000000005e2d58d8LL
#define CONST_SQRTE 0x00000001a61298e1LL
#define CONST_1_SQRTE 0x000000009b4597e3LL
#define CONST_LOG2_E 0x0000000171547652LL
#define CONST_LOG10_E 0x000000006f2dec54LL
#define CONST_LN2 0x00000000b17217f7LL
#define CONST_LN10 0x000000024d763776LL
#define CONST_PI 0x00000003243f6a88LL
#define CONST_PI_2 0x00000001921fb544LL
#define CONST_PI_4 0x00000000c90fdaa2LL
#define CONST_1_PI 0x00000000517cc1b7LL
#define CONST_2_PI 0x00000000a2f9836eLL
#define CONST_2_SQRTPI 0x0000000120dd7504LL
#define CONST_SQRT2 0x000000016a09e667LL
#define CONST_1_SQRT2 0x00000000b504f333LL
static __inline__ sll slladd(sll x, sll y)
{
return (x + y);
}
static __inline__ sll sllsub(sll x, sll y)
{
return (x - y);
}
/*
* Let a = A * 2^32 + a_hi * 2^0 + a_lo * 2^(-32)
* Let b = B * 2^32 + b_hi * 2^0 + b_lo * 2^(-32)
*
* Where:
* *_hi is the integer part
* *_lo the fractional part
* A and B are the sign (0 for positive, -1 for negative).
*
* a * b = (A * 2^32 + a_hi * 2^0 + a_lo * 2^-32)
* * (B * 2^32 + b_hi * 2^0 + b_lo * 2^-32)
*
* Expanding the terms, we get:
*
* = A * B * 2^64 + A * b_h * 2^32 + A * b_l * 2^0
* + a_h * B * 2^32 + a_h * b_h * 2^0 + a_h * b_l * 2^-32
* + a_l * B * 2^0 + a_l * b_h * 2^-32 + a_l * b_l * 2^-64
*
* Grouping by powers of 2, we get:
*
* = A * B * 2^64
* Meaningless overflow from sign extension - ignore
*
* + (A * b_h + a_h * B) * 2^32
* Overflow which we can't handle - ignore
*
* + (A * b_l + a_h * b_h + a_l * B) * 2^0
* We only need the low 32 bits of this term, as the rest is overflow
*
* + (a_h * b_l + a_l * b_h) * 2^-32
* We need all 64 bits of this term
*
* + a_l * b_l * 2^-64
* We only need the high 32 bits of this term, as the rest is underflow
*
* Note that:
* a > 0 && b > 0: A = 0, B = 0 and the third term is a_h * b_h
* a < 0 && b > 0: A = -1, B = 0 and the third term is a_h * b_h - b_l
* a > 0 && b < 0: A = 0, B = -1 and the third term is a_h * b_h - a_l
* a < 0 && b < 0: A = -1, B = -1 and the third term is a_h * b_h - a_l - b_l
*/
#if defined(__arm__)
static __inline__ sll sllmul(sll left, sll right)
{
/*
* From gcc/config/arm/arm.h:
* In a pair of registers containing a DI or DF value the 'Q'
* operand returns the register number of the register containing
* the least significant part of the value. The 'R' operand returns
* the register number of the register containing the most
* significant part of the value.
*/
sll retval;
__asm__ (
"@ sllmul\n\t"
"umull %R0, %Q0, %Q1, %Q2\n\t"
"mul %R0, %R1, %R2\n\t"
"umlal %Q0, %R0, %Q1, %R2\n\t"
"umlal %Q0, %R0, %Q2, %R1\n\t"
"tst %R1, #0x80000000\n\t"
"subne %R0, %R0, %Q2\n\t"
"tst %R2, #0x80000000\n\t"
"subne %R0, %R0, %Q1\n\t"
: "=&r" (retval)
: "%r" (left), "r" (right)
: "cc"
);
return retval;
}
#elif defined(__i386__)
static __inline__ sll sllmul(sll left, sll right)
{
register sll retval;
__asm__(
"# sllmul\n\t"
" movl %1, %%eax\n\t"
" mull %3\n\t"
" movl %%edx, %%ebx\n\t"
"\n\t"
" movl %2, %%eax\n\t"
" mull %4\n\t"
" movl %%eax, %%ecx\n\t"
"\n\t"
" movl %1, %%eax\n\t"
" mull %4\n\t"
" addl %%eax, %%ebx\n\t"
" adcl %%edx, %%ecx\n\t"
"\n\t"
" movl %2, %%eax\n\t"
" mull %3\n\t"
" addl %%ebx, %%eax\n\t"
" adcl %%ecx, %%edx\n\t"
"\n\t"
" btl $31, %2\n\t"
" jnc 1f\n\t"
" subl %3, %%edx\n\t"
"1: btl $31, %4\n\t"
" jnc 1f\n\t"
" subl %1, %%edx\n\t"
"1:\n\t"
: "=&A" (retval)
: "m" (left), "m" (((unsigned *) &left)[1]),
"m" (right), "m" (((unsigned *) &right)[1])
: "ebx", "ecx", "cc"
);
return retval;
}
#else
/* Plain C version: not optimal but portable. */
#warning Fixed Point no optimal
static __inline__ sll sllmul(sll a, sll b)
{
unsigned int a_lo, b_lo;
signed int a_hi, b_hi;
sll x;
a_lo = a;
a_hi = (ull) a >> 32;
b_lo = b;
b_hi = (ull) b >> 32;
x = ((ull) (a_hi * b_hi) << 32)
+ (((ull) a_lo * b_lo) >> 32)
+ (sll) a_lo * b_hi
+ (sll) b_lo * a_hi;
return x;
}
#endif
static __inline__ sll sllinv(sll v)
{
int sgn = 0;
sll u;
ull s = -1;
/* Use positive numbers, or the approximation won't work */
if (v < CONST_0) {
v = sllneg(v);
sgn = 1;
}
/* An approximation - must be larger than the actual value */
for (u = v; u; ((ull)u) >>= 1)
s >>= 1;
/* Newton's Method */
u = sllmul(s, _sllsub(CONST_2, sllmul(v, s)));
u = sllmul(u, _sllsub(CONST_2, sllmul(v, u)));
u = sllmul(u, _sllsub(CONST_2, sllmul(v, u)));
u = sllmul(u, _sllsub(CONST_2, sllmul(v, u)));
u = sllmul(u, _sllsub(CONST_2, sllmul(v, u)));
u = sllmul(u, _sllsub(CONST_2, sllmul(v, u)));
return ((sgn) ? sllneg(u): u);
}
static __inline__ sll slldiv(sll left, sll right)
{
return sllmul(left, sllinv(right));
}
static __inline__ sll sllmul2(sll x)
{
return x << 1;
}
static __inline__ sll sllmul4(sll x)
{
return x << 2;
}
static __inline__ sll sllmul2n(sll x, int n)
{
sll y;
#if defined(__arm__)
/*
* On ARM we need to do explicit assembly since the compiler
* doesn't know the range of n is limited and decides to call
* a library function instead.
*/
__asm__ (
"@ sllmul2n\n\t"
"mov %R0, %R1, lsl %2\n\t"
"orr %R0, %R0, %Q1, lsr %3\n\t"
"mov %Q0, %Q1, lsl %2\n\t"
: "=r" (y)
: "r" (x), "rM" (n), "rM" (32 - n)
);
#else
y = x << n;
#endif
return y;
}
static __inline__ sll slldiv2(sll x)
{
return x >> 1;
}
static __inline__ sll slldiv4(sll x)
{
return x >> 2;
}
static __inline__ sll slldiv2n(sll x, int n)
{
sll y;
#if defined(__arm__)
/*
* On ARM we need to do explicit assembly since the compiler
* doesn't know the range of n is limited and decides to call
* a library function instead.
*/
__asm__ (
"@ slldiv2n\n\t"
"mov %Q0, %Q1, lsr %2\n\t"
"orr %Q0, %Q0, %R1, lsl %3\n\t"
"mov %R0, %R1, asr %2\n\t"
: "=r" (y)
: "r" (x), "rM" (n), "rM" (32 - n)
);
#else
y = x >> n;
#endif
return y;
}
/*
* Unpack the IEEE floating point double format and put it in fixed point
* sll format.
*/
static __inline__ sll dbl2sll(double dbl)
{
union {
double d;
unsigned u[2];
ull ull;
sll sll;
} in, retval;
register unsigned exp;
/* Move into memory as args might be passed in regs */
in.d = dbl;
#if defined(__arm__)
/* ARM architecture has a big-endian double */
exp = in.u[0];
in.u[0] = in.u[1];
in.u[1] = exp;
#endif /* defined(__arm__) */
/* Leading 1 is assumed by IEEE */
retval.u[1] = 0x40000000;
/* Unpack the mantissa into the unsigned long */
retval.u[1] |= (in.u[1] << 10) & 0x3ffffc00;
retval.u[1] |= (in.u[0] >> 22) & 0x000003ff;
retval.u[0] = in.u[0] << 10;
/* Extract the exponent and align the decimals */
exp = (in.u[1] >> 20) & 0x7ff;
if (exp)
retval.ull >>= 1053 - exp;
else
return 0L;
/* Negate if negative flag set */
if (in.u[1] & 0x80000000)
retval.sll = -retval.sll;
return retval.sll;
}
static __inline__ sll float2sll(float f)
{
return dbl2sll((double)f);
}
static __inline__ double sll2dbl(sll s)
{
union {
double d;
unsigned u[2];
ull ull;
sll sll;
} in, retval;
register unsigned exp;
register unsigned flag;
if (s == 0)
return 0.0;
/* Move into memory as args might be passed in regs */
in.sll = s;
/* Handle the negative flag */
if (in.sll < 1) {
flag = 0x80000000;
in.ull = sllneg(in.sll);
} else
flag = 0x00000000;
/* Normalize */
for (exp = 1053; in.ull && (in.u[1] & 0x80000000) == 0; exp--) {
in.ull <<= 1;
}
in.ull <<= 1;
exp++;
in.ull >>= 12;
retval.ull = in.ull;
retval.u[1] |= flag | (exp << 20);
#if defined(__arm__)
/* ARM architecture has a big-endian double */
exp = retval.u[0];
retval.u[0] = retval.u[1];
retval.u[1] = exp;
#endif /* defined(__arm__) */
return retval.d;
}
static __inline__ float sll2float(sll s)
{
return ((float)sll2dbl(s));
}
/*
* Calculate cos x where -pi/4 <= x <= pi/4
*
* Description:
* cos x = 1 - x^2 / 2! + x^4 / 4! - ... + x^(2N) / (2N)!
* Note that (pi/4)^12 / 12! < 2^-32 which is the smallest possible number.
*/
static __inline__ sll _sllcos(sll x)
{
sll retval, x2;
x2 = sllmul(x, x);
/*
* cos x = t0 + t1 + t2 + t3 + t4 + t5 + t6
*
* f0 = 0! = 1
* f1 = 2! = 2 * 1 * f0 = 2 * f0
* f2 = 4! = 4 * 3 * f1 = 12 x f1
* f3 = 6! = 6 * 5 * f2 = 30 * f2
* f4 = 8! = 8 * 7 * f3 = 56 * f3
* f5 = 10! = 10 * 9 * f4 = 90 * f4
* f6 = 12! = 12 * 11 * f5 = 132 * f5
*
* t0 = 1
* t1 = -t0 * x2 / 2 = -t0 * x2 * CONST_1_2
* t2 = -t1 * x2 / 12 = -t1 * x2 * CONST_1_12
* t3 = -t2 * x2 / 30 = -t2 * x2 * CONST_1_30
* t4 = -t3 * x2 / 56 = -t3 * x2 * CONST_1_56
* t5 = -t4 * x2 / 90 = -t4 * x2 * CONST_1_90
* t6 = -t5 * x2 / 132 = -t5 * x2 * CONST_1_132
*/
retval = _sllsub(CONST_1, sllmul(x2, CONST_1_132));
retval = _sllsub(CONST_1, sllmul(sllmul(x2, retval), CONST_1_90));
retval = _sllsub(CONST_1, sllmul(sllmul(x2, retval), CONST_1_56));
retval = _sllsub(CONST_1, sllmul(sllmul(x2, retval), CONST_1_30));
retval = _sllsub(CONST_1, sllmul(sllmul(x2, retval), CONST_1_12));
retval = _sllsub(CONST_1, slldiv2(sllmul(x2, retval)));
return retval;
}
/*
* Calculate sin x where -pi/4 <= x <= pi/4
*
* Description:
* sin x = x - x^3 / 3! + x^5 / 5! - ... + x^(2N+1) / (2N+1)!
* Note that (pi/4)^13 / 13! < 2^-32 which is the smallest possible number.
*/
static __inline__ sll _sllsin(sll x)
{
sll retval, x2;
x2 = sllmul(x, x);
/*
* sin x = t0 + t1 + t2 + t3 + t4 + t5 + t6
*
* f0 = 0! = 1
* f1 = 3! = 3 * 2 * f0 = 6 * f0
* f2 = 5! = 5 * 4 * f1 = 20 x f1
* f3 = 7! = 7 * 6 * f2 = 42 * f2
* f4 = 9! = 9 * 8 * f3 = 72 * f3
* f5 = 11! = 11 * 10 * f4 = 110 * f4
* f6 = 13! = 13 * 12 * f5 = 156 * f5
*
* t0 = 1
* t1 = -t0 * x2 / 6 = -t0 * x2 * CONST_1_6
* t2 = -t1 * x2 / 20 = -t1 * x2 * CONST_1_20
* t3 = -t2 * x2 / 42 = -t2 * x2 * CONST_1_42
* t4 = -t3 * x2 / 72 = -t3 * x2 * CONST_1_72
* t5 = -t4 * x2 / 110 = -t4 * x2 * CONST_1_110
* t6 = -t5 * x2 / 156 = -t5 * x2 * CONST_1_156
*/
retval = _sllsub(x, sllmul(x2, CONST_1_156));
retval = _sllsub(x, sllmul(sllmul(x2, retval), CONST_1_110));
retval = _sllsub(x, sllmul(sllmul(x2, retval), CONST_1_72));
retval = _sllsub(x, sllmul(sllmul(x2, retval), CONST_1_42));
retval = _sllsub(x, sllmul(sllmul(x2, retval), CONST_1_20));
retval = _sllsub(x, sllmul(sllmul(x2, retval), CONST_1_6));
return retval;
}
static __inline__ sll sllcos(sll x)
{
int i;
sll retval;
/* Calculate cos (x - i * pi/2), where -pi/4 <= x - i * pi/2 <= pi/4 */
i = sll2int(_slladd(sllmul(x, CONST_2_PI), CONST_1_2));
x = _sllsub(x, sllmul(int2sll(i), CONST_PI_2));
switch (i & 3) {
default:
case 0:
retval = _sllcos(x);
break;
case 1:
retval = sllneg(_sllsin(x));
break;
case 2:
retval = sllneg(_sllcos(x));
break;
case 3:
retval = _sllsin(x);
break;
}
return retval;
}
static __inline__ sll sllsin(sll x)
{
int i;
sll retval;
/* Calculate sin (x - n * pi/2), where -pi/4 <= x - i * pi/2 <= pi/4 */
i = sll2int(_slladd(sllmul(x, CONST_2_PI), CONST_1_2));
x = _sllsub(x, sllmul(int2sll(i), CONST_PI_2));
switch (i & 3) {
default:
case 0:
retval = _sllsin(x);
break;
case 1:
retval = _sllcos(x);
break;
case 2:
retval = sllneg(_sllsin(x));
break;
case 3:
retval = sllneg(_sllcos(x));
break;
}
return retval;
}
static __inline__ sll slltan(sll x)
{
int i;
sll retval;
i = sll2int(_slladd(sllmul(x, CONST_2_PI), CONST_1_2));
x = _sllsub(x, sllmul(int2sll(i), CONST_PI_2));
switch (i & 3) {
default:
case 0:
case 2:
retval = slldiv(_sllsin(x), _sllcos(x));
break;
case 1:
case 3:
retval = sllneg(slldiv(_sllcos(x), _sllsin(x)));
break;
}
return retval;
}
/*
* atan x = SUM[n=0,) (-1)^n * x^(2n + 1)/(2n + 1), |x| < 1
*
* Two term approximation
* a = x - x^3/3
* Gives us
* atan x = a + ??
* Let ?? = arctan ?
* atan x = a + arctan ?
* Rearrange
* atan x - a = arctan ?
* Apply tan to both sides
* tan (atan x - a) = tan arctan ?
* tan (atan x - a) = ?
* Applying the standard formula
* tan (u - v) = (tan u - tan v) / (1 + tan u * tan v)
* Gives us
* tan (atan x - a) = (tan atan x - tan a) / (1 + tan arctan x * tan a)
* Let t = tan a
* tan (atan x - a) = (x - t) / (1 + x * t)
* So finally
* arctan x = a + arctan ((tan x - t) / (1 + x * t))
* And the typical worst case is x = 1.0 which converges in 3 iterations.
*/
static __inline__ sll _sllatan(sll x)
{
sll a, t, retval;
/* First iteration */
a = sllmul(x, _sllsub(CONST_1, sllmul(x, sllmul(x, CONST_1_3))));
retval = a;
/* Second iteration */
t = slldiv(_sllsin(a), _sllcos(a));
x = slldiv(_sllsub(x, t), _slladd(CONST_1, sllmul(t, x)));
a = sllmul(x, _sllsub(CONST_1, sllmul(x, sllmul(x, CONST_1_3))));
retval = _slladd(retval, a);
/* Third iteration */
t = slldiv(_sllsin(a), _sllcos(a));
x = slldiv(_sllsub(x, t), _slladd(CONST_1, sllmul(t, x)));
a = sllmul(x, _sllsub(CONST_1, sllmul(x, sllmul(x, CONST_1_3))));
return _slladd(retval, a);
}
static __inline__ sll sllatan(sll x)
{
sll retval;
if (x < -sllneg(CONST_1))
retval = sllneg(CONST_PI_2);
else if (x > CONST_1)
retval = CONST_PI_2;
else
return _sllatan(x);
return _sllsub(retval, _sllatan(sllinv(x)));
}
/*
* Calculate e^x where -0.5 <= x <= 0.5
*
* Description:
* e^x = x^0 / 0! + x^1 / 1! + ... + x^N / N!
* Note that 0.5^11 / 11! < 2^-32 which is the smallest possible number.
*/
static __inline__ sll _sllexp(sll x)
{
sll retval;
retval = _slladd(CONST_1, sllmul(0, sllmul(x, CONST_1_11)));
retval = _slladd(CONST_1, sllmul(retval, sllmul(x, CONST_1_11)));
retval = _slladd(CONST_1, sllmul(retval, sllmul(x, CONST_1_10)));
retval = _slladd(CONST_1, sllmul(retval, sllmul(x, CONST_1_9)));
retval = _slladd(CONST_1, sllmul(retval, slldiv2n(x, 3)));
retval = _slladd(CONST_1, sllmul(retval, sllmul(x, CONST_1_7)));
retval = _slladd(CONST_1, sllmul(retval, sllmul(x, CONST_1_6)));
retval = _slladd(CONST_1, sllmul(retval, sllmul(x, CONST_1_5)));
retval = _slladd(CONST_1, sllmul(retval, slldiv4(x)));
retval = _slladd(CONST_1, sllmul(retval, sllmul(x, CONST_1_3)));
retval = _slladd(CONST_1, sllmul(retval, slldiv2(x)));
return retval;
}
/*
* Calculate e^x where x is arbitrary
*/
static __inline__ sll sllexp(sll x)
{
int i;
sll e, retval;
e = CONST_E;
/* -0.5 <= x <= 0.5 */
i = sll2int(_slladd(x, CONST_1_2));
retval = _sllexp(_sllsub(x, int2sll(i)));
/* i >= 0 */
if (i < 0) {
i = -i;
e = CONST_1_E;
}
/* Scale the result */
for (;i; i >>= 1) {
if (i & 1)
retval = sllmul(retval, e);
e = sllmul(e, e);
}
return retval;
}
/*
* Calculate natural logarithm using Netwton-Raphson method
*/
static __inline__ sll slllog(sll x)
{
sll x1, ln = 0;
/* Scale: e^(-1/2) <= x <= e^(1/2) */
while (x < CONST_1_SQRTE) {
ln = _sllsub(ln, CONST_1);
x = sllmul(x, CONST_E);
}
while (x > CONST_SQRTE) {
ln = _slladd(ln, CONST_1);
x = sllmul(x, CONST_1_E);
}
/* First iteration */
x1 = sllmul(_sllsub(x, CONST_1), slldiv2(_sllsub(x, CONST_3)));
ln = _sllsub(ln, x1);
x = sllmul(x, _sllexp(x1));
/* Second iteration */
x1 = sllmul(_sllsub(x, CONST_1), slldiv2(_sllsub(x, CONST_3)));
ln = _sllsub(ln, x1);
x = sllmul(x, _sllexp(x1));
/* Third iteration */
x1 = sllmul(_sllsub(x, CONST_1), slldiv2(_sllsub(x, CONST_3)));
ln = _sllsub(ln, x1);
return ln;
}
/*
* ln x^y = y * log x
* e^(ln x^y) = e^(y * log x)
* x^y = e^(y * ln x)
*/
static __inline__ sll sllpow(sll x, sll y)
{
if (y == CONST_0)
return CONST_1;
return sllexp(sllmul(y, slllog(x)));
}
/*
* Consider a parabola centered on the y-axis
* y = a * x^2 + b
* Has zeros (y = 0) at
* a * x^2 + b = 0
* a * x^2 = -b
* x^2 = -b / a
* x = +- (-b / a)^(1 / 2)
* Letting a = 1 and b = -X
* y = x^2 - X
* x = +- X^(1 / 2)
* Which is convenient since we want to find the square root of X, and we can
* use Newton's Method to find the zeros of any f(x)
* xn = x - f(x) / f'(x)
* Applied Newton's Method to our parabola
* f(x) = x^2 - X
* xn = x - (x^2 - X) / (2 * x)
* xn = x - (x - X / x) / 2
* To make this converge quickly, we scale X so that
* X = 4^N * z
* Taking the roots of both sides
* X^(1 / 2) = (4^n * z)^(1 / 2)
* X^(1 / 2) = 2^n * z^(1 / 2)
* Let N = 2^n
* x^(1 / 2) = N * z^(1 / 2)
* We want this to converge to the positive root, so we must start at a point
* 0 < start <= x^(1 / 2)
* or
* x^(1/2) <= start <= infinity
* since
* (1/2)^(1/2) = 0.707
* 2^(1/2) = 1.414
* A good choice is 1 which lies in the middle, and takes 4 iterations to
* converge from either extreme.
*/
static __inline__ sll sllsqrt(sll x)
{
sll n, xn;
/* Start with a scaling factor of 1 */
n = CONST_1;
/* Quick solutions for the simple cases */
if (x <= CONST_0 || x == CONST_1)
return x;
/* Scale x so that 0.5 <= x < 2 */
while (x >= CONST_2) {
x = slldiv4(x);
n = sllmul2(n);
}
while (x < CONST_1_2) {
x = sllmul4(x);
n = slldiv2(n);
}
/* Simple solution if x = 4^n */
if (x == CONST_1)
return n;
/* The starting point */
xn = CONST_1;
/* Four iterations will be enough */
xn = _sllsub(xn, slldiv2(_sllsub(xn, slldiv(x, xn))));
xn = _sllsub(xn, slldiv2(_sllsub(xn, slldiv(x, xn))));
xn = _sllsub(xn, slldiv2(_sllsub(xn, slldiv(x, xn))));
xn = _sllsub(xn, slldiv2(_sllsub(xn, slldiv(x, xn))));
/* Scale the result */
return sllmul(n, xn);
}
#endif
#ifdef __cplusplus
}
#endif
#endif /* MATHSLL_H */
En otro orden de cosas, el utilizar switchs en este caso (aparte de mejorar la visibilidad del código) a efectos de rendimiento no tendría mucha mayor repercusión que utilizar ifs, puesto que una vez compilado y optimizado, las operaciones a realizar por el procesador serían practicamente identicas en la mayoria de los switchs. Sin embargo, nos aprovechamos de la estructura del switch, jugando con el break para poder realizar una pequeña optimización en caso de que el jugador mantenga el boton presionado para caminar, en caso de que una vez el actor haya terminado de moverse, no deba esperar a la siguiente iteración de lógica.
En cuanto consiga algo de tiempo veré si puedo hacer una pequeña demo con este sistema, pero no aseguro cuando podré sacarlo. Apenas tengo tiempo estos días, y ya estoy cargado hasta las cejas de tareas.
Si podríamos/deberíamos hacer uso de esto,( yo no sabia donde estaba el código, ni quien es el tal Chuy -_-), no tengo inconvenietne con usar punto fijo.
Si veras, para dar el "efecto gráfico" de movimiento se hacen 2 cosas, mover el grafico del mapa mientras el grafico de player queda fijo, o mover el grafico del player mientras el mapa queda fijo, en el maker normalmente se mueve el grafico del player, pero cuando el mapa mide mas de 32 *24 es decir es mas grande que la ventana y el grafico del palyer llegas al centro de la pantalla no se mueve el grafico del player sino que mueve el grafico del mapa, eso es lo que dije que falta.
Los switchs te incluyen instrucciones de ciclado que casi nunca se utilizan y algunos compiladores no hacen bien el optimizado de código, es por seguridad mas que nada, yo se no es el caso de linux.
[/quote]
void CWorld::Scroll(CActor * actor) {
// If no valid actor was passed within the method call,
// there's no need to continue further.
if (actor == NULL) exit;
// Calculate scroll in top-left position and limit scroll position
// to stay within the map boundaries. Note we convert the map
// size to real pixel size by shifting to left 4 bits (wich equals to
// multiply by 16)
scroll.x = Maths->Clamp(actor->x - (System->Driver->WindowWidth()>>1), 0, (Map->Width<<4)-System->Driver->WindowWidth());
scroll.y = Maths->Clamp(actor->y - (System->Driver->WindowHeight()>>1), 0, (Map->Height<<4)-System->Driver->WindowHeight());
}
Despues de esto, todo es cuestión de renderizar el mundo acorde a este offset. Basicamente restando a la posición de los actores o añadiendole como offset al renderizador del mapa estos valores.
sobre el movimiento del personaje, en mi experiencia con el maker
en el RPG Maker 2000/2003 el personaje está "centrado" horizontalmente 16 pixeles a la derecha (no está en el centro), y está centrado 16 pixeles verticales hacia arriba, quedando exactamente en el hueco de un tile
el RPG Maker XP al colocar un chara, en forma vertical, lo divide a la mitad y le suma 32 (tamaño_chara_pantalla = tamaño_vertical_chara_original / 2 + 32), mas o menos asi, eso pa quedar en el hueco de un tile
digo mínimo, para que se imite bien, y como dije en un anterior post, se lee a 20 (40 en XP), se muestran 20 fps, o almenos eso me parecio en mi experiencia
el personaje tiene una secuencia al avanzar, con este formato: (x-x),(x-x)
si se oprime la tecla de abajo, y solo da un paso, la secuencia es: 0,2-1,2, usando 2 frames por cada frame (de 20 frames por segundo o 0.2 seg aproximadamente), si sigue avanzando es: 0,2-1,2-0,2-2,2 y asi infinitamente... hasta que se deje de oprimir la tecla de abajo...
[attachment=558]
[attachment=559]