yes w3school print javascript graphics html5-canvas game-physics trigonometry

javascript - w3school - Campo de estrellas giratorio de lona



send alert javascript (2)

Estoy tomando el siguiente enfoque para animar un campo de estrellas a través de la pantalla, pero estoy atascado para la siguiente parte.

JS

var c = document.getElementById(''stars''), ctx = c.getContext("2d"), t = 0; // time c.width = 300; c.height = 300; var w = c.width, h = c.height, z = c.height, v = Math.PI; // angle of vision (function animate() { Math.seedrandom(''bg''); ctx.globalAlpha = 1; for (var i = 0; i <= 100; i++) { var x = Math.floor(Math.random() * w), // pos x y = Math.floor(Math.random() * h), // pos y r = Math.random()*2 + 1, // radius a = Math.random()*0.5 + 0.5, // alpha // linear d = (r*a), // depth p = t*d; // pixels per t x = x - p; // movement x = x - w * Math.floor(x / w); // go around when x < 0 (function draw(x,y) { var gradient = ctx.createRadialGradient(x, y, 0, x + r, y + r, r * 2); gradient.addColorStop(0, ''rgba(255, 255, 255, '' + a + '')''); gradient.addColorStop(1, ''rgba(0, 0, 0, 0)''); ctx.beginPath(); ctx.arc(x, y, r, 0, 2*Math.PI); ctx.fillStyle = gradient; ctx.fill(); return draw; })(x, y); } ctx.restore(); t += 1; requestAnimationFrame(function() { ctx.clearRect(0, 0, c.width, c.height); animate(); }); })();

HTML

<canvas id="stars"></canvas>

CSS

canvas { background: black; }

JSFiddle

Lo que hace ahora es animar cada estrella con un delta X que considera la opacidad y el tamaño de la estrella, por lo que los más pequeños parecen moverse más lentamente.

Utilice p = t; tener todas las estrellas moviéndose a la misma velocidad.

PREGUNTA

Estoy buscando un modelo claramente definido donde las velocidades den la ilusión de las estrellas que giran alrededor del expectador , definidas en términos del centro de la rotación cX, cY y el ángulo de visión v que es la fracción de 2π que puede ser visto (si el centro del círculo no es el centro de la pantalla, el radio debe ser al menos la parte más grande). Estoy luchando para encontrar una manera que aplique este coseno a la velocidad de los movimientos de las estrellas, incluso para un círculo centrado con una rotación de π.

Estos diagramas podrían explicar con más detalle lo que busco:

Círculo centrado:

No centrado:

Ángulo de visión diferente:

Estoy realmente perdido en cuanto a cómo avanzar. Ya me estiré un poco para llegar hasta aquí. ¿Me pueden ayudar con algunos primeros pasos?

Gracias

ACTUALIZAR

He hecho algunos progresos con este código:

// linear d = (r*a)*z, // depth v = (2*Math.PI)/w, p = Math.floor( d * Math.cos( t * v ) ); // pixels per t x = x + p; // movement x = x - w * Math.floor(x / w); // go around when x < 0

JSFiddle

Donde p es la coordenada x de una partícula en movimiento circular uniforme y v es la velocidad angular, pero esto genera un efecto de péndulo. No estoy seguro de cómo cambiar estas ecuaciones para crear la ilusión de que el observador está cambiando.

ACTUALIZACIÓN 2:

Casi allí. Un usuario en el canal ## Math freenode tuvo la amabilidad de sugerir el siguiente cálculo:

// linear d = (r*a), // depth p = t*d; // pixels per t x = x - p; // movement x = x - w * Math.floor(x / w); // go around when x < 0 x = (x / w) - 0.5; y = (y / h) - 0.5; y /= Math.cos(x); x = (x + 0.5) * w; y = (y + 0.5) * h;

JSFiddle

Esto logra el efecto visualmente, pero no sigue un modelo claramente definido en términos de las variables (simplemente "corta" el efecto), por lo que no puedo ver una forma sencilla de realizar diferentes implementaciones (cambiar el centro, el ángulo de visión). El modelo real podría ser muy similar a este.

ACTUALIZACIÓN 3

Siguiendo la respuesta de Iftah, pude usar Sylvester para aplicar una matriz de rotación a las estrellas, que deben guardarse primero en una matriz. Además, la coordenada z cada estrella ahora está determinada y el radio r y la opacidad a se derivan de ella. El código es sustancialmente diferente y más largo, así que no lo estoy publicando, pero podría ser un paso en la dirección correcta. Todavía no puedo hacer que esto gire continuamente. El uso de operaciones matriciales en cada marco parece costoso en términos de rendimiento.

JSFiddle


Aquí hay un pseudocódigo que hace lo que estás hablando.

Make a bunch of stars not too far but not too close (via rejection sampling) Set up a projection matrix (defines the camera frustum) Each frame Compute our camera rotation angle Make a "view" matrix (repositions the stars to be relative to our view) Compose the view and projection matrix into the view-projection matrix For each star Apply the view-projection matrix to give screen star coordinates If the star is behind the camera skip it Do some math to give the star a nice seeming ''size'' Scale the star coordinate to the canvas Draw the star with its canvas coordinate and size

He hecho una implementación de lo anterior. Utiliza la biblioteca de gl-matrix Javascript para manejar algunas de las matrices matemáticas. Son buenas cosas (Violín para esto está here , o ver más abajo.)

var c = document.getElementById(''c''); var n = c.getContext(''2d''); // View matrix, defines where you''re looking var viewMtx = mat4.create(); // Projection matrix, defines how the view maps onto the screen var projMtx = mat4.create(); // Adapted from http://.com/questions/18404890/how-to-build-perspective-projection-matrix-no-api function ComputeProjMtx(field_of_view, aspect_ratio, near_dist, far_dist, left_handed) { // We''ll assume input parameters are sane. field_of_view = field_of_view * Math.PI / 180.0; // Convert degrees to radians var frustum_depth = far_dist - near_dist; var one_over_depth = 1 / frustum_depth; var e11 = 1.0 / Math.tan(0.5 * field_of_view); var e00 = (left_handed ? 1 : -1) * e11 / aspect_ratio; var e22 = far_dist * one_over_depth; var e32 = (-far_dist * near_dist) * one_over_depth; return [ e00, 0, 0, 0, 0, e11, 0, 0, 0, 0, e22, e32, 0, 0, 1, 0 ]; } // Make a view matrix with a simple rotation about the Y axis (up-down axis) function ComputeViewMtx(angle) { angle = angle * Math.PI / 180.0; // Convert degrees to radians return [ Math.cos(angle), 0, Math.sin(angle), 0, 0, 1, 0, 0, -Math.sin(angle), 0, Math.cos(angle), 0, 0, 0, 0, 1 ]; } projMtx = ComputeProjMtx(70, c.width / c.height, 1, 200, true); var angle = 0; var viewProjMtx = mat4.create(); var minDist = 100; var maxDist = 1000; function Star() { var d = 0; do { // Create random points in a cube.. but not too close. this.x = Math.random() * maxDist - (maxDist / 2); this.y = Math.random() * maxDist - (maxDist / 2); this.z = Math.random() * maxDist - (maxDist / 2); var d = this.x * this.x + this.y * this.y + this.z * this.z; } while ( d > maxDist * maxDist / 4 || d < minDist * minDist ); this.dist = Math.sqrt(d); } Star.prototype.AsVector = function() { return [this.x, this.y, this.z, 1]; } var stars = []; for (var i = 0; i < 5000; i++) stars.push(new Star()); var lastLoop = Date.now(); function loop() { var now = Date.now(); var dt = (now - lastLoop) / 1000.0; lastLoop = now; angle += 30.0 * dt; viewMtx = ComputeViewMtx(angle); //console.log(''---''); //console.log(projMtx); //console.log(viewMtx); mat4.multiply(viewProjMtx, projMtx, viewMtx); //console.log(viewProjMtx); n.beginPath(); n.rect(0, 0, c.width, c.height); n.closePath(); n.fillStyle = ''#000''; n.fill(); n.fillStyle = ''#fff''; var v = vec4.create(); for (var i = 0; i < stars.length; i++) { var star = stars[i]; vec4.transformMat4(v, star.AsVector(), viewProjMtx); v[0] /= v[3]; v[1] /= v[3]; v[2] /= v[3]; //v[3] /= v[3]; if (v[3] < 0) continue; var x = (v[0] * 0.5 + 0.5) * c.width; var y = (v[1] * 0.5 + 0.5) * c.height; // Compute a visual size... // This assumes all stars are the same size. // It also doesn''t scale with canvas size well -- we''d have to take more into account. var s = 300 / star.dist; n.beginPath(); n.arc(x, y, s, 0, Math.PI * 2); //n.rect(x, y, s, s); n.closePath(); n.fill(); } window.requestAnimationFrame(loop); } loop();

<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.1/gl-matrix-min.js"></script> <canvas id="c" width="500" height="500"></canvas>

Algunos enlaces:

Actualizar

Aquí hay otra versión que tiene controles de teclado. Un poco divertido. Puede ver la diferencia entre rotar y paralaje de strafing. Funciona mejor página completa. (Violín para esto está here o ver más abajo.)

var c = document.getElementById(''c''); var n = c.getContext(''2d''); // View matrix, defines where you''re looking var viewMtx = mat4.create(); // Projection matrix, defines how the view maps onto the screen var projMtx = mat4.create(); // Adapted from http://.com/questions/18404890/how-to-build-perspective-projection-matrix-no-api function ComputeProjMtx(field_of_view, aspect_ratio, near_dist, far_dist, left_handed) { // We''ll assume input parameters are sane. field_of_view = field_of_view * Math.PI / 180.0; // Convert degrees to radians var frustum_depth = far_dist - near_dist; var one_over_depth = 1 / frustum_depth; var e11 = 1.0 / Math.tan(0.5 * field_of_view); var e00 = (left_handed ? 1 : -1) * e11 / aspect_ratio; var e22 = far_dist * one_over_depth; var e32 = (-far_dist * near_dist) * one_over_depth; return [ e00, 0, 0, 0, 0, e11, 0, 0, 0, 0, e22, e32, 0, 0, 1, 0 ]; } // Make a view matrix with a simple rotation about the Y axis (up-down axis) function ComputeViewMtx(angle) { angle = angle * Math.PI / 180.0; // Convert degrees to radians return [ Math.cos(angle), 0, Math.sin(angle), 0, 0, 1, 0, 0, -Math.sin(angle), 0, Math.cos(angle), 0, 0, 0, -250, 1 ]; } projMtx = ComputeProjMtx(70, c.width / c.height, 1, 200, true); var angle = 0; var viewProjMtx = mat4.create(); var minDist = 100; var maxDist = 1000; function Star() { var d = 0; do { // Create random points in a cube.. but not too close. this.x = Math.random() * maxDist - (maxDist / 2); this.y = Math.random() * maxDist - (maxDist / 2); this.z = Math.random() * maxDist - (maxDist / 2); var d = this.x * this.x + this.y * this.y + this.z * this.z; } while ( d > maxDist * maxDist / 4 || d < minDist * minDist ); this.dist = 100; } Star.prototype.AsVector = function() { return [this.x, this.y, this.z, 1]; } var stars = []; for (var i = 0; i < 5000; i++) stars.push(new Star()); var lastLoop = Date.now(); var dir = { up: 0, down: 1, left: 2, right: 3 }; var dirStates = [false, false, false, false]; var shiftKey = false; var moveSpeed = 100.0; var turnSpeed = 1.0; function loop() { var now = Date.now(); var dt = (now - lastLoop) / 1000.0; lastLoop = now; angle += 30.0 * dt; //viewMtx = ComputeViewMtx(angle); var tf = mat4.create(); if (dirStates[dir.up]) mat4.translate(tf, tf, [0, 0, moveSpeed * dt]); if (dirStates[dir.down]) mat4.translate(tf, tf, [0, 0, -moveSpeed * dt]); if (dirStates[dir.left]) if (shiftKey) mat4.rotate(tf, tf, -turnSpeed * dt, [0, 1, 0]); else mat4.translate(tf, tf, [moveSpeed * dt, 0, 0]); if (dirStates[dir.right]) if (shiftKey) mat4.rotate(tf, tf, turnSpeed * dt, [0, 1, 0]); else mat4.translate(tf, tf, [-moveSpeed * dt, 0, 0]); mat4.multiply(viewMtx, tf, viewMtx); //console.log(''---''); //console.log(projMtx); //console.log(viewMtx); mat4.multiply(viewProjMtx, projMtx, viewMtx); //console.log(viewProjMtx); n.beginPath(); n.rect(0, 0, c.width, c.height); n.closePath(); n.fillStyle = ''#000''; n.fill(); n.fillStyle = ''#fff''; var v = vec4.create(); for (var i = 0; i < stars.length; i++) { var star = stars[i]; vec4.transformMat4(v, star.AsVector(), viewProjMtx); if (v[3] < 0) continue; var d = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); v[0] /= v[3]; v[1] /= v[3]; v[2] /= v[3]; //v[3] /= v[3]; var x = (v[0] * 0.5 + 0.5) * c.width; var y = (v[1] * 0.5 + 0.5) * c.height; // Compute a visual size... // This assumes all stars are the same size. // It also doesn''t scale with canvas size well -- we''d have to take more into account. var s = 300 / d; n.beginPath(); n.arc(x, y, s, 0, Math.PI * 2); //n.rect(x, y, s, s); n.closePath(); n.fill(); } window.requestAnimationFrame(loop); } loop(); function keyToDir(evt) { var d = -1; if (evt.keyCode === 38) d = dir.up else if (evt.keyCode === 37) d = dir.left; else if (evt.keyCode === 39) d = dir.right; else if (evt.keyCode === 40) d = dir.down; return d; } window.onkeydown = function(evt) { var d = keyToDir(evt); if (d >= 0) dirStates[d] = true; if (evt.keyCode === 16) shiftKey = true; } window.onkeyup = function(evt) { var d = keyToDir(evt); if (d >= 0) dirStates[d] = false; if (evt.keyCode === 16) shiftKey = false; }

<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.1/gl-matrix-min.js"></script> <div>Click in this pane. Use up/down/left/right, hold shift + left/right to rotate.</div> <canvas id="c" width="500" height="500"></canvas>

Actualización 2

Alain Jacomet Forte preguntó:

¿Cuál es su método recomendado para crear propósitos generales 3d y si recomendaría trabajar a nivel de matrices o no, específicamente quizás a este escenario en particular?

Respecto a las matrices: si estás escribiendo un motor desde cero en cualquier plataforma, entonces inevitablemente terminarás trabajando con matrices, ya que ayudan a generalizar las matemáticas básicas en 3D. Incluso si usas OpenGL / WebGL o Direct3D, terminarás haciendo una matriz de visualización y proyección y matrices adicionales para propósitos más sofisticados. (Manejo de mapas normales, alineación de objetos del mundo, skinings, etc ...)

Con respecto a un método de creación de propósito general 3d ... No. Se ejecutará lentamente y no tendrá un rendimiento sin mucho trabajo. Confíe en una biblioteca acelerada por hardware para hacer el trabajo pesado. Crear motores 3D limitados para proyectos específicos es divertido e instructivo (por ejemplo, quiero una animación genial en mi página web), pero cuando se trata de colocar los píxeles en la pantalla para algo serio, desea que el hardware lo maneje todo lo que pueda para fines de rendimiento.

Lamentablemente, la web no tiene un gran estándar para eso todavía, pero viene en WebGL: aprenda WebGL, use WebGL. Funciona muy bien y funciona bien cuando es compatible. (Sin embargo, puede salirse con la suya con un montón de cosas simplemente utilizando las transformaciones CSS 3D y Javascript ).

Si estás haciendo programación de escritorio, recomiendo OpenGL a través de SDL (todavía no estoy vendido en SFML), es multiplataforma y está bien soportado.

Si está programando teléfonos móviles, OpenGL ES es prácticamente su única opción (aparte de un renderizador de software lento).

Si quieres hacer las cosas en lugar de escribir tu propio motor desde cero, la versión de facto para la web es Three.js (que me parece efectiva pero mediocre). Si quieres un motor de juego completo, hay algunas opciones gratuitas en estos días, las principales comerciales son Unity y Unreal. Irrlicht ha existido por mucho tiempo, aunque nunca tuve la oportunidad de usarlo, pero he oído que es bueno.

Pero si quieres hacer todas las cosas en 3D desde cero ... siempre encontré que el renderizador de software en Quake se convirtió en un caso de estudio bastante bueno. Algo de eso se puede encontrar here .


Está reiniciando las estrellas en la posición 2d de cada cuadro, luego moviendo las estrellas (dependiendo de la cantidad de tiempo y la velocidad de cada estrella), esta es una mala forma de lograr su objetivo. Como descubrió, se vuelve muy complejo cuando intenta extender esta solución a más escenarios.

Una mejor manera sería establecer la ubicación de las estrellas 3d solo una vez (en la inicialización) y luego mover una "cámara" a cada fotograma (según el tiempo). Cuando quieras renderizar la imagen 2d, calcula la ubicación de las estrellas en la pantalla. La ubicación en la pantalla depende de la ubicación 3d de las estrellas y la ubicación actual de la cámara. Esto te permitirá mover la cámara (en cualquier dirección), rotarla (en cualquier ángulo) y renderizar la posición correcta de las estrellas Y mantener la cordura.