Skip to content

Commit

Permalink
Merge pull request #17 from concord-consortium/188086293-tilt-slider
Browse files Browse the repository at this point in the history
Add Tilt Slider
  • Loading branch information
bacalj authored Aug 8, 2024
2 parents a12e81a + bc4b870 commit cfb15c3
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Header } from "./header";
import "../assets/scss/App.scss";

export const App: React.FC = () => {
const [activeTab, setActiveTab] = useState<"location" | "simulation">("location");
const [activeTab, setActiveTab] = useState<"location" | "simulation">("simulation");
const [latitude, setLatitude] = useState("");
const [longitude, setLongitude] = useState("");
const [dayOfYear, setDayOfYear] = useState<string>("280");
Expand Down
2 changes: 1 addition & 1 deletion src/grasp-seasons/3d-models/common-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default {

sun (params: IModelParams) {
const texture = new THREE.TextureLoader().load(SunPNG);
const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false });
const material = new THREE.SpriteMaterial({ map: texture, transparent: true });
const sprite = new THREE.Sprite(material);
sprite.renderOrder = 1;
sprite.scale.set(100000000 * c.SF, 100000000 * c.SF, 1);
Expand Down
4 changes: 4 additions & 0 deletions src/grasp-seasons/3d-views/base-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export default class BaseView {
this.controls.enablePan = false;
this.controls.enableZoom = false;
this.controls.rotateSpeed = 0.5;
// Very important: 0 degrees would place the camera in a position where it is impossible to determine
// the desired direction of the camera tilt action.
this.controls.minPolarAngle = THREE.MathUtils.degToRad(1);
this.controls.maxPolarAngle = THREE.MathUtils.degToRad(179);

this.dispatch = new EventEmitter();

Expand Down
65 changes: 54 additions & 11 deletions src/grasp-seasons/3d-views/orbit-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const DEF_PROPERTIES = {
sunEarthLine: true
};

const CAMERA_TWEEN_LENGTH = 1000;
const CAMERA_TWEEN_LENGTH = 1500;

// Cubic Bezier function
function cubicBezier(t: number, p0: number, p1: number, p2: number, p3: number): number {
Expand Down Expand Up @@ -48,20 +48,52 @@ export default class OrbitView extends BaseView {
constructor(parentEl: HTMLElement, props = DEF_PROPERTIES) {
super(parentEl, props, "orbit-view");
this.registerInteractionHandler(this.earthDraggingInteraction);

this.controls.addEventListener("change", () => {
this.dispatch.emit("props.change", { cameraTiltAngle: this.getCameraTiltAngle() });
});
}

setViewAxis(vec3: THREE.Vector3) {
this.cameraSymbol.lookAt(vec3);
this.cameraSymbol.rotateX(Math.PI * 0.5);
}

getCameraAngle() {
const refVec = this.camera.position.clone().setY(0);
let angle = this.camera.position.angleTo(refVec) * 180 / Math.PI;
getCameraTiltAngle() {
const targetPos = this.controls.target.clone();
const refVec = this.camera.position.clone().sub(targetPos).setY(0);
let angle = this.camera.position.clone().sub(targetPos).angleTo(refVec);
if (this.camera.position.y < 0) angle *= -1;
return angle;
const angleInDeg = angle * 180 / Math.PI;
return angleInDeg;
}

setCameraTiltAngle(angleInDeg: number) {
const angleInRad = angleInDeg * Math.PI / 180;

const targetPos = this.controls.target.clone();
const cameraToTarget = this.camera.position.clone().sub(targetPos);
const cameraToTargetLen = cameraToTarget.length();

// Calculate reference vector with zero y-component
const refVec = cameraToTarget.clone().setY(0);

// Calculate the axis of rotation (cross product of the Y-axis and refVec)
const axisOfRotation = new THREE.Vector3(0, 1, 0).cross(refVec).normalize();

// Rotate the reference vector to achieve the desired tilt angle
const newPos = refVec.applyAxisAngle(axisOfRotation, -angleInRad);

// Set the length of the new position vector to the original distance from target
newPos.setLength(cameraToTargetLen);

// Translate the new position back to the world coordinates
newPos.add(targetPos);

// Update the camera's position
this.camera.position.copy(newPos);
}

getEarthPosition() {
const vector = this.earth.posObject.position.clone();

Expand All @@ -83,7 +115,7 @@ export default class OrbitView extends BaseView {
}

getCloseUpCameraPosition() {
const cameraOffset = new THREE.Vector3(0, 0, 40000000 / data.SCALE_FACTOR);
const cameraOffset = new THREE.Vector3(0, 1000000 / data.SCALE_FACTOR, 40000000 / data.SCALE_FACTOR);
return this.earth.posObject.position.clone().add(cameraOffset);
}

Expand All @@ -110,6 +142,7 @@ export default class OrbitView extends BaseView {

this.camera.position.lerpVectors(this.startingCameraPos, this.desiredCameraPos, progressEased);
this.controls.target.lerpVectors(this.startingCameraLookAt, this.desiredCameraLookAt, progressEased);

if (progress === 1) {
this.startingCameraPos = undefined;
this.desiredCameraPos = undefined;
Expand All @@ -121,7 +154,7 @@ export default class OrbitView extends BaseView {
}

getInitialCameraPosition() {
return new THREE.Vector3(0, 440000000 / data.SCALE_FACTOR, 0);
return new THREE.Vector3(0, 440000000 / data.SCALE_FACTOR, 10);
}

_setInitialCamPos() {
Expand Down Expand Up @@ -174,15 +207,25 @@ export default class OrbitView extends BaseView {
}
}

_updateCameraTiltAngle() {
if (this.desiredCameraPos || this.desiredCameraLookAt) {
// Do not try to update camera tilt angle while transitioning camera position
return;
}
this.setCameraTiltAngle(this.props.cameraTiltAngle ?? 0);
}

_updateDay(): void {
const oldEarthPosition = this.earth.posObject.position.clone();
super._updateDay();
if (this.props.earthCloseUpView) {
const positionDiff = this.earth.posObject.position.clone().sub(oldEarthPosition);
if (this.desiredCameraPos && this.desiredCameraLookAt) {
this.desiredCameraPos.copy(this.getCloseUpCameraPosition());
this.desiredCameraLookAt.copy(this.earth.posObject.position);
this.desiredCameraPos.add(positionDiff);
this.desiredCameraLookAt.add(positionDiff);
} else {
this.camera.position.copy(this.getCloseUpCameraPosition());
this.controls.target.copy(this.earth.posObject.position);
this.camera.position.add(positionDiff);
this.controls.target.add(positionDiff);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/grasp-seasons/components/orbit-view-comp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ export default class OrbitViewComp extends CanvasView<IProps> {

_setupLogging() {
this.externalView.on("camera.changeStart", () => {
this._startAngle = this.externalView.getCameraAngle();
this._startAngle = this.externalView.getCameraTiltAngle();
});
this.externalView.on("camera.changeEnd", () => {
this.props.log?.("OrbitViewAngleChanged", {
value: this.externalView.getCameraAngle(),
value: this.externalView.getCameraTiltAngle(),
prevValue: this._startAngle
});
});
Expand Down
18 changes: 18 additions & 0 deletions src/grasp-seasons/components/seasons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ body {
}
}

.tilt-slider {
position: absolute;
bottom: 10px;
right: 10px;
width: 36px;
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;

.slider-container {
height: 90px;
margin-top: 20px;
margin-bottom: 20px;
}
}

.view-type-dropdown {
position: absolute;
top: 10px;
Expand Down
26 changes: 24 additions & 2 deletions src/grasp-seasons/components/seasons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ const DEFAULT_SIM_STATE: ISimState = {
earthTilt: true,
earthRotation: 1.539,
sunEarthLine: true,
lat: 40.11,
long: -88.2,
lat: 0,
long: 0,
sunrayColor: "#D8D8AC",
groundColor: "#4C7F19", // 'auto' will make color different for each season
sunrayDistMarker: false,
Expand All @@ -36,6 +36,7 @@ const DEFAULT_SIM_STATE: ISimState = {
// camera follows Earth's position but does not rotate. As the year passes, we'll see different parts of Earth,
// including its night side. This is useful for keeping the Earth's axis constant.
earthCloseUpView: false,
cameraTiltAngle: 89
};

function capitalize(string: string) {
Expand Down Expand Up @@ -194,6 +195,10 @@ const Seasons: React.FC<IProps> = ({ lang = "en_us", initialState = {}, log = (a
setLocationSearch("");
};

const handleTiltSliderChange = (event: any, ui: any) => {
setSimState(prevState => ({ ...prevState, cameraTiltAngle: ui.value }));
};

const handleMyLocationChange = (lat: number, long: number, name: string) => {
const rot = -long * Math.PI / 180;
setSimState(prevState => ({ ...prevState, lat, long, earthRotation: rot }));
Expand Down Expand Up @@ -242,6 +247,23 @@ const Seasons: React.FC<IProps> = ({ lang = "en_us", initialState = {}, log = (a
{ t("~DAILY_ROTATION", simLang) }
</label>
</div>
<div className="tilt-slider">
<label>{ t("~TILT_VIEW", simLang) }</label>
<div className="slider-container">
<Slider
orientation="vertical"
value={simState.cameraTiltAngle}
min={0}
// Very important: 90 degrees would place the camera in a position where it is impossible to determine
// the desired direction of the camera tilt action.
max={89}
step={1}
slide={handleTiltSliderChange}
log={log}
logId="Tilt"
/>
</div>
</div>
</div>
<div className="day">
<label>{ t("~DAY", simLang) }:</label>
Expand Down
12 changes: 12 additions & 0 deletions src/grasp-seasons/components/slider/slider.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,16 @@
.ui-slider-tick-label {
margin-top: 15px;
}

&.ui-slider-vertical {
width: 8px;
height: 100%;
margin-top: 0;
margin-bottom: 0;

.ui-slider-handle {
margin-left: -10px;
margin-bottom: -15px;
}
}
}
1 change: 1 addition & 0 deletions src/grasp-seasons/components/slider/slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface IProps {
log: ((action: string, data: any) => void) | null;
start?: (event: any, ui: any) => void;
stop?: (event: any, ui: any) => void;
orientation?: "horizontal" | "vertical";
}
interface IOptions extends IProps {
_slideStart: number;
Expand Down
1 change: 1 addition & 0 deletions src/grasp-seasons/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface ISimState {
// camera follows Earth's position but does not rotate. As the year passes, we'll see different parts of Earth,
// including its night side. This is useful for keeping the Earth's axis constant.
earthCloseUpView: boolean;
cameraTiltAngle: number;
}

export type ModelType = "earth-view" | "orbit-view" | "unknown";
Expand Down

0 comments on commit cfb15c3

Please sign in to comment.