|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|
|
|
<title>The AI Creature</title> |
|
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script> |
|
<script src="https://preview.babylonjs.com/ammo.js"></script> |
|
<script src="https://preview.babylonjs.com/cannon.js"></script> |
|
<script src="https://preview.babylonjs.com/Oimo.js"></script> |
|
<script src="https://preview.babylonjs.com/earcut.min.js"></script> |
|
<script src="https://preview.babylonjs.com/babylon.js"></script> |
|
<script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script> |
|
<script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script> |
|
<script src="https://preview.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script> |
|
<script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.js"></script> |
|
<script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script> |
|
<script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script> |
|
<script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script> |
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.12.0/dist/tf.min.js"></script> |
|
|
|
<script src="agent_sac.js"></script> |
|
|
|
<style> |
|
html, body { |
|
overflow: hidden; |
|
width: 100%; |
|
height: 100%; |
|
margin: 0; |
|
padding: 0; |
|
} |
|
|
|
#renderCanvas { |
|
width: 100%; |
|
height: 100%; |
|
touch-action: none; |
|
} |
|
|
|
#testCanvas0 { |
|
position:absolute; |
|
width: 128px; |
|
height: 128px; |
|
right:600px; |
|
bottom: 0; |
|
} |
|
|
|
#testCanvas1 { |
|
position:absolute; |
|
width: 128px; |
|
height: 128px; |
|
right:450px; |
|
bottom: 0; |
|
} |
|
|
|
#testCanvas2 { |
|
position:absolute; |
|
width: 128px; |
|
height: 128px; |
|
right: 300px; |
|
bottom: 0; |
|
} |
|
|
|
#testCanvas3 { |
|
position: absolute; |
|
width: 128px; |
|
height: 128px; |
|
right: 150px; |
|
bottom: 0; |
|
} |
|
|
|
#testCanvas4 { |
|
position: absolute; |
|
width: 128px; |
|
height: 128px; |
|
right: 0px; |
|
bottom: 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.vote { |
|
position: absolute; |
|
width: 60px; |
|
height: 60px; |
|
right: 10px; |
|
} |
|
|
|
.vote:hover { |
|
cursor: pointer; |
|
} |
|
|
|
#like { |
|
bottom: 200px; |
|
} |
|
|
|
#dislike { |
|
bottom: 120px; |
|
-webkit-transform: scaleX(-1); |
|
transform: scaleX(-1); |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<canvas id="renderCanvas"></canvas> |
|
<canvas id="testCanvas0"></canvas> |
|
<canvas id="testCanvas1"></canvas> |
|
<canvas id="testCanvas2"></canvas> |
|
<canvas id="testCanvas3"></canvas> |
|
<canvas id="testCanvas4"></canvas> |
|
|
|
|
|
<div class="vote" id="like"> |
|
<img src="data:image/png;base64,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" alt="like"> |
|
</div> |
|
<div class="vote" id="dislike"> |
|
<img src="data:image/png;base64,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" alt=""> |
|
</div> |
|
|
|
|
|
<script> |
|
|
|
window.engine = null; |
|
window.scene = null; |
|
window.sceneToRender = null; |
|
|
|
const agent = new AgentSac({trainable: false, verbose: false}) |
|
|
|
const canvas = document.getElementById("renderCanvas"); |
|
const createDefaultEngine = () => new BABYLON.Engine(canvas, true, { |
|
preserveDrawingBuffer: true, |
|
stencil: true, |
|
disableWebGL2Support: false |
|
}) |
|
|
|
window.vote = 0 |
|
document.getElementById("like").addEventListener("click", () => { |
|
|
|
|
|
window.reward = 1 |
|
|
|
|
|
|
|
}) |
|
|
|
document.getElementById("dislike").addEventListener("click", () => { |
|
|
|
|
|
window.reward = -1 |
|
|
|
|
|
|
|
}) |
|
|
|
window.transitions = [] |
|
window.globalReward = 0 |
|
const BINOCULAR = true |
|
|
|
const createScene = async () => { |
|
await agent.init() |
|
|
|
|
|
|
|
|
|
const scene = new BABYLON.Scene(engine); |
|
scene.collisionsEnabled = true |
|
|
|
|
|
const hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData("3d/env/environment.dds", scene); |
|
hdrTexture.name = "envTex"; |
|
hdrTexture.gammaSpace = false; |
|
scene.environmentTexture = hdrTexture; |
|
|
|
const skybox = BABYLON.MeshBuilder.CreateBox("skyBox", {size:1000.0}, scene); |
|
const skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene); |
|
skyboxMaterial.backFaceCulling = false; |
|
skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("3d/env/skybox", scene); |
|
skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE; |
|
skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0); |
|
skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0); |
|
skybox.material = skyboxMaterial; |
|
|
|
|
|
const camera = new BABYLON.ArcRotateCamera("Camera", BABYLON.Tools.ToRadians(-120), BABYLON.Tools.ToRadians(80), 65, new BABYLON.Vector3(0, -15, 0), scene); |
|
camera.attachControl(canvas, true); |
|
camera.lowerRadiusLimit = 10; |
|
camera.upperRadiusLimit = 120; |
|
camera.collisionRadius = new BABYLON.Vector3(2, 2, 2); |
|
camera.checkCollisions = true; |
|
|
|
|
|
scene.enablePhysics(new BABYLON.Vector3(0, 0, 0), new BABYLON.AmmoJSPlugin(false)); |
|
|
|
const physicsEngine = scene.getPhysicsEngine() |
|
|
|
physicsEngine.setTimeStep(1 / 60) |
|
physicsEngine.setSubTimeStep(1) |
|
|
|
|
|
const light1 = new BABYLON.PointLight("light1", new BABYLON.Vector3(0, 5,-6), scene); |
|
const light2 = new BABYLON.PointLight("light2", new BABYLON.Vector3(6, 5, 3.5), scene); |
|
const light3 = new BABYLON.DirectionalLight("light3", new BABYLON.Vector3(20, -5, 20), scene); |
|
light1.intensity = 15; |
|
light2.intensity = 5; |
|
|
|
engine.displayLoadingUI(); |
|
|
|
await Promise.all([ |
|
BABYLON.SceneLoader.AppendAsync("3d/marbleTower.glb"), |
|
BABYLON.SceneLoader.AppendAsync("https://models.babylonjs.com/Marble/marble/marble.gltf") |
|
]) |
|
scene.getMeshByName("marble").isVisible = false |
|
|
|
const tower = scene.getMeshByName("tower"); |
|
tower.setParent(null) |
|
tower.checkCollisions = true; |
|
tower.impostor = new BABYLON.PhysicsImpostor(tower, BABYLON.PhysicsImpostor.MeshImpostor, { |
|
mass: 0, |
|
friction: 1 |
|
}, scene); |
|
tower.material = scene.getMaterialByName("stone") |
|
tower.material.backFaceCulling = false |
|
|
|
|
|
|
|
const creature = BABYLON.MeshBuilder.CreateSphere("creature", {diameter: 1, segments:32}, scene) |
|
creature.parent = null |
|
creature.setParent(null) |
|
creature.position = new BABYLON.Vector3(0,-5,0) |
|
|
|
creature.isPickable = false |
|
|
|
const crMat = new BABYLON.StandardMaterial("cr_mat", scene); |
|
crMat.alpha = 0 |
|
creature.material = crMat |
|
|
|
creature.impostor = new BABYLON.PhysicsImpostor(creature, BABYLON.PhysicsImpostor.SphereImpostor, { |
|
mass: 1, |
|
friction: 0, |
|
stiffness: 0, |
|
restitution: 0 |
|
}, scene) |
|
|
|
BABYLON.ParticleHelper.SnippetUrl = "3d/snippet"; |
|
|
|
creature.sparks = await BABYLON.ParticleHelper.CreateFromSnippetAsync("UY098C-3.json", scene, false); |
|
creature.sparks.emitter = creature; |
|
|
|
creature.glow = await BABYLON.ParticleHelper.CreateFromSnippetAsync("EXUQ7M-5.json", scene, false); |
|
creature.glow.emitter = creature; |
|
|
|
|
|
const crCameraLeft = new BABYLON.UniversalCamera("cr_camera_l", new BABYLON.Vector3(0, 0, 0), scene) |
|
crCameraLeft.parent = creature |
|
crCameraLeft.position = new BABYLON.Vector3(-0.5, 0, 0) |
|
crCameraLeft.fov = 2 |
|
crCameraLeft.setTarget(new BABYLON.Vector3(-1, 0, 0.6)) |
|
|
|
const crCameraRight = new BABYLON.UniversalCamera("cr_camera_r", new BABYLON.Vector3(0, 0, 0), scene) |
|
crCameraRight.parent = creature |
|
crCameraRight.position = new BABYLON.Vector3(0.5, 0, 0) |
|
crCameraRight.fov = 2 |
|
crCameraRight.setTarget(new BABYLON.Vector3(1, 0, 0.6)) |
|
|
|
|
|
|
|
const crCameraLeftPl = BABYLON.MeshBuilder.CreateSphere("crCameraLeftPl", {diameter: 0.1, segments: 32}, scene); |
|
crCameraLeftPl.parent = creature |
|
crCameraLeftPl.position = new BABYLON.Vector3(-0.5, 0, 0) |
|
const crCameraLeftPlclMat = new BABYLON.StandardMaterial("crCameraLeftPlclMat", scene) |
|
crCameraLeftPlclMat.alpha = 0.3 |
|
crCameraLeftPlclMat.diffuseColor = new BABYLON.Color3(0, 0, 0) |
|
crCameraLeftPl.material = crCameraLeftPlclMat |
|
|
|
const crCameraRightPl = BABYLON.MeshBuilder.CreateSphere("crCameraRightPl", {diameter: 0.1, segments: 32}, scene); |
|
crCameraRightPl.parent = creature |
|
crCameraRightPl.position = new BABYLON.Vector3(0.5, 0, 0) |
|
const crCameraRightPlclMat = new BABYLON.StandardMaterial("crCameraRightPlclMat", scene) |
|
crCameraRightPlclMat.alpha = 0.3 |
|
crCameraRightPlclMat.diffuseColor = new BABYLON.Color3(0, 0, 0) |
|
crCameraRightPl.material = crCameraRightPlclMat |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const client = BABYLON.MeshBuilder.CreateSphere("client", {diameter: 3, segments: 32}, scene); |
|
client.parent = camera |
|
client.setParent(camera) |
|
|
|
|
|
const clMat = new BABYLON.StandardMaterial("cl_mat", scene) |
|
clMat.diffuseColor = new BABYLON.Color3(0, 0, 0) |
|
client.material = clMat |
|
|
|
engine.hideLoadingUI(); |
|
|
|
|
|
const cage = BABYLON.MeshBuilder.CreateSphere("cage", { |
|
segements: 64, |
|
diameter: 50 |
|
}, scene) |
|
|
|
|
|
|
|
|
|
|
|
|
|
cage.parent = null |
|
cage.setParent(null) |
|
cage.position = new BABYLON.Vector3(0, -12,0) |
|
cage.isPickable = true |
|
|
|
const cageMat = new BABYLON.StandardMaterial("cage_mat", scene); |
|
cageMat.alpha = 0.1 |
|
cage.material = cageMat |
|
cage.material.backFaceCulling = false |
|
|
|
cage.impostor = new BABYLON.PhysicsImpostor(cage, BABYLON.PhysicsImpostor.MeshImpostor, { |
|
mass: 0, |
|
friction: 1 |
|
}, scene); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const ballPos = [[-10,-10,10], [10,-10,-10], [-10,-10,-10], [10,-10,10]] |
|
|
|
const balls = ['green', 'green', 'red', 'red'].map((color, i) => { |
|
const ball = BABYLON.MeshBuilder.CreateSphere("ball_"+ color + i, {diameter: 7, segments: 64}, scene) |
|
ball.position = new BABYLON.Vector3(...ballPos[i]) |
|
ball.parent = null |
|
ball.setParent(null) |
|
ball.isPickable = true |
|
ball.impostor = new BABYLON.PhysicsImpostor(ball, BABYLON.PhysicsImpostor.SphereImpostor, { |
|
mass: 7, |
|
friction: 1, |
|
stiffness: 1, |
|
restitution: 1 |
|
}, scene); |
|
ball.material = scene.getMaterialByName(color + "Mat") |
|
ball.checkCollisions = true |
|
ball.material.backFaceCulling = false |
|
|
|
return ball |
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const worker = new Worker('worker.js') |
|
let inited = false |
|
worker.addEventListener('message', e => { |
|
const { weights, frame } = e.data |
|
|
|
tf.tidy(() => { |
|
if (weights) { |
|
inited = true |
|
agent.actor.setWeights(weights.map(w => tf.tensor(w))) |
|
if (Math.random() > 0.99) console.log('weights:', weights) |
|
} |
|
|
|
}) |
|
}) |
|
|
|
|
|
const impostors = scene.getPhysicsEngine()._impostors.filter(im => im.object.id !== creature.id) |
|
creature.impostor.registerOnPhysicsCollide(impostors, (body1, body2) => {}) |
|
impostors.forEach(impostor => { |
|
impostor.onCollide = e => { |
|
if (window.onCollide) { |
|
const collision = e.point.subtract(creature.position).normalize() |
|
window.onCollide(collision, impostor.object.id) |
|
} |
|
} |
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const base64ToImg = (base64) => new Promise((res, _) => { |
|
const img = new Image() |
|
img.src = base64 |
|
img.onload = () => res(img) |
|
}) |
|
const TRANSITIONS_BUFFER_SIZE = 2 |
|
const frameEvery = 1000/30 |
|
const frameStack = [] |
|
|
|
|
|
|
|
let timer = Date.now() |
|
let busy = false |
|
let stateId = 0 |
|
|
|
let prevLinearVelocity = BABYLON.Vector3.Zero() |
|
window.collision = BABYLON.Vector3.Zero() |
|
window.reward = 0 |
|
window.globalReward = 0 |
|
|
|
|
|
const testLayer = agent.actor.layers[4] |
|
const spy = tf.model({inputs: agent.actor.inputs, outputs: testLayer.output}) |
|
|
|
scene.registerAfterRender(async () => { |
|
if (busy || !inited) return |
|
|
|
|
|
|
|
|
|
busy = true |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!frameStack.length) { |
|
frameStack.push([ |
|
await BABYLON.Tools.CreateScreenshotUsingRenderTargetAsync(engine, crCameraLeft, { |
|
height: agent._frameShape[0], |
|
width: agent._frameShape[1] |
|
}) |
|
]) |
|
} else { |
|
frameStack[0].push( |
|
await BABYLON.Tools.CreateScreenshotUsingRenderTargetAsync(engine, crCameraRight, { |
|
height: agent._frameShape[0], |
|
width: agent._frameShape[1] |
|
}) |
|
) |
|
} |
|
|
|
|
|
|
|
|
|
if (frameStack.length >= agent._nFrames && frameStack[0].length == 2) { |
|
if (frameStack.length > agent._nFrames) |
|
throw new Error("(⊙_⊙')") |
|
|
|
const imgs = await Promise.all(frameStack.flat().map(fr => base64ToImg(fr))) |
|
|
|
const framesNorm = tf.tidy(() => { |
|
const greyScaler = tf.tensor([0.299, 0.587, 0.114], [1, 1, 3]) |
|
let imgTensors = imgs.map(img => tf.browser.fromPixels(img) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
imgTensors = imgTensors.map((t, i) => { |
|
const canv = document.getElementById('testCanvas' + (i+3)) |
|
if (canv) { |
|
tf.browser.toPixels(t, canv) |
|
} |
|
return t |
|
.sub(255/2) |
|
.div(255/2) |
|
}) |
|
|
|
|
|
const resL = tf.concat(imgTensors.filter((el, i) => i%2==0), -1) |
|
const resR = tf.concat(imgTensors.filter((el, i) => i%2==1), -1) |
|
return [resL, resR] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}) |
|
const framesBatch = framesNorm.map(fr => tf.stack([fr])) |
|
|
|
const delta = (Date.now() - timer) / 1000 |
|
console.log('delta (s)', delta) |
|
const linearVelocity = creature.impostor.getLinearVelocity() |
|
const linearVelocityNorm = linearVelocity.normalize() |
|
const acceleration = linearVelocity.subtract(prevLinearVelocity).scale(1/delta).normalize() |
|
|
|
timer = Date.now() |
|
prevLinearVelocity = linearVelocity |
|
|
|
const ray = new BABYLON.Ray(creature.position, linearVelocityNorm) |
|
const hit = scene.pickWithRay(ray) |
|
let lidar = 0 |
|
if (hit.pickedMesh) { |
|
lidar = Math.tanh((hit.distance - creature.impostor.getRadius())/10) |
|
|
|
} |
|
|
|
const telemetry = [ |
|
linearVelocityNorm.x, |
|
linearVelocityNorm.y, |
|
linearVelocityNorm.z, |
|
acceleration.x, |
|
acceleration.y, |
|
acceleration.z, |
|
window.collision.x, |
|
window.collision.y, |
|
window.collision.z, |
|
lidar |
|
] |
|
const reward = window.reward |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
window.collision = BABYLON.Vector3.Zero() |
|
window.reward = -0.01 |
|
window.onCollide = undefined |
|
const telemetryBatch = tf.tensor(telemetry, [1, agent._nTelemetry]) |
|
const action = agent.sampleAction([telemetryBatch, ...framesBatch]) |
|
|
|
|
|
|
|
console.time('await') |
|
const [framesArrL, framesArrR,[actionArr]] = await Promise.all([...(framesNorm.map(fr => fr.array())), action.array()]) |
|
console.timeEnd('await') |
|
|
|
tf.tidy(() => { |
|
const testOutput = spy.predict([telemetryBatch, ...framesBatch], {batchSize: 1}) |
|
console.log('spy', testLayer.name, testOutput.arraySync()) |
|
|
|
return |
|
|
|
let tiles = tf.clipByValue(tf.squeeze(testOutput), 0, 1) |
|
tiles = tf.transpose(tiles, [2,0,1]) |
|
tiles = tf.unstack(tiles) |
|
|
|
let res = [], line = [] |
|
for (const [i, tile] of tiles.entries()) { |
|
line.push(tile) |
|
if ((i+1) % 8 == 0 && i) { |
|
res.push(tf.concat(line, 1)) |
|
line = [] |
|
} |
|
} |
|
const testFr = tf.concat(res) |
|
tf.browser.toPixels(testFr, document.getElementById('testCanvas2')) |
|
}) |
|
|
|
const |
|
impulse = actionArr.slice(0, 3) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.assert(actionArr.length === 3, actionArr.length) |
|
console.assert(impulse.length === 3) |
|
|
|
|
|
|
|
|
|
|
|
creature.impostor.setAngularVelocity(BABYLON.Quaternion.Zero()) |
|
|
|
creature.impostor.applyImpulse(new BABYLON.Vector3(...impulse), creature.getAbsolutePosition()) |
|
creature.impostor.setAngularVelocity(BABYLON.Quaternion.Zero()) |
|
|
|
|
|
|
|
const newLinearVelocity = creature.impostor.getLinearVelocity().normalize() |
|
|
|
creature.lookAt(creature.position.add(newLinearVelocity)) |
|
|
|
|
|
|
|
const transtion = { |
|
id: stateId++, |
|
state: [telemetry, framesArrL, framesArrR], |
|
action: actionArr, |
|
reward |
|
} |
|
transitions.push(transtion) |
|
|
|
window.onCollide = (collision, mesh) => { |
|
window.collision = collision |
|
window.reward += -0.05 |
|
|
|
if (mesh.startsWith('ball_')) { |
|
console.log('reward', mesh) |
|
window.reward = 1 |
|
|
|
if (mesh.includes('red')) |
|
window.reward = -1 |
|
} |
|
|
|
window.onCollide = undefined |
|
} |
|
|
|
if (transitions.length >= TRANSITIONS_BUFFER_SIZE) { |
|
if (transitions.length > TRANSITIONS_BUFFER_SIZE || TRANSITIONS_BUFFER_SIZE < 2) |
|
throw new Error("(⊙_⊙')") |
|
|
|
const transition = transitions.shift() |
|
|
|
|
|
|
|
|
|
|
|
window.globalReward += transition.reward |
|
console.log('reward', transition.reward, window.globalReward) |
|
|
|
|
|
worker.postMessage({action: 'newTransition', transition}) |
|
|
|
} |
|
|
|
|
|
|
|
framesNorm.map(fr => fr.dispose()) |
|
framesBatch.map(fr => fr.dispose()) |
|
telemetryBatch.dispose() |
|
action.dispose() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frameStack.length = 0 |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
busy = false |
|
}) |
|
|
|
return scene |
|
}; |
|
|
|
window.initFunction = async function() { |
|
await Ammo(); |
|
|
|
const asyncEngineCreation = async function() { |
|
try { |
|
return createDefaultEngine(); |
|
} catch(e) { |
|
console.log("the available createEngine function failed. Creating the default engine instead"); |
|
return createDefaultEngine(); |
|
} |
|
} |
|
|
|
window.engine = await asyncEngineCreation(); |
|
|
|
if (!engine) throw 'engine should not be null.'; |
|
|
|
window.scene = await createScene(); |
|
}; |
|
|
|
initFunction().then(() => { |
|
sceneToRender = scene; |
|
engine.runRenderLoop(function () { |
|
if (sceneToRender && sceneToRender.activeCamera) { |
|
sceneToRender.render(); |
|
} |
|
}); |
|
}); |
|
|
|
window.addEventListener("resize", function () { |
|
engine.resize(); |
|
}); |
|
</script> |
|
</body> |
|
</html> |
|
|