JavaScript - Library (Available on WebRepo)

@binaryblues Loading from a web url seems to work just fine if you use the Three.js project from WebRepo and replace the JS with this:

// Black texture workaround for iOS
        // https://discourse.threejs.org/t/textures-in-gltf-sometimes-display-black-but-only-on-ios/30520/28
        window.createImageBitmap = undefined;
        
        let camera, scene, renderer;
			let mesh;

			init();
			animate();

			function init() {

				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
				camera.position.z = 400;

				scene = new THREE.Scene();

				const texture = new THREE.TextureLoader().load( 'https://www.code3dgames.com/textures/hardwood.png');

				const geometry = new THREE.BoxGeometry( 200, 200, 200 );
				const material = new THREE.MeshBasicMaterial( { map: texture } );

				mesh = new THREE.Mesh( geometry, material );
				scene.add( mesh );

				renderer = new THREE.WebGLRenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				document.body.appendChild( renderer.domElement );
			}

			function animate() {

				requestAnimationFrame( animate );

				mesh.rotation.x += 0.005;
				mesh.rotation.y += 0.01;

				renderer.render( scene, camera );

			}

It may have been an issue with an older version of the JavaScript lib from the forum.

I’ll also point out that any texture loaded from the internet needs to support Cross-Origin-Resource-Sharing CORS. If the server doesn’t return an access-control-allow-origin: * header then WebGL will allow the image to be used.

@Steppers Yes! It works! very well!

It looks like you’ve made some modifications to three.js. Do all the modules you need to import do the same In that case? suppose I want to import these modules:

https://code3dgames.com/controls/FlyControls.js
https://code3dgames.com/controls/OrbitControls.js
https://code3dgames.com/physi.js
https://code3dgames.com/tween.js
https://code3dgames.com/sounds.js
https://code3dgames.com/noise.js
https://code3dgames.com/scoreboard.js

What Should I do? Thank you!

This is the video:

https://youtu.be/Twh9Ch3LIos

@binaryblues I’m assuming you’re using the book by Chris Strom?

If you are then the examples appear to not be using ES modules at all as it’s a far older version of Three.js so it can just be loaded like a normal JS script.

You’ll need to add the latest ‘JavaScript’ 1.1.1 lib (on WebRepo) as a dependency but this should work just fine once you add some user code :smile:

function main()
    
    -- Create a webview
    webview = WebView()
    
    -- Import initial global values
    importGlobals(webview)
    
    -- Load Three.js
    webview:loadJS('https://code3dgames.com/three.js', true)
    webview:loadJS('https://code3dgames.com/controls/FlyControls.js', true)
    webview:loadJS('https://code3dgames.com/controls/OrbitControls.js', true)
    webview:loadJS('https://code3dgames.com/physi.js', true)
    webview:loadJS('https://code3dgames.com/tween.js', true)
    webview:loadJS('https://code3dgames.com/sounds.js', true)
    webview:loadJS('https://code3dgames.com/noise.js', true)
    webview:loadJS('https://code3dgames.com/scoreboard.js', true)
    
    -- Load the user code
    webview:loadJS([[
        — Your user code here…
    ]], true)
    
    -- Display once the engine has initialised
    webview:show()
end

@Steppers Yes, you guessed it, I Learned javascript from this book and found it to be a particularly good introduction to javascript.

Now there is a new problem, It showed error when I install the project from WebRepo:

the error of WebRepo:

DB:231: attempt to index a nil value (local 'file')
stack traceback:
	DB:231: in field 'installApp'
	AppWindow:151: in upvalue 'callback'
	_dep_Documents:Oil:655: in local 'handler'
	_dep_Documents:Oil:1944: in method 'internal_handle_event'
	_dep_Documents:Oil:1936: in method 'handle_event'
	_dep_Documents:Oil:1954: in method 'children_handle_event'
	_dep_Documents:Oil:938: in method 'handle_event'
	_dep_Documents:Oil:1954: in method 'children_handle_event'
	_dep_Documents:Oil:1932: in method 'handle_event'
	_dep_Documents:Oil:1954: in method 'children_handle_event'
	_dep_Documents:Oil:1932: in method 'handle_event'
	_dep_Documents:Oil:429: in upvalue 'dispatch_event'
	_dep_Documents:Oil:527: in field 'touch'
	Main:173: in function 'touched'

the error of JavaScript:

_dep_Documents:STLib:158: Main:3: attempt to index a nil value (field 'doc')
stack traceback:
	[C]: in function 'error'
	_dep_Documents:STLib:158: in upvalue 'callback'
	_dep_Documents:STLib:88: in field 'callback'
	_dep_Documents:STLib:63: in upvalue 'finishTween'
	...in pairs(tweens) do
    c = c + 1
  end
  return c
end

:589: in function <...in pairs(tweens) do
    c = c + 1
  end
  return c
end

:582>

@binaryblues That looks like you have an old version of WebRepo which means project subdirectories won’t work. Does WebRepo not offer an update for itself? You’ll need the latest version (2.1.2).

Also available here: https://github.com/steppers/codea-community-repo/releases/tag/2.1.1

@Steppers Yes, I used an old WebRepo. When I get the new one(2.1.1) from github, it can install the JavaScript and the Three.js, and the Three.js need to modify the line:

function main()
    local webview = WebView(asset.doc.index)
    webview:show()
    viewer.mode = FULLSCREEN
end

to

 local webview = WebView(asset.threejs_demo.index)

then it will show the model scene like the video:

https://youtu.be/xWKYbNthA3g

Btw. a very interesting scene! I like it.

In Three.js there is another error, this line:

function demo2()
    
    -- Load and show a HTML page from disk
    local webview = WebView(asset.core)
    webview:show()

In the project, I can not find the file core.

And it works! Need to comment this line in the main():

importGlobals(webview)

Using the Three.js from https://www.code3dgames.com/

Only in one example, these lines, I modified it from:

// Physics settings
Physijs.scripts.ammo = '/ammo.js';
Physijs.scripts.worker = '/physijs_worker.js';

to

// Physics settings
//Physijs.scripts.ammo = '/ammo.js';
//Physijs.scripts.worker = '/physijs_worker.js';
Physijs.scripts.ammo = 'https://www.code3dgames.com/ammo.js';
Physijs.scripts.worker = 'https://www.code3dgames.com/physijs_worker.js';

It returned this:

393:28 SecurityError: The operation is insecure.

Here is the whole code of myJS:

-- MyJS

function main()
    
    -- Create a webview
    webview = WebView()
    
    -- Import initial global values
    -- importGlobals(webview)
    
    -- Load Three.js
    print("Loading modules...")
    webview:loadJS('https://code3dgames.com/three.js', true)
    webview:loadJS('https://code3dgames.com/controls/FlyControls.js', true)
    webview:loadJS('https://code3dgames.com/controls/OrbitControls.js', true)
    webview:loadJS('https://code3dgames.com/physi.js', true)
    webview:loadJS('https://code3dgames.com/tween.js', true)
    webview:loadJS('https://code3dgames.com/sounds.js', true)
    webview:loadJS('https://code3dgames.com/noise.js', true)
    webview:loadJS('https://code3dgames.com/scoreboard.js', true)
    print("Done loading modules.")
    
    -- Load the user code
    webview:loadJS(JSCode4, true)

    -- Display once the engine has initialised
    webview:show()
end


JSCode4 = [[

// Physics settings
//Physijs.scripts.ammo = '/ammo.js';
//Physijs.scripts.worker = '/physijs_worker.js';
Physijs.scripts.ammo = 'https://www.code3dgames.com/ammo.js';
Physijs.scripts.worker = 'https://www.code3dgames.com/physijs_worker.js';

// The "scene" is where stuff in our game will happen:
var scene = new Physijs.Scene();
scene.setGravity(new THREE.Vector3( 0, -100, 0 ));
var flat = {flatShading: true};
var light = new THREE.AmbientLight('white', 0.2);
scene.add(light);



// The "camera" is what sees the stuff:
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
camera.position.z = 500;
camera.position.y = 200;
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene.add(camera);

// The "renderer" draws what the camera sees onto the screen:
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

// ******** START CODING ON THE NEXT LINE ********
function addGround() {
var w = window.innerWidth;
var h = window.innerHeight;
var shape = new THREE.BoxGeometry(2*w, h ,1);
var cover = new THREE.MeshBasicMaterial({color: 'lawngreen'});
var ground = new Physijs.BoxMesh(shape, cover, 0)
ground.position.y = -h/2;
ground.receiveShadow.enabled = true;
scene.add(ground);
return ground;
}

var ground = addGround();

function addLight() {
var light = new THREE.PointLight('white', 0.4);
light.position.set(50,200,-100);
light.castShadow = true;
scene.add(light);
return light;
}

var light1 = addLight();

function Launcher() {
this.angle = 0;
this.power = 0;
this.draw();
}

Launcher.prototype.draw = function() {
var direction = new THREE.Vector3(0, 1, 0);
var position = new THREE.Vector3(0, -100, 250);
var length = 100;
this.arrow = new THREE.ArrowHelper(
direction,
position,
length,
'yellow'
);
scene.add(this.arrow);  
};

Launcher.prototype.vector = function() {
return new THREE.Vector3(
Math.sin(this.angle),
Math.cos(this.angle),
0
);
}

Launcher.prototype.moveLeft = function() {
this.angle = this.angle - Math.PI/100;
this.arrow.setDirection(this.vector());
}

Launcher.prototype.moveRight = function() {
this.angle = this.angle + Math.PI/100;
this.arrow.setDirection(this.vector());
}

Launcher.prototype.powerUp = function() {
if (this.power >= 100) return;
this.power = this.power + 5;
this.arrow.setLength(this.power);
}

Launcher.prototype.launch = function() {
var shape = new THREE.SphereGeometry(10);
var material = new THREE.MeshPhongMaterial({color: 'yellow'});
var ball = new Physijs.SphereMesh(shape, material, 1);
ball.name = "Game Ball";
ball.position.set(0, 0, 300);
ball.castShadow = true;
scene.add(ball);

var speedVector = new THREE.Vector3(
2.5*this.power * this.vector().x,
2.5*this.power * this.vector().y,
-80
);
ball.setLinearVelocity(speedVector);
this.power = 0;
this.arrow.setLength(100);
};


function Basket(size, points) {
this.size = size;
this.points = points;
this.height = 100/Math.log10(size);
var r = Math.random;
this.color = new THREE.Color(r(), r(), r());

this.draw();
}

Basket.prototype.draw = function() {
var cover = new THREE.MeshPhongMaterial({
color: this.color,
shininess: 50,
specular: 'white'
});

var shape = new THREE.CubeGeometry(this.size, 1, this.size);
var goal = new Physijs.BoxMesh(shape, cover, 0);
goal.position.y = this.height/100;
scene.add(goal);

var halfSize = this.size/2;
var halfHeight = this.height/2;

shape = new THREE.CubeGeometry(this.size, this.height, 1);
var side1 = new Physijs.BoxMesh(shape, cover, 0);
side1.position.set(0, halfHeight, halfSize);
scene.add(side1);

var side2 = new Physijs.BoxMesh(shape, cover, 0);
side2.position.set(0, halfHeight, -halfSize);
scene.add(side2);

shape = new THREE.CubeGeometry(1, this.height, this.size);
var side3 = new Physijs.BoxMesh(shape, cover, 0);
side3.position.set(halfSize, halfHeight, 0);
scene.add(side3);

var side4 = new Physijs.BoxMesh(shape, cover, 0);
side4.position.set(-halfSize, halfHeight, 0);
scene.add(side4);

this.waitForScore(goal);
}

Basket.prototype.waitForScore = function(goal) {
goal.addEventListener('collision', this.score.bind(this));
}

Basket.prototype.score = function(ball) {
if (scoreboard.getTimeRemaining() == 0) return;
scoreboard.addPoints(this.points);
scene.remove(ball);
}

function Wind() {
this.draw();
this.change();
}

Wind.prototype.draw = function() {
var dir = new THREE.Vector3(1, 0, 0);
var start = new THREE.Vector3(0, 200, 250);
this.arrow = new THREE.ArrowHelper(dir, start, 1, 'lightblue');
scene.add(this.arrow);
}

Wind.prototype.change = function() {
if (Math.random() < 0.5) this.direction = -1;
else this.direction =1;
this.strength = 20 * Math.random();

this.arrow.setLength(5*this.strength);
this.arrow.setDirection(this.vector());

setTimeout(this.change.bind(this), 10000);
}

Wind.prototype.vector = function() {
var x = this.direction * this.stength;
return new THREE.Vector3(x, 0, 0);
}

function allBalls(){
var balls = [];
for (var i=0; i<scene.children.length; i++) {
if (scene.children[i].name.startsWith('Game Ball')) {
balls.push(scene.children[i]);
}
}
return balls;
}
var goal1 = new Basket(200,10);
var goal2 = new Basket(40, 100);

var launcher = new Launcher();
var wind = new Wind();

var scoreboard = new Scoreboard();
scoreboard.countdown(60);
scoreboard.score(0);
scoreboard.help(
'?????????????.' +
'???????????????.' +
'?????????.' +
'????!!!'
);
scoreboard.onTimeExpired(timeExpired);
function timeExpired() {
scoreboard.message("Game Over!");
}

// Animate motion in the game
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}

animate();

// Run physics
function gameStep() {
scene.simulate();

var balls = allBalls();
for (var i=0; i<balls.length; i++) {
balls[i].applyCentralForce(wind.vector());
if (balls[i].position.y < -100) scene.remove(balls[i]);
}
// Update physics 60 times a second so that motion is smooth
setTimeout(gameStep, 1000/60);
}
gameStep();

document.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') launcher.moveLeft();
if (code == 'ArrowRight') launcher.moveRight();
if (code == 'ArrowDown') launcher.powerUp();
//if (code == 'Arrow')
}

document.addEventListener('keyup', sendKeyUp);
function sendKeyUp(event) {
var code = event.code;
if (code == 'ArrowDown') launcher.launch();
}

]]

@binaryblues Those issues should be resolved in the latest Three.js project on WebRepo. Though even with those errors in the editor it still ran for me.

I’ll also point out that the _dep_ tabs are auto included dependencies generated during an upload to WebRepo and should usually be ignored and not modified at all.

@binaryblues Try the same script as you had above with Three.js 1.0.3 (on WebRepo). Seems to work for me now.

Turns out JavaScript Worker threads aren’t perfect in a WKWebView but 1.0.3 has a workaround included automatically now :smile:

@Steppers I got the new version:

  • three.js : 1.0.3
  • javascript: 1.1.2

Now all work! Thank you! Very well?

Have you ever wondered how to use a virtual keyboard in Codea Javascript code ?

@binaryblues With the latest ‘JavaScript’ lib from WebRepo (v1.1.3):

    local webview = WebView()
    
    -- Alias console.log to Lua's print function
    webview:import("console.log", print)
    
    webview:import({
        showKeyboard = function()
            print("Showing keyboard")
            showKeyboard();
        end,
        hideKeyboard = function()
            print("Hiding keyboard")
            hideKeyboard();
        end,
    })
    webview:loadJS([[
        // Show the keyboard
        showKeyboard();
    
        // Function to be called on key events
        function keyboard(key) {
                        
            // Print the key!
            console.log(key);
        }
    ]], true)
    
    _G.keyboard = function(key)
        ST.runAsync(function()
            webview:callAsync("keyboard", key)
        end)
    end

@Steppers Get the JavaScript 1.1.3 and it works very well!

The only thing that matters now is how to distinguish between keydown and keyup, the two states of pressing a key and releasing a key, but this seems to be handled by Codea’s keyboard() function.

Other than that, everything’s great. Thank you for your work.

@Steppers In case I didn’t make myself clear, I’m talking about the iPad’s virtual keyboard, not the external physical Bluetooth keyboard.

In my example code, I have these lines?

document.addEventListener('keydown', sendKeyDown);
function sendKeyDown(event) {
var code = event.code;
if (code == 'ArrowLeft') launcher.moveLeft();
if (code == 'ArrowRight') launcher.moveRight();
if (code == 'ArrowDown') launcher.powerUp();
//if (code == 'Arrow')
}

document.addEventListener('keyup', sendKeyUp);
function sendKeyUp(event) {
var code = event.code;
if (code == 'ArrowDown') launcher.launch();
}

But on a virtual keyboard, they don’t work.

@binaryblues Check out ‘MKInput’ on WebRepo :smile: It does exactly that in Lua (not JS).

However, doing this from within JS is preferable (you need to tap or click first for key events to work):

function main()
    local webview = WebView()
    webview:show()
    
    webview:import("console.log", print)
    webview:loadJS([[
        window.addEventListener("keydown", (event) => {
            if (event.repeat) return;
            console.log(`Key pressed: ${event.code}`);
        });
    
        window.addEventListener("keyup", (event) => {
            console.log(`Key released: ${event.code}`);
        });
    ]], true)
end

@binaryblues Ah, I see what you mean.
As far as I know, the iOS virtual keyboard doesn’t support that at all I’m afraid.

@Steppers I got it, Thank you for explaining that.

I’ll try to check the iOS documentation to see if there’s a clear indication of support.

Couldn’t download from WebRepo.

Error message attached.

Project was created but it only had two tabs.

@UberGoober You’ll want the latest update from the WebRepo GitHub page :smile:

@Steppers WebRepo won’t update itself correctly anymore?