Skip to content

Instantly share code, notes, and snippets.

@PhoenixIllusion
Created April 16, 2024 21:44
Show Gist options
  • Save PhoenixIllusion/791fc945e83a0ce3b3379b7c1596c69f to your computer and use it in GitHub Desktop.
Save PhoenixIllusion/791fc945e83a0ce3b3379b7c1596c69f to your computer and use it in GitHub Desktop.
Crude port of example.js in Jolt Physics Example folder to Typescript for typescript coding
import * as THREE from 'three';
import Stats from 'stats.js';
import JOLT_NS from 'jolt-physics/wasm-compat';
export var Jolt: typeof JOLT_NS;
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { WebGL } from './webgl';
// Graphics variables
let container: HTMLDivElement;
export function setContaienr(_: HTMLDivElement) {
container = _;
}
export var stats: Stats;
export var camera: THREE.PerspectiveCamera;
export var controls: OrbitControls;
export var scene: THREE.Scene;
export var renderer: THREE.WebGLRenderer;
// Timers
export var clock = new THREE.Clock();
export var time = 0;
// Physics variables
export var jolt: JOLT_NS.JoltInterface;
export var physicsSystem: JOLT_NS.PhysicsSystem;
export var bodyInterface: JOLT_NS.BodyInterface;
// List of objects spawned
export var dynamicObjects: THREE.Mesh[] = [];
declare type UpdateFunction = (time: number, deltaTime: number)=>void;
export var onExampleUpdate: UpdateFunction | null | undefined;
export const DegreesToRadians = (deg: number) => deg * (Math.PI / 180.0);
export const wrapVec3 = (v: JOLT_NS.Vec3) => new THREE.Vector3(v.GetX(), v.GetY(), v.GetZ());
export const unwrapVec3 = (v: THREE.Vector3) => new Jolt.Vec3(v.x, v.y, v.z);
export const wrapRVec3 = (v: JOLT_NS.RVec3) => new THREE.Vector3(v.GetX(), v.GetY(), v.GetZ());;
export const unwrapRVec3 = (v: THREE.Vector3) => new Jolt.RVec3(v.x, v.y, v.z);
export const wrapQuat = (q: JOLT_NS.Quat) => new THREE.Quaternion(q.GetX(), q.GetY(), q.GetZ(), q.GetW());
export const unwrapQuat = (q: THREE.Quaternion) => new Jolt.Quat(q.x, q.y, q.z, q.w);
// Object layers
declare type LAYER = typeof LAYER_NON_MOVING | typeof LAYER_MOVING;
export const LAYER_NON_MOVING = 0;
export const LAYER_MOVING = 1;
export const NUM_OBJECT_LAYERS = 2;
export function getRandomQuat() {
let vec = new Jolt.Vec3(0.001 + Math.random(), Math.random(), Math.random());
let quat = Jolt.Quat.prototype.sRotation(vec.Normalized(), 2 * Math.PI * Math.random());
Jolt.destroy(vec);
return quat;
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
export function initGraphics() {
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xbfd1e5);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.2, 2000);
camera.position.set(0, 15, 30);
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene = new THREE.Scene();
var dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(10, 10, 5);
scene.add(dirLight);
controls = new OrbitControls(camera, container);
container.appendChild(renderer.domElement);
stats = new Stats();
stats.dom.style.position = 'absolute';
stats.dom.style.top = '0px';
container.appendChild(stats.dom);
window.addEventListener('resize', onWindowResize, false);
}
let setupCollisionFiltering = function (settings: JOLT_NS.JoltSettings) {
// Layer that objects can be in, determines which other objects it can collide with
// Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more
// layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation
// but only if you do collision testing).
let objectFilter = new Jolt.ObjectLayerPairFilterTable(NUM_OBJECT_LAYERS);
objectFilter.EnableCollision(LAYER_NON_MOVING, LAYER_MOVING);
objectFilter.EnableCollision(LAYER_MOVING, LAYER_MOVING);
// Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have
// a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame.
// You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have
// many object layers you'll be creating many broad phase trees, which is not efficient.
const BP_LAYER_NON_MOVING = new Jolt.BroadPhaseLayer(0);
const BP_LAYER_MOVING = new Jolt.BroadPhaseLayer(1);
const NUM_BROAD_PHASE_LAYERS = 2;
let bpInterface = new Jolt.BroadPhaseLayerInterfaceTable(NUM_OBJECT_LAYERS, NUM_BROAD_PHASE_LAYERS);
bpInterface.MapObjectToBroadPhaseLayer(LAYER_NON_MOVING, BP_LAYER_NON_MOVING);
bpInterface.MapObjectToBroadPhaseLayer(LAYER_MOVING, BP_LAYER_MOVING);
settings.mObjectLayerPairFilter = objectFilter;
settings.mBroadPhaseLayerInterface = bpInterface;
settings.mObjectVsBroadPhaseLayerFilter = new Jolt.ObjectVsBroadPhaseLayerFilterTable(settings.mBroadPhaseLayerInterface, NUM_BROAD_PHASE_LAYERS, settings.mObjectLayerPairFilter, NUM_OBJECT_LAYERS);
};
function initPhysics() {
// Initialize Jolt
const settings = new Jolt.JoltSettings();
setupCollisionFiltering(settings);
jolt = new Jolt.JoltInterface(settings);
Jolt.destroy(settings);
physicsSystem = jolt.GetPhysicsSystem();
bodyInterface = physicsSystem.GetBodyInterface();
// Helper functions
/*Jolt.Vec3.prototype.ToString = function () { return `(${this.GetX()}, ${this.GetY()}, ${this.GetZ()})` };
Jolt.Vec3.prototype.Clone = function () { return new Jolt.Vec3(this.GetX(), this.GetY(), this.GetZ()); };
Jolt.RVec3.prototype.ToString = function () { return `(${this.GetX()}, ${this.GetY()}, ${this.GetZ()})` };
Jolt.RVec3.prototype.Clone = function () { return new Jolt.RVec3(this.GetX(), this.GetY(), this.GetZ()); };
Jolt.Quat.prototype.ToString = function () { return `(${this.GetX()}, ${this.GetY()}, ${this.GetZ()}, ${this.GetW()})` };
Jolt.Quat.prototype.Clone = function () { return new Jolt.Quat(this.GetX(), this.GetY(), this.GetZ(), this.GetW()); };
Jolt.AABox.prototype.ToString = function () { return `[${this.mMax.ToString()}, ${this.mMin.ToString()}]`; };*/
}
function updatePhysics(deltaTime: number) {
// When running below 55 Hz, do 2 steps instead of 1
var numSteps = deltaTime > 1.0 / 55.0 ? 2 : 1;
// Step the physics world
jolt.Step(deltaTime, numSteps);
}
export function initExample(_Jolt: typeof Jolt, updateFunction?: UpdateFunction|null) {
Jolt = _Jolt;
container.innerHTML = "";
if (WebGL.isWebGLAvailable()) {
onExampleUpdate = updateFunction;
initGraphics();
initPhysics();
renderExample();
} else {
const warning = WebGL.getWebGLErrorMessage();
container.appendChild(warning);
}
// The memory profiler doesn't have an ID so we can't mess with it in css, set an ID here
let memoryprofilerCanvas = document.getElementById("memoryprofiler_canvas")!;
if (memoryprofilerCanvas)
memoryprofilerCanvas.parentElement!.id = "memoryprofiler";
}
function renderExample() {
requestAnimationFrame(renderExample);
// Don't go below 30 Hz to prevent spiral of death
var deltaTime = clock.getDelta();
deltaTime = Math.min(deltaTime, 1.0 / 30.0);
if (onExampleUpdate != null)
onExampleUpdate(time, deltaTime);
// Update object transforms
for (let i = 0, il = dynamicObjects.length; i < il; i++) {
let objThree = dynamicObjects[i];
let body = objThree.userData['body'];
objThree.position.copy(wrapVec3(body.GetPosition()));
objThree.quaternion.copy(wrapQuat(body.GetRotation()));
if (body.GetBodyType() == Jolt.EBodyType_SoftBody) {
const updateVertex: ()=>void = objThree.userData['updateVertex'];
if (updateVertex) {
updateVertex();
} else {
objThree.geometry = createMeshForShape(body.GetShape());
}
}
}
time += deltaTime;
updatePhysics(deltaTime);
controls.update(deltaTime);
renderer.render(scene, camera);
stats.update();
}
export function addToThreeScene(body: JOLT_NS.Body, color: number): void {
let threeObject = getThreeObjectForBody(body, color);
threeObject.userData['body'] = body;
scene.add(threeObject);
dynamicObjects.push(threeObject);
}
export function addToScene(body: JOLT_NS.Body, color: number): void{
bodyInterface.AddBody(body.GetID(), Jolt.EActivation_Activate);
addToThreeScene(body, color);
}
export function removeFromScene(threeObject: THREE.Mesh) {
let id = threeObject.userData['body'].GetID();
bodyInterface.RemoveBody(id);
bodyInterface.DestroyBody(id);
delete threeObject.userData['body'];
scene.remove(threeObject);
let idx = dynamicObjects.indexOf(threeObject);
dynamicObjects.splice(idx, 1);
}
export function createFloor(size = 50): JOLT_NS.Body {
var shape = new Jolt.BoxShape(new Jolt.Vec3(size, 0.5, size), 0.05);
var creationSettings = new Jolt.BodyCreationSettings(shape, new Jolt.RVec3(0, -0.5, 0), new Jolt.Quat(0, 0, 0, 1), Jolt.EMotionType_Static, LAYER_NON_MOVING);
let body = bodyInterface.CreateBody(creationSettings);
Jolt.destroy(creationSettings);
addToScene(body, 0xc7c7c7);
return body;
}
export function createBox(position: JOLT_NS.RVec3, rotation: JOLT_NS.Quat, halfExtent: JOLT_NS.Vec3, motionType: JOLT_NS.EMotionType, layer: LAYER, color: number = 0xffffff): JOLT_NS.Body {
let shape = new Jolt.BoxShape(halfExtent, 0.05);
let creationSettings = new Jolt.BodyCreationSettings(shape, position, rotation, motionType, layer);
let body = bodyInterface.CreateBody(creationSettings);
Jolt.destroy(creationSettings);
addToScene(body, color);
return body;
}
export function createSphere(position: JOLT_NS.RVec3, radius: number, motionType: JOLT_NS.EMotionType, layer: LAYER, color = 0xffffff): JOLT_NS.Body {
let shape = new Jolt.SphereShape(radius);
let creationSettings = new Jolt.BodyCreationSettings(shape, position, Jolt.Quat.prototype.sIdentity(), motionType, layer);
let body = bodyInterface.CreateBody(creationSettings);
Jolt.destroy(creationSettings);
addToScene(body, color);
return body;
}
export function createMeshForShape(shape: JOLT_NS.Shape): THREE.BufferGeometry {
// Get triangle data
let scale = new Jolt.Vec3(1, 1, 1);
let triContext = new Jolt.ShapeGetTriangles(shape, Jolt.AABox.prototype.sBiggest(), shape.GetCenterOfMass(), Jolt.Quat.prototype.sIdentity(), scale);
Jolt.destroy(scale);
// Get a view on the triangle data (does not make a copy)
let vertices = new Float32Array(Jolt.HEAPF32.buffer, triContext.GetVerticesData(), triContext.GetVerticesSize() / Float32Array.BYTES_PER_ELEMENT);
// Now move the triangle data to a buffer and clone it so that we can free the memory from the C++ heap (which could be limited in size)
let buffer = new THREE.BufferAttribute(vertices, 3).clone();
Jolt.destroy(triContext);
// Create a three mesh
let geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', buffer);
geometry.computeVertexNormals();
return geometry;
}
function getSoftBodyMesh(body: JOLT_NS.Body, material: THREE.Material) {
const motionProperties = Jolt.castObject(body.GetMotionProperties(), Jolt.SoftBodyMotionProperties);
const vertexSettings = motionProperties.GetVertices();
const settings = motionProperties.GetSettings();
const positionOffset = Jolt.prototype.SoftBodyVertexTraits.prototype.mPositionOffset;
const faceData = settings.mFaces;
const softVertex: Float32Array[] = [];
// WARNING: The code uses direct memory mapping of properties in Jolt and makes assumptions about the memory layout.
function memoryMapVertex(i: number) {
const offset = Jolt.getPointer(vertexSettings.at(i));
return new Float32Array(Jolt.HEAPF32.buffer, offset + positionOffset, 3);
}
for (let i = 0; i < faceData.size(); i++) {
const [v0, v1, v2] = new Uint32Array(Jolt.HEAP32.buffer, Jolt.getPointer(faceData.at(i)), 3);
softVertex.push(memoryMapVertex(v0))
softVertex.push(memoryMapVertex(v1))
softVertex.push(memoryMapVertex(v2))
}
// Get a view on the triangle data (does not make a copy)
let vertices = new Float32Array(settings.mFaces.size() * 9);
for (let i = 0; i < softVertex.length; i++) {
vertices.set(softVertex[i], i * 3);
}
let buffer = new THREE.BufferAttribute(vertices, 3);
// Create a three mesh
let geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', buffer);
geometry.computeVertexNormals();
material.side = THREE.DoubleSide;
const threeObject = new THREE.Mesh(geometry, material);
threeObject.userData['updateVertex'] = () => {
for (let i = 0; i < softVertex.length; i++) {
vertices.set(softVertex[i], i * 3);
}
geometry.computeVertexNormals();
geometry.getAttribute('position').needsUpdate = true;
geometry.getAttribute('normal').needsUpdate = true;
}
return threeObject;
}
function getThreeObjectForBody(body: JOLT_NS.Body, color: number) {
let material = new THREE.MeshPhongMaterial({ color: color });
let threeObject;
let shape = body.GetShape();
switch (shape.GetSubType()) {
case Jolt.EShapeSubType_Box:
let boxShape = Jolt.castObject(shape, Jolt.BoxShape);
let extent = wrapVec3(boxShape.GetHalfExtent()).multiplyScalar(2);
threeObject = new THREE.Mesh(new THREE.BoxGeometry(extent.x, extent.y, extent.z, 1, 1, 1), material);
break;
case Jolt.EShapeSubType_Sphere:
let sphereShape = Jolt.castObject(shape, Jolt.SphereShape);
threeObject = new THREE.Mesh(new THREE.SphereGeometry(sphereShape.GetRadius(), 32, 32), material);
break;
case Jolt.EShapeSubType_Capsule:
let capsuleShape = Jolt.castObject(shape, Jolt.CapsuleShape);
threeObject = new THREE.Mesh(new THREE.CapsuleGeometry(capsuleShape.GetRadius(), 2 * capsuleShape.GetHalfHeightOfCylinder(), 20, 10), material);
break;
case Jolt.EShapeSubType_Cylinder:
let cylinderShape = Jolt.castObject(shape, Jolt.CylinderShape);
threeObject = new THREE.Mesh(new THREE.CylinderGeometry(cylinderShape.GetRadius(), cylinderShape.GetRadius(), 2 * cylinderShape.GetHalfHeight(), 20, 1), material);
break;
default:
if (body.GetBodyType() == Jolt.EBodyType_SoftBody)
threeObject = getSoftBodyMesh(body, material);
else
threeObject = new THREE.Mesh(createMeshForShape(shape), material);
break;
}
threeObject.position.copy(wrapRVec3(body.GetPosition()));
threeObject.quaternion.copy(wrapQuat(body.GetRotation()));
return threeObject;
}
export function createMeshFloor(n: number, cellSize: number, maxHeight: number, posX: number, posY: number, posZ: number) {
// Create regular grid of triangles
let height = function (x: number, y: number) { return Math.sin(x / 2) * Math.cos(y / 3); };
let triangles = new Jolt.TriangleList;
triangles.resize(n * n * 2);
for (let x = 0; x < n; ++x)
for (let z = 0; z < n; ++z) {
let center = n * cellSize / 2;
let x1 = cellSize * x - center;
let z1 = cellSize * z - center;
let x2 = x1 + cellSize;
let z2 = z1 + cellSize;
{
let t = triangles.at((x * n + z) * 2);
let v1 = t.get_mV(0), v2 = t.get_mV(1), v3 = t.get_mV(2);
v1.x = x1, v1.y = height(x, z), v1.z = z1;
v2.x = x1, v2.y = height(x, z + 1), v2.z = z2;
v3.x = x2, v3.y = height(x + 1, z + 1), v3.z = z2;
}
{
let t = triangles.at((x * n + z) * 2 + 1);
let v1 = t.get_mV(0), v2 = t.get_mV(1), v3 = t.get_mV(2);
v1.x = x1, v1.y = height(x, z), v1.z = z1;
v2.x = x2, v2.y = height(x + 1, z + 1), v2.z = z2;
v3.x = x2, v3.y = height(x + 1, z), v3.z = z1;
}
}
let materials = new Jolt.PhysicsMaterialList;
let shape = new Jolt.MeshShapeSettings(triangles, materials).Create().Get();
Jolt.destroy(triangles);
Jolt.destroy(materials);
// Create body
let creationSettings = new Jolt.BodyCreationSettings(shape, new Jolt.RVec3(posX, posY, posZ), new Jolt.Quat(0, 0, 0, 1), Jolt.EMotionType_Static, LAYER_NON_MOVING);
let body = bodyInterface.CreateBody(creationSettings);
Jolt.destroy(creationSettings);
addToScene(body, 0xc7c7c7);
}
export function createVehicleTrack() {
const track = [
[[[38, 64, -14], [38, 64, -16], [38, -64, -16], [38, -64, -14], [64, -64, -16], [64, -64, -14], [64, 64, -16], [64, 64, -14]], [[-16, 64, -14], [-16, 64, -16], [-16, -64, -16], [-16, -64, -14], [10, -64, -16], [10, -64, -14], [10, 64, -16], [10, 64, -14]], [[10, -48, -14], [10, -48, -16], [10, -64, -16], [10, -64, -14], [38, -64, -16], [38, -64, -14], [38, -48, -16], [38, -48, -14]], [[10, 64, -14], [10, 64, -16], [10, 48, -16], [10, 48, -14], [38, 48, -16], [38, 48, -14], [38, 64, -16], [38, 64, -14]]],
[[[38, 48, -10], [38, 48, -14], [38, -48, -14], [38, -48, -10], [40, -48, -14], [40, -48, -10], [40, 48, -14], [40, 48, -10]], [[62, 62, -10], [62, 62, -14], [62, -64, -14], [62, -64, -10], [64, -64, -14], [64, -64, -10], [64, 62, -14], [64, 62, -10]], [[8, 48, -10], [8, 48, -14], [8, -48, -14], [8, -48, -10], [10, -48, -14], [10, -48, -10], [10, 48, -14], [10, 48, -10]], [[-16, 62, -10], [-16, 62, -14], [-16, -64, -14], [-16, -64, -10], [-14, -64, -14], [-14, -64, -10], [-14, 62, -14], [-14, 62, -10]], [[-14, -62, -10], [-14, -62, -14], [-14, -64, -14], [-14, -64, -10], [62, -64, -14], [62, -64, -10], [62, -62, -14], [62, -62, -10]], [[8, -48, -10], [8, -48, -14], [8, -50, -14], [8, -50, -10], [40, -50, -14], [40, -50, -10], [40, -48, -14], [40, -48, -10]], [[8, 50, -10], [8, 50, -14], [8, 48, -14], [8, 48, -10], [40, 48, -14], [40, 48, -10], [40, 50, -14], [40, 50, -10]], [[-16, 64, -10], [-16, 64, -14], [-16, 62, -14], [-16, 62, -10], [64, 62, -14], [64, 62, -10], [64, 64, -14], [64, 64, -10]]],
[[[-4, 22, -14], [-4, -14, -14], [-4, -14, -10], [4, -14, -14], [4, -14, -10], [4, 22, -14]], [[-4, -27, -14], [-4, -48, -14], [-4, -48, -11], [4, -48, -14], [4, -48, -11], [4, -27, -14]], [[-4, 50, -14], [-4, 30, -14], [-4, 30, -12], [4, 30, -14], [4, 30, -12], [4, 50, -14]], [[46, 50, -14], [46, 31, -14], [46, 50, -12], [54, 31, -14], [54, 50, -12], [54, 50, -14]], [[46, 16, -14], [46, -19, -14], [46, 16, -10], [54, -19, -14], [54, 16, -10], [54, 16, -14]], [[46, -28, -14], [46, -48, -14], [46, -28, -11], [54, -48, -14], [54, -28, -11], [54, -28, -14]]]
];
const mapColors = [0x666666, 0x006600, 0x000066];
let tempVec = new Jolt.Vec3(0, 1, 0);
const mapRot = Jolt.Quat.prototype.sRotation(tempVec, 0.5 * Math.PI);
let tempRVec = new Jolt.RVec3(0, 0, 0);
track.forEach((type, tIdx) => {
type.forEach(block => {
const hull = new Jolt.ConvexHullShapeSettings;
block.forEach(v => {
tempVec.Set(-v[1], v[2], v[0]);
hull.mPoints.push_back(tempVec);
});
const shape = hull.Create().Get();
tempRVec.Set(0, 10, 0);
const creationSettings = new Jolt.BodyCreationSettings(shape, tempRVec, mapRot, Jolt.EMotionType_Static, LAYER_NON_MOVING);
Jolt.destroy(hull);
const body = bodyInterface.CreateBody(creationSettings);
Jolt.destroy(creationSettings);
body.SetFriction(1.0);
addToScene(body, mapColors[tIdx]);
});
});
Jolt.destroy(tempVec);
Jolt.destroy(tempRVec);
}
export function addLine(from: JOLT_NS.RVec3, to: JOLT_NS.RVec3, color: number): void {
const material = new THREE.LineBasicMaterial({ color: color });
const points = [];
points.push(wrapRVec3(from));
points.push(wrapRVec3(to));
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry, material);
scene.add(line);
}
export function addMarker(location: JOLT_NS.Vec3, size: number, color: number): void {
const material = new THREE.LineBasicMaterial({ color: color });
const points = [];
const center = wrapVec3(location);
points.push(center.clone().add(new THREE.Vector3(-size, 0, 0)));
points.push(center.clone().add(new THREE.Vector3(size, 0, 0)));
points.push(center.clone().add(new THREE.Vector3(0, -size, 0)));
points.push(center.clone().add(new THREE.Vector3(0, size, 0)));
points.push(center.clone().add(new THREE.Vector3(0, 0, -size)));
points.push(center.clone().add(new THREE.Vector3(0, 0, size)));
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.LineSegments(geometry, material);
scene.add(line);
}
export class WebGL {
static isWebGLAvailable() {
try {
const canvas = document.createElement( 'canvas' );
return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
} catch ( e ) {
return false;
}
}
static isWebGL2Available() {
try {
const canvas = document.createElement( 'canvas' );
return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) );
} catch ( e ) {
return false;
}
}
static getWebGLErrorMessage() {
return this.getErrorMessage( 1 );
}
static getWebGL2ErrorMessage() {
return this.getErrorMessage( 2 );
}
static getErrorMessage( version: 1|2 ) {
const names = {
1: 'WebGL',
2: 'WebGL 2'
};
const contexts = {
1: window.WebGLRenderingContext,
2: window.WebGL2RenderingContext
};
let message = 'Your $0 does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">$1</a>';
const element = document.createElement( 'div' );
element.id = 'webglmessage';
element.style.fontFamily = 'monospace';
element.style.fontSize = '13px';
element.style.fontWeight = 'normal';
element.style.textAlign = 'center';
element.style.background = '#fff';
element.style.color = '#000';
element.style.padding = '1.5em';
element.style.width = '400px';
element.style.margin = '5em auto 0';
if ( contexts[ version ] ) {
message = message.replace( '$0', 'graphics card' );
} else {
message = message.replace( '$0', 'browser' );
}
message = message.replace( '$1', names[ version ] );
element.innerHTML = message;
return element;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment