Mercurial > projects > openmelee
diff render.d @ 0:c10bc63824e7
Initial commit!
author | zzzzrrr <mason.green@gmail.com> |
---|---|
date | Fri, 20 Mar 2009 06:41:25 -0400 |
parents | |
children | a40d066ebbd1 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/render.d Fri Mar 20 06:41:25 2009 -0400 @@ -0,0 +1,663 @@ +/* + * Copyright (c) 2009, Mason Green (zzzzrrr) + * Based on Box2D by Erin Catto, http://www.box2d.org + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the polygonal nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +module melee.render; + +import xf.dog.Dog; +import xf.omg.core.LinearAlgebra; +import xf.hybrid.Event; +import xf.hybrid.Font; + +import melee.melee; + +/// Color for drawing. Each value has the range [0,1]. +struct Color { + static Color opCall(float r, float g, float b) + { + Color u; + u.r = r; + u.g = g; + u.b = b; + return u; + } + + float r = 0; + float g = 0; + float b = 0; +} + +class Render : Melee +{ + +this(Settings *settings) { + + super(settings); +} + +void drawCircle(GL gl, vec2 center, float radius, bool water = false, float theta = float.nan) +{ + int segs = cast(int)(radius) + 20; + if (segs > MAX_CIRCLE_RES) segs = MAX_CIRCLE_RES; + double coef = 2.0 * PI / segs; + + auto realTheta = (theta <>= 0 ? theta : 0); + if (water) { + gl.immediate(GL_TRIANGLE_FAN, + { + gl.Vertex2fv(center.ptr); + for (int n = 0; n <= segs; n++) { + double rads = n * coef; + gl.Vertex2f(radius * cos(rads + realTheta) + center.x, radius * sin(rads + realTheta) + center.y); + } + }); + } + + gl.immediate(GL_LINE_STRIP, + { + for (int n = 0; n <= segs; n++) { + double rads = n * coef; + gl.Vertex2f(radius * cos(rads + realTheta) + center.x, radius * sin(rads + realTheta) + center.y); + } + if (theta <>= 0) + gl.Vertex2fv(center.ptr); + }); +} + +void drawSolidCircle(GL gl, vec2 center, float radius, vec2 axis, Color color) +{ + const k_segments = 16.0f; + const k_increment = 2.0f * PI / k_segments; + float theta = 0.0f; + gl.Enable(GL_BLEND); + gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.Color4f(0.5f * color.r, 0.5f * color.g, 0.5f * color.b, 0.5f); + gl.Begin(GL_TRIANGLE_FAN); + for (int i = 0; i < k_segments; ++i) { + vec2 v = center + radius * vec2(cos(theta), sin(theta)); + gl.Vertex2f(v.x, v.y); + theta += k_increment; + } + gl.End(); + gl.Disable(GL_BLEND); + + theta = 0.0f; + gl.Color4f(color.r, color.g, color.b, 1.0f); + gl.Begin(GL_LINE_LOOP); + for (int i = 0; i < k_segments; ++i) { + vec2 v = center + radius * vec2(cos(theta), sin(theta)); + gl.Vertex2f(v.x, v.y); + theta += k_increment; + } + gl.End(); + + vec2 p = center + radius * axis; + gl.Begin(GL_LINES); + gl.Vertex2f(center.x, center.y); + gl.Vertex2f(p.x, p.y); + gl.End(); +} + +void drawPolygon(GL gl, vec2[] glVerts, Color color) +{ + gl.Color3f(color.r, color.g, color.b); + gl.immediate(GL_LINE_LOOP, + { + foreach (v; glVerts) + gl.Vertex2fv(v.ptr); + }); +} + +void drawSolidPolygon(GL gl, vec2[] vertices, Color color) +{ + gl.Enable(GL_BLEND); + gl.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.Color4f(0.5f * color.r, 0.5f * color.g, 0.5f * color.b, 0.5f); + gl.Begin(GL_TRIANGLE_FAN); + for (int i = 0; i < vertices.length; ++i) { + gl.Vertex2f(vertices[i].x, vertices[i].y); + } + gl.End(); + gl.Disable(GL_BLEND); + + gl.Color4f(color.r, color.g, color.b, 1.0f); + gl.Begin(GL_LINE_LOOP); + for (int i = 0; i < vertices.length; ++i) { + gl.Vertex2f(vertices[i].x, vertices[i].y); + } + gl.End(); +} + + +void drawPoint(GL gl, vec2 p, float size, Color color) +{ + gl.Color3f(color.r, color.g, color.b); + gl.PointSize(size); + gl.Begin(GL_POINTS); + gl.Vertex2f(p.x, p.y); + gl.End(); + gl.PointSize(1.0f); +} + +void drawSegment(GL gl, vec2 begin, vec2 end, Color color) +{ + gl.Color3f(color.r, color.g, color.b); + gl.immediate(GL_LINES, + { + gl.Vertex2fv(begin.ptr); + gl.Vertex2fv(end.ptr); + }); +} + +// TODO: handle inequal radii correctly +void connectCircles(GL gl, vec2 center1, float radius1, vec2 center2, float radius2) +{ + auto d = center2 - center1; + if (!d.length) + return; + d *= (d.length - radius1) / d.length; + center1 += d; + center2 -= d; + gl.immediate(GL_LINES, + { + gl.Vertex2fv(center1.ptr); + gl.Vertex2fv(center2.ptr); + }); +} + +void drawXForm(GL gl, bzXForm xf) +{ + bzVec2 p1 = xf.position, p2; + const k_axisScale = 0.4f; + + gl.Begin(GL_LINES); + { + gl.Color3f(1.0f, 0.0f, 0.0f); + gl.Vertex2f(p1.x, p1.y); + p2 = p1 + k_axisScale * xf.R.col1; + gl.Vertex2f(p2.x, p2.y); + + gl.Color3f(0.0f, 1.0f, 0.0f); + gl.Vertex2f(p1.x, p1.y); + p2 = p1 + k_axisScale * xf.R.col2; + gl.Vertex2f(p2.x, p2.y); + } + gl.End(); +} + +void drawSpring(GL gl, vec2 a, vec2 b, uint zigs) +{ + zigs++; + + // Portion of length dedicated to connectors + const float connPart = 0.2; + + vec2 inc = (b - a) / (zigs); + // One step from a to b + vec2 zigLen = inc * (1 - connPart); + // Length of a connector + vec2 connLen = inc * (connPart / 2) * zigs; + // Width of a zig + vec2 zigWidth = (b - a).rotatedHalfPi.normalized; + gl.immediate(GL_LINE_STRIP, + { + gl.Vertex2fv(a.ptr); + + a += connLen; + gl.Vertex2fv(a.ptr); + + bool dir = true; + a += zigWidth / 2 + zigLen / 2; + for (int i = 0; i < zigs; i++) { + gl.Vertex2fv(a.ptr); + a += zigLen; + if (dir) { + a -= zigWidth; + }else { + a += zigWidth; + } + dir = !dir; + } + + gl.Vertex2fv((b - connLen).ptr); + gl.Vertex2fv(b.ptr); + }); +} + +void drawShape(GL gl, bzShape shape, bzXForm xf, Color color, bool core) +{ + Color coreColor = Color(0.9f, 0.6f, 0.6f); + + switch (shape.type) { + case bzShapeType.CIRCLE: + auto circle = cast(bzCircle)shape; + + vec2 center = vec2.from(bzMul(xf, circle.localPosition)); + float radius = circle.radius; + vec2 axis = vec2.from(xf.R.col1); + + gl.drawSolidCircle(center, radius, axis, color); + + if (core) { + gl.Color3f(coreColor.r, coreColor.g, coreColor.b); + gl.drawCircle(center, radius - k_toiSlop); + } + break; + case bzShapeType.POLYGON: + { + bzPolygon poly = cast(bzPolygon)shape; + bzVec2[] vertices = poly.worldVertices; + vec2[] verts; + verts.length = vertices.length; + foreach (int i, v; vertices) { + verts[i] = vec2.from(v); + } + + gl.drawSolidPolygon(verts, color); + + if (core) { + bzVec2[] localCoreVertices = poly.coreVertices; + verts.length = localCoreVertices.length; + for (int i = 0; i < localCoreVertices.length; ++i) { + verts[i] = vec2.from(bzMul(xf, localCoreVertices[i])); + } + gl.drawPolygon(verts, coreColor); + } + } + break; + + case bzShapeType.EDGE: + { + bzEdge edge = cast(bzEdge)shape; + + vec2 p1 = vec2.from(bzMul(xf, edge.vertex1)); + vec2 p2 = vec2.from(bzMul(xf, edge.vertex2)); + gl.drawSegment(p1, p2, color); + + if (core) { + p1 = vec2.from(bzMul(xf, edge.coreVertex1)); + p2 = vec2.from(bzMul(xf, edge.coreVertex2)); + gl.drawSegment(p1, p2, coreColor); + } + } + break; + } +} + +void draw(vec2i screenSize, GL gl) +{ + this.screenSize = screenSize; + + gl.LoadIdentity(); + gl.MatrixMode(GL_PROJECTION); + gl.LoadIdentity(); + // Left, right, bottom, top + gl.gluOrtho2D(-screenSize.x / zoom, screenSize.x / zoom, -screenSize.y / zoom, screenSize.y / zoom); + gl.Translatef(-viewCenter.x, -viewCenter.y, 0); + gl.MatrixMode(GL_MODELVIEW); + gl.Disable(GL_DEPTH_TEST); + gl.LoadIdentity(); + gl.Clear(GL_COLOR_BUFFER_BIT); + + // Draw dynamic bodies + if (settings.drawShapes) { + for (bzBody b = world.bodyList; b; b = b.next) { + for (bzShape shape = b.shapeList; shape; shape = shape.next) { + + bzShape s = shape; + bzXForm xf = b.xf; + + if (b.isStatic) { + gl.drawShape(s, xf, Color(0.5f, 0.9f, 0.5f), settings.drawCoreShapes); + }else if (b.isSleeping) { + gl.drawShape(s, xf, Color(0.5f, 0.5f, 0.9f), settings.drawCoreShapes); + }else if (b.userData) { + auto ss = cast(Select)b.userData; + if (ss) { + gl.drawShape(s, xf, Color(0, .5, 1), settings.drawCoreShapes); + } + }else { + gl.drawShape(s, xf, Color(0.9f, 0.9f, 0.9f), settings.drawCoreShapes); + } + + gl.LoadIdentity(); + gl.Flush(); + } + } + + // Draw water + bzFluidParticle[] particles = world.particles; + gl.Color3f(0, 0, 1); + foreach (p; particles) { + gl.drawCircle(vec2.from(p.position), WATER_RADIUS, true); + } + } + + // Draw joints + if (settings.drawJoints) { + Color color = Color(0, 0, 1); + gl.Color3f(0, 0, 1); + + gl.LineWidth(1); + for (bzJoint joint = world.jointList; joint; joint = joint.next) { + auto distance = cast(bzDistanceJoint)joint; + auto pulley = cast(bzPulleyJoint)joint; + auto revolute = cast(bzRevoluteJoint)joint; + auto prismatic = cast(bzPrismaticJoint)joint; + auto line = cast(bzLineJoint)joint; + if (distance) { + color = Color(.5, .5, 0); + // Endpoints + vec2 a = vec2.from(distance.anchor1); + vec2 b = vec2.from(distance.anchor2); + // Circles + gl.drawCircle(a, HINGE_RADIUS); + gl.drawCircle(b, HINGE_RADIUS); + // Connecting line + gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS); + }else if (pulley) { + auto a = vec2.from(pulley.anchor1); + auto b = vec2.from(pulley.groundAnchor1); + auto c = vec2.from(pulley.groundAnchor2); + auto d = vec2.from(pulley.anchor2); + gl.drawCircle(a, HINGE_RADIUS); + gl.drawCircle(b, HINGE_RADIUS); + gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS); + gl.drawSegment(b, c, color); + gl.drawCircle(c, HINGE_RADIUS); + gl.drawCircle(d, HINGE_RADIUS); + gl.connectCircles(c, HINGE_RADIUS, d, HINGE_RADIUS); + }else if (revolute) { + auto a = vec2.from(revolute.rBody1.position); + auto b = vec2.from(revolute.anchor1); + auto c = vec2.from(revolute.rBody2.position); + gl.drawCircle(a, HINGE_RADIUS); + gl.drawCircle(b, HINGE_RADIUS); + gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS); + gl.drawCircle(c, HINGE_RADIUS); + gl.connectCircles(b, HINGE_RADIUS, c, HINGE_RADIUS); + }else if (prismatic) { + auto a = vec2.from(prismatic.rBody1.position); + auto b = vec2.from(prismatic.anchor1); + auto c = vec2.from(prismatic.rBody2.position); + gl.drawCircle(a, HINGE_RADIUS); + gl.drawCircle(b, HINGE_RADIUS); + gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS); + gl.drawCircle(c, HINGE_RADIUS); + gl.connectCircles(b, HINGE_RADIUS, c, HINGE_RADIUS); + }else if (line) { + auto a = vec2.from(line.rBody1.position); + auto b = vec2.from(line.anchor1); + auto c = vec2.from(line.rBody2.position); + gl.drawCircle(a, HINGE_RADIUS); + gl.drawCircle(b, HINGE_RADIUS); + gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS); + gl.drawCircle(c, HINGE_RADIUS); + gl.connectCircles(b, HINGE_RADIUS, c, HINGE_RADIUS); + } + } + + if (settings.drawControllers) { + bzForceGenerator[] forces = world.forces; + foreach (f; forces) { + auto spring1 = cast(bzSpring1) f; + auto spring2 = cast(bzSpring2) f; + auto buoyancy = cast(bzBuoyancy) f; + + if (spring1) { + auto bungee1 = cast(bzBungee1)spring1; + if (bungee1) { + gl.Color3f(.5, .5, 0); + // Endpoints + vec2 a = vec2.from(bungee1.rBody.position); + vec2 b = vec2.from(bungee1.anchor); + // Circles + gl.drawCircle(a, HINGE_RADIUS); + gl.drawCircle(b, HINGE_RADIUS); + // Connecting line + gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS); + }else { + uint zigs = 10; + auto anchor1 = vec2.from(spring1.anchor); + auto anchor2 = vec2.from(spring1.rBody.position); + gl.drawSpring(anchor1, anchor2, zigs); + } + } + + if (spring2) { + auto bungee2 = cast(bzBungee2)spring2; + if (bungee2) { + gl.Color3f(.5, .5, 0); + // Endpoints + vec2 a = vec2.from(bungee2.rBody.position); + vec2 b = vec2.from(bungee2.otherBody.position); + // Circles + gl.drawCircle(a, HINGE_RADIUS); + gl.drawCircle(b, HINGE_RADIUS); + // Connecting line + gl.connectCircles(a, HINGE_RADIUS, b, HINGE_RADIUS); + }else { + uint zigs = 10; + auto anchor1 = vec2.from(spring2.otherBody.position); + auto anchor2 = vec2.from(spring2.rBody.position); + gl.drawSpring(anchor1, anchor2, zigs); + } + } + + if(buoyancy) { + float plane = buoyancy.planeOffset; + vec2 p1 = vec2(-50, plane); + vec2 p2 = vec2(50, plane); + gl.drawSegment(p1, p2, color); + } + } + } + } + + if(settings.drawPairs) { + + bzBroadPhase bp = world.broadPhase; + bzVec2 invQ; + invQ.set(1.0f / bp.m_quantizationFactor.x, 1.0f / bp.m_quantizationFactor.y); + Color color = Color(0.9f, 0.9f, 0.3f); + + const k_tableCapacity = bzPairManager.TABLE_CAPACITY; + + for (int i = 0; i < k_tableCapacity; ++i) + { + ushort index = bp.m_pairManager.m_hashTable[i]; + while (index < bp.m_pairManager.m_pairs.length) + { + if(index == bzPairManager.NULL_PROXY) { + break; + } + bzPair pair = bp.m_pairManager.m_pairs[index]; + bzProxy p1 = bp.m_proxyPool[pair.proxyId1]; + bzProxy p2 = bp.m_proxyPool[pair.proxyId2]; + + bzAABB b1, b2; + b1.lowerBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p1.lowerBounds[0]].value; + b1.lowerBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p1.lowerBounds[1]].value; + b1.upperBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p1.upperBounds[0]].value; + b1.upperBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p1.upperBounds[1]].value; + b2.lowerBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p2.lowerBounds[0]].value; + b2.lowerBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p2.lowerBounds[1]].value; + b2.upperBound.x = bp.m_worldAABB.lowerBound.x + invQ.x * bp.m_bounds[0][p2.upperBounds[0]].value; + b2.upperBound.y = bp.m_worldAABB.lowerBound.y + invQ.y * bp.m_bounds[1][p2.upperBounds[1]].value; + + bzVec2 x1 = 0.5f * (b1.lowerBound + b1.upperBound); + bzVec2 x2 = 0.5f * (b2.lowerBound + b2.upperBound); + + gl.drawSegment(vec2.from(x1),vec2.from(x2), color); + + index = pair.next; + } + } + } + + // Draw axis aligned bounding boxes (bzAABB) + if (settings.drawAABBs) { + bzBroadPhase bp = world.broadPhase; + bzVec2 worldLower = bp.m_worldAABB.lowerBound; + bzVec2 worldUpper = bp.m_worldAABB.upperBound; + Color color; + bzVec2 invQ; + invQ.set(1.0f / bp.m_quantizationFactor.x, 1.0f / bp.m_quantizationFactor.y); + color = Color(1.0f, 1.0f, 1.0f); + + for (int i = 0; i < k_maxProxies; ++i) { + bzProxy p = bp.m_proxyPool[i]; + if (!p.isValid) { + continue; + } + + bzAABB b; + b.lowerBound.x = worldLower.x + invQ.x * bp.m_bounds[0][p.lowerBounds[0]].value; + b.lowerBound.y = worldLower.y + invQ.y * bp.m_bounds[1][p.lowerBounds[1]].value; + b.upperBound.x = worldLower.x + invQ.x * bp.m_bounds[0][p.upperBounds[0]].value; + b.upperBound.y = worldLower.y + invQ.y * bp.m_bounds[1][p.upperBounds[1]].value; + + vec2 vs[4]; + vs[0] = vec2(b.lowerBound.x, b.lowerBound.y); + vs[1] = vec2(b.upperBound.x, b.lowerBound.y); + vs[2] = vec2(b.upperBound.x, b.upperBound.y); + vs[3] = vec2(b.lowerBound.x, b.upperBound.y); + + drawPolygon(gl, vs, color); + } + + vec2 vs[4]; + vs[0] = vec2(worldLower.x, worldLower.y); + vs[1] = vec2(worldUpper.x, worldLower.y); + vs[2] = vec2(worldUpper.x, worldUpper.y); + vs[3] = vec2(worldLower.x, worldUpper.y); + + color = Color(0.3f, 0.9f, 0.9f); + drawPolygon(gl, vs, color); + } + + // Draw contact points + if (settings.drawContactPoints) { + const k_axisScale = 0.3f; + const k_forceScale = 0.01f; + + + for (int i = 0; i < pointCount; ++i) { + ContactPoint point = points[i]; + Color color; + + if (point.state == ContactState.e_contactAdded) { + // Add + color = Color(0.3f, 0.95f, 0.3f); + vec2 p = vec2.from(point.position); + gl.drawPoint(p, 10.0f, color); + }else if (point.state == ContactState.e_contactPersisted) { + // Persist + color = Color(0.3f, 0.3f, 0.95f); + vec2 p = vec2.from(point.position); + gl.drawPoint(p, 5.0f, color); + }else { + // Remove + color = Color(0.95f, 0.3f, 0.3f); + vec2 p = vec2.from(point.position); + gl.drawPoint(p, 10.0f, color); + } + + if (settings.drawContactNormals == 1) { + vec2 p1 = vec2.from(point.position); + vec2 p2 = p1 + k_axisScale * vec2.from(point.normal); + color = Color(0.4f, 0.9f, 0.4f); + gl.drawSegment(p1, p2, color); + }else if (settings.drawContactForces) { /* + vec2 p1 = vec2.from(point.position); + vec2 p2 = p1 + k_forceScale * vec2.from(point.normalForce * point.normal); + color = Color(0.9f, 0.9f, 0.3f); + gl.drawSegment(p1, p2, color);*/ + } + + if (settings.drawFrictionForces) { /* + vec2 tangent = vec2.from(bzCross(point.normal, 1.0f)); + vec2 p1 = point.position; + vec2 p2 = p1 + k_forceScale * vec2.from(point.tangentForce) * tangent; + color = Color(0.9f, 0.9f, 0.3f); + gl.drawSegment(p1, p2, color); */ + } + } + } + + if (settings.drawOBBs) { + Color color = Color(0.5f, 0.3f, 0.5f); + + for (bzBody b = world.bodyList; b; b = b.next) { + bzXForm xf = b.xf; + for (bzShape s = b.shapeList; s; s = s.next) { + if (s.type != bzShapeType.POLYGON) { + continue; + } + + bzPolygon poly = cast(bzPolygon)s; + bzOBB obb = poly.obb; + bzVec2 h = obb.extents; + bzVec2 vs[4]; + vs[0].set(-h.x, -h.y); + vs[1].set(h.x, -h.y); + vs[2].set(h.x, h.y); + vs[3].set(-h.x, h.y); + + vec2[4] v; + for (int i = 0; i < 4; ++i) { + vs[i] = obb.center + bzMul(obb.R, vs[i]); + v[i] = vec2.from(bzMul(xf, vs[i])); + } + + drawPolygon(gl, v, color); + } + } + } + + if (settings.drawCOMs) { + for (bzBody b = world.bodyList; b; b = b.next) { + bzXForm xf = b.xf; + xf.position = b.worldCenter; + drawXForm(gl, xf); + } + } + + // Nonphysical stuffs + // Universal '.' cursor + gl.Color3f(1, 1, 1); + gl.immediate(GL_POINTS, + { + gl.Vertex2fv(mousePos.ptr); + }); + + pointCount = 0; +} + +}