From 70c6d6348216714d62ccde86aaefc3acba3bdb82 Mon Sep 17 00:00:00 2001 From: Cassie <95011670+cassiehuang72@users.noreply.github.com> Date: Tue, 10 Feb 2026 18:06:36 -0500 Subject: [PATCH 1/5] Update repo and add new VR scenes and textures --- js/scenes/campFire.js | 48 +++++++++++++++++++++++ js/scenes/car.js | 56 +++++++++++++++++++++++++++ js/scenes/carDrive.js | 83 ++++++++++++++++++++++++++++++++++++++++ js/scenes/classUse1.js | 40 +++++++++++++++++++ js/scenes/classUse2.js | 40 +++++++++++++++++++ js/scenes/joints.js | 2 +- js/scenes/scenes.js | 5 +++ media/textures/log.png | Bin 0 -> 6920 bytes media/textures/log1.png | Bin 0 -> 12394 bytes media/textures/tire.png | Bin 0 -> 30900 bytes package.json | 3 ++ 11 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 js/scenes/campFire.js create mode 100644 js/scenes/car.js create mode 100644 js/scenes/carDrive.js create mode 100644 js/scenes/classUse1.js create mode 100644 js/scenes/classUse2.js create mode 100644 media/textures/log.png create mode 100644 media/textures/log1.png create mode 100644 media/textures/tire.png diff --git a/js/scenes/campFire.js b/js/scenes/campFire.js new file mode 100644 index 0000000..867824c --- /dev/null +++ b/js/scenes/campFire.js @@ -0,0 +1,48 @@ +/* + This scene is an example of how to use procedural texture + to animate the shape of an object. In this case the object + is a waving flag. The noise function is used to animate + the position of each vertex of the flag geometry. +*/ + +import * as cg from "../render/core/cg.js"; + +export const init = async model => { + // Define a tall, thin grid for the flame + clay.defineMesh('flame', clay.createGrid(20, 30)); + + // fire layer holder + let fireLayers = []; + for (let i = 0; i < 6; i++){ + let fire = model.add('flame').color(1, .4, 0); // Orange color + fire.angle = i * 30; // angle = 0, 60, 120. + fireLayers.push(fire); + } + + // logs + model.txtrSrc(1, '../media/textures/log1.png'); + let logs =[] + for (let i = 0; i < 6; i++){ + let log =model.add('tubeX').color(0.4,0.3,0.3).txtr(1).scale(1.3,0.07,0.07).move(0,0,0); + log.angle = i * 30 + //log.turnY(log.angle); + logs.push(log); + } + + model.scale(0.3).move(0,4,0).animate(() => { + // Animate the flame by modifying its vertices + fireLayers.forEach(fire => { + fire.identity().turnY(fire.angle); + fire.setVertices((u,v) => { + return [0.8*(u-0.5)*(1-v), + 2*v, + .3 * v * cg.noise(5*u,5*v-model.time*3,model.time) + ]; + }); + }); + + // logs.forEach(log => { + // log.identity().turnY(log.angle); + // }); + }); +} diff --git a/js/scenes/car.js b/js/scenes/car.js new file mode 100644 index 0000000..15812d6 --- /dev/null +++ b/js/scenes/car.js @@ -0,0 +1,56 @@ +/* + Create and animate hierarchical joints. +*/ +let speed = 0.8 + +export const init = async model => { + + model.txtrSrc(1, '../media/textures/tire.png'); + + // CREATE NODES WITH NO SHAPES AS JOINTS FOR ANIMATION. + let carbody = model.add(); + + //wheel center joint + let wheelFLCenter = carbody.add(); + let wheelFRCenter = carbody.add(); + let wheelBLCenter = carbody.add(); + let wheelBRCenter = carbody.add(); + + //wheel joint + let wheelFL = wheelFLCenter.add(); + let wheelFR = wheelFRCenter.add(); + let wheelBL = wheelBLCenter.add(); + let wheelBR = wheelBRCenter.add(); + + // CREATE AND PLACE SHAPES THAT WILL MOVE WITH EACH JOINT. + // carboday + carbody.add('cube').scale(.8,.25,.4).move(0.8,1.5,-1).color(1,0,0); + //car cabin + carbody.add('cube').scale(.5,.2,.35).move(0.8,3.8,-1.2).color(1,1,1);; + + // wheel centers + wheelFRCenter.move(1.2,0,0) + wheelBLCenter.move(0,0,-0.8) + wheelFLCenter.move(1.2,0,-0.8) + + wheelBR.add('torusZ').scale(.18,.18,.2).txtr(1);; + wheelFR.add('torusZ').scale(.18,.18,.2).txtr(1);; + wheelBL.add('torusZ').scale(.18,.18,.2).txtr(1);; + wheelFL.add('torusZ').scale(.18,.18,.2).txtr(1);; + + // ANIMATE THE JOINTS OVER TIME. + model.scale(0.8,0.8,0.8).move(-0.5,1.3,0).animate(() => { + carbody.identity() + .turnY(Math.sin(speed*model.time)*.7+.7); + + wheelFL.identity() + .turnZ(Math.sin(speed*model.time)*.7+.7); + wheelFR.identity() + .turnZ(Math.sin(speed*model.time)*.7+.7); + wheelBL.identity() + .turnZ(Math.sin(speed*model.time)*.7+.7); + wheelBR.identity() + .turnZ(Math.sin(speed*model.time)*.7+.7); + }); +} + diff --git a/js/scenes/carDrive.js b/js/scenes/carDrive.js new file mode 100644 index 0000000..1cdf5b1 --- /dev/null +++ b/js/scenes/carDrive.js @@ -0,0 +1,83 @@ +/* + This is a very simple example of how to use the + inputEvents object. + + When the scene is in XR mode, the x position of + the left controller controls the red component + of the cube's color, and the x position of the + right controller controls the blue component of + the cube's color. +*/ +export const init = async model => { + // See what the inputEvents can do + // console.log(inputEvents); + + model.txtrSrc(1, '../media/textures/tire.png'); + + let speed = 0; + let color = [1,0,0]; + // CREATE NODES WITH NO SHAPES AS JOINTS FOR ANIMATION. + let carbody = model.add(); + + //wheel center joint + let wheelFLCenter = carbody.add(); + let wheelFRCenter = carbody.add(); + let wheelBLCenter = carbody.add(); + let wheelBRCenter = carbody.add(); + + //wheel joint + let wheelFL = wheelFLCenter.add(); + let wheelFR = wheelFRCenter.add(); + let wheelBL = wheelBLCenter.add(); + let wheelBR = wheelBRCenter.add(); + + // CREATE AND PLACE SHAPES THAT WILL MOVE WITH EACH JOINT. + // carboday + let chassis = carbody.add('cube').scale(.8,.25,.4).move(0.8,1.5,-1).color(color); + //car cabin + carbody.add('cube').scale(.5,.2,.35).move(0.8,3.8,-1.2).color(1,1,1);; + + // wheel centers + wheelFRCenter.move(1.2,0,0) + wheelBLCenter.move(0,0,-0.8) + wheelFLCenter.move(1.2,0,-0.8) + + wheelBR.add('torusZ').scale(.18,.18,.2).txtr(1);; + wheelFR.add('torusZ').scale(.18,.18,.2).txtr(1);; + wheelBL.add('torusZ').scale(.18,.18,.2).txtr(1);; + wheelFL.add('torusZ').scale(.18,.18,.2).txtr(1);; + + // USING THE GLOBAL inputEvents OBJECT + + inputEvents.onMove = hand => { + if (isXR()) { + if (hand == 'left'){ + color[0] = inputEvents.pos(hand)[0] * .5 + .5; + color[1] = inputEvents.pos(hand)[2] * .5 + .5; + } + } + } + + model.scale(0.3).move(-0.5,4.5,0).animate(() => { + if (inputEvents.isPressed('right')) { + speed += 0.005; + } else { + speed *= 0.95; + } + + //.identity resets everything + carbody.identity().move(speed * 2, 0, 0); + chassis.color(color); + + wheelFL.identity() + .turnZ(-speed*model.time*.7); + wheelFR.identity() + .turnZ(-speed*model.time*.7); + wheelBL.identity() + .turnZ(-speed*model.time*.7); + wheelBR.identity() + .turnZ(-speed*model.time*.7); + + }); +} + diff --git a/js/scenes/classUse1.js b/js/scenes/classUse1.js new file mode 100644 index 0000000..e1ca017 --- /dev/null +++ b/js/scenes/classUse1.js @@ -0,0 +1,40 @@ +/* + This scene is an example of how to use procedural texture + to animate the shape of an object. In this case the object + is a waving flag. The noise function is used to animate + the position of each vertex of the flag geometry. +*/ + +import * as cg from "../render/core/cg.js"; + +export const init = async model => { + + // DEFINE A NEW TERRAIN OBJECT TYPE AS A 30x20 GRID. + + clay.defineMesh('myTerrain', clay.createGrid(30, 20)); + + // LOAD A CHECKERBOARD TEXTURE FOR IT. + + model.txtrSrc(1, '../media/textures/chessboard.png'); + + // INSTANTIATE THE NEW TERRAIN OBJECT. + + let terrain = model.add('myTerrain').txtr(1); + + // MOVE THE OBJECT INTO PLACE. + + terrain.identity().move(-.4,1.5,0).scale(.4); + + model.animate(() => { + + // SIMULATE THE APPEARANCE OF A BILLOWING FLAG. + + terrain.setVertices((u,v) => { + return [ 3*u, + 2*v-1, + .4 * u * cg.noise(3*u-model.time,3*v,model.time) + ]; + }); + }); +} + diff --git a/js/scenes/classUse2.js b/js/scenes/classUse2.js new file mode 100644 index 0000000..e1ca017 --- /dev/null +++ b/js/scenes/classUse2.js @@ -0,0 +1,40 @@ +/* + This scene is an example of how to use procedural texture + to animate the shape of an object. In this case the object + is a waving flag. The noise function is used to animate + the position of each vertex of the flag geometry. +*/ + +import * as cg from "../render/core/cg.js"; + +export const init = async model => { + + // DEFINE A NEW TERRAIN OBJECT TYPE AS A 30x20 GRID. + + clay.defineMesh('myTerrain', clay.createGrid(30, 20)); + + // LOAD A CHECKERBOARD TEXTURE FOR IT. + + model.txtrSrc(1, '../media/textures/chessboard.png'); + + // INSTANTIATE THE NEW TERRAIN OBJECT. + + let terrain = model.add('myTerrain').txtr(1); + + // MOVE THE OBJECT INTO PLACE. + + terrain.identity().move(-.4,1.5,0).scale(.4); + + model.animate(() => { + + // SIMULATE THE APPEARANCE OF A BILLOWING FLAG. + + terrain.setVertices((u,v) => { + return [ 3*u, + 2*v-1, + .4 * u * cg.noise(3*u-model.time,3*v,model.time) + ]; + }); + }); +} + diff --git a/js/scenes/joints.js b/js/scenes/joints.js index f828488..e36d5b6 100644 --- a/js/scenes/joints.js +++ b/js/scenes/joints.js @@ -3,7 +3,7 @@ */ export const init = async model => { - + // CREATE NODES WITH NO SHAPES AS JOINTS FOR ANIMATION. let shoulder = model.add(); diff --git a/js/scenes/scenes.js b/js/scenes/scenes.js index e7f61ce..5de3d0e 100644 --- a/js/scenes/scenes.js +++ b/js/scenes/scenes.js @@ -31,6 +31,11 @@ export default () => { { name: "headGaze" , path: "./headGaze.js" , public: true }, { name: "reading" , path: "./reading.js" , public: true }, { name: "parse2" , path: "./parse2.js" , public: true }, + { name: "car" , path: "./car.js" , public: true }, + { name: "carDrive" , path: "./carDrive.js" , public: true }, + { name: "campFire" , path: "./campFire.js" , public: true }, + { name: "classUse1" , path: "./classUse1.js" , public: true }, + { name: "classUse2" , path: "./classUse2.js" , public: true }, ] }; } diff --git a/media/textures/log.png b/media/textures/log.png new file mode 100644 index 0000000000000000000000000000000000000000..1e520d8a6108f0de4e533de35a487971d1310afb GIT binary patch literal 6920 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 3809a5f..2e69cb4 100644 --- a/package.json +++ b/package.json @@ -39,5 +39,8 @@ "devDependencies": { "gltf-import-export": "^1.0.16", "prettier": "2.2.1" + }, + "volta": { + "node": "18.20.8" } } From 7a6a2c2ab5bccc025a55bfe721f778b8a90bcddc Mon Sep 17 00:00:00 2001 From: Cassie <95011670+cassiehuang72@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:55:00 -0500 Subject: [PATCH 2/5] Qiya Huang Assignments Submission --- js/scenes/classUse1.js | 42 +++------------- js/scenes/classUse2.js | 40 --------------- js/scenes/master2.js | 112 +++++++++++++++++++++++++++++++++++++++++ js/scenes/scenes.js | 3 +- js/scenes/text1.js | 1 - js/scenes/textHW.js | 61 ++++++++++++++++++++++ js/util/texts.js | 2 + server/main.js | 5 ++ 8 files changed, 189 insertions(+), 77 deletions(-) delete mode 100644 js/scenes/classUse2.js create mode 100644 js/scenes/master2.js create mode 100644 js/scenes/textHW.js diff --git a/js/scenes/classUse1.js b/js/scenes/classUse1.js index e1ca017..4d70b52 100644 --- a/js/scenes/classUse1.js +++ b/js/scenes/classUse1.js @@ -1,40 +1,12 @@ -/* - This scene is an example of how to use procedural texture - to animate the shape of an object. In this case the object - is a waving flag. The noise function is used to animate - the position of each vertex of the flag geometry. -*/ - -import * as cg from "../render/core/cg.js"; +import { texts } from "../util/texts.js"; export const init = async model => { - - // DEFINE A NEW TERRAIN OBJECT TYPE AS A 30x20 GRID. - - clay.defineMesh('myTerrain', clay.createGrid(30, 20)); - - // LOAD A CHECKERBOARD TEXTURE FOR IT. - - model.txtrSrc(1, '../media/textures/chessboard.png'); - - // INSTANTIATE THE NEW TERRAIN OBJECT. - - let terrain = model.add('myTerrain').txtr(1); - - // MOVE THE OBJECT INTO PLACE. - - terrain.identity().move(-.4,1.5,0).scale(.4); - model.animate(() => { - - // SIMULATE THE APPEARANCE OF A BILLOWING FLAG. - - terrain.setVertices((u,v) => { - return [ 3*u, - 2*v-1, - .4 * u * cg.noise(3*u-model.time,3*v,model.time) - ]; - }); + let myText = clay.text(texts[0],true); + while (model.nChildren()) + model.remove(0); + model.add('square').move(0,1.5,-.001).scale(.35).color(0,0,0).opacity(.8); + model.add(myText).move(-.3,1.8 ,0).color(1,1,1).scale(1); + model.add(myText).move(-.1,1.45,0).color(1,1,1).scale(.1); }); } - diff --git a/js/scenes/classUse2.js b/js/scenes/classUse2.js deleted file mode 100644 index e1ca017..0000000 --- a/js/scenes/classUse2.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - This scene is an example of how to use procedural texture - to animate the shape of an object. In this case the object - is a waving flag. The noise function is used to animate - the position of each vertex of the flag geometry. -*/ - -import * as cg from "../render/core/cg.js"; - -export const init = async model => { - - // DEFINE A NEW TERRAIN OBJECT TYPE AS A 30x20 GRID. - - clay.defineMesh('myTerrain', clay.createGrid(30, 20)); - - // LOAD A CHECKERBOARD TEXTURE FOR IT. - - model.txtrSrc(1, '../media/textures/chessboard.png'); - - // INSTANTIATE THE NEW TERRAIN OBJECT. - - let terrain = model.add('myTerrain').txtr(1); - - // MOVE THE OBJECT INTO PLACE. - - terrain.identity().move(-.4,1.5,0).scale(.4); - - model.animate(() => { - - // SIMULATE THE APPEARANCE OF A BILLOWING FLAG. - - terrain.setVertices((u,v) => { - return [ 3*u, - 2*v-1, - .4 * u * cg.noise(3*u-model.time,3*v,model.time) - ]; - }); - }); -} - diff --git a/js/scenes/master2.js b/js/scenes/master2.js new file mode 100644 index 0000000..f582c93 --- /dev/null +++ b/js/scenes/master2.js @@ -0,0 +1,112 @@ + +// MASTER-OWNED MULTIPLAYER COMET: +// - CONTROL GOES TO THE MOST RECENT TRIGGER PRESS (master2 pattern). +// - COLOR FOLLOWS LEFT/RIGHT HAND INPUT (multiplayer1 pattern). +// - EVERYONE SEES THE SAME FADING TRAIL. + +window.sharedState = { + time: 0, + pos: [0, 1.5, 0], + color: [1, 0.2, 0.2], + pulse: 0, + controller: {}, + trail: [], +}; + +const TRAIL_COUNT = 16; + +const colorForInput = (hand, id) => { + let hash = 0; + for (let i = 0; i < id.length; i++) + hash = (hash * 31 + id.charCodeAt(i)) % 997; + const n = (hash % 100) / 100; + return hand == 'left' ? [0.2 + 0.5 * n, 1, 0.2] : [0.2, 0.5 + 0.5 * n, 1]; +}; + +export const init = async model => { + let comet = model.add('sphere'); + let halo = model.add('ringY'); + let trail = []; + for (let i = 0; i < TRAIL_COUNT; i++) + trail.push(model.add('sphere')); + + inputEvents.onPress = hand => { + const id = hand + clientID; + sharedState.controller[id] = { + pos: [...inputEvents.pos(hand)], + time: sharedState.time, + color: colorForInput(hand, id), + }; + server.broadcastGlobal('sharedState'); + }; + + inputEvents.onDrag = hand => { + const id = hand + clientID; + if (sharedState.controller[id]) { + sharedState.controller[id].pos = [...inputEvents.pos(hand)]; + server.broadcastGlobal('sharedState'); + } + }; + + inputEvents.onRelease = hand => { + delete sharedState.controller[hand + clientID]; + server.broadcastGlobal('sharedState'); + }; + + model.animate(() => { + sharedState = server.synchronize('sharedState'); + + if (clientID == clients[0]) { + sharedState.time = model.time; + + let newest = null; + let newestTime = -1; + for (let id in sharedState.controller) + if (sharedState.controller[id].time > newestTime) { + newestTime = sharedState.controller[id].time; + newest = sharedState.controller[id]; + } + + if (newest) { + sharedState.pos = [...newest.pos]; + sharedState.color = [...newest.color]; + sharedState.pulse = 1; + sharedState.trail.unshift({ + pos: [...sharedState.pos], + color: [...sharedState.color], + time: sharedState.time, + }); + } else { + sharedState.pos = [ + sharedState.pos[0], + 1.45 + 0.1 * Math.sin(2 * sharedState.time), + sharedState.pos[2], + ]; + sharedState.pulse *= 0.95; + } + + while (sharedState.trail.length > TRAIL_COUNT) + sharedState.trail.pop(); + sharedState.trail = sharedState.trail.filter(p => sharedState.time - p.time < 2.5); + server.broadcastGlobal('sharedState'); + } + + const pulse = 1 + 0.25 * sharedState.pulse * Math.abs(Math.sin(10 * sharedState.time)); + comet.identity().move(sharedState.pos).scale(0.07 * pulse).color(...sharedState.color); + halo.identity().move(sharedState.pos).turnY(2 * sharedState.time).scale(0.11).color(...sharedState.color); + + for (let i = 0; i < TRAIL_COUNT; i++) { + const p = sharedState.trail[i]; + if (!p) { + trail[i].identity().scale(0); + continue; + } + const age = Math.max(0, sharedState.time - p.time); + const fade = Math.max(0, 1 - age / 2.5); + trail[i].identity() + .move(p.pos) + .scale(0.05 * fade) + .color(p.color[0] * fade, p.color[1] * fade, p.color[2] * fade); + } + }); +}; diff --git a/js/scenes/scenes.js b/js/scenes/scenes.js index 5de3d0e..a3b8580 100644 --- a/js/scenes/scenes.js +++ b/js/scenes/scenes.js @@ -35,7 +35,8 @@ export default () => { { name: "carDrive" , path: "./carDrive.js" , public: true }, { name: "campFire" , path: "./campFire.js" , public: true }, { name: "classUse1" , path: "./classUse1.js" , public: true }, - { name: "classUse2" , path: "./classUse2.js" , public: true }, + { name: "textHW" , path: "./textHW.js" , public: true }, + { name: "master2" , path: "./master2.js" , public: true } ] }; } diff --git a/js/scenes/text1.js b/js/scenes/text1.js index 8c06bb9..db5ea63 100644 --- a/js/scenes/text1.js +++ b/js/scenes/text1.js @@ -10,4 +10,3 @@ export const init = async model => { model.add(myText).move(-.1,1.45,0).color(1,1,1).scale(.1); }); } - diff --git a/js/scenes/textHW.js b/js/scenes/textHW.js new file mode 100644 index 0000000..f280885 --- /dev/null +++ b/js/scenes/textHW.js @@ -0,0 +1,61 @@ +export const init = async model => { + let words = [ + "WOW", + "PLAY", + "DANCE", + "BOUNCE", + "SPIN", + "LAUGH", + "JUMP", + "GLOW" + ]; + + let t = 0; + let speed = 0.004; + let zOffset = -2.2; + + let wordPose = (i, tt) => { + let a = 0.75 * tt + (2 * Math.PI * i) / words.length; + let r = 1.9 + 0.08 * Math.sin(1.5 * tt + i); + return { + x: r * Math.cos(a) - 0.23, + y: 2.1 + 0.1 * Math.sin(2 * tt + i) + 0.04, + z: -1.25 + r * Math.sin(a) + zOffset, + }; + }; + + model.animate(() => { + while (model.nChildren()) + model.remove(0); + + let paused = false; + if (typeof inputEvents.isPressed == "function") + paused = inputEvents.isPressed("left") || inputEvents.isPressed("right"); + + if (!paused) + t += speed; + + model.add(clay.text("TEXT PARTY")) + .move(-0.95, 1.18, -0.59+zOffset) + .scale(4.8) + .color(1, 0.2, 0.95); + + for (let i = 0; i < words.length; i++) { + let w = wordPose(i, t); + let red = 0.5 + 0.5 * Math.sin(1.4 * t + i); + let green = 0.5 + 0.5 * Math.sin(1.4 * t + i + 2.1); + let blue = 0.5 + 0.5 * Math.sin(1.4 * t + i + 4.2); + + model.add(clay.text(words[i])) + .move(w.x, w.y, w.z) + .scale(2.2) + .color(paused ? 1 : red, paused ? 0.95 : green, paused ? 0.2 : blue); + } + + let hint = paused ? "PAUSED - RELEASE TRIGGER TO RESUME" : "HOLD LEFT/RIGHT TRIGGER TO PAUSE"; + model.add(clay.text(hint)) + .move(-0.95, 0.5, -0.59+zOffset) + .scale(1.6) + .color(0.2, 1, 0.95); + }); +}; diff --git a/js/util/texts.js b/js/util/texts.js index 8eb6ca5..74cf8e5 100644 --- a/js/util/texts.js +++ b/js/util/texts.js @@ -1,4 +1,6 @@ export let texts = [ +`Hello, world! +`, ` import * as cg from "../render/core/cg.js"; import { G3 } from "../util/g3.js"; diff --git a/server/main.js b/server/main.js index 15e4426..487393d 100644 --- a/server/main.js +++ b/server/main.js @@ -1,3 +1,8 @@ +const os = require('os'); +if (!os.tmpDir) { + os.tmpDir = os.tmpdir; +} + var bodyParser = require("body-parser"); var express = require("express"); var formidable = require("formidable"); From 70dfa5a6851df70110d8627c63f1b8476fbc4c09 Mon Sep 17 00:00:00 2001 From: Cassie <95011670+cassiehuang72@users.noreply.github.com> Date: Tue, 10 Feb 2026 18:06:36 -0500 Subject: [PATCH 3/5] Update repo and add new VR scenes and textures --- js/scenes/classUse1.js | 12 ------------ js/scenes/scenes.js | 1 - 2 files changed, 13 deletions(-) delete mode 100644 js/scenes/classUse1.js diff --git a/js/scenes/classUse1.js b/js/scenes/classUse1.js deleted file mode 100644 index 4d70b52..0000000 --- a/js/scenes/classUse1.js +++ /dev/null @@ -1,12 +0,0 @@ -import { texts } from "../util/texts.js"; - -export const init = async model => { - model.animate(() => { - let myText = clay.text(texts[0],true); - while (model.nChildren()) - model.remove(0); - model.add('square').move(0,1.5,-.001).scale(.35).color(0,0,0).opacity(.8); - model.add(myText).move(-.3,1.8 ,0).color(1,1,1).scale(1); - model.add(myText).move(-.1,1.45,0).color(1,1,1).scale(.1); - }); -} diff --git a/js/scenes/scenes.js b/js/scenes/scenes.js index a3b8580..2ce31bf 100644 --- a/js/scenes/scenes.js +++ b/js/scenes/scenes.js @@ -34,7 +34,6 @@ export default () => { { name: "car" , path: "./car.js" , public: true }, { name: "carDrive" , path: "./carDrive.js" , public: true }, { name: "campFire" , path: "./campFire.js" , public: true }, - { name: "classUse1" , path: "./classUse1.js" , public: true }, { name: "textHW" , path: "./textHW.js" , public: true }, { name: "master2" , path: "./master2.js" , public: true } ] From 60a4b6c91e95617bd29a3b2fb2692508909fe4e4 Mon Sep 17 00:00:00 2001 From: Cassie <95011670+cassiehuang72@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:48:19 -0500 Subject: [PATCH 4/5] Update scenes and ignore recording folder --- .gitignore | 1 + js/scenes/scenes.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 40c89ca..454750b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ cert.cer !package.json !package-lock.json +recording/ diff --git a/js/scenes/scenes.js b/js/scenes/scenes.js index 2ce31bf..a3250e6 100644 --- a/js/scenes/scenes.js +++ b/js/scenes/scenes.js @@ -35,6 +35,11 @@ export default () => { { name: "carDrive" , path: "./carDrive.js" , public: true }, { name: "campFire" , path: "./campFire.js" , public: true }, { name: "textHW" , path: "./textHW.js" , public: true }, + { name: "master2" , path: "./master2.js" , public: true }, + { name: "car" , path: "./car.js" , public: true }, + { name: "carDrive" , path: "./carDrive.js" , public: true }, + { name: "campFire" , path: "./campFire.js" , public: true }, + { name: "textHW" , path: "./textHW.js" , public: true }, { name: "master2" , path: "./master2.js" , public: true } ] }; From 8bf32443de3daaeee7d5baade69a0a48426ab0e5 Mon Sep 17 00:00:00 2001 From: Cassie <95011670+cassiehuang72@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:45:46 -0500 Subject: [PATCH 5/5] Add spirit exercise scene --- js/scenes/spirit_exercise.js | 248 +++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 js/scenes/spirit_exercise.js diff --git a/js/scenes/spirit_exercise.js b/js/scenes/spirit_exercise.js new file mode 100644 index 0000000..e3e5b26 --- /dev/null +++ b/js/scenes/spirit_exercise.js @@ -0,0 +1,248 @@ +import * as cg from "../render/core/cg.js"; +import { loadSound, playSoundAtPosition } from "../util/positional-audio.js"; + +const TARGET_COUNT = 36; +const TRAIL_COUNT = 24; +const STRIKE_Z = -0.55; +const DESPAWN_Z = -0.15; +const SPAWN_Z = -5.6; + +const lanes = [-1.0, -0.35, 0.35, 1.0]; +const heights = [1.15, 1.5, 1.85]; + +const leftColor = [0.22, 0.9, 1.0]; +const rightColor = [1.0, 0.35, 0.25]; +const neutralColor = [0.95, 0.9, 0.35]; +const pulseColor = [0.2, 0.9, 0.5]; + +let soundBuffer = [], loadSounds = []; +for (let i = 0; i < 6; i++) + loadSounds.push(loadSound("../../media/sound/bounce/" + i + ".wav", buffer => soundBuffer[i] = buffer)); +Promise.all(loadSounds); + +const makeTarget = () => ({ + active: false, + hand: "any", + lane: 0, + level: 1, + spawnTime: 0, + life: 0, + speed: 0, + hitFlash: 0, + pos: [0, 1.5, SPAWN_Z], +}); + +const targetColor = hand => hand == "left" ? leftColor : hand == "right" ? rightColor : neutralColor; + +const choosePattern = beat => { + const phase = beat % 16; + + if (phase == 12 || phase == 13 || phase == 14) + return { step: 0.23, count: 1, speed: 3.4, mode: "burst" }; + + if (phase >= 8) + return { step: 0.36, count: 1, speed: 3.0, mode: "cross" }; + + return { step: 0.42, count: 1, speed: 2.7, mode: "flow" }; +}; + +export const init = async model => { + let beat = 0; + let nextSpawnTime = 0; + let combo = 0; + let bestCombo = 0; + let score = 0; + let hits = 0; + let misses = 0; + let coachPulse = 0; + + let targets = []; + let trails = []; + + for (let i = 0; i < TARGET_COUNT; i++) { + targets.push(makeTarget()); + model.add("sphere"); + } + + for (let i = 0; i < TRAIL_COUNT; i++) { + trails.push({ pos: [0, 1.5, -1], life: 0, color: [1, 1, 1] }); + model.add("sphere"); + } + + const leftGuide = model.add("ringZ"); + const rightGuide = model.add("ringZ"); + const pulseRing = model.add("ringY"); + + const horizon = model.add("square"); + const floor = model.add("square"); + const leftPeak = model.add("coneY"); + const rightPeak = model.add("coneY"); + const moon = model.add("sphere"); + + let addTrail = (pos, color) => { + trails.unshift({ pos: [...pos], life: 1, color: [...color] }); + trails.pop(); + }; + + let spawnTarget = (spawnAt, patternMode, speed) => { + for (let i = 0; i < TARGET_COUNT; i++) { + if (targets[i].active) + continue; + + const lane = patternMode == "cross" + ? (beat % 2 == 0 ? 0 : 3) + : 4 * Math.random() >> 0; + + const level = patternMode == "burst" + ? ((beat + i) % 3) + : (3 * Math.random() >> 0); + + let hand = "any"; + if (lane < 2) + hand = "left"; + if (lane > 1) + hand = "right"; + if (patternMode == "flow" && beat % 4 == 3) + hand = "any"; + + targets[i].active = true; + targets[i].hand = hand; + targets[i].lane = lane; + targets[i].level = level; + targets[i].spawnTime = spawnAt; + targets[i].life = 1; + targets[i].speed = speed; + targets[i].hitFlash = 0; + targets[i].pos = [lanes[lane], heights[level], SPAWN_Z]; + return; + } + }; + + let playHit = index => { + if (soundBuffer.length == 0) + return; + playSoundAtPosition(soundBuffer[index % soundBuffer.length], targets[index].pos); + }; + + model.animate(() => { + const t = model.time; + const dt = model.deltaTime; + const bpm = 132 + 16 * Math.sin(0.05 * t); + const beatDuration = 60 / bpm; + + while (t >= nextSpawnTime) { + const pattern = choosePattern(beat); + for (let i = 0; i < pattern.count; i++) + spawnTarget(nextSpawnTime + i * pattern.step, pattern.mode, pattern.speed); + + nextSpawnTime += beatDuration; + beat++; + coachPulse = 1; + } + + const leftHand = clientState.finger(clientID, "left", 1); + const rightHand = clientState.finger(clientID, "right", 1); + + if (Array.isArray(leftHand)) + addTrail(leftHand, leftColor); + if (Array.isArray(rightHand)) + addTrail(rightHand, rightColor); + + coachPulse *= 0.93; + + for (let i = 0; i < TARGET_COUNT; i++) { + const targetNode = model.child(i); + const target = targets[i]; + + if (!target.active) { + targetNode.identity().scale(0); + continue; + } + + target.pos[2] += target.speed * dt; + target.hitFlash *= 0.86; + + let gotHit = false; + const hitRadius = 0.28; + + if (Array.isArray(leftHand) && target.hand != "right" && cg.distance(leftHand, target.pos) < hitRadius) + gotHit = true; + + if (Array.isArray(rightHand) && target.hand != "left" && cg.distance(rightHand, target.pos) < hitRadius) + gotHit = true; + + if (gotHit) { + target.hitFlash = 1; + target.active = false; + combo++; + hits++; + score += 10 + combo; + bestCombo = Math.max(bestCombo, combo); + coachPulse = 1; + playHit(i); + continue; + } + + if (target.pos[2] > DESPAWN_Z) { + target.active = false; + misses++; + combo = 0; + continue; + } + + const c = targetColor(target.hand); + const glow = 0.2 + 0.25 * Math.sin(14 * t + i); + const depthScale = 1.05 + 0.8 * (target.pos[2] - STRIKE_Z) / (SPAWN_Z - STRIKE_Z); + + targetNode.identity() + .move(target.pos) + .scale(0.13 * depthScale) + .color(c[0] + glow, c[1] + glow, c[2] + glow); + } + + for (let i = 0; i < TRAIL_COUNT; i++) { + const n = model.child(TARGET_COUNT + i); + const tr = trails[i]; + tr.life *= 0.9; + + if (tr.life < 0.06) { + n.identity().scale(0); + continue; + } + + n.identity().move(tr.pos).scale(0.04 * tr.life) + .color(tr.color[0] * tr.life, tr.color[1] * tr.life, tr.color[2] * tr.life); + } + + const leftGuidePos = Array.isArray(leftHand) ? leftHand : [-0.45, 1.4, -0.55]; + const rightGuidePos = Array.isArray(rightHand) ? rightHand : [0.45, 1.4, -0.55]; + + leftGuide.identity().move(leftGuidePos).scale(0.1 + 0.03 * Math.sin(9 * t)).color(...leftColor); + rightGuide.identity().move(rightGuidePos).scale(0.1 + 0.03 * Math.sin(9 * t + 1)).color(...rightColor); + + pulseRing.identity().move(0, 1.5, STRIKE_Z - 0.02) + .turnY(2 * t) + .scale(1.0 + 0.45 * coachPulse) + .color(pulseColor[0], pulseColor[1], pulseColor[2]); + + horizon.identity().move(0, 2.0, -7.5).scale(9.5, 4.8, 1).color(0.06, 0.1, 0.2); + floor.identity().move(0, 0.72, -2.8).turnX(-Math.PI / 2).scale(3.2, 3.2, 1).color(0.04, 0.06, 0.12); + leftPeak.identity().move(-4.8, 0.8, -8.0).scale(1.8, 3.2, 1.8).color(0.08, 0.14, 0.2); + rightPeak.identity().move(4.8, 0.82, -8.1).scale(2.1, 3.4, 2.1).color(0.09, 0.12, 0.22); + moon.identity().move(2.4, 3.1, -7.0).scale(0.32).color(0.85, 0.95, 1.0); + + const total = hits + misses; + const accuracy = total > 0 ? Math.floor(100 * hits / total) : 100; + + while (model.nChildren() > TARGET_COUNT + TRAIL_COUNT + 8) + model.remove(TARGET_COUNT + TRAIL_COUNT + 8); + + model.add(clay.text("RHYTHM CARDIO")).move(-1.05, 2.52, -1.85).scale(1.95).color(0.9, 1.0, 1.0); + model.add(clay.text("SCORE " + score)).move(-1.05, 2.30, -1.85).scale(1.2).color(0.85, 0.95, 1.0); + model.add(clay.text("COMBO " + combo + " BEST " + bestCombo)).move(-1.05, 2.13, -1.85).scale(1.08).color(1.0, 0.95, 0.5); + model.add(clay.text("ACCURACY " + accuracy + "%")) + .move(-1.05, 1.96, -1.85).scale(1.08).color(0.5, 1.0, 0.8); + model.add(clay.text("STRIKE IN TIME WITH THE BEAT")) + .move(-1.05, 1.73, -1.85).scale(0.92).color(0.65, 0.85, 1.0); + }); +};